Skip to content

Commit 03fa058

Browse files
committed
refactor, dup code,
1 parent 70bf759 commit 03fa058

File tree

5 files changed

+146
-137
lines changed

5 files changed

+146
-137
lines changed

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FaviconService.cs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ public partial class FaviconService : IDisposable
3939
private static partial Regex BaseHrefRegex();
4040

4141
private record struct FaviconCandidate(string Url, int Score);
42+
private record struct FetchResult(string? TempPath, int Size);
4243

43-
public FaviconService(PluginInitContext context, Settings settings, string tempPath)
44+
public FaviconService(PluginInitContext context, Settings settings, string tempPath)
4445
{
4546
_context = context;
4647
_settings = settings;
@@ -127,9 +128,9 @@ private static string GetCachePath(string url, string cacheDir)
127128
using var overallCts = CancellationTokenSource.CreateLinkedTokenSource(token);
128129
overallCts.CancelAfter(TimeSpan.FromSeconds(10));
129130
var linkedToken = overallCts.Token;
130-
131-
(string? TempPath, int Size) icoResult = (null, -1);
132-
(string? TempPath, int Size) htmlResult = (null, -1);
131+
132+
FetchResult icoResult = default;
133+
FetchResult htmlResult = default;
133134

134135
try
135136
{
@@ -141,21 +142,11 @@ private static string GetCachePath(string url, string cacheDir)
141142
icoResult = icoTask.Result;
142143
htmlResult = htmlTask.Result;
143144

144-
string? winnerPath = null;
145-
if (htmlResult.Size >= 32)
146-
winnerPath = htmlResult.TempPath;
147-
else if (icoResult.Size >= 32)
148-
winnerPath = icoResult.TempPath;
149-
else if (htmlResult.Size > icoResult.Size)
150-
winnerPath = htmlResult.TempPath;
151-
else if (icoResult.Size >= 0)
152-
winnerPath = icoResult.TempPath;
153-
else if (htmlResult.Size >= 0)
154-
winnerPath = htmlResult.TempPath;
155-
156-
if (winnerPath != null)
145+
var bestResult = SelectBestFavicon(icoResult, htmlResult);
146+
147+
if (bestResult.TempPath != null)
157148
{
158-
File.Move(winnerPath, cachePath, true);
149+
File.Move(bestResult.TempPath, cachePath, true);
159150
_context.API.LogDebug(nameof(FaviconService), $"Favicon for {urlString} cached successfully.");
160151
return cachePath;
161152
}
@@ -176,18 +167,29 @@ private static string GetCachePath(string url, string cacheDir)
176167

177168
return null;
178169
}
170+
171+
private FetchResult SelectBestFavicon(FetchResult icoResult, FetchResult htmlResult)
172+
{
173+
if (htmlResult.Size >= 32) return htmlResult;
174+
if (icoResult.Size >= 32) return icoResult;
175+
if (htmlResult.Size > icoResult.Size) return htmlResult;
176+
// If sizes are equal, prefer ico as it's the standard. If htmlResult was better, it would likely have a larger size.
177+
if (icoResult.Size >= 0) return icoResult;
178+
if (htmlResult.Size >= 0) return htmlResult;
179+
return default;
180+
}
179181

180-
private async Task<(string? TempPath, int Size)> FetchAndProcessHtmlAsync(Uri pageUri, CancellationToken token)
182+
private async Task<FetchResult> FetchAndProcessHtmlAsync(Uri pageUri, CancellationToken token)
181183
{
182184
var bestCandidate = await GetBestCandidateFromHtmlAsync(pageUri, token);
183185
if (bestCandidate != null && Uri.TryCreate(bestCandidate.Value.Url, UriKind.Absolute, out var candidateUri))
184186
{
185187
return await FetchAndProcessUrlAsync(candidateUri, token);
186188
}
187-
return (null, -1);
189+
return default;
188190
}
189191

190-
private async Task<(string? TempPath, int Size)> FetchAndProcessUrlAsync(Uri faviconUri, CancellationToken token)
192+
private async Task<FetchResult> FetchAndProcessUrlAsync(Uri faviconUri, CancellationToken token)
191193
{
192194
var tempPath = Path.GetTempFileName();
193195
try
@@ -200,7 +202,7 @@ private static string GetCachePath(string url, string cacheDir)
200202
{
201203
_context.API.LogDebug(nameof(FaviconService), $"Fetch failed for {faviconUri} with status code {response.StatusCode}");
202204
File.Delete(tempPath);
203-
return (null, -1);
205+
return default;
204206
}
205207

206208
await using var contentStream = await response.Content.ReadAsStreamAsync(token);
@@ -210,7 +212,7 @@ private static string GetCachePath(string url, string cacheDir)
210212
{
211213
await File.WriteAllBytesAsync(tempPath, pngData, token);
212214
_context.API.LogDebug(nameof(FaviconService), $"Successfully processed favicon for {faviconUri} with original size {size}x{size}");
213-
return (tempPath, size);
215+
return new FetchResult(tempPath, size);
214216
}
215217

216218
_context.API.LogDebug(nameof(FaviconService), $"Failed to process or invalid image for {faviconUri}.");
@@ -222,7 +224,7 @@ private static string GetCachePath(string url, string cacheDir)
222224
}
223225

224226
File.Delete(tempPath);
225-
return (null, -1);
227+
return default;
226228
}
227229

