Skip to content

Commit c22b1a6

Browse files
committed
Implement 'Upgrade All' option and app exclusion in settings
1 parent 203ae01 commit c22b1a6

File tree

6 files changed

+392
-19
lines changed

6 files changed

+392
-19
lines changed

Flow.Launcher.Plugin.AppUpgrader.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,7 @@
3737
<ItemGroup>
3838
<PackageReference Include="Flow.Launcher.Plugin" Version="4.1.0" />
3939
</ItemGroup>
40+
<ItemGroup>
41+
<Page Include="SettingsPage.xaml" />
42+
</ItemGroup>
4043
</Project>

Main.cs

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
using System.Diagnostics;
55
using System.IO;
66
using System.Linq;
7-
using System.Text;
87
using System.Text.RegularExpressions;
98
using System.Threading;
109
using System.Threading.Tasks;
1110
using Flow.Launcher.Plugin;
1211
using Microsoft.Win32;
13-
12+
using System.Windows.Controls;
13+
using System.Windows;
1414
namespace Flow.Launcher.Plugin.AppUpgrader
1515
{
16-
public class AppUpgrader : IAsyncPlugin
16+
public class AppUpgrader : IAsyncPlugin, ISettingProvider
1717
{
18+
private SettingsPage settingsPage;
1819
internal PluginInitContext Context;
1920
private ConcurrentBag<UpgradableApp> upgradableApps;
2021
private ConcurrentDictionary<string, string> appIconPaths;
@@ -33,11 +34,20 @@ public class AppUpgrader : IAsyncPlugin
3334
TimeSpan.FromMilliseconds(500)
3435
);
3536

36-
public Task InitAsync(PluginInitContext context)
37+
public async Task InitAsync(PluginInitContext context)
3738
{
3839
Context = context;
3940
appIconPaths = new ConcurrentDictionary<string, string>();
4041

42+
Application.Current.Dispatcher.Invoke(() =>
43+
{
44+
settingsPage = new SettingsPage(Context);
45+
settingsPage.SettingLoaded += async (s, e) =>
46+
{
47+
settingsPage.ExcludedApps.CollectionChanged += ExcludedApps_CollectionChanged;
48+
RemoveExcludedAppsFromUpgradableList();
49+
};
50+
});
4151
Task.Run(async () =>
4252
{
4353
try
@@ -48,7 +58,36 @@ public Task InitAsync(PluginInitContext context)
4858
});
4959

5060
ThreadPool.SetMinThreads(Environment.ProcessorCount * 2, Environment.ProcessorCount * 2);
51-
return Task.CompletedTask;
61+
await Task.CompletedTask;
62+
}
63+
64+
65+
private void RemoveExcludedAppsFromUpgradableList()
66+
{
67+
var excludedApps = settingsPage.ExcludedApps;
68+
69+
if (excludedApps == null || !excludedApps.Any())
70+
{
71+
return;
72+
}
73+
74+
var updatedApps = upgradableApps
75+
.Where(app => !excludedApps.Any(excludedApp =>
76+
app.Name.Contains(excludedApp, StringComparison.OrdinalIgnoreCase) ||
77+
app.Id.Contains(excludedApp, StringComparison.OrdinalIgnoreCase)))
78+
.ToList();
79+
80+
upgradableApps = new ConcurrentBag<UpgradableApp>(updatedApps);
81+
}
82+
83+
private void ExcludedApps_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
84+
{
85+
86+
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add ||
87+
e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
88+
{
89+
RemoveExcludedAppsFromUpgradableList();
90+
}
5291
}
5392

5493
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
@@ -61,18 +100,44 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
61100
if (upgradableApps == null || !upgradableApps.Any())
62101
{
63102
return new List<Result>
64-
{
65-
new Result
66-
{
67-
Title = "No updates available",
68-
SubTitle = "All applications are up-to-date.",
69-
IcoPath = "Images\\app.png"
70-
}
71-
};
103+
{
104+
new Result
105+
{
106+
Title = "No updates available",
107+
SubTitle = "All applications are up-to-date.",
108+
IcoPath = "Images\\app.png"
109+
}
110+
};
72111
}
73112

74113
string filterTerm = query.Search?.Trim().ToLower();
75114

115+
var results = new List<Result>();
116+
117+
if (settingsPage.EnableUpgradeAll)
118+
{
119+
results.Add(new Result
120+
{
121+
Title = "Upgrade All Applications",
122+
SubTitle = "Upgrade all apps listed below.",
123+
IcoPath = "Images\\app.png",
124+
Action = context =>
125+
{
126+
Task.Run(async () =>
127+
{
128+
try
129+
{
130+
foreach (var app in upgradableApps)
131+
{
132+
await PerformUpgradeAsync(app);
133+
}
134+
}
135+
catch (Exception ex){}
136+
});
137+
return true;
138+
}
139+
});
140+
}
76141
var tasks = upgradableApps.AsParallel()
77142
.WithDegreeOfParallelism(Environment.ProcessorCount)
78143
.Where(app => string.IsNullOrEmpty(filterTerm) ||
@@ -99,9 +164,12 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
99164
}
100165
});
101166

