Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;

namespace Flow.Launcher.Core.ExternalPlugins
{
Expand All @@ -13,9 +13,11 @@ public record UserPlugin
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);
}
}
11 changes: 7 additions & 4 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,8 @@ public static bool PluginModified(string uuid)


/// <summary>
/// Update a plugin to new version, from a zip file. Will Delete zip after updating.
/// 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 void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
Expand All @@ -390,11 +391,11 @@ public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVe
}

/// <summary>
/// Install a plugin. Will Delete zip after updating.
/// 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, true);
InstallPlugin(plugin, zipFilePath, checkModified: true);
}

/// <summary>
Expand All @@ -420,7 +421,9 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c
// Unzip plugin files to temp folder
var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
File.Delete(zipFilePath);

if(!plugin.IsFromLocalInstallPath)
File.Delete(zipFilePath);

var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);

Expand Down
19 changes: 19 additions & 0 deletions Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
#pragma warning disable IDE0005
using System.Windows;
#pragma warning restore IDE0005
Expand Down Expand Up @@ -200,6 +201,24 @@ public static void OpenFile(string filePath, string workingDir = "", bool asAdmi
}
}

///<summary>
/// This checks whether a given string is a zip file path.
/// By default does not check if the zip file actually exist on disk, can do so by
/// setting checkFileExists = true.
///</summary>
public static bool IsZipFilePath(string querySearchString, bool checkFileExists = false)
{
if (IsLocationPathString(querySearchString) && querySearchString.Split('.').Last() == "zip")
{
if (checkFileExists)
return FileExists(querySearchString);

return true;
}

return false;
}

///<summary>
/// This checks whether a given string is a directory path or network location string.
/// It does not check if location actually exists.
Expand Down
4 changes: 4 additions & 0 deletions Flow.Launcher.Test/Plugins/ExplorerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,15 @@ public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileConte
[TestCase(@"\c:\", false)]
[TestCase(@"cc:\", false)]
[TestCase(@"\\\SomeNetworkLocation\", false)]
[TestCase(@"\\SomeNetworkLocation\", true)]
[TestCase("RandomFile", false)]
[TestCase(@"c:\>*", true)]
[TestCase(@"c:\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation\SomeFile.exe", true)]
[TestCase(@"\\SomeNetworkLocation\SomeFile.exe", true)]

public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
{
// When, Given
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
<system:String x:Key="plugin_pluginsmanager_update_prompt">{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.</system:String>
<system:String x:Key="plugin_pluginsmanager_update_prompt_no_restart">{0} by {1} {2}{2}Would you like to update this plugin?</system:String>
<system:String x:Key="plugin_pluginsmanager_update_title">Plugin Update</system:String>
<system:String x:Key="plugin_pluginsmanager_update_exists">This plugin has an update, would you like to see it?</system:String>
<system:String x:Key="plugin_pluginsmanager_update_alreadyexists">This plugin is already installed</system:String>
<system:String x:Key="plugin_pluginsmanager_update_failed_title">Plugin Manifest Download Failed</system:String>
<system:String x:Key="plugin_pluginsmanager_update_failed_subtitle">Please check if you can connect to github.com. This error means you may not be able to install or update plugins.</system:String>
Expand Down
122 changes: 94 additions & 28 deletions Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
Expand Down Expand Up @@ -99,13 +97,12 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin)
if (Context.API.GetAllPlugins()
.Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0))
{
if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"),
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
Context
.API
.ChangeQuery(
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.Name}");
var updateDetail = !plugin.IsFromLocalInstallPath ? plugin.Name : plugin.LocalInstallPath;

Context
.API
.ChangeQuery(
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {updateDetail}");

var mainWindow = Application.Current.MainWindow;
mainWindow.Show();
Expand Down Expand Up @@ -147,12 +144,17 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin)

try
{
if (File.Exists(filePath))
if (!plugin.IsFromLocalInstallPath)
{
File.Delete(filePath);
}
if (File.Exists(filePath))
File.Delete(filePath);

await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
}
else
{
filePath = plugin.LocalInstallPath;
}

Install(plugin, filePath);
}
Expand Down Expand Up @@ -193,24 +195,38 @@ internal async ValueTask<List<Result>> RequestUpdateAsync(string search, Cancell
{
await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);

var pluginFromLocalPath = null as UserPlugin;
var updateFromLocalPath = false;

if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
{
pluginFromLocalPath = Utilities.GetPluginInfoFromZip(search);
pluginFromLocalPath.LocalInstallPath = search;
updateFromLocalPath = true;
}

var updateSource = !updateFromLocalPath
? PluginsManifest.UserPlugins
: new List<UserPlugin> { pluginFromLocalPath };

var resultsForUpdate = (
from existingPlugin in Context.API.GetAllPlugins()
join pluginFromManifest in PluginsManifest.UserPlugins
on existingPlugin.Metadata.ID equals pluginFromManifest.ID
where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version,
join pluginUpdateSource in updateSource
on existingPlugin.Metadata.ID equals pluginUpdateSource.ID
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
StringComparison.InvariantCulture) <
0 // if current version precedes manifest version
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
&& !PluginManager.PluginModified(existingPlugin.Metadata.ID)
select
new
{
pluginFromManifest.Name,
pluginFromManifest.Author,
pluginUpdateSource.Name,
pluginUpdateSource.Author,
CurrentVersion = existingPlugin.Metadata.Version,
NewVersion = pluginFromManifest.Version,
NewVersion = pluginUpdateSource.Version,
existingPlugin.Metadata.IcoPath,
PluginExistingMetadata = existingPlugin.Metadata,
PluginNewUserPlugin = pluginFromManifest
PluginNewUserPlugin = pluginUpdateSource
}).ToList();

if (!resultsForUpdate.Any())
Expand Down Expand Up @@ -261,13 +277,21 @@ where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version

_ = Task.Run(async delegate
{
if (File.Exists(downloadToFilePath))
if (!x.PluginNewUserPlugin.IsFromLocalInstallPath)
{
File.Delete(downloadToFilePath);
}
if (File.Exists(downloadToFilePath))
{
File.Delete(downloadToFilePath);
}

await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
}
else
{
downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath;
}


PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin,
downloadToFilePath);
Expand Down Expand Up @@ -396,7 +420,7 @@ await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFileP
results = results.Prepend(updateAllResult);
}

return Search(results, search);
return !updateFromLocalPath ? Search(results, search) : results.ToList();
}

internal bool PluginExists(string id)
Expand Down Expand Up @@ -470,6 +494,42 @@ internal List<Result> InstallFromWeb(string url)
return new List<Result> { result };
}

internal List<Result> InstallFromLocalPath(string localPath)
{
var plugin = Utilities.GetPluginInfoFromZip(localPath);

plugin.LocalInstallPath = localPath;

return new List<Result>
{
new Result
{
Title = $"{plugin.Name} by {plugin.Author}",
SubTitle = plugin.Description,
IcoPath = plugin.IcoPath,
Action = e =>
{
if (Settings.WarnFromUnknownSource)
{
if (!InstallSourceKnown(plugin.Website)
&& MessageBox.Show(string.Format(
Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"),
Environment.NewLine),
Context.API.GetTranslation(
"plugin_pluginsmanager_install_unknown_source_warning_title"),
MessageBoxButton.YesNo) == MessageBoxResult.No)
return false;
}

Application.Current.MainWindow.Hide();
_ = InstallOrUpdateAsync(plugin);

return ShouldHideWindow;
}
}
};
}

private bool InstallSourceKnown(string url)
{
var author = url.Split('/')[3];
Expand All @@ -489,6 +549,9 @@ internal async ValueTask<List<Result>> RequestInstallOrUpdate(string search, Can
&& search.Split('.').Last() == zip)
return InstallFromWeb(search);

if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
return InstallFromLocalPath(search);

var results =
PluginsManifest
.UserPlugins
Expand Down Expand Up @@ -522,10 +585,13 @@ private void Install(UserPlugin plugin, string downloadedFilePath)
if (!File.Exists(downloadedFilePath))
throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}",
downloadedFilePath);

try
{
PluginManager.InstallPlugin(plugin, downloadedFilePath);
File.Delete(downloadedFilePath);

if (!plugin.IsFromLocalInstallPath)
File.Delete(downloadedFilePath);
}
catch (FileNotFoundException e)
{
Expand Down
30 changes: 29 additions & 1 deletion Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
using ICSharpCode.SharpZipLib.Zip;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure.UserSettings;
using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json;
using System.IO;
using System.IO.Compression;
using System.Linq;

namespace Flow.Launcher.Plugin.PluginsManager
{
Expand Down Expand Up @@ -55,5 +60,28 @@ internal static string GetContainingFolderPathAfterUnzip(string unzippedParentFo

return string.Empty;
}

internal static UserPlugin GetPluginInfoFromZip(string filePath)
{
var plugin = null as UserPlugin;

using (ZipArchive archive = System.IO.Compression.ZipFile.OpenRead(filePath))
{
var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString();
ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath);

if (pluginJsonEntry != null)
{
using (StreamReader reader = new StreamReader(pluginJsonEntry.Open()))
{
string pluginJsonContent = reader.ReadToEnd();
plugin = JsonConvert.DeserializeObject<UserPlugin>(pluginJsonContent);
plugin.IcoPath = "Images\\zipfolder.png";
}
}
}

return plugin;
}
}
}