Merge remote-tracking branch 'origin/dev' into AdjustProgramPlugin

This commit is contained in:
Jeremy 2021-11-29 20:18:18 +11:00
commit dbf3f1e159
30 changed files with 506 additions and 171 deletions

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
@ -45,8 +45,56 @@ namespace Flow.Launcher.Core.Plugin
}
}
}
return allPluginMetadata;
(List<PluginMetadata> uniqueList, List<PluginMetadata> duplicateList) = GetUniqueLatestPluginMetadata(allPluginMetadata);
duplicateList
.ForEach(
x => Log.Warn("PluginConfig",
string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
"not loaded due to version not the highest of the duplicates",
x.Name, x.ID, x.Version),
"GetUniqueLatestPluginMetadata"));
return uniqueList;
}
internal static (List<PluginMetadata>, List<PluginMetadata>) GetUniqueLatestPluginMetadata(List<PluginMetadata> allPluginMetadata)
{
var duplicate_list = new List<PluginMetadata>();
var unique_list = new List<PluginMetadata>();
var duplicateGroups = allPluginMetadata.GroupBy(x => x.ID).Where(g => g.Count() > 1).Select(y => y).ToList();
foreach (var metadata in allPluginMetadata)
{
var duplicatesExist = false;
foreach (var group in duplicateGroups)
{
if (metadata.ID == group.Key)
{
duplicatesExist = true;
// If metadata's version greater than each duplicate's version, CompareTo > 0
var count = group.Where(x => metadata.Version.CompareTo(x.Version) > 0).Count();
// Only add if the meatadata's version is the highest of all duplicates in the group
if (count == group.Count() - 1)
{
unique_list.Add(metadata);
}
else
{
duplicate_list.Add(metadata);
}
}
}
if (!duplicatesExist)
unique_list.Add(metadata);
}
return (unique_list, duplicate_list);
}
private static PluginMetadata GetPluginMetadata(string pluginDirectory)

View file

@ -35,6 +35,10 @@ namespace Flow.Launcher.Infrastructure
public const string DefaultTheme = "Win11Light";
public const string Light = "Light";
public const string Dark = "Dark";
public const string System = "System";
public const string Themes = "Themes";
public const string Settings = "Settings";
public const string Logs = "Logs";

View file

@ -29,6 +29,15 @@ namespace Flow.Launcher.Plugin
/// </summary>
void RestartApp();
/// <summary>
/// Run a shell command
/// </summary>
/// <param name="cmd">The command or program to run</param>
/// <param name="filename">the shell type to run, e.g. powershell.exe</param>
/// <exception cref="FileNotFoundException">Thrown when unable to find the file specified in the command </exception>
/// <exception cref="Win32Exception">Thrown when error occurs during the execution of the command </exception>
void ShellRun(string cmd, string filename = "cmd.exe");
/// <summary>
/// Save everything, all of Flow Launcher and plugins' data and settings
/// </summary>

View file

@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin
/// <summary>
/// Delegate to Get Image Source
/// </summary>
public IconDelegate Icon { get; set; }
public IconDelegate Icon;
/// <summary>
/// Information for Glyph Icon

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@ -60,17 +60,39 @@ namespace Flow.Launcher.Plugin.SharedCommands
return sb.ToString();
}
public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "")
public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "", bool createNoWindow = false)
{
var info = new ProcessStartInfo
{
FileName = fileName,
WorkingDirectory = workingDirectory,
Arguments = arguments,
Verb = verb
Verb = verb,
CreateNoWindow = createNoWindow
};
return info;
}
/// <summary>
/// Runs a windows command using the provided ProcessStartInfo
/// </summary>
/// <exception cref="FileNotFoundException">Thrown when unable to find the file specified in the command </exception>
/// <exception cref="Win32Exception">Thrown when error occurs during the execution of the command </exception>
public static void Execute(ProcessStartInfo info)
{
Execute(Process.Start, info);
}
/// <summary>
/// Runs a windows command using the provided ProcessStartInfo using a custom execute command function
/// </summary>
/// <param name="Func startProcess">allows you to pass in a custom command execution function</param>
/// <exception cref="FileNotFoundException">Thrown when unable to find the file specified in the command </exception>
/// <exception cref="Win32Exception">Thrown when error occurs during the execution of the command </exception>
public static void Execute(Func<ProcessStartInfo, Process> startProcess, ProcessStartInfo info)
{
startProcess(info);
}
}
}

View file

@ -0,0 +1,92 @@
using NUnit.Framework;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
using System.Collections.Generic;
using System.Linq;
namespace Flow.Launcher.Test
{
[TestFixture]
class PluginLoadTest
{
[Test]
public void GivenDuplicatePluginMetadatasWhenLoadedThenShouldReturnOnlyUniqueList()
{
// Given
var duplicateList = new List<PluginMetadata>
{
new PluginMetadata
{
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.0"
},
new PluginMetadata
{
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.1"
},
new PluginMetadata
{
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.2"
},
new PluginMetadata
{
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.0"
},
new PluginMetadata
{
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.0"
},
new PluginMetadata
{
ID = "ABC0TYUC6D3B7855823D60DC76F28855",
Version = "1.0.0"
},
new PluginMetadata
{
ID = "ABC0TYUC6D3B7855823D60DC76F28855",
Version = "1.0.0"
}
};
// When
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
// Then
Assert.True(unique.FirstOrDefault().ID == "CEA0TYUC6D3B4085823D60DC76F28855" && unique.FirstOrDefault().Version == "1.0.2");
Assert.True(unique.Count() == 1);
Assert.False(duplicates.Any(x => x.Version == "1.0.2" && x.ID == "CEA0TYUC6D3B4085823D60DC76F28855"));
Assert.True(duplicates.Count() == 6);
}
[Test]
public void GivenDuplicatePluginMetadatasWithNoUniquePluginWhenLoadedThenShouldReturnEmptyList()
{
// Given
var duplicateList = new List<PluginMetadata>
{
new PluginMetadata
{
ID = "CEA0TYUC6D3B7855823D60DC76F28855",
Version = "1.0.0"
},
new PluginMetadata
{
ID = "CEA0TYUC6D3B7855823D60DC76F28855",
Version = "1.0.0"
}
};
// When
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
// Then
Assert.True(unique.Count() == 0);
Assert.True(duplicates.Count() == 2);
}
}
}

