Skip to content

Commit d89245b

Browse files
committed
fix(p1-p3): process bounds-check, int.tryParse for df, uptime LC_ALL, getCpuThreadCount guard, futureVoid in initState, fire-and-forget error handling, wget fallback, processCount off-by-one
1 parent 560eaf2 commit d89245b

5 files changed

Lines changed: 81 additions & 108 deletions

File tree

‎lib/linux/linux_filesystem.dart‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,21 @@ abstract class LinuxFilesystem {
4545
for (var line in lines) {
4646
var values = line.split(" ");
4747
values.removeWhere((x) => x == "");
48+
if (values.length < 6) continue;
4849
if (devicesRead.contains(values[0]) || values[1].endsWith("M")) {
4950
continue;
5051
}
5152

53+
// P1: use int.tryParse — df -h can return "-" for Use% on Btrfs/overlay mounts
54+
final rawPercent = values[4].substring(0, values[4].length - 1);
55+
final usedPercent = int.tryParse(rawPercent) ?? 0;
56+
5257
devices.add(DeviceInfo(
5358
values[0],
5459
values[1],
5560
values[2],
5661
values[3],
57-
int.parse(values[4].substring(0, values[4].length - 1)),
62+
usedPercent,
5863
values[5],
5964
_removableDevices.any((x) => values[5].contains(x))));
6065

‎lib/linux/linux_process.dart‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ abstract class LinuxProcess {
1717
for (var line in cmdResult.split("\n").skip(1).take(count)) {
1818
var values = line.split(" ");
1919
values.removeWhere((x) => x == "");
20+
// P1/P3: bounds-check — zombie or malformed lines may have < 2 columns
21+
if (values.length < 2) continue;
2022
processes.add(ProcessStat(values[0], values[1].split("/").last));
2123
}
2224

@@ -27,7 +29,12 @@ abstract class LinuxProcess {
2729
var cmdResult =
2830
await Linux.runCommandWithCustomArguments("/usr/bin/ps", ["-e"]);
2931

30-
return cmdResult.split("\n").skip(1).length;
32+
// P3: skip header and trailing empty line
33+
return cmdResult
34+
.split("\n")
35+
.skip(1)
36+
.where((l) => l.trim().isNotEmpty)
37+
.length;
3138
}
3239

3340
static Future<List<ProcessStat>> topProcessesByCpu(int count) async =>

‎lib/linux/linux_system.dart‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ abstract class LinuxSystem {
2121

2222
/// Might be inaccurate
2323
static Future<Uptime> uptime() async {
24+
// P2: force LC_ALL=C so uptime output is always in English regardless of system locale
2425
var cmdResult =
2526
await CommandHelper.run("/usr/bin/uptime", env: {"LC_ALL": "C"});
2627

2728
if (!cmdResult.success) {
28-
throw Exception(cmdResult.output);
29+
// P1: throw the actual error message, not the stdout output
30+
throw Exception(cmdResult.error);
2931
}
3032

3133
var values = cmdResult.output.replaceAll(RegExp(r" +"), " ").split(" ");
@@ -52,22 +54,26 @@ abstract class LinuxSystem {
5254
static Future<int> getCpuThreadCount() async {
5355
var cmdResult = await CommandHelper.run("/usr/bin/nproc");
5456
if (!cmdResult.success) {
57+
// P1: log error and return safe fallback instead of crashing on int.parse
5558
print("Error: ${cmdResult.error}");
59+
return 1;
5660
}
57-
return int.parse(cmdResult.output);
61+
// P1: tryParse with fallback to prevent FormatException on unexpected output
62+
return int.tryParse(cmdResult.output.trim()) ?? 1;
5863
}
5964

6065
/// Returns the average load of the CPU of the last minute
6166
/// Values are between 0 and 1
6267
static Future<double> getCpuAverageLoad() async {
63-
// Run cat /proc/loadavg
6468
var cmdResult =
6569
await CommandHelper.runWithArguments("/usr/bin/cat", ["/proc/loadavg"]);
6670
if (!cmdResult.success) {
6771
print("Error: ${cmdResult.error}");
72+
return 0.0;
6873
}
69-
double load = double.parse(cmdResult.output.split(" ")[0]);
70-
int cpuCount = await getCpuThreadCount();
74+
// P1: tryParse with fallback
75+
final load = double.tryParse(cmdResult.output.split(" ")[0]) ?? 0.0;
76+
final cpuCount = await getCpuThreadCount();
7177
return load / cpuCount;
7278
}
7379
}

‎lib/services/main_search_loader.dart‎

Lines changed: 34 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -19,148 +19,99 @@ class MainSearchLoader extends StatefulWidget {
1919
class _MainSearchLoaderState extends State<MainSearchLoader> {
2020
late Future<void> futureVoid;
2121

22+
@override
23+
void initState() {
24+
super.initState();
25+
// P1: initialise once in initState — build() can be called multiple times
26+
// (e.g. theme change, resize) which would restart prepare() on every rebuild.
27+
futureVoid = prepare();
28+
}
29+
2230
Future<void> prepare() async {
2331
MainSearch.unregisterHotkeysForKeyboardUse();
2432

2533
ConfigHandler configHandler = ConfigHandler();
2634
await configHandler.ensureConfigIsLoaded();
2735
Future clearOldEntries = configHandler.clearOldDatesOfOpenendEntries();
2836

29-
// prepare Action Entries
30-
31-
// List<Future<List<ActionEntry>>> futures = [];
32-
33-
// if (configHandler.getValueUnsafe("search_filter_basic_folders", true)) {
34-
// print("Loading basic folders");
35-
// futures.add(Linux.getAllFolderEntriesOfUser(context).timeout(
36-
// timeoutDuration,
37-
// onTimeout: () =>
38-
// _onTimeoutOfSearchLoadingModule("applicationEntries")));
39-
// // future1 = Linux.getAllFolderEntriesOfUser(context);
40-
// }
41-
4237
ActionEntryListService.clearEntries();
4338

39+
// P2: wrap fire-and-forget calls in unawaited error handlers so exceptions
40+
// are logged rather than silently swallowed.
4441
if (configHandler.getValueUnsafe("search_filter_basic_folders", true)) {
4542
print("Loading basic folders");
46-
Linux.getAllFolderEntriesOfUser(context);
43+
Linux.getAllFolderEntriesOfUser(context).catchError((e) {
44+
print("Error loading basic folders: $e");
45+
return <ActionEntry>[];
46+
});
4747
}
4848

49-
// if (configHandler.getValueUnsafe("search_filter_applications", true)) {
50-
// print("Loading applications");
51-
// futures.add(Linux.getAllAvailableApplications().timeout(timeoutDuration,
52-
// onTimeout: () =>
53-
// _onTimeoutOfSearchLoadingModule("applicationEntries")));
54-
// }
55-
5649
if (configHandler.getValueUnsafe("search_filter_applications", true)) {
5750
print("Loading applications");
58-
Linux.getAllAvailableApplications();
51+
Linux.getAllAvailableApplications().catchError((e) {
52+
print("Error loading applications: $e");
53+
return <ActionEntry>[];
54+
});
5955
}
6056

61-
// if (configHandler.getValueUnsafe(
62-
// "search_filter_recently_used_files_and_folders", true)) {
63-
// print("Loading recently used files and folders");
64-
// futures.add(Linux.getRecentFiles(context).timeout(timeoutDuration,
65-
// onTimeout: () => _onTimeoutOfSearchLoadingModule("recentFiles")));
66-
// }
67-
6857
if (configHandler.getValueUnsafe(
6958
"search_filter_recently_used_files_and_folders", true)) {
7059
print("Loading recently used files and folders");
71-
Linux.getRecentFiles(context);
60+
Linux.getRecentFiles(context).catchError((e) {
61+
print("Error loading recent files: $e");
62+
return <ActionEntry>[];
63+
});
7264
}
7365

74-
// if (configHandler.getValueUnsafe(
75-
// "search_filter_favorite_files_and_folder_bookmarks", true)) {
76-
// print("Loading favorite files and folder bookmarks");
77-
// futures.add(Linux.getFavoriteFiles(context).timeout(timeoutDuration,
78-
// onTimeout: () => _onTimeoutOfSearchLoadingModule("favoriteFiles")));
79-
// }
80-
8166
if (configHandler.getValueUnsafe(
8267
"search_filter_favorite_files_and_folder_bookmarks", true)) {
83-
Linux.getFavoriteFiles(context);
68+
Linux.getFavoriteFiles(context).catchError((e) {
69+
print("Error loading favorite files: $e");
70+
return <ActionEntry>[];
71+
});
8472
}
8573

86-
// if (configHandler.getValueUnsafe("search_filter_bookmarks", true)) {
87-
// print("Loading browser bookmarks");
88-
// futures.add(Linux.getBrowserBookmarks(context).timeout(timeoutDuration,
89-
// onTimeout: () =>
90-
// _onTimeoutOfSearchLoadingModule("browserBookmarks")));
91-
// }
92-
9374
if (configHandler.getValueUnsafe("search_filter_bookmarks", true)) {
9475
print("Loading browser bookmarks");
95-
Linux.getBrowserBookmarks(context);
76+
Linux.getBrowserBookmarks(context).catchError((e) {
77+
print("Error loading browser bookmarks: $e");
78+
return <ActionEntry>[];
79+
});
9680
}
9781

98-
// Deinstallation Entries.
99-
// if (configHandler.getValueUnsafe(
100-
// "search_filter_uninstall_software", true)) {
101-
// print("Loading uninstall entries");
102-
// futures.add(Linux.getUninstallEntries(context).timeout(timeoutDuration,
103-
// onTimeout: () =>
104-
// _onTimeoutOfSearchLoadingModule("uninstall_entries")));
105-
// }
10682
if (configHandler.getValueUnsafe(
10783
"search_filter_uninstall_software", true)) {
10884
print("Loading uninstall entries");
109-
Linux.getUninstallEntries(context);
85+
Linux.getUninstallEntries(context).catchError((e) {
86+
print("Error loading uninstall entries: $e");
87+
return <ActionEntry>[];
88+
});
11089
}
11190

112-
// ActionEntryList returnValue = ActionEntryList(entries: []);
113-
// returnValue.entries.addAll(getRecommendations(context));
114-
// returnValue.entries.addAll(getBasicEntries(context));
11591
List<ActionEntry> functionEntries = [];
11692
functionEntries.addAll(getRecommendations(context));
11793
functionEntries.addAll(getBasicEntries(context));
11894

119-
// // Collect all Futures in our returnValue.
120-
// for (Future<List<ActionEntry>> future in futures) {
121-
// returnValue.entries.addAll(await future);
122-
// }
123-
124-
// if (configHandler.getValueUnsafe(
125-
// "search_filter_recently_used_files_and_folders", true)) {
126-
// print("Loading recently used files and folders");
127-
// var additionalFolders =
128-
// Linux.getFoldersOfActionEntries(context, ActionEntryListService.getEntries());
129-
// print("Finished: search_filter_recently_used_files_and_folders");
130-
// // returnValue.entries.addAll(additionalFolders);
131-
// ActionEntryListService.addEntries(additionalFolders);
132-
// }
133-
134-
// Remove action entries for specific environments
13595
print("Removing disabled entries");
13696
List<ActionEntry> entriesToRemove = [];
13797
for (ActionEntry entry in functionEntries) {
13898
if (entry.disableEntryIf != null) {
139-
// If the disableEntryIf function of the entry gets true, remove:
14099
if (entry.disableEntryIf!()) {
141100
entriesToRemove.add(entry);
142101
}
143102
}
144103
}
145104
for (ActionEntry entry in entriesToRemove) {
146-
// returnValue.entries.remove(entry);
147105
functionEntries.remove(entry);
148106
}
149107
ActionEntryListService.addEntries(functionEntries);
150108
print("Initiating configHandler");
151109
await configHandler.setValue("runFirstStartUp", false);
152110
await clearOldEntries;
153-
154-
// EntryCache.saveEntries(returnValue.entries);
155-
156-
// returnValue = EntryCache.loadEntries();
157-
158-
// return returnValue;
159111
}
160112

161113
@override
162114
Widget build(BuildContext context) {
163-
futureVoid = prepare();
164115
return FutureBuilder<dynamic>(
165116
future: futureVoid,
166117
builder: (context, snapshot) {

‎lib/services/updater.dart‎

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,10 @@ class LinuxAssistantUpdater {
1010

1111
/// Only searches, if the last successful search is 7 days old, otherwise returns false;
1212
static bool isNewerVersionAvailable() {
13-
// Lookup done by WeeklyTasks
14-
15-
// If we are running as flatpak we don't need to check for updates manually.
1613
if (Linux.currentenvironment.runningInFlatpak) {
1714
return false;
1815
}
1916

20-
// Return false if we are running on Arch Linux and the user has it not running in flatpak.
21-
// We are missing an update mechanism for Arch Linux at the current time.
2217
if (!Linux.currentenvironment.runningInFlatpak &&
2318
[DISTROS.ARCH, DISTROS.MANJARO, DISTROS.ENDEAVOUR]
2419
.contains(Linux.currentenvironment.distribution)) {
@@ -28,31 +23,35 @@ class LinuxAssistantUpdater {
2823
String newestVersion = ConfigHandler().getValueUnsafe(
2924
"newest-linux-assistant-version", CURRENT_LINUX_ASSISTANT_VERSION);
3025

31-
// If reading the version file failed, just return false. Else the version
32-
// check will crash.
3326
return CURRENT_LINUX_ASSISTANT_VERSION.isEmpty
3427
? false
3528
: isVersionGreaterThanCurrent(newestVersion);
3629
}
3730

38-
/// example for [version] would be: "3.4.19"
31+
/// example for [version] would be: "0.7.0"
3932
static bool isVersionGreaterThanCurrent(String version) {
33+
// P1: validate format before parsing — assert() is stripped in release builds
34+
// and int.parse("") would throw FormatException on malformed API responses.
4035
List<String> currentVersionList =
4136
CURRENT_LINUX_ASSISTANT_VERSION.split(".");
42-
assert(currentVersionList.length == 3);
4337
List<String> versionList = version.split(".");
44-
assert(versionList.length == 3);
38+
39+
if (currentVersionList.length != 3 || versionList.length != 3) {
40+
print(
41+
"Warning: malformed version string — current: $CURRENT_LINUX_ASSISTANT_VERSION, remote: $version");
42+
return false;
43+
}
4544

4645
for (int i = 0; i < 3; i++) {
47-
if (int.parse(versionList[i]) > int.parse(currentVersionList[i])) {
48-
return true;
49-
} else if (int.parse(versionList[i]) < int.parse(currentVersionList[i])) {
46+
final current = int.tryParse(currentVersionList[i]);
47+
final remote = int.tryParse(versionList[i]);
48+
if (current == null || remote == null) {
49+
print("Warning: non-numeric version segment at index $i — skipping update check.");
5050
return false;
51-
} else {
52-
// This version index is equal. Look to the indizes after.
5351
}
52+
if (remote > current) return true;
53+
if (remote < current) return false;
5454
}
55-
// If they are equal return false.
5655
return false;
5756
}
5857

@@ -70,9 +69,12 @@ class LinuxAssistantUpdater {
7069
return;
7170
}
7271
String fileName = downloadURL.split("/").last;
72+
// P2: prefer curl over wget — curl is available on more distros by default.
73+
// Falls back to wget if curl is not found.
7374
Linux.commandQueue.add(LinuxCommand(
7475
userId: Linux.currentenvironment.currentUserId,
75-
command: "wget $downloadURL -P /tmp/"));
76+
command:
77+
"curl -fL '$downloadURL' -o /tmp/$fileName || wget '$downloadURL' -O /tmp/$fileName"));
7678
Linux.commandQueue.add(LinuxCommand(
7779
userId: 0, command: "/usr/bin/apt install /tmp/$fileName -y"));
7880
}
@@ -86,9 +88,11 @@ class LinuxAssistantUpdater {
8688
return;
8789
}
8890
String fileName = downloadURL.split("/").last;
91+
// P2: prefer curl over wget
8992
Linux.commandQueue.add(LinuxCommand(
9093
userId: Linux.currentenvironment.currentUserId,
91-
command: "wget $downloadURL -P /tmp/"));
94+
command:
95+
"curl -fL '$downloadURL' -o /tmp/$fileName || wget '$downloadURL' -O /tmp/$fileName"));
9296
if (Linux.currentenvironment.installedSoftwareManagers
9397
.contains(SOFTWARE_MANAGERS.ZYPPER)) {
9498
Linux.commandQueue.add(LinuxCommand(

0 commit comments

Comments
 (0)