228230
private async Task<FaviconCandidate?> GetBestCandidateFromHtmlAsync(Uri pageUri, CancellationToken token)

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/LocalFaviconExtractor.cs

Lines changed: 45 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -29,64 +29,41 @@ var s when s.Contains("Firefox") => await GetFirefoxFaviconAsync(bookmark, token
2929
};
3030
}
3131

32-
private async Task<byte[]?> GetChromiumFaviconAsync(Bookmark bookmark, CancellationToken token)
32+
private Task<byte[]?> GetChromiumFaviconAsync(Bookmark bookmark, CancellationToken token)
3333
{
34-
var dbPath = Path.Combine(bookmark.ProfilePath, "Favicons");
35-
if (!File.Exists(dbPath)) return null;
34+
const string query = @"
35+
SELECT b.image_data FROM favicon_bitmaps b
36+
JOIN icon_mapping m ON b.icon_id = m.icon_id
37+
WHERE m.page_url = @url
38+
ORDER BY b.width DESC LIMIT 1";
3639

37-
var tempDbPath = Path.Combine(_tempPath, $"chromium_favicons_{Guid.NewGuid()}.db");
38-
try
39-
{
40-
File.Copy(dbPath, tempDbPath, true);
41-
42-
var query = @"
43-
SELECT b.image_data FROM favicon_bitmaps b
44-
JOIN icon_mapping m ON b.icon_id = m.icon_id
45-
WHERE m.page_url = @url
46-
ORDER BY b.width DESC LIMIT 1";
47-
48-
var connectionString = $"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false;";
49-
await using var connection = new SqliteConnection(connectionString);
50-
await connection.OpenAsync(token);
51-
await using var cmd = connection.CreateCommand();
52-
cmd.CommandText = query;
53-
cmd.Parameters.AddWithValue("@url", bookmark.Url);
54-
55-
var result = await cmd.ExecuteScalarAsync(token);
56-
if (result is byte[] data && data.Length > 0)
57-
{
58-
_context.API.LogDebug(nameof(LocalFaviconExtractor), $"Extracted {data.Length} bytes for {bookmark.Url} from Chromium DB.");
59-
return data;
60-
}
61-
return null;
62-
}
63-
catch (Exception ex)
64-
{
65-
_context.API.LogException(nameof(LocalFaviconExtractor), $"Failed to extract Chromium favicon for {bookmark.Url} from {bookmark.Source}", ex);
66-
return null;
67-
}
68-
finally
69-
{
70-
CleanupTempFiles(tempDbPath);
71-
}
40+
return GetFaviconFromDbAsync(bookmark, "Favicons", query, null, token);
7241
}
7342

74-
private async Task<byte[]?> GetFirefoxFaviconAsync(Bookmark bookmark, CancellationToken token)
43+
private Task<byte[]?> GetFirefoxFaviconAsync(Bookmark bookmark, CancellationToken token)
7544
{
76-
var dbPath = Path.Combine(bookmark.ProfilePath, "favicons.sqlite");
77-
if (!File.Exists(dbPath)) return null;
45+
const string query = @"
46+
SELECT i.data FROM moz_icons i
47+
JOIN moz_icons_to_pages ip ON i.id = ip.icon_id
48+
JOIN moz_pages_w_icons p ON ip.page_id = p.id
49+
WHERE p.page_url = @url
50+
ORDER BY i.width DESC LIMIT 1";
51+
52+
return GetFaviconFromDbAsync(bookmark, "favicons.sqlite", query, PostProcessFirefoxFavicon, token);
53+
}
7854

