Skip to content

Commit ab81a90

Browse files
committed
BrowserBookmarks improved
1 parent 8ecba5f commit ab81a90

18 files changed

+272
-162
lines changed

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Flow.Launcher.Plugin.BrowserBookmarks.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@
110110

111111
<ItemGroup>
112112
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
113-
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.5" />
113+
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
114114
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.9" />
115-
<PackageReference Include="Svg.Skia" Version="3.0.6" />
115+
<PackageReference Include="Svg.Skia" Version="3.2.1" />
116116
<PackageReference Include="SkiaSharp" Version="3.119.0" />
117117
</ItemGroup>
118118

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/en.xaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
<system:String x:Key="flowlauncher_plugin_browserbookmark_addBrowserBookmark">Add</system:String>
1919
<system:String x:Key="flowlauncher_plugin_browserbookmark_editBrowserBookmark">Edit</system:String>
2020
<system:String x:Key="flowlauncher_plugin_browserbookmark_removeBrowserBookmark">Delete</system:String>
21-
<system:String x:Key="flowlauncher_plugin_browserbookmark_enable_favicons">Enable favicons (requires more memory)</system:String>
22-
<system:String x:Key="flowlauncher_plugin_browserbookmark_fetch_favicons">Fetch missing favicons from the web (can be slow)</system:String>
21+
<system:String x:Key="flowlauncher_plugin_browserbookmark_enable_favicons">Enable favicons</system:String>
22+
<system:String x:Key="flowlauncher_plugin_browserbookmark_fetch_favicons">Fetch missing favicons from the web</system:String>
2323

2424
<!-- Custom Browser Window -->
2525
<system:String x:Key="flowlauncher_plugin_browserbookmark_bookmarkDataSetting">Custom Browser Setting</system:String>

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Main.cs

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#nullable enable
12
using Flow.Launcher.Plugin.BrowserBookmarks.Models;
23
using System;
34
using System.Collections.Generic;
@@ -16,12 +17,12 @@ namespace Flow.Launcher.Plugin.BrowserBookmarks;
1617

1718
public class Main : ISettingProvider, IPlugin, IAsyncReloadable, IPluginI18n, IContextMenu, IDisposable
1819
{
19-
internal static PluginInitContext Context { get; set; }
20-
private static Settings _settings;
20+
internal static PluginInitContext Context { get; set; } = null!;
21+
private static Settings _settings = null!;
2122

22-
private BookmarkLoaderService _bookmarkLoader;
23-
private FaviconService _faviconService;
24-
private BookmarkWatcherService _bookmarkWatcher;
23+
private BookmarkLoaderService _bookmarkLoader = null!;
24+
private FaviconService _faviconService = null!;
25+
private BookmarkWatcherService _bookmarkWatcher = null!;
2526

2627
private List<Bookmark> _bookmarks = new();
2728
private readonly CancellationTokenSource _cancellationTokenSource = new();
@@ -33,38 +34,33 @@ public void Init(PluginInitContext context)
3334
_settings.PropertyChanged += OnSettingsPropertyChanged;
3435
_settings.CustomBrowsers.CollectionChanged += OnCustomBrowsersChanged;
3536

36-
CleanupOrphanedCacheFiles();
37+
var tempPath = SetupTempDirectory();
3738

38-
_bookmarkLoader = new BookmarkLoaderService(Context, _settings);
39-
_faviconService = new FaviconService(Context, _settings);
39+
_bookmarkLoader = new BookmarkLoaderService(Context, _settings, tempPath);
40+
_faviconService = new FaviconService(Context, _settings, tempPath);
4041
_bookmarkWatcher = new BookmarkWatcherService();
4142
_bookmarkWatcher.OnBookmarkFileChanged += OnBookmarkFileChanged;
4243

4344
// Fire and forget the initial load to make Flow's UI responsive immediately.
4445
_ = ReloadDataAsync();
4546
}
4647

47-
private void CleanupOrphanedCacheFiles()
48+
private string SetupTempDirectory()
4849
{
50+
var tempPath = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, "Temp");
4951
try
5052
{
51-
var cachePath = Context.CurrentPluginMetadata.PluginCacheDirectoryPath;
52-
if (!Directory.Exists(cachePath)) return;
53-
54-
var files = Directory.GetFiles(cachePath);
55-
foreach (var file in files)
53+
if (Directory.Exists(tempPath))
5654
{
57-
var extension = Path.GetExtension(file);
58-
if (extension is ".db" or ".sqlite" or ".db-shm" or ".db-wal" or ".sqlite-shm" or ".sqlite-wal")
59-
{
60-
File.Delete(file);
61-
}
55+
Directory.Delete(tempPath, true);
6256
}
57+
Directory.CreateDirectory(tempPath);
6358
}
6459
catch (Exception e)
6560
{
66-
Context.API.LogException(nameof(Main), "Failed to clean up orphaned cache files.", e);
61+
Context.API.LogException(nameof(Main), "Failed to set up temporary directory.", e);
6762
}
63+
return tempPath;
6864
}
6965

