using System; using System.IO; using System.Windows.Input; using MossyUpdater.Services; using Ookii.Dialogs.Wpf; namespace MossyUpdater.ViewModels { public class MainViewModel : ViewModelBase, IDisposable { private readonly IDownloadService _downloadService; private readonly ISettingsService _settingsService; private readonly ILogger _logger; private readonly IQuotesService _quotesService; private CancellationTokenSource? _cancellationTokenSource; private System.Windows.Threading.DispatcherTimer? _quoteTimer; private string _url = "https://git.snakedesert.se/SawatoMosswartsEnjoyersClub/MosswartMassacre/raw/branch/spawn-detection/MosswartMassacre/bin/Release/MosswartMassacre.dll"; private string _folder = string.Empty; private string _filename = "MosswartMassacre.dll"; private string _statusMessage = string.Empty; private System.Windows.Media.Brush _statusColor = System.Windows.Media.Brushes.Green; private double _progressValue = 0; private bool _isProgressVisible = false; private bool _isDownloading = false; private string _updateStatus = string.Empty; private bool _updateAvailable = false; private string _currentQuote = string.Empty; private double _quoteOpacity = 1.0; public MainViewModel(IDownloadService downloadService, ISettingsService settingsService, ILogger logger, IQuotesService quotesService) { _downloadService = downloadService ?? throw new ArgumentNullException(nameof(downloadService)); _settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _quotesService = quotesService ?? throw new ArgumentNullException(nameof(quotesService)); BrowseCommand = new RelayCommand(BrowseFolder); DownloadCommand = new AsyncRelayCommand(DownloadAndUnblock, CanDownload); CancelCommand = new RelayCommand(CancelDownload, () => IsDownloading); CheckUpdatesCommand = new AsyncRelayCommand(CheckForUpdates); InitializeQuoteRotation(); _ = LoadSettingsAsync(); } public string Url { get => _url; set => SetProperty(ref _url, value); } public string Folder { get => _folder; set => SetProperty(ref _folder, value); } public string Filename { get => _filename; set => SetProperty(ref _filename, value); } public string StatusMessage { get => _statusMessage; set => SetProperty(ref _statusMessage, value); } public System.Windows.Media.Brush StatusColor { get => _statusColor; set => SetProperty(ref _statusColor, value); } public double ProgressValue { get => _progressValue; set => SetProperty(ref _progressValue, value); } public bool IsProgressVisible { get => _isProgressVisible; set => SetProperty(ref _isProgressVisible, value); } public bool IsDownloading { get => _isDownloading; set { SetProperty(ref _isDownloading, value); OnPropertyChanged(nameof(CanInteract)); } } public string UpdateStatus { get => _updateStatus; set => SetProperty(ref _updateStatus, value); } public bool UpdateAvailable { get => _updateAvailable; set => SetProperty(ref _updateAvailable, value); } public string CurrentQuote { get => _currentQuote; set => SetProperty(ref _currentQuote, value); } public double QuoteOpacity { get => _quoteOpacity; set => SetProperty(ref _quoteOpacity, value); } public bool CanInteract => !IsDownloading; public ICommand BrowseCommand { get; } public ICommand DownloadCommand { get; } public ICommand CancelCommand { get; } public ICommand CheckUpdatesCommand { get; } private void BrowseFolder() { var dialog = new VistaFolderBrowserDialog { Description = "Select a destination folder", UseDescriptionForTitle = true, SelectedPath = Folder }; if (dialog.ShowDialog() == true) { Folder = dialog.SelectedPath; _ = SaveSettingsAsync(); } } private bool CanDownload() { return !IsDownloading && !string.IsNullOrWhiteSpace(Url) && !string.IsNullOrWhiteSpace(Folder) && !string.IsNullOrWhiteSpace(Filename); } private async Task DownloadAndUnblock() { if (string.IsNullOrWhiteSpace(Url) || string.IsNullOrWhiteSpace(Folder) || string.IsNullOrWhiteSpace(Filename)) { SetStatus("Please fill in all fields.", System.Windows.Media.Brushes.Red); return; } var fullPath = Path.Combine(Folder, Filename); _cancellationTokenSource = new CancellationTokenSource(); try { IsDownloading = true; IsProgressVisible = true; ProgressValue = 0; var progress = new Progress(OnDownloadProgress); var result = await _downloadService.DownloadFileAsync(Url, fullPath, progress, _cancellationTokenSource.Token); if (result.Success) { _downloadService.UnblockFile(fullPath); SetStatus($"Downloaded and unblocked successfully!\n{fullPath}\nSize: {FormatBytes(result.BytesDownloaded)}, Duration: {result.Duration:mm\\:ss}", System.Windows.Media.Brushes.Green); ProgressValue = 100; await CheckForUpdates(); } else if (!string.IsNullOrEmpty(result.ErrorMessage)) { SetStatus($"Download failed: {result.ErrorMessage}", System.Windows.Media.Brushes.Red); ProgressValue = 0; } } catch (Exception ex) { SetStatus($"Unexpected error: {ex.Message}", System.Windows.Media.Brushes.Red); ProgressValue = 0; } finally { IsDownloading = false; _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; await Task.Delay(3000); IsProgressVisible = false; } } private void CancelDownload() { _cancellationTokenSource?.Cancel(); SetStatus("Download cancelled by user.", System.Windows.Media.Brushes.Orange); } private async Task CheckForUpdates() { if (string.IsNullOrWhiteSpace(Url) || string.IsNullOrWhiteSpace(Folder) || string.IsNullOrWhiteSpace(Filename)) { UpdateStatus = ""; UpdateAvailable = false; return; } var localPath = Path.Combine(Folder, Filename); try { var updateInfo = await _downloadService.CheckForUpdatesAsync(Url, localPath); if (!string.IsNullOrEmpty(updateInfo.ErrorMessage)) { UpdateStatus = $"Update check failed: {updateInfo.ErrorMessage}"; UpdateAvailable = false; } else if (updateInfo.UpdateAvailable) { UpdateStatus = "Update available! Remote file is newer or different."; UpdateAvailable = true; } else { UpdateStatus = "File is up to date."; UpdateAvailable = false; } } catch (Exception ex) { UpdateStatus = $"Update check error: {ex.Message}"; UpdateAvailable = false; } } private void OnDownloadProgress(DownloadProgress progress) { ProgressValue = progress.ProgressPercentage; SetStatus(progress.Status, System.Windows.Media.Brushes.Blue); } private void SetStatus(string message, System.Windows.Media.Brush color) { StatusMessage = message; StatusColor = color; } private async Task LoadSettingsAsync() { try { var settings = await _settingsService.LoadSettingsAsync(); Url = settings.DefaultUrl; Folder = settings.LastUsedFolder; Filename = settings.DefaultFilename; if (settings.CheckForUpdatesOnStartup && !string.IsNullOrWhiteSpace(Url) && !string.IsNullOrWhiteSpace(Folder) && !string.IsNullOrWhiteSpace(Filename)) { await CheckForUpdates(); } } catch (Exception ex) { await _logger.LogErrorAsync("Failed to load settings", ex); } } private async Task SaveSettingsAsync() { try { var settings = new AppSettings { DefaultUrl = Url, LastUsedFolder = Folder, DefaultFilename = Filename, CheckForUpdatesOnStartup = true, DownloadTimeoutMinutes = 10 }; await _settingsService.SaveSettingsAsync(settings); } catch (Exception ex) { await _logger.LogErrorAsync("Failed to save settings", ex); } } private static string FormatBytes(long bytes) { string[] suffixes = { "B", "KB", "MB", "GB", "TB" }; int counter = 0; decimal number = bytes; while (Math.Round(number / 1024) >= 1) { number /= 1024; counter++; } return $"{number:n1} {suffixes[counter]}"; } private void InitializeQuoteRotation() { CurrentQuote = _quotesService.GetNextQuote(); _quoteTimer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(5) }; _quoteTimer.Tick += OnQuoteTimerTick; _quoteTimer.Start(); } private async void OnQuoteTimerTick(object? sender, EventArgs e) { await RotateQuoteWithFade(); } private async Task RotateQuoteWithFade() { const double fadeOutDuration = 300; const double fadeInDuration = 300; const int fadeSteps = 30; var fadeOutDelay = TimeSpan.FromMilliseconds(fadeOutDuration / fadeSteps); var fadeInDelay = TimeSpan.FromMilliseconds(fadeInDuration / fadeSteps); for (int i = fadeSteps; i >= 0; i--) { QuoteOpacity = (double)i / fadeSteps; await Task.Delay(fadeOutDelay); } CurrentQuote = _quotesService.GetNextQuote(); for (int i = 0; i <= fadeSteps; i++) { QuoteOpacity = (double)i / fadeSteps; await Task.Delay(fadeInDelay); } } public void Dispose() { _quoteTimer?.Stop(); _quoteTimer = null; _cancellationTokenSource?.Dispose(); } } }