79-
var tempDbPath = Path.Combine(_tempPath, $"firefox_favicons_{Guid.NewGuid()}.sqlite");
55+
private async Task<byte[]?> GetFaviconFromDbAsync(Bookmark bookmark, string dbFileName, string query,
56+
Func<byte[], CancellationToken, Task<byte[]>>? postProcessor, CancellationToken token)
57+
{
58+
var dbPath = Path.Combine(bookmark.ProfilePath, dbFileName);
59+
if (!File.Exists(dbPath))
60+
return null;
61+
62+
var tempDbPath = Path.Combine(_tempPath, $"{Path.GetFileNameWithoutExtension(dbFileName)}_{Guid.NewGuid()}{Path.GetExtension(dbFileName)}");
63+
8064
try
8165
{
8266
File.Copy(dbPath, tempDbPath, true);
83-
84-
var query = @"
85-
SELECT i.data FROM moz_icons i
86-
JOIN moz_icons_to_pages ip ON i.id = ip.icon_id
87-
JOIN moz_pages_w_icons p ON ip.page_id = p.id
88-
WHERE p.page_url = @url
89-
ORDER BY i.width DESC LIMIT 1";
9067

9168
var connectionString = $"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false;";
9269
await using var connection = new SqliteConnection(connectionString);
@@ -95,27 +72,16 @@ SELECT i.data FROM moz_icons i
9572
cmd.CommandText = query;
9673
cmd.Parameters.AddWithValue("@url", bookmark.Url);
9774

98-
var result = await cmd.ExecuteScalarAsync(token);
99-
if (result is not byte[] imageData || imageData.Length == 0)
75+
if (await cmd.ExecuteScalarAsync(token) is not byte[] data || data.Length == 0)
10076
return null;
101-
102-
_context.API.LogDebug(nameof(LocalFaviconExtractor), $"Extracted {imageData.Length} bytes for {bookmark.Url} from Firefox DB.");
10377

104-
// Handle old GZipped favicons
105-
if (imageData.Length > 2 && imageData[0] == 0x1f && imageData[1] == 0x8b)
106-
{
107-
await using var inputStream = new MemoryStream(imageData);
108-
await using var gZipStream = new GZipStream(inputStream, CompressionMode.Decompress);
109-
await using var outputStream = new MemoryStream();
110-
await gZipStream.CopyToAsync(outputStream, token);
111-
return outputStream.ToArray();
112-
}
78+
_context.API.LogDebug(nameof(LocalFaviconExtractor), $"Extracted {data.Length} bytes for {bookmark.Url} from {dbFileName}.");
11379

114-
return imageData;
80+
return postProcessor != null ? await postProcessor(data, token) : data;
11581
}
11682
catch (Exception ex)
11783
{
118-
_context.API.LogException(nameof(LocalFaviconExtractor), $"Failed to extract Firefox favicon for {bookmark.Url} from {bookmark.Source}", ex);
84+
_context.API.LogException(nameof(LocalFaviconExtractor), $"Failed to extract favicon for {bookmark.Url} from {bookmark.Source}'s {dbFileName}", ex);
11985
return null;
12086
}
12187
finally
@@ -124,6 +90,21 @@ SELECT i.data FROM moz_icons i
12490
}
12591
}
12692

