11using System ;
2+ using System . Collections . Concurrent ;
23using System . Collections . Generic ;
34using System . IO ;
45using System . Linq ;
6+ using System . Threading . Tasks ;
57using Flow . Launcher . Plugin . BrowserBookmark . Models ;
68using Microsoft . Data . Sqlite ;
79
@@ -30,8 +32,6 @@ INNER JOIN moz_bookmarks ON (
3032 ORDER BY moz_places.visit_count DESC
3133 """ ;
3234
33- private const string DbPathFormat = "Data Source={0}" ;
34-
3535 protected List < Bookmark > GetBookmarksFromPath ( string placesPath )
3636 {
3737 // Variable to store bookmark list
@@ -41,30 +41,32 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
4141 if ( string . IsNullOrEmpty ( placesPath ) || ! File . Exists ( placesPath ) )
4242 return bookmarks ;
4343
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+
4455 var tempDbPath = Path . Combine ( _faviconCacheDir , $ "tempplaces_{ Guid . NewGuid ( ) } .sqlite") ;
4556
4657 try
4758 {
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-
5859 // Use a copy to avoid lock issues with the original file
5960 File . Copy ( placesPath , tempDbPath , true ) ;
6061
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
6466 dbConnection . Open ( ) ;
6567 var reader = new SqliteCommand ( QueryAllBookmarks , dbConnection ) . ExecuteReader ( ) ;
6668
67- // Create bookmark list
69+ // Get results in List<Bookmark> format
6870 bookmarks = reader
6971 . Select (
7072 x => new Bookmark (
@@ -75,12 +77,20 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
7577 )
7678 . ToList ( ) ;
7779
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 )
8182 {
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+ }
8391 }
92+
93+ // Close the connection so that we can delete the temporary file
8494 // https://github.com/dotnet/efcore/issues/26580
8595 SqliteConnection . ClearPool ( dbConnection ) ;
8696 dbConnection . Close ( ) ;
@@ -93,7 +103,10 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
93103 // Delete temporary file
94104 try
95105 {
96- File . Delete ( tempDbPath ) ;
106+ if ( File . Exists ( tempDbPath ) )
107+ {
108+ File . Delete ( tempDbPath ) ;
109+ }
97110 }
98111 catch ( Exception ex )
99112 {
@@ -103,34 +116,52 @@ protected List<Bookmark> GetBookmarksFromPath(string placesPath)
103116 return bookmarks ;
104117 }
105118
106- private void LoadFaviconsFromDb ( string faviconDbPath , List < Bookmark > bookmarks )
119+ private void LoadFaviconsFromDb ( string dbPath , List < Bookmark > bookmarks )
107120 {
121+ // Use a copy to avoid lock issues with the original file
108122 var tempDbPath = Path . Combine ( _faviconCacheDir , $ "tempfavicons_{ Guid . NewGuid ( ) } .sqlite") ;
109123
110124 try
111125 {
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 =>
125152 {
153+ // Use read-only connection to avoid locking issues
154+ var connection = new SqliteConnection ( $ "Data Source={ tempDbPath } ;Mode=ReadOnly") ;
155+ connection . Open ( ) ;
156+
126157 try
127158 {
128159 if ( string . IsNullOrEmpty ( bookmark . Url ) )
129- continue ;
160+ return ;
130161
131162 // Extract domain from URL
132163 if ( ! Uri . TryCreate ( bookmark . Url , UriKind . Absolute , out Uri uri ) )
133- continue ;
164+ return ;
134165
135166 var domain = uri . Host ;
136167
@@ -150,12 +181,12 @@ ORDER BY i.width DESC -- Select largest icon available
150181
151182 using var reader = cmd . ExecuteReader ( ) ;
152183 if ( ! reader . Read ( ) || reader . IsDBNull ( 0 ) )
153- continue ;
184+ return ;
154185
155186 var imageData = ( byte [ ] ) reader [ "data" ] ;
156187
157188 if ( imageData is not { Length : > 0 } )
158- continue ;
189+ return ;
159190
160191 string faviconPath ;
161192 if ( IsSvgData ( imageData ) )
@@ -166,23 +197,31 @@ ORDER BY i.width DESC -- Select largest icon available
166197 {
167198 faviconPath = Path . Combine ( _faviconCacheDir , $ "firefox_{ domain } .png") ;
168199 }
169- SaveBitmapData ( imageData , faviconPath ) ;
200+
201+ // Filter out duplicate favicons
202+ if ( savedPaths . TryAdd ( faviconPath , true ) )
203+ {
204+ SaveBitmapData ( imageData , faviconPath ) ;
205+ }
170206
171207 bookmark . FaviconPath = faviconPath ;
172208 }
173209 catch ( Exception ex )
174210 {
175211 Main . _context . API . LogException ( ClassName , $ "Failed to extract Firefox favicon: { bookmark . Url } ", ex ) ;
176212 }
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+ } ) ;
182221 }
183222 catch ( Exception ex )
184223 {
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 ) ;
186225 }
187226
188227 // Delete temporary file
0 commit comments