From c8e82cbd09db15ed2ba667a5f1b50b8195dbe6fb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 8 Jun 2025 00:14:31 +0800 Subject: [PATCH 1/3] Cache connection and clear pool after all operations to avoid ObjectDisposedException --- .../ChromiumBookmarkLoader.cs | 13 +++++++++++-- .../FirefoxBookmarkLoader.cs | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index e102e43b67b..0a531f82447 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -155,6 +155,9 @@ private void LoadFaviconsFromDb(string dbPath, List bookmarks) return; } + // Cache connection for pool clean + SqliteConnection connection1 = null; + try { // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates @@ -216,8 +219,9 @@ ORDER BY b.width DESC } finally { - // https://github.com/dotnet/efcore/issues/26580 - SqliteConnection.ClearPool(connection); + // Cache connection and clear pool after all operations to avoid issue: + // ObjectDisposedException: Safe handle has been closed. + connection1 = connection; connection.Close(); connection.Dispose(); } @@ -231,6 +235,11 @@ ORDER BY b.width DESC // Delete temporary file try { + // https://github.com/dotnet/efcore/issues/26580 + if (connection1 != null) + { + SqliteConnection.ClearPool(connection1); + } File.Delete(tempDbPath); } catch (Exception ex) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index acace2506d4..5e4f7cbeeb2 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -142,6 +142,9 @@ private void LoadFaviconsFromDb(string dbPath, List bookmarks) return; } + // Cache connection for pool clean + SqliteConnection connection1 = null; + try { // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates @@ -212,8 +215,9 @@ ORDER BY i.width DESC -- Select largest icon available } finally { - // https://github.com/dotnet/efcore/issues/26580 - SqliteConnection.ClearPool(connection); + // Cache connection and clear pool after all operations to avoid issue: + // ObjectDisposedException: Safe handle has been closed. + connection1 = connection; connection.Close(); connection.Dispose(); } @@ -227,6 +231,11 @@ ORDER BY i.width DESC -- Select largest icon available // Delete temporary file try { + // https://github.com/dotnet/efcore/issues/26580 + if (connection1 != null) + { + SqliteConnection.ClearPool(connection1); + } File.Delete(tempDbPath); } catch (Exception ex) From 2ed32b318d84d7b743d8830570f60a9cb81aab74 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 8 Jun 2025 12:17:47 +0800 Subject: [PATCH 2/3] Do not use pooling so that we do not need to clear pool --- .../ChromiumBookmarkLoader.cs | 12 ++---------- .../FirefoxBookmarkLoader.cs | 12 ++---------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 0a531f82447..67d54a78647 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -155,9 +155,6 @@ private void LoadFaviconsFromDb(string dbPath, List bookmarks) return; } - // Cache connection for pool clean - SqliteConnection connection1 = null; - try { // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates @@ -167,7 +164,8 @@ private void LoadFaviconsFromDb(string dbPath, List bookmarks) Parallel.ForEach(bookmarks, bookmark => { // Use read-only connection to avoid locking issues - var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly"); + // Do not use pooling so that we do not need to clear pool: https://github.com/dotnet/efcore/issues/26580 + var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false"); connection.Open(); try @@ -221,7 +219,6 @@ ORDER BY b.width DESC { // Cache connection and clear pool after all operations to avoid issue: // ObjectDisposedException: Safe handle has been closed. - connection1 = connection; connection.Close(); connection.Dispose(); } @@ -235,11 +232,6 @@ ORDER BY b.width DESC // Delete temporary file try { - // https://github.com/dotnet/efcore/issues/26580 - if (connection1 != null) - { - SqliteConnection.ClearPool(connection1); - } File.Delete(tempDbPath); } catch (Exception ex) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 5e4f7cbeeb2..b1ed0d4305b 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -142,9 +142,6 @@ private void LoadFaviconsFromDb(string dbPath, List bookmarks) return; } - // Cache connection for pool clean - SqliteConnection connection1 = null; - try { // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates @@ -154,7 +151,8 @@ private void LoadFaviconsFromDb(string dbPath, List bookmarks) Parallel.ForEach(bookmarks, bookmark => { // Use read-only connection to avoid locking issues - var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly"); + // Do not use pooling so that we do not need to clear pool: https://github.com/dotnet/efcore/issues/26580 + var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false"); connection.Open(); try @@ -217,7 +215,6 @@ ORDER BY i.width DESC -- Select largest icon available { // Cache connection and clear pool after all operations to avoid issue: // ObjectDisposedException: Safe handle has been closed. - connection1 = connection; connection.Close(); connection.Dispose(); } @@ -231,11 +228,6 @@ ORDER BY i.width DESC -- Select largest icon available // Delete temporary file try { - // https://github.com/dotnet/efcore/issues/26580 - if (connection1 != null) - { - SqliteConnection.ClearPool(connection1); - } File.Delete(tempDbPath); } catch (Exception ex) From d7b8f85f4a581dd36a5ca261a650953638830685 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 8 Jun 2025 12:35:39 +0800 Subject: [PATCH 3/3] Use FaviconHelper --- .../ChromiumBookmarkLoader.cs | 71 +++-------------- .../FirefoxBookmarkLoader.cs | 68 ++--------------- .../Helper/FaviconHelper.cs | 76 +++++++++++++++++++ 3 files changed, 92 insertions(+), 123 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 67d54a78647..6e6b2e5f4ad 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text.Json; using System.Threading.Tasks; +using Flow.Launcher.Plugin.BrowserBookmark.Helper; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Microsoft.Data.Sqlite; @@ -131,31 +132,7 @@ private static void EnumerateFolderBookmark(JsonElement folderElement, ICollecti 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()}.db"); - - try - { - 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 + FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) => { // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates var savedPaths = new ConcurrentDictionary(); @@ -181,13 +158,13 @@ private void LoadFaviconsFromDb(string dbPath, List bookmarks) using var cmd = connection.CreateCommand(); cmd.CommandText = @" - SELECT f.id, b.image_data - FROM favicons f - JOIN favicon_bitmaps b ON f.id = b.icon_id - JOIN icon_mapping m ON f.id = m.icon_id - WHERE m.page_url LIKE @url - ORDER BY b.width DESC - LIMIT 1"; + SELECT f.id, b.image_data + FROM favicons f + JOIN favicon_bitmaps b ON f.id = b.icon_id + JOIN icon_mapping m ON f.id = m.icon_id + WHERE m.page_url LIKE @url + ORDER BY b.width DESC + LIMIT 1"; cmd.Parameters.AddWithValue("@url", $"%{domain}%"); @@ -206,7 +183,7 @@ ORDER BY b.width DESC // Filter out duplicate favicons if (savedPaths.TryAdd(faviconPath, true)) { - SaveBitmapData(imageData, faviconPath); + FaviconHelper.SaveBitmapData(imageData, faviconPath); } bookmark.FaviconPath = faviconPath; @@ -223,32 +200,6 @@ ORDER BY b.width DESC connection.Dispose(); } }); - } - catch (Exception ex) - { - Main._context.API.LogException(ClassName, $"Failed to connect to SQLite: {tempDbPath}", ex); - } - - // Delete temporary file - try - { - File.Delete(tempDbPath); - } - catch (Exception ex) - { - Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex); - } - } - - private static void SaveBitmapData(byte[] imageData, string outputPath) - { - try - { - File.WriteAllBytes(outputPath, imageData); - } - catch (Exception ex) - { - Main._context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex); - } + }); } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index b1ed0d4305b..82e7c01f629 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using Flow.Launcher.Plugin.BrowserBookmark.Helper; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Microsoft.Data.Sqlite; @@ -118,31 +119,7 @@ protected List GetBookmarksFromPath(string placesPath) 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"); - - try - { - 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 + FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) => { // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates var savedPaths = new ConcurrentDictionary(); @@ -190,7 +167,7 @@ ORDER BY i.width DESC -- Select largest icon available return; string faviconPath; - if (IsSvgData(imageData)) + if (FaviconHelper.IsSvgData(imageData)) { faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.svg"); } @@ -202,7 +179,7 @@ ORDER BY i.width DESC -- Select largest icon available // Filter out duplicate favicons if (savedPaths.TryAdd(faviconPath, true)) { - SaveBitmapData(imageData, faviconPath); + FaviconHelper.SaveBitmapData(imageData, faviconPath); } bookmark.FaviconPath = faviconPath; @@ -219,42 +196,7 @@ ORDER BY i.width DESC -- Select largest icon available connection.Dispose(); } }); - } - catch (Exception ex) - { - Main._context.API.LogException(ClassName, $"Failed to load Firefox favicon DB: {tempDbPath}", ex); - } - - // Delete temporary file - try - { - File.Delete(tempDbPath); - } - catch (Exception ex) - { - Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex); - } - } - - private static void SaveBitmapData(byte[] imageData, string outputPath) - { - try - { - File.WriteAllBytes(outputPath, imageData); - } - catch (Exception ex) - { - Main._context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex); - } - } - - private static bool IsSvgData(byte[] data) - { - if (data.Length < 5) - return false; - string start = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length)); - return start.Contains(" loadAction) + { + // Use a copy to avoid lock issues with the original file + var tempDbPath = Path.Combine(faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.db"); + + try + { + 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 + { + loadAction(tempDbPath); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to connect to SQLite: {tempDbPath}", ex); + } + + // Delete temporary file + try + { + File.Delete(tempDbPath); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex); + } + } + + public static void SaveBitmapData(byte[] imageData, string outputPath) + { + try + { + File.WriteAllBytes(outputPath, imageData); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex); + } + } + + public static bool IsSvgData(byte[] data) + { + if (data.Length < 5) + return false; + string start = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length)); + return start.Contains("