mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into 250331-NewPluginsPage
This commit is contained in:
commit
c351a3870f
31 changed files with 621 additions and 258 deletions
|
|
@ -12,7 +12,7 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
|
|||
{
|
||||
public class JsonRPCPublicAPI
|
||||
{
|
||||
private IPublicAPI _api;
|
||||
private readonly IPublicAPI _api;
|
||||
|
||||
public JsonRPCPublicAPI(IPublicAPI api)
|
||||
{
|
||||
|
|
@ -104,7 +104,6 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
|
|||
return _api.GetAllPlugins();
|
||||
}
|
||||
|
||||
|
||||
public MatchResult FuzzySearch(string query, string stringToCompare)
|
||||
{
|
||||
return _api.FuzzySearch(query, stringToCompare);
|
||||
|
|
@ -156,6 +155,11 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
|
|||
_api.LogWarn(className, message, methodName);
|
||||
}
|
||||
|
||||
public void LogError(string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
_api.LogError(className, message, methodName);
|
||||
}
|
||||
|
||||
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
|
||||
{
|
||||
_api.OpenDirectory(DirectoryPath, FileNameOrFilePath);
|
||||
|
|
@ -185,5 +189,10 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
|
|||
{
|
||||
_api.StopLoadingBar();
|
||||
}
|
||||
|
||||
public void SavePluginCaches()
|
||||
{
|
||||
_api.SavePluginCaches();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Flow.Launcher.Infrastructure.Logger;
|
|||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using IRemovable = Flow.Launcher.Core.Storage.IRemovable;
|
||||
using ISavable = Flow.Launcher.Plugin.ISavable;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
|
|
@ -65,6 +66,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
|
||||
API.SavePluginSettings();
|
||||
API.SavePluginCaches();
|
||||
}
|
||||
|
||||
public static async ValueTask DisposePluginsAsync()
|
||||
|
|
@ -575,11 +577,11 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
if (removePluginSettings)
|
||||
{
|
||||
// For dotnet plugins, we need to remove their PluginJsonStorage instance
|
||||
if (AllowedLanguage.IsDotNet(plugin.Language))
|
||||
// For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances
|
||||
if (AllowedLanguage.IsDotNet(plugin.Language) && API is IRemovable removable)
|
||||
{
|
||||
var method = API.GetType().GetMethod("RemovePluginSettings");
|
||||
method?.Invoke(API, new object[] { plugin.AssemblyName });
|
||||
removable.RemovePluginSettings(plugin.AssemblyName);
|
||||
removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath);
|
||||
}
|
||||
|
||||
try
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Flow.Launcher.Infrastructure;
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Flow.Launcher.Core.Resource
|
||||
|
|
@ -81,11 +82,6 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
#region Theme Resources
|
||||
|
||||
public string GetCurrentTheme()
|
||||
{
|
||||
return _settings.Theme;
|
||||
}
|
||||
|
||||
private void MakeSureThemeDirectoriesExist()
|
||||
{
|
||||
foreach (var dir in _themeDirectories.Where(dir => !Directory.Exists(dir)))
|
||||
|
|
@ -127,7 +123,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
try
|
||||
{
|
||||
// Load a ResourceDictionary for the specified theme.
|
||||
var themeName = GetCurrentTheme();
|
||||
var themeName = _settings.Theme;
|
||||
var dict = GetThemeResourceDictionary(themeName);
|
||||
|
||||
// Apply font settings to the theme resource.
|
||||
|
|
@ -330,7 +326,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
private ResourceDictionary GetCurrentResourceDictionary()
|
||||
{
|
||||
return GetResourceDictionary(GetCurrentTheme());
|
||||
return GetResourceDictionary(_settings.Theme);
|
||||
}
|
||||
|
||||
private ThemeData GetThemeDataFromPath(string path)
|
||||
|
|
@ -383,9 +379,20 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
#endregion
|
||||
|
||||
#region Load & Change
|
||||
#region Get & Change Theme
|
||||
|
||||
public List<ThemeData> LoadAvailableThemes()
|
||||
public ThemeData GetCurrentTheme()
|
||||
{
|
||||
var themes = GetAvailableThemes();
|
||||
var matchingTheme = themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme);
|
||||
if (matchingTheme == null)
|
||||
{
|
||||
Log.Warn($"No matching theme found for '{_settings.Theme}'. Falling back to the first available theme.");
|
||||
}
|
||||
return matchingTheme ?? themes.FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<ThemeData> GetAvailableThemes()
|
||||
{
|
||||
List<ThemeData> themes = new List<ThemeData>();
|
||||
foreach (var themeDirectory in _themeDirectories)
|
||||
|
|
@ -403,7 +410,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
public bool ChangeTheme(string theme = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(theme))
|
||||
theme = GetCurrentTheme();
|
||||
theme = _settings.Theme;
|
||||
|
||||
string path = GetThemePath(theme);
|
||||
try
|
||||
|
|
@ -426,7 +433,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
BlurEnabled = IsBlurTheme();
|
||||
|
||||
// Can only apply blur but here also apply drop shadow effect to avoid possible drop shadow effect issues
|
||||
// Apply blur and drop shadow effect so that we do not need to call it again
|
||||
_ = RefreshFrameAsync();
|
||||
|
||||
return true;
|
||||
|
|
@ -591,7 +598,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
AutoDropShadow(useDropShadowEffect);
|
||||
}
|
||||
SetBlurForWindow(GetCurrentTheme(), backdropType);
|
||||
SetBlurForWindow(_settings.Theme, backdropType);
|
||||
|
||||
if (!BlurEnabled)
|
||||
{
|
||||
|
|
@ -610,7 +617,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
// Get the actual backdrop type and drop shadow effect settings
|
||||
var (backdropType, _) = GetActualValue();
|
||||
|
||||
SetBlurForWindow(GetCurrentTheme(), backdropType);
|
||||
SetBlurForWindow(_settings.Theme, backdropType);
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
|
|
@ -898,11 +905,5 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Classes
|
||||
|
||||
public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
Flow.Launcher.Core/Storage/IRemovable.cs
Normal file
19
Flow.Launcher.Core/Storage/IRemovable.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
namespace Flow.Launcher.Core.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Remove storage instances from <see cref="Launcher.Plugin.IPublicAPI"/> instance
|
||||
/// </summary>
|
||||
public interface IRemovable
|
||||
{
|
||||
/// <summary>
|
||||
/// Remove all <see cref="Infrastructure.Storage.PluginJsonStorage{T}"/> instances of one plugin
|
||||
/// </summary>
|
||||
/// <param name="assemblyName"></param>
|
||||
public void RemovePluginSettings(string assemblyName);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all <see cref="Infrastructure.Storage.PluginBinaryStorage{T}"/> instances of one plugin
|
||||
/// </summary>
|
||||
/// <param name="cacheDirectory"></param>
|
||||
public void RemovePluginCaches(string cacheDirectory);
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
_hashGenerator = new ImageHashGenerator();
|
||||
|
||||
var usage = await LoadStorageToConcurrentDictionaryAsync();
|
||||
_storage.ClearData();
|
||||
|
||||
ImageCache.Initialize(usage);
|
||||
|
||||
|
|
@ -284,7 +285,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
return ImageCache.TryGetValue(path, loadFullImage, out image);
|
||||
}
|
||||
|
||||
public static async ValueTask<ImageSource> LoadAsync(string path, bool loadFullImage = false)
|
||||
public static async ValueTask<ImageSource> LoadAsync(string path, bool loadFullImage = false, bool cacheImage = true)
|
||||
{
|
||||
var imageResult = await LoadInternalAsync(path, loadFullImage);
|
||||
|
||||
|
|
@ -300,16 +301,18 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
// image already exists
|
||||
img = ImageCache[key, loadFullImage] ?? img;
|
||||
}
|
||||
else
|
||||
else if (cacheImage)
|
||||
{
|
||||
// new guid
|
||||
|
||||
// save guid key
|
||||
GuidToKey[hash] = path;
|
||||
}
|
||||
}
|
||||
|
||||
// update cache
|
||||
ImageCache[path, loadFullImage] = img;
|
||||
if (cacheImage)
|
||||
{
|
||||
// update cache
|
||||
ImageCache[path, loadFullImage] = img;
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using NLog.Targets.Wrappers;
|
||||
using System.Runtime.ExceptionServices;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Logger
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@
|
|||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using MemoryPack;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -12,44 +15,55 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
/// Normally, it has better performance, but not readable
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It utilize MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// It utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// </remarks>
|
||||
public class BinaryStorage<T>
|
||||
public class BinaryStorage<T> : ISavable
|
||||
{
|
||||
protected T? Data;
|
||||
|
||||
public const string FileSuffix = ".cache";
|
||||
|
||||
// Let the derived class to set the file path
|
||||
public BinaryStorage(string filename, string directoryPath = null)
|
||||
{
|
||||
directoryPath ??= DataLocation.CacheDirectory;
|
||||
FilesFolders.ValidateDirectory(directoryPath);
|
||||
protected string FilePath { get; init; } = null!;
|
||||
|
||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
||||
protected string DirectoryPath { get; init; } = null!;
|
||||
|
||||
// Let the derived class to set the file path
|
||||
protected BinaryStorage()
|
||||
{
|
||||
}
|
||||
|
||||
public string FilePath { get; }
|
||||
public BinaryStorage(string filename)
|
||||
{
|
||||
DirectoryPath = DataLocation.CacheDirectory;
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
|
||||
public async ValueTask<T> TryLoadAsync(T defaultData)
|
||||
{
|
||||
if (Data != null) return Data;
|
||||
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
if (new FileInfo(FilePath).Length == 0)
|
||||
{
|
||||
Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
|
||||
await SaveAsync(defaultData);
|
||||
return defaultData;
|
||||
Data = defaultData;
|
||||
await SaveAsync();
|
||||
}
|
||||
|
||||
await using var stream = new FileStream(FilePath, FileMode.Open);
|
||||
var d = await DeserializeAsync(stream, defaultData);
|
||||
return d;
|
||||
Data = await DeserializeAsync(stream, defaultData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
|
||||
await SaveAsync(defaultData);
|
||||
return defaultData;
|
||||
Data = defaultData;
|
||||
await SaveAsync();
|
||||
}
|
||||
|
||||
return Data;
|
||||
}
|
||||
|
||||
private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
|
||||
|
|
@ -57,7 +71,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
try
|
||||
{
|
||||
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
|
||||
return t;
|
||||
return t ?? defaultData;
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
|
|
@ -66,6 +80,27 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var serialized = MemoryPackSerializer.Serialize(Data);
|
||||
|
||||
File.WriteAllBytes(FilePath, serialized);
|
||||
}
|
||||
|
||||
public async ValueTask SaveAsync()
|
||||
{
|
||||
await SaveAsync(Data.NonNull());
|
||||
}
|
||||
|
||||
// ImageCache need to convert data into concurrent dictionary for usage,
|
||||
// so we would better to clear the data
|
||||
public void ClearData()
|
||||
{
|
||||
Data = default;
|
||||
}
|
||||
|
||||
// ImageCache storages data in its class,
|
||||
// so we need to pass it to SaveAsync
|
||||
public async ValueTask SaveAsync(T data)
|
||||
{
|
||||
await using var stream = new FileStream(FilePath, FileMode.Create);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize object using json format.
|
||||
/// </summary>
|
||||
public class JsonStorage<T> where T : new()
|
||||
public class JsonStorage<T> : ISavable where T : new()
|
||||
{
|
||||
protected T? Data;
|
||||
|
||||
|
|
|
|||
49
Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
Normal file
49
Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
{
|
||||
public class PluginBinaryStorage<T> : BinaryStorage<T> where T : new()
|
||||
{
|
||||
private static readonly string ClassName = "PluginBinaryStorage";
|
||||
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
public PluginBinaryStorage(string cacheName, string cacheDirectory)
|
||||
{
|
||||
DirectoryPath = cacheDirectory;
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
FilePath = Path.Combine(DirectoryPath, $"{cacheName}{FileSuffix}");
|
||||
}
|
||||
|
||||
public new void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Save();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public new async Task SaveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await base.SaveAsync();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
|
|
@ -8,6 +6,9 @@ using System.Runtime.CompilerServices;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
|
|
@ -227,6 +228,11 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
void LogWarn(string className, string message, [CallerMemberName] string methodName = "");
|
||||
|
||||
/// <summary>
|
||||
/// Log error message. Preferred error logging method for plugins.
|
||||
/// </summary>
|
||||
void LogError(string className, string message, [CallerMemberName] string methodName = "");
|
||||
|
||||
/// <summary>
|
||||
/// Log an Exception. Will throw if in debug mode so developer will be aware,
|
||||
/// otherwise logs the eror message. This is the primary logging method used for Flow
|
||||
|
|
@ -346,6 +352,72 @@ namespace Flow.Launcher.Plugin
|
|||
public void StopLoadingBar();
|
||||
|
||||
/// <summary>
|
||||
/// Get all available themes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<ThemeData> GetAvailableThemes();
|
||||
|
||||
/// <summary>
|
||||
/// Get the current theme
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ThemeData GetCurrentTheme();
|
||||
|
||||
/// <summary>
|
||||
/// Set the current theme
|
||||
/// </summary>
|
||||
/// <param name="theme"></param>
|
||||
/// <returns>
|
||||
/// True if the theme is set successfully, false otherwise.
|
||||
/// </returns>
|
||||
public bool SetCurrentTheme(ThemeData theme);
|
||||
|
||||
/// Save all Flow's plugins caches
|
||||
/// </summary>
|
||||
void SavePluginCaches();
|
||||
|
||||
/// <summary>
|
||||
/// Load BinaryStorage for current plugin's cache. This is the method used to load cache from binary in Flow.
|
||||
/// When the file is not exist, it will create a new instance for the specific type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type for deserialization</typeparam>
|
||||
/// <param name="cacheName">Cache file name</param>
|
||||
/// <param name="cacheDirectory">Cache directory from plugin metadata</param>
|
||||
/// <param name="defaultData">Default data to return</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// </remarks>
|
||||
Task<T> LoadCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory, T defaultData) where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// Save BinaryStorage for current plugin's cache. This is the method used to save cache to binary in Flow.Launcher
|
||||
/// This method will save the original instance loaded with LoadCacheBinaryStorageAsync.
|
||||
/// This API call is for manually Save. Flow will automatically save all cache type that has called LoadCacheBinaryStorageAsync or SaveCacheBinaryStorageAsync previously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type for Serialization</typeparam>
|
||||
/// <param name="cacheName">Cache file name</param>
|
||||
/// <param name="cacheDirectory">Cache directory from plugin metadata</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// </remarks>
|
||||
Task SaveCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory) where T : new();
|
||||
|
||||
/// Load image from path. Support local, remote and data:image url.
|
||||
/// If image path is missing, it will return a missing icon.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the image.</param>
|
||||
/// <param name="loadFullImage">
|
||||
/// Load full image or not.
|
||||
/// </param>
|
||||
/// <param name="cacheImage">
|
||||
/// Cache the image or not. Cached image will be stored in FL cache.
|
||||
/// If the image is just used one time, it's better to set this to false.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true);
|
||||
|
||||
/// Update the plugin manifest
|
||||
/// </summary>
|
||||
/// <param name="usePrimaryUrlOnly">
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
namespace Flow.Launcher.Plugin
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Inherit this interface if additional data e.g. cache needs to be saved.
|
||||
/// Inherit this interface if you need to save additional data which is not a setting or cache,
|
||||
/// please implement this interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For storing plugin settings, prefer <see cref="IPublicAPI.LoadSettingJsonStorage{T}"/>
|
||||
/// or <see cref="IPublicAPI.SaveSettingJsonStorage{T}"/>.
|
||||
/// Once called, your settings will be automatically saved by Flow.
|
||||
/// or <see cref="IPublicAPI.SaveSettingJsonStorage{T}"/>.
|
||||
/// For storing plugin caches, prefer <see cref="IPublicAPI.LoadCacheBinaryStorageAsync{T}"/>
|
||||
/// or <see cref="IPublicAPI.SaveCacheBinaryStorageAsync{T}(string, string)"/>.
|
||||
/// Once called, those settings and caches will be automatically saved by Flow.
|
||||
/// </remarks>
|
||||
public interface ISavable : IFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Save additional plugin data, such as cache.
|
||||
/// Save additional plugin data.
|
||||
/// </summary>
|
||||
void Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
77
Flow.Launcher.Plugin/SharedModels/ThemeData.cs
Normal file
77
Flow.Launcher.Plugin/SharedModels/ThemeData.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
/// <summary>
|
||||
/// Theme data model
|
||||
/// </summary>
|
||||
public class ThemeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Theme file name without extension
|
||||
/// </summary>
|
||||
public string FileNameWithoutExtension { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Theme name
|
||||
/// </summary>
|
||||
public string Name { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the theme supports dark mode
|
||||
/// </summary>
|
||||
public bool? IsDark { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the theme supports blur effects
|
||||
/// </summary>
|
||||
public bool? HasBlur { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Theme data constructor
|
||||
/// </summary>
|
||||
public ThemeData(string fileNameWithoutExtension, string name, bool? isDark = null, bool? hasBlur = null)
|
||||
{
|
||||
FileNameWithoutExtension = fileNameWithoutExtension;
|
||||
Name = name;
|
||||
IsDark = isDark;
|
||||
HasBlur = hasBlur;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public static bool operator ==(ThemeData left, ThemeData right)
|
||||
{
|
||||
if (left is null && right is null)
|
||||
return true;
|
||||
if (left is null || right is null)
|
||||
return false;
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public static bool operator !=(ThemeData left, ThemeData right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is not ThemeData other)
|
||||
return false;
|
||||
return FileNameWithoutExtension == other.FileNameWithoutExtension &&
|
||||
Name == other.Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(FileNameWithoutExtension, Name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ namespace Flow.Launcher
|
|||
private async Task SetImageAsync(string imageName)
|
||||
{
|
||||
var imagePath = Path.Combine(Constant.ProgramDirectory, "Images", imageName);
|
||||
var imageSource = await ImageLoader.LoadAsync(imagePath);
|
||||
var imageSource = await App.API.LoadImageAsync(imagePath);
|
||||
Img.Source = imageSource;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ namespace Flow.Launcher
|
|||
|
||||
private async System.Threading.Tasks.Task LoadImageAsync()
|
||||
{
|
||||
imgClose.Source = await ImageLoader.LoadAsync(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\close.png"));
|
||||
imgClose.Source = await App.API.LoadImageAsync(Path.Combine(Constant.ProgramDirectory, "Images\\close.png"));
|
||||
}
|
||||
|
||||
void imgClose_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
|
|
@ -71,11 +71,11 @@ namespace Flow.Launcher
|
|||
|
||||
if (!File.Exists(iconPath))
|
||||
{
|
||||
imgIco.Source = await ImageLoader.LoadAsync(Path.Combine(Constant.ProgramDirectory, "Images\\app.png"));
|
||||
imgIco.Source = await App.API.LoadImageAsync(Path.Combine(Constant.ProgramDirectory, "Images\\app.png"));
|
||||
}
|
||||
else
|
||||
{
|
||||
imgIco.Source = await ImageLoader.LoadAsync(iconPath);
|
||||
imgIco.Source = await App.API.LoadImageAsync(iconPath);
|
||||
}
|
||||
|
||||
Show();
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ using System.Runtime.CompilerServices;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Squirrel;
|
||||
using Flow.Launcher.Core;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using Flow.Launcher.Core.Storage;
|
||||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Http;
|
||||
|
|
@ -27,17 +30,19 @@ using Flow.Launcher.Plugin.SharedModels;
|
|||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using JetBrains.Annotations;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using Squirrel;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public class PublicAPIInstance : IPublicAPI
|
||||
public class PublicAPIInstance : IPublicAPI, IRemovable
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly Internationalization _translater;
|
||||
private readonly MainViewModel _mainVM;
|
||||
|
||||
private Theme _theme;
|
||||
private Theme Theme => _theme ??= Ioc.Default.GetRequiredService<Theme>();
|
||||
|
||||
private readonly object _saveSettingsLock = new();
|
||||
|
||||
#region Constructor
|
||||
|
|
@ -88,7 +93,11 @@ namespace Flow.Launcher
|
|||
|
||||
public bool IsMainWindowVisible() => _mainVM.MainWindowVisibilityStatus;
|
||||
|
||||
public event VisibilityChangedEventHandler VisibilityChanged { add => _mainVM.VisibilityChanged += value; remove => _mainVM.VisibilityChanged -= value; }
|
||||
public event VisibilityChangedEventHandler VisibilityChanged
|
||||
{
|
||||
add => _mainVM.VisibilityChanged += value;
|
||||
remove => _mainVM.VisibilityChanged -= value;
|
||||
}
|
||||
|
||||
// Must use Ioc.Default.GetRequiredService<Updater>() to avoid circular dependency
|
||||
public void CheckForNewUpdate() => _ = Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync(false);
|
||||
|
|
@ -175,13 +184,14 @@ namespace Flow.Launcher
|
|||
public MatchResult FuzzySearch(string query, string stringToCompare) =>
|
||||
StringMatcher.FuzzySearch(query, stringToCompare);
|
||||
|
||||
public Task<string> HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url, token);
|
||||
public Task<string> HttpGetStringAsync(string url, CancellationToken token = default) =>
|
||||
Http.GetAsync(url, token);
|
||||
|
||||
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) =>
|
||||
Http.GetStreamAsync(url, token);
|
||||
|
||||
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null,
|
||||
CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token);
|
||||
CancellationToken token = default) =>Http.DownloadAsync(url, filePath, reportProgress, token);
|
||||
|
||||
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
|
||||
PluginManager.AddActionKeyword(pluginId, newActionKeyword);
|
||||
|
|
@ -200,8 +210,11 @@ namespace Flow.Launcher
|
|||
public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") =>
|
||||
Log.Warn(className, message, methodName);
|
||||
|
||||
public void LogException(string className, string message, Exception e,
|
||||
[CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName);
|
||||
public void LogError(string className, string message, [CallerMemberName] string methodName = "") =>
|
||||
Log.Error(className, message, methodName);
|
||||
|
||||
public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") =>
|
||||
Log.Exception(className, message, e, methodName);
|
||||
|
||||
private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new();
|
||||
|
||||
|
|
@ -214,20 +227,17 @@ namespace Flow.Launcher
|
|||
var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString();
|
||||
if (name == assemblyName)
|
||||
{
|
||||
_pluginJsonStorages.Remove(key, out var pluginJsonStorage);
|
||||
_pluginJsonStorages.TryRemove(key, out var _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save plugin settings.
|
||||
/// </summary>
|
||||
public void SavePluginSettings()
|
||||
{
|
||||
foreach (var value in _pluginJsonStorages.Values)
|
||||
{
|
||||
var method = value.GetType().GetMethod("Save");
|
||||
method?.Invoke(value, null);
|
||||
var savable = value as ISavable;
|
||||
savable?.Save();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,14 +259,6 @@ namespace Flow.Launcher
|
|||
((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
|
||||
}
|
||||
|
||||
public void SaveJsonStorage<T>(T settings) where T : new()
|
||||
{
|
||||
var type = typeof(T);
|
||||
_pluginJsonStorages[type] = new PluginJsonStorage<T>(settings);
|
||||
|
||||
((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
|
||||
}
|
||||
|
||||
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
|
||||
{
|
||||
using var explorer = new Process();
|
||||
|
|
@ -343,17 +345,75 @@ namespace Flow.Launcher
|
|||
|
||||
private readonly List<Func<int, int, SpecialKeyState, bool>> _globalKeyboardHandlers = new();
|
||||
|
||||
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Add(callback);
|
||||
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Remove(callback);
|
||||
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) =>
|
||||
_globalKeyboardHandlers.Add(callback);
|
||||
|
||||
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) =>
|
||||
_globalKeyboardHandlers.Remove(callback);
|
||||
|
||||
public void ReQuery(bool reselect = true) => _mainVM.ReQuery(reselect);
|
||||
|
||||
public void BackToQueryResults() => _mainVM.BackToQueryResults();
|
||||
|
||||
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) =>
|
||||
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "",
|
||||
MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None,
|
||||
MessageBoxResult defaultResult = MessageBoxResult.OK) =>
|
||||
MessageBoxEx.Show(messageBoxText, caption, button, icon, defaultResult);
|
||||
|
||||
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
|
||||
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync,
|
||||
Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
|
||||
|
||||
public List<ThemeData> GetAvailableThemes() => Theme.GetAvailableThemes();
|
||||
|
||||
public ThemeData GetCurrentTheme() => Theme.GetCurrentTheme();
|
||||
|
||||
public bool SetCurrentTheme(ThemeData theme) =>
|
||||
Theme.ChangeTheme(theme.FileNameWithoutExtension);
|
||||
|
||||
private readonly ConcurrentDictionary<(string, string, Type), object> _pluginBinaryStorages = new();
|
||||
|
||||
public void RemovePluginCaches(string cacheDirectory)
|
||||
{
|
||||
foreach (var keyValuePair in _pluginBinaryStorages)
|
||||
{
|
||||
var key = keyValuePair.Key;
|
||||
var currentCacheDirectory = key.Item2;
|
||||
if (cacheDirectory == currentCacheDirectory)
|
||||
{
|
||||
_pluginBinaryStorages.TryRemove(key, out var _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SavePluginCaches()
|
||||
{
|
||||
foreach (var value in _pluginBinaryStorages.Values)
|
||||
{
|
||||
var savable = value as ISavable;
|
||||
savable?.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> LoadCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory, T defaultData) where T : new()
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
|
||||
_pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage<T>(cacheName, cacheDirectory);
|
||||
|
||||
return await ((PluginBinaryStorage<T>)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).TryLoadAsync(defaultData);
|
||||
}
|
||||
|
||||
public async Task SaveCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory) where T : new()
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
|
||||
_pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage<T>(cacheName, cacheDirectory);
|
||||
|
||||
await ((PluginBinaryStorage<T>)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).SaveAsync();
|
||||
}
|
||||
|
||||
public ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) =>
|
||||
ImageLoader.LoadAsync(path, loadFullImage, cacheImage);
|
||||
|
||||
public Task<bool> UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) =>
|
||||
PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using Flow.Launcher.Helper;
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using ModernWpf;
|
||||
using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager;
|
||||
|
|
@ -28,25 +29,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
|||
public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/";
|
||||
public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";
|
||||
|
||||
private List<Theme.ThemeData> _themes;
|
||||
public List<Theme.ThemeData> Themes => _themes ??= _theme.LoadAvailableThemes();
|
||||
private List<ThemeData> _themes;
|
||||
public List<ThemeData> Themes => _themes ??= App.API.GetAvailableThemes();
|
||||
|
||||
private Theme.ThemeData _selectedTheme;
|
||||
public Theme.ThemeData SelectedTheme
|
||||
private ThemeData _selectedTheme;
|
||||
public ThemeData SelectedTheme
|
||||
{
|
||||
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme());
|
||||
get => _selectedTheme ??= Themes.Find(v => v == App.API.GetCurrentTheme());
|
||||
set
|
||||
{
|
||||
_selectedTheme = value;
|
||||
_theme.ChangeTheme(value.FileNameWithoutExtension);
|
||||
App.API.SetCurrentTheme(value);
|
||||
|
||||
// Update UI state
|
||||
OnPropertyChanged(nameof(BackdropType));
|
||||
OnPropertyChanged(nameof(IsBackdropEnabled));
|
||||
OnPropertyChanged(nameof(IsDropShadowEnabled));
|
||||
OnPropertyChanged(nameof(DropShadowEffect));
|
||||
|
||||
_ = _theme.RefreshFrameAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1168,7 +1168,7 @@ namespace Flow.Launcher.ViewModel
|
|||
else if (plugins.Count == 1)
|
||||
{
|
||||
PluginIconPath = plugins.Single().Metadata.IcoPath;
|
||||
PluginIconSource = await ImageLoader.LoadAsync(PluginIconPath);
|
||||
PluginIconSource = await App.API.LoadImageAsync(PluginIconPath);
|
||||
SearchIconVisibility = Visibility.Hidden;
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private async Task LoadIconAsync()
|
||||
{
|
||||
Image = await ImageLoader.LoadAsync(PluginPair.Metadata.IcoPath);
|
||||
Image = await App.API.LoadImageAsync(PluginPair.Metadata.IcoPath);
|
||||
OnPropertyChanged(nameof(Image));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
return await ImageLoader.LoadAsync(imagePath, loadFullImage).ConfigureAwait(false);
|
||||
return await App.API.LoadImageAsync(imagePath, loadFullImage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task LoadImageAsync()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Windows;
|
|||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Views;
|
||||
|
|
@ -89,7 +88,7 @@ public partial class PreviewPanel : UserControl, INotifyPropertyChanged
|
|||
|
||||
private async Task LoadImageAsync()
|
||||
{
|
||||
PreviewImage = await ImageLoader.LoadAsync(FilePath, true).ConfigureAwait(false);
|
||||
PreviewImage = await Main.Context.API.LoadImageAsync(FilePath, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.Program.Programs;
|
||||
using Flow.Launcher.Plugin.Program.Views;
|
||||
|
|
@ -19,19 +18,20 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
|||
|
||||
namespace Flow.Launcher.Plugin.Program
|
||||
{
|
||||
public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable,
|
||||
IDisposable
|
||||
public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, IAsyncReloadable, IDisposable
|
||||
{
|
||||
internal static Win32[] _win32s { get; set; }
|
||||
internal static UWPApp[] _uwps { get; set; }
|
||||
internal static Settings _settings { get; set; }
|
||||
private const string Win32CacheName = "Win32";
|
||||
private const string UwpCacheName = "UWP";
|
||||
|
||||
internal static List<Win32> _win32s { get; private set; }
|
||||
internal static List<UWPApp> _uwps { get; private set; }
|
||||
internal static Settings _settings { get; private set; }
|
||||
|
||||
internal static SemaphoreSlim _win32sLock = new(1, 1);
|
||||
internal static SemaphoreSlim _uwpsLock = new(1, 1);
|
||||
|
||||
internal static PluginInitContext Context { get; private set; }
|
||||
|
||||
private static BinaryStorage<Win32[]> _win32Storage;
|
||||
private static BinaryStorage<UWPApp[]> _uwpStorage;
|
||||
|
||||
private static readonly List<Result> emptyResults = new();
|
||||
|
||||
private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
|
||||
|
|
@ -77,22 +77,15 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
private static readonly string WindowsAppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps");
|
||||
|
||||
static Main()
|
||||
{
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_win32Storage.SaveAsync(_win32s);
|
||||
_uwpStorage.SaveAsync(_uwps);
|
||||
}
|
||||
|
||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
var result = await cache.GetOrCreateAsync(query.Search, async entry =>
|
||||
{
|
||||
var resultList = await Task.Run(() =>
|
||||
var resultList = await Task.Run(async () =>
|
||||
{
|
||||
await _win32sLock.WaitAsync(token);
|
||||
await _uwpsLock.WaitAsync(token);
|
||||
|
||||
try
|
||||
{
|
||||
// Collect all UWP Windows app directories
|
||||
|
|
@ -119,7 +112,11 @@ namespace Flow.Launcher.Plugin.Program
|
|||
Log.Debug("|Flow.Launcher.Plugin.Program.Main|Query operation cancelled");
|
||||
return emptyResults;
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
_uwpsLock.Release();
|
||||
_win32sLock.Release();
|
||||
}
|
||||
}, token);
|
||||
|
||||
resultList = resultList.Any() ? resultList : emptyResults;
|
||||
|
|
@ -189,9 +186,12 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
_settings = context.API.LoadSettingJsonStorage<Settings>();
|
||||
|
||||
var _win32sCount = 0;
|
||||
var _uwpsCount = 0;
|
||||
await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () =>
|
||||
{
|
||||
FilesFolders.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
var pluginCacheDirectory = Context.CurrentPluginMetadata.PluginCacheDirectoryPath;
|
||||
FilesFolders.ValidateDirectory(pluginCacheDirectory);
|
||||
|
||||
static void MoveFile(string sourcePath, string destinationPath)
|
||||
{
|
||||
|
|
@ -236,22 +236,27 @@ namespace Flow.Launcher.Plugin.Program
|
|||
}
|
||||
|
||||
// Move old cache files to the new cache directory
|
||||
var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"Win32.cache");
|
||||
var newWin32CacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"Win32.cache");
|
||||
var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"{Win32CacheName}.cache");
|
||||
var newWin32CacheFile = Path.Combine(pluginCacheDirectory, $"{Win32CacheName}.cache");
|
||||
MoveFile(oldWin32CacheFile, newWin32CacheFile);
|
||||
var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"UWP.cache");
|
||||
var newUWPCacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"UWP.cache");
|
||||
var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"{UwpCacheName}.cache");
|
||||
var newUWPCacheFile = Path.Combine(pluginCacheDirectory, $"{UwpCacheName}.cache");
|
||||
MoveFile(oldUWPCacheFile, newUWPCacheFile);
|
||||
|
||||
_win32Storage = new BinaryStorage<Win32[]>("Win32", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_win32s = await _win32Storage.TryLoadAsync(Array.Empty<Win32>());
|
||||
_uwpStorage = new BinaryStorage<UWPApp[]>("UWP", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_uwps = await _uwpStorage.TryLoadAsync(Array.Empty<UWPApp>());
|
||||
});
|
||||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
|
||||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
|
||||
await _win32sLock.WaitAsync();
|
||||
_win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List<Win32>());
|
||||
_win32sCount = _win32s.Count;
|
||||
_win32sLock.Release();
|
||||
|
||||
bool cacheEmpty = !_win32s.Any() || !_uwps.Any();
|
||||
await _uwpsLock.WaitAsync();
|
||||
_uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List<UWPApp>());
|
||||
_uwpsCount = _uwps.Count;
|
||||
_uwpsLock.Release();
|
||||
});
|
||||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32sCount}>");
|
||||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwpsCount}>");
|
||||
|
||||
var cacheEmpty = _win32sCount == 0 || _uwpsCount == 0;
|
||||
|
||||
if (cacheEmpty || _settings.LastIndexTime.AddHours(30) < DateTime.Now)
|
||||
{
|
||||
|
|
@ -273,36 +278,69 @@ namespace Flow.Launcher.Plugin.Program
|
|||
}
|
||||
}
|
||||
|
||||
public static void IndexWin32Programs()
|
||||
public static async Task IndexWin32ProgramsAsync()
|
||||
{
|
||||
var win32S = Win32.All(_settings);
|
||||
_win32s = win32S;
|
||||
ResetCache();
|
||||
_win32Storage.SaveAsync(_win32s);
|
||||
_settings.LastIndexTime = DateTime.Now;
|
||||
await _win32sLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var win32S = Win32.All(_settings);
|
||||
_win32s.Clear();
|
||||
foreach (var win32 in win32S)
|
||||
{
|
||||
_win32s.Add(win32);
|
||||
}
|
||||
ResetCache();
|
||||
await Context.API.SaveCacheBinaryStorageAsync<List<Win32>>(Win32CacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_settings.LastIndexTime = DateTime.Now;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception("|Flow.Launcher.Plugin.Program.Main|Failed to index Win32 programs", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_win32sLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static void IndexUwpPrograms()
|
||||
public static async Task IndexUwpProgramsAsync()
|
||||
{
|
||||
var applications = UWPPackage.All(_settings);
|
||||
_uwps = applications;
|
||||
ResetCache();
|
||||
_uwpStorage.SaveAsync(_uwps);
|
||||
_settings.LastIndexTime = DateTime.Now;
|
||||
await _uwpsLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var uwps = UWPPackage.All(_settings);
|
||||
_uwps.Clear();
|
||||
foreach (var uwp in uwps)
|
||||
{
|
||||
_uwps.Add(uwp);
|
||||
}
|
||||
ResetCache();
|
||||
await Context.API.SaveCacheBinaryStorageAsync<List<UWPApp>>(UwpCacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_settings.LastIndexTime = DateTime.Now;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception("|Flow.Launcher.Plugin.Program.Main|Failed to index Uwp programs", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_uwpsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task IndexProgramsAsync()
|
||||
{
|
||||
var a = Task.Run(() =>
|
||||
var win32Task = Task.Run(async () =>
|
||||
{
|
||||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
|
||||
await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32ProgramsAsync);
|
||||
});
|
||||
|
||||
var b = Task.Run(() =>
|
||||
var uwpTask = Task.Run(async () =>
|
||||
{
|
||||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpPrograms);
|
||||
await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpProgramsAsync);
|
||||
});
|
||||
await Task.WhenAll(a, b).ConfigureAwait(false);
|
||||
|
||||
await Task.WhenAll(win32Task, uwpTask).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static void ResetCache()
|
||||
|
|
@ -314,7 +352,7 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new ProgramSetting(Context, _settings, _win32s, _uwps);
|
||||
return new ProgramSetting(Context, _settings);
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
|
|
@ -342,7 +380,7 @@ namespace Flow.Launcher.Plugin.Program
|
|||
Title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_program"),
|
||||
Action = c =>
|
||||
{
|
||||
DisableProgram(program);
|
||||
_ = DisableProgramAsync(program);
|
||||
Context.API.ShowMsg(
|
||||
Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"),
|
||||
Context.API.GetTranslation(
|
||||
|
|
@ -358,30 +396,43 @@ namespace Flow.Launcher.Plugin.Program
|
|||
return menuOptions;
|
||||
}
|
||||
|
||||
private static void DisableProgram(IProgram programToDelete)
|
||||
private static async Task DisableProgramAsync(IProgram programToDelete)
|
||||
{
|
||||
if (_settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
return;
|
||||
|
||||
await _uwpsLock.WaitAsync();
|
||||
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
{
|
||||
var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
||||
program.Enabled = false;
|
||||
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
IndexUwpPrograms();
|
||||
});
|
||||
_uwpsLock.Release();
|
||||
|
||||
// Reindex UWP programs
|
||||
_ = Task.Run(IndexUwpProgramsAsync);
|
||||
return;
|
||||
}
|
||||
else if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
else
|
||||
{
|
||||
_uwpsLock.Release();
|
||||
}
|
||||
|
||||
await _win32sLock.WaitAsync();
|
||||
if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
{
|
||||
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
||||
program.Enabled = false;
|
||||
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
IndexWin32Programs();
|
||||
});
|
||||
_win32sLock.Release();
|
||||
|
||||
// Reindex Win32 programs
|
||||
_ = Task.Run(IndexWin32ProgramsAsync);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_win32sLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
PackageChangeChannel.Reader.TryRead(out _);
|
||||
await Task.Run(Main.IndexUwpPrograms);
|
||||
await Task.Run(Main.IndexUwpProgramsAsync);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -797,7 +797,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
}
|
||||
|
||||
await Task.Run(Main.IndexWin32Programs);
|
||||
await Task.Run(Main.IndexWin32ProgramsAsync);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.Program.Views.Models;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Views.Commands
|
||||
|
|
@ -15,21 +16,24 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
|||
.ToList();
|
||||
}
|
||||
|
||||
internal static void DisplayAllPrograms()
|
||||
internal static async Task DisplayAllProgramsAsync()
|
||||
{
|
||||
await Main._win32sLock.WaitAsync();
|
||||
var win32 = Main._win32s
|
||||
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||
.Select(x => new ProgramSource(x));
|
||||
ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
|
||||
Main._win32sLock.Release();
|
||||
|
||||
await Main._uwpsLock.WaitAsync();
|
||||
var uwp = Main._uwps
|
||||
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||
.Select(x => new ProgramSource(x));
|
||||
|
||||
ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
|
||||
ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
|
||||
Main._uwpsLock.Release();
|
||||
}
|
||||
|
||||
internal static void SetProgramSourcesStatus(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
|
||||
internal static async Task SetProgramSourcesStatusAsync(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
|
||||
{
|
||||
foreach(var program in ProgramSetting.ProgramSettingDisplayList)
|
||||
{
|
||||
|
|
@ -39,14 +43,17 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
|||
}
|
||||
}
|
||||
|
||||
foreach(var program in Main._win32s)
|
||||
await Main._win32sLock.WaitAsync();
|
||||
foreach (var program in Main._win32s)
|
||||
{
|
||||
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
||||
{
|
||||
program.Enabled = status;
|
||||
}
|
||||
}
|
||||
Main._win32sLock.Release();
|
||||
|
||||
await Main._uwpsLock.WaitAsync();
|
||||
foreach (var program in Main._uwps)
|
||||
{
|
||||
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
||||
|
|
@ -54,6 +61,7 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
|||
program.Enabled = status;
|
||||
}
|
||||
}
|
||||
Main._uwpsLock.Release();
|
||||
}
|
||||
|
||||
internal static void StoreDisabledInSettings()
|
||||
|
|
@ -72,12 +80,22 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
|||
Main._settings.DisabledProgramSources.RemoveAll(t1 => t1.Enabled);
|
||||
}
|
||||
|
||||
internal static bool IsReindexRequired(this List<ProgramSource> selectedItems)
|
||||
internal static async Task<bool> IsReindexRequiredAsync(this List<ProgramSource> selectedItems)
|
||||
{
|
||||
// Not in cache
|
||||
if (selectedItems.Any(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
|
||||
await Main._win32sLock.WaitAsync();
|
||||
await Main._uwpsLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (selectedItems.Any(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
|
||||
&& selectedItems.Any(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)))
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Main._win32sLock.Release();
|
||||
Main._uwpsLock.Release();
|
||||
}
|
||||
|
||||
// ProgramSources holds list of user added directories,
|
||||
// so when we enable/disable we need to reindex to show/not show the programs
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
/// </summary>
|
||||
public partial class ProgramSetting : UserControl
|
||||
{
|
||||
private PluginInitContext context;
|
||||
private Settings _settings;
|
||||
private readonly PluginInitContext context;
|
||||
private readonly Settings _settings;
|
||||
private GridViewColumnHeader _lastHeaderClicked;
|
||||
private ListSortDirection _lastDirection;
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
public bool ShowUWPCheckbox => UWPPackage.SupportUWP();
|
||||
|
||||
public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWPApp[] uwps)
|
||||
public ProgramSetting(PluginInitContext context, Settings settings)
|
||||
{
|
||||
this.context = context;
|
||||
_settings = settings;
|
||||
|
|
@ -183,7 +183,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
EditProgramSource(selectedProgramSource);
|
||||
}
|
||||
|
||||
private void EditProgramSource(ProgramSource selectedProgramSource)
|
||||
private async void EditProgramSource(ProgramSource selectedProgramSource)
|
||||
{
|
||||
if (selectedProgramSource == null)
|
||||
{
|
||||
|
|
@ -202,13 +202,13 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
{
|
||||
if (selectedProgramSource.Enabled)
|
||||
{
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(new List<ProgramSource> { selectedProgramSource },
|
||||
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List<ProgramSource> { selectedProgramSource },
|
||||
true); // sync status in win32, uwp and disabled
|
||||
ProgramSettingDisplay.RemoveDisabledFromSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(new List<ProgramSource> { selectedProgramSource },
|
||||
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List<ProgramSource> { selectedProgramSource },
|
||||
false);
|
||||
ProgramSettingDisplay.StoreDisabledInSettings();
|
||||
}
|
||||
|
|
@ -277,14 +277,14 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
}
|
||||
}
|
||||
|
||||
private void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
|
||||
private async void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProgramSettingDisplay.DisplayAllPrograms();
|
||||
await ProgramSettingDisplay.DisplayAllProgramsAsync();
|
||||
|
||||
ViewRefresh();
|
||||
}
|
||||
|
||||
private void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
|
||||
private async void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectedItems = programSourceView
|
||||
.SelectedItems.Cast<ProgramSource>()
|
||||
|
|
@ -311,18 +311,18 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
}
|
||||
else if (HasMoreOrEqualEnabledItems(selectedItems))
|
||||
{
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, false);
|
||||
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, false);
|
||||
|
||||
ProgramSettingDisplay.StoreDisabledInSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, true);
|
||||
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, true);
|
||||
|
||||
ProgramSettingDisplay.RemoveDisabledFromSettings();
|
||||
}
|
||||
|
||||
if (selectedItems.IsReindexRequired())
|
||||
if (await selectedItems.IsReindexRequiredAsync())
|
||||
ReIndexing();
|
||||
|
||||
programSourceView.SelectedItems.Clear();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Sys
|
||||
{
|
||||
|
|
@ -11,32 +10,6 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
|
||||
private readonly PluginInitContext _context;
|
||||
|
||||
// Do not initialize it in the constructor, because it will cause null reference in
|
||||
// var dicts = Application.Current.Resources.MergedDictionaries; line of Theme
|
||||
private Theme theme = null;
|
||||
private Theme Theme => theme ??= Ioc.Default.GetRequiredService<Theme>();
|
||||
|
||||
#region Theme Selection
|
||||
|
||||
// Theme select codes simplified from SettingsPaneThemeViewModel.cs
|
||||
|
||||
private Theme.ThemeData _selectedTheme;
|
||||
public Theme.ThemeData SelectedTheme
|
||||
{
|
||||
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Theme.GetCurrentTheme());
|
||||
set
|
||||
{
|
||||
_selectedTheme = value;
|
||||
Theme.ChangeTheme(value.FileNameWithoutExtension);
|
||||
|
||||
_ = Theme.RefreshFrameAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Theme.ThemeData> Themes => Theme.LoadAvailableThemes();
|
||||
|
||||
#endregion
|
||||
|
||||
public ThemeSelector(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
|
|
@ -44,28 +17,30 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
var themes = _context.API.GetAvailableThemes();
|
||||
var selectedTheme = _context.API.GetCurrentTheme();
|
||||
|
||||
var search = query.SecondToEndSearch;
|
||||
if (string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
return Themes.Select(CreateThemeResult)
|
||||
return themes.Select(x => CreateThemeResult(x, selectedTheme))
|
||||
.OrderBy(x => x.Title)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return Themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name)))
|
||||
return themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name)))
|
||||
.Where(x => x.matchResult.IsSearchPrecisionScoreMet())
|
||||
.Select(x => CreateThemeResult(x.theme, x.matchResult.Score, x.matchResult.MatchData))
|
||||
.Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData))
|
||||
.OrderBy(x => x.Title)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private Result CreateThemeResult(Theme.ThemeData theme) => CreateThemeResult(theme, 0, null);
|
||||
private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null);
|
||||
|
||||
private Result CreateThemeResult(Theme.ThemeData theme, int score, IList<int> highlightData)
|
||||
private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList<int> highlightData)
|
||||
{
|
||||
string themeName = theme.Name;
|
||||
string title;
|
||||
if (theme == SelectedTheme)
|
||||
if (theme == selectedTheme)
|
||||
{
|
||||
title = $"{theme.Name} ★";
|
||||
// Set current theme to the top
|
||||
|
|
@ -101,8 +76,10 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Score = score,
|
||||
Action = c =>
|
||||
{
|
||||
SelectedTheme = theme;
|
||||
_context.API.ReQuery();
|
||||
if (_context.API.SetCurrentTheme(theme))
|
||||
{
|
||||
_context.API.ReQuery();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Flow.Launcher.Infrastructure.Image;
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
#pragma warning disable IDE0005
|
||||
|
|
@ -41,8 +40,8 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
#if DEBUG
|
||||
throw;
|
||||
#else
|
||||
Main._context.API.ShowMsgBox(string.Format("Copying the selected image file to {0} has failed, changes will now be reverted", destinationFileNameFullPath));
|
||||
UpdateIconAttributes(selectedSearchSource, fullPathToOriginalImage);
|
||||
Main._context.API.ShowMsgBox(string.Format("Copying the selected image file to {0} has failed, changes will now be reverted", destinationFileNameFullPath));
|
||||
UpdateIconAttributes(selectedSearchSource, fullPathToOriginalImage);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +60,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
|
||||
internal async ValueTask<ImageSource> LoadPreviewIconAsync(string pathToPreviewIconImage)
|
||||
{
|
||||
return await ImageLoader.LoadAsync(pathToPreviewIconImage);
|
||||
return await Main._context.API.LoadImageAsync(pathToPreviewIconImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ using System.Linq;
|
|||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
|
||||
|
|
@ -22,11 +20,11 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
try
|
||||
{
|
||||
const string api = "http://suggestion.baidu.com/su?json=1&wd=";
|
||||
result = await Http.GetAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false);
|
||||
result = await Main._context.API.HttpGetStringAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
|
||||
{
|
||||
Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e);
|
||||
Main._context.API.LogException(nameof(Baidu), "Can't get suggestion from Baidu", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +39,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Log.Exception("|Baidu.Suggestions|can't parse suggestions", e);
|
||||
Main._context.API.LogException(nameof(Baidu), "Can't parse suggestions", e);
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -10,16 +8,15 @@ using System.Threading;
|
|||
|
||||
namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
||||
{
|
||||
class Bing : SuggestionSource
|
||||
public class Bing : SuggestionSource
|
||||
{
|
||||
public override async Task<List<string>> SuggestionsAsync(string query, CancellationToken token)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
const string api = "https://api.bing.com/qsonhs.aspx?q=";
|
||||
|
||||
await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false);
|
||||
await using var resultStream = await Main._context.API.HttpGetStreamAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false);
|
||||
|
||||
using var json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token));
|
||||
var root = json.RootElement.GetProperty("AS");
|
||||
|
|
@ -33,18 +30,15 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
.EnumerateArray()
|
||||
.Select(s => s.GetProperty("Txt").GetString()))
|
||||
.ToList();
|
||||
|
||||
|
||||
|
||||
}
|
||||
catch (Exception e) when (e is HttpRequestException or { InnerException: TimeoutException })
|
||||
{
|
||||
Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e);
|
||||
Main._context.API.LogException(nameof(Bing), "Can't get suggestion from Bing", e);
|
||||
return null;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Log.Exception("|Bing.Suggestions|can't parse suggestions", e);
|
||||
Main._context.API.LogException(nameof(Bing), "Can't parse suggestions", e);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Text.Json;
|
||||
|
|
@ -25,7 +23,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
{
|
||||
const string api = "https://duckduckgo.com/ac/?type=list&q=";
|
||||
|
||||
await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false);
|
||||
await using var resultStream = await Main._context.API.HttpGetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false);
|
||||
|
||||
using var json = await JsonDocument.ParseAsync(resultStream, cancellationToken: token);
|
||||
|
||||
|
|
@ -36,12 +34,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
}
|
||||
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
|
||||
{
|
||||
Log.Exception("|DuckDuckGo.Suggestions|Can't get suggestion from DuckDuckGo", e);
|
||||
Main._context.API.LogException(nameof(DuckDuckGo), "Can't get suggestion from DuckDuckGo", e);
|
||||
return null;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Log.Exception("|DuckDuckGo.Suggestions|can't parse suggestions", e);
|
||||
Main._context.API.LogException(nameof(DuckDuckGo), "Can't parse suggestions", e);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Text.Json;
|
||||
|
|
@ -18,7 +16,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
{
|
||||
const string api = "https://www.google.com/complete/search?output=chrome&q=";
|
||||
|
||||
await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false);
|
||||
await using var resultStream = await Main._context.API.HttpGetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false);
|
||||
|
||||
using var json = await JsonDocument.ParseAsync(resultStream, cancellationToken: token);
|
||||
|
||||
|
|
@ -29,12 +27,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
}
|
||||
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
|
||||
{
|
||||
Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e);
|
||||
Main._context.API.LogException(nameof(Google), "Can't get suggestion from Google", e);
|
||||
return null;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Log.Exception("|Google.Suggestions|can't parse suggestions", e);
|
||||
Main._context.API.LogException(nameof(Google), "Can't parse suggestions", e);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue