diff --git a/PantherByte/Messages/OpenSimpleDialogMessage.cs b/PantherByte/Messages/OpenSimpleDialogMessage.cs new file mode 100644 index 0000000..026c227 --- /dev/null +++ b/PantherByte/Messages/OpenSimpleDialogMessage.cs @@ -0,0 +1,8 @@ +using CommunityToolkit.Mvvm.Messaging.Messages; +using PantherByte.ViewModels; + +namespace PantherByte.Messages; + +public class OpenSimpleDialogMessage(SimpleDialogViewModel vm) : AsyncRequestMessage { + public SimpleDialogViewModel ViewModel = vm; +} \ No newline at end of file diff --git a/PantherByte/ViewModels/DependencyProgressViewModel.cs b/PantherByte/ViewModels/DependencyProgressViewModel.cs new file mode 100644 index 0000000..1d25267 --- /dev/null +++ b/PantherByte/ViewModels/DependencyProgressViewModel.cs @@ -0,0 +1,105 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Input; +using ReactiveUI; + +namespace PantherByte.ViewModels; + +public partial class DependencyProgressViewModel : ViewModelBase, IProgressWindowViewModel { + private readonly ProcessStartInfo _processStartInfo; + private string _processStdOut = ""; + private bool _error = false; + private bool _inProgress = false; + private string _statusMessage = "Downloading ffmpeg..."; + private string _barColor = "CornflowerBlue"; + + public string ProcessStdOut { + get => _processStdOut; + set => this.RaiseAndSetIfChanged(ref _processStdOut, value); + } + + public string StatusMessage { + get => _statusMessage; + set => this.RaiseAndSetIfChanged(ref _statusMessage, value); + } + + public bool Error { + get => _error; + set => this.RaiseAndSetIfChanged(ref _error, value); + } + + public bool InProgress { + get => _inProgress; + set => this.RaiseAndSetIfChanged(ref _inProgress, value); + } + + public string BarColor { + get => _barColor; + set => this.RaiseAndSetIfChanged(ref _barColor, value); + } + + public DependencyProgressViewModel() { + _processStartInfo = new ProcessStartInfo() { + UseShellExecute = false + }; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + // Windows specific command. + _processStartInfo.FileName = "winget"; + _processStartInfo.Arguments = "install ffmpeg"; + _processStartInfo.Verb = "runas"; + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + // Linux specific command. + _processStartInfo.FileName = "sudo"; + _processStartInfo.Arguments = "apt install ffmpeg"; + } + } + + [RelayCommand] + public async Task RunProcessAsync() { + Process installProcess = new(); + installProcess.StartInfo = _processStartInfo; + + installProcess.EnableRaisingEvents = true; + installProcess.OutputDataReceived += (s, e) => { + if (string.IsNullOrWhiteSpace(e.Data)) return; + + Console.WriteLine(e.Data); + ProcessStdOut += e.Data; + }; + + installProcess.ErrorDataReceived += (s, e) => { + if (string.IsNullOrWhiteSpace(e.Data)) return; + Console.WriteLine(e.Data); + ProcessStdOut += e.Data + "\n"; + Error = true; + StatusMessage = "Error occurred while installing ffmpeg."; + BarColor = "Red"; + }; + + InProgress = true; + try { + installProcess.Start(); + Console.WriteLine("Installation process started."); + installProcess.BeginOutputReadLine(); + installProcess.BeginErrorReadLine(); + await installProcess.WaitForExitAsync(); + } + catch (Exception ex) { + Console.WriteLine(ex.Message); + Error = true; + StatusMessage = "Error occurred while installing ffmpeg."; + BarColor = "Red"; + ProcessStdOut += ex.Message; + } + finally { + InProgress = false; + + if (!Error) { + BarColor = "Green"; + StatusMessage = "Download finished!"; + } + } + } +} \ No newline at end of file diff --git a/PantherByte/ViewModels/IProgressWindowViewModel.cs b/PantherByte/ViewModels/IProgressWindowViewModel.cs new file mode 100644 index 0000000..4803f52 --- /dev/null +++ b/PantherByte/ViewModels/IProgressWindowViewModel.cs @@ -0,0 +1,18 @@ +using System.ComponentModel; +using System.Threading.Tasks; + +namespace PantherByte.ViewModels; + +/// +/// Defines a series of properties and methods that are used by the . +/// It requires the implementation of as well, so that it can +/// preserve the reactivity of the interface. +/// +public interface IProgressWindowViewModel : INotifyPropertyChanged { + public string ProcessStdOut { get; set; } + public string StatusMessage { get; set; } + public bool Error { get; set; } + public bool InProgress { get; set; } + public string BarColor { get; set; } + public Task RunProcessAsync(); +} diff --git a/PantherByte/ViewModels/MainWindowViewModel.cs b/PantherByte/ViewModels/MainWindowViewModel.cs index b115efc..b39d0dc 100644 --- a/PantherByte/ViewModels/MainWindowViewModel.cs +++ b/PantherByte/ViewModels/MainWindowViewModel.cs @@ -13,6 +13,7 @@ using PantherByte.Messages; using PantherByte.Services; using ReactiveUI; using PantherByte.Services.Interfaces; +using PantherByte.Views; namespace PantherByte.ViewModels; @@ -147,6 +148,32 @@ public partial class MainWindowViewModel : ViewModelBase { }); } + private async Task CheckFfmpegInstalled() { + return await Task.Run(async () => { + ProcessStartInfo ffmpegInfo = new("ffmpeg") { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + + Process process = new() { + StartInfo = ffmpegInfo + }; + + bool result = true; + + try { + process.Start(); + await process.WaitForExitAsync(); + } + catch (Exception ex) { + result = false; + } + + return result; + }); + } + /// /// Handles the download process of the file. Generates the command and runs it. /// @@ -167,6 +194,17 @@ public partial class MainWindowViewModel : ViewModelBase { }; info.ArgumentList.AddRange(args[1..]); + // Check that ffmpeg is installed. + var temp = await CheckFfmpegInstalled(); + // if (!await CheckFfmpegInstalled()) { + SimpleDialogViewModel vm = new("Dependency ffmpeg was not found. Would you like to install it?", + "Dependency Install"); + var result = await WeakReferenceMessenger.Default.Send(new OpenSimpleDialogMessage(vm)); + if (result is not null) { + Console.WriteLine($"Received result: {result}"); + } + // } + // This window isn't linked to the download process yet, but that is the plan. await OpenProgressWindowAsync(info); } catch (Exception ex) { diff --git a/PantherByte/ViewModels/ProgressWindowViewModel.cs b/PantherByte/ViewModels/ProgressWindowViewModel.cs index 9527156..0a541dc 100644 --- a/PantherByte/ViewModels/ProgressWindowViewModel.cs +++ b/PantherByte/ViewModels/ProgressWindowViewModel.cs @@ -5,7 +5,7 @@ using ReactiveUI; namespace PantherByte.ViewModels; -public class ProgressWindowViewModel : ViewModelBase { +public partial class ProgressWindowViewModel : ViewModelBase, IProgressWindowViewModel { // Pass variables between the MainWindow and this window, specifically what point the application // is at in terms of downloading. We'll likely do this as a simple percentage of total progress. private readonly ProcessStartInfo _processStartInfo; @@ -48,12 +48,13 @@ public class ProgressWindowViewModel : ViewModelBase { public async Task RunProcessAsync() { Process downloadProcess = new(); downloadProcess.StartInfo = _processStartInfo; - + + downloadProcess.EnableRaisingEvents = true; downloadProcess.OutputDataReceived += (_, e) => { - if (!string.IsNullOrWhiteSpace(e.Data)) { - Console.WriteLine(e.Data); - ProcessStdOut += e.Data + "\n"; - } + if (string.IsNullOrWhiteSpace(e.Data)) return; + + Console.WriteLine(e.Data); + ProcessStdOut += e.Data + "\n"; }; downloadProcess.ErrorDataReceived += (_, e) => { diff --git a/PantherByte/ViewModels/SimpleDialogViewModel.cs b/PantherByte/ViewModels/SimpleDialogViewModel.cs new file mode 100644 index 0000000..17208da --- /dev/null +++ b/PantherByte/ViewModels/SimpleDialogViewModel.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Input; + +namespace PantherByte.ViewModels; + +public partial class SimpleDialogViewModel(string prompt, string title) : ViewModelBase { + public enum SimpleDialogResult { + Yes, + No + } + + public string PromptMessage { get; set; } = prompt; + public string Title { get; set; } = title; +} \ No newline at end of file diff --git a/PantherByte/Views/MainWindow.axaml.cs b/PantherByte/Views/MainWindow.axaml.cs index 9f51aa7..cb4e647 100644 --- a/PantherByte/Views/MainWindow.axaml.cs +++ b/PantherByte/Views/MainWindow.axaml.cs @@ -8,13 +8,21 @@ namespace PantherByte.Views; public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); - - WeakReferenceMessenger.Default.Register(this, + + WeakReferenceMessenger.Default.Register(this, static (w, m) => { var dialog = new ProgressWindow { DataContext = new ProgressWindowViewModel(m.Info) }; m.Reply(dialog.ShowDialog(w)); }); + + WeakReferenceMessenger.Default.Register(this, + static (w, m) => { + var dialog = new SimpleDialog { + DataContext = m.ViewModel + }; + m.Reply(dialog.ShowDialog(w)); + }); } } diff --git a/PantherByte/Views/ProgressWindow.axaml.cs b/PantherByte/Views/ProgressWindow.axaml.cs index 147e165..7688254 100644 --- a/PantherByte/Views/ProgressWindow.axaml.cs +++ b/PantherByte/Views/ProgressWindow.axaml.cs @@ -9,10 +9,8 @@ public partial class ProgressWindow : Window { // This feels a little fragile, but we'll leave it for now. Opened += async (_, _) => { - if (DataContext is not ProgressWindowViewModel vm) - return; - - await vm.RunProcessAsync(); + if (DataContext is IProgressWindowViewModel vm) + await vm.RunProcessAsync(); }; StdOutText.SizeChanged += (_, _) => ScrollView.ScrollToEnd(); diff --git a/PantherByte/Views/SimpleDialog.axaml b/PantherByte/Views/SimpleDialog.axaml new file mode 100644 index 0000000..fc87df9 --- /dev/null +++ b/PantherByte/Views/SimpleDialog.axaml @@ -0,0 +1,35 @@ + + + + + + + + + +