Merge branch 'dev' into 250405-KoreanIME

This commit is contained in:
DB p 2025-04-08 17:04:33 +09:00
commit 762b267a59
29 changed files with 283 additions and 161 deletions

View file

@ -1,5 +1,6 @@
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
using System;
using System.Collections.Generic;
using System.Net;

View file

@ -2,6 +2,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins
{

View file

@ -1,8 +1,9 @@
using Flow.Launcher.Infrastructure.Logger;
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins
{
@ -17,11 +18,11 @@ namespace Flow.Launcher.Core.ExternalPlugins
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
private static DateTime lastFetchedAt = DateTime.MinValue;
private static TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
public static List<UserPlugin> UserPlugins { get; private set; }
public static async Task<bool> UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false)
public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
{
try
{
@ -43,7 +44,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
catch (Exception e)
{
Log.Exception($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http request failed", e);
Ioc.Default.GetRequiredService<IPublicAPI>().LogException(nameof(PluginsManifest), "Http request failed", e);
}
finally
{

View file

@ -1,23 +0,0 @@
using System;
namespace Flow.Launcher.Core.ExternalPlugins
{
public record UserPlugin
{
public string ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Author { get; set; }
public string Version { get; set; }
public string Language { get; set; }
public string Website { get; set; }
public string UrlDownload { get; set; }
public string UrlSourceCode { get; set; }
public string LocalInstallPath { get; set; }
public string IcoPath { get; set; }
public DateTime? LatestReleaseDate { get; set; }
public DateTime? DateAdded { get; set; }
public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath);
}
}

View file

@ -454,16 +454,11 @@ namespace Flow.Launcher.Core.Plugin
#region Public functions
public static bool PluginModified(string uuid)
public static bool PluginModified(string id)
{
return _modifiedPlugins.Contains(uuid);
return _modifiedPlugins.Contains(id);
}
/// <summary>
/// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url,
/// unless it's a local path installation
/// </summary>
public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
InstallPlugin(newVersion, zipFilePath, checkModified:false);
@ -471,17 +466,11 @@ namespace Flow.Launcher.Core.Plugin
_modifiedPlugins.Add(existingVersion.ID);
}
/// <summary>
/// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation
/// </summary>
public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
{
InstallPlugin(plugin, zipFilePath, checkModified: true);
}
/// <summary>
/// Uninstall a plugin.
/// </summary>
public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
{
await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
@ -525,20 +514,20 @@ namespace Flow.Launcher.Core.Plugin
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
var defaultPluginIDs = new List<string>
{
"0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
"CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
"572be03c74c642baae319fc283e561a8", // Explorer
"6A122269676E40EB86EB543B945932B9", // PluginIndicator
"9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
"b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
"791FC278BA414111B8D1886DFE447410", // Program
"D409510CD0D2481F853690A07E6DC426", // Shell
"CEA08895D2544B019B2E9C5009600DF4", // Sys
"0308FD86DE0A4DEE8D62B9B535370992", // URL
"565B73353DBF4806919830B9202EE3BF", // WebSearch
"5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
};
{
"0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
"CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
"572be03c74c642baae319fc283e561a8", // Explorer
"6A122269676E40EB86EB543B945932B9", // PluginIndicator
"9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
"b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
"791FC278BA414111B8D1886DFE447410", // Program
"D409510CD0D2481F853690A07E6DC426", // Shell
"CEA08895D2544B019B2E9C5009600DF4", // Sys
"0308FD86DE0A4DEE8D62B9B535370992", // URL
"565B73353DBF4806919830B9202EE3BF", // WebSearch
"5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
};
// Treat default plugin differently, it needs to be removable along with each flow release
var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)

View file

@ -5,12 +5,10 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using static Flow.Launcher.Infrastructure.Http.Http;
namespace Flow.Launcher.Infrastructure.Image
{
@ -28,7 +26,6 @@ namespace Flow.Launcher.Infrastructure.Image
public const int SmallIconSize = 64;
public const int FullIconSize = 256;
private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
public static async Task InitializeAsync()
@ -183,7 +180,7 @@ namespace Flow.Launcher.Infrastructure.Image
private static async Task<BitmapImage> LoadRemoteImageAsync(bool loadFullImage, Uri uriResult)
{
// Download image from url
await using var resp = await GetStreamAsync(uriResult);
await using var resp = await Http.Http.GetStreamAsync(uriResult);
await using var buffer = new MemoryStream();
await resp.CopyToAsync(buffer);
buffer.Seek(0, SeekOrigin.Begin);
@ -287,7 +284,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);
@ -303,16 +300,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;

View file

@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace Flow.Launcher.Plugin
{
@ -344,5 +345,76 @@ namespace Flow.Launcher.Plugin
/// Stop the loading bar in main window
/// </summary>
public void StopLoadingBar();
/// <summary>
/// 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">
/// FL has multiple urls to download the plugin manifest. Set this to true to only use the primary url.
/// </param>
/// <param name="token"></param>
/// <returns>True if the manifest is updated successfully, false otherwise</returns>
public Task<bool> UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default);
/// <summary>
/// Get the plugin manifest
/// </summary>
/// <returns></returns>
public IReadOnlyList<UserPlugin> GetPluginManifest();
/// <summary>
/// Check if the plugin has been modified.
/// If this plugin is updated, installed or uninstalled and users do not restart the app,
/// it will be marked as modified
/// </summary>
/// <param name="id">Plugin id</param>
/// <returns></returns>
public bool PluginModified(string id);
/// <summary>
/// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url,
/// unless it's a local path installation
/// </summary>
/// <param name="pluginMetadata">The metadata of the old plugin to update</param>
/// <param name="plugin">The new plugin to update</param>
/// <param name="zipFilePath">
/// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed.
/// </param>
/// <returns></returns>
public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath);
/// <summary>
/// Install a plugin. By default will remove the zip file if installation is from url,
/// unless it's a local path installation
/// </summary>
/// <param name="plugin">The plugin to install</param>
/// <param name="zipFilePath">
/// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed.
/// </param>
public void InstallPlugin(UserPlugin plugin, string zipFilePath);
/// <summary>
/// Uninstall a plugin
/// </summary>
/// <param name="pluginMetadata">The metadata of the plugin to uninstall</param>
/// <param name="removePluginSettings">
/// Plugin has their own settings. If this is set to true, the plugin settings will be removed.
/// </param>
/// <returns></returns>
public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false);
}
}

