diff --git a/build.ps1 b/build.ps1 index 8c9f4f0..19247d5 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,6 +1,6 @@ param( [string] $Architecture = "x64", - [string] $Version = "0.0.3.0" + [string] $Version = "0.0.4.0" ) $ErrorActionPreference = "Stop"; diff --git a/src/PipManager.PackageSearch/PipManager.PackageSearch.csproj b/src/PipManager.PackageSearch/PipManager.PackageSearch.csproj index 9c60061..2523916 100644 --- a/src/PipManager.PackageSearch/PipManager.PackageSearch.csproj +++ b/src/PipManager.PackageSearch/PipManager.PackageSearch.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/PipManager/App.xaml b/src/PipManager/App.xaml index 3c7c0a3..7454926 100644 --- a/src/PipManager/App.xaml +++ b/src/PipManager/App.xaml @@ -13,7 +13,8 @@ - + + diff --git a/src/PipManager/App.xaml.cs b/src/PipManager/App.xaml.cs index 56f99d6..f3a2384 100644 --- a/src/PipManager/App.xaml.cs +++ b/src/PipManager/App.xaml.cs @@ -31,6 +31,8 @@ using System.Net.Http; using System.Runtime.InteropServices; using System.Windows.Threading; +using PipManager.ViewModels.Pages.Overlay; +using PipManager.Views.Pages.Overlay; using Wpf.Ui; using AboutViewModel = PipManager.ViewModels.Pages.About.AboutViewModel; using ActionViewModel = PipManager.ViewModels.Pages.Action.ActionViewModel; @@ -90,6 +92,9 @@ public partial class App services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/PipManager/AppStarting.cs b/src/PipManager/AppStarting.cs index a9aa8e8..dfb18ce 100644 --- a/src/PipManager/AppStarting.cs +++ b/src/PipManager/AppStarting.cs @@ -65,24 +65,26 @@ public void LogDeletion() if (!_config.Personalization.LogAutoDeletion || !Directory.Exists(AppInfo.LogDir)) return; var fileList = Directory.GetFileSystemEntries(AppInfo.LogDir); var logFileAmount = fileList.Count(file => File.Exists(file) && file.EndsWith(".txt")); - if (logFileAmount >= _config.Personalization.LogAutoDeletionTimes) + if (logFileAmount < _config.Personalization.LogAutoDeletionTimes) { - var directoryInfo = new DirectoryInfo(AppInfo.LogDir); - var filesInfo = directoryInfo.GetFileSystemInfos(); - foreach (var file in filesInfo) + return; + } + + var directoryInfo = new DirectoryInfo(AppInfo.LogDir); + var filesInfo = directoryInfo.GetFileSystemInfos(); + foreach (var file in filesInfo) + { + if (file.Extension != ".txt") continue; + try + { + File.Delete(file.FullName); + } + catch { - if (file.Extension != ".txt") continue; - try - { - File.Delete(file.FullName); - } - catch - { - // ignored - } + Log.Warning("Failed to delete log: {FileFullName}", file.FullName); } - Log.Information($"{logFileAmount} log file(s) deleted"); } + Log.Information($"{logFileAmount} log file(s) deleted"); } public void CrushesDeletion() @@ -106,7 +108,7 @@ public void CrushesDeletion() } catch { - // ignored + Log.Warning("Failed to delete crush file: {FileFullName}", file.FullName); } } Log.Information($"{crushFileAmount} crush file(s) deleted"); @@ -124,9 +126,9 @@ public void CachesDeletion() { subDir.Delete(true); } - catch (Exception) + catch { - // ignored + Log.Warning("Failed to delete cache directory: {DirFullName}", subDir.FullName); } } foreach (var file in filesInfo) @@ -143,7 +145,7 @@ public void CachesDeletion() } catch { - // ignored + Log.Warning("Failed to delete cache file: {FileFullName}", file.FullName); } } Log.Information($"{cacheFileAmount} cache file(s) deleted"); diff --git a/src/PipManager/Controls/Toast.xaml.cs b/src/PipManager/Controls/Toast.xaml.cs index ffddcad..ef390b9 100644 --- a/src/PipManager/Controls/Toast.xaml.cs +++ b/src/PipManager/Controls/Toast.xaml.cs @@ -8,23 +8,13 @@ namespace PipManager.Controls { public class ToastOptions { - public double ToastWidth { get; set; } - public double ToastHeight { get; set; } - public double TextWidth { get; set; } - public int Time { get; set; } = 2000; - public SymbolRegular Icon { get; set; } = SymbolRegular.Info24; - public Brush Foreground { get; set; } = new SolidColorBrush(Colors.Black); - public Brush IconForeground { get; set; } = new SolidColorBrush(Colors.Black); - public FontFamily FontFamily { get; set; } = new("Microsoft YaHei"); - public FontWeight FontWeight { get; set; } = SystemFonts.MenuFontWeight; - public Brush BorderBrush { get; set; } = new SolidColorBrush(Color.FromRgb(229, 229, 229)); - public Thickness BorderThickness { get; set; } = new(2); - public Brush Background { get; set; } = new SolidColorBrush(Color.FromArgb(40, 0, 255, 0)); - public ApplicationTheme Theme { get; set; } = ApplicationTheme.Light; - public ToastType ToastType { get; set; } = ToastType.Info; - public EventHandler? Closed { get; internal set; } - public EventHandler? Click { get; internal set; } - public Thickness ToastMargin { get; set; } = new(0, 120, 0, 0); + public int Time { get; init; } = 2000; + public FontFamily FontFamily { get; } = new(new Uri("pack://application:,,,/"), "./Resources/Fonts/MiSans-Regular.ttf#MiSans"); + public FontWeight FontWeight { get; } = SystemFonts.MenuFontWeight; + public Thickness BorderThickness { get; } = new(2); + public ApplicationTheme Theme { get; init; } = ApplicationTheme.Light; + public ToastType ToastType { get; init; } = ToastType.Info; + public Thickness ToastMargin { get; } = new(0, 120, 0, 0); } public enum ToastType @@ -34,10 +24,7 @@ public enum ToastType Error, Success } - - /// - /// Toast.xaml 的交互逻辑 - /// + public partial class Toast { private readonly Window? _owner; @@ -58,13 +45,7 @@ private Toast(Window? owner, string title, string message, ToastOptions? options InitializeComponent(); if (options != null) { - if (options.ToastWidth != 0) ToastWidth = options.ToastWidth; - if (options.ToastHeight != 0) ToastHeight = options.ToastHeight; - if (options.TextWidth != 0) TextWidth = options.TextWidth; - Time = options.Time; - Closed += options.Closed; - Click += options.Click; FontFamily = options.FontFamily; FontWeight = options.FontWeight; BorderThickness = options.BorderThickness; @@ -283,7 +264,7 @@ private static void SetPopupOffset(Popup? popup, Toast toast) popup.VerticalOffset = margin.Top; } - public void Close() + private void Close() { _timer?.Stop(); _timer = null; @@ -357,7 +338,7 @@ private string Message private double ToastWidth { get => (double)GetValue(ToastWidthProperty); - set => SetValue(ToastWidthProperty, value); + init => SetValue(ToastWidthProperty, value); } private static readonly DependencyProperty ToastWidthProperty = @@ -366,7 +347,7 @@ private double ToastWidth private double ToastHeight { get => (double)GetValue(ToastHeightProperty); - set => SetValue(ToastHeightProperty, value); + init => SetValue(ToastHeightProperty, value); } private static readonly DependencyProperty ToastHeightProperty = @@ -384,7 +365,7 @@ private SymbolRegular Icon private int Time { get => (int)GetValue(TimeProperty); - set => SetValue(TimeProperty, value); + init => SetValue(TimeProperty, value); } private static readonly DependencyProperty TimeProperty = @@ -402,7 +383,7 @@ public double TextWidth public Thickness ToastMargin { get => (Thickness)GetValue(ToastMarginProperty); - set => SetValue(ToastMarginProperty, value); + init => SetValue(ToastMarginProperty, value); } public static readonly DependencyProperty ToastMarginProperty = diff --git a/src/PipManager/Languages/Lang.Designer.cs b/src/PipManager/Languages/Lang.Designer.cs index 6eaa83e..2a2b18c 100644 --- a/src/PipManager/Languages/Lang.Designer.cs +++ b/src/PipManager/Languages/Lang.Designer.cs @@ -312,11 +312,11 @@ public static string Action_Operation_Update { } /// - /// Looks up a localized string similar to Action Already Running. + /// Looks up a localized string similar to Failed to cancel. /// - public static string Action_OperationCanceled_AlreadyRunning { + public static string Action_OperationCanceled_Failed { get { - return ResourceManager.GetString("Action_OperationCanceled_AlreadyRunning", resourceCulture); + return ResourceManager.GetString("Action_OperationCanceled_Failed", resourceCulture); } } @@ -644,6 +644,15 @@ public static string Development_Developing { } } + /// + /// Looks up a localized string similar to Open download folder (for wheel files). + /// + public static string Dialog_Title_DownloadDistributions { + get { + return ResourceManager.GetString("Dialog_Title_DownloadDistributions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add Environment. /// @@ -1445,6 +1454,33 @@ public static string Mask_Loading { } } + /// + /// Looks up a localized string similar to Cancel. + /// + public static string Overlay_Button_Cancel { + get { + return ResourceManager.GetString("Overlay_Button_Cancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Update. + /// + public static string Overlay_Button_Update { + get { + return ResourceManager.GetString("Overlay_Button_Update", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New Versions Found. + /// + public static string Overlay_Title_PackageUpdate { + get { + return ResourceManager.GetString("Overlay_Title_PackageUpdate", resourceCulture); + } + } + /// /// Looks up a localized string similar to The newest and compatible version will be selected. /// diff --git a/src/PipManager/Languages/Lang.resx b/src/PipManager/Languages/Lang.resx index ed1af42..d23e07c 100644 --- a/src/PipManager/Languages/Lang.resx +++ b/src/PipManager/Languages/Lang.resx @@ -702,8 +702,8 @@ Action Cancelled - - Action Already Running + + Failed to cancel Cancel @@ -732,4 +732,16 @@ Failed to clear caches + + Open download folder (for wheel files) + + + New Versions Found + + + Cancel + + + Update + \ No newline at end of file diff --git a/src/PipManager/Languages/Lang.zh-cn.resx b/src/PipManager/Languages/Lang.zh-cn.resx index 0e72601..427f093 100644 --- a/src/PipManager/Languages/Lang.zh-cn.resx +++ b/src/PipManager/Languages/Lang.zh-cn.resx @@ -702,8 +702,8 @@ 任务已取消 - - 任务已运行 + + 任务终止失败 取消 @@ -732,4 +732,16 @@ 清除缓存失败 + + 打开下载文件夹 + + + 发现新版本 + + + 取消 + + + 更新 + \ No newline at end of file diff --git a/src/PipManager/Models/Action/ActionListItem.cs b/src/PipManager/Models/Action/ActionListItem.cs index 01b56ce..d761006 100644 --- a/src/PipManager/Models/Action/ActionListItem.cs +++ b/src/PipManager/Models/Action/ActionListItem.cs @@ -5,12 +5,12 @@ namespace PipManager.Models.Action; public partial class ActionListItem : ObservableObject { - public ActionListItem(ActionType operationType, string[] operationCommand, string displayCommand = "", string path = "", string[]? extraParameters = null, bool progressIntermediate = false, int totalSubTaskNumber = 1) + public ActionListItem(ActionType operationType, string[] operationCommand, string displayCommand = "", string path = "", string[]? extraParameters = null, bool progressIntermediate = false) { OperationType = operationType; OperationCommand = operationCommand; ProgressIntermediate = progressIntermediate; - TotalSubTaskNumber = totalSubTaskNumber; + TotalSubTaskNumber = operationCommand.Length; Path = path; ExtraParameters = extraParameters; DisplayCommand = displayCommand switch @@ -68,7 +68,7 @@ public ActionListItem(ActionType operationType, string[] operationCommand, strin [ObservableProperty] [NotifyPropertyChangedFor(nameof(ProgressBarValue))] - private int _totalSubTaskNumber = 1; + private int _totalSubTaskNumber; [ObservableProperty] [NotifyPropertyChangedFor(nameof(ProgressBarValue))] diff --git a/src/PipManager/Models/AppConfig.cs b/src/PipManager/Models/AppConfig.cs index 802b704..ded0761 100644 --- a/src/PipManager/Models/AppConfig.cs +++ b/src/PipManager/Models/AppConfig.cs @@ -6,7 +6,7 @@ namespace PipManager.Models; public class AppConfig { [JsonProperty("currentEnvironment")] public EnvironmentItem? CurrentEnvironment { get; set; } - [JsonProperty("environments")] public List EnvironmentItems { get; set; } = new(); + [JsonProperty("environments")] public List EnvironmentItems { get; set; } = []; [JsonProperty("packageSource")] public PackageSource PackageSource { get; set; } = new(); [JsonProperty("personalization")] public Personalization Personalization { get; set; } = new(); } \ No newline at end of file diff --git a/src/PipManager/Models/DataColor.cs b/src/PipManager/Models/DataColor.cs deleted file mode 100644 index 0f52d22..0000000 --- a/src/PipManager/Models/DataColor.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Windows.Media; - -namespace PipManager.Models; - -public struct DataColor -{ - public Brush Color { get; set; } -} \ No newline at end of file diff --git a/src/PipManager/Models/ExceptionType.cs b/src/PipManager/Models/ExceptionType.cs index 9ccb69c..6576ccc 100644 --- a/src/PipManager/Models/ExceptionType.cs +++ b/src/PipManager/Models/ExceptionType.cs @@ -3,8 +3,8 @@ public enum ExceptionType { // Environment - Environment_Broken, + EnvironmentBroken, // Process - Process_Error + ProcessError } \ No newline at end of file diff --git a/src/PipManager/Models/Package/PackageUpdateItem.cs b/src/PipManager/Models/Package/PackageUpdateItem.cs new file mode 100644 index 0000000..4089d3d --- /dev/null +++ b/src/PipManager/Models/Package/PackageUpdateItem.cs @@ -0,0 +1,11 @@ +using PipManager.Languages; +using PipManager.Models.Pages; + +namespace PipManager.Models.Package; + +public class PackageUpdateItem(LibraryListItem libraryListItem, string newVersion) +{ + public string PackageName { get; set; } = libraryListItem.PackageName; + public string PackageVersion { get; set; } = string.Format(Lang.Library_CheckUpdate_Current, libraryListItem.PackageVersion); + public string NewVersion { get; set; } = string.Format(Lang.Library_CheckUpdate_Latest, newVersion); +} diff --git a/src/PipManager/Models/Package/PackageVersion.cs b/src/PipManager/Models/Package/PackageVersion.cs index 2adcf95..9e76a7a 100644 --- a/src/PipManager/Models/Package/PackageVersion.cs +++ b/src/PipManager/Models/Package/PackageVersion.cs @@ -2,24 +2,6 @@ public class PackageVersion { - public PackageVersion() - { - } - - public PackageVersion(string epoch, string release, string preL, string preN, string postN1, string postL, string postN2, string devL, string devN, string local) - { - Epoch = epoch; - Release = release; - PreL = preL; - PreN = preN; - PostN1 = postN1; - PostL = postL; - PostN2 = postN2; - DevL = devL; - DevN = devN; - Local = local; - } - public string Epoch { get; set; } = ""; public string Release { get; set; } = ""; public string PreL { get; set; } = ""; diff --git a/src/PipManager/Models/Pages/AddEnvironmentByWaysListItem.cs b/src/PipManager/Models/Pages/AddEnvironmentByWaysListItem.cs deleted file mode 100644 index 4831172..0000000 --- a/src/PipManager/Models/Pages/AddEnvironmentByWaysListItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Wpf.Ui.Controls; - -namespace PipManager.Models.Pages; - -public class AddEnvironmentByWaysListItem -{ - public AddEnvironmentByWaysListItem(SymbolIcon icon, string way) - { - SymbolIcon = icon; - Way = way; - } - - public SymbolIcon SymbolIcon { get; set; } - public string Way { get; set; } -} \ No newline at end of file diff --git a/src/PipManager/Models/Pages/LibraryListItem.cs b/src/PipManager/Models/Pages/LibraryListItem.cs index 691c38d..8bf0e8b 100644 --- a/src/PipManager/Models/Pages/LibraryListItem.cs +++ b/src/PipManager/Models/Pages/LibraryListItem.cs @@ -3,22 +3,18 @@ namespace PipManager.Models.Pages; -public class LibraryListItem +public class LibraryListItem( + SymbolIcon icon, + string packageName, + string packageVersion, + PackageVersion packageDetailedVersion, + string packageSummary, + bool isSelected) { - public LibraryListItem(SymbolIcon icon, string packageName, string packageVersion, PackageVersion packageDetailedVersion, string packageSummary, bool isSelected) - { - PackageIcon = icon; - PackageName = packageName; - PackageVersion = packageVersion; - PackageSummary = packageSummary; - IsSelected = isSelected; - PackageDetailedVersion = packageDetailedVersion; - } - - public SymbolIcon PackageIcon { get; set; } - public string PackageName { get; set; } - public string PackageVersion { get; set; } - public PackageVersion PackageDetailedVersion { get; set; } - public string PackageSummary { get; set; } - public bool IsSelected { get; set; } + public SymbolIcon PackageIcon { get; set; } = icon; + public string PackageName { get; set; } = packageName; + public string PackageVersion { get; set; } = packageVersion; + public PackageVersion PackageDetailedVersion { get; set; } = packageDetailedVersion; + public string PackageSummary { get; set; } = packageSummary; + public bool IsSelected { get; set; } = isSelected; } \ No newline at end of file diff --git a/src/PipManager/PipManager.csproj b/src/PipManager/PipManager.csproj index c8ebb3d..0ef5769 100644 --- a/src/PipManager/PipManager.csproj +++ b/src/PipManager/PipManager.csproj @@ -28,7 +28,6 @@ - @@ -39,10 +38,11 @@ - + - + + @@ -65,7 +65,7 @@ Lang.Designer.cs - + PublicResXFileCodeGenerator diff --git a/src/PipManager/Resources/Animations.xaml b/src/PipManager/Resources/Animations.xaml new file mode 100644 index 0000000..56a6145 --- /dev/null +++ b/src/PipManager/Resources/Animations.xaml @@ -0,0 +1,259 @@ + + + 24 + 0.75 + 1.25 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/PipManager/Resources/Library/CheckUpdateContentDialog.cs b/src/PipManager/Resources/Library/CheckUpdateContentDialog.cs deleted file mode 100644 index e59efa3..0000000 --- a/src/PipManager/Resources/Library/CheckUpdateContentDialog.cs +++ /dev/null @@ -1,49 +0,0 @@ -using PipManager.Languages; -using PipManager.Models.Pages; -using System.Windows.Controls; -using Wpf.Ui.Controls; -using TextBlock = System.Windows.Controls.TextBlock; - -namespace PipManager.Resources.Library; - -public class CheckUpdateContentDialog -{ - private readonly ContentDialog _contentDialog; - public List LibraryList { get; set; } - - public CheckUpdateContentDialog(ContentPresenter contentPresenter, List libraryList) - { - LibraryList = libraryList; - _contentDialog = new ContentDialog(contentPresenter) - { - PrimaryButtonText = Lang.ContentDialog_PrimaryButton_Action, - CloseButtonText = Lang.ContentDialog_CloseButton_Cancel, - IsPrimaryButtonEnabled = false, - Title = Lang.ContentDialog_Title_Notice, - Content = Application.Current.TryFindResource("LibraryCheckUpdateContentDialogContent") - }; - (((_contentDialog.Content as Grid)!.Children[2] as ScrollViewer)!.Content as ItemsControl)!.ItemsSource = LibraryList; - - var needUpdate = LibraryList.Any(item => item.NeedUpdate); - ((_contentDialog.Content as Grid)!.Children[0] as TextBlock)!.Visibility = - needUpdate ? Visibility.Visible : Visibility.Collapsed; - (((_contentDialog.Content as Grid)!.Children[2] as ScrollViewer)!.Content as ItemsControl)!.Visibility = - needUpdate ? Visibility.Visible : Visibility.Collapsed; - ((_contentDialog.Content as Grid)!.Children[1] as TextBlock)!.Visibility = - needUpdate ? Visibility.Collapsed : Visibility.Visible; - _contentDialog.IsPrimaryButtonEnabled = needUpdate; - } - - public async Task ShowAsync() - { - return await _contentDialog.ShowAsync(); - } -} - -public class LibraryCheckUpdateContentDialogContentListItem(LibraryListItem libraryListItem, string newVersion) -{ - public string PackageName { get; set; } = libraryListItem.PackageName; - public string PackageVersion { get; set; } = string.Format(Lang.Library_CheckUpdate_Current, libraryListItem.PackageVersion); - public string NewVersion { get; set; } = string.Format(Lang.Library_CheckUpdate_Latest, newVersion); - public bool NeedUpdate { get; set; } = newVersion != libraryListItem.PackageVersion; -} \ No newline at end of file diff --git a/src/PipManager/Resources/Library/DeletionWarningContentDialog.cs b/src/PipManager/Resources/Library/DeletionWarningContentDialog.cs index 7590f80..78045f7 100644 --- a/src/PipManager/Resources/Library/DeletionWarningContentDialog.cs +++ b/src/PipManager/Resources/Library/DeletionWarningContentDialog.cs @@ -8,9 +8,9 @@ namespace PipManager.Resources.Library; public class DeletionWarningContentDialog { private readonly ContentDialog _contentDialog; - public List LibraryList { get; set; } + private List LibraryList { get; set; } - public DeletionWarningContentDialog(ContentPresenter contentPresenter, List libraryList) + public DeletionWarningContentDialog(ContentPresenter? contentPresenter, List libraryList) { LibraryList = libraryList; _contentDialog = new ContentDialog(contentPresenter) diff --git a/src/PipManager/Resources/Library/InstallAddContentDialog.cs b/src/PipManager/Resources/Library/InstallAddContentDialog.cs index 705937e..44b7ed1 100644 --- a/src/PipManager/Resources/Library/InstallAddContentDialog.cs +++ b/src/PipManager/Resources/Library/InstallAddContentDialog.cs @@ -5,7 +5,7 @@ namespace PipManager.Resources.Library; -public class InstallAddContentDialog(ContentPresenter contentPresenter) +public class InstallAddContentDialog(ContentPresenter? contentPresenter) { private readonly ContentDialog _contentDialog = new(contentPresenter) { diff --git a/src/PipManager/Resources/Library/LibraryStyles.xaml b/src/PipManager/Resources/Library/LibraryStyles.xaml index 807c4b3..beac0a6 100644 --- a/src/PipManager/Resources/Library/LibraryStyles.xaml +++ b/src/PipManager/Resources/Library/LibraryStyles.xaml @@ -2,10 +2,8 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:lang="clr-namespace:PipManager.Languages" - xmlns:library="clr-namespace:PipManager.Resources.Library" xmlns:pages="clr-namespace:PipManager.Models.Pages" - xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" - xmlns:valueConverters="clr-namespace:ValueConverters;assembly=ValueConverters"> + xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - action.OperationId == operationId); if (ActionList[targetAction].OperationStatus != Lang.Action_CurrentStatus_WaitingInQueue) { - return Lang.Action_OperationCanceled_AlreadyRunning; + return environmentService.TryKillProcess(); } ActionList.Remove(ActionList[targetAction]); - return Lang.Action_OperationCanceled_Success; + return true; + } + + private static void ConsoleOutputUpdater(ref bool currentActionRunning, ref ActionListItem currentAction, string? data) + { + if (!currentActionRunning && !string.IsNullOrEmpty(data)) + { + currentAction.ConsoleOutput = data.Trim(); + currentActionRunning = true; + } + else if (!string.IsNullOrEmpty(data)) + { + currentAction.ConsoleOutput += '\n' + data.Trim(); + } } public void Runner() @@ -52,18 +65,7 @@ public void Runner() foreach (var item in queue) { currentAction.OperationStatus = $"Uninstalling {item}"; - var result = environmentService.Uninstall(item, (_, eventArgs) => - { - if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput = eventArgs.Data.Trim(); - currentActionRunning = true; - } - else if (!string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim(); - } - }); + var result = environmentService.Uninstall(item, (_, eventArgs) => ConsoleOutputUpdater(ref currentActionRunning, ref currentAction, eventArgs.Data)); currentAction.CompletedSubTaskNumber++; Log.Information(result.Success ? $"[Runner] {item} uninstall sub-task completed" @@ -79,18 +81,7 @@ public void Runner() foreach (var item in queue) { currentAction.OperationStatus = $"Installing {item}"; - var result = environmentService.Install(item, (_, eventArgs) => - { - if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput = eventArgs.Data.Trim(); - currentActionRunning = true; - } - else if (!string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim(); - } - }, extraParameters: currentAction.ExtraParameters); + var result = environmentService.Install(item, (_, eventArgs) => ConsoleOutputUpdater(ref currentActionRunning, ref currentAction, eventArgs.Data), extraParameters: currentAction.ExtraParameters); currentAction.CompletedSubTaskNumber++; if (!result.Success) { @@ -110,18 +101,7 @@ public void Runner() var requirementsTempFilePath = Path.Combine(AppInfo.CachesDir, $"temp_install_requirements_{currentAction.OperationId}.txt"); File.WriteAllText(requirementsTempFilePath, currentAction.OperationCommand[0]); currentAction.OperationStatus = "Installing from requirements.txt"; - var result = environmentService.InstallByRequirements(requirementsTempFilePath, (_, eventArgs) => - { - if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput = eventArgs.Data.Trim(); - currentActionRunning = true; - } - else if (!string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim(); - } - }); + var result = environmentService.InstallByRequirements(requirementsTempFilePath, (_, eventArgs) => ConsoleOutputUpdater(ref currentActionRunning, ref currentAction, eventArgs.Data)); if (!result.Success) { errorDetection = true; @@ -137,18 +117,7 @@ public void Runner() foreach (var item in queue) { currentAction.OperationStatus = $"Downloading {item}"; - var result = environmentService.Download(item, currentAction.Path, (_, eventArgs) => - { - if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput = eventArgs.Data.Trim(); - currentActionRunning = true; - } - else if (!string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim(); - } - }, extraParameters: currentAction.ExtraParameters); + var result = environmentService.Download(item, currentAction.Path, (_, eventArgs) => ConsoleOutputUpdater(ref currentActionRunning, ref currentAction, eventArgs.Data), extraParameters: currentAction.ExtraParameters); currentAction.CompletedSubTaskNumber++; if (!result.Success) { @@ -169,18 +138,7 @@ public void Runner() foreach (var item in queue) { currentAction.OperationStatus = $"Updating {item}"; - var result = environmentService.Update(item, (_, eventArgs) => - { - if (!currentActionRunning && !string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput = eventArgs.Data.Trim(); - currentActionRunning = true; - } - else if (!string.IsNullOrEmpty(eventArgs.Data)) - { - currentAction.ConsoleOutput += '\n' + eventArgs.Data.Trim(); - } - }); + var result = environmentService.Update(item, (_, eventArgs) => ConsoleOutputUpdater(ref currentActionRunning, ref currentAction, eventArgs.Data)); currentAction.CompletedSubTaskNumber++; if (!result.Success) { diff --git a/src/PipManager/Services/Action/IActionService.cs b/src/PipManager/Services/Action/IActionService.cs index fc43d29..a2c7337 100644 --- a/src/PipManager/Services/Action/IActionService.cs +++ b/src/PipManager/Services/Action/IActionService.cs @@ -11,7 +11,7 @@ public interface IActionService public void AddOperation(ActionListItem actionListItem); - public string? TryCancelOperation(string operationId); + public bool TryCancelOperation(string operationId); public void Runner(); } \ No newline at end of file diff --git a/src/PipManager/Services/Environment/EnvironmentService.cs b/src/PipManager/Services/Environment/EnvironmentService.cs index 573cb8c..262e313 100644 --- a/src/PipManager/Services/Environment/EnvironmentService.cs +++ b/src/PipManager/Services/Environment/EnvironmentService.cs @@ -33,7 +33,7 @@ public ActionResponse CheckEnvironmentAvailable(EnvironmentItem environmentItem) var verify = configurationService.GetEnvironmentItemFromCommand(environmentItem.PythonPath!, "-m pip -V"); return verify != null && environmentItem.PythonPath != string.Empty ? new ActionResponse { Success = true } - : new ActionResponse { Success = false, Exception = ExceptionType.Environment_Broken }; + : new ActionResponse { Success = false, Exception = ExceptionType.EnvironmentBroken }; } public ActionResponse PurgeEnvironmentCache(EnvironmentItem environmentItem) @@ -57,7 +57,7 @@ public ActionResponse PurgeEnvironmentCache(EnvironmentItem environmentItem) process.Close(); process.Dispose(); error = error.Replace("WARNING: No matching packages", "").Trim(); - return !string.IsNullOrEmpty(error) ? new ActionResponse { Success = false, Exception = ExceptionType.Process_Error, Message = error } : new ActionResponse { Success = true, Message = output[15..].TrimEnd()}; + return !string.IsNullOrEmpty(error) ? new ActionResponse { Success = false, Exception = ExceptionType.ProcessError, Message = error } : new ActionResponse { Success = true, Message = output[15..].TrimEnd()}; } public async Task?> GetLibraries() @@ -70,6 +70,7 @@ public ActionResponse PurgeEnvironmentCache(EnvironmentItem environmentItem) var packageDirInfo = new DirectoryInfo(Path.Combine( Path.GetDirectoryName(configurationService.AppConfig.CurrentEnvironment!.PythonPath)!, @"Lib\site-packages")); + var packages = new ConcurrentBag(); var ioTaskList = new List(); var distInfoDirectories = packageDirInfo.GetDirectories() @@ -251,132 +252,80 @@ public async Task GetVersions(string packageName) return new GetVersionsResponse { Status = 1, Versions = [] }; } } - - public ActionResponse Install(string packageName, DataReceivedEventHandler consoleOutputCallback, string[]? extraParameters = null) + + private Process? BasicCommandProcess { get; set; } + + public bool TryKillProcess() { - string? extra = extraParameters != null ? string.Join(" ", extraParameters) : null; - var process = new Process + if (BasicCommandProcess is null) return false; + try { - StartInfo = new ProcessStartInfo - { - FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath, - Arguments = - $"-m pip install \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6 {extra}", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - } - }; - process.OutputDataReceived += consoleOutputCallback; - process.Start(); - process.BeginOutputReadLine(); - var error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - process.Close(); - process.Dispose(); - return new ActionResponse { Success = string.IsNullOrEmpty(error), Exception = ExceptionType.Process_Error, Message = error }; - } - - public ActionResponse InstallByRequirements(string requirementsFilePath, DataReceivedEventHandler consoleOutputCallback) - { - var process = new Process + BasicCommandProcess.Kill(); + return true; + } + catch (Exception) { - StartInfo = new ProcessStartInfo - { - FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath, - Arguments = - $"-m pip install -r \"{requirementsFilePath}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - } - }; - process.OutputDataReceived += consoleOutputCallback; - process.Start(); - process.BeginOutputReadLine(); - var error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - process.Close(); - process.Dispose(); - return new ActionResponse { Success = string.IsNullOrEmpty(error), Exception = ExceptionType.Process_Error, Message = error }; + return false; + } } - - public ActionResponse Download(string packageName, string downloadPath, DataReceivedEventHandler consoleOutputCallback, string[]? extraParameters = null) + + private ActionResponse RaiseProcess(string arguments, DataReceivedEventHandler consoleOutputCallback, + string[]? extraParameters = null) { string? extra = extraParameters != null ? string.Join(" ", extraParameters) : null; - var process = new Process + BasicCommandProcess = new Process { StartInfo = new ProcessStartInfo { FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath, - Arguments = - $"-m pip download -d \"{downloadPath}\" \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6 {extra}", + Arguments = $"{arguments} {extra}", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; - process.OutputDataReceived += consoleOutputCallback; - process.Start(); - process.BeginOutputReadLine(); - var error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - process.Close(); - process.Dispose(); - return new ActionResponse { Success = string.IsNullOrEmpty(error), Exception = ExceptionType.Process_Error, Message = error }; + BasicCommandProcess.OutputDataReceived += consoleOutputCallback; + BasicCommandProcess.Start(); + BasicCommandProcess.BeginOutputReadLine(); + var error = BasicCommandProcess.StandardError.ReadToEnd(); + BasicCommandProcess.WaitForExit(); + BasicCommandProcess.Close(); + BasicCommandProcess.Dispose(); + return new ActionResponse { Success = string.IsNullOrEmpty(error), Exception = ExceptionType.ProcessError, Message = error }; } + #region Basic Command + + public ActionResponse Install(string packageName, DataReceivedEventHandler consoleOutputCallback, + string[]? extraParameters = null) + => RaiseProcess( + $"-m pip install \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6", + consoleOutputCallback, extraParameters); + + public ActionResponse InstallByRequirements(string requirementsFilePath, + DataReceivedEventHandler consoleOutputCallback) + => RaiseProcess( + $"-m pip install -r \"{requirementsFilePath}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6", + consoleOutputCallback); + + public ActionResponse Download(string packageName, string downloadPath, DataReceivedEventHandler consoleOutputCallback, string[]? extraParameters = null) + => RaiseProcess( + $"-m pip download -d \"{downloadPath}\" \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6", + consoleOutputCallback, extraParameters); + public ActionResponse Update(string packageName, DataReceivedEventHandler consoleOutputCallback) - { - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath, - Arguments = - $"-m pip install --upgrade \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - } - }; - process.OutputDataReceived += consoleOutputCallback; - process.Start(); - process.BeginOutputReadLine(); - var error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - process.Close(); - process.Dispose(); - return new ActionResponse { Success = string.IsNullOrEmpty(error), Exception = ExceptionType.Process_Error, Message = error }; - } + => RaiseProcess( + $"-m pip install --upgrade \"{packageName}\" -i {configurationService.GetUrlFromPackageSourceType()} --retries 1 --timeout 6", + consoleOutputCallback); public ActionResponse Uninstall(string packageName, DataReceivedEventHandler consoleOutputCallback) - { - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = configurationService.AppConfig.CurrentEnvironment!.PythonPath, - Arguments = $"-m pip uninstall -y \"{packageName}\"", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - } - }; - process.OutputDataReceived += consoleOutputCallback; - process.Start(); - var error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - process.Close(); - return new ActionResponse { Success = string.IsNullOrEmpty(error), Exception = ExceptionType.Process_Error, Message = error }; - } + => RaiseProcess( + $"-m pip uninstall -y \"{packageName}\"", consoleOutputCallback); - // Package Version Validation + #endregion + + #region Package Version Validation [GeneratedRegex("[-_.]+", RegexOptions.IgnoreCase)] private static partial Regex PackageNameNormalizerRegex(); @@ -386,4 +335,6 @@ public ActionResponse Uninstall(string packageName, DataReceivedEventHandler con [GeneratedRegex("^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", RegexOptions.IgnoreCase)] private static partial Regex PackageNameVerificationRegex(); + + #endregion } \ No newline at end of file diff --git a/src/PipManager/Services/Environment/IEnvironmentService.cs b/src/PipManager/Services/Environment/IEnvironmentService.cs index 9267e32..a9e7bb9 100644 --- a/src/PipManager/Services/Environment/IEnvironmentService.cs +++ b/src/PipManager/Services/Environment/IEnvironmentService.cs @@ -16,6 +16,7 @@ public interface IEnvironmentService public Task?> GetLibraries(); public Task GetVersions(string packageName); + public bool TryKillProcess(); public ActionResponse Install(string packageName, DataReceivedEventHandler consoleOutputCallback, string[]? extraParameters = null); diff --git a/src/PipManager/ViewModels/Pages/Action/ActionViewModel.cs b/src/PipManager/ViewModels/Pages/Action/ActionViewModel.cs index bc4300c..929f05b 100644 --- a/src/PipManager/ViewModels/Pages/Action/ActionViewModel.cs +++ b/src/PipManager/ViewModels/Pages/Action/ActionViewModel.cs @@ -59,10 +59,9 @@ private void CancelAction(string? operationId) return; } - var result = _actionService.TryCancelOperation(operationId); - if(result == Lang.Action_OperationCanceled_AlreadyRunning) + if(!_actionService.TryCancelOperation(operationId)) { - _toastService.Error(Lang.Action_OperationCanceled_AlreadyRunning); + _toastService.Error(Lang.Action_OperationCanceled_Failed); Log.Warning("[Action] Operation cancellation failed (already running): {OperationId}", operationId); } else diff --git a/src/PipManager/ViewModels/Pages/Environment/EnvironmentViewModel.cs b/src/PipManager/ViewModels/Pages/Environment/EnvironmentViewModel.cs index c87d8a6..40e1176 100644 --- a/src/PipManager/ViewModels/Pages/Environment/EnvironmentViewModel.cs +++ b/src/PipManager/ViewModels/Pages/Environment/EnvironmentViewModel.cs @@ -132,8 +132,7 @@ await Task.Run(async () => ( ActionType.Update, ["pip"], - progressIntermediate: false, - totalSubTaskNumber: 1 + progressIntermediate: false )); navigationService.Navigate(typeof(ActionPage)); configurationService.RefreshAllEnvironmentVersions(); diff --git a/src/PipManager/ViewModels/Pages/Lab/LabViewModel.cs b/src/PipManager/ViewModels/Pages/Lab/LabViewModel.cs index f461853..c34112b 100644 --- a/src/PipManager/ViewModels/Pages/Lab/LabViewModel.cs +++ b/src/PipManager/ViewModels/Pages/Lab/LabViewModel.cs @@ -17,11 +17,9 @@ private void ActionTest() ( ActionType.Install, ["pytorch"], - totalSubTaskNumber: 1, progressIntermediate: false )); } - public void OnNavigatedTo() { if (!_isInitialized) diff --git a/src/PipManager/ViewModels/Pages/Library/LibraryDetailViewModel.cs b/src/PipManager/ViewModels/Pages/Library/LibraryDetailViewModel.cs index 56a9712..95af72f 100644 --- a/src/PipManager/ViewModels/Pages/Library/LibraryDetailViewModel.cs +++ b/src/PipManager/ViewModels/Pages/Library/LibraryDetailViewModel.cs @@ -56,7 +56,7 @@ private void InitializeViewModel() _isInitialized = true; } - public void Receive(object recipient, LibraryDetailMessage message) + private void Receive(object recipient, LibraryDetailMessage message) { Package = message.Package; diff --git a/src/PipManager/ViewModels/Pages/Library/LibraryInstallViewModel.cs b/src/PipManager/ViewModels/Pages/Library/LibraryInstallViewModel.cs index 919cecc..ad1d0da 100644 --- a/src/PipManager/ViewModels/Pages/Library/LibraryInstallViewModel.cs +++ b/src/PipManager/ViewModels/Pages/Library/LibraryInstallViewModel.cs @@ -62,7 +62,7 @@ private void InitializeViewModel() _isInitialized = true; } - public void Receive(object recipient, InstalledPackagesMessage message) + private void Receive(object recipient, InstalledPackagesMessage message) { _installedPackages = message.InstalledPackages; } @@ -75,7 +75,7 @@ public void Receive(object recipient, InstalledPackagesMessage message) [RelayCommand] private async Task AddDefaultTask() { - var custom = new InstallAddContentDialog(_contentDialogService.GetContentPresenter()); + var custom = new InstallAddContentDialog(_contentDialogService.GetDialogHost()); var packageName = await custom.ShowAsync(); if (packageName == "") { @@ -124,8 +124,7 @@ private void AddDefaultToAction() _actionService.AddOperation(new ActionListItem ( ActionType.Install, - operationCommand.ToArray(), - totalSubTaskNumber: operationCommand.Count + operationCommand.ToArray() )); PreInstallPackages.Clear(); } @@ -197,7 +196,7 @@ private void AddRequirementsToAction() [RelayCommand] private async Task DownloadDistributionsTask() { - var custom = new InstallAddContentDialog(_contentDialogService.GetContentPresenter()); + var custom = new InstallAddContentDialog(_contentDialogService.GetDialogHost()); var packageName = await custom.ShowAsync(); if (packageName == "") { @@ -237,14 +236,16 @@ private void BrowseDownloadDistributionsFolderTask() { var openFolderDialog = new OpenFolderDialog { - Title = "Download Folder (for wheel files)" + Title = Lang.Dialog_Title_DownloadDistributions }; var result = openFolderDialog.ShowDialog(); - if (result == true) + if (result != true) { - DownloadDistributionsFolderPath = openFolderDialog.FolderName; - DownloadDistributionsEnabled = PreDownloadPackages.Count > 0; + return; } + + DownloadDistributionsFolderPath = openFolderDialog.FolderName; + DownloadDistributionsEnabled = PreDownloadPackages.Count > 0; } [RelayCommand] @@ -259,8 +260,7 @@ private void DownloadDistributionsToAction() ActionType.Download, operationCommand.ToArray(), path: DownloadDistributionsFolderPath, - extraParameters: DownloadWheelDependencies ? null : ["--no-deps"], - totalSubTaskNumber: operationCommand.Count + extraParameters: DownloadWheelDependencies ? null : ["--no-deps"] )); PreDownloadPackages.Clear(); _navigationService.Navigate(typeof(ActionPage)); @@ -316,7 +316,7 @@ private async Task SelectDistributions() using var wheelFileArchive = new ZipArchive(wheelFileStream, ZipArchiveMode.Read); foreach (ZipArchiveEntry entry in wheelFileArchive.Entries) { - if (!entry.FullName.Contains(".dist-info/METADATA") || !entry.FullName.Contains("PKG-INFO")) + if (!entry.FullName.Contains(".dist-info/METADATA") && !entry.FullName.Contains("PKG-INFO")) { continue; } @@ -334,6 +334,8 @@ private async Task SelectDistributions() break; } } + + } } else if (fileName.EndsWith(".tar.gz")) @@ -420,7 +422,6 @@ private void InstallDistributionsToAction() ( ActionType.Install, operationCommand.ToArray(), - totalSubTaskNumber: operationCommand.Count, extraParameters: DownloadWheelDependencies ? null : ["--no-deps"] )); PreInstallDistributions.Clear(); diff --git a/src/PipManager/ViewModels/Pages/Library/LibraryViewModel.cs b/src/PipManager/ViewModels/Pages/Library/LibraryViewModel.cs index 2628ecb..4bb123d 100644 --- a/src/PipManager/ViewModels/Pages/Library/LibraryViewModel.cs +++ b/src/PipManager/ViewModels/Pages/Library/LibraryViewModel.cs @@ -14,6 +14,7 @@ using PipManager.Views.Pages.Library; using Serilog; using System.Collections.ObjectModel; +using PipManager.ViewModels.Pages.Overlay; using Wpf.Ui; using Wpf.Ui.Appearance; using Wpf.Ui.Controls; @@ -33,8 +34,9 @@ public partial class LibraryViewModel : ObservableObject, INavigationAware private readonly IMaskService _maskService; private readonly IToastService _toastService; private readonly IContentDialogService _contentDialogService; + private readonly OverlayViewModel _overlayViewModel; - public LibraryViewModel(INavigationService navigationService, IEnvironmentService environmentService, + public LibraryViewModel(INavigationService navigationService, IEnvironmentService environmentService, OverlayViewModel overlayViewModel, IConfigurationService configurationService, IActionService actionService, IThemeService themeService, IMaskService maskService, IToastService toastService, IContentDialogService contentDialogService) { _navigationService = navigationService; @@ -44,6 +46,7 @@ public LibraryViewModel(INavigationService navigationService, IEnvironmentServic _maskService = maskService; _toastService = toastService; _contentDialogService = contentDialogService; + _overlayViewModel = overlayViewModel; themeService.SetTheme(_configurationService.AppConfig.Personalization.Theme switch { @@ -87,7 +90,7 @@ private void InstallPackage() private async Task DeletePackageAsync() { var selected = LibraryList.Where(libraryListItem => libraryListItem.IsSelected).ToList(); - var custom = new DeletionWarningContentDialog(_contentDialogService.GetContentPresenter(), selected); + var custom = new DeletionWarningContentDialog(_contentDialogService.GetDialogHost(), selected); var result = await custom.ShowAsync(); var command = selected.Aggregate("", (current, item) => current + (item.PackageName + ' ')); if (result != ContentDialogResult.Primary) return; @@ -95,8 +98,7 @@ private async Task DeletePackageAsync() ( ActionType.Uninstall, command.Trim().Split(' '), - progressIntermediate: false, - totalSubTaskNumber: selected.Count + progressIntermediate: false )); _navigationService.Navigate(typeof(ActionPage)); } @@ -109,7 +111,7 @@ private async Task DeletePackageAsync() private async Task CheckUpdate() { _maskService.Show(Lang.Library_Operation_CheckUpdate); - var msgList = new List(); + var msgList = new List(); var operationList = ""; var ioTaskList = new List(); var msgListLock = new object(); @@ -123,7 +125,7 @@ await Task.Run(() => lock (msgListLock) { operationList += $"{item.PackageName}=={latest.Versions!.Last()} "; - msgList.Add(new LibraryCheckUpdateContentDialogContentListItem(item, latest.Versions!.Last())); + msgList.Add(new PackageUpdateItem(item, latest.Versions!.Last())); } }))); Task.WaitAll([.. ioTaskList]); @@ -135,17 +137,16 @@ await Task.Run(() => } else { - var custom = new CheckUpdateContentDialog(_contentDialogService.GetContentPresenter(), msgList); - var result = await custom.ShowAsync(); - if (result != ContentDialogResult.Primary) return; - _actionService.AddOperation(new ActionListItem - ( - ActionType.Update, - operationList.Trim().Split(' '), - progressIntermediate: false, - totalSubTaskNumber: msgList.Count - )); - _navigationService.Navigate(typeof(ActionPage)); + _overlayViewModel.ShowPackageUpdateOverlay(msgList, () => + { + _actionService.AddOperation(new ActionListItem + ( + ActionType.Update, + operationList.Trim().Split(' '), + progressIntermediate: false + )); + _navigationService.Navigate(typeof(ActionPage)); + }); } } @@ -169,7 +170,6 @@ private void ToDetailPage(object parameter) [ObservableProperty] private ObservableCollection _libraryList = []; [ObservableProperty] private bool _environmentFoundVisible; - [ObservableProperty] private bool _listVisible; [RelayCommand] private void NavigateToAddEnvironment() diff --git a/src/PipManager/ViewModels/Pages/Overlay/OverlayViewModel.cs b/src/PipManager/ViewModels/Pages/Overlay/OverlayViewModel.cs new file mode 100644 index 0000000..fcfc453 --- /dev/null +++ b/src/PipManager/ViewModels/Pages/Overlay/OverlayViewModel.cs @@ -0,0 +1,42 @@ +using System.Collections.ObjectModel; +using PipManager.Models.Package; +using PipManager.Views.Windows; + +namespace PipManager.ViewModels.Pages.Overlay; + +public partial class OverlayViewModel: ObservableObject +{ + private System.Action? _confirmCallback; + + [ObservableProperty] + private bool _isOverlayVisible; + + [RelayCommand] + private void CloseOverlay() + { + IsOverlayVisible = false; + App.GetService().TitleBarCoverageGrid.Visibility = Visibility.Collapsed; + } + + private void ShowOverlay() + { + IsOverlayVisible = true; + App.GetService().TitleBarCoverageGrid.Visibility = Visibility.Visible; + } + + [ObservableProperty] private ObservableCollection _packageUpdateItems = []; + + public void ShowPackageUpdateOverlay(List packageUpdates, System.Action callback) + { + _confirmCallback = callback; + PackageUpdateItems = new ObservableCollection(packageUpdates); + ShowOverlay(); + } + + [RelayCommand] + private void Confirm() + { + CloseOverlay(); + _confirmCallback?.Invoke(); + } +} diff --git a/src/PipManager/ViewModels/Pages/Search/SearchDetailViewModel.cs b/src/PipManager/ViewModels/Pages/Search/SearchDetailViewModel.cs index a49b5e9..b5c71e0 100644 --- a/src/PipManager/ViewModels/Pages/Search/SearchDetailViewModel.cs +++ b/src/PipManager/ViewModels/Pages/Search/SearchDetailViewModel.cs @@ -10,7 +10,11 @@ using System.Collections.ObjectModel; using System.Drawing; using System.Net.Http; +using Microsoft.Win32; +using PipManager.Models.Action; +using PipManager.Services.Action; using Wpf.Ui; +using Wpf.Ui.Appearance; using Wpf.Ui.Controls; namespace PipManager.ViewModels.Pages.Search; @@ -23,6 +27,7 @@ public record SearchDetailMessage(QueryListItemModel Package); private readonly HttpClient _httpClient; private readonly IThemeService _themeService; private readonly IToastService _toastService; + private readonly IActionService _actionService; private readonly IEnvironmentService _environmentService; [ObservableProperty] @@ -35,7 +40,7 @@ public record SearchDetailMessage(QueryListItemModel Package); private int _themeTypeInInteger = 16448250; - private const string _htmlModel = """ + private const string HtmlModel = """ @@ -54,13 +59,14 @@ public record SearchDetailMessage(QueryListItemModel Package); [ObservableProperty] private QueryListItemModel? _package; - public SearchDetailViewModel(INavigationService navigationService, HttpClient httpClient, IThemeService themeService, IToastService toastService, IEnvironmentService environmentService) + public SearchDetailViewModel(INavigationService navigationService, HttpClient httpClient, IThemeService themeService, IToastService toastService, IEnvironmentService environmentService, IActionService actionService) { _navigationService = navigationService; _httpClient = httpClient; _themeService = themeService; _toastService = toastService; _environmentService = environmentService; + _actionService = actionService; WeakReferenceMessenger.Default.Register(this, Receive); } @@ -72,17 +78,21 @@ public void OnNavigatedTo() _navigationService.GetNavigationControl().BreadcrumbBar!.Visibility = Visibility.Collapsed; switch (_themeService.GetTheme()) { - case Wpf.Ui.Appearance.ApplicationTheme.Light: + case ApplicationTheme.Light: _themeType = "light"; ThemeTypeInHex = "#FFFFFF"; _themeTypeInInteger = 16777215; break; - case Wpf.Ui.Appearance.ApplicationTheme.Dark: + case ApplicationTheme.Dark: _themeType = "dark"; ThemeTypeInHex = "#0D1117"; _themeTypeInInteger = 856343; break; + case ApplicationTheme.Unknown: + case ApplicationTheme.HighContrast: + default: + throw new ArgumentOutOfRangeException(); } SearchDetailPage.ProjectDescriptionWebView!.DefaultBackgroundColor = Color.FromArgb(_themeTypeInInteger); } @@ -103,6 +113,27 @@ private void InitializeViewModel() [ObservableProperty] private string _targetVersion = ""; + [RelayCommand] + private void DownloadPackage() + { + var openFolderDialog = new OpenFolderDialog + { + Title = Lang.Dialog_Title_DownloadDistributions + }; + var result = openFolderDialog.ShowDialog(); + if (result != true) + { + return; + } + _actionService.AddOperation(new ActionListItem + ( + ActionType.Download, + [$"{Package!.Name}=={TargetVersion}"], + path: openFolderDialog.FolderName, + extraParameters: ["--no-deps"] + )); + } + [RelayCommand] private async Task InstallPackage() { @@ -115,10 +146,16 @@ private async Task InstallPackage() if (installedPackages.Any(item => item.Name == Package!.Name)) { _toastService.Error(Lang.LibraryInstall_Add_AlreadyInstalled); + return; } + _actionService.AddOperation(new ActionListItem + ( + ActionType.Install, + [$"{Package!.Name}=={TargetVersion}"] + )); } - public void Receive(object recipient, SearchDetailMessage message) + private void Receive(object recipient, SearchDetailMessage message) { Package = message.Package; @@ -149,7 +186,7 @@ public void Receive(object recipient, SearchDetailMessage message) var html = await _httpClient.GetStringAsync(projectDescriptionUrl); var htmlDocument = new HtmlDocument(); htmlDocument.LoadHtml(html); - string projectDescriptionHtml = string.Format(_htmlModel, _themeType, ThemeTypeInHex, htmlDocument.DocumentNode.SelectSingleNode("//*[@id=\"description\"]/div").InnerHtml); + string projectDescriptionHtml = string.Format(HtmlModel, _themeType, ThemeTypeInHex, htmlDocument.DocumentNode.SelectSingleNode("//*[@id=\"description\"]/div").InnerHtml); SearchDetailPage.ProjectDescriptionWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark; SearchDetailPage.ProjectDescriptionWebView.NavigateToString(projectDescriptionHtml); @@ -158,7 +195,7 @@ public void Receive(object recipient, SearchDetailMessage message) { Log.Error(ex.Message); _toastService.Error(Lang.SearchDetail_ProjectDescription_LoadFailed); - string projectDescriptionHtml = string.Format(_htmlModel, _themeType, ThemeTypeInHex, $"

{Lang.SearchDetail_ProjectDescription_LoadFailed}

"); + string projectDescriptionHtml = string.Format(HtmlModel, _themeType, ThemeTypeInHex, $"

{Lang.SearchDetail_ProjectDescription_LoadFailed}

"); SearchDetailPage.ProjectDescriptionWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark; SearchDetailPage.ProjectDescriptionWebView.NavigateToString(projectDescriptionHtml); diff --git a/src/PipManager/ViewModels/Pages/Search/SearchViewModel.cs b/src/PipManager/ViewModels/Pages/Search/SearchViewModel.cs index d16c340..2361be0 100644 --- a/src/PipManager/ViewModels/Pages/Search/SearchViewModel.cs +++ b/src/PipManager/ViewModels/Pages/Search/SearchViewModel.cs @@ -74,7 +74,7 @@ private void ToDetailPage(object parameter) #endregion Details [RelayCommand] - public async Task ToPreviousPage() + private async Task ToPreviousPage() { if (CurrentPage == 1) { @@ -89,7 +89,7 @@ public async Task ToPreviousPage() } [RelayCommand] - public async Task ToNextPage() + private async Task ToNextPage() { if (CurrentPage == MaxPage) { @@ -113,12 +113,9 @@ private void Process(QueryWrapper queryWrapper) { if (queryWrapper.Status == QueryStatus.Success) { - foreach (var resultItem in queryWrapper.Results!) + foreach (var resultItem in queryWrapper.Results!.Where(resultItem => string.IsNullOrEmpty(resultItem.Description))) { - if (string.IsNullOrEmpty(resultItem.Description)) - { - resultItem.Description = Lang.Search_List_NoDescription; - } + resultItem.Description = Lang.Search_List_NoDescription; } QueryList = new ObservableCollection(queryWrapper.Results!); TotalResultNumber = queryWrapper.ResultCount!; @@ -128,13 +125,18 @@ private void Process(QueryWrapper queryWrapper) } else { - if (queryWrapper.Status == QueryStatus.NoResults) + switch (queryWrapper.Status) { - toastService.Error(Lang.Search_Query_NoResults); - } - else if (queryWrapper.Status == QueryStatus.Timeout) - { - toastService.Error(Lang.Search_Query_Timeout); + case QueryStatus.NoResults: + toastService.Error(Lang.Search_Query_NoResults); + break; + case QueryStatus.Timeout: + toastService.Error(Lang.Search_Query_Timeout); + break; + case QueryStatus.Success: + break; + default: + throw new ArgumentOutOfRangeException(); } QueryList.Clear(); TotalResultNumber = ""; @@ -144,7 +146,7 @@ private void Process(QueryWrapper queryWrapper) } [RelayCommand] - public async Task Search(string? parameter) + private async Task Search(string? parameter) { if (parameter != null && !string.IsNullOrEmpty(parameter)) { diff --git a/src/PipManager/Views/Pages/Action/ActionPage.xaml b/src/PipManager/Views/Pages/Action/ActionPage.xaml index 171afa9..beca245 100644 --- a/src/PipManager/Views/Pages/Action/ActionPage.xaml +++ b/src/PipManager/Views/Pages/Action/ActionPage.xaml @@ -1,25 +1,25 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> @@ -34,74 +34,81 @@ Icon="{ui:SymbolIcon Bug24}" /> + ItemsSource="{Binding ViewModel.Actions.AsObservable}" + x:Name="ActionList"> - + - + - + + Text="{Binding OperationDescription}" + VerticalAlignment="Center" /> + VerticalAlignment="Center"> + Value="{Binding ProgressBarValue, Mode=OneWay}" + Width="300" /> - + + TextWrapping="WrapWithOverflow" + VerticalAlignment="Center" /> - + + Margin="5,0,0,0" + Text="{Binding OperationStatus}" + VerticalAlignment="Center" /> @@ -116,15 +123,18 @@ Grid.Column="1" Margin="0,5,0,0" Style="{StaticResource BodyLargeTextBlockStyle}" - Text="Console Output" /> + Text="{I18N {x:Static lang:LangKeys.Action_ConsoleOutput}}" /> + MaxHeight="220" + Text="{Binding ConsoleOutput}" + x:Name="ActionConsoleOutputTextbox"> - + @@ -136,8 +146,8 @@ diff --git a/src/PipManager/Views/Pages/Lab/LabPage.xaml b/src/PipManager/Views/Pages/Lab/LabPage.xaml index c91aa61..09b4493 100644 --- a/src/PipManager/Views/Pages/Lab/LabPage.xaml +++ b/src/PipManager/Views/Pages/Lab/LabPage.xaml @@ -18,6 +18,5 @@ mc:Ignorable="d"> - \ No newline at end of file diff --git a/src/PipManager/Views/Pages/Library/LibraryDetailPage.xaml b/src/PipManager/Views/Pages/Library/LibraryDetailPage.xaml index a57af06..4029612 100644 --- a/src/PipManager/Views/Pages/Library/LibraryDetailPage.xaml +++ b/src/PipManager/Views/Pages/Library/LibraryDetailPage.xaml @@ -1,4 +1,5 @@ - + + Visibility="{Binding ViewModel.LibraryList, Converter={StaticResource NotNullToVisibility}}"> @@ -74,7 +74,7 @@ BorderThickness="1" CornerRadius="5" Margin="0,0,5,0" - Visibility="{Binding ViewModel.ListVisible, Converter={StaticResource InverseBoolToVisibility}}"> + Visibility="{Binding ViewModel.LibraryList, Converter={StaticResource NotNullToVisibility}}"> @@ -93,21 +93,21 @@
- - - - - - + Grid.Row="1" + IsTextSearchCaseSensitive="False" + IsTextSearchEnabled="True" + ItemsSource="{Binding ViewModel.LibraryList, Mode=TwoWay}" + Margin="0,10,0,0" + SelectionMode="Multiple" + TextSearch.TextPath="PackageName" + Visibility="{Binding ViewModel.LibraryList, Converter={StaticResource NotNullToVisibility}}" + x:Name="LibraryList"> + + + + + + diff --git a/src/PipManager/Views/Pages/Overlay/OverlayPage.xaml b/src/PipManager/Views/Pages/Overlay/OverlayPage.xaml new file mode 100644 index 0000000..5a334d4 --- /dev/null +++ b/src/PipManager/Views/Pages/Overlay/OverlayPage.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/PipManager/Views/Pages/Overlay/OverlayPage.xaml.cs b/src/PipManager/Views/Pages/Overlay/OverlayPage.xaml.cs new file mode 100644 index 0000000..f45a90a --- /dev/null +++ b/src/PipManager/Views/Pages/Overlay/OverlayPage.xaml.cs @@ -0,0 +1,15 @@ +using PipManager.ViewModels.Pages.Overlay; + +namespace PipManager.Views.Pages.Overlay; + +public partial class OverlayPage +{ + public OverlayViewModel ViewModel { get; } + + public OverlayPage() + { + ViewModel = App.GetService(); + DataContext = this; + InitializeComponent(); + } +} diff --git a/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml b/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml index 683135c..81ac877 100644 --- a/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml +++ b/src/PipManager/Views/Pages/Search/SearchDetailPage.xaml @@ -1,4 +1,5 @@ - + diff --git a/src/PipManager/Views/Windows/MainWindow.xaml b/src/PipManager/Views/Windows/MainWindow.xaml index 776aa14..1c0d1c6 100644 --- a/src/PipManager/Views/Windows/MainWindow.xaml +++ b/src/PipManager/Views/Windows/MainWindow.xaml @@ -10,7 +10,6 @@ WindowBackdropType="Mica" WindowCornerPreference="Round" WindowStartupLocation="CenterScreen" - WindowState="Normal" d:DataContext="{d:DesignInstance local:MainWindow, IsDesignTimeCreatable=True}" d:DesignHeight="700" @@ -35,7 +34,8 @@ xmlns:tools="clr-namespace:PipManager.Views.Pages.Tools" xmlns:tray="http://schemas.lepo.co/wpfui/2022/xaml/tray" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:overlay="clr-namespace:PipManager.Views.Pages.Overlay"> @@ -99,27 +99,26 @@ Grid.Row="0" Grid.RowSpan="2" x:Name="MaskPresenter" /> - - + + - + + + +