Skip to content

Commit 1415df2

Browse files
authored
Merge pull request #3674 from Flow-Launcher/clear_pool
Cache connection and clear pool after all operations to avoid ObjectDisposedException
2 parents a77c80f + d7b8f85 commit 1415df2

File tree

3 files changed

+100
-129
lines changed

3 files changed

+100
-129
lines changed

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

Lines changed: 15 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using System.Text.Json;
66
using System.Threading.Tasks;
7+
using Flow.Launcher.Plugin.BrowserBookmark.Helper;
78
using Flow.Launcher.Plugin.BrowserBookmark.Models;
89
using Microsoft.Data.Sqlite;
910

@@ -131,31 +132,7 @@ private static void EnumerateFolderBookmark(JsonElement folderElement, ICollecti
131132

132133
private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
133134
{
134-
// Use a copy to avoid lock issues with the original file
135-
var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.db");
136-
137-
try
138-
{
139-
File.Copy(dbPath, tempDbPath, true);
140-
}
141-
catch (Exception ex)
142-
{
143-
try
144-
{
145-
if (File.Exists(tempDbPath))
146-
{
147-
File.Delete(tempDbPath);
148-
}
149-
}
150-
catch (Exception ex1)
151-
{
152-
Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex1);
153-
}
154-
Main._context.API.LogException(ClassName, $"Failed to copy favicon DB: {dbPath}", ex);
155-
return;
156-
}
157-
158-
try
135+
FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) =>
159136
{
160137
// Since some bookmarks may have same favicon id, we need to record them to avoid duplicates
161138
var savedPaths = new ConcurrentDictionary<string, bool>();
@@ -164,7 +141,8 @@ private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
164141
Parallel.ForEach(bookmarks, bookmark =>
165142
{
166143
// Use read-only connection to avoid locking issues
167-
var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly");
144+
// Do not use pooling so that we do not need to clear pool: https://github.com/dotnet/efcore/issues/26580
145+
var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false");
168146
connection.Open();
169147

170148
try
@@ -180,13 +158,13 @@ private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
180158

181159
using var cmd = connection.CreateCommand();
182160
cmd.CommandText = @"
183-
SELECT f.id, b.image_data
184-
FROM favicons f
185-
JOIN favicon_bitmaps b ON f.id = b.icon_id
186-
JOIN icon_mapping m ON f.id = m.icon_id
187-
WHERE m.page_url LIKE @url
188-
ORDER BY b.width DESC
189-
LIMIT 1";
161+
SELECT f.id, b.image_data
162+
FROM favicons f
163+
JOIN favicon_bitmaps b ON f.id = b.icon_id
164+
JOIN icon_mapping m ON f.id = m.icon_id
165+
WHERE m.page_url LIKE @url
166+
ORDER BY b.width DESC
167+
LIMIT 1";
190168

191169
cmd.Parameters.AddWithValue("@url", $"%{domain}%");
192170

@@ -205,7 +183,7 @@ ORDER BY b.width DESC
205183
// Filter out duplicate favicons
206184
if (savedPaths.TryAdd(faviconPath, true))
207185
{
208-
SaveBitmapData(imageData, faviconPath);
186+
FaviconHelper.SaveBitmapData(imageData, faviconPath);
209187
}
210188

211189
bookmark.FaviconPath = faviconPath;
@@ -216,38 +194,12 @@ ORDER BY b.width DESC
216194
}
217195
finally
218196
{
219-
// https://github.com/dotnet/efcore/issues/26580
220-
SqliteConnection.ClearPool(connection);
197+
// Cache connection and clear pool after all operations to avoid issue:
198+
// ObjectDisposedException: Safe handle has been closed.
221199
connection.Close();
222200
connection.Dispose();
223201
}
224202
});
225-
}
226-
catch (Exception ex)
227-
{
228-
Main._context.API.LogException(ClassName, $"Failed to connect to SQLite: {tempDbPath}", ex);
229-
}
230-
231-
// Delete temporary file
232-
try
233-
{
234-
File.Delete(tempDbPath);
235-
}
236-
catch (Exception ex)
237-
{
238-
Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex);
239-
}
240-
}
241-
242-
private static void SaveBitmapData(byte[] imageData, string outputPath)
243-
{
244-
try
245-
{
246-
File.WriteAllBytes(outputPath, imageData);
247-
}
248-
catch (Exception ex)
249-
{
250-
Main._context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex);
251-
}
203+
});
252204
}
253205
}

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

Lines changed: 9 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using System.Linq;
66
using System.Threading.Tasks;
7+
using Flow.Launcher.Plugin.BrowserBookmark.Helper;
78
using Flow.Launcher.Plugin.BrowserBookmark.Models;
89
using Microsoft.Data.Sqlite;
910

@@ -118,31 +119,7 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
118119