View file

@ -0,0 +1,80 @@
using System;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// User Plugin Model for Flow Launcher
/// </summary>
public record UserPlugin
{
/// <summary>
/// Unique identifier of the plugin
/// </summary>
public string ID { get; set; }
/// <summary>
/// Name of the plugin
/// </summary>
public string Name { get; set; }
/// <summary>
/// Description of the plugin
/// </summary>
public string Description { get; set; }
/// <summary>
/// Author of the plugin
/// </summary>
public string Author { get; set; }
/// <summary>
/// Version of the plugin
/// </summary>
public string Version { get; set; }
/// <summary>
/// Allow language of the plugin <see cref="AllowedLanguage"/>
/// </summary>
public string Language { get; set; }
/// <summary>
/// Website of the plugin
/// </summary>
public string Website { get; set; }
/// <summary>
/// URL to download the plugin
/// </summary>
public string UrlDownload { get; set; }
/// <summary>
/// URL to the source code of the plugin
/// </summary>
public string UrlSourceCode { get; set; }
/// <summary>
/// Local path where the plugin is installed
/// </summary>
public string LocalInstallPath { get; set; }
/// <summary>
/// Icon path of the plugin
/// </summary>
public string IcoPath { get; set; }
/// <summary>
/// The date when the plugin was last updated
/// </summary>
public DateTime? LatestReleaseDate { get; set; }
/// <summary>
/// The date when the plugin was added to the local system
/// </summary>
public DateTime? DateAdded { get; set; }
/// <summary>
/// Indicates whether the plugin is installed from a local path
/// </summary>
public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath);
}
}

View file

@ -179,8 +179,6 @@ namespace Flow.Launcher
Current.MainWindow = _mainWindow;
Current.MainWindow.Title = Constant.FlowLauncher;
HotKeyMapper.Initialize();
// main windows needs initialized before theme change because of blur settings
Ioc.Default.GetRequiredService<Theme>().ChangeTheme();

View file

@ -16,8 +16,10 @@ using System.Windows.Threading;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.ViewModel;
@ -173,6 +175,9 @@ namespace Flow.Launcher
// Without this part, when shown for the first time, switching the context menu does not move the cursor to the end.
_viewModel.QueryTextCursorMovedToEnd = false;
// Initialize hotkey mapper after window is loaded
HotKeyMapper.Initialize();
// View model property changed event
_viewModel.PropertyChanged += (o, e) =>
{
@ -289,6 +294,7 @@ namespace Flow.Launcher
_notifyIcon.Visible = false;
App.API.SaveAppAllSettings();
e.Cancel = true;
await ImageLoader.WaitSaveAsync();
await PluginManager.DisposePluginsAsync();
Notification.Uninstall();
// After plugins are all disposed, we can close the main window

View file

@ -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;
}

