Merge pull request #2652 from Flow-Launcher/code-cleanup/Flow.Launcher.Plugin.BrowserBookmark

Code cleanup: Flow.Launcher.Plugin.BrowserBookmark
This commit is contained in:
VictoriousRaptor 2024-05-01 11:41:59 +08:00 committed by GitHub
commit 3554136a55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 695 additions and 713 deletions

View file

@ -3,3 +3,6 @@ https
ssh
ubuntu
runcount
Firefox
Português
Português (Brasil)

View file

@ -74,6 +74,7 @@ WCA_ACCENT_POLICY
HGlobal
dopusrt
firefox
Firefox
msedge
svgc
ime

View file

@ -3,23 +3,22 @@ using System;
using System.Collections.Generic;
using System.IO;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class ChromeBookmarkLoader : ChromiumBookmarkLoader
{
public override List<Bookmark> GetBookmarks()
{
return LoadChromeBookmarks();
}
namespace Flow.Launcher.Plugin.BrowserBookmark;
private List<Bookmark> LoadChromeBookmarks()
{
var bookmarks = new List<Bookmark>();
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium"));
return bookmarks;
}
public class ChromeBookmarkLoader : ChromiumBookmarkLoader
{
public override List<Bookmark> GetBookmarks()
{
return LoadChromeBookmarks();
}
}
private List<Bookmark> LoadChromeBookmarks()
{
var bookmarks = new List<Bookmark>();
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium"));
return bookmarks;
}
}

View file

@ -4,91 +4,90 @@ using System.IO;
using System.Text.Json;
using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Plugin.BrowserBookmark
namespace Flow.Launcher.Plugin.BrowserBookmark;
public abstract class ChromiumBookmarkLoader : IBookmarkLoader
{
public abstract class ChromiumBookmarkLoader : IBookmarkLoader
public abstract List<Bookmark> GetBookmarks();
protected List<Bookmark> LoadBookmarks(string browserDataPath, string name)
{
public abstract List<Bookmark> GetBookmarks();
var bookmarks = new List<Bookmark>();
if (!Directory.Exists(browserDataPath)) return bookmarks;
var paths = Directory.GetDirectories(browserDataPath);
protected List<Bookmark> LoadBookmarks(string browserDataPath, string name)
foreach (var profile in paths)
{
var bookmarks = new List<Bookmark>();
if (!Directory.Exists(browserDataPath)) return bookmarks;
var paths = Directory.GetDirectories(browserDataPath);
var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!File.Exists(bookmarkPath))
continue;
foreach (var profile in paths)
{
var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!File.Exists(bookmarkPath))
continue;
Main.RegisterBookmarkFile(bookmarkPath);
Main.RegisterBookmarkFile(bookmarkPath);
var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})");
bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source));
}
var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})");
bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source));
}
return bookmarks;
}
protected List<Bookmark> LoadBookmarksFromFile(string path, string source)
{
var bookmarks = new List<Bookmark>();
if (!File.Exists(path))
return bookmarks;
}
protected 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);
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 void EnumerateRoot(JsonElement rootElement, ICollection<Bookmark> bookmarks, string source)
private void EnumerateRoot(JsonElement rootElement, ICollection<Bookmark> bookmarks, string source)
{
foreach (var folder in rootElement.EnumerateObject())
{
foreach (var folder in rootElement.EnumerateObject())
{
if (folder.Value.ValueKind != JsonValueKind.Object)
continue;
if (folder.Value.ValueKind != JsonValueKind.Object)
continue;
// Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details.
// If various exceptions start to build up here consider splitting this Loader into multiple separate ones.
if (folder.Name == "custom_root")
EnumerateRoot(folder.Value, bookmarks, source);
else
EnumerateFolderBookmark(folder.Value, bookmarks, source);
// Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details.
// If various exceptions start to build up here consider splitting this Loader into multiple separate ones.
if (folder.Name == "custom_root")
EnumerateRoot(folder.Value, bookmarks, source);
else
EnumerateFolderBookmark(folder.Value, bookmarks, source);
}
}
private 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;
}
}
}
private void EnumerateFolderBookmark(JsonElement folderElement, ICollection<Bookmark> bookmarks,
string source)
{
if (!folderElement.TryGetProperty("children", out var childrenElement))
return;
foreach (var subElement in childrenElement.EnumerateArray())
else
{
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()}");
}
Log.Error(
$"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}");
}
}
}

View file

