diff --git a/UVtools.Core/Objects/StaticObjects.cs b/UVtools.Core/Objects/StaticObjects.cs new file mode 100644 index 0000000..6c655ba --- /dev/null +++ b/UVtools.Core/Objects/StaticObjects.cs @@ -0,0 +1,19 @@ +using System.IO; +using System.Security.Cryptography; + +namespace UVtools.Core.Objects +{ + public static class StaticObjects + { + public static SHA256 Sha256 { get; } = SHA256.Create(); + + // Compute the file's hash. + public static byte[] GetHashSha256(string filename) + { + using (var stream = File.OpenRead(filename)) + { + return Sha256.ComputeHash(stream); + } + } + } +} diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index 8e9c32b..1919abd 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -162,6 +162,11 @@ namespace UVtools.WPF public static Bitmap GetBitmapFromAsset(string url) => new Bitmap(GetAsset(url)); + public static string GetApplicationPath() + { + return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + } + #endregion } } diff --git a/UVtools.WPF/Extensions/WindowExtensions.cs b/UVtools.WPF/Extensions/WindowExtensions.cs index 3a8ddd9..2aecc3a 100644 --- a/UVtools.WPF/Extensions/WindowExtensions.cs +++ b/UVtools.WPF/Extensions/WindowExtensions.cs @@ -5,6 +5,8 @@ * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ + +using System.Threading; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Threading; @@ -44,6 +46,30 @@ namespace UVtools.WPF.Extensions => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", buttons, Icon.Setting, WindowStartupLocation.CenterOwner, style); + public static void ShowDialogSync(this Window window, Window parent = null) + { + if (parent is null) parent = window; + using (var source = new CancellationTokenSource()) + { + window.ShowDialog(parent).ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext()); + Dispatcher.UIThread.MainLoop(source.Token); + } + } + + public static T ShowDialogSync(this Window window, Window parent = null) + { + if (parent is null) parent = window; + using (var source = new CancellationTokenSource()) + { + var task = window.ShowDialog(parent); + task.ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext()); + Dispatcher.UIThread.MainLoop(source.Token); + return task.Result; + } + + return default(T); + } + public static void ResetDataContext(this Window window) { var old = window.DataContext; diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 0021f65..feb9ade 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -584,6 +584,11 @@ namespace UVtools.WPF await new AboutWindow().ShowDialog(this); } + public async void MenuHelpInstallProfilesClicked() + { + await new PrusaSlicerManager().ShowDialog(this); + } + #endregion #region Methods diff --git a/UVtools.WPF/Structures/PEProfileFolder.cs b/UVtools.WPF/Structures/PEProfileFolder.cs new file mode 100644 index 0000000..1d83a5c --- /dev/null +++ b/UVtools.WPF/Structures/PEProfileFolder.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using Avalonia.Controls; +using Avalonia.Media; +using UVtools.Core.Objects; + +namespace UVtools.WPF.Structures +{ + public class PEProfileFolder : BindableBase + { + private ObservableCollection _items = new ObservableCollection(); + private ushort _installed; + private ushort _updates; + + public enum FolderType + { + Print, + Printer + } + + public FolderType Type { get; } + + public string SourcePath { get; } + public string TargetPath { get; } + + public ObservableCollection Items + { + get => _items; + set => RaiseAndSetIfChanged(ref _items, value); + } + + public ushort Installed + { + get => _installed; + set => RaiseAndSetIfChanged(ref _installed, value); + } + + public ushort Updates + { + get => _updates; + set => RaiseAndSetIfChanged(ref _updates, value); + } + + public PEProfileFolder(FolderType type) + { + Type = type; + + switch (type) + { + case FolderType.Print: + SourcePath = string.Format("{0}{1}Assets{1}PrusaSlicer{1}sla_print", + App.GetApplicationPath(), Path.DirectorySeparatorChar); + TargetPath = string.Format("{0}{1}PrusaSlicer{1}sla_print", + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + Path.DirectorySeparatorChar); + break; + case FolderType.Printer: + SourcePath = string.Format("{0}{1}Assets{1}PrusaSlicer{1}printer", + App.GetApplicationPath(), Path.DirectorySeparatorChar); + TargetPath = string.Format("{0}{1}PrusaSlicer{1}printer", + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + Path.DirectorySeparatorChar); + break; + } + + Reset(); + } + + public void Reset() + { + Items.Clear(); + Updates = 0; + Installed = 0; + if (!Directory.Exists(SourcePath)) return; + DirectoryInfo di = new DirectoryInfo(SourcePath); + var files = di.GetFiles("*.ini"); + if (files.Length == 0) return; + + bool folderExists = Directory.Exists(TargetPath); + + for (int i = 0; i < files.Length; i++) + { + Items.Add(new CheckBox + { + Content = files[i].Name, + Tag = files + }); + + if (folderExists) + { + var targetFile = $"{TargetPath}{Path.DirectorySeparatorChar}{files[i].Name}"; + FileInfo targetFileInfo = new FileInfo(targetFile); + if (targetFileInfo.Exists) + { + Installed++; + if (targetFileInfo.Length != files[i].Length || !StaticObjects.GetHashSha256(targetFileInfo.FullName).SequenceEqual(StaticObjects.GetHashSha256(files[i].FullName))) + { + Items[i].Foreground = Brushes.Red; + Items[i].IsChecked = true; + Updates++; + } + else + { + Items[i].Foreground = Brushes.Green; + Items[i].IsEnabled = false; + } + } + } + } + } + + public void SelectNone() + { + foreach (var checkBox in Items) + { + checkBox.IsChecked = false; + } + } + + public void SelectAll() + { + foreach (var checkBox in Items) + { + checkBox.IsChecked = checkBox.IsEnabled; + } + } + } +} diff --git a/UVtools.WPF/Windows/PrusaSlicerManager.axaml b/UVtools.WPF/Windows/PrusaSlicerManager.axaml new file mode 100644 index 0000000..393256e --- /dev/null +++ b/UVtools.WPF/Windows/PrusaSlicerManager.axaml @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UVtools.WPF/Windows/PrusaSlicerManager.axaml.cs b/UVtools.WPF/Windows/PrusaSlicerManager.axaml.cs new file mode 100644 index 0000000..d7f81b7 --- /dev/null +++ b/UVtools.WPF/Windows/PrusaSlicerManager.axaml.cs @@ -0,0 +1,38 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using UVtools.WPF.Controls; +using UVtools.WPF.Structures; + +namespace UVtools.WPF.Windows +{ + public class PrusaSlicerManager : WindowEx + { + public PEProfileFolder[] Profiles { get;} + + public PrusaSlicerManager() + { + InitializeComponent(); + Profiles = new[] + { + new PEProfileFolder(PEProfileFolder.FolderType.Print), + new PEProfileFolder(PEProfileFolder.FolderType.Printer), + }; + + DataContext = this; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public void RefreshProfiles() + { + foreach (var profile in Profiles) + { + profile.Reset(); + } + } + } +}