diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index e102e43b67b..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(); @@ -164,7 +141,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 @@ -180,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}%"); @@ -205,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; @@ -216,38 +194,12 @@ 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. connection.Close(); 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 acace2506d4..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(); @@ -151,7 +128,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 @@ -189,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"); } @@ -201,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; @@ -212,48 +190,13 @@ 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. connection.Close(); 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("