| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- using Avalonia.Collections;
- using DynamicData;
- using Gommon;
- using Avalonia.Threading;
- using Ryujinx.Ava.Common;
- using Ryujinx.Ava.Common.Locale;
- using Ryujinx.Ava.Common.Models;
- using Ryujinx.Ava.UI.Helpers;
- using Ryujinx.Ava.Utilities.AppLibrary;
- using Ryujinx.Common.Utilities;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Linq;
- using System.Threading;
- using static Ryujinx.Common.Utilities.XCIFileTrimmer;
- namespace Ryujinx.Ava.UI.ViewModels
- {
- public class XCITrimmerViewModel : BaseModel
- {
- private const long _bytesPerMB = 1024 * 1024;
- private enum ProcessingMode
- {
- Trimming,
- Untrimming
- }
- public enum SortField
- {
- Name,
- Saved
- }
- private const string _FileExtXCI = "XCI";
- private readonly Ryujinx.Common.Logging.XCIFileTrimmerLog _logger;
- private ApplicationLibrary ApplicationLibrary => _mainWindowViewModel.ApplicationLibrary;
- private Optional<XCITrimmerFileModel> _processingApplication = null;
- private AvaloniaList<XCITrimmerFileModel> _allXCIFiles = [];
- private AvaloniaList<XCITrimmerFileModel> _selectedXCIFiles = [];
- private AvaloniaList<XCITrimmerFileModel> _displayedXCIFiles = [];
- private MainWindowViewModel _mainWindowViewModel;
- private CancellationTokenSource _cancellationTokenSource;
- private string _search;
- private ProcessingMode _processingMode;
- private SortField _sortField = SortField.Name;
- private bool _sortAscending = true;
- public XCITrimmerViewModel(MainWindowViewModel mainWindowViewModel)
- {
- _logger = new XCITrimmerLog.TrimmerWindow(this);
- _mainWindowViewModel = mainWindowViewModel;
- LoadXCIApplications();
- }
- private void LoadXCIApplications()
- {
- IEnumerable<ApplicationData> apps = ApplicationLibrary.Applications.Items
- .Where(app => app.FileExtension == _FileExtXCI);
- foreach (ApplicationData xciApp in apps)
- AddOrUpdateXCITrimmerFile(CreateXCITrimmerFile(xciApp.Path));
- ApplicationsChanged();
- }
- private XCITrimmerFileModel CreateXCITrimmerFile(
- string path,
- OperationOutcome operationOutcome = OperationOutcome.Undetermined)
- {
- ApplicationData xciApp = ApplicationLibrary.Applications.Items.First(app => app.FileExtension == _FileExtXCI && app.Path == path);
- return XCITrimmerFileModel.FromApplicationData(xciApp, _logger) with { ProcessingOutcome = operationOutcome };
- }
- private bool AddOrUpdateXCITrimmerFile(XCITrimmerFileModel xci, bool suppressChanged = true, bool autoSelect = true)
- {
- bool replaced = _allXCIFiles.ReplaceWith(xci);
- _displayedXCIFiles.ReplaceWith(xci, Filter(xci));
- _selectedXCIFiles.ReplaceWith(xci, xci.Trimmable && autoSelect);
- if (!suppressChanged)
- ApplicationsChanged();
- return replaced;
- }
- private void FilteringChanged()
- {
- OnPropertyChanged(nameof(Search));
- SortAndFilter();
- }
- private void SortingChanged()
- {
- OnPropertiesChanged(
- nameof(IsSortedByName),
- nameof(IsSortedBySaved),
- nameof(SortingAscending),
- nameof(SortingField),
- nameof(SortingFieldName));
-
- SortAndFilter();
- }
- private void DisplayedChanged()
- {
- OnPropertiesChanged(nameof(Status), nameof(DisplayedXCIFiles), nameof(SelectedDisplayedXCIFiles));
- }
- private void ApplicationsChanged()
- {
- OnPropertiesChanged(
- nameof(AllXCIFiles),
- nameof(Status),
- nameof(PotentialSavings),
- nameof(ActualSavings),
- nameof(CanTrim),
- nameof(CanUntrim));
-
- DisplayedChanged();
- SortAndFilter();
- }
- private void SelectionChanged(bool displayedChanged = true)
- {
- OnPropertiesChanged(
- nameof(Status),
- nameof(CanTrim),
- nameof(CanUntrim),
- nameof(SelectedXCIFiles));
- if (displayedChanged)
- OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
- }
- private void ProcessingChanged()
- {
- OnPropertiesChanged(
- nameof(Processing),
- nameof(Cancel),
- nameof(Status),
- nameof(CanTrim),
- nameof(CanUntrim));
- }
- private IEnumerable<XCITrimmerFileModel> GetSelectedDisplayedXCIFiles()
- {
- return _displayedXCIFiles.Where(xci => _selectedXCIFiles.Contains(xci));
- }
- private void PerformOperation(ProcessingMode processingMode)
- {
- if (Processing)
- {
- return;
- }
- _processingMode = processingMode;
- Processing = true;
- CancellationToken cancellationToken = _cancellationTokenSource.Token;
- Thread XCIFileTrimThread = new(() =>
- {
- List<XCITrimmerFileModel> toProcess = Sort(SelectedXCIFiles
- .Where(xci =>
- (processingMode == ProcessingMode.Untrimming && xci.Untrimmable) ||
- (processingMode == ProcessingMode.Trimming && xci.Trimmable)
- )).ToList();
- List<XCITrimmerFileModel> viewsSaved = DisplayedXCIFiles.ToList();
- Dispatcher.UIThread.Post(() =>
- {
- _selectedXCIFiles.Clear();
- _displayedXCIFiles.Clear();
- _displayedXCIFiles.AddRange(toProcess);
- });
- try
- {
- foreach (XCITrimmerFileModel xciApp in toProcess)
- {
- if (cancellationToken.IsCancellationRequested)
- break;
- XCIFileTrimmer trimmer = new(xciApp.Path, _logger);
- Dispatcher.UIThread.Post(() =>
- {
- ProcessingApplication = xciApp;
- });
- OperationOutcome outcome = OperationOutcome.Undetermined;
- try
- {
- if (cancellationToken.IsCancellationRequested)
- break;
- switch (processingMode)
- {
- case ProcessingMode.Trimming:
- outcome = trimmer.Trim(cancellationToken);
- break;
- case ProcessingMode.Untrimming:
- outcome = trimmer.Untrim(cancellationToken);
- break;
- }
- if (outcome == OperationOutcome.Cancelled)
- outcome = OperationOutcome.Undetermined;
- }
- finally
- {
- Dispatcher.UIThread.Post(() =>
- {
- ProcessingApplication = CreateXCITrimmerFile(xciApp.Path);
- AddOrUpdateXCITrimmerFile(ProcessingApplication, false, false);
- ProcessingApplication = null;
- });
- }
- }
- }
- finally
- {
- Dispatcher.UIThread.Post(() =>
- {
- _displayedXCIFiles.AddOrReplaceMatching(_allXCIFiles, viewsSaved);
- _selectedXCIFiles.AddOrReplaceMatching(_allXCIFiles, toProcess);
- Processing = false;
- ApplicationsChanged();
- });
- }
- })
- {
- Name = "GUI.XCIFilesTrimmerThread",
- IsBackground = true,
- };
- XCIFileTrimThread.Start();
- }
- private bool Filter<T>(T arg)
- {
- if (arg is XCITrimmerFileModel content)
- {
- return string.IsNullOrWhiteSpace(_search)
- || content.Name.ToLower().Contains(_search.ToLower())
- || content.Path.ToLower().Contains(_search.ToLower());
- }
- return false;
- }
- private class CompareXCITrimmerFiles : IComparer<XCITrimmerFileModel>
- {
- private XCITrimmerViewModel _viewModel;
- public CompareXCITrimmerFiles(XCITrimmerViewModel ViewModel)
- {
- _viewModel = ViewModel;
- }
- public int Compare(XCITrimmerFileModel x, XCITrimmerFileModel y)
- {
- int result = 0;
- switch (_viewModel.SortingField)
- {
- case SortField.Name:
- result = x.Name.CompareTo(y.Name);
- break;
- case SortField.Saved:
- result = x.PotentialSavingsB.CompareTo(y.PotentialSavingsB);
- break;
- }
- if (!_viewModel.SortingAscending)
- result = -result;
- if (result == 0)
- result = x.Path.CompareTo(y.Path);
- return result;
- }
- }
- private IOrderedEnumerable<XCITrimmerFileModel> Sort(IEnumerable<XCITrimmerFileModel> list)
- {
- return list
- .OrderBy(xci => xci, new CompareXCITrimmerFiles(this))
- .ThenBy(it => it.Path);
- }
- public void TrimSelected()
- {
- PerformOperation(ProcessingMode.Trimming);
- }
- public void UntrimSelected()
- {
- PerformOperation(ProcessingMode.Untrimming);
- }
- public void SetProgress(int current, int maximum)
- {
- if (_processingApplication != null)
- {
- int percentageProgress = 100 * current / maximum;
- if (!ProcessingApplication.HasValue || (ProcessingApplication.Value.PercentageProgress != percentageProgress))
- ProcessingApplication = ProcessingApplication.Value with { PercentageProgress = percentageProgress };
- }
- }
- public void SelectDisplayed()
- {
- SelectedXCIFiles.AddRange(DisplayedXCIFiles);
- SelectionChanged();
- }
- public void DeselectDisplayed()
- {
- SelectedXCIFiles.RemoveMany(DisplayedXCIFiles);
- SelectionChanged();
- }
- public void Select(XCITrimmerFileModel model)
- {
- bool selectionChanged = !SelectedXCIFiles.Contains(model);
- bool displayedSelectionChanged = !SelectedDisplayedXCIFiles.Contains(model);
- SelectedXCIFiles.ReplaceOrAdd(model, model);
- if (selectionChanged)
- SelectionChanged(displayedSelectionChanged);
- }
- public void Deselect(XCITrimmerFileModel model)
- {
- bool displayedSelectionChanged = !SelectedDisplayedXCIFiles.Contains(model);
- if (SelectedXCIFiles.Remove(model))
- SelectionChanged(displayedSelectionChanged);
- }
- public void SortAndFilter()
- {
- if (Processing)
- return;
- Sort(AllXCIFiles)
- .AsObservableChangeSet()
- .Filter(Filter)
- .Bind(out ReadOnlyObservableCollection<XCITrimmerFileModel> view).AsObservableList();
- _displayedXCIFiles.Clear();
- _displayedXCIFiles.AddRange(view);
- DisplayedChanged();
- }
- public Optional<XCITrimmerFileModel> ProcessingApplication
- {
- get => _processingApplication;
- set
- {
- if (!value.HasValue && _processingApplication.HasValue)
- value = _processingApplication.Value with { PercentageProgress = null };
- if (value.HasValue)
- _displayedXCIFiles.ReplaceWith(value);
- _processingApplication = value;
- OnPropertyChanged();
- }
- }
- public XCITrimmerFileModel NullableProcessingApplication
- {
- get => _processingApplication.OrDefault();
- set
- {
- _processingApplication = value;
- OnPropertyChanged();
- }
- }
- public bool Processing
- {
- get => _cancellationTokenSource != null;
- private set
- {
- if (value && !Processing)
- {
- _cancellationTokenSource = new CancellationTokenSource();
- }
- else if (!value && Processing)
- {
- _cancellationTokenSource.Dispose();
- _cancellationTokenSource = null;
- }
- ProcessingChanged();
- }
- }
- public bool Cancel
- {
- get => _cancellationTokenSource != null && _cancellationTokenSource.IsCancellationRequested;
- set
- {
- if (value)
- {
- if (!Processing)
- return;
- _cancellationTokenSource.Cancel();
- }
- ProcessingChanged();
- }
- }
- public string Status
- {
- get
- {
- if (Processing)
- {
- return _processingMode switch
- {
- ProcessingMode.Trimming => string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusTrimming], DisplayedXCIFiles.Count),
- ProcessingMode.Untrimming => string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusUntrimming], DisplayedXCIFiles.Count),
- _ => string.Empty
- };
- }
- else
- {
- return string.IsNullOrEmpty(Search) ?
- string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusCount], SelectedXCIFiles.Count, AllXCIFiles.Count) :
- string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusCountWithFilter], SelectedXCIFiles.Count, AllXCIFiles.Count, DisplayedXCIFiles.Count);
- }
- }
- }
- public string Search
- {
- get => _search;
- set
- {
- _search = value;
- FilteringChanged();
- }
- }
- public SortField SortingField
- {
- get => _sortField;
- set
- {
- _sortField = value;
- SortingChanged();
- }
- }
- public string SortingFieldName
- {
- get
- {
- return SortingField switch
- {
- SortField.Name => LocaleManager.Instance[LocaleKeys.XCITrimmerSortName],
- SortField.Saved => LocaleManager.Instance[LocaleKeys.XCITrimmerSortSaved],
- _ => string.Empty,
- };
- }
- }
- public bool SortingAscending
- {
- get => _sortAscending;
- set
- {
- _sortAscending = value;
- SortingChanged();
- }
- }
- public bool IsSortedByName
- {
- get => _sortField == SortField.Name;
- }
- public bool IsSortedBySaved
- {
- get => _sortField == SortField.Saved;
- }
- public AvaloniaList<XCITrimmerFileModel> SelectedXCIFiles
- {
- get => _selectedXCIFiles;
- set
- {
- _selectedXCIFiles = value;
- SelectionChanged();
- }
- }
- public AvaloniaList<XCITrimmerFileModel> AllXCIFiles
- {
- get => _allXCIFiles;
- }
- public AvaloniaList<XCITrimmerFileModel> DisplayedXCIFiles
- {
- get => _displayedXCIFiles;
- }
- public string PotentialSavings
- {
- get
- {
- return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.PotentialSavingsB / _bytesPerMB));
- }
- }
- public string ActualSavings
- {
- get
- {
- return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.CurrentSavingsB / _bytesPerMB));
- }
- }
- public IEnumerable<XCITrimmerFileModel> SelectedDisplayedXCIFiles
- {
- get
- {
- return GetSelectedDisplayedXCIFiles().ToList();
- }
- }
- public bool CanTrim
- {
- get
- {
- return !Processing && _selectedXCIFiles.Any(xci => xci.Trimmable);
- }
- }
- public bool CanUntrim
- {
- get
- {
- return !Processing && _selectedXCIFiles.Any(xci => xci.Untrimmable);
- }
- }
- }
- }
|