@ -4,56 +4,55 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.BrowserBookmark.Models;
using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.Plugin.BrowserBookmark.Commands
namespace Flow.Launcher.Plugin.BrowserBookmark.Commands;
internal static class BookmarkLoader
{
internal static class BookmarkLoader
internal static MatchResult MatchProgram(Bookmark bookmark, string queryString)
{
internal static MatchResult MatchProgram(Bookmark bookmark, string queryString)
{
var match = StringMatcher.FuzzySearch(queryString, bookmark.Name);
if (match.IsSearchPrecisionScoreMet())
return match;
var match = StringMatcher.FuzzySearch(queryString, bookmark.Name);
if (match.IsSearchPrecisionScoreMet())
return match;
return StringMatcher.FuzzySearch(queryString, bookmark.Url);
return StringMatcher.FuzzySearch(queryString, bookmark.Url);
}
internal static List<Bookmark> LoadAllBookmarks(Settings setting)
{
var allBookmarks = new List<Bookmark>();
if (setting.LoadChromeBookmark)
{
// Add Chrome bookmarks
var chromeBookmarks = new ChromeBookmarkLoader();
allBookmarks.AddRange(chromeBookmarks.GetBookmarks());
}
internal static List<Bookmark> LoadAllBookmarks(Settings setting)
if (setting.LoadFirefoxBookmark)
{
var allBookmarks = new List<Bookmark>();
if (setting.LoadChromeBookmark)
{
// Add Chrome bookmarks
var chromeBookmarks = new ChromeBookmarkLoader();
allBookmarks.AddRange(chromeBookmarks.GetBookmarks());
}
if (setting.LoadFirefoxBookmark)
{
// Add Firefox bookmarks
var mozBookmarks = new FirefoxBookmarkLoader();
allBookmarks.AddRange(mozBookmarks.GetBookmarks());
}
if (setting.LoadEdgeBookmark)
{
// Add Edge (Chromium) bookmarks
var edgeBookmarks = new EdgeBookmarkLoader();
allBookmarks.AddRange(edgeBookmarks.GetBookmarks());
}
foreach (var browser in setting.CustomChromiumBrowsers)
{
IBookmarkLoader loader = browser.BrowserType switch
{
BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser),
BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser),
_ => new CustomChromiumBookmarkLoader(browser),
};
allBookmarks.AddRange(loader.GetBookmarks());
}
return allBookmarks.Distinct().ToList();
// Add Firefox bookmarks
var mozBookmarks = new FirefoxBookmarkLoader();
allBookmarks.AddRange(mozBookmarks.GetBookmarks());
}
if (setting.LoadEdgeBookmark)
{
// Add Edge (Chromium) bookmarks
var edgeBookmarks = new EdgeBookmarkLoader();
allBookmarks.AddRange(edgeBookmarks.GetBookmarks());
}
foreach (var browser in setting.CustomChromiumBrowsers)
{
IBookmarkLoader loader = browser.BrowserType switch
{
BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser),
BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser),
_ => new CustomChromiumBookmarkLoader(browser),
};
allBookmarks.AddRange(loader.GetBookmarks());
}
return allBookmarks.Distinct().ToList();
}
}

View file

@ -1,19 +1,18 @@
using Flow.Launcher.Plugin.BrowserBookmark.Models;
using System.Collections.Generic;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader
{
public CustomChromiumBookmarkLoader(CustomBrowser browser)
{
BrowserName = browser.Name;
BrowserDataPath = browser.DataDirectoryPath;
}
public string BrowserDataPath { get; init; }
public string BookmarkFilePath { get; init; }
public string BrowserName { get; init; }
namespace Flow.Launcher.Plugin.BrowserBookmark;
public override List<Bookmark> GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName);
public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader
{
public CustomChromiumBookmarkLoader(CustomBrowser browser)
{
BrowserName = browser.Name;
BrowserDataPath = browser.DataDirectoryPath;
}
}
public string BrowserDataPath { get; init; }
public string BookmarkFilePath { get; init; }
public string BrowserName { get; init; }
public override List<Bookmark> GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName);
}

View file

