mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3674 from Flow-Launcher/clear_pool
Cache connection and clear pool after all operations to avoid ObjectDisposedException
This commit is contained in:
commit
1415df2003
3 changed files with 100 additions and 129 deletions
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Flow.Launcher.Plugin.BrowserBookmark.Helper;
|
||||||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
|
@ -131,31 +132,7 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
||||||
|
|
||||||
private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
|
private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
|
||||||
{
|
{
|
||||||
// Use a copy to avoid lock issues with the original file
|
FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) =>
|
||||||
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
|
|
||||||
{
|
{
|
||||||
// Since some bookmarks may have same favicon 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<string, bool>();
|
var savedPaths = new ConcurrentDictionary<string, bool>();
|
||||||
|
|
@ -164,7 +141,8 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
||||||
Parallel.ForEach(bookmarks, bookmark =>
|
Parallel.ForEach(bookmarks, bookmark =>
|
||||||
{
|
{
|
||||||
// Use read-only connection to avoid locking issues
|
// 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();
|
connection.Open();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -180,13 +158,13 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
||||||
|
|
||||||
using var cmd = connection.CreateCommand();
|
using var cmd = connection.CreateCommand();
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
SELECT f.id, b.image_data
|
SELECT f.id, b.image_data
|
||||||
FROM favicons f
|
FROM favicons f
|
||||||
JOIN favicon_bitmaps b ON f.id = b.icon_id
|
JOIN favicon_bitmaps b ON f.id = b.icon_id
|
||||||
JOIN icon_mapping m ON f.id = m.icon_id
|
JOIN icon_mapping m ON f.id = m.icon_id
|
||||||
WHERE m.page_url LIKE @url
|
WHERE m.page_url LIKE @url
|
||||||
ORDER BY b.width DESC
|
ORDER BY b.width DESC
|
||||||
LIMIT 1";
|
LIMIT 1";
|
||||||
|
|
||||||
cmd.Parameters.AddWithValue("@url", $"%{domain}%");
|
cmd.Parameters.AddWithValue("@url", $"%{domain}%");
|
||||||
|
|
||||||
|
|
@ -205,7 +183,7 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
||||||
// Filter out duplicate favicons
|
// Filter out duplicate favicons
|
||||||
if (savedPaths.TryAdd(faviconPath, true))
|
if (savedPaths.TryAdd(faviconPath, true))
|
||||||
{
|
{
|
||||||
SaveBitmapData(imageData, faviconPath);
|
FaviconHelper.SaveBitmapData(imageData, faviconPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
bookmark.FaviconPath = faviconPath;
|
bookmark.FaviconPath = faviconPath;
|
||||||
|
|
@ -216,38 +194,12 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// https://github.com/dotnet/efcore/issues/26580
|
// Cache connection and clear pool after all operations to avoid issue:
|
||||||
SqliteConnection.ClearPool(connection);
|
// ObjectDisposedException: Safe handle has been closed.
|
||||||
connection.Close();
|
connection.Close();
|
||||||
connection.Dispose();
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Flow.Launcher.Plugin.BrowserBookmark.Helper;
|
||||||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
|
@ -118,31 +119,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
||||||
|
|
||||||
private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
|
private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
|
||||||
{
|
{
|
||||||
// Use a copy to avoid lock issues with the original file
|
FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) =>
|
||||||
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
|
|
||||||
{
|
{
|
||||||
// Since some bookmarks may have same favicon 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<string, bool>();
|
var savedPaths = new ConcurrentDictionary<string, bool>();
|
||||||
|
|
@ -151,7 +128,8 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
||||||
Parallel.ForEach(bookmarks, bookmark =>
|
Parallel.ForEach(bookmarks, bookmark =>
|
||||||
{
|
{
|
||||||
// Use read-only connection to avoid locking issues
|
// 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();
|
connection.Open();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -189,7 +167,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string faviconPath;
|
string faviconPath;
|
||||||
if (IsSvgData(imageData))
|
if (FaviconHelper.IsSvgData(imageData))
|
||||||
{
|
{
|
||||||
faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.svg");
|
faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.svg");
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +179,7 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
||||||
// Filter out duplicate favicons
|
// Filter out duplicate favicons
|
||||||
if (savedPaths.TryAdd(faviconPath, true))
|
if (savedPaths.TryAdd(faviconPath, true))
|
||||||
{
|
{
|
||||||
SaveBitmapData(imageData, faviconPath);
|
FaviconHelper.SaveBitmapData(imageData, faviconPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
bookmark.FaviconPath = faviconPath;
|
bookmark.FaviconPath = faviconPath;
|
||||||
|
|
@ -212,48 +190,13 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// https://github.com/dotnet/efcore/issues/26580
|
// Cache connection and clear pool after all operations to avoid issue:
|
||||||
SqliteConnection.ClearPool(connection);
|
// ObjectDisposedException: Safe handle has been closed.
|
||||||
connection.Close();
|
connection.Close();
|
||||||
connection.Dispose();
|
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("<svg") ||
|
|
||||||
(start.StartsWith("<?xml") && start.Contains("<svg"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Plugin.BrowserBookmark.Helper;
|
||||||
|
|
||||||
|
public static class FaviconHelper
|
||||||
|
{
|
||||||
|
private static readonly string ClassName = nameof(FaviconHelper);
|
||||||
|
|
||||||
|
public static void LoadFaviconsFromDb(string faviconCacheDir, string dbPath, Action<string> 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("<svg") ||
|
||||||
|
(start.StartsWith("<?xml") && start.Contains("<svg"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue