|
6 | 6 | using System.Text.Json; |
7 | 7 | using System.Threading; |
8 | 8 | using System.Threading.Tasks; |
| 9 | +using System.Windows; |
9 | 10 | using Flow.Launcher.Core.ExternalPlugins; |
10 | 11 | using Flow.Launcher.Core.Resource; |
11 | 12 | using Flow.Launcher.Infrastructure; |
@@ -813,15 +814,13 @@ private static string GetContainingFolderPathAfterUnzip(string unzippedParentFol |
813 | 814 | return string.Empty; |
814 | 815 | } |
815 | 816 |
|
816 | | - private static bool SameOrLesserPluginVersionExists(string metadataPath) |
| 817 | + private static bool SameOrLesserPluginVersionExists(PluginMetadata metadata) |
817 | 818 | { |
818 | | - var newMetadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(metadataPath)); |
819 | | - |
820 | | - if (!Version.TryParse(newMetadata.Version, out var newVersion)) |
| 819 | + if (!Version.TryParse(metadata.Version, out var newVersion)) |
821 | 820 | return true; // If version is not valid, we assume it is lesser than any existing version |
822 | 821 |
|
823 | 822 | // Get all plugins even if initialization failed so that we can check if the plugin with the same ID exists |
824 | | - return GetAllInitializedPlugins(includeFailed: true).Any(x => x.Metadata.ID == newMetadata.ID |
| 823 | + return GetAllInitializedPlugins(includeFailed: true).Any(x => x.Metadata.ID == metadata.ID |
825 | 824 | && Version.TryParse(x.Metadata.Version, out var version) |
826 | 825 | && newVersion <= version); |
827 | 826 | } |
@@ -881,84 +880,116 @@ internal static bool InstallPlugin(UserPlugin plugin, string zipFilePath, bool c |
881 | 880 | var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); |
882 | 881 | System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath); |
883 | 882 |
|
884 | | - if(!plugin.IsFromLocalInstallPath) |
885 | | - File.Delete(zipFilePath); |
| 883 | + try |
| 884 | + { |
| 885 | + if (!plugin.IsFromLocalInstallPath) |
| 886 | + File.Delete(zipFilePath); |
886 | 887 |
|
887 | | - var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath); |
| 888 | + var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath); |
888 | 889 |
|
889 | | - var metadataJsonFilePath = string.Empty; |
890 | | - if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName))) |
891 | | - metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName); |
| 890 | + var metadataJsonFilePath = string.Empty; |
| 891 | + if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName))) |
| 892 | + metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName); |
892 | 893 |
|
893 | | - if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) |
894 | | - { |
895 | | - PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), |
896 | | - Localize.fileNotFoundMessage(pluginFolderPath)); |
897 | | - return false; |
898 | | - } |
899 | | - |
900 | | - if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) |
901 | | - { |
902 | | - PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), |
903 | | - Localize.pluginExistAlreadyMessage()); |
904 | | - return false; |
905 | | - } |
| 894 | + if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) |
| 895 | + { |
| 896 | + PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), |
| 897 | + Localize.fileNotFoundMessage(pluginFolderPath)); |
| 898 | + return false; |
| 899 | + } |
906 | 900 |
|
907 | | - var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}"; |
| 901 | + PluginMetadata newMetadata; |
| 902 | + try |
| 903 | + { |
| 904 | + newMetadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(metadataJsonFilePath)) ?? |
| 905 | + throw new JsonException("Deserialized metadata is null"); |
| 906 | + } |
| 907 | + catch (Exception ex) |
| 908 | + { |
| 909 | + PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), |
| 910 | + Localize.pluginJsonInvalidOrCorrupted()); |
| 911 | + PublicApi.Instance.LogException(ClassName, |
| 912 | + $"Failed to deserialize plugin metadata for plugin {plugin.Name} from file {metadataJsonFilePath}", ex); |
| 913 | + return false; |
| 914 | + } |
908 | 915 |
|
909 | | - var defaultPluginIDs = new List<string> |
| 916 | + if (SameOrLesserPluginVersionExists(newMetadata)) |
910 | 917 | { |
911 | | - "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark |
912 | | - "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator |
913 | | - "572be03c74c642baae319fc283e561a8", // Explorer |
914 | | - "6A122269676E40EB86EB543B945932B9", // PluginIndicator |
915 | | - "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager |
916 | | - "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller |
917 | | - "791FC278BA414111B8D1886DFE447410", // Program |
918 | | - "D409510CD0D2481F853690A07E6DC426", // Shell |
919 | | - "CEA08895D2544B019B2E9C5009600DF4", // Sys |
920 | | - "0308FD86DE0A4DEE8D62B9B535370992", // URL |
921 | | - "565B73353DBF4806919830B9202EE3BF", // WebSearch |
922 | | - "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings |
923 | | - }; |
| 918 | + PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), |
| 919 | + Localize.pluginExistAlreadyMessage()); |
| 920 | + return false; |
| 921 | + } |
924 | 922 |
|
925 | | - // Treat default plugin differently, it needs to be removable along with each flow release |
926 | | - var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID) |
927 | | - ? DataLocation.PluginsDirectory |
928 | | - : Constant.PreinstalledDirectory; |
| 923 | + if (!IsMinimumAppVersionSatisfied(newMetadata.Name, newMetadata.MinimumAppVersion)) |
| 924 | + { |
| 925 | + // Ask users if they want to install the plugin that doesn't satisfy the minimum app version requirement |
| 926 | + if (PublicApi.Instance.ShowMsgBox( |
| 927 | + Localize.pluginMinimumAppVersionUnsatisfiedMessage(newMetadata.Name, Environment.NewLine), |
| 928 | + Localize.pluginMinimumAppVersionUnsatisfiedTitle(newMetadata.Name, newMetadata.MinimumAppVersion), |
| 929 | + MessageBoxButton.YesNo) == MessageBoxResult.No) |
| 930 | + { |
| 931 | + return false; |
| 932 | + } |
| 933 | + } |
929 | 934 |
|
930 | | - var newPluginPath = Path.Combine(installDirectory, folderName); |
| 935 | + var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}"; |
931 | 936 |
|
932 | | - FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => PublicApi.Instance.ShowMsgBox(s)); |
| 937 | + var defaultPluginIDs = new List<string> |
| 938 | + { |
| 939 | + "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark |
| 940 | + "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator |
| 941 | + "572be03c74c642baae319fc283e561a8", // Explorer |
| 942 | + "6A122269676E40EB86EB543B945932B9", // PluginIndicator |
| 943 | + "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager |
| 944 | + "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller |
| 945 | + "791FC278BA414111B8D1886DFE447410", // Program |
| 946 | + "D409510CD0D2481F853690A07E6DC426", // Shell |
| 947 | + "CEA08895D2544B019B2E9C5009600DF4", // Sys |
| 948 | + "0308FD86DE0A4DEE8D62B9B535370992", // URL |
| 949 | + "565B73353DBF4806919830B9202EE3BF", // WebSearch |
| 950 | + "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings |
| 951 | + }; |
| 952 | + |
| 953 | + // Treat default plugin differently, it needs to be removable along with each flow release |
| 954 | + var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID) |
| 955 | + ? DataLocation.PluginsDirectory |
| 956 | + : Constant.PreinstalledDirectory; |
| 957 | + |
| 958 | + var newPluginPath = Path.Combine(installDirectory, folderName); |
| 959 | + |
| 960 | + FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => PublicApi.Instance.ShowMsgBox(s)); |
| 961 | + |
| 962 | + // Check if marker file exists and delete it |
| 963 | + try |
| 964 | + { |
| 965 | + var markerFilePath = Path.Combine(newPluginPath, DataLocation.PluginDeleteFile); |
| 966 | + if (File.Exists(markerFilePath)) |
| 967 | + File.Delete(markerFilePath); |
| 968 | + } |
| 969 | + catch (Exception e) |
| 970 | + { |
| 971 | + PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin marker file in {newPluginPath}", e); |
| 972 | + } |
933 | 973 |
|
934 | | - // Check if marker file exists and delete it |
935 | | - try |
936 | | - { |
937 | | - var markerFilePath = Path.Combine(newPluginPath, DataLocation.PluginDeleteFile); |
938 | | - if (File.Exists(markerFilePath)) |
939 | | - File.Delete(markerFilePath); |
940 | | - } |
941 | | - catch (Exception e) |
942 | | - { |
943 | | - PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin marker file in {newPluginPath}", e); |
944 | | - } |
| 974 | + if (checkModified) |
| 975 | + { |
| 976 | + ModifiedPlugins.Add(plugin.ID); |
| 977 | + } |
945 | 978 |
|
946 | | - try |
947 | | - { |
948 | | - if (Directory.Exists(tempFolderPluginPath)) |
949 | | - Directory.Delete(tempFolderPluginPath, true); |
950 | | - } |
951 | | - catch (Exception e) |
952 | | - { |
953 | | - PublicApi.Instance.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e); |
| 979 | + return true; |
954 | 980 | } |
955 | | - |
956 | | - if (checkModified) |
| 981 | + finally |
957 | 982 | { |
958 | | - ModifiedPlugins.Add(plugin.ID); |
| 983 | + try |
| 984 | + { |
| 985 | + if (Directory.Exists(tempFolderPluginPath)) |
| 986 | + Directory.Delete(tempFolderPluginPath, true); |
| 987 | + } |
| 988 | + catch (Exception e) |
| 989 | + { |
| 990 | + PublicApi.Instance.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e); |
| 991 | + } |
959 | 992 | } |
960 | | - |
961 | | - return true; |
962 | 993 | } |
963 | 994 |
|
964 | 995 | internal static async Task<bool> UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) |
@@ -1050,6 +1081,27 @@ internal static async Task<bool> UninstallPluginAsync(PluginMetadata plugin, boo |
1050 | 1081 | return true; |
1051 | 1082 | } |
1052 | 1083 |
|
| 1084 | + internal static bool IsMinimumAppVersionSatisfied(string pluginName, string minimumAppVersion) |
| 1085 | + { |
| 1086 | + // If the minimum app version is not specified in plugin.json, this plugin is compatible with all app versions |
| 1087 | + if (string.IsNullOrEmpty(minimumAppVersion)) |
| 1088 | + return true; |
| 1089 | + |
| 1090 | + var appVersion = Version.Parse(Constant.Version); |
| 1091 | + |
| 1092 | + if (!Version.TryParse(minimumAppVersion, out var minimumVersion)) |
| 1093 | + { |
| 1094 | + PublicApi.Instance.LogError(ClassName, |
| 1095 | + $"Failed to parse the minimum app version {minimumAppVersion} for plugin {pluginName}."); |
| 1096 | + return false; // If the minimum app version specified in plugin.json is invalid, we assume it is not satisfied |
| 1097 | + } |
| 1098 | + |
| 1099 | + if (appVersion >= minimumVersion) |
| 1100 | + return true; |
| 1101 | + |
| 1102 | + return false; |
| 1103 | + } |
| 1104 | + |
1053 | 1105 | #endregion |
1054 | 1106 |
|
1055 | 1107 | #endregion |
|
0 commit comments