7066
public List<Result> Query(Query query)
@@ -95,7 +91,7 @@ public List<Result> Query(Query query)
9591
{
9692
Title = bookmark.Name,
9793
SubTitle = bookmark.Url,
98-
IcoPath = !string.IsNullOrEmpty(bookmark.FaviconPath) && File.Exists(bookmark.FaviconPath)
94+
IcoPath = !string.IsNullOrEmpty(bookmark.FaviconPath)
9995
? bookmark.FaviconPath
10096
: @"Images\bookmark.png",
10197
Score = score,
@@ -142,12 +138,12 @@ public Control CreateSettingPanel()
142138

143139
public string GetTranslatedPluginTitle()
144140
{
145-
return Context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name");
141+
return Localize.flowlauncher_plugin_browserbookmark_plugin_name();
146142
}
147143

148144
public string GetTranslatedPluginDescription()
149145
{
150-
return Context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description");
146+
return Localize.flowlauncher_plugin_browserbookmark_plugin_description();
151147
}
152148

153149
public List<Result> LoadContextMenus(Result selectedResult)
@@ -159,8 +155,8 @@ public List<Result> LoadContextMenus(Result selectedResult)
159155
{
160156
new()
161157
{
162-
Title = Context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"),
163-
SubTitle = Context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"),
158+
Title = Localize.flowlauncher_plugin_browserbookmark_copyurl_title(),
159+
SubTitle = Localize.flowlauncher_plugin_browserbookmark_copyurl_subtitle(),
164160
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8"),
165161
IcoPath = @"Images\copylink.png",
166162
Action = _ =>
@@ -173,7 +169,7 @@ public List<Result> LoadContextMenus(Result selectedResult)
173169
catch(Exception ex)
174170
{
175171
Context.API.LogException(nameof(Main), "Failed to copy URL to clipboard", ex);
176-
Context.API.ShowMsgError(Context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copy_failed"));
172+
Context.API.ShowMsgError(Localize.flowlauncher_plugin_browserbookmark_copy_failed());
177173
return false;
178174
}
179175
}

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/BaseModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#nullable enable
12
using System.ComponentModel;
23
using System.Runtime.CompilerServices;
34

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/Bookmark.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#nullable enable
12
using System;
23

34
namespace Flow.Launcher.Plugin.BrowserBookmarks.Models;

Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/Settings.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public class Settings : BaseModel
77
private bool _loadChromeBookmark = true;
88
private bool _loadFirefoxBookmark = true;
99
private bool _loadEdgeBookmark = true;
10+
private bool _loadChromiumBookmark = true;
1011
private bool _enableFavicons = true;
1112
private bool _fetchMissingFavicons = false;
1213

@@ -28,6 +29,12 @@ public bool LoadEdgeBookmark
2829
set { _loadEdgeBookmark = value; OnPropertyChanged(); }
2930
}
3031

32+
public bool LoadChromiumBookmark
33+
{
34+
get => _loadChromiumBookmark;
35+
set { _loadChromiumBookmark = value; OnPropertyChanged(); }
36+
}
37+
3138
public bool EnableFavicons
3239
{
3340
get => _enableFavicons;
Lines changed: 36 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
#nullable enable
12
using Flow.Launcher.Plugin.BrowserBookmarks.Models;
23
using System;
34
using System.Collections.Concurrent;
45
using System.Collections.Generic;
56
using System.IO;
67
using System.Linq;
7-
using System.Text.RegularExpressions;
88
using System.Threading;
99
using System.Threading.Tasks;
1010

@@ -14,14 +14,16 @@ public class BookmarkLoaderService
1414
{
1515
private readonly PluginInitContext _context;
1616
private readonly Settings _settings;
17+
private readonly string _tempPath;
1718

1819
// This will hold the actual paths to the bookmark files for the watcher service
1920
public List<string> DiscoveredBookmarkFiles { get; } = new();
2021

21-
public BookmarkLoaderService(PluginInitContext context, Settings settings)
22+
public BookmarkLoaderService(PluginInitContext context, Settings settings, string tempPath)
2223
{
2324
_context = context;
2425
_settings = settings;
26+
_tempPath = tempPath;
2527
}
2628

2729
public async Task<List<Bookmark>> LoadBookmarksAsync(CancellationToken cancellationToken)
@@ -45,7 +47,7 @@ public async Task<List<Bookmark>> LoadBookmarksAsync(CancellationToken cancellat
4547
}
4648
catch (Exception e)
4749
{
48-
_context.API.LogException(nameof(BookmarkLoaderService), $"Failed to load bookmarks from a source.", e);
50+
_context.API.LogException(nameof(BookmarkLoaderService), $"Failed to load bookmarks from {loader.Name}.", e);
4951
}
5052
});
5153

@@ -72,22 +74,41 @@ private IEnumerable<IBookmarkLoader> GetBookmarkLoaders()
7274
yield return new ChromiumBookmarkLoader("Microsoft Edge", path, logAction, DiscoveredBookmarkFiles);
7375
}
7476

77+
if (_settings.LoadChromiumBookmark)
78+
{
79+
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Chromium\User Data");
80+
if(Directory.Exists(path))
81+
yield return new ChromiumBookmarkLoader("Chromium", path, logAction, DiscoveredBookmarkFiles);
82+
}
83+
7584
if (_settings.LoadFirefoxBookmark)
7685
{
77-
// Standard MSI installer path
78-
var placesPath = GetFirefoxPlacesPathFromProfileDir(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox"));
86+
string? placesPath = null;
87+
try
88+
{
89+
placesPath = FirefoxProfileFinder.GetFirefoxPlacesPath();
90+
}
91+
catch (Exception ex)
92+
{
93+
_context.API.LogException(nameof(BookmarkLoaderService), "Failed to find Firefox profile", ex);
94+
}
7995
if (!string.IsNullOrEmpty(placesPath))
8096
{
81-
// Do not add Firefox places.sqlite to the watcher as it's updated constantly for history.
82-
yield return new FirefoxBookmarkLoader("Firefox", placesPath, _context.CurrentPluginMetadata.PluginCacheDirectoryPath, logAction);
97+
yield return new FirefoxBookmarkLoader("Firefox", placesPath, _tempPath, logAction);
8398
}
8499

85-
// MSIX (Microsoft Store) installer path
86-
var msixPlacesPath = GetFirefoxMsixPlacesPath();
100+
string? msixPlacesPath = null;
101+
try
102+
{
103+
msixPlacesPath = FirefoxProfileFinder.GetFirefoxMsixPlacesPath();
104+
}
105+
catch (Exception ex)
106+
{
107+
_context.API.LogException(nameof(BookmarkLoaderService), "Failed to find Firefox MSIX package", ex);
108+
}
87109
if (!string.IsNullOrEmpty(msixPlacesPath))
88110
{
89-
// Do not add Firefox places.sqlite to the watcher as it's updated constantly for history.
90-
yield return new FirefoxBookmarkLoader("Firefox (Store)", msixPlacesPath, _context.CurrentPluginMetadata.PluginCacheDirectoryPath, logAction);
111+
yield return new FirefoxBookmarkLoader("Firefox (Store)", msixPlacesPath, _tempPath, logAction);
91112
}
92113
}
93114

@@ -105,73 +126,19 @@ private IEnumerable<IBookmarkLoader> GetBookmarkLoaders()
105126
yield return loader;
106127
}
107128
}
108-
129+
109130
private IBookmarkLoader CreateCustomFirefoxLoader(string name, string dataDirectoryPath)
110131
{
111132
var logAction = (string tag, string msg, Exception? ex) => _context.API.LogException(tag, msg, ex);
112133
// Custom Firefox paths might point to the root profile dir (e.g. ...\Mozilla\Firefox)
113-
var placesPath = GetFirefoxPlacesPathFromProfileDir(dataDirectoryPath);
134+
var placesPath = FirefoxProfileFinder.GetPlacesPathFromProfileDir(dataDirectoryPath);
114135
if (string.IsNullOrEmpty(placesPath))
115136
{
116137
// Or they might point directly to a profile folder (e.g. ...\Profiles\xyz.default-release)
117138
placesPath = Path.Combine(dataDirectoryPath, "places.sqlite");
118139
}
119-
120-
// Do not add Firefox places.sqlite to the watcher as it's updated constantly for history.
121-
return new FirefoxBookmarkLoader(name, placesPath, _context.CurrentPluginMetadata.PluginCacheDirectoryPath, logAction);
122-
}
123-
124-
private string GetFirefoxMsixPlacesPath()
125-
{
126-
var packagesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Packages");
127-
if (!Directory.Exists(packagesPath)) return string.Empty;
128140

129-
try
130-
{
131-
var firefoxPackageFolder = Directory.EnumerateDirectories(packagesPath, "Mozilla.Firefox*", SearchOption.TopDirectoryOnly).FirstOrDefault();
132-
if (firefoxPackageFolder == null) return string.Empty;
133-
134-
var profileFolderPath = Path.Combine(firefoxPackageFolder, @"LocalCache\Roaming\Mozilla\Firefox");
135-
return GetFirefoxPlacesPathFromProfileDir(profileFolderPath);
136-
}
137-
catch (Exception ex)
138-
{
139-
_context.API.LogException(nameof(BookmarkLoaderService), "Failed to find Firefox MSIX package", ex);
140-
return string.Empty;
141-
}
142-
}
143-
144-
private static string GetFirefoxPlacesPathFromProfileDir(string profileFolderPath)
145-
{
146-
var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
147-
if (!File.Exists(profileIni))
148-
return string.Empty;
149-
150-
try
151-
{
152-
var iniContent = File.ReadAllText(profileIni);
153-
var profileSectionMatch = Regex.Match(iniContent, @"\[Profile[^\]]+\]\s*Name=default-release[\s\S]+?Path=([^\r\n]+)[\s\S]+?Default=1", RegexOptions.IgnoreCase);
154-
if (!profileSectionMatch.Success)
155-
{
156-
profileSectionMatch = Regex.Match(iniContent, @"\[Profile[^\]]+\][\s\S]+?Path=([^\r\n]+)[\s\S]+?Default=1", RegexOptions.IgnoreCase);
157-
}
158-
if (!profileSectionMatch.Success)
159-
{
160-
profileSectionMatch = Regex.Match(iniContent, @"\[Profile[^\]]+\][\s\S]+?Path=([^\r\n]+)");
161-
}
162-
if (!profileSectionMatch.Success) return string.Empty;
163-
164-
var path = profileSectionMatch.Groups[1].Value;
165-
var isRelative = !path.Contains(':');
166-
167-
var profilePath = isRelative ? Path.Combine(profileFolderPath, path.Replace('/', Path.DirectorySeparatorChar)) : path;
168-
var placesDb = Path.Combine(profilePath, "places.sqlite");
169-
170-
return File.Exists(placesDb) ? placesDb : string.Empty;
171-
}
172-
catch
173-
{
174-
return string.Empty;
175-
}
141+
// Do not add Firefox places.sqlite to the watcher as it's updated constantly for history.
142+
return new FirefoxBookmarkLoader(name, placesPath, _tempPath, logAction);
176143
}
177144
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#nullable enable
12
using Flow.Launcher.Plugin.BrowserBookmarks.Models;
23
using System;
34
using System.Collections.Generic;
@@ -16,6 +17,8 @@ public class ChromiumBookmarkLoader : IBookmarkLoader
1617
private readonly Action<string, string, Exception?> _logException;
1718
private readonly List<string> _discoveredFiles;
1819

20+
public string Name => _browserName;
21+
1922
public ChromiumBookmarkLoader(string browserName, string browserDataPath, Action<string, string, Exception?> logException, List<string> discoveredFiles)
2023
{
2124
_browserName = browserName;
@@ -57,7 +60,7 @@ public async IAsyncEnumerable<Bookmark> GetBookmarksAsync([EnumeratorCancellatio
5760
}
5861
catch(JsonException ex)
5962
{
60-
_logException(nameof(ChromiumBookmarkLoader), $"Failed to parse bookmarks file: {bookmarkPath}", ex);
63+
_logException(nameof(ChromiumBookmarkLoader), $"Failed to parse bookmarks file for {_browserName}: {bookmarkPath}", ex);
6164
}
6265

6366
foreach (var bookmark in bookmarks)
@@ -94,7 +97,7 @@ private void EnumerateFolderBookmark(JsonElement folderElement, ICollection<Book
9497
EnumerateFolderBookmark(subElement, bookmarks, source, profilePath);
9598
break;
9699
case "url":
97-
if (subElement.TryGetProperty("name", out var name) &&
100+
if (subElement.TryGetProperty("name", out var name) &&
98101
subElement.TryGetProperty("url", out var url) &&
99102
!string.IsNullOrEmpty(name.GetString()) &&
100103
!string.IsNullOrEmpty(url.GetString()))

0 commit comments

Comments
 (0)