mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
- Add SVG convert for firefox
- Add remove cache UI
This commit is contained in:
parent
a837e8e197
commit
dfc9d5b84f
5 changed files with 200 additions and 78 deletions
|
|
@ -106,89 +106,131 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadFaviconsFromDb(string faviconDbPath, List<Bookmark> bookmarks)
|
private void LoadFaviconsFromDb(string faviconDbPath, List<Bookmark> bookmarks)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
// Use a copy to avoid lock issues with the original file
|
||||||
|
var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite");
|
||||||
|
File.Copy(faviconDbPath, tempDbPath, true);
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
// Use a copy to avoid lock issues with the original file
|
|
||||||
var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite");
|
|
||||||
File.Copy(faviconDbPath, tempDbPath, true);
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(bookmark.Url))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Extract domain from URL
|
|
||||||
if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var domain = uri.Host;
|
|
||||||
|
|
||||||
// Query for latest Firefox version favicon structure
|
|
||||||
using var cmd = connection.CreateCommand();
|
|
||||||
cmd.CommandText = @"
|
|
||||||
SELECT i.data
|
|
||||||
FROM moz_icons i
|
|
||||||
JOIN moz_icons_to_pages ip ON i.id = ip.icon_id
|
|
||||||
JOIN moz_pages_w_icons p ON ip.page_id = p.id
|
|
||||||
WHERE p.page_url LIKE @url
|
|
||||||
AND i.data IS NOT NULL
|
|
||||||
ORDER BY i.width DESC -- Select largest icon available
|
|
||||||
LIMIT 1";
|
|
||||||
|
|
||||||
cmd.Parameters.AddWithValue("@url", $"%{domain}%");
|
|
||||||
|
|
||||||
using var reader = cmd.ExecuteReader();
|
|
||||||
if (!reader.Read() || reader.IsDBNull(0))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var imageData = (byte[])reader["data"];
|
|
||||||
|
|
||||||
if (imageData is not { Length: > 0 })
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var ext = IsSvgData(imageData) ? "svg" : "png";
|
|
||||||
var faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.{ext}");
|
|
||||||
|
|
||||||
if (!File.Exists(faviconPath))
|
|
||||||
{
|
|
||||||
SaveBitmapData(imageData, faviconPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmark.FaviconPath = faviconPath;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Exception($"Failed to extract Firefox favicon: {bookmark.Url}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/dotnet/efcore/issues/26580
|
|
||||||
SqliteConnection.ClearPool(connection);
|
|
||||||
connection.Close();
|
|
||||||
|
|
||||||
// Delete temporary file
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.Delete(tempDbPath);
|
if (string.IsNullOrEmpty(bookmark.Url))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Extract domain from URL
|
||||||
|
if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var domain = uri.Host;
|
||||||
|
|
||||||
|
// Query for latest Firefox version favicon structure
|
||||||
|
using var cmd = connection.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
SELECT i.data
|
||||||
|
FROM moz_icons i
|
||||||
|
JOIN moz_icons_to_pages ip ON i.id = ip.icon_id
|
||||||
|
JOIN moz_pages_w_icons p ON ip.page_id = p.id
|
||||||
|
WHERE p.page_url LIKE @url
|
||||||
|
AND i.data IS NOT NULL
|
||||||
|
ORDER BY i.width DESC -- Select largest icon available
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
cmd.Parameters.AddWithValue("@url", $"%{domain}%");
|
||||||
|
|
||||||
|
using var reader = cmd.ExecuteReader();
|
||||||
|
if (!reader.Read() || reader.IsDBNull(0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var imageData = (byte[])reader["data"];
|
||||||
|
|
||||||
|
if (imageData is not { Length: > 0 })
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png");
|
||||||
|
|
||||||
|
if (!File.Exists(faviconPath))
|
||||||
|
{
|
||||||
|
if (IsSvgData(imageData))
|
||||||
|
{
|
||||||
|
// SVG를 PNG로 변환
|
||||||
|
var pngData = ConvertSvgToPng(imageData);
|
||||||
|
if (pngData != null)
|
||||||
|
{
|
||||||
|
SaveBitmapData(pngData, faviconPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 변환 실패 시 빈 문자열 설정 (기본 아이콘 사용)
|
||||||
|
bookmark.FaviconPath = string.Empty;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// PNG는 그대로 저장
|
||||||
|
SaveBitmapData(imageData, faviconPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmark.FaviconPath = faviconPath;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Exception($"Failed to delete temporary favicon DB: {tempDbPath}", ex);
|
Log.Exception($"Failed to extract Firefox favicon: {bookmark.Url}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/dotnet/efcore/issues/26580
|
||||||
|
SqliteConnection.ClearPool(connection);
|
||||||
|
connection.Close();
|
||||||
|
|
||||||
|
// Delete temporary file
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(tempDbPath);
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Exception($"Failed to load Firefox favicon DB: {faviconDbPath}", ex);
|
Log.Exception($"Failed to delete temporary favicon DB: {tempDbPath}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Exception($"Failed to load Firefox favicon DB: {faviconDbPath}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] ConvertSvgToPng(byte[] svgData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
// 메모리에 SVG 데이터 로드
|
||||||
|
using (var image = new ImageMagick.MagickImage(svgData))
|
||||||
|
{
|
||||||
|
// 적절한 크기로 리사이징 (32x32가 일반적인 파비콘 크기)
|
||||||
|
image.Resize(32, 32);
|
||||||
|
// PNG 형식으로 변환하여 메모리 스트림에 저장
|
||||||
|
image.Format = ImageMagick.MagickFormat.Png;
|
||||||
|
image.Write(memoryStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Exception("SVG to PNG conversion failed", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsSvgData(byte[] data)
|
private static bool IsSvgData(byte[] data)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.5.0" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,16 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex
|
||||||
private static Settings _settings;
|
private static Settings _settings;
|
||||||
|
|
||||||
private static bool _initialized = false;
|
private static bool _initialized = false;
|
||||||
|
|
||||||
|
public static PluginInitContext GetContext()
|
||||||
|
{
|
||||||
|
return _context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPluginDirectory()
|
||||||
|
{
|
||||||
|
return _context?.CurrentPluginMetadata?.PluginDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
public void Init(PluginInitContext context)
|
public void Init(PluginInitContext context)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@
|
||||||
Margin="0,0,15,0"
|
Margin="0,0,15,0"
|
||||||
Click="Others_Click"
|
Click="Others_Click"
|
||||||
Content="{DynamicResource flowlauncher_plugin_browserbookmark_others}" />
|
Content="{DynamicResource flowlauncher_plugin_browserbookmark_others}" />
|
||||||
|
<Button
|
||||||
|
Margin="0,0,15,0"
|
||||||
|
Click="RemoveFaviconCache_Click"
|
||||||
|
Content="Remove Favicon Cache" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Name="CustomBrowsersList" Visibility="Collapsed">
|
<StackPanel Name="CustomBrowsersList" Visibility="Collapsed">
|
||||||
<ListView
|
<ListView
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,27 @@ using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||||
|
|
||||||
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 Settings Settings { get; }
|
||||||
|
private readonly PluginInitContext _context;
|
||||||
|
public SettingsControl(Settings settings)
|
||||||
|
{
|
||||||
|
// Settings 객체를 직접 받도록 수정
|
||||||
|
Settings = settings;
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = this;
|
||||||
|
}
|
||||||
public CustomBrowser SelectedCustomBrowser { get; set; }
|
public CustomBrowser SelectedCustomBrowser { get; set; }
|
||||||
|
|
||||||
public bool LoadChromeBookmark
|
public bool LoadChromeBookmark
|
||||||
|
|
@ -51,13 +65,6 @@ public partial class SettingsControl : INotifyPropertyChanged
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenInNewBrowserWindow)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenInNewBrowserWindow)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SettingsControl(Settings settings)
|
|
||||||
{
|
|
||||||
Settings = settings;
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
private void NewCustomBrowser(object sender, RoutedEventArgs e)
|
private void NewCustomBrowser(object sender, RoutedEventArgs e)
|
||||||
|
|
@ -116,4 +123,62 @@ public partial class SettingsControl : INotifyPropertyChanged
|
||||||
_ = Task.Run(() => Main.ReloadAllBookmarks());
|
_ = Task.Run(() => Main.ReloadAllBookmarks());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void RemoveFaviconCache_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 플러그인 디렉토리 경로 가져오기
|
||||||
|
var pluginDir = Main.GetPluginDirectory();
|
||||||
|
if (string.IsNullOrEmpty(pluginDir))
|
||||||
|
{
|
||||||
|
MessageBox.Show("플러그인 디렉토리를 찾을 수 없습니다.", "오류", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 파비콘 캐시 디렉토리 경로 지정
|
||||||
|
string cacheDir = Path.Combine(pluginDir, "Images", "Favicons");
|
||||||
|
|
||||||
|
// 디렉토리 존재 확인 및 생성
|
||||||
|
if (!Directory.Exists(cacheDir))
|
||||||
|
{
|
||||||
|
MessageBox.Show("파비콘 캐시 디렉토리가 존재하지 않습니다. 캐시가 비어있거나 아직 생성되지 않았을 수 있습니다.", "알림", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 파일 수 확인
|
||||||
|
var files = Directory.GetFiles(cacheDir);
|
||||||
|
if (files.Length == 0)
|
||||||
|
{
|
||||||
|
MessageBox.Show("파비콘 캐시가 이미 비어 있습니다.", "알림", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 디렉토리 내 모든 파일 삭제
|
||||||
|
int deletedCount = 0;
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
deletedCount++;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Flow.Launcher.Infrastructure.Logger.Log.Exception(
|
||||||
|
$"Failed to delete favicon cache file: {file}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 북마크 다시 로드
|
||||||
|
Main.ReloadAllBookmarks();
|
||||||
|
|
||||||
|
// 완료 메시지 표시
|
||||||
|
MessageBox.Show($"{deletedCount}개의 파비콘 캐시 파일이 삭제되었습니다.", "캐시 삭제 완료", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Flow.Launcher.Infrastructure.Logger.Log.Exception("Failed to remove favicon cache", ex);
|
||||||
|
MessageBox.Show($"파비콘 캐시 삭제 중 오류가 발생했습니다: {ex.Message}", "오류", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue