Skip to content

Commit ef6e339

Browse files
committed
Merge branch 'BrowserBookmark-Advanced' of https://github.com/dcog989/Flow.Launcher into BrowserBookmark-Advanced
2 parents dcb1837 + 43934c4 commit ef6e339

File tree

10 files changed

+270
-122
lines changed

10 files changed

+270
-122
lines changed

Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@
88
using Flow.Launcher.Plugin.BrowserBookmark.Services;
99
using System.ComponentModel;
1010
using System.Linq;
11-
using Flow.Launcher.Plugin.SharedCommands;
1211
using System.Collections.Specialized;
13-
using Flow.Launcher.Plugin.SharedModels;
1412
using System.IO;
15-
using System.Collections.Concurrent;
1613

1714
namespace Flow.Launcher.Plugin.BrowserBookmark;
1815

@@ -29,6 +26,7 @@ public class Main : ISettingProvider, IPlugin, IAsyncReloadable, IPluginI18n, IC
2926
private readonly CancellationTokenSource _cancellationTokenSource = new();
3027
private PeriodicTimer? _firefoxBookmarkTimer;
3128
private static readonly TimeSpan FirefoxPollingInterval = TimeSpan.FromHours(3);
29+
private readonly SemaphoreSlim _reloadGate = new(1, 1);
3230

