Skip to content

Commit 3e9f7a0

Browse files
committed
Enhance performance
1 parent cd1d096 commit 3e9f7a0

File tree

2 files changed

+85
-43
lines changed

2 files changed

+85
-43
lines changed

Main.cs

Lines changed: 84 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Diagnostics;
45
using System.Linq;
@@ -13,37 +14,52 @@ namespace Flow.Launcher.Plugin.AppUpgrader
1314
public class AppUpgrader : IAsyncPlugin
1415
{
1516
internal PluginInitContext Context;
16-
private List<UpgradableApp> upgradableApps;
17-
17+
private ConcurrentBag<UpgradableApp> upgradableApps;
18+
private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1);
19+
private DateTime _lastRefreshTime = DateTime.MinValue;
20+
private const int CACHE_EXPIRATION_MINUTES = 15;
21+
private const int COMMAND_TIMEOUT_SECONDS = 10;
22+
private static readonly Regex AppLineRegex = new Regex(@"^(.+?)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$",
23+
RegexOptions.Compiled | RegexOptions.IgnoreCase);
1824
public Task InitAsync(PluginInitContext context)
1925
{
2026
Context = context;
27+
Task.Run(async () =>
28+
{
29+
try
30+
{
31+
await RefreshUpgradableAppsAsync();
32+
}
33+
catch (Exception ex) { }
34+
});
35+
36+
ThreadPool.SetMinThreads(Environment.ProcessorCount * 2, Environment.ProcessorCount * 2);
2137
return Task.CompletedTask;
2238
}
2339

2440
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
2541
{
26-
var results = new List<Result>();
27-
try
28-
{
29-
upgradableApps = GetUpgradableAppsAsync().GetAwaiter().GetResult();
30-
}
31-
catch (Exception ex)
42+
if (ShouldRefreshCache())
3243
{
33-
return results;
44+
await RefreshUpgradableAppsAsync();
3445
}
3546

36-
await Task.Yield();
37-
string keyword = query.FirstSearch.Trim().ToLower();
38-
3947
if (upgradableApps == null || !upgradableApps.Any())
4048
{
41-
return results;
49+
return new List<Result>
50+
{
51+
new Result
52+
{
53+
Title = "No updates available",
54+
SubTitle = "All applications are up-to-date.",
55+
IcoPath = "Images\\app.png"
56+
}
57+
};
4258
}
4359

44-
foreach (var app in upgradableApps.ToList())
45-
{
46-
results.Add(new Result
60+
return upgradableApps.AsParallel()
61+
.WithDegreeOfParallelism(Environment.ProcessorCount)
62+
.Select(app => new Result
4763
{
4864
Title = $"Upgrade {app.Name}",
4965
SubTitle = $"From {app.Version} to {app.AvailableVersion}",
@@ -55,29 +71,60 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
5571
{
5672
await PerformUpgradeAsync(app);
5773
}
58-
catch (Exception ex){}
59-
}, token);
74+
catch (Exception ex)
75+
{
76+
Context.API.ShowMsg($"Upgrade failed: {ex.Message}");
77+
}
78+
});
6079
return true;
6180
},
6281
IcoPath = "Images\\app.png"
63-
});
64-
}
82+
}).ToList();
83+
}
6584

66-
return results;
85+
86+
private bool ShouldRefreshCache()
87+
{
88+
return upgradableApps == null ||
89+
DateTime.Now - _lastRefreshTime > TimeSpan.FromMinutes(CACHE_EXPIRATION_MINUTES);
6790
}
6891

92+
private async Task RefreshUpgradableAppsAsync()
93+
{
94+
if (!ShouldRefreshCache())
95+
return;
96+
97+
await _refreshSemaphore.WaitAsync();
98+
try
99+
{
100+
if (!ShouldRefreshCache())
101+
return;
102+
103+
var apps = await GetUpgradableAppsAsync();
104+
upgradableApps = new ConcurrentBag<UpgradableApp>(apps);
105+
_lastRefreshTime = DateTime.Now;
106+
}
107+
catch (Exception ex) { }
108+
finally
109+
{
110+
_refreshSemaphore.Release();
111+
}
112+
}
69113

70114

71115
private async Task PerformUpgradeAsync(UpgradableApp app)
72116
{
73-
Context.API.ShowMsg($"Attempting to update {app.Name}...");
117+
Context.API.ShowMsg($"Preparing to update {app.Name}... This may take a moment.");
74118
await ExecuteWingetCommandAsync($"winget upgrade --id {app.Id} -i");
75-
upgradableApps = await GetUpgradableAppsAsync();
119+
upgradableApps = new ConcurrentBag<UpgradableApp>(upgradableApps.Where(a => a.Id != app.Id));
120+
await RefreshUpgradableAppsAsync();
76121
}
77122

123+
78124
private async Task<List<UpgradableApp>> GetUpgradableAppsAsync()
79125
{
80-
var output = await ExecuteWingetCommandAsync("winget upgrade");
126+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(COMMAND_TIMEOUT_SECONDS));
127+
var output = await ExecuteWingetCommandAsync("winget upgrade", cts.Token);
81128
return ParseWingetOutput(output);
82129
}
83130

@@ -86,17 +133,15 @@ private List<UpgradableApp> ParseWingetOutput(string output)
86133
var upgradableApps = new List<UpgradableApp>();
87134
var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
88135

89-
// Find the header line
90-
int startIndex = Array.FindIndex(lines, line => Regex.IsMatch(line, @"^-+$"));
136+
var startIndex = Array.FindIndex(lines, line => Regex.IsMatch(line, @"^-+$"));
91137
if (startIndex == -1) return upgradableApps;
92138

93-
// Analyze each line after the header line, ignoring the last line (which is the number of upgrade available)
94139
for (int i = startIndex + 1; i < lines.Length - 1; i++)
95140
{
96141
var line = lines[i].Trim();
97142
if (string.IsNullOrWhiteSpace(line)) continue;
98143

99-
var match = Regex.Match(line, @"^(.+?)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$");
144+
var match = AppLineRegex.Match(line);
100145
if (match.Success)
101146
{
102147
var app = new UpgradableApp
@@ -114,11 +159,10 @@ private List<UpgradableApp> ParseWingetOutput(string output)
114159
}
115160
}
116161
}
117-
118162
return upgradableApps;
119163
}
120164

121-
private async Task<string> ExecuteWingetCommandAsync(string command)
165+
private async Task<string> ExecuteWingetCommandAsync(string command, CancellationToken cancellationToken = default)
122166
{
123167
var processInfo = new ProcessStartInfo("cmd.exe", "/c " + command)
124168
{
@@ -128,22 +172,20 @@ private async Task<string> ExecuteWingetCommandAsync(string command)
128172
CreateNoWindow = true
129173
};
130174

131-
var output = new StringBuilder();
132-
133-
using (var process = new Process())
134-
{
135-
process.StartInfo = processInfo;
175+
using var process = Process.Start(processInfo);
176+
if (process == null)
177+
throw new InvalidOperationException("Failed to start process.");
136178

137-
process.OutputDataReceived += (sender, e) => { if (e.Data != null) output.AppendLine(e.Data); };
138-
process.ErrorDataReceived += (sender, e) => { if (e.Data != null) output.AppendLine(e.Data); };
179+
var output = await process.StandardOutput.ReadToEndAsync();
180+
var error = await process.StandardError.ReadToEndAsync();
139181

140-
process.Start();
141-
process.BeginOutputReadLine();
142-
process.BeginErrorReadLine();
143-
await Task.Run(() => process.WaitForExit());
182+
await process.WaitForExitAsync(cancellationToken);
183+
if (!string.IsNullOrEmpty(error))
184+
{
185+
throw new InvalidOperationException(error);
144186
}
145187

146-
return output.ToString();
188+
return output;
147189
}
148190
}
149191

plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"Name": "AppUpgrader",
55
"Description": "Allows you to keep your applications up using winget",
66
"Author": "Exarilo",
7-
"Version": "1.0.7",
7+
"Version": "1.0.8",
88
"Language": "csharp",
99
"Website": "https://github.com/Exarilo/Flow.Launcher.Plugin.AppUpgrader",
1010
"IcoPath": "Images\\app.png",

0 commit comments

Comments
 (0)