View file

@ -1,17 +0,0 @@
using NUnit.Framework;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.Exception;
namespace Flow.Launcher.Test.Plugins
{
[TestFixture]
public class PluginInitTest
{
[Test]
public void PublicAPIIsNullTest()
{
//Assert.Throws(typeof(Flow.LauncherFatalException), () => PluginManager.Initialize(null));
}
}
}

View file

@ -4,12 +4,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{DynamicResource actionKeywordsTitle}"
Width="450"
Height="345"
Background="{DynamicResource PopuBGColor}"
Foreground="{DynamicResource PopupTextColor}"
Icon="Images\app.png"
Loaded="ActionKeyword_OnLoaded"
ResizeMode="NoResize"
SizeToContent="Height"
WindowStartupLocation="CenterScreen">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
@ -91,7 +91,7 @@
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock
Grid.Row="1"
Grid.Column="1"
@ -117,17 +117,18 @@
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button
x:Name="btnCancel"
Width="100"
Width="145"
Height="30"
Margin="10,0,5,0"
Click="BtnCancel_OnClick"
Content="{DynamicResource cancel}" />
<Button
x:Name="btnDone"
Width="100"
Width="145"
Height="30"
Margin="5,0,10,0"
Click="btnDone_OnClick">
Click="btnDone_OnClick"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
</Button>
</StackPanel>

View file

@ -16,6 +16,7 @@
<system:String x:Key="iconTrayExit">Exit</system:String>
<system:String x:Key="closeWindow">Close</system:String>
<system:String x:Key="GameMode">Game Mode</system:String>
<system:String x:Key="GameModeToolTip">Suspend the use of Hotkeys.</system:String>
<!-- Setting General -->
<system:String x:Key="flowlauncher_settings">Flow Launcher Settings</system:String>
@ -55,15 +56,17 @@
<system:String x:Key="disable">Off</system:String>
<system:String x:Key="actionKeywordsTitle">Action keyword Setting</system:String>
<system:String x:Key="actionKeywords">Action keyword</system:String>
<system:String x:Key="currentActionKeywords">Current action keyword:</system:String>
<system:String x:Key="newActionKeyword">New action keyword:</system:String>
<system:String x:Key="currentPriority">Current Priority:</system:String>
<system:String x:Key="newPriority">New Priority:</system:String>
<system:String x:Key="currentActionKeywords">Current action keyword</system:String>
<system:String x:Key="newActionKeyword">New action keyword</system:String>
<system:String x:Key="currentPriority">Current Priority</system:String>
<system:String x:Key="newPriority">New Priority</system:String>
<system:String x:Key="priority">Priority</system:String>
<system:String x:Key="pluginDirectory">Plugin Directory</system:String>
<system:String x:Key="author">Author:</system:String>
<system:String x:Key="plugin_init_time">Init time:</system:String>
<system:String x:Key="plugin_query_time">Query time:</system:String>
<system:String x:Key="plugin_query_version">| Version</system:String>
<system:String x:Key="plugin_query_web">Website</system:String>
<!-- Setting Plugin Store -->
@ -85,7 +88,6 @@
<system:String x:Key="ThemeFolder">Theme Folder</system:String>
<system:String x:Key="OpenThemeFolder">Open Theme Folder</system:String>
<system:String x:Key="DarkMode">Dark Mode</system:String>
<system:String x:Key="DarkModeTip">System settings will take effect from the next run</system:String>
<system:String x:Key="DarkModeSystem">System Default</system:String>
<system:String x:Key="DarkModeLight">Light</system:String>
<system:String x:Key="DarkModeDark">Dark</system:String>

View file