3331
public void Init(PluginInitContext context)
3432
{
@@ -70,7 +68,7 @@ private string SetupTempDirectory()
7068
public List<Result> Query(Query query)
7169
{
7270
var search = query.Search.Trim();
73-
var bookmarks = _bookmarks; // use a local copy
71+
var bookmarks = Volatile.Read(ref _bookmarks); // use a local copy with proper memory barrier
7472

7573
if (!string.IsNullOrEmpty(search))
7674
{
@@ -109,15 +107,23 @@ public List<Result> Query(Query query)
109107

110108
public async Task ReloadDataAsync()
111109
{
112-
var (bookmarks, discoveredFiles) = await _bookmarkLoader.LoadBookmarksAsync(_cancellationTokenSource.Token);
110+
await _reloadGate.WaitAsync(_cancellationTokenSource.Token);
111+
try
112+
{
113+
var (bookmarks, discoveredFiles) = await _bookmarkLoader.LoadBookmarksAsync(_cancellationTokenSource.Token);
113114

114-
// Atomically swap the list. This prevents the Query method from seeing a partially loaded list.
115-
Volatile.Write(ref _bookmarks, bookmarks);
115+
// Atomically swap the list. This prevents the Query method from seeing a partially loaded list.
116+
Volatile.Write(ref _bookmarks, bookmarks);
116117

117-
_bookmarkWatcher.UpdateWatchers(discoveredFiles);
118+
_bookmarkWatcher.UpdateWatchers(discoveredFiles);
118119

119-
// Fire and forget favicon processing to not block the UI
120-
_ = _faviconService.ProcessBookmarkFavicons(_bookmarks, _cancellationTokenSource.Token);
120+
// Fire and forget favicon processing to not block the UI
121+
_ = _faviconService.ProcessBookmarkFavicons(_bookmarks, _cancellationTokenSource.Token);
122+
}
123+
finally
124+
{
125+
_reloadGate.Release();
126+
}
121127
}
122128

123129
private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs e)
@@ -149,71 +155,86 @@ private void StartFirefoxBookmarkTimer()
149155

150156
_firefoxBookmarkTimer = new PeriodicTimer(FirefoxPollingInterval);
151157

158+
var timer = _firefoxBookmarkTimer!;
152159
_ = Task.Run(async () =>
153160
{
154-
while (await _firefoxBookmarkTimer.WaitForNextTickAsync(_cancellationTokenSource.Token))
161+
try
155162
{
156-
await ReloadFirefoxBookmarksAsync();
163+
while (await timer.WaitForNextTickAsync(_cancellationTokenSource.Token))
164+
{
165+
await ReloadFirefoxBookmarksAsync();
166+
}
157167
}
168+
catch (OperationCanceledException) { }
169+
catch (ObjectDisposedException) { }
158170
}, _cancellationTokenSource.Token);
159171
}
160172

161173
private async Task ReloadFirefoxBookmarksAsync()
162174
{
163-
Context.API.LogInfo(nameof(Main), "Starting periodic reload of Firefox bookmarks.");
164-
165-
var firefoxLoaders = _bookmarkLoader.GetFirefoxBookmarkLoaders().ToList();
166-
if (!firefoxLoaders.Any())
175+
// Share the same gate to avoid conflicting with full reloads
176+
await _reloadGate.WaitAsync(_cancellationTokenSource.Token);
177+
try
167178
{
168-
Context.API.LogInfo(nameof(Main), "No Firefox bookmark loaders enabled, skipping reload.");
169-
return;
170-
}
179+
Context.API.LogInfo(nameof(Main), "Starting periodic reload of Firefox bookmarks.");
171180

172-
var tasks = firefoxLoaders.Select(async loader =>
173-
{
174-
var loadedBookmarks = new List<Bookmark>();
175-
try
176-
{
177-
await foreach (var bookmark in loader.GetBookmarksAsync(_cancellationTokenSource.Token))
178-
{
179-
loadedBookmarks.Add(bookmark);
180-
}
181-
return (Loader: loader, Bookmarks: loadedBookmarks, Success: true);
182-
}
183-
catch (OperationCanceledException)
181+
var firefoxLoaders = _bookmarkLoader.GetFirefoxBookmarkLoaders().ToList();
182+
if (!firefoxLoaders.Any())
184183
{
185-
return (Loader: loader, Bookmarks: new List<Bookmark>(), Success: false);
184+
Context.API.LogInfo(nameof(Main), "No Firefox bookmark loaders enabled, skipping reload.");
185+
return;
186186
}
187-
catch (Exception e)
187+
188+
var tasks = firefoxLoaders.Select(async loader =>
188189
{
189-
Context.API.LogException(nameof(Main), $"Failed to load bookmarks from {loader.Name}.", e);
190-
return (Loader: loader, Bookmarks: new List<Bookmark>(), Success: false);
191-
}
192-
});
190+
var loadedBookmarks = new List<Bookmark>();
191+
try
192+
{
193+
await foreach (var bookmark in loader.GetBookmarksAsync(_cancellationTokenSource.Token))
194+
{
195+
loadedBookmarks.Add(bookmark);
196+
}
197+
return (Loader: loader, Bookmarks: loadedBookmarks, Success: true);
198+
}
199+
catch (OperationCanceledException)
200+
{
201+
return (Loader: loader, Bookmarks: new List<Bookmark>(), Success: false);
202+
}
203+
catch (Exception e)
204+
{
205+
Context.API.LogException(nameof(Main), $"Failed to load bookmarks from {loader.Name}.", e);
206+
return (Loader: loader, Bookmarks: new List<Bookmark>(), Success: false);
207+
}
208+
});
193209

194-
var results = await Task.WhenAll(tasks);
195-
var successfulResults = results.Where(r => r.Success).ToList();
210+
var results = await Task.WhenAll(tasks);
211+
var successfulResults = results.Where(r => r.Success).ToList();
196212

197-
if (!successfulResults.Any())
198-
{
199-
Context.API.LogInfo(nameof(Main), "No Firefox bookmarks successfully reloaded.");
200-
return;
201-
}
213+
if (!successfulResults.Any())
214+
{
215+
Context.API.LogInfo(nameof(Main), "No Firefox bookmarks successfully reloaded.");
216+
return;
217+
}
202218

203-
var newFirefoxBookmarks = successfulResults.SelectMany(r => r.Bookmarks).ToList();
204-
var successfulLoaderNames = successfulResults.Select(r => r.Loader.Name).ToHashSet();
219+
var newFirefoxBookmarks = successfulResults.SelectMany(r => r.Bookmarks).ToList();
220+
var successfulLoaderNames = successfulResults.Select(r => r.Loader.Name).ToHashSet();
205221

206-
var currentBookmarks = Volatile.Read(ref _bookmarks);
222+
var currentBookmarks = Volatile.Read(ref _bookmarks);
207223

208-
var otherBookmarks = currentBookmarks.Where(b => !successfulLoaderNames.Any(name => b.Source.StartsWith(name, StringComparison.OrdinalIgnoreCase)));
224+
var otherBookmarks = currentBookmarks.Where(b => !successfulLoaderNames.Any(name => b.Source.StartsWith(name, StringComparison.OrdinalIgnoreCase)));
209225

210-
var newBookmarkList = otherBookmarks.Concat(newFirefoxBookmarks).Distinct().ToList();
226+
var newBookmarkList = otherBookmarks.Concat(newFirefoxBookmarks).Distinct().ToList();
211227

212-
Volatile.Write(ref _bookmarks, newBookmarkList);
228+
Volatile.Write(ref _bookmarks, newBookmarkList);
213229

214-
Context.API.LogInfo(nameof(Main), $"Periodic reload complete. Loaded {newFirefoxBookmarks.Count} Firefox bookmarks from {successfulLoaderNames.Count} sources.");
230+
Context.API.LogInfo(nameof(Main), $"Periodic reload complete. Loaded {newFirefoxBookmarks.Count} Firefox bookmarks from {successfulLoaderNames.Count} sources.");
215231

216-
_ = _faviconService.ProcessBookmarkFavicons(newFirefoxBookmarks, _cancellationTokenSource.Token);
232+
_ = _faviconService.ProcessBookmarkFavicons(newFirefoxBookmarks, _cancellationTokenSource.Token);
233+
}
234+
finally
235+
{
236+
_reloadGate.Release();
237+
}
217238
}
218239

219240
public Control CreateSettingPanel()
@@ -251,7 +272,7 @@ public List<Result> LoadContextMenus(Result selectedResult)
251272
Context.API.CopyToClipboard(url);
252273
return true;
253274
}
254-
catch(Exception ex)
275+
catch (Exception ex)
255276
{
256277
Context.API.LogException(nameof(Main), "Failed to copy URL to clipboard", ex);
257278
Context.API.ShowMsgError(Localize.flowlauncher_plugin_browserbookmark_copy_failed());

Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using Flow.Launcher.Localization.Attributes;
2-
using System.Collections.Generic;
3-
using System.Linq;
42

53
namespace Flow.Launcher.Plugin.BrowserBookmark.Models;
64

Plugins/Flow.Launcher.Plugin.BrowserBookmark/Services/ChromiumBookmarkLoader.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Collections.Concurrent;
55
using System.Collections.Generic;
66
using System.IO;
7-
using System.Linq;
87
using System.Runtime.CompilerServices;
98
using System.Text.Json;
109
using System.Threading;
@@ -56,10 +55,22 @@ public async IAsyncEnumerable<Bookmark> GetBookmarksAsync([EnumeratorCancellatio
5655
bookmarks.AddRange(EnumerateBookmarks(rootElement, source, profilePath));
5756
}
5857
}
58+
catch (IOException ex)
59+
{
60+
_logException(nameof(ChromiumBookmarkLoader), $"IO error reading {_browserName} bookmarks: {bookmarkPath}", ex);
61+
}
62+
catch (UnauthorizedAccessException ex)
63+
{
64+
_logException(nameof(ChromiumBookmarkLoader), $"Unauthorized to read {_browserName} bookmarks: {bookmarkPath}", ex);
65+
}
5966
catch (JsonException ex)
6067
{
6168
_logException(nameof(ChromiumBookmarkLoader), $"Failed to parse bookmarks file for {_browserName}: {bookmarkPath}", ex);
6269
}
70+
catch (Exception ex)
71+
{
72+
_logException(nameof(ChromiumBookmarkLoader), $"Unexpected error loading {_browserName} bookmarks: {bookmarkPath}", ex);
73+
}
6374

6475
foreach (var bookmark in bookmarks)
6576
{
@@ -113,7 +124,7 @@ private void EnumerateFolderBookmark(JsonElement folderElement, ICollection<Book
113124
}
114125
else
115126
{
116-
_logException(nameof(ChromiumBookmarkLoader), $"type property not found for {subElement.GetString()}", null);
127+
_logException(nameof(ChromiumBookmarkLoader), "type property not found in bookmark node.", null);
117128
}
118129
}
119130
}

0 commit comments

Comments
 (0)