@ -2,26 +2,25 @@
using System.IO;
using Flow.Launcher.Plugin.BrowserBookmark.Models;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
{
public CustomFirefoxBookmarkLoader(CustomBrowser browser)
{
BrowserName = browser.Name;
BrowserDataPath = browser.DataDirectoryPath;
}
/// <summary>
/// Path to places.sqlite
/// </summary>
public string BrowserDataPath { get; init; }
public string BrowserName { get; init; }
namespace Flow.Launcher.Plugin.BrowserBookmark;
public override List<Bookmark> GetBookmarks()
{
return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite"));
}
public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
{
public CustomFirefoxBookmarkLoader(CustomBrowser browser)
{
BrowserName = browser.Name;
BrowserDataPath = browser.DataDirectoryPath;
}
/// <summary>
/// Path to places.sqlite
/// </summary>
public string BrowserDataPath { get; init; }
public string BrowserName { get; init; }
public override List<Bookmark> GetBookmarks()
{
return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite"));
}
}

View file

@ -3,21 +3,20 @@ using System;
using System.Collections.Generic;
using System.IO;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class EdgeBookmarkLoader : ChromiumBookmarkLoader
{
private List<Bookmark> LoadEdgeBookmarks()
{
var bookmarks = new List<Bookmark>();
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary"));
namespace Flow.Launcher.Plugin.BrowserBookmark;
return bookmarks;
}
public override List<Bookmark> GetBookmarks() => LoadEdgeBookmarks();
public class EdgeBookmarkLoader : ChromiumBookmarkLoader
{
private List<Bookmark> LoadEdgeBookmarks()
{
var bookmarks = new List<Bookmark>();
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary"));
return bookmarks;
}
}
public override List<Bookmark> GetBookmarks() => LoadEdgeBookmarks();
}

View file

@ -5,140 +5,133 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Flow.Launcher.Plugin.BrowserBookmark
namespace Flow.Launcher.Plugin.BrowserBookmark;
public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
{
public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
{
public abstract List<Bookmark> GetBookmarks();
public abstract List<Bookmark> GetBookmarks();
private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title
FROM moz_places
INNER JOIN moz_bookmarks ON (
private const string QueryAllBookmarks = """
SELECT moz_places.url, moz_bookmarks.title
FROM moz_places
INNER JOIN moz_bookmarks ON (
moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id
)
ORDER BY moz_places.visit_count DESC
";
)
ORDER BY moz_places.visit_count DESC
""";
private const string dbPathFormat = "Data Source ={0}";
private const string DbPathFormat = "Data Source ={0}";
protected static List<Bookmark> GetBookmarksFromPath(string placesPath)
{
// Return empty list if the places.sqlite file cannot be found
if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath))
return new List<Bookmark>();
protected static List<Bookmark> GetBookmarksFromPath(string placesPath)
{
// Return empty list if the places.sqlite file cannot be found
if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath))
return new List<Bookmark>();
var bookmarkList = new List<Bookmark>();
Main.RegisterBookmarkFile(placesPath);
Main.RegisterBookmarkFile(placesPath);
// create the connection string and init the connection
string dbPath = string.Format(DbPathFormat, placesPath);
using var dbConnection = new SqliteConnection(dbPath);
// Open connection to the database file and execute the query
dbConnection.Open();
var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader();
// create the connection string and init the connection
string dbPath = string.Format(dbPathFormat, placesPath);
using var dbConnection = new SqliteConnection(dbPath);
// Open connection to the database file and execute the query
dbConnection.Open();
var reader = new SqliteCommand(queryAllBookmarks, dbConnection).ExecuteReader();
// return results in List<Bookmark> format
bookmarkList = reader.Select(
x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(),
x["url"].ToString())
).ToList();
// return results in List<Bookmark> format
return reader
.Select(
x => new Bookmark(
x["title"] is DBNull ? string.Empty : x["title"].ToString(),
x["url"].ToString()
)
)
.ToList();
}
}
return bookmarkList;
}
public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
{
/// <summary>
/// Searches the places.sqlite db and returns all bookmarks
/// </summary>
public override List<Bookmark> GetBookmarks()
{
return GetBookmarksFromPath(PlacesPath);
}
public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
/// <summary>
/// Path to places.sqlite
/// </summary>
private string PlacesPath
{
/// <summary>
/// Searches the places.sqlite db and returns all bookmarks
/// </summary>
public override List<Bookmark> GetBookmarks()
get
{
return GetBookmarksFromPath(PlacesPath);
}
/// <summary>
/// Path to places.sqlite
/// </summary>
private string PlacesPath
{
get
{
var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox");
var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox");
var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
if (!File.Exists(profileIni))
return string.Empty;
if (!File.Exists(profileIni))
return string.Empty;
// get firefox default profile directory from profiles.ini
string ini;
using (var sReader = new StreamReader(profileIni))
{
ini = sReader.ReadToEnd();
}
// get firefox default profile directory from profiles.ini
using var sReader = new StreamReader(profileIni);
var ini = sReader.ReadToEnd();
/*
Current profiles.ini structure example as of Firefox version 69.0.1
[Install736426B0AF4A39CB]
Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
Locked=1
/*
Current profiles.ini structure example as of Firefox version 69.0.1
[Profile2]
Name=newblahprofile
IsRelative=0
Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.
[Install736426B0AF4A39CB]
Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
Locked=1
[Profile1]
Name=default
IsRelative=1
Path=Profiles/cydum7q4.default
Default=1
[Profile2]
Name=newblahprofile
IsRelative=0
Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.
[Profile0]
Name=default-release
IsRelative=1
Path=Profiles/7789f565.default-release
[Profile1]
Name=default
IsRelative=1
Path=Profiles/cydum7q4.default
Default=1
[General]
StartWithLastProfile=1
Version=2
*/
[Profile0]
Name=default-release
IsRelative=1
Path=Profiles/7789f565.default-release
var lines = ini.Split(new string[]
{
"\r\n"
}, StringSplitOptions.None).ToList();
[General]
StartWithLastProfile=1
Version=2
*/
var lines = ini.Split("\r\n").ToList();
var defaultProfileFolderNameRaw = lines.Where(x => x.Contains("Default=") && x != "Default=1").FirstOrDefault() ?? string.Empty;
var defaultProfileFolderNameRaw = lines.FirstOrDefault(x => x.Contains("Default=") && x != "Default=1") ?? string.Empty;
if (string.IsNullOrEmpty(defaultProfileFolderNameRaw))
return string.Empty;
if (string.IsNullOrEmpty(defaultProfileFolderNameRaw))
return string.Empty;
var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last();
var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last();
var indexOfDefaultProfileAtttributePath = lines.IndexOf("Path=" + defaultProfileFolderName);
var indexOfDefaultProfileAttributePath = lines.IndexOf("Path=" + defaultProfileFolderName);
// Seen in the example above, the IsRelative attribute is always above the Path attribute
var relativeAttribute = lines[indexOfDefaultProfileAtttributePath - 1];
// Seen in the example above, the IsRelative attribute is always above the Path attribute
var relativeAttribute = lines[indexOfDefaultProfileAttributePath - 1];
return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
? defaultProfileFolderName + @"\places.sqlite"
: Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite";
}
}
}
public static class Extensions
{
public static IEnumerable<T> Select<T>(this SqliteDataReader reader, Func<SqliteDataReader, T> projection)
{
while (reader.Read())
{
yield return projection(reader);
}
return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
? defaultProfileFolderName + @"\places.sqlite"
: Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite";
}
}
}
public static class Extensions
{
public static IEnumerable<T> Select<T>(this SqliteDataReader reader, Func<SqliteDataReader, T> projection)
{
while (reader.Read())
{
yield return projection(reader);
}
}
}

View file

@ -1,10 +1,9 @@
using Flow.Launcher.Plugin.BrowserBookmark.Models;
using System.Collections.Generic;
namespace Flow.Launcher.Plugin.BrowserBookmark
namespace Flow.Launcher.Plugin.BrowserBookmark;
public interface IBookmarkLoader
{
public interface IBookmarkLoader
{
public List<Bookmark> GetBookmarks();
}
}
public List<Bookmark> GetBookmarks();
}

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin.BrowserBookmark.Commands;
@ -12,233 +11,234 @@ using System.Threading.Channels;
using System.Threading.Tasks;
using System.Threading;
namespace Flow.Launcher.Plugin.BrowserBookmark
namespace Flow.Launcher.Plugin.BrowserBookmark;
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable
{
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable
private static PluginInitContext _context;
private static List<Bookmark> _cachedBookmarks = new List<Bookmark>();
private static Settings _settings;
private static bool _initialized = false;
public void Init(PluginInitContext context)
{
private static PluginInitContext context;
_context = context;
private static List<Bookmark> cachedBookmarks = new List<Bookmark>();
_settings = context.API.LoadSettingJsonStorage<Settings>();
private static Settings _settings;
LoadBookmarksIfEnabled();
}
private static bool initialized = false;
public void Init(PluginInitContext context)
private static void LoadBookmarksIfEnabled()
{
if (_context.CurrentPluginMetadata.Disabled)
{
Main.context = context;
// Don't load or monitor files if disabled
return;
}
_settings = context.API.LoadSettingJsonStorage<Settings>();
_cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings);
_ = MonitorRefreshQueueAsync();
_initialized = true;
}
public List<Result> Query(Query query)
{
// For when the plugin being previously disabled and is now re-enabled
if (!_initialized)
{
LoadBookmarksIfEnabled();
}
private static void LoadBookmarksIfEnabled()
string param = query.Search.TrimStart();
// Should top results be returned? (true if no search parameters have been passed)
var topResults = string.IsNullOrEmpty(param);
if (!topResults)
{
if (context.CurrentPluginMetadata.Disabled)
{
// Don't load or monitor files if disabled
return;
}
cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings);
_ = MonitorRefreshQueueAsync();
initialized = true;
}
public List<Result> Query(Query query)
{
// For when the plugin being previously disabled and is now renabled
if (!initialized)
{
LoadBookmarksIfEnabled();
}
string param = query.Search.TrimStart();
// Should top results be returned? (true if no search parameters have been passed)
var topResults = string.IsNullOrEmpty(param);
if (!topResults)
{
// Since we mixed chrome and firefox bookmarks, we should order them again
var returnList = cachedBookmarks.Select(c => new Result()
{
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = BookmarkLoader.MatchProgram(c, param).Score,
Action = _ =>
// Since we mixed chrome and firefox bookmarks, we should order them again
return _cachedBookmarks
.Select(
c => new Result
{
context.API.OpenUrl(c.Url);
return true;
},
ContextData = new BookmarkAttributes
{
Url = c.Url
}
}).Where(r => r.Score > 0);
return returnList.ToList();
}
else
{
return cachedBookmarks.Select(c => new Result()
{
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = 5,
Action = _ =>
{
context.API.OpenUrl(c.Url);
return true;
},
ContextData = new BookmarkAttributes
{
Url = c.Url
}
}).ToList();
}
}
private static Channel<byte> refreshQueue = Channel.CreateBounded<byte>(1);
private static SemaphoreSlim fileMonitorSemaphore = new(1, 1);
private static async Task MonitorRefreshQueueAsync()
{
if (fileMonitorSemaphore.CurrentCount < 1)
{
return;
}
await fileMonitorSemaphore.WaitAsync();
var reader = refreshQueue.Reader;
while (await reader.WaitToReadAsync())
{
if (reader.TryRead(out _))
{
ReloadAllBookmarks(false);
}
}
fileMonitorSemaphore.Release();
}
private static readonly List<FileSystemWatcher> Watchers = new();
internal static void RegisterBookmarkFile(string path)
{
var directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory) || !File.Exists(path))
{
return;
}
if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase)))
{
return;
}
var watcher = new FileSystemWatcher(directory!);
watcher.Filter = Path.GetFileName(path);
watcher.NotifyFilter = NotifyFilters.FileName |
NotifyFilters.LastWrite |
NotifyFilters.Size;
watcher.Changed += static (_, _) =>
{
refreshQueue.Writer.TryWrite(default);
};
watcher.Renamed += static (_, _) =>
{
refreshQueue.Writer.TryWrite(default);
};
watcher.EnableRaisingEvents = true;
Watchers.Add(watcher);
}
public void ReloadData()
{
ReloadAllBookmarks();
}
public static void ReloadAllBookmarks(bool disposeFileWatchers = true)
{
cachedBookmarks.Clear();
if (disposeFileWatchers)
DisposeFileWatchers();
LoadBookmarksIfEnabled();
}
public string GetTranslatedPluginTitle()
{
return context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description");
}
public Control CreateSettingPanel()
{
return new SettingsControl(_settings);
}
public List<Result> LoadContextMenus(Result selectedResult)
{
return new List<Result>()
{
new Result
{
Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"),
Action = _ =>
{
try
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = BookmarkLoader.MatchProgram(c, param).Score,
Action = _ =>
{
context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url);
_context.API.OpenUrl(c.Url);
return true;
}
catch (Exception e)
},
ContextData = new BookmarkAttributes { Url = c.Url }
}
)
.Where(r => r.Score > 0)
.ToList();
}
else
{
return _cachedBookmarks
.Select(
c => new Result
{
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = 5,
Action = _ =>
{
var message = "Failed to set url in clipboard";
Log.Exception("Main", message, e, "LoadContextMenus");
context.API.ShowMsg(message);
return false;
}
},
IcoPath = "Images\\copylink.png",
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8")
}
};
}
internal class BookmarkAttributes
{
internal string Url { get; set; }
}
public void Dispose()
{
DisposeFileWatchers();
}
private static void DisposeFileWatchers()
{
foreach (var watcher in Watchers)
{
watcher.Dispose();
}
Watchers.Clear();
_context.API.OpenUrl(c.Url);
return true;
},
ContextData = new BookmarkAttributes { Url = c.Url }
}
)
.ToList();
}
}
private static Channel<byte> _refreshQueue = Channel.CreateBounded<byte>(1);
private static SemaphoreSlim _fileMonitorSemaphore = new(1, 1);
private static async Task MonitorRefreshQueueAsync()
{
if (_fileMonitorSemaphore.CurrentCount < 1)
{
return;
}
await _fileMonitorSemaphore.WaitAsync();
var reader = _refreshQueue.Reader;
while (await reader.WaitToReadAsync())
{
if (reader.TryRead(out _))
{
ReloadAllBookmarks(false);
}
}
_fileMonitorSemaphore.Release();
}
private static readonly List<FileSystemWatcher> Watchers = new();
internal static void RegisterBookmarkFile(string path)
{
var directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory) || !File.Exists(path))
{
return;
}
if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase)))
{
return;
}
var watcher = new FileSystemWatcher(directory!);
watcher.Filter = Path.GetFileName(path);
watcher.NotifyFilter = NotifyFilters.FileName |
NotifyFilters.LastWrite |
NotifyFilters.Size;
watcher.Changed += static (_, _) =>
{
_refreshQueue.Writer.TryWrite(default);
};
watcher.Renamed += static (_, _) =>
{
_refreshQueue.Writer.TryWrite(default);
};
watcher.EnableRaisingEvents = true;
Watchers.Add(watcher);
}
public void ReloadData()
{
ReloadAllBookmarks();
}
public static void ReloadAllBookmarks(bool disposeFileWatchers = true)
{
_cachedBookmarks.Clear();
if (disposeFileWatchers)
DisposeFileWatchers();
LoadBookmarksIfEnabled();
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description");
}
public Control CreateSettingPanel()
{
return new SettingsControl(_settings);
}
public List<Result> LoadContextMenus(Result selectedResult)
{
return new List<Result>()
{
new Result
{
Title = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"),
SubTitle = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"),
Action = _ =>
{
try
{
_context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url);
return true;
}
catch (Exception e)
{
var message = "Failed to set url in clipboard";
Log.Exception("Main", message, e, "LoadContextMenus");
_context.API.ShowMsg(message);
return false;
}
},
IcoPath = @"Images\copylink.png",
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8")
}
};
}
internal class BookmarkAttributes
{
internal string Url { get; set; }
}
public void Dispose()
{
DisposeFileWatchers();
}
private static void DisposeFileWatchers()
{
foreach (var watcher in Watchers)
{
watcher.Dispose();
}
Watchers.Clear();
}
}

View file

@ -1,22 +1,21 @@
using System.Collections.Generic;
namespace Flow.Launcher.Plugin.BrowserBookmark.Models
namespace Flow.Launcher.Plugin.BrowserBookmark.Models;
// Source may be important in the future
public record Bookmark(string Name, string Url, string Source = "")
{
// Source may be important in the future
public record Bookmark(string Name, string Url, string Source = "")
public override int GetHashCode()
{
public override int GetHashCode()
{
var hashName = Name?.GetHashCode() ?? 0;
var hashUrl = Url?.GetHashCode() ?? 0;
return hashName ^ hashUrl;
}
public virtual bool Equals(Bookmark other)
{
return other != null && Name == other.Name && Url == other.Url;
}
public List<CustomBrowser> CustomBrowsers { get; set; }= new();
var hashName = Name?.GetHashCode() ?? 0;
var hashUrl = Url?.GetHashCode() ?? 0;
return hashName ^ hashUrl;
}
}
public virtual bool Equals(Bookmark other)
{
return other != null && Name == other.Name && Url == other.Url;
}
public List<CustomBrowser> CustomBrowsers { get; set; } = new();
}

View file

@ -1,45 +1,44 @@
namespace Flow.Launcher.Plugin.BrowserBookmark.Models
{
public class CustomBrowser : BaseModel
{
private string _name;
private string _dataDirectoryPath;
private BrowserType browserType = BrowserType.Chromium;
namespace Flow.Launcher.Plugin.BrowserBookmark.Models;
public string Name
public class CustomBrowser : BaseModel
{
private string _name;
private string _dataDirectoryPath;
private BrowserType _browserType = BrowserType.Chromium;
public string Name
{
get => _name;
set
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
public string DataDirectoryPath
{
get => _dataDirectoryPath;
set
{
_dataDirectoryPath = value;
OnPropertyChanged(nameof(DataDirectoryPath));
}
}
public BrowserType BrowserType
{
get => browserType;
set
{
browserType = value;
OnPropertyChanged(nameof(BrowserType));
}
_name = value;
OnPropertyChanged();
}
}
public enum BrowserType
public string DataDirectoryPath
{
Chromium,
Firefox,
get => _dataDirectoryPath;
set
{
_dataDirectoryPath = value;
OnPropertyChanged();
}
}
public BrowserType BrowserType
{
get => _browserType;
set
{
_browserType = value;
OnPropertyChanged();
}
}
}
public enum BrowserType
{
Chromium,
Firefox,
}

View file

@ -1,17 +1,16 @@
using System.Collections.ObjectModel;
namespace Flow.Launcher.Plugin.BrowserBookmark.Models
namespace Flow.Launcher.Plugin.BrowserBookmark.Models;
public class Settings : BaseModel
{
public class Settings : BaseModel
{
public bool OpenInNewBrowserWindow { get; set; } = true;
public bool OpenInNewBrowserWindow { get; set; } = true;
public string BrowserPath { get; set; }
public string BrowserPath { get; set; }
public bool LoadChromeBookmark { get; set; } = true;
public bool LoadFirefoxBookmark { get; set; } = true;
public bool LoadEdgeBookmark { get; set; } = true;
public bool LoadChromeBookmark { get; set; } = true;
public bool LoadFirefoxBookmark { get; set; } = true;
public bool LoadEdgeBookmark { get; set; } = true;
public ObservableCollection<CustomBrowser> CustomChromiumBrowsers { get; set; } = new();
}
}
public ObservableCollection<CustomBrowser> CustomChromiumBrowsers { get; set; } = new();
}

View file

@ -63,7 +63,6 @@
<StackPanel Margin="26,0,26,0">
<StackPanel Margin="0,0,0,12">
<TextBlock
Grid.Column="0"
Margin="0,0,0,0"
FontSize="20"
FontWeight="SemiBold"

View file

@ -3,55 +3,54 @@ using System.Windows;
using System.Windows.Input;
using System.Windows.Forms;
namespace Flow.Launcher.Plugin.BrowserBookmark.Views
namespace Flow.Launcher.Plugin.BrowserBookmark.Views;
/// <summary>
/// Interaction logic for CustomBrowserSetting.xaml
/// </summary>
public partial class CustomBrowserSettingWindow : Window
{
/// <summary>
/// Interaction logic for CustomBrowserSetting.xaml
/// </summary>
public partial class CustomBrowserSettingWindow : Window
private CustomBrowser _currentCustomBrowser;
public CustomBrowserSettingWindow(CustomBrowser browser)
{
private CustomBrowser currentCustomBrowser;
public CustomBrowserSettingWindow(CustomBrowser browser)
InitializeComponent();
_currentCustomBrowser = browser;
DataContext = new CustomBrowser
{
InitializeComponent();
currentCustomBrowser = browser;
DataContext = new CustomBrowser
{
Name = browser.Name,
DataDirectoryPath = browser.DataDirectoryPath,
BrowserType = browser.BrowserType,
};
}
private void ConfirmEditCustomBrowser(object sender, RoutedEventArgs e)
{
CustomBrowser editBrowser = (CustomBrowser)DataContext;
currentCustomBrowser.Name = editBrowser.Name;
currentCustomBrowser.DataDirectoryPath = editBrowser.DataDirectoryPath;
currentCustomBrowser.BrowserType = editBrowser.BrowserType;
DialogResult = true;
Close();
}
Name = browser.Name,
DataDirectoryPath = browser.DataDirectoryPath,
BrowserType = browser.BrowserType,
};
}
private void CancelEditCustomBrowser(object sender, RoutedEventArgs e)
{
Close();
}
private void ConfirmEditCustomBrowser(object sender, RoutedEventArgs e)
{
CustomBrowser editBrowser = (CustomBrowser)DataContext;
_currentCustomBrowser.Name = editBrowser.Name;
_currentCustomBrowser.DataDirectoryPath = editBrowser.DataDirectoryPath;
_currentCustomBrowser.BrowserType = editBrowser.BrowserType;
DialogResult = true;
Close();
}
private void WindowKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
ConfirmEditCustomBrowser(sender, e);
}
}
private void CancelEditCustomBrowser(object sender, RoutedEventArgs e)
{
Close();
}
private void OnSelectPathClick(object sender, RoutedEventArgs e)
private void WindowKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
var dialog = new FolderBrowserDialog();
dialog.ShowDialog();
CustomBrowser editBrowser = (CustomBrowser)DataContext;
editBrowser.DataDirectoryPath = dialog.SelectedPath;
ConfirmEditCustomBrowser(sender, e);
}
}
private void OnSelectPathClick(object sender, RoutedEventArgs e)
{
var dialog = new FolderBrowserDialog();
dialog.ShowDialog();
CustomBrowser editBrowser = (CustomBrowser)DataContext;
editBrowser.DataDirectoryPath = dialog.SelectedPath;
}
}

View file

@ -4,119 +4,116 @@ using System.Windows.Input;
using System.ComponentModel;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin.BrowserBookmark.Views
namespace Flow.Launcher.Plugin.BrowserBookmark.Views;
public partial class SettingsControl : INotifyPropertyChanged
{
public partial class SettingsControl : INotifyPropertyChanged
public Settings Settings { get; }
public CustomBrowser SelectedCustomBrowser { get; set; }
public bool LoadChromeBookmark
{
public Settings Settings { get; }
public CustomBrowser SelectedCustomBrowser { get; set; }
public bool LoadChromeBookmark
get => Settings.LoadChromeBookmark;
set
{
get => Settings.LoadChromeBookmark;
set
Settings.LoadChromeBookmark = value;
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
public bool LoadFirefoxBookmark
{
get => Settings.LoadFirefoxBookmark;
set
{
Settings.LoadFirefoxBookmark = value;
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
public bool LoadEdgeBookmark
{
get => Settings.LoadEdgeBookmark;
set
{
Settings.LoadEdgeBookmark = value;
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
public bool OpenInNewBrowserWindow
{
get => Settings.OpenInNewBrowserWindow;
set
{
Settings.OpenInNewBrowserWindow = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenInNewBrowserWindow)));
}
}
public SettingsControl(Settings settings)
{
Settings = settings;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NewCustomBrowser(object sender, RoutedEventArgs e)
{
var newBrowser = new CustomBrowser();
var window = new CustomBrowserSettingWindow(newBrowser);
window.ShowDialog();
if (newBrowser is not
{
Settings.LoadChromeBookmark = value;
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
public bool LoadFirefoxBookmark
Name: null,
DataDirectoryPath: null
})
{
get => Settings.LoadFirefoxBookmark;
set
{
Settings.LoadFirefoxBookmark = value;
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
Settings.CustomChromiumBrowsers.Add(newBrowser);
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
public bool LoadEdgeBookmark
private void DeleteCustomBrowser(object sender, RoutedEventArgs e)
{
if (CustomBrowsers.SelectedItem is CustomBrowser selectedCustomBrowser)
{
get => Settings.LoadEdgeBookmark;
set
{
Settings.LoadEdgeBookmark = value;
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
Settings.CustomChromiumBrowsers.Remove(selectedCustomBrowser);
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
public bool OpenInNewBrowserWindow
private void MouseDoubleClickOnSelectedCustomBrowser(object sender, MouseButtonEventArgs e)
{
EditSelectedCustomBrowser();
}
private void Others_Click(object sender, RoutedEventArgs e)
{
CustomBrowsersList.Visibility = CustomBrowsersList.Visibility switch
{
get => Settings.OpenInNewBrowserWindow;
set
{
Settings.OpenInNewBrowserWindow = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenInNewBrowserWindow)));
}
}
Visibility.Collapsed => Visibility.Visible,
_ => Visibility.Collapsed
};
}
public SettingsControl(Settings settings)
private void EditCustomBrowser(object sender, RoutedEventArgs e)
{
EditSelectedCustomBrowser();
}
private void EditSelectedCustomBrowser()
{
if (SelectedCustomBrowser is null)
return;
var window = new CustomBrowserSettingWindow(SelectedCustomBrowser);
var result = window.ShowDialog() ?? false;
if (result)
{
Settings = settings;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NewCustomBrowser(object sender, RoutedEventArgs e)
{
var newBrowser = new CustomBrowser();
var window = new CustomBrowserSettingWindow(newBrowser);
window.ShowDialog();
if (newBrowser is not
{
Name: null,
DataDirectoryPath: null
})
{
Settings.CustomChromiumBrowsers.Add(newBrowser);
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
private void DeleteCustomBrowser(object sender, RoutedEventArgs e)
{
if (CustomBrowsers.SelectedItem is CustomBrowser selectedCustomBrowser)
{
Settings.CustomChromiumBrowsers.Remove(selectedCustomBrowser);
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
private void MouseDoubleClickOnSelectedCustomBrowser(object sender, MouseButtonEventArgs e)
{
EditSelectedCustomBrowser();
}
private void Others_Click(object sender, RoutedEventArgs e)
{
if (CustomBrowsersList.Visibility == Visibility.Collapsed)
{
CustomBrowsersList.Visibility = Visibility.Visible;
}
else
CustomBrowsersList.Visibility = Visibility.Collapsed;
}
private void EditCustomBrowser(object sender, RoutedEventArgs e)
{
EditSelectedCustomBrowser();
}
private void EditSelectedCustomBrowser()
{
if (SelectedCustomBrowser is null)
return;
var window = new CustomBrowserSettingWindow(SelectedCustomBrowser);
var result = window.ShowDialog() ?? false;
if (result)
{
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
_ = Task.Run(() => Main.ReloadAllBookmarks());
}
}
}