33using System . IO ;
44using System . Text . Json ;
55using Flow . Launcher . Infrastructure . Logger ;
6+ using System ;
7+ using System . Data . SQLite ;
8+ using SkiaSharp ;
69
710namespace Flow . Launcher . Plugin . BrowserBookmark ;
811
912public abstract class ChromiumBookmarkLoader : IBookmarkLoader
1013{
14+ private readonly string _faviconCacheDir ;
15+
16+ protected ChromiumBookmarkLoader ( )
17+ {
18+ _faviconCacheDir = Path . Combine (
19+ Path . GetDirectoryName ( typeof ( ChromiumBookmarkLoader ) . Assembly . Location ) ,
20+ "FaviconCache" ) ;
21+ Directory . CreateDirectory ( _faviconCacheDir ) ;
22+ }
23+
1124 public abstract List < Bookmark > GetBookmarks ( ) ;
1225
1326 protected List < Bookmark > LoadBookmarks ( string browserDataPath , string name )
@@ -22,10 +35,30 @@ protected List<Bookmark> LoadBookmarks(string browserDataPath, string name)
2235 if ( ! File . Exists ( bookmarkPath ) )
2336 continue ;
2437
25- Main . RegisterBookmarkFile ( bookmarkPath ) ;
38+ // Register bookmark file monitoring (direct call to Main.RegisterBookmarkFile)
39+ try
40+ {
41+ if ( File . Exists ( bookmarkPath ) )
42+ {
43+ //Main.RegisterBookmarkFile(bookmarkPath);
44+ }
45+ }
46+ catch ( Exception ex )
47+ {
48+ Log . Exception ( $ "Failed to register bookmark file monitoring: { bookmarkPath } ", ex ) ;
49+ }
2650
2751 var source = name + ( Path . GetFileName ( profile ) == "Default" ? "" : $ " ({ Path . GetFileName ( profile ) } )") ;
28- bookmarks . AddRange ( LoadBookmarksFromFile ( bookmarkPath , source ) ) ;
52+ var profileBookmarks = LoadBookmarksFromFile ( bookmarkPath , source ) ;
53+
54+ // Load favicons after loading bookmarks
55+ var faviconDbPath = Path . Combine ( profile , "Favicons" ) ;
56+ if ( File . Exists ( faviconDbPath ) )
57+ {
58+ LoadFaviconsFromDb ( faviconDbPath , profileBookmarks ) ;
59+ }
60+
61+ bookmarks . AddRange ( profileBookmarks ) ;
2962 }
3063
3164 return bookmarks ;
@@ -52,8 +85,7 @@ private void EnumerateRoot(JsonElement rootElement, ICollection<Bookmark> bookma
5285 if ( folder . Value . ValueKind != JsonValueKind . Object )
5386 continue ;
5487
55- // Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details.
56- // If various exceptions start to build up here consider splitting this Loader into multiple separate ones.
88+ // Fix for Opera. It stores bookmarks slightly different than chrome.
5789 if ( folder . Name == "custom_root" )
5890 EnumerateRoot ( folder . Value , bookmarks , source ) ;
5991 else
@@ -91,4 +123,108 @@ private void EnumerateFolderBookmark(JsonElement folderElement, ICollection<Book
91123 }
92124 }
93125 }
126+
127+ private void LoadFaviconsFromDb ( string dbPath , List < Bookmark > bookmarks )
128+ {
129+ try
130+ {
131+ // Use a copy to avoid lock issues with the original file
132+ var tempDbPath = Path . Combine ( _faviconCacheDir , $ "tempfavicons_{ Guid . NewGuid ( ) } .db") ;
133+
134+ try
135+ {
136+ File . Copy ( dbPath , tempDbPath , true ) ;
137+ }
138+ catch ( Exception ex )
139+ {
140+ Log . Exception ( $ "Failed to copy favicon DB: { dbPath } ", ex ) ;
141+ return ;
142+ }
143+
144+ try
145+ {
146+ using var connection = new SQLiteConnection ( $ "Data Source={ tempDbPath } ;Version=3;Read Only=True;") ;
147+ connection . Open ( ) ;
148+
149+ foreach ( var bookmark in bookmarks )
150+ {
151+ try
152+ {
153+ var url = bookmark . Url ;
154+ if ( string . IsNullOrEmpty ( url ) ) continue ;
155+
156+ // Extract domain from URL
157+ if ( ! Uri . TryCreate ( url , UriKind . Absolute , out Uri uri ) )
158+ continue ;
159+
160+ var domain = uri . Host ;
161+
162+ using var cmd = connection . CreateCommand ( ) ;
163+ cmd . CommandText = @"
164+ SELECT f.id, b.image_data
165+ FROM favicons f
166+ JOIN favicon_bitmaps b ON f.id = b.icon_id
167+ JOIN icon_mapping m ON f.id = m.icon_id
168+ WHERE m.page_url LIKE @url
169+ ORDER BY b.width DESC
170+ LIMIT 1" ;
171+
172+ cmd . Parameters . AddWithValue ( "@url" , $ "%{ domain } %") ;
173+
174+ using var reader = cmd . ExecuteReader ( ) ;
175+ if ( reader . Read ( ) && ! reader . IsDBNull ( 1 ) )
176+ {
177+ var iconId = reader . GetInt64 ( 0 ) . ToString ( ) ;
178+ var imageData = ( byte [ ] ) reader [ "image_data" ] ;
179+
180+ if ( imageData != null && imageData . Length > 0 )
181+ {
182+ var faviconPath = Path . Combine ( _faviconCacheDir , $ "{ domain } _{ iconId } .png") ;
183+ if ( ! File . Exists ( faviconPath ) )
184+ {
185+ SaveBitmapData ( imageData , faviconPath ) ;
186+ }
187+ bookmark . FaviconPath = faviconPath ;
188+ }
189+ }
190+ }
191+ catch ( Exception ex )
192+ {
193+ Log . Exception ( $ "Failed to extract bookmark favicon: { bookmark . Url } ", ex ) ;
194+ }
195+ }
196+ }
197+ catch ( Exception ex )
198+ {
199+ Log . Exception ( $ "Failed to connect to SQLite: { tempDbPath } ", ex ) ;
200+ }
201+
202+ // Delete temporary file
203+ try { File . Delete ( tempDbPath ) ; } catch { /* Ignore */ }
204+ }
205+ catch ( Exception ex )
206+ {
207+ Log . Exception ( $ "Failed to load favicon DB: { dbPath } ", ex ) ;
208+ }
209+ }
210+
211+ private void SaveBitmapData ( byte [ ] imageData , string outputPath )
212+ {
213+ try
214+ {
215+ using var ms = new MemoryStream ( imageData ) ;
216+ using var bitmap = SKBitmap . Decode ( ms ) ;
217+ if ( bitmap != null )
218+ {
219+ using var image = SKImage . FromBitmap ( bitmap ) ;
220+ using var data = image . Encode ( SKEncodedImageFormat . Png , 100 ) ;
221+ using var fs = File . OpenWrite ( outputPath ) ;
222+ data . SaveTo ( fs ) ;
223+ }
224+ }
225+ catch ( Exception ex )
226+ {
227+ Log . Exception ( $ "Failed to save image: { outputPath } ", ex ) ;
228+ }
229+ }
94230}
0 commit comments