1
1
using System ;
2
+ using System . Collections . Concurrent ;
2
3
using System . Collections . Generic ;
3
4
using System . IO ;
4
5
using System . Linq ;
6
+ using System . Threading . Tasks ;
5
7
using Flow . Launcher . Plugin . BrowserBookmark . Models ;
6
8
using Microsoft . Data . Sqlite ;
7
9
@@ -30,8 +32,6 @@ INNER JOIN moz_bookmarks ON (
30
32
ORDER BY moz_places.visit_count DESC
31
33
""" ;
32
34
33
- private const string DbPathFormat = "Data Source={0}" ;
34
-
35
35
protected List < Bookmark > GetBookmarksFromPath ( string placesPath )
36
36
{
37
37
// Variable to store bookmark list
@@ -41,30 +41,32 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
41
41
if ( string . IsNullOrEmpty ( placesPath ) || ! File . Exists ( placesPath ) )
42
42
return bookmarks ;
43
43
44
+ // Try to register file monitoring
45
+ try
46
+ {
47
+ Main . RegisterBookmarkFile ( placesPath ) ;
48
+ }
49
+ catch ( Exception ex )
50
+ {
51
+ Main . _context . API . LogException ( ClassName , $ "Failed to register Firefox bookmark file monitoring: { placesPath } ", ex ) ;
52
+ return bookmarks ;
53
+ }
54
+
44
55
var tempDbPath = Path . Combine ( _faviconCacheDir , $ "tempplaces_{ Guid . NewGuid ( ) } .sqlite") ;
45
56
46
57
try
47
58
{
48
- // Try to register file monitoring
49
- try
50
- {
51
- Main . RegisterBookmarkFile ( placesPath ) ;
52
- }
53
- catch ( Exception ex )
54
- {
55
- Main . _context . API . LogException ( ClassName , $ "Failed to register Firefox bookmark file monitoring: { placesPath } ", ex ) ;
56
- }
57
-
58
59
// Use a copy to avoid lock issues with the original file
59
60
File . Copy ( placesPath , tempDbPath , true ) ;
60
61
61
- // Connect to database and execute query
62
- string dbPath = string . Format ( DbPathFormat , tempDbPath ) ;
63
- using var dbConnection = new SqliteConnection ( dbPath ) ;
62
+ // Create the connection string and init the connection
63
+ using var dbConnection = new SqliteConnection ( $ "Data Source={ tempDbPath } ;Mode=ReadOnly") ;
64
+
65
+ // Open connection to the database file and execute the query
64
66
dbConnection . Open ( ) ;
65
67
var reader = new SqliteCommand ( QueryAllBookmarks , dbConnection ) . ExecuteReader ( ) ;
66
68
67
- // Create bookmark list
69
+ // Get results in List<Bookmark> format
68
70
bookmarks = reader
69
71
. Select (
70
72
x => new Bookmark (
@@ -75,12 +77,20 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
75
77
)
76
78
. ToList ( ) ;
77
79
78
- // Path to favicon database
79
- var faviconDbPath = Path . Combine ( Path . GetDirectoryName ( placesPath ) , "favicons.sqlite" ) ;
80
- if ( File . Exists ( faviconDbPath ) )
80
+ // Load favicons after loading bookmarks
81
+ if ( Main . _settings . EnableFavicons )
81
82
{
82
- LoadFaviconsFromDb ( faviconDbPath , bookmarks ) ;
83
+ var faviconDbPath = Path . Combine ( Path . GetDirectoryName ( placesPath ) , "favicons.sqlite" ) ;
84
+ if ( File . Exists ( faviconDbPath ) )
85
+ {
86
+ Main . _context . API . StopwatchLogInfo ( ClassName , $ "Load { bookmarks . Count } favicons cost", ( ) =>
87
+ {
88
+ LoadFaviconsFromDb ( faviconDbPath , bookmarks ) ;
89
+ } ) ;
90
+ }
83
91
}
92
+
93
+ // Close the connection so that we can delete the temporary file
84
94
// https://github.com/dotnet/efcore/issues/26580
85
95
SqliteConnection . ClearPool ( dbConnection ) ;
86
96
dbConnection . Close ( ) ;
@@ -93,7 +103,10 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
93
103
// Delete temporary file
94
104
try
95
105
{
96
- File . Delete ( tempDbPath ) ;
106
+ if ( File . Exists ( tempDbPath ) )
107
+ {
108
+ File . Delete ( tempDbPath ) ;
109
+ }
97
110
}
98
111
catch ( Exception ex )
99
112
{
@@ -103,34 +116,52 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
103
116
return bookmarks ;
104
117
}
105
118
106
- private void LoadFaviconsFromDb ( string faviconDbPath , List < Bookmark > bookmarks )
119
+ private void LoadFaviconsFromDb ( string dbPath , List < Bookmark > bookmarks )
107
120
{
121
+ // Use a copy to avoid lock issues with the original file
108
122
var tempDbPath = Path . Combine ( _faviconCacheDir , $ "tempfavicons_{ Guid . NewGuid ( ) } .sqlite") ;
109
123
110
124
try
111
125
{
112
- // Use a copy to avoid lock issues with the original file
113
- File . Copy ( faviconDbPath , tempDbPath , true ) ;
114
-
115
- var defaultIconPath = Path . Combine (
116
- Path . GetDirectoryName ( typeof ( FirefoxBookmarkLoaderBase ) . Assembly . Location ) ,
117
- "bookmark.png" ) ;
118
-
119
- string dbPath = string . Format ( DbPathFormat , tempDbPath ) ;
120
- using var connection = new SqliteConnection ( dbPath ) ;
121
- connection . Open ( ) ;
122
-
123
- // Get favicons based on bookmark URLs
124
- foreach ( var bookmark in bookmarks )
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
146
+ {
147
+ // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates
148
+ var savedPaths = new ConcurrentDictionary < string , bool > ( ) ;
149
+
150
+ // Get favicons based on bookmarks concurrently
151
+ Parallel . ForEach ( bookmarks , bookmark =>
125
152
{
153
+ // Use read-only connection to avoid locking issues
154
+ var connection = new SqliteConnection ( $ "Data Source={ tempDbPath } ;Mode=ReadOnly") ;
155
+ connection . Open ( ) ;
156
+
126
157
try
127
158
{
128
159
if ( string . IsNullOrEmpty ( bookmark . Url ) )
129
- continue ;
160
+ return ;
130
161
131
162
// Extract domain from URL
132
163
if ( ! Uri . TryCreate ( bookmark . Url , UriKind . Absolute , out Uri uri ) )
133
- continue ;
164
+ return ;
134
165
135
166
var domain = uri . Host ;
136
167
@@ -150,12 +181,12 @@ ORDER BY i.width DESC -- Select largest icon available
150
181
151
182
using var reader = cmd . ExecuteReader ( ) ;
152
183
if ( ! reader . Read ( ) || reader . IsDBNull ( 0 ) )
153
- continue ;
184
+ return ;
154
185
155
186
var imageData = ( byte [ ] ) reader [ "data" ] ;
156
187
157
188
if ( imageData is not { Length : > 0 } )
158
- continue ;
189
+ return ;
159
190
160
191
string faviconPath ;
161
192
if ( IsSvgData ( imageData ) )
@@ -166,23 +197,31 @@ ORDER BY i.width DESC -- Select largest icon available
166
197
{
167
198
faviconPath = Path . Combine ( _faviconCacheDir , $ "firefox_{ domain } .png") ;
168
199
}
169
- SaveBitmapData ( imageData , faviconPath ) ;
200
+
201
+ // Filter out duplicate favicons
202
+ if ( savedPaths . TryAdd ( faviconPath , true ) )
203
+ {
204
+ SaveBitmapData ( imageData , faviconPath ) ;
205
+ }
170
206
171
207
bookmark . FaviconPath = faviconPath ;
172
208
}
173
209
catch ( Exception ex )
174
210
{
175
211
Main . _context . API . LogException ( ClassName , $ "Failed to extract Firefox favicon: { bookmark . Url } ", ex ) ;
176
212
}
177
- }
178
-
179
- // https://github.com/dotnet/efcore/issues/26580
180
- SqliteConnection . ClearPool ( connection ) ;
181
- connection . Close ( ) ;
213
+ finally
214
+ {
215
+ // https://github.com/dotnet/efcore/issues/26580
216
+ SqliteConnection . ClearPool ( connection ) ;
217
+ connection . Close ( ) ;
218
+ connection . Dispose ( ) ;
219
+ }
220
+ } ) ;
182
221
}
183
222
catch ( Exception ex )
184
223
{
185
- Main . _context . API . LogException ( ClassName , $ "Failed to load Firefox favicon DB: { faviconDbPath } ", ex ) ;
224
+ Main . _context . API . LogException ( ClassName , $ "Failed to load Firefox favicon DB: { tempDbPath } ", ex ) ;
186
225
}
187
226
188
227
// Delete temporary file
0 commit comments