93+
private async Task<byte[]> PostProcessFirefoxFavicon(byte[] imageData, CancellationToken token)
94+
{
95+
// Handle old GZipped favicons
96+
if (imageData.Length > 2 && imageData[0] == 0x1f && imageData[1] == 0x8b)
97+
{
98+
await using var inputStream = new MemoryStream(imageData);
99+
await using var gZipStream = new GZipStream(inputStream, CompressionMode.Decompress);
100+
await using var outputStream = new MemoryStream();
101+
await gZipStream.CopyToAsync(outputStream, token);
102+
return outputStream.ToArray();
103+
}
104+
105+
return imageData;
106+
}
107+
127108
private void CleanupTempFiles(string mainTempDbPath)
128109
{
129110
// This method ensures that the main temp file and any of its associated files
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#nullable enable
2+
using CommunityToolkit.Mvvm.ComponentModel;
3+
using CommunityToolkit.Mvvm.Input;
4+
using Flow.Launcher.Plugin.BrowserBookmarks.Models;
5+
using System;
6+
using System.Windows.Forms;
7+
8+
namespace Flow.Launcher.Plugin.BrowserBookmarks.ViewModels;
9+
10+
public partial class CustomBrowserSettingViewModel : ObservableObject
11+
{
12+
private readonly CustomBrowser _originalBrowser;
13+
private readonly Action<bool> _closeAction;
14+
15+
[ObservableProperty]
16+
private CustomBrowser _editableBrowser;
17+
18+
public CustomBrowserSettingViewModel(CustomBrowser browser, Action<bool> closeAction)
19+
{
20+
_originalBrowser = browser;
21+
_closeAction = closeAction;
22+
EditableBrowser = new CustomBrowser
23+
{
24+
Name = browser.Name,
25+
DataDirectoryPath = browser.DataDirectoryPath,
26+
BrowserType = browser.BrowserType
27+
};
28+
}
29+
30+
[RelayCommand]
31+
private void Save()
32+
{
33+
_originalBrowser.Name = EditableBrowser.Name;
34+
_originalBrowser.DataDirectoryPath = EditableBrowser.DataDirectoryPath;
35+
_originalBrowser.BrowserType = EditableBrowser.BrowserType;
36+
_closeAction(true);
37+
}
38+
39+
[RelayCommand]
40+
private void Cancel()
41+
{
42+
_closeAction(false);
43+
}
44+
45+
[RelayCommand]
46+
private void BrowseDataDirectory()
47+
{
48+
var dialog = new FolderBrowserDialog();
49+
if (dialog.ShowDialog() == DialogResult.OK)
50+
{
51+
EditableBrowser.DataDirectoryPath = dialog.SelectedPath;
52+
}
53+
}
54+
}

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Views/CustomBrowserSetting.xaml

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
66
xmlns:local="clr-namespace:Flow.Launcher.Plugin.BrowserBookmarks.Models"
77
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
xmlns:viewModels="clr-namespace:Flow.Launcher.Plugin.BrowserBookmarks.ViewModels"
89
Title="{DynamicResource flowlauncher_plugin_browserbookmark_bookmarkDataSetting}"
910
Width="550"
11+
d:DataContext="{d:DesignInstance viewModels:CustomBrowserSettingViewModel}"
1012
Background="{DynamicResource PopuBGColor}"
1113
Foreground="{DynamicResource PopupTextColor}"
1214
KeyDown="WindowKeyDown"
@@ -18,9 +20,6 @@
1820
<WindowChrome CaptionHeight="32"
1921
ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
2022
</WindowChrome.WindowChrome>
21-
<Window.DataContext>
22-
<local:CustomBrowser />
23-
</Window.DataContext>
2423
<Grid>
2524
<Grid.RowDefinitions>
2625
<RowDefinition />
@@ -39,7 +38,7 @@
3938
</Grid.ColumnDefinitions>
4039
<Button
4140
Grid.Column="4"
42-
Click="CancelEditCustomBrowser"
41+
Command="{Binding CancelCommand}"
4342
Style="{StaticResource TitleBarCloseButtonStyle}">
4443
<Path
4544
Width="46"
@@ -127,7 +126,7 @@
127126
Margin="5 0 10 0"
128127
HorizontalAlignment="Left"
129128
VerticalAlignment="Center"
130-
Text="{Binding Name}" />
129+
Text="{Binding EditableBrowser.Name}" />
131130
<TextBlock
132131
Grid.Row="2"
133132
Grid.Column="0"
@@ -145,8 +144,8 @@
145144
HorizontalAlignment="Left"
146145
VerticalAlignment="Center"
147146
DisplayMemberPath="Display"
148-
ItemsSource="{Binding AllBrowserTypes}"
149-
SelectedValue="{Binding BrowserType}"
147+
ItemsSource="{Binding EditableBrowser.AllBrowserTypes}"
148+
SelectedValue="{Binding EditableBrowser.BrowserType}"
150149
SelectedValuePath="Value" />
151150
<TextBlock
152151
Grid.Row="3"
@@ -165,7 +164,7 @@
165164
MinWidth="100"
166165
Margin="5 10 0 0"
167166
VerticalContentAlignment="Stretch"
168-
Click="OnSelectPathClick"
167+
Command="{Binding BrowseDataDirectoryCommand}"
169168
Content="{DynamicResource flowlauncher_plugin_browserbookmark_browseBrowserBookmark}"
170169
DockPanel.Dock="Right" />
171170
<TextBox
@@ -174,7 +173,7 @@
174173
Margin="5 10 0 0"
175174
HorizontalAlignment="Stretch"
176175
VerticalAlignment="Center"
177-
Text="{Binding DataDirectoryPath}" />
176+
Text="{Binding EditableBrowser.DataDirectoryPath}" />
178177
</DockPanel>
179178
</Grid>
180179
</StackPanel>
@@ -190,13 +189,13 @@
190189
x:Name="btnCancel"
191190
MinWidth="145"
192191
Margin="0 0 5 0"
193-
Click="CancelEditCustomBrowser"
192+
Command="{Binding CancelCommand}"
194193
Content="{DynamicResource cancel}" />
195194
<Button
196195
Name="btnConfirm"
197196
MinWidth="145"
198197
Margin="5 0 0 0"
199-
Click="ConfirmEditCustomBrowser"
198+
Command="{Binding SaveCommand}"
200199
Style="{StaticResource AccentButtonStyle}">
201200
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
202201
</Button>

0 commit comments

Comments
 (0)