119120
private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
120121
{
121-
// Use a copy to avoid lock issues with the original file
122-
var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite");
123-
124-
try
125-
{
126-
File.Copy(dbPath, tempDbPath, true);
127-
}
128-
catch (Exception ex)
129-
{
130-
try
131-
{
132-
if (File.Exists(tempDbPath))
133-
{
134-
File.Delete(tempDbPath);
135-
}
136-
}
137-
catch (Exception ex1)
138-
{
139-
Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex1);
140-
}
141-
Main._context.API.LogException(ClassName, $"Failed to copy favicon DB: {dbPath}", ex);
142-
return;
143-
}
144-
145-
try
122+
FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) =>
146123
{
147124
// Since some bookmarks may have same favicon id, we need to record them to avoid duplicates
148125
var savedPaths = new ConcurrentDictionary<string, bool>();
@@ -151,7 +128,8 @@ private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
151128
Parallel.ForEach(bookmarks, bookmark =>
152129
{
153130
// Use read-only connection to avoid locking issues
154-
var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly");
131+
// Do not use pooling so that we do not need to clear pool: https://github.com/dotnet/efcore/issues/26580
132+
var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false");
155133
connection.Open();
156134

157135
try
@@ -189,7 +167,7 @@ ORDER BY i.width DESC -- Select largest icon available
189167
return;
190168

191169
string faviconPath;
192-
if (IsSvgData(imageData))
170+
if (FaviconHelper.IsSvgData(imageData))
193171
{
194172
faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.svg");
195173
}
@@ -201,7 +179,7 @@ ORDER BY i.width DESC -- Select largest icon available
201179
// Filter out duplicate favicons
202180
if (savedPaths.TryAdd(faviconPath, true))
203181
{
204-
SaveBitmapData(imageData, faviconPath);
182+
FaviconHelper.SaveBitmapData(imageData, faviconPath);
205183
}
206184

207185
bookmark.FaviconPath = faviconPath;
@@ -212,48 +190,13 @@ ORDER BY i.width DESC -- Select largest icon available
212190
}
213191
finally
214192
{
215-
// https://github.com/dotnet/efcore/issues/26580
216-
SqliteConnection.ClearPool(connection);
193+
// Cache connection and clear pool after all operations to avoid issue:
194+
// ObjectDisposedException: Safe handle has been closed.
217195
connection.Close();
218196
connection.Dispose();
219197
}
220198
});
221-
}
222-
catch (Exception ex)
223-
{
224-
Main._context.API.LogException(ClassName, $"Failed to load Firefox favicon DB: {tempDbPath}", ex);
225-
}
226-
227-
// Delete temporary file
228-
try
229-
{
230-
File.Delete(tempDbPath);
231-
}
232-
catch (Exception ex)
233-
{
234-
Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex);
235-
}
236-
}
237-
238-
private static void SaveBitmapData(byte[] imageData, string outputPath)
239-
{
240-
try
241-
{
242-
File.WriteAllBytes(outputPath, imageData);
243-
}
244-
catch (Exception ex)
245-
{
246-
Main._context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex);
247-
}
248-
}
249-
250-
private static bool IsSvgData(byte[] data)
251-
{
252-
if (data.Length < 5)
253-
return false;
254-
string start = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length));
255-
return start.Contains("<svg") ||
256-
(start.StartsWith("<?xml") && start.Contains("<svg"));
199+
});
257200
}
258201
}
259202

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace Flow.Launcher.Plugin.BrowserBookmark.Helper;
5+
6+
public static class FaviconHelper
7+
{
8+
private static readonly string ClassName = nameof(FaviconHelper);
9+
10+
public static void LoadFaviconsFromDb(string faviconCacheDir, string dbPath, Action<string> loadAction)
11+
{
12+
// Use a copy to avoid lock issues with the original file
13+
var tempDbPath = Path.Combine(faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.db");
14+
15+
try
16+
{
17+
File.Copy(dbPath, tempDbPath, true);
18+
}
19+
catch (Exception ex)
20+
{
21+
try
22+
{
23+
if (File.Exists(tempDbPath))
24+
{
25+
File.Delete(tempDbPath);
26+
}
27+
}
28+
catch (Exception ex1)
29+
{
30+
Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex1);
31+
}
32+
Main._context.API.LogException(ClassName, $"Failed to copy favicon DB: {dbPath}", ex);
33+
return;
34+
}
35+
36+
try
37+
{
38+
loadAction(tempDbPath);
39+
}
40+
catch (Exception ex)
41+
{
42+
Main._context.API.LogException(ClassName, $"Failed to connect to SQLite: {tempDbPath}", ex);
43+
}
44+
45+
// Delete temporary file
46+
try
47+
{
48+
File.Delete(tempDbPath);
49+
}
50+
catch (Exception ex)
51+
{
52+
Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex);
53+
}
54+
}
55+
56+
public static void SaveBitmapData(byte[] imageData, string outputPath)
57+
{
58+
try
59+
{
60+
File.WriteAllBytes(outputPath, imageData);
61+
}
62+
catch (Exception ex)
63+
{
64+
Main._context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex);
65+
}
66+
}
67+
68+
public static bool IsSvgData(byte[] data)
69+
{
70+
if (data.Length < 5)
71+
return false;
72+
string start = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length));
73+
return start.Contains("<svg") ||
74+
(start.StartsWith("<?xml") && start.Contains("<svg"));
75+
}
76+
}

0 commit comments

Comments
 (0)