View file

@ -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();

View file

@ -10,6 +10,7 @@ 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;
@ -28,6 +29,7 @@ using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.ViewModel;
using JetBrains.Annotations;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Core.ExternalPlugins;
namespace Flow.Launcher
{
@ -354,6 +356,25 @@ namespace Flow.Launcher
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
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);
public IReadOnlyList<UserPlugin> GetPluginManifest() => PluginsManifest.UserPlugins;
public bool PluginModified(string id) => PluginManager.PluginModified(id);
public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath) =>
PluginManager.UpdatePluginAsync(pluginMetadata, plugin, zipFilePath);
public void InstallPlugin(UserPlugin plugin, string zipFilePath) =>
PluginManager.InstallPlugin(plugin, zipFilePath);
public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) =>
PluginManager.UninstallPluginAsync(pluginMetadata, removePluginSettings);
#endregion
#region Private Methods

View file

@ -2,7 +2,6 @@
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
@ -14,7 +13,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
public string FilterText { get; set; } = string.Empty;
public IList<PluginStoreItemViewModel> ExternalPlugins =>
PluginsManifest.UserPlugins?.Select(p => new PluginStoreItemViewModel(p))
App.API.GetPluginManifest()?.Select(p => new PluginStoreItemViewModel(p))
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
@ -24,7 +23,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
[RelayCommand]
private async Task RefreshExternalPluginsAsync()
{
if (await PluginsManifest.UpdateManifestAsync())
if (await App.API.UpdatePluginManifestAsync())
{
OnPropertyChanged(nameof(ExternalPlugins));
}

View file

@ -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

View file

@ -1,10 +1,8 @@
using System;
using System.Linq;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
using SemanticVersioning;
using Version = SemanticVersioning.Version;
namespace Flow.Launcher.ViewModel

View file

@ -45,7 +45,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));
}

View file

@ -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()

View file

@ -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;

View file

@ -1,5 +1,4 @@
using Flow.Launcher.Core.ExternalPlugins;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Flow.Launcher.Plugin.PluginsManager

View file

@ -18,8 +18,6 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
<ProjectReference Include="..\..\Flow.Launcher.Core\Flow.Launcher.Core.csproj" />
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup>
@ -37,4 +35,8 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.4.2" />
</ItemGroup>
</Project>

View file

@ -1,18 +1,16 @@
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Plugin.PluginsManager.ViewModels;
using Flow.Launcher.Plugin.PluginsManager.Views;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using Flow.Launcher.Infrastructure;
using System.Threading.Tasks;
using System.Threading;
using Flow.Launcher.Plugin.PluginsManager.ViewModels;
using Flow.Launcher.Plugin.PluginsManager.Views;
namespace Flow.Launcher.Plugin.PluginsManager
{
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
{
internal PluginInitContext Context { get; set; }
internal static PluginInitContext Context { get; set; }
internal Settings Settings;
@ -35,7 +33,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
contextMenu = new ContextMenu(Context);
pluginManager = new PluginsManager(Context, Settings);
await PluginsManifest.UpdateManifestAsync();
await Context.API.UpdatePluginManifestAsync();
}
public List<Result> LoadContextMenus(Result selectedResult)
@ -56,7 +54,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Settings.UpdateCommand => await pluginManager.RequestUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery),
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
{
hotkey.Score = StringMatcher.FuzzySearch(query.Search, hotkey.Title).Score;
hotkey.Score = Context.API.FuzzySearch(query.Search, hotkey.Title).Score;
return hotkey.Score > 0;
}).ToList()
};

View file

