From 2f8e5b91b0666a366e012b56ba614caaaa6be892 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 4 Jun 2025 23:17:38 +0800 Subject: [PATCH 01/17] Add new setting to enable favorite icons --- .../Languages/en.xaml | 2 ++ .../Models/Settings.cs | 2 ++ .../Views/SettingsControl.xaml | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml index e5f3d541e..861779a3d 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml @@ -27,4 +27,6 @@ Browser Engine If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. + Load favorite icons (It may cost much time) + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs index 86532d275..44993aec9 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs @@ -8,6 +8,8 @@ public class Settings : BaseModel public string BrowserPath { get; set; } + public bool EnableFavoriteIcons { get; set; } = false; + public bool LoadChromeBookmark { get; set; } = true; public bool LoadFirefoxBookmark { get; set; } = true; public bool LoadEdgeBookmark { get; set; } = true; diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml index 30424f4e8..fef60eea4 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml @@ -12,6 +12,7 @@ + + \ No newline at end of file From fd8222ba8075a2412bebe2d40a443dca9378cfb5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 4 Jun 2025 23:17:51 +0800 Subject: [PATCH 02/17] Organize usings --- Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index 155069495..c08a508d3 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Threading.Channels; +using System.Threading.Tasks; +using System.Threading; using System.Windows.Controls; using Flow.Launcher.Plugin.BrowserBookmark.Commands; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Views; -using System.IO; -using System.Threading.Channels; -using System.Threading.Tasks; -using System.Threading; using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.BrowserBookmark; From cef3e8e4bb8ecdd0da22f91178382421d298ee2f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 4 Jun 2025 23:18:01 +0800 Subject: [PATCH 03/17] Make settings internal --- Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index c08a508d3..91ade206b 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -21,9 +21,9 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex internal static PluginInitContext _context; - private static List _cachedBookmarks = new(); + internal static Settings _settings; - private static Settings _settings; + private static List _cachedBookmarks = new(); private static bool _initialized = false; From f982d80ffe66bb6ab3b0e03fda14896173f204e6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 4 Jun 2025 23:21:57 +0800 Subject: [PATCH 04/17] Support enable favorite icons for chromiun bookmarks --- .../ChromiumBookmarkLoader.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index e859976bd..b8874c94a 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -43,16 +43,20 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader catch (Exception ex) { Main._context.API.LogException(ClassName, $"Failed to register bookmark file monitoring: {bookmarkPath}", ex); + continue; } var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); var profileBookmarks = LoadBookmarksFromFile(bookmarkPath, source); // Load favicons after loading bookmarks - var faviconDbPath = Path.Combine(profile, "Favicons"); - if (File.Exists(faviconDbPath)) + if (Main._settings.EnableFavoriteIcons) { - LoadFaviconsFromDb(faviconDbPath, profileBookmarks); + var faviconDbPath = Path.Combine(profile, "Favicons"); + if (File.Exists(faviconDbPath)) + { + LoadFaviconsFromDb(faviconDbPath, profileBookmarks); + } } bookmarks.AddRange(profileBookmarks); From b68b299cd49271013e5f31f3bed4203076da9437 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 4 Jun 2025 23:26:32 +0800 Subject: [PATCH 05/17] Support enable favorite icons for firefox bookmarks --- .../FirefoxBookmarkLoader.cs | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 75f26d322..5cbd92586 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -41,30 +41,33 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath)) return bookmarks; + // Try to register file monitoring + try + { + Main.RegisterBookmarkFile(placesPath); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to register Firefox bookmark file monitoring: {placesPath}", ex); + return bookmarks; + } + var tempDbPath = Path.Combine(_faviconCacheDir, $"tempplaces_{Guid.NewGuid()}.sqlite"); try { - // Try to register file monitoring - try - { - Main.RegisterBookmarkFile(placesPath); - } - catch (Exception ex) - { - Main._context.API.LogException(ClassName, $"Failed to register Firefox bookmark file monitoring: {placesPath}", ex); - } - // Use a copy to avoid lock issues with the original file File.Copy(placesPath, tempDbPath, true); - // Connect to database and execute query + // Create the connection string and init the connection string dbPath = string.Format(DbPathFormat, tempDbPath); using var dbConnection = new SqliteConnection(dbPath); + + // Open connection to the database file and execute the query dbConnection.Open(); var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader(); - // Create bookmark list + // Get results in List format bookmarks = reader .Select( x => new Bookmark( @@ -75,12 +78,16 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader ) .ToList(); - // Path to favicon database - var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite"); - if (File.Exists(faviconDbPath)) + // Load favicons after loading bookmarks + if (Main._settings.EnableFavoriteIcons) { - LoadFaviconsFromDb(faviconDbPath, bookmarks); + var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite"); + if (File.Exists(faviconDbPath)) + { + LoadFaviconsFromDb(faviconDbPath, bookmarks); + } } + // https://github.com/dotnet/efcore/issues/26580 SqliteConnection.ClearPool(dbConnection); dbConnection.Close(); From 6d5b95c6168ffb07fd2ffa08686eaef368ecd189 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 4 Jun 2025 23:31:10 +0800 Subject: [PATCH 06/17] Improve code comments --- .../FirefoxBookmarkLoader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 5cbd92586..e090d8c14 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -88,6 +88,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader } } + // Close the connection so that we can delete the temporary file // https://github.com/dotnet/efcore/issues/26580 SqliteConnection.ClearPool(dbConnection); dbConnection.Close(); From a761ec880bd8de87c36771e7a2fab72f4b591bcf Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 4 Jun 2025 23:31:18 +0800 Subject: [PATCH 07/17] Check file exists --- .../FirefoxBookmarkLoader.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index e090d8c14..06ddba7af 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -101,7 +101,10 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader // Delete temporary file try { - File.Delete(tempDbPath); + if (File.Exists(tempDbPath)) + { + File.Delete(tempDbPath); + } } catch (Exception ex) { From 72bd4c202a194667308d3d6adb778bf9b42640b6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 5 Jun 2025 00:27:03 +0800 Subject: [PATCH 08/17] Info favorite icons cost --- .../ChromiumBookmarkLoader.cs | 5 ++++- .../FirefoxBookmarkLoader.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index b8874c94a..16f0a0798 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -55,7 +55,10 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader var faviconDbPath = Path.Combine(profile, "Favicons"); if (File.Exists(faviconDbPath)) { - LoadFaviconsFromDb(faviconDbPath, profileBookmarks); + Main._context.API.StopwatchLogInfo(ClassName, $"Load {profileBookmarks.Count} favorite icons cost", () => + { + LoadFaviconsFromDb(faviconDbPath, profileBookmarks); + }); } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 06ddba7af..d387cd7b5 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -84,7 +84,10 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite"); if (File.Exists(faviconDbPath)) { - LoadFaviconsFromDb(faviconDbPath, bookmarks); + Main._context.API.StopwatchLogInfo(ClassName, $"Load {bookmarks.Count} favorite icons cost", () => + { + LoadFaviconsFromDb(faviconDbPath, bookmarks); + }); } } From 0ec38faf1deb008ece5ae41868bf5347488381c5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 5 Jun 2025 01:12:12 +0800 Subject: [PATCH 09/17] Add code comments & Improve code quality --- .../ChromiumBookmarkLoader.cs | 1 + .../FirefoxBookmarkLoader.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 16f0a0798..a20baccb5 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -132,6 +132,7 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader // Use a copy to avoid lock issues with the original file var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.db"); + // Use a copy to avoid lock issues with the original file try { File.Copy(dbPath, tempDbPath, true); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index d387cd7b5..11fc688e7 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -60,7 +60,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader File.Copy(placesPath, tempDbPath, true); // Create the connection string and init the connection - string dbPath = string.Format(DbPathFormat, tempDbPath); + var dbPath = string.Format($"Data Source={tempDbPath};Mode=ReadOnly"); using var dbConnection = new SqliteConnection(dbPath); // Open connection to the database file and execute the query @@ -119,8 +119,10 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader private void LoadFaviconsFromDb(string faviconDbPath, List bookmarks) { + // Use a copy to avoid lock issues with the original file var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite"); + // Use a copy to avoid lock issues with the original file try { // Use a copy to avoid lock issues with the original file From d6f40ec488c4fd1134e3510cee52623b741fad04 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 5 Jun 2025 01:12:46 +0800 Subject: [PATCH 10/17] Use concurrent way to load favorite icons for chromium --- .../ChromiumBookmarkLoader.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index a20baccb5..ddb50235c 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Text.Json; -using System; +using System.Threading.Tasks; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Microsoft.Data.Sqlite; @@ -156,19 +158,24 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader try { - using var connection = new SqliteConnection($"Data Source={tempDbPath}"); - connection.Open(); + // Since some bookmarks may have same favorite icon id, we need to record them to avoid duplicates + var savedPaths = new ConcurrentDictionary(); - foreach (var bookmark in bookmarks) + // Get favicons based on bookmarks concurrently + Parallel.ForEach(bookmarks.ToArray(), bookmark => { + // Use read-only connection to avoid locking issues + var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly"); + connection.Open(); + try { var url = bookmark.Url; - if (string.IsNullOrEmpty(url)) continue; + if (string.IsNullOrEmpty(url)) return; // Extract domain from URL if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) - continue; + return; var domain = uri.Host; @@ -186,16 +193,21 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader using var reader = cmd.ExecuteReader(); if (!reader.Read() || reader.IsDBNull(1)) - continue; + return; var iconId = reader.GetInt64(0).ToString(); var imageData = (byte[])reader["image_data"]; if (imageData is not { Length: > 0 }) - continue; + return; var faviconPath = Path.Combine(_faviconCacheDir, $"chromium_{domain}_{iconId}.png"); - SaveBitmapData(imageData, faviconPath); + + // Filter out duplicate favorite icons + if (savedPaths.TryAdd(faviconPath, true)) + { + SaveBitmapData(imageData, faviconPath); + } bookmark.FaviconPath = faviconPath; } @@ -203,11 +215,14 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader { Main._context.API.LogException(ClassName, $"Failed to extract bookmark favicon: {bookmark.Url}", ex); } - } - - // https://github.com/dotnet/efcore/issues/26580 - SqliteConnection.ClearPool(connection); - connection.Close(); + finally + { + // https://github.com/dotnet/efcore/issues/26580 + SqliteConnection.ClearPool(connection); + connection.Close(); + connection.Dispose(); + } + }); } catch (Exception ex) { From 8bb2b50a4c11ebe71211e908c423c47f2185c1bc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 5 Jun 2025 01:13:48 +0800 Subject: [PATCH 11/17] Use concurrent way to load favorite icons for firefox --- .../FirefoxBookmarkLoader.cs | 79 ++++++++++++------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 11fc688e7..5d6854aa5 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Microsoft.Data.Sqlite; @@ -30,8 +32,6 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader ORDER BY moz_places.visit_count DESC """; - private const string DbPathFormat = "Data Source={0}"; - protected List GetBookmarksFromPath(string placesPath) { // Variable to store bookmark list @@ -117,7 +117,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader return bookmarks; } - private void LoadFaviconsFromDb(string faviconDbPath, List bookmarks) + private void LoadFaviconsFromDb(string dbPath, List bookmarks) { // Use a copy to avoid lock issues with the original file var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite"); @@ -125,28 +125,45 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader // Use a copy to avoid lock issues with the original file try { - // Use a copy to avoid lock issues with the original file - File.Copy(faviconDbPath, tempDbPath, true); - - var defaultIconPath = Path.Combine( - Path.GetDirectoryName(typeof(FirefoxBookmarkLoaderBase).Assembly.Location), - "bookmark.png"); - - string dbPath = string.Format(DbPathFormat, tempDbPath); - using var connection = new SqliteConnection(dbPath); - connection.Open(); - - // Get favicons based on bookmark URLs - foreach (var bookmark in bookmarks) + File.Copy(dbPath, tempDbPath, true); + } + catch (Exception ex) + { + try { + if (File.Exists(tempDbPath)) + { + File.Delete(tempDbPath); + } + } + catch (Exception ex1) + { + Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex1); + } + Main._context.API.LogException(ClassName, $"Failed to copy favicon DB: {dbPath}", ex); + return; + } + + try + { + // Since some bookmarks may have same favorite icon id, we need to record them to avoid duplicates + var savedPaths = new ConcurrentDictionary(); + + // Get favicons based on bookmarks concurrently + Parallel.ForEach(bookmarks, bookmark => + { + // Use read-only connection to avoid locking issues + var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly"); + connection.Open(); + try { if (string.IsNullOrEmpty(bookmark.Url)) - continue; + return; // Extract domain from URL if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri)) - continue; + return; var domain = uri.Host; @@ -166,12 +183,12 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader using var reader = cmd.ExecuteReader(); if (!reader.Read() || reader.IsDBNull(0)) - continue; + return; var imageData = (byte[])reader["data"]; if (imageData is not { Length: > 0 }) - continue; + return; string faviconPath; if (IsSvgData(imageData)) @@ -182,7 +199,12 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader { faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png"); } - SaveBitmapData(imageData, faviconPath); + + // Filter out duplicate favorite icons + if (savedPaths.TryAdd(faviconPath, true)) + { + SaveBitmapData(imageData, faviconPath); + } bookmark.FaviconPath = faviconPath; } @@ -190,15 +212,18 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader { Main._context.API.LogException(ClassName, $"Failed to extract Firefox favicon: {bookmark.Url}", ex); } - } - - // https://github.com/dotnet/efcore/issues/26580 - SqliteConnection.ClearPool(connection); - connection.Close(); + finally + { + // https://github.com/dotnet/efcore/issues/26580 + SqliteConnection.ClearPool(connection); + connection.Close(); + connection.Dispose(); + } + }); } catch (Exception ex) { - Main._context.API.LogException(ClassName, $"Failed to load Firefox favicon DB: {faviconDbPath}", ex); + Main._context.API.LogException(ClassName, $"Failed to load Firefox favicon DB: {tempDbPath}", ex); } // Delete temporary file From f717376a1745db848a5f3427ca67dc53fb372dab Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 5 Jun 2025 01:30:26 +0800 Subject: [PATCH 12/17] Remove dulplicated comments --- .../ChromiumBookmarkLoader.cs | 1 - .../FirefoxBookmarkLoader.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index ddb50235c..8a23cc8fd 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -134,7 +134,6 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader // Use a copy to avoid lock issues with the original file var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.db"); - // Use a copy to avoid lock issues with the original file try { File.Copy(dbPath, tempDbPath, true); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 5d6854aa5..62b71da4c 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -122,7 +122,6 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader // Use a copy to avoid lock issues with the original file var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite"); - // Use a copy to avoid lock issues with the original file try { File.Copy(dbPath, tempDbPath, true); From 49a42dd1b45d107b14fbdec7a4f36bf2e9db35d1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 5 Jun 2025 01:31:06 +0800 Subject: [PATCH 13/17] Improve code quality --- .../FirefoxBookmarkLoader.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 62b71da4c..cd2f40d58 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -60,8 +60,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader File.Copy(placesPath, tempDbPath, true); // Create the connection string and init the connection - var dbPath = string.Format($"Data Source={tempDbPath};Mode=ReadOnly"); - using var dbConnection = new SqliteConnection(dbPath); + using var dbConnection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly"); // Open connection to the database file and execute the query dbConnection.Open(); From 76dc35433bfed913d460bfdfc92f83bf71d9d393 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 5 Jun 2025 12:12:55 +1000 Subject: [PATCH 14/17] update wording --- Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml index 861779a3d..44118c099 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml @@ -27,6 +27,6 @@ Browser Engine If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favorite icons (It may cost much time) + Load favorite icons (can be time consuming during startup) \ No newline at end of file From d87a7ce32beed41f45bb765a87965304f2df65ed Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 5 Jun 2025 12:15:47 +1000 Subject: [PATCH 15/17] use favicons instead of favourite icons --- Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml index 44118c099..a133d2402 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml @@ -27,6 +27,6 @@ Browser Engine If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favorite icons (can be time consuming during startup) + Load favicons (can be time consuming during startup) \ No newline at end of file From 69dd0383115e52b2ffacc275756b4a353f47b1d2 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 5 Jun 2025 10:16:02 +0800 Subject: [PATCH 16/17] No need to convert to array --- .../ChromiumBookmarkLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 8a23cc8fd..cbcbf70ea 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -161,7 +161,7 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader var savedPaths = new ConcurrentDictionary(); // Get favicons based on bookmarks concurrently - Parallel.ForEach(bookmarks.ToArray(), bookmark => + Parallel.ForEach(bookmarks, bookmark => { // Use read-only connection to avoid locking issues var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly"); From 94b8a66db73f547f660580d77471f3cb5854d1c9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 5 Jun 2025 10:21:24 +0800 Subject: [PATCH 17/17] Rename to favicon --- .../ChromiumBookmarkLoader.cs | 8 ++++---- .../FirefoxBookmarkLoader.cs | 8 ++++---- .../Languages/en.xaml | 2 +- .../Models/Settings.cs | 2 +- .../Views/SettingsControl.xaml | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index cbcbf70ea..e102e43b6 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -52,12 +52,12 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader var profileBookmarks = LoadBookmarksFromFile(bookmarkPath, source); // Load favicons after loading bookmarks - if (Main._settings.EnableFavoriteIcons) + if (Main._settings.EnableFavicons) { var faviconDbPath = Path.Combine(profile, "Favicons"); if (File.Exists(faviconDbPath)) { - Main._context.API.StopwatchLogInfo(ClassName, $"Load {profileBookmarks.Count} favorite icons cost", () => + Main._context.API.StopwatchLogInfo(ClassName, $"Load {profileBookmarks.Count} favicons cost", () => { LoadFaviconsFromDb(faviconDbPath, profileBookmarks); }); @@ -157,7 +157,7 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader try { - // Since some bookmarks may have same favorite icon id, we need to record them to avoid duplicates + // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates var savedPaths = new ConcurrentDictionary(); // Get favicons based on bookmarks concurrently @@ -202,7 +202,7 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader var faviconPath = Path.Combine(_faviconCacheDir, $"chromium_{domain}_{iconId}.png"); - // Filter out duplicate favorite icons + // Filter out duplicate favicons if (savedPaths.TryAdd(faviconPath, true)) { SaveBitmapData(imageData, faviconPath); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index cd2f40d58..492a76c7b 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -78,12 +78,12 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader .ToList(); // Load favicons after loading bookmarks - if (Main._settings.EnableFavoriteIcons) + if (Main._settings.EnableFavicons) { var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite"); if (File.Exists(faviconDbPath)) { - Main._context.API.StopwatchLogInfo(ClassName, $"Load {bookmarks.Count} favorite icons cost", () => + Main._context.API.StopwatchLogInfo(ClassName, $"Load {bookmarks.Count} favicons cost", () => { LoadFaviconsFromDb(faviconDbPath, bookmarks); }); @@ -144,7 +144,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader try { - // Since some bookmarks may have same favorite icon id, we need to record them to avoid duplicates + // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates var savedPaths = new ConcurrentDictionary(); // Get favicons based on bookmarks concurrently @@ -198,7 +198,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png"); } - // Filter out duplicate favorite icons + // Filter out duplicate favicons if (savedPaths.TryAdd(faviconPath, true)) { SaveBitmapData(imageData, faviconPath); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml index a133d2402..22830e7c8 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml @@ -27,6 +27,6 @@ Browser Engine If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favicons (can be time consuming during startup) + Load favicons (can be time consuming during startup) \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs index 44993aec9..a0041e0d6 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs @@ -8,7 +8,7 @@ public class Settings : BaseModel public string BrowserPath { get; set; } - public bool EnableFavoriteIcons { get; set; } = false; + public bool EnableFavicons { get; set; } = false; public bool LoadChromeBookmark { get; set; } = true; public bool LoadFirefoxBookmark { get; set; } = true; diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml index fef60eea4..0767ee980 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml @@ -97,7 +97,7 @@ Margin="{StaticResource SettingPanelItemTopBottomMargin}" HorizontalAlignment="Left" VerticalAlignment="Center" - Content="{DynamicResource flowlauncher_plugin_browserbookmark_enable_favorite_icons}" - IsChecked="{Binding Settings.EnableFavoriteIcons}" /> + Content="{DynamicResource flowlauncher_plugin_browserbookmark_enable_favicons}" + IsChecked="{Binding Settings.EnableFavicons}" /> \ No newline at end of file