|
1 | 1 | using System; |
2 | | -using System.Collections.Concurrent; |
3 | 2 | using System.Collections.Generic; |
4 | 3 | using System.IO; |
5 | 4 | using System.Text.Json; |
6 | | -using System.Threading.Tasks; |
7 | 5 | using Flow.Launcher.Plugin.BrowserBookmark.Helper; |
8 | 6 | using Flow.Launcher.Plugin.BrowserBookmark.Models; |
9 | 7 | using Microsoft.Data.Sqlite; |
@@ -78,10 +76,18 @@ protected static List<Bookmark> LoadBookmarksFromFile(string path, string source |
78 | 76 | if (!File.Exists(path)) |
79 | 77 | return bookmarks; |
80 | 78 |
|
81 | | - using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); |
82 | | - if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) |
83 | | - return bookmarks; |
84 | | - EnumerateRoot(rootElement, bookmarks, source); |
| 79 | + try |
| 80 | + { |
| 81 | + using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); |
| 82 | + if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) |
| 83 | + return bookmarks; |
| 84 | + EnumerateRoot(rootElement, bookmarks, source); |
| 85 | + } |
| 86 | + catch (JsonException e) |
| 87 | + { |
| 88 | + Main._context.API.LogException(ClassName, $"Failed to parse bookmarks file: {path}", e); |
| 89 | + } |
| 90 | + |
85 | 91 | return bookmarks; |
86 | 92 | } |
87 | 93 |
|
@@ -115,91 +121,41 @@ private static void EnumerateFolderBookmark(JsonElement folderElement, ICollecti |
115 | 121 | case "workspace": // Edge Workspace |
116 | 122 | EnumerateFolderBookmark(subElement, bookmarks, source); |
117 | 123 | break; |
118 | | - default: |
119 | | - bookmarks.Add(new Bookmark( |
120 | | - subElement.GetProperty("name").GetString(), |
121 | | - subElement.GetProperty("url").GetString(), |
122 | | - source)); |
| 124 | + case "url": |
| 125 | + if (subElement.TryGetProperty("name", out var name) && |
| 126 | + subElement.TryGetProperty("url", out var url)) |
| 127 | + { |
| 128 | + bookmarks.Add(new Bookmark(name.GetString(), url.GetString(), source)); |
| 129 | + } |
123 | 130 | break; |
124 | 131 | } |
125 | 132 | } |
126 | 133 | else |
127 | 134 | { |
128 | | - Main._context.API.LogError(ClassName, $"type property not found for {subElement.GetString()}"); |
| 135 | + Main._context.API.LogError(ClassName, $"type property not found for {subElement.ToString()}"); |
129 | 136 | } |
130 | 137 | } |
131 | 138 | } |
132 | 139 |
|
133 | 140 | private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks) |
134 | 141 | { |
135 | | - FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) => |
136 | | - { |
137 | | - // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates |
138 | | - var savedPaths = new ConcurrentDictionary<string, bool>(); |
139 | | - |
140 | | - // Get favicons based on bookmarks concurrently |
141 | | - Parallel.ForEach(bookmarks, bookmark => |
142 | | - { |
143 | | - // Use read-only connection to avoid locking issues |
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"); |
146 | | - connection.Open(); |
147 | | - |
148 | | - try |
149 | | - { |
150 | | - var url = bookmark.Url; |
151 | | - if (string.IsNullOrEmpty(url)) return; |
152 | | - |
153 | | - // Extract domain from URL |
154 | | - if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) |
155 | | - return; |
156 | | - |
157 | | - var domain = uri.Host; |
158 | | - |
159 | | - using var cmd = connection.CreateCommand(); |
160 | | - cmd.CommandText = @" |
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"; |
168 | | - |
169 | | - cmd.Parameters.AddWithValue("@url", $"%{domain}%"); |
170 | | - |
171 | | - using var reader = cmd.ExecuteReader(); |
172 | | - if (!reader.Read() || reader.IsDBNull(1)) |
173 | | - return; |
174 | | - |
175 | | - var iconId = reader.GetInt64(0).ToString(); |
176 | | - var imageData = (byte[])reader["image_data"]; |
177 | | - |
178 | | - if (imageData is not { Length: > 0 }) |
179 | | - return; |
180 | | - |
181 | | - var faviconPath = Path.Combine(_faviconCacheDir, $"chromium_{domain}_{iconId}.png"); |
182 | | - |
183 | | - // Filter out duplicate favicons |
184 | | - if (savedPaths.TryAdd(faviconPath, true)) |
185 | | - { |
186 | | - FaviconHelper.SaveBitmapData(imageData, faviconPath); |
187 | | - } |
188 | | - |
189 | | - bookmark.FaviconPath = faviconPath; |
190 | | - } |
191 | | - catch (Exception ex) |
192 | | - { |
193 | | - Main._context.API.LogException(ClassName, $"Failed to extract bookmark favicon: {bookmark.Url}", ex); |
194 | | - } |
195 | | - finally |
196 | | - { |
197 | | - // Cache connection and clear pool after all operations to avoid issue: |
198 | | - // ObjectDisposedException: Safe handle has been closed. |
199 | | - connection.Close(); |
200 | | - connection.Dispose(); |
201 | | - } |
202 | | - }); |
203 | | - }); |
| 142 | + const string sql = @" |
| 143 | + SELECT f.id, b.image_data |
| 144 | + FROM favicons f |
| 145 | + JOIN favicon_bitmaps b ON f.id = b.icon_id |
| 146 | + JOIN icon_mapping m ON f.id = m.icon_id |
| 147 | + WHERE m.page_url GLOB @pattern |
| 148 | + ORDER BY b.width DESC |
| 149 | + LIMIT 1"; |
| 150 | + |
| 151 | + FaviconHelper.ProcessFavicons( |
| 152 | + dbPath, |
| 153 | + _faviconCacheDir, |
| 154 | + bookmarks, |
| 155 | + sql, |
| 156 | + "http*", |
| 157 | + reader => (reader.GetInt64(0).ToString(), (byte[])reader["image_data"]), |
| 158 | + (uri, id, data) => Path.Combine(_faviconCacheDir, $"chromium_{uri.Host}_{id}.png") |
| 159 | + ); |
204 | 160 | } |
205 | 161 | } |
0 commit comments