@ -1,7 +1,8 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<!--MainWindow-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<!-- MainWindow -->
<system:String x:Key="registerHotkeyFailed">핫키 등록 실패: {0}</system:String>
<system:String x:Key="couldnotStartCmd">{0}을 실행할 수 없습니다.</system:String>
<system:String x:Key="invalidFlowLauncherPluginFileFormat">Flow Launcher 플러그인 파일 형식이 유효하지 않습니다.</system:String>
@ -14,8 +15,10 @@
<system:String x:Key="iconTrayAbout">정보</system:String>
<system:String x:Key="iconTrayExit">종료</system:String>
<system:String x:Key="closeWindow">닫기</system:String>
<system:String x:Key="GameMode">게임 모드</system:String>
<system:String x:Key="GameModeToolTip">핫키 사용을 일시중단합니다.</system:String>
<!--Setting General-->
<!-- Setting General -->
<system:String x:Key="flowlauncher_settings">Flow Launcher 설정</system:String>
<system:String x:Key="general">일반</system:String>
<system:String x:Key="portableMode">포터블 모드</system:String>
@ -33,41 +36,48 @@
<system:String x:Key="maxShowResults">표시할 결과 수</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreen">전체화면 모드에서는 핫키 무시</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreenToolTip">게이머라면 켜는 것을 추천합니다.</system:String>
<system:String x:Key="defaultFileManager">기본 파일관리자</system:String>
<system:String x:Key="defaultFileManagerToolTip">폴더를 열 때 사용할 파일관리자를 선택하세요.</system:String>
<system:String x:Key="pythonDirectory">Python 디렉토리</system:String>
<system:String x:Key="autoUpdates">자동 업데이트</system:String>
<system:String x:Key="selectPythonDirectory">선택</system:String>
<system:String x:Key="hideOnStartup">시작 시 Flow Launcher 숨김</system:String>
<system:String x:Key="hideNotifyIcon">트레이 아이콘 숨기기</system:String>
<system:String x:Key="querySearchPrecision">쿼리 검색 정도</system:String>
<system:String x:Key="querySearchPrecisionToolTip">검색 결과가 좀 더 정확해집니다.</system:String>
<system:String x:Key="querySearchPrecision">쿼리 검색 정도</system:String>
<system:String x:Key="querySearchPrecisionToolTip">검색 결과에 필요한 최소 매치 점수를 변경합니다.</system:String>
<system:String x:Key="ShouldUsePinyin">항상 Pinyin 사용</system:String>
<system:String x:Key="ShouldUsePinyinToolTip">Pinyin을 사용하여 검색할 수 있습니다. Pinyin(병음)은 로마자 중국어 입력 방식입니다.</system:String>
<system:String x:Key="shadowEffectNotAllowed">반투명 흐림 효과를 사용하는 경우, 그림자 효과를 쓸 수 없습니다.</system:String>
<!--Setting Plugin-->
<!-- Setting Plugin -->
<system:String x:Key="plugin">플러그인</system:String>
<system:String x:Key="browserMorePlugins">플러그인 더 찾아보기</system:String>
<system:String x:Key="enable">On</system:String>
<system:String x:Key="disable">Off</system:String>
<system:String x:Key="enable"></system:String>
<system:String x:Key="disable"></system:String>
<system:String x:Key="actionKeywords">액션 키워드</system:String>
<system:String x:Key="currentActionKeywords">현재 액션 키워드</system:String>
<system:String x:Key="newActionKeyword">새 액션 키워드</system:String>
<system:String x:Key="currentPriority">현재 중요도:</system:String>
<system:String x:Key="newPriority">새 중요도:</system:String>
<system:String x:Key="priority">중요도</system:String>
<system:String x:Key="pluginDirectory">플러그인 디렉토리</system:String>
<system:String x:Key="author">자</system:String>
<system:String x:Key="pluginDirectory">플러그인 폴더</system:String>
<system:String x:Key="author">제작자</system:String>
<system:String x:Key="plugin_init_time">초기화 시간:</system:String>
<system:String x:Key="plugin_query_time">쿼리 시간:</system:String>
<!--Setting Plugin Store-->
<system:String x:Key="plugin_query_version">| 버전</system:String>
<system:String x:Key="plugin_query_web">웹사이트</system:String>
<!-- Setting Plugin Store -->
<system:String x:Key="pluginStore">플러그인 스토어</system:String>
<system:String x:Key="refresh">새로고침</system:String>
<system:String x:Key="install">설치</system:String>
<!--Setting Theme-->
<!-- Setting Theme -->
<system:String x:Key="theme">테마</system:String>
<system:String x:Key="browserMoreThemes">테마 더 찾아보기</system:String>
<system:String x:Key="browserMoreThemes">테마 갤러리</system:String>
<system:String x:Key="howToCreateTheme">테마 제작 안내</system:String>
<system:String x:Key="hiThere">Hi There</system:String>
<system:String x:Key="queryBoxFont">쿼리 상자 글꼴</system:String>
<system:String x:Key="resultItemFont">결과 항목 글꼴</system:String>
@ -77,8 +87,17 @@
<system:String x:Key="theme_load_failure_parse_error">{0} 테마 로드에 실패했습니다. 기본 테마로 변경합니다.</system:String>
<system:String x:Key="ThemeFolder">테마 폴더</system:String>
<system:String x:Key="OpenThemeFolder">테마 폴더 열기</system:String>
<system:String x:Key="DarkMode">다크 모드</system:String>
<system:String x:Key="DarkModeTip">System settings will take effect from the next run</system:String>
<system:String x:Key="DarkModeSystem">시스템 기본</system:String>
<system:String x:Key="DarkModeLight">밝게</system:String>
<system:String x:Key="DarkModeDark">어둡게</system:String>
<system:String x:Key="SoundEffect">소리 효과</system:String>
<system:String x:Key="SoundEffectTip">검색창을 열 때 작은 소리를 재생합니다.</system:String>
<system:String x:Key="Animation">애니메이션</system:String>
<system:String x:Key="AnimationTip">일부 UI에 애니메이션을 사용합니다.</system:String>
<!--Setting Hotkey-->
<!-- Setting Hotkey -->
<system:String x:Key="hotkey">핫키</system:String>
<system:String x:Key="flowlauncherHotkey">Flow Launcher 핫키</system:String>
<system:String x:Key="flowlauncherHotkeyToolTip">Flow Launcher를 열 때 사용할 단축키를 입력합니다.</system:String>
@ -87,7 +106,7 @@
<system:String x:Key="showOpenResultHotkey">단축키 표시</system:String>
<system:String x:Key="showOpenResultHotkeyToolTip">결과창에서 결과 선택 단축키를 표시합니다.</system:String>
<system:String x:Key="customQueryHotkey">사용자지정 쿼리 핫키</system:String>
<system:String x:Key="customQuery">Query</system:String>
<system:String x:Key="customQuery">쿼리</system:String>
<system:String x:Key="delete">삭제</system:String>
<system:String x:Key="edit">편집</system:String>
<system:String x:Key="add">추가</system:String>
@ -95,10 +114,11 @@
<system:String x:Key="deleteCustomHotkeyWarning">{0} 플러그인 핫키를 삭제하시겠습니까?</system:String>
<system:String x:Key="queryWindowShadowEffect">그림자 효과</system:String>
<system:String x:Key="shadowEffectCPUUsage">그림자 효과는 GPU를 사용합니다. 컴퓨터 퍼포먼스가 제한적인 경우 사용을 추천하지 않습니다.</system:String>
<system:String x:Key="windowWidthSize">창 넓이</system:String>
<system:String x:Key="useGlyphUI">플루언트 아이콘 사용</system:String>
<system:String x:Key="useGlyphUIEffect">결과 및 일부 메뉴에서 플루언트 아이콘을 사용합니다.</system:String>
<!--Setting Proxy-->
<!-- Setting Proxy -->
<system:String x:Key="proxy">HTTP 프록시</system:String>
<system:String x:Key="enableProxy">HTTP 프록시 켜기</system:String>
<system:String x:Key="server">HTTP 서버</system:String>
@ -114,26 +134,41 @@
<system:String x:Key="proxyIsCorrect">프록시 설정 정상</system:String>
<system:String x:Key="proxyConnectFailed">프록시 연결 실패</system:String>
<!--Setting About-->
<!-- Setting About -->
<system:String x:Key="about">정보</system:String>
<system:String x:Key="website">웹사이트</system:String>
<system:String x:Key="github">Github</system:String>
<system:String x:Key="docs">문서</system:String>
<system:String x:Key="version">버전</system:String>
<system:String x:Key="about_activate_times">Flow Launcher를 {0}번 실행했습니다.</system:String>
<system:String x:Key="checkUpdates">업데이트 확인</system:String>
<system:String x:Key="newVersionTips">새 버전({0})이 있습니다. Flow Launcher를 재시작하세요.</system:String>
<system:String x:Key="checkUpdatesFailed">업데이트 확인을 실패했습니다. api.github.com로의 연결 또는 프록시 설정을 확인해주세요.</system:String>
<system:String x:Key="downloadUpdatesFailed">
업데이트 다운로드에 실패했습니다. github-cloud.s3.amazonaws.com의 연결 또는 프록시 설정을 확인해주세요.
업데이트 다운로드에 실패했습니다. github-cloud.s3.amazonaws.com의 연결 또는 프록시 설정을 확인해주세요.
수동 다운로드를 하려면 https://github.com/Flow-Launcher/Flow.Launcher/releases 으로 방문하세요.
</system:String>
<system:String x:Key="releaseNotes">릴리즈 노트:</system:String>
<system:String x:Key="documentation">사용 팁:</system:String>
<system:String x:Key="releaseNotes">릴리즈 노트</system:String>
<system:String x:Key="documentation">사용 팁</system:String>
<system:String x:Key="devtool">개발자도구</system:String>
<system:String x:Key="settingfolder">설정 폴더</system:String>
<system:String x:Key="logfolder">로그 폴더</system:String>
<!--Priority Setting Dialog-->
<!-- FileManager Setting Dialog -->
<system:String x:Key="fileManagerWindow">파일관리자 선택</system:String>
<system:String x:Key="fileManager_tips">사용하려는 파일관리자를 선택하고 필요한 경우 인수를 추가하세요. 기본 인수는 &quot;%d&quot; 이며 해당 위치에 경로가 입력됩니다. 예를들어 &quot;totalcmd.exe /A c:\windows&quot;와 같은 명령이 필요한 경우, 인수는 /A &quot;%d&quot; 입니다.</system:String>
<system:String x:Key="fileManager_tips2">&quot;%f&quot;는 특정 파일의 경로를 나타냅니다. 파일관리자에서 선택한 파일/폴더의 위치를 강조하는 기능에서 사용됩니다. 이 인수는 &quot;파일경로 인수&quot; 항목에서만 사용할 수 있습니다. 파일관리자에 해당 기능이 없거나 잘 모를 경우 &quot;%d&quot; 인수를 사용할 수 있습니다.</system:String>
<system:String x:Key="fileManager_name">파일관리자</system:String>
<system:String x:Key="fileManager_profile_name">프로필 이름</system:String>
<system:String x:Key="fileManager_path">파일관리자 경로</system:String>
<system:String x:Key="fileManager_directory_arg">폴더경로 인수</system:String>
<system:String x:Key="fileManager_file_arg">파일경로 인수</system:String>
<!-- Priority Setting Dialog -->
<system:String x:Key="changePriorityWindow">중요도 변경</system:String>
<system:String x:Key="priority_tips">높은 수를 넣을수록 상위 결과에 표시됩니다. 5를 시도해보세요. 다른 플러그인 보다 결과를 낮추고 싶다면, 그보다 낮은 수를 입력하세요.</system:String>
<system:String x:Key="priority_tips">높은 수를 넣을수록 상위 결과에 표시됩니다. 5를 시도해보세요. 다른 플러그인 보다 결과를 낮춰 표시하고 싶다면, 그보다 낮은 수를 입력하세요.</system:String>
<system:String x:Key="invalidPriority">중요도에 올바른 정수를 입력하세요.</system:String>
<!--Action Keyword Setting Dialog-->
<!-- Action Keyword Setting Dialog -->
<system:String x:Key="oldActionKeywords">예전 액션 키워드</system:String>
<system:String x:Key="newActionKeywords">새 액션 키워드</system:String>
<system:String x:Key="cancel">취소</system:String>
@ -143,19 +178,20 @@
<system:String x:Key="newActionKeywordsHasBeenAssigned">새 액션 키워드가 할당된 플러그인이 이미 있습니다. 다른 액션 키워드를 입력하세요.</system:String>
<system:String x:Key="success">성공</system:String>
<system:String x:Key="completedSuccessfully">성공적으로 완료했습니다.</system:String>
<system:String x:Key="actionkeyword_tips">액션 키워드를 지정하지 않으려면 *를 사용하세요.</system:String>
<system:String x:Key="actionkeyword_tips">플러그인을 시작하는데 필요한 액션 키워드를 입력하세요. 액션 키워드를 지정하지 않으려면 *를 사용하세요. 이 경우 키워드를 입력하지 않아도 동작합니다.</system:String>
<!--Custom Query Hotkey Dialog-->
<!-- Custom Query Hotkey Dialog -->
<system:String x:Key="customeQueryHotkeyTitle">커스텀 플러그인 핫키</system:String>
<system:String x:Key="customeQueryHotkeyTips">단축키를 지정하여 특정 쿼리를 자동으로 입력할 수 있습니다. 사용하고 싶은 단축키를 눌러 지정한 후, 사용할 쿼리를 입력하세요.</system:String>
<system:String x:Key="preview">미리보기</system:String>
<system:String x:Key="hotkeyIsNotUnavailable">핫키를 사용할 수 없습니다. 다른 핫키를 입력하세요.</system:String>
<system:String x:Key="invalidPluginHotkey">플러그인 핫키가 유효하지 않습니다.</system:String>
<system:String x:Key="update">업데이트</system:String>
<!--Hotkey Control-->
<!-- Hotkey Control -->
<system:String x:Key="hotkeyUnavailable">핫키를 사용할 수 없습니다.</system:String>
<!--Crash Reporter-->
<!-- Crash Reporter -->
<system:String x:Key="reportWindow_version">버전</system:String>
<system:String x:Key="reportWindow_time">시간</system:String>
<system:String x:Key="reportWindow_reproduce">수정을 위해 애플리케이션이 어떻게 충돌했는지 알려주세요.</system:String>
@ -170,17 +206,19 @@
<system:String x:Key="reportWindow_report_succeed">보고서를 정상적으로 보냈습니다.</system:String>
<system:String x:Key="reportWindow_report_failed">보고서를 보내지 못했습니다.</system:String>
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher에 문제가 발생했습니다.</system:String>
<!--General Notice-->
<!-- General Notice -->
<system:String x:Key="pleaseWait">잠시 기다려주세요...</system:String>
<!--update-->
<!-- update -->
<system:String x:Key="update_flowlauncher_update_check">새 업데이트 확인 중</system:String>
<system:String x:Key="update_flowlauncher_update_new_version_available">새 Flow Launcher 버전({0})을 사용할 수 있습니다.</system:String>
<system:String x:Key="update_flowlauncher_already_on_latest">이미 가장 최신 버전의 Flow Launcher를 사용중입니다.</system:String>
<system:String x:Key="update_flowlauncher_update_found">업데이트 발견</system:String>
<system:String x:Key="update_flowlauncher_updating">업데이트 중...</system:String>
<system:String x:Key="update_flowlauncher_fail_moving_portable_user_profile_data">Flow Launcher가 유저 정보 데이터를 새버전으로 옮길 수 없습니다.
프로필 데이터 폴더를 수동으로 {0} 에서 {1}로 옮겨주세요. </system:String>
<system:String x:Key="update_flowlauncher_fail_moving_portable_user_profile_data">
Flow Launcher가 유저 정보 데이터를 새버전으로 옮길 수 없습니다.
프로필 데이터 폴더를 수동으로 {0} 에서 {1}로 옮겨주세요.
</system:String>
<system:String x:Key="update_flowlauncher_new_update">새 업데이트</system:String>
<system:String x:Key="update_flowlauncher_update_error">소프트웨어 업데이트를 설치하는 중에 오류가 발생했습니다.</system:String>
<system:String x:Key="update_flowlauncher_update">업데이트</system:String>

