-
-
Notifications
You must be signed in to change notification settings - Fork 344
/
Copy pathChromiumBookmarkLoader.cs
232 lines (200 loc) · 7.9 KB
/
ChromiumBookmarkLoader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
using Flow.Launcher.Plugin.BrowserBookmark.Models;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Flow.Launcher.Infrastructure.Logger;
using System;
using Microsoft.Data.Sqlite;
namespace Flow.Launcher.Plugin.BrowserBookmark;
public abstract class ChromiumBookmarkLoader : IBookmarkLoader
{
private readonly string _faviconCacheDir;
protected ChromiumBookmarkLoader()
{
_faviconCacheDir = Path.Combine(
Path.GetDirectoryName(typeof(ChromiumBookmarkLoader).Assembly.Location),
"FaviconCache");
Directory.CreateDirectory(_faviconCacheDir);
}
public abstract List<Bookmark> GetBookmarks();
protected List<Bookmark> LoadBookmarks(string browserDataPath, string name)
{
var bookmarks = new List<Bookmark>();
if (!Directory.Exists(browserDataPath)) return bookmarks;
var paths = Directory.GetDirectories(browserDataPath);
foreach (var profile in paths)
{
var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!File.Exists(bookmarkPath))
continue;
// Register bookmark file monitoring (direct call to Main.RegisterBookmarkFile)
try
{
if (File.Exists(bookmarkPath))
{
//Main.RegisterBookmarkFile(bookmarkPath);
}
}
catch (Exception ex)
{
Log.Exception($"Failed to register bookmark file monitoring: {bookmarkPath}", ex);
}
var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})");
var profileBookmarks = LoadBookmarksFromFile(bookmarkPath, source);
// Load favicons after loading bookmarks
var faviconDbPath = Path.Combine(profile, "Favicons");
if (File.Exists(faviconDbPath))
{
LoadFaviconsFromDb(faviconDbPath, profileBookmarks);
}
bookmarks.AddRange(profileBookmarks);
}
return bookmarks;
}
protected static List<Bookmark> LoadBookmarksFromFile(string path, string source)
{
var bookmarks = new List<Bookmark>();
if (!File.Exists(path))
return bookmarks;
using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path));
if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement))
return bookmarks;
EnumerateRoot(rootElement, bookmarks, source);
return bookmarks;
}
private static void EnumerateRoot(JsonElement rootElement, ICollection<Bookmark> bookmarks, string source)
{
foreach (var folder in rootElement.EnumerateObject())
{
if (folder.Value.ValueKind != JsonValueKind.Object)
continue;
// Fix for Opera. It stores bookmarks slightly different than chrome.
if (folder.Name == "custom_root")
EnumerateRoot(folder.Value, bookmarks, source);
else
EnumerateFolderBookmark(folder.Value, bookmarks, source);
}
}
private static void EnumerateFolderBookmark(JsonElement folderElement, ICollection<Bookmark> bookmarks,
string source)
{
if (!folderElement.TryGetProperty("children", out var childrenElement))
return;
foreach (var subElement in childrenElement.EnumerateArray())
{
if (subElement.TryGetProperty("type", out var type))
{
switch (type.GetString())
{
case "folder":
case "workspace": // Edge Workspace
EnumerateFolderBookmark(subElement, bookmarks, source);
break;
default:
bookmarks.Add(new Bookmark(
subElement.GetProperty("name").GetString(),
subElement.GetProperty("url").GetString(),
source));
break;
}
}
else
{
Log.Error(
$"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}");
}
}
}
private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
{
try
{
// Use a copy to avoid lock issues with the original file
var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.db");
try
{
File.Copy(dbPath, tempDbPath, true);
}
catch (Exception ex)
{
Log.Exception($"Failed to copy favicon DB: {dbPath}", ex);
return;
}
try
{
using var connection = new SqliteConnection($"Data Source={tempDbPath}");
connection.Open();
foreach (var bookmark in bookmarks)
{
try
{
var url = bookmark.Url;
if (string.IsNullOrEmpty(url)) continue;
// Extract domain from URL
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
continue;
var domain = uri.Host;
using var cmd = connection.CreateCommand();
cmd.CommandText = @"
SELECT f.id, b.image_data
FROM favicons f
JOIN favicon_bitmaps b ON f.id = b.icon_id
JOIN icon_mapping m ON f.id = m.icon_id
WHERE m.page_url LIKE @url
ORDER BY b.width DESC
LIMIT 1";
cmd.Parameters.AddWithValue("@url", $"%{domain}%");
using var reader = cmd.ExecuteReader();
if (!reader.Read() || reader.IsDBNull(1))
continue;
var iconId = reader.GetInt64(0).ToString();
var imageData = (byte[])reader["image_data"];
if (imageData is not { Length: > 0 })
continue;
var faviconPath = Path.Combine(_faviconCacheDir, $"{domain}_{iconId}.png");
if (!File.Exists(faviconPath))
{
SaveBitmapData(imageData, faviconPath);
}
bookmark.FaviconPath = faviconPath;
}
catch (Exception ex)
{
Log.Exception($"Failed to extract bookmark favicon: {bookmark.Url}", ex);
}
}
// https://github.com/dotnet/efcore/issues/26580
SqliteConnection.ClearPool(connection);
connection.Close();
}
catch (Exception ex)
{
Log.Exception($"Failed to connect to SQLite: {tempDbPath}", ex);
}
// Delete temporary file
try
{
File.Delete(tempDbPath);
}
catch (Exception ex)
{
Log.Exception($"Failed to delete temporary favicon DB: {tempDbPath}", ex);
}
}
catch (Exception ex)
{
Log.Exception($"Failed to load favicon DB: {dbPath}", ex);
}
}
private static void SaveBitmapData(byte[] imageData, string outputPath)
{
try
{
File.WriteAllBytes(outputPath, imageData);
}
catch (Exception ex)
{
Log.Exception($"Failed to save image: {outputPath}", ex);
}
}
}