@ -1,10 +1,4 @@
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -12,12 +6,15 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Plugin.PluginsManager
{
internal class PluginsManager
{
private const string zip = "zip";
private const string ZipSuffix = "zip";
private static readonly string ClassName = nameof(PluginsManager);
private PluginInitContext Context { get; set; }
@ -51,7 +48,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
return new List<Result>()
{
new Result()
new()
{
Title = Settings.InstallCommand,
IcoPath = icoPath,
@ -63,7 +60,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
return false;
}
},
new Result()
new()
{
Title = Settings.UninstallCommand,
IcoPath = icoPath,
@ -75,7 +72,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
return false;
}
},
new Result()
new()
{
Title = Settings.UpdateCommand,
IcoPath = icoPath,
@ -169,7 +166,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.ShowMsgError(
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
Context.API.LogException(ClassName, "An error occurred while downloading plugin", e);
return;
}
@ -179,7 +176,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
plugin.Name));
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
Context.API.LogException(ClassName, "An error occurred while downloading plugin", e);
return;
}
@ -237,7 +234,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
internal async ValueTask<List<Result>> RequestUpdateAsync(string search, CancellationToken token,
bool usePrimaryUrlOnly = false)
{
await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
await Context.API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
var pluginFromLocalPath = null as UserPlugin;
var updateFromLocalPath = false;
@ -250,7 +247,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
var updateSource = !updateFromLocalPath
? PluginsManifest.UserPlugins
? Context.API.GetPluginManifest()
: new List<UserPlugin> { pluginFromLocalPath };
var resultsForUpdate = (
@ -260,7 +257,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
StringComparison.InvariantCulture) <
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
&& !PluginManager.PluginModified(existingPlugin.Metadata.ID)
&& !Context.API.PluginModified(existingPlugin.Metadata.ID)
select
new
{
@ -276,7 +273,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (!resultsForUpdate.Any())
return new List<Result>
{
new Result
new()
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"),
@ -341,7 +338,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
else
{
await PluginManager.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
downloadToFilePath);
if (Settings.AutoRestartAfterChanging)
@ -366,7 +363,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
}).ContinueWith(t =>
{
Log.Exception("PluginsManager", $"Update failed for {x.Name}",
Context.API.LogException(ClassName, $"Update failed for {x.Name}",
t.Exception.InnerException);
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
@ -433,12 +430,12 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (cts.IsCancellationRequested)
return;
else
await PluginManager.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
await Context.API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
downloadToFilePath);
}
catch (Exception ex)
{
Log.Exception("PluginsManager", $"Update failed for {plugin.Name}", ex.InnerException);
Context.API.LogException(ClassName, $"Update failed for {plugin.Name}", ex.InnerException);
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(
@ -486,7 +483,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
return results
.Where(x =>
{
var matchResult = StringMatcher.FuzzySearch(searchName, x.Title);
var matchResult = Context.API.FuzzySearch(searchName, x.Title);
if (matchResult.IsSearchPrecisionScoreMet())
x.Score = matchResult.Score;
@ -498,7 +495,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
internal List<Result> InstallFromWeb(string url)
{
var filename = url.Split("/").Last();
var name = filename.Split(string.Format(".{0}", zip)).First();
var name = filename.Split(string.Format(".{0}", ZipSuffix)).First();
var plugin = new UserPlugin
{
@ -552,7 +549,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
return new List<Result>
{
new Result
new()
{
Title = $"{plugin.Name} by {plugin.Author}",
SubTitle = plugin.Description,
@ -602,19 +599,18 @@ namespace Flow.Launcher.Plugin.PluginsManager
internal async ValueTask<List<Result>> RequestInstallOrUpdateAsync(string search, CancellationToken token,
bool usePrimaryUrlOnly = false)
{
await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
await Context.API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
if (Uri.IsWellFormedUriString(search, UriKind.Absolute)
&& search.Split('.').Last() == zip)
&& search.Split('.').Last() == ZipSuffix)
return InstallFromWeb(search);
if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
return InstallFromLocalPath(search);
var results =
PluginsManifest
.UserPlugins
.Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID))
Context.API.GetPluginManifest()
.Where(x => !PluginExists(x.ID) && !Context.API.PluginModified(x.ID))
.Select(x =>
new Result
{
@ -647,7 +643,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
try
{
PluginManager.InstallPlugin(plugin, downloadedFilePath);
Context.API.InstallPlugin(plugin, downloadedFilePath);
if (!plugin.IsFromLocalInstallPath)
File.Delete(downloadedFilePath);
@ -656,21 +652,21 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
Context.API.LogException(ClassName, e.Message, e);
}
catch (InvalidOperationException e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"),
plugin.Name));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
Context.API.LogException(ClassName, e.Message, e);
}
catch (ArgumentException e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
plugin.Name));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
Context.API.LogException(ClassName, e.Message, e);
}
}
@ -740,11 +736,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
await PluginManager.UninstallPluginAsync(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings);
await Context.API.UninstallPluginAsync(plugin, removePluginSettings);
}
catch (ArgumentException e)
{
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
Context.API.LogException(ClassName, e.Message, e);
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
}

View file

@ -1,5 +1,4 @@
using Flow.Launcher.Core.ExternalPlugins;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Zip;
using System.IO;
using System.IO.Compression;
using System.Linq;

View file

@ -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);
}
}
}

View file

@ -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>();
}

View file

@ -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>();
}
}

View file

@ -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>();
}
}

View file

@ -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>();
}
}