mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into file_tooltip
This commit is contained in:
commit
4cd08917cf
9 changed files with 192 additions and 87 deletions
|
|
@ -1,8 +1,9 @@
|
|||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Flow.Launcher.Plugin.SharedCommands
|
||||
{
|
||||
|
|
@ -13,7 +14,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
{
|
||||
private static string GetDefaultBrowserPath()
|
||||
{
|
||||
string name = string.Empty;
|
||||
var name = string.Empty;
|
||||
try
|
||||
{
|
||||
using var regDefault = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice", false);
|
||||
|
|
@ -23,8 +24,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
name = regKey.GetValue(null).ToString().ToLower().Replace("\"", "");
|
||||
|
||||
if (!name.EndsWith("exe"))
|
||||
name = name.Substring(0, name.LastIndexOf(".exe") + 4);
|
||||
|
||||
name = name[..(name.LastIndexOf(".exe") + 4)];
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -65,12 +65,21 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
{
|
||||
Process.Start(psi)?.Dispose();
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception)
|
||||
// This error may be thrown if browser path is incorrect
|
||||
catch (Win32Exception)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
try
|
||||
{
|
||||
FileName = url, UseShellExecute = true
|
||||
});
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw; // Re-throw the exception if we cannot open the URL in the default browser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,12 +109,20 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
Process.Start(psi)?.Dispose();
|
||||
}
|
||||
// This error may be thrown if browser path is incorrect
|
||||
catch (System.ComponentModel.Win32Exception)
|
||||
catch (Win32Exception)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
try
|
||||
{
|
||||
FileName = url, UseShellExecute = true
|
||||
});
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw; // Re-throw the exception if we cannot open the URL in the default browser
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -473,6 +473,7 @@
|
|||
</system:String>
|
||||
<system:String x:Key="errorTitle">Error</system:String>
|
||||
<system:String x:Key="folderOpenError">An error occurred while opening the folder. {0}</system:String>
|
||||
<system:String x:Key="browserOpenError">An error occurred while opening the URL in the browser. Please check your Default Web Browser configuration in the General section of the settings window</system:String>
|
||||
|
||||
<!-- General Notice -->
|
||||
<system:String x:Key="pleaseWait">Please wait...</system:String>
|
||||
|
|
|
|||
|
|
@ -399,13 +399,27 @@ namespace Flow.Launcher
|
|||
|
||||
var path = browserInfo.Path == "*" ? "" : browserInfo.Path;
|
||||
|
||||
if (browserInfo.OpenInTab)
|
||||
try
|
||||
{
|
||||
uri.AbsoluteUri.OpenInBrowserTab(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg);
|
||||
if (browserInfo.OpenInTab)
|
||||
{
|
||||
uri.AbsoluteUri.OpenInBrowserTab(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
uri.AbsoluteUri.OpenInBrowserWindow(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception e)
|
||||
{
|
||||
uri.AbsoluteUri.OpenInBrowserWindow(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg);
|
||||
var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window";
|
||||
LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e);
|
||||
ShowMsgBox(
|
||||
GetTranslation("browserOpenError"),
|
||||
GetTranslation("errorTitle"),
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
|
|
@ -43,16 +45,23 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
|||
catch (Exception ex)
|
||||
{
|
||||
Main._context.API.LogException(ClassName, $"Failed to register bookmark file monitoring: {bookmarkPath}", ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
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))
|
||||
if (Main._settings.EnableFavicons)
|
||||
{
|
||||
LoadFaviconsFromDb(faviconDbPath, profileBookmarks);
|
||||
var faviconDbPath = Path.Combine(profile, "Favicons");
|
||||
if (File.Exists(faviconDbPath))
|
||||
{
|
||||
Main._context.API.StopwatchLogInfo(ClassName, $"Load {profileBookmarks.Count} favicons cost", () =>
|
||||
{
|
||||
LoadFaviconsFromDb(faviconDbPath, profileBookmarks);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bookmarks.AddRange(profileBookmarks);
|
||||
|
|
@ -148,19 +157,24 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
|||
|
||||
try
|
||||
{
|
||||
using var connection = new SqliteConnection($"Data Source={tempDbPath}");
|
||||
connection.Open();
|
||||
// Since some bookmarks may have same favicon id, we need to record them to avoid duplicates
|
||||
var savedPaths = new ConcurrentDictionary<string, bool>();
|
||||
|
||||
foreach (var bookmark in bookmarks)
|
||||
// Get favicons based on bookmarks concurrently
|
||||
Parallel.ForEach(bookmarks, bookmark =>
|
||||
{
|
||||
// Use read-only connection to avoid locking issues
|
||||
var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly");
|
||||
connection.Open();
|
||||
|
||||
try
|
||||
{
|
||||
var url = bookmark.Url;
|
||||
if (string.IsNullOrEmpty(url)) continue;
|
||||
if (string.IsNullOrEmpty(url)) return;
|
||||
|
||||
// Extract domain from URL
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
|
||||
continue;
|
||||
return;
|
||||
|
||||
var domain = uri.Host;
|
||||
|
||||
|
|
@ -178,16 +192,21 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
|||
|
||||
using var reader = cmd.ExecuteReader();
|
||||
if (!reader.Read() || reader.IsDBNull(1))
|
||||
continue;
|
||||
return;
|
||||
|
||||
var iconId = reader.GetInt64(0).ToString();
|
||||
var imageData = (byte[])reader["image_data"];
|
||||
|
||||
if (imageData is not { Length: > 0 })
|
||||
continue;
|
||||
return;
|
||||
|
||||
var faviconPath = Path.Combine(_faviconCacheDir, $"chromium_{domain}_{iconId}.png");
|
||||
SaveBitmapData(imageData, faviconPath);
|
||||
|
||||
// Filter out duplicate favicons
|
||||
if (savedPaths.TryAdd(faviconPath, true))
|
||||
{
|
||||
SaveBitmapData(imageData, faviconPath);
|
||||
}
|
||||
|
||||
bookmark.FaviconPath = faviconPath;
|
||||
}
|
||||
|
|
@ -195,11 +214,14 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
|
|||
{
|
||||
Main._context.API.LogException(ClassName, $"Failed to extract bookmark favicon: {bookmark.Url}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/dotnet/efcore/issues/26580
|
||||
SqliteConnection.ClearPool(connection);
|
||||
connection.Close();
|
||||
finally
|
||||
{
|
||||
// https://github.com/dotnet/efcore/issues/26580
|
||||
SqliteConnection.ClearPool(connection);
|
||||
connection.Close();
|
||||
connection.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
|
|
@ -30,8 +32,6 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
|||
ORDER BY moz_places.visit_count DESC
|
||||
""";
|
||||
|
||||
private const string DbPathFormat = "Data Source={0}";
|
||||
|
||||
protected List<Bookmark> GetBookmarksFromPath(string placesPath)
|
||||
{
|
||||
// Variable to store bookmark list
|
||||
|
|
@ -41,30 +41,32 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
|||
if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath))
|
||||
return bookmarks;
|
||||
|
||||
// Try to register file monitoring
|
||||
try
|
||||
{
|
||||
Main.RegisterBookmarkFile(placesPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main._context.API.LogException(ClassName, $"Failed to register Firefox bookmark file monitoring: {placesPath}", ex);
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
var tempDbPath = Path.Combine(_faviconCacheDir, $"tempplaces_{Guid.NewGuid()}.sqlite");
|
||||
|
||||
try
|
||||
{
|
||||
// Try to register file monitoring
|
||||
try
|
||||
{
|
||||
Main.RegisterBookmarkFile(placesPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main._context.API.LogException(ClassName, $"Failed to register Firefox bookmark file monitoring: {placesPath}", ex);
|
||||
}
|
||||
|
||||
// Use a copy to avoid lock issues with the original file
|
||||
File.Copy(placesPath, tempDbPath, true);
|
||||
|
||||
// Connect to database and execute query
|
||||
string dbPath = string.Format(DbPathFormat, tempDbPath);
|
||||
using var dbConnection = new SqliteConnection(dbPath);
|
||||
// Create the connection string and init the connection
|
||||
using var dbConnection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly");
|
||||
|
||||
// Open connection to the database file and execute the query
|
||||
dbConnection.Open();
|
||||
var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader();
|
||||
|
||||
// Create bookmark list
|
||||
// Get results in List<Bookmark> format
|
||||
bookmarks = reader
|
||||
.Select(
|
||||
x => new Bookmark(
|
||||
|
|
@ -75,12 +77,20 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
|||
)
|
||||
.ToList();
|
||||
|
||||
// Path to favicon database
|
||||
var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite");
|
||||
if (File.Exists(faviconDbPath))
|
||||
// Load favicons after loading bookmarks
|
||||
if (Main._settings.EnableFavicons)
|
||||
{
|
||||
LoadFaviconsFromDb(faviconDbPath, bookmarks);
|
||||
var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite");
|
||||
if (File.Exists(faviconDbPath))
|
||||
{
|
||||
Main._context.API.StopwatchLogInfo(ClassName, $"Load {bookmarks.Count} favicons cost", () =>
|
||||
{
|
||||
LoadFaviconsFromDb(faviconDbPath, bookmarks);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Close the connection so that we can delete the temporary file
|
||||
// https://github.com/dotnet/efcore/issues/26580
|
||||
SqliteConnection.ClearPool(dbConnection);
|
||||
dbConnection.Close();
|
||||
|
|
@ -93,7 +103,10 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
|||
// Delete temporary file
|
||||
try
|
||||
{
|
||||
File.Delete(tempDbPath);
|
||||
if (File.Exists(tempDbPath))
|
||||
{
|
||||
File.Delete(tempDbPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -103,34 +116,52 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
|||
return bookmarks;
|
||||
}
|
||||
|
||||
private void LoadFaviconsFromDb(string faviconDbPath, List<Bookmark> bookmarks)
|
||||
private void LoadFaviconsFromDb(string dbPath, List<Bookmark> bookmarks)
|
||||
{
|
||||
// Use a copy to avoid lock issues with the original file
|
||||
var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite");
|
||||
|
||||
try
|
||||
{
|
||||
// Use a copy to avoid lock issues with the original file
|
||||
File.Copy(faviconDbPath, tempDbPath, true);
|
||||
|
||||
var defaultIconPath = Path.Combine(
|
||||
Path.GetDirectoryName(typeof(FirefoxBookmarkLoaderBase).Assembly.Location),
|
||||
"bookmark.png");
|
||||
|
||||
string dbPath = string.Format(DbPathFormat, tempDbPath);
|
||||
using var connection = new SqliteConnection(dbPath);
|
||||
connection.Open();
|
||||
|
||||
// Get favicons based on bookmark URLs
|
||||
foreach (var bookmark in bookmarks)
|
||||
File.Copy(dbPath, tempDbPath, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(tempDbPath))
|
||||
{
|
||||
File.Delete(tempDbPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex1)
|
||||
{
|
||||
Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex1);
|
||||
}
|
||||
Main._context.API.LogException(ClassName, $"Failed to copy favicon DB: {dbPath}", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Since some bookmarks may have same favicon id, we need to record them to avoid duplicates
|
||||
var savedPaths = new ConcurrentDictionary<string, bool>();
|
||||
|
||||
// Get favicons based on bookmarks concurrently
|
||||
Parallel.ForEach(bookmarks, bookmark =>
|
||||
{
|
||||
// Use read-only connection to avoid locking issues
|
||||
var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly");
|
||||
connection.Open();
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(bookmark.Url))
|
||||
continue;
|
||||
return;
|
||||
|
||||
// Extract domain from URL
|
||||
if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri))
|
||||
continue;
|
||||
return;
|
||||
|
||||
var domain = uri.Host;
|
||||
|
||||
|
|
@ -150,12 +181,12 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
|||
|
||||
using var reader = cmd.ExecuteReader();
|
||||
if (!reader.Read() || reader.IsDBNull(0))
|
||||
continue;
|
||||
return;
|
||||
|
||||
var imageData = (byte[])reader["data"];
|
||||
|
||||
if (imageData is not { Length: > 0 })
|
||||
continue;
|
||||
return;
|
||||
|
||||
string faviconPath;
|
||||
if (IsSvgData(imageData))
|
||||
|
|
@ -166,7 +197,12 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
|||
{
|
||||
faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png");
|
||||
}
|
||||
SaveBitmapData(imageData, faviconPath);
|
||||
|
||||
// Filter out duplicate favicons
|
||||
if (savedPaths.TryAdd(faviconPath, true))
|
||||
{
|
||||
SaveBitmapData(imageData, faviconPath);
|
||||
}
|
||||
|
||||
bookmark.FaviconPath = faviconPath;
|
||||
}
|
||||
|
|
@ -174,15 +210,18 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
|||
{
|
||||
Main._context.API.LogException(ClassName, $"Failed to extract Firefox favicon: {bookmark.Url}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/dotnet/efcore/issues/26580
|
||||
SqliteConnection.ClearPool(connection);
|
||||
connection.Close();
|
||||
finally
|
||||
{
|
||||
// https://github.com/dotnet/efcore/issues/26580
|
||||
SqliteConnection.ClearPool(connection);
|
||||
connection.Close();
|
||||
connection.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main._context.API.LogException(ClassName, $"Failed to load Firefox favicon DB: {faviconDbPath}", ex);
|
||||
Main._context.API.LogException(ClassName, $"Failed to load Firefox favicon DB: {tempDbPath}", ex);
|
||||
}
|
||||
|
||||
// Delete temporary file
|
||||
|
|
|
|||
|
|
@ -27,4 +27,6 @@
|
|||
<system:String x:Key="flowlauncher_plugin_browserbookmark_browserEngine">Browser Engine</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_guideMessage01">If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work.</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_guideMessage02">For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file.</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_enable_favicons">Load favicons (can be time consuming during startup)</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Commands;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Views;
|
||||
using System.IO;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Plugin.BrowserBookmark;
|
||||
|
|
@ -21,9 +21,9 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex
|
|||
|
||||
internal static PluginInitContext _context;
|
||||
|
||||
private static List<Bookmark> _cachedBookmarks = new();
|
||||
internal static Settings _settings;
|
||||
|
||||
private static Settings _settings;
|
||||
private static List<Bookmark> _cachedBookmarks = new();
|
||||
|
||||
private static bool _initialized = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ public class Settings : BaseModel
|
|||
|
||||
public string BrowserPath { get; set; }
|
||||
|
||||
public bool EnableFavicons { get; set; } = false;
|
||||
|
||||
public bool LoadChromeBookmark { get; set; } = true;
|
||||
public bool LoadFirefoxBookmark { get; set; } = true;
|
||||
public bool LoadEdgeBookmark { get; set; } = true;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
|
|
@ -91,5 +92,12 @@
|
|||
Content="{DynamicResource flowlauncher_plugin_browserbookmark_removeBrowserBookmark}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<CheckBox
|
||||
Grid.Row="2"
|
||||
Margin="{StaticResource SettingPanelItemTopBottomMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Content="{DynamicResource flowlauncher_plugin_browserbookmark_enable_favicons}"
|
||||
IsChecked="{Binding Settings.EnableFavicons}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
Loading…
Reference in a new issue