View file

@ -16,6 +16,7 @@ using ContextMenuStrip = System.Windows.Forms.ContextMenuStrip;
using DragEventArgs = System.Windows.DragEventArgs;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using NotifyIcon = System.Windows.Forms.NotifyIcon;
using Flow.Launcher.Infrastructure;
namespace Flow.Launcher
{
@ -210,6 +211,7 @@ namespace Flow.Launcher
exit.Click += (o, e) => Close();
contextMenu.Items.Add(header);
contextMenu.Items.Add(open);
gamemode.ToolTip = InternationalizationManager.Instance.GetTranslation("GameModeToolTip");
contextMenu.Items.Add(gamemode);
contextMenu.Items.Add(settings);
contextMenu.Items.Add(exit);
@ -480,16 +482,14 @@ namespace Flow.Launcher
public void InitializeDarkMode()
{
if (_settings.DarkMode == "Light")
if (_settings.DarkMode == Constant.Light)
{
ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Light;
}
else if (_settings.DarkMode == "Dark")
else if (_settings.DarkMode == Constant.Dark)
{
ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Dark;
}
else
{ }
}
}
}

View file

@ -104,15 +104,18 @@
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button
x:Name="btnCancel"
Width="100"
Width="145"
Height="30"
Margin="0,0,5,0"
Click="BtnCancel_OnClick"
Content="{DynamicResource cancel}" />
<Button
x:Name="btnDone"
Width="100"
Width="145"
Height="30"
Margin="5,0,0,0"
Click="btnDone_OnClick">
Click="btnDone_OnClick"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
</Button>
</StackPanel>

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@ -14,6 +14,7 @@ using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Plugin.SharedCommands;
using System.Threading;
using System.IO;
using Flow.Launcher.Infrastructure.Http;
@ -106,6 +107,14 @@ namespace Flow.Launcher
});
}
public void ShellRun(string cmd, string filename = "cmd.exe")
{
var args = filename == "cmd.exe" ? $"/C {cmd}" : $"{cmd}";
var startInfo = ShellCommand.SetProcessStartInfo(filename, arguments: args, createNoWindow: true);
ShellCommand.Execute(startInfo);
}
public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible;
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;