102-
return (await Task.WhenAll(tasks)).ToList();
167+
results.AddRange(await Task.WhenAll(tasks));
168+
169+
return results;
103170
}
104171

172+
105173
private async Task<string> GetAppIconPath(string appId, string appName)
106174
{
107175
if (appIconPaths.TryGetValue(appId, out string cachedPath))
@@ -263,14 +331,16 @@ private async Task RefreshUpgradableAppsAsync()
263331
if (!ShouldRefreshCache())
264332
return;
265333

266-
var apps = await GetUpgradableAppsAsync();
267-
upgradableApps = new ConcurrentBag<UpgradableApp>(apps);
268-
_lastRefreshTime = DateTime.UtcNow;
334+
var apps = await GetUpgradableAppsAsync();
335+
upgradableApps = new ConcurrentBag<UpgradableApp>(apps);
336+
RemoveExcludedAppsFromUpgradableList();
337+
338+
_lastRefreshTime = DateTime.UtcNow;
269339
}
270340
catch (Exception ex){}
271341
finally
272342
{
273-
_refreshSemaphore.Release();
343+
_refreshSemaphore.Release();
274344
}
275345
}
276346

@@ -288,6 +358,12 @@ private async Task PerformUpgradeAsync(UpgradableApp app)
288358
await RefreshUpgradableAppsAsync();
289359
}
290360

361+
public Control CreateSettingPanel()
362+
{
363+
364+
return settingsPage;
365+
}
366+
291367
private async Task<List<UpgradableApp>> GetUpgradableAppsAsync()
292368
{
293369
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(COMMAND_TIMEOUT_SECONDS));

SettingItem.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.ComponentModel;
2+
using System.Text.Json.Serialization;
3+
4+
namespace Flow.Launcher.Plugin.AppUpgrader
5+
{
6+
public class SettingItem
7+
{
8+
public string Key { get; set; }
9+
public string Value { get; set; }
10+
}
11+
}

SettingsPage.cs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
using System;
2+
using System.Linq;
3+
using System.Windows;
4+
using System.Windows.Controls;
5+
using System.Collections.ObjectModel;
6+
using System.Text.Json;
7+
using System.IO;
8+
using Flow.Launcher.Plugin;
9+
10+
namespace Flow.Launcher.Plugin.AppUpgrader
11+
{
12+
public partial class SettingsPage : UserControl
13+
{
14+
private ObservableCollection<SettingItem> settings { get; set; } = new ObservableCollection<SettingItem>();
15+
private readonly PluginInitContext context;
16+
17+
public event EventHandler EnableUpgradeAllChanged;
18+
public EventHandler SettingLoaded;
19+
20+
public bool EnableUpgradeAll
21+
{
22+
get => GetSetting("EnableUpgradeAll", false);
23+
set
24+
{
25+
var previousValue = GetSetting("EnableUpgradeAll", false);
26+
if (previousValue != value)
27+
{
28+
UpdateSetting("EnableUpgradeAll", value);
29+
SaveSettingsToFile();
30+
EnableUpgradeAllChanged?.Invoke(this, EventArgs.Empty);
31+
}
32+
}
33+
}
34+
35+
public ObservableCollection<string> ExcludedApps { get; } = new ObservableCollection<string>();
36+
37+
string SettingsPath => Path.Combine(
38+
Path.GetDirectoryName(Path.GetDirectoryName(context.CurrentPluginMetadata.PluginDirectory)),
39+
"Settings",
40+
"Plugins",
41+
"Flow.Launcher.Plugin.AppUpgrader",
42+
"Settings.json"
43+
);
44+
45+
public SettingsPage(PluginInitContext context)
46+
{
47+
InitializeComponent();
48+
49+
this.context = context;
50+
DataContext = this;
51+
52+
LoadSettingsFromFile();
53+
LoadExcludedApps();
54+
this.Loaded += (sender, e) => SettingLoaded?.Invoke(this, EventArgs.Empty);
55+
}
56+
57+
private void LoadExcludedApps()
58+
{
59+
var excludedApps = GetSetting("ExcludedApps", string.Empty)
60+
.Split(',', StringSplitOptions.RemoveEmptyEntries)
61+
.Select(app => app.Trim('"'))
62+
.ToList();
63+
64+
ExcludedApps.Clear();
65+
foreach (var app in excludedApps)
66+
{
67+
ExcludedApps.Add(app);
68+
}
69+
}
70+
71+
private void SaveExcludedApps()
72+
{
73+
var apps = string.Join(",", ExcludedApps);
74+
UpdateSetting("ExcludedApps", apps);
75+
SaveSettingsToFile();
76+
}
77+
78+
private T GetSetting<T>(string key, T defaultValue)
79+
{
80+
var item = settings.FirstOrDefault(s => s.Key == key);
81+
if (item == null)
82+
{
83+
return defaultValue;
84+
}
85+
86+
try
87+
{
88+
return JsonSerializer.Deserialize<T>(item.Value);
89+
}
90+
catch
91+
{
92+
return defaultValue;
93+
}
94+
}
95+
96+
private void UpdateSetting<T>(string key, T value)
97+
{
98+
var jsonValue = JsonSerializer.Serialize(value);
99+
var item = settings.FirstOrDefault(s => s.Key == key);
100+
101+
if (item == null)
102+
{
103+
settings.Add(new SettingItem { Key = key, Value = jsonValue });
104+
}
105+
else
106+
{
107+
item.Value = jsonValue;
108+
}
109+
}
110+
111+
private void SaveSettingsToFile()
112+
{
113+
try
114+
{
115+
var filePath = SettingsPath;
116+
var jsonSettings = JsonSerializer.Serialize(settings);
117+
118+
var directory = Path.GetDirectoryName(filePath);
119+
if (!Directory.Exists(directory))
120+
{
121+
Directory.CreateDirectory(directory);
122+
}
123+
124+
File.WriteAllText(filePath, jsonSettings);
125+
}
126+
catch (Exception ex)
127+
{
128+
MessageBox.Show($"Error saving settings: {ex.Message}");
129+
}
130+
}
131+
132+
private void LoadSettingsFromFile()
133+
{
134+
try
135+
{
136+
var filePath = SettingsPath;
137+
138+
if (File.Exists(filePath))
139+
{
140+
var jsonSettings = File.ReadAllText(filePath);
141+
settings = JsonSerializer.Deserialize<ObservableCollection<SettingItem>>(jsonSettings);
142+
}
143+
}
144+
catch (Exception ex)
145+
{
146+
}
147+
}
148+
149+
private void AddExclusionButton_Click(object sender, RoutedEventArgs e)
150+
{
151+
var appIdOrName = ExcludeAppTextBox.Text?.Trim();
152+
if (string.IsNullOrEmpty(appIdOrName))
153+
{
154+
return;
155+
}
156+
157+
if (!ExcludedApps.Any(x => x.Equals(appIdOrName, StringComparison.OrdinalIgnoreCase)))
158+
{
159+
ExcludedApps.Add(appIdOrName);
160+
ExcludeAppTextBox.Clear();
161+
SaveExcludedApps();
162+
}
163+
}
164+
165+
private void RemoveExclusion_Click(object sender, RoutedEventArgs e)
166+
{
167+
if (sender is Button button && button.Tag is string appIdOrName)
168+
{
169+
ExcludedApps.Remove(appIdOrName);
170+
SaveExcludedApps();
171+
}
172+
}
173+
}
174+
}

0 commit comments

Comments
 (0)