View file

@ -239,21 +239,19 @@
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button
x:Name="btnCancel"
Width="100"
Height="30"
Width="145"
Margin="0,0,5,0"
Click="btnCancel_Click"
Content="{DynamicResource cancel}" />
<Button
x:Name="btnDone"
Width="100"
Height="30"
Width="145"
Margin="5,0,0,0"
Click="btnDone_Click"
Content="{DynamicResource done}"
ForceCursor="True">
<Button.Style>
<Style BasedOn="{StaticResource DefaultButtonStyle}" TargetType="{x:Type Button}">
<Style BasedOn="{StaticResource AccentButtonStyle}" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Text.Length, ElementName=ProfileTextBox, UpdateSourceTrigger=PropertyChanged}" Value="0">
<Setter Property="IsEnabled" Value="False" />

View file

@ -1169,7 +1169,7 @@
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource PluginInfoColor}"
Text="| version" />
Text="{DynamicResource plugin_query_version}" />
<TextBlock
MaxWidth="100"
Margin="5,0,0,0"
@ -1189,7 +1189,7 @@
Foreground="{DynamicResource PluginInfoColor}"
NavigateUri="{Binding PluginPair.Metadata.Website}"
RequestNavigate="OnRequestNavigate">
<Run Text="Website" />
<Run Text="{DynamicResource plugin_query_web}" />
</Hyperlink>
</TextBlock>
@ -1920,7 +1920,6 @@
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource DarkMode}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource DarkModeTip}" />
</StackPanel>
<ComboBox
x:Name="DarkModeComboBox"
@ -1949,7 +1948,7 @@
Width="180"
Margin="0,0,18,0"
HorizontalAlignment="Center"
Click="OpenPluginFolder"
Click="OpenThemeFolder"
Content="{DynamicResource OpenThemeFolder}"
DockPanel.Dock="Top" />
<TextBlock Style="{StaticResource Glyph}">

View file

@ -265,7 +265,7 @@ namespace Flow.Launcher
Close();
}
private void OpenPluginFolder(object sender, RoutedEventArgs e)
private void OpenThemeFolder(object sender, RoutedEventArgs e)
{
PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
}
@ -277,7 +277,7 @@ namespace Flow.Launcher
private void OpenLogFolder(object sender, RoutedEventArgs e)
{
PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Logs));
PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, Constant.Version));
}
private void OnPluginStoreRefreshClick(object sender, RoutedEventArgs e)
@ -308,9 +308,9 @@ namespace Flow.Launcher
private void DarkModeSelectedIndexChanged(object sender, EventArgs e) => ThemeManager.Current.ApplicationTheme = settings.DarkMode switch
{
"Light" => ApplicationTheme.Light,
"Dark" => ApplicationTheme.Dark,
"System" => null,
Constant.Light => ApplicationTheme.Light,
Constant.Dark => ApplicationTheme.Dark,
Constant.System => null,
_ => ThemeManager.Current.ApplicationTheme
};

View file

@ -15,12 +15,10 @@ using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using ApplicationTheme = ModernWpf.ApplicationTheme;
namespace Flow.Launcher.ViewModel
{
@ -387,7 +385,6 @@ namespace Flow.Launcher.ViewModel
}
}
public ResultsViewModel PreviewResults
{
get

View file

@ -4,13 +4,16 @@
<!--Dialogues-->
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">Downloading plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_please_wait">Please wait...</system:String>
<system:String x:Key="plugin_pluginsmanager_download_success">Successfully downloaded</system:String>
<system:String x:Key="plugin_pluginsmanager_download_error">Error: Unable to download the plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart.</system:String>
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart.</system:String>
<system:String x:Key="plugin_pluginsmanager_install_title">Plugin Install</system:String>
<system:String x:Key="plugin_pluginsmanager_install_from_web">Download and install {0}</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_title">Plugin Uninstall</system:String>
<system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Install failed: unable to find the plugin.json metadata file from the new plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_install_success_restart">Plugin successfully installed. Restarting Flow, please wait...</system:String>
<system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Unable to find the plugin.json metadata file from the extracted zip file.</system:String>
<system:String x:Key="plugin_pluginsmanager_install_error_duplicate">Error: A plugin which has the same or greater version with {0} already exists.</system:String>
<system:String x:Key="plugin_pluginsmanager_install_error_title">Error installing plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_install_error_subtitle">Error occured while trying to install {0}</system:String>
<system:String x:Key="plugin_pluginsmanager_update_noresult_title">No update available</system:String>
@ -21,12 +24,15 @@
<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>
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning_title">Installing from an unknown source</system:String>
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning">You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)</system:String>
<!--Controls-->
<!--Plugin Infos-->
<system:String x:Key="plugin_pluginsmanager_plugin_name">Plugins Manager</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String>
<system:String x:Key="plugin_pluginsmanager_unknown_author">Unknown Author</system:String>
<!--Context menu items-->
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_title">Open website</system:String>
@ -36,6 +42,8 @@
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_title">Suggest an enhancement or submit an issue</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle">Suggest an enhancement or submit an issue to the plugin developer</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title">Go to Flow's plugins repository</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle">Visit the PluginsManifest repository to see comunity-made plugin submissions</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle">Visit the PluginsManifest repository to see community-made plugin submissions</system:String>
<!--Settings menu items-->
<system:String x:Key="plugin_pluginsmanager_plugin_settings_unknown_source">Install from unknown source warning</system:String>
</ResourceDictionary>

View file

@ -4,7 +4,6 @@
<!--Dialogues-->
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">Sťahovanie pluginu</system:String>
<system:String x:Key="plugin_pluginsmanager_please_wait">Čakajte, prosím…</system:String>
<system:String x:Key="plugin_pluginsmanager_download_success">Úspešne stiahnuté</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} od {1} {2}{3}Chcete odinštalovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje.</system:String>
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Chcete nainštalovať tento plugin? Po nainštalovaní sa Flow automaticky reštartuje.</system:String>

View file

@ -4,7 +4,6 @@
<!--Dialogues-->
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">下载插件</system:String>
<system:String x:Key="plugin_pluginsmanager_please_wait">请稍等...</system:String>
<system:String x:Key="plugin_pluginsmanager_download_success">下载完成</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} by {1} {2}{3} 您要卸载此插件吗? 卸载后Flow Launcher 将自动重启。</system:String>
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3} 您要安装此插件吗? 安装后Flow Launcher 将自动重启</system:String>

View file

@ -55,7 +55,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{
var search = query.Search.ToLower();
var search = query.Search;
if (string.IsNullOrWhiteSpace(search))
return pluginManager.GetDefaultHotKeys();
@ -70,9 +70,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
return search switch
{
var s when s.StartsWith(Settings.HotKeyInstall) => await pluginManager.RequestInstallOrUpdate(s, token),
var s when s.StartsWith(Settings.HotkeyUninstall) => pluginManager.RequestUninstall(s),
var s when s.StartsWith(Settings.HotkeyUpdate) => await pluginManager.RequestUpdate(s, token),
//search could be url, no need ToLower() when passed in
var s when s.StartsWith(Settings.HotKeyInstall, StringComparison.OrdinalIgnoreCase)
=> await pluginManager.RequestInstallOrUpdate(search, token),
var s when s.StartsWith(Settings.HotkeyUninstall, StringComparison.OrdinalIgnoreCase)
=> pluginManager.RequestUninstall(search),
var s when s.StartsWith(Settings.HotkeyUpdate, StringComparison.OrdinalIgnoreCase)
=> await pluginManager.RequestUpdate(search, token),
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
{
hotkey.Score = StringMatcher.FuzzySearch(search, hotkey.Title).Score;

View file

@ -9,6 +9,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@ -17,6 +19,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
internal class PluginsManager
{
const string zip = "zip";
private PluginInitContext Context { get; set; }
private Settings Settings { get; set; }
@ -47,7 +51,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
private Task _downloadManifestTask = Task.CompletedTask;
internal Task UpdateManifestAsync()
{
if (_downloadManifestTask.Status == TaskStatus.Running)
@ -138,13 +141,15 @@ namespace Flow.Launcher.Plugin.PluginsManager
MessageBoxButton.YesNo) == MessageBoxResult.No)
return;
var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}.zip");
// at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
var downloadFilename = string.IsNullOrEmpty(plugin.Version)
? $"{plugin.Name}-{Guid.NewGuid()}.zip"
: $"{plugin.Name}-{plugin.Version}.zip";
var filePath = Path.Combine(DataLocation.PluginsDirectory, downloadFilename);
try
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
@ -154,7 +159,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
catch (Exception e)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
if (e is HttpRequestException)
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_download_error"),
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"));
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
plugin.Name));
@ -163,6 +172,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
return;
}
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"));
Context.API.RestartApp();
}
@ -183,7 +195,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (autocompletedResults.Any())
return autocompletedResults;
var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty).TrimStart();
var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart();
var resultsForUpdate =
from existingPlugin in Context.API.GetAllPlugins()
@ -239,10 +251,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
Task.Run(async delegate
{
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
@ -302,6 +310,62 @@ namespace Flow.Launcher.Plugin.PluginsManager
.ToList();
}
internal List<Result> InstallFromWeb(string url)
{
var filename = url.Split("/").Last();
var name = filename.Split(string.Format(".{0}", zip)).First();
var plugin = new UserPlugin
{
ID = "",
Name = name,
Version = string.Empty,
Author = Context.API.GetTranslation("plugin_pluginsmanager_unknown_author"),
UrlDownload = url
};
var result = new Result
{
Title = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_from_web"), filename),
SubTitle = plugin.UrlDownload,
IcoPath = icoPath,
Action = e =>
{
if (e.SpecialKeyState.CtrlPressed)
{
SearchWeb.NewTabInBrowser(plugin.UrlDownload);
return ShouldHideWindow;
}
if (Settings.WarnFromUnknownSource)
{
if (!InstallSourceKnown(plugin.UrlDownload)
&& 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();
_ = InstallOrUpdate(plugin);
return ShouldHideWindow;
}
};
return new List<Result> { result };
}
private bool InstallSourceKnown(string url)
{
var author = url.Split('/')[3];
var acceptedSource = "https://github.com";
var contructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
return url.StartsWith(acceptedSource) && Context.API.GetAllPlugins().Any(x => x.Metadata.Website.StartsWith(contructedUrlPart));
}
internal async ValueTask<List<Result>> RequestInstallOrUpdate(string searchName, CancellationToken token)
{
if (!PluginsManifest.UserPlugins.Any())
@ -311,7 +375,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
token.ThrowIfCancellationRequested();
var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty).Trim();
var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty, StringComparison.OrdinalIgnoreCase).Trim();
if (Uri.IsWellFormedUriString(searchNameWithoutKeyword, UriKind.Absolute)
&& searchNameWithoutKeyword.Split('.').Last() == zip)
return InstallFromWeb(searchNameWithoutKeyword);
var results =
PluginsManifest
@ -369,11 +437,26 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
{
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
return;
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"),
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
throw new FileNotFoundException (
string.Format("Unable to find plugin.json from the extracted zip file, or this path {0} does not exist", pluginFolderPath));
}
string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}");
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
{
MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name),
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
throw new InvalidOperationException(
string.Format("A plugin with the same ID and version already exists, " +
"or the version is greater than this downloaded plugin {0}",
plugin.Name));
}
var directory = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
var newPluginPath = Path.Combine(DataLocation.PluginsDirectory, directory);
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
@ -390,7 +473,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (autocompletedResults.Any())
return autocompletedResults;
var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty).TrimStart();
var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart();
var results = Context.API
.GetAllPlugins()
@ -466,5 +549,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
return new List<Result>();
}
private bool SameOrLesserPluginVersionExists(string metadataPath)
{
var newMetadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(metadataPath));
return Context.API.GetAllPlugins()
.Any(x => x.Metadata.ID == newMetadata.ID
&& newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
}
}
}
}

View file

@ -7,8 +7,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
internal class Settings
{
internal string HotKeyInstall { get; set; } = "install";
internal string HotkeyUninstall { get; set; } = "uninstall";
internal string HotkeyUpdate { get; set; } = "update";
public bool WarnFromUnknownSource { get; set; } = true;
}
}

View file

@ -14,5 +14,11 @@ namespace Flow.Launcher.Plugin.PluginsManager.ViewModels
Context = context;
Settings = settings;
}
public bool WarnFromUnknownSource
{
get => Settings.WarnFromUnknownSource;
set => Settings.WarnFromUnknownSource = value;
}
}
}

View file

@ -3,10 +3,18 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Flow.Launcher.Plugin.PluginsManager.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid Margin="70 15 0 15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{DynamicResource plugin_pluginsmanager_plugin_settings_unknown_source}"
VerticalAlignment="Center"
FontSize="14"/>
<CheckBox Grid.Column="1" IsChecked="{Binding WarnFromUnknownSource}" />
</Grid>
</UserControl>

View file

@ -16,7 +16,7 @@ namespace Flow.Launcher.Plugin.PluginsManager.Views
this.viewModel = viewModel;
//RefreshView();
this.DataContext = viewModel;
}
}
}

View file

@ -6,7 +6,7 @@
"Name": "Plugins Manager",
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
"Author": "Jeremy Wu",
"Version": "1.9.1",
"Version": "1.10.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",

View file

@ -193,51 +193,63 @@ namespace Flow.Launcher.Plugin.Shell
var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var runAsAdministratorArg = !runAsAdministrator && !_settings.RunAsAdministrator ? "" : "runas";
ProcessStartInfo info;
if (_settings.Shell == Shell.Cmd)
ProcessStartInfo info = new()
{
var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause";
info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsAdministratorArg);
}
else if (_settings.Shell == Shell.Powershell)
Verb = runAsAdministratorArg,
WorkingDirectory = workingDirectory,
};
switch (_settings.Shell)
{
string arguments;
if (_settings.LeaveShellOpen)
{
arguments = $"-NoExit \"{command}\"";
}
else
{
arguments = $"\"{command} ; Read-Host -Prompt \\\"Press Enter to continue\\\"\"";
}
info = ShellCommand.SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsAdministratorArg);
}
else if (_settings.Shell == Shell.RunCommand)
{
var parts = command.Split(new[] { ' ' }, 2);
if (parts.Length == 2)
{
var filename = parts[0];
if (ExistInPath(filename))
case Shell.Cmd:
{
var arguments = parts[1];
info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsAdministratorArg);
info.FileName = "cmd.exe";
info.ArgumentList.Add(_settings.LeaveShellOpen ? "/k" : "/c");
info.ArgumentList.Add(command);
break;
}
else
case Shell.Powershell:
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
info.FileName = "powershell.exe";
if (_settings.LeaveShellOpen)
{
info.ArgumentList.Add("-NoExit");
info.ArgumentList.Add(command);
}
else
{
info.ArgumentList.Add("-Command");
info.ArgumentList.Add(command);
}
break;
}
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
}
}
else
{
throw new NotImplementedException();
case Shell.RunCommand:
{
var parts = command.Split(new[] { ' ' }, 2);
if (parts.Length == 2)
{
var filename = parts[0];
if (ExistInPath(filename))
{
var arguments = parts[1];
info.FileName = filename;
info.ArgumentList.Add(arguments);
}
else
{
info.FileName = command;
}
}
else
{
info.FileName = command;
}
break;
}
default:
throw new NotImplementedException();
}
info.UseShellExecute = true;
@ -251,7 +263,7 @@ namespace Flow.Launcher.Plugin.Shell
{
try
{
startProcess(info);
ShellCommand.Execute(startProcess, info);
}
catch (FileNotFoundException e)
{

View file

@ -190,8 +190,8 @@ namespace Flow.Launcher.Plugin.Sys
var info = ShellCommand.SetProcessStartInfo("shutdown", arguments:"/h");
info.WindowStyle = ProcessWindowStyle.Hidden;
info.UseShellExecute = true;
Process.Start(info);
ShellCommand.Execute(info);
return true;
}

View file

@ -4,7 +4,7 @@
"Name": "System Commands",
"Description": "Provide System related commands. e.g. shutdown,lock, setting etc.",
"Author": "qianlifeng",
"Version": "1.5.0",
"Version": "1.5.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll",