mirror of
https://github.com/claunia/romrepomgr.git
synced 2025-12-16 19:24:51 +00:00
558 lines
22 KiB
C#
558 lines
22 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Input;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Platform.Storage;
|
|
using Avalonia.Threading;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using RomRepoMgr.Core.EventArgs;
|
|
using RomRepoMgr.Core.Workers;
|
|
using RomRepoMgr.Database;
|
|
using RomRepoMgr.Database.Models;
|
|
using RomRepoMgr.Models;
|
|
using RomRepoMgr.Resources;
|
|
using Serilog;
|
|
using Serilog.Extensions.Logging;
|
|
using SharpCompress.Readers;
|
|
|
|
namespace RomRepoMgr.ViewModels;
|
|
|
|
public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
|
{
|
|
readonly Context _ctx =
|
|
Context.Create(Settings.Settings.Current.DatabasePath, new SerilogLoggerFactory(Log.Logger));
|
|
readonly Stopwatch _mainStopwatch = new();
|
|
readonly ConcurrentBag<DbDisk> _newDisks = [];
|
|
readonly ConcurrentBag<DbFile> _newFiles = [];
|
|
readonly ConcurrentBag<DbMedia> _newMedias = [];
|
|
readonly Stopwatch _stopwatch = new();
|
|
[ObservableProperty]
|
|
bool _canChoose;
|
|
[ObservableProperty]
|
|
bool _canClose;
|
|
[ObservableProperty]
|
|
bool _canStart;
|
|
[ObservableProperty]
|
|
string _folderPath;
|
|
[ObservableProperty]
|
|
bool _isImporting;
|
|
[ObservableProperty]
|
|
bool _isReady;
|
|
[ObservableProperty]
|
|
bool _knownOnlyChecked;
|
|
int _listPosition;
|
|
[ObservableProperty]
|
|
bool _progress2IsIndeterminate;
|
|
[ObservableProperty]
|
|
double _progress2Maximum;
|
|
[ObservableProperty]
|
|
double _progress2Minimum;
|
|
[ObservableProperty]
|
|
double _progress2Value;
|
|
[ObservableProperty]
|
|
bool _progress2Visible;
|
|
[ObservableProperty]
|
|
bool _progressIsIndeterminate;
|
|
[ObservableProperty]
|
|
double _progressMaximum;
|
|
[ObservableProperty]
|
|
double _progressMinimum;
|
|
[ObservableProperty]
|
|
double _progressValue;
|
|
[ObservableProperty]
|
|
bool _progressVisible;
|
|
bool _recurseArchivesChecked;
|
|
[ObservableProperty]
|
|
bool _removeFilesChecked;
|
|
[ObservableProperty]
|
|
bool _removeFilesEnabled;
|
|
FileImporter _rootImporter;
|
|
[ObservableProperty]
|
|
string _statusMessage;
|
|
[ObservableProperty]
|
|
string _statusMessage2;
|
|
[ObservableProperty]
|
|
bool _statusMessage2Visible;
|
|
|
|
public ImportRomFolderViewModel()
|
|
{
|
|
SelectFolderCommand = new AsyncRelayCommand(SelectFolderAsync);
|
|
CloseCommand = new RelayCommand(Close);
|
|
StartCommand = new RelayCommand(Start);
|
|
CanClose = true;
|
|
RemoveFilesChecked = false;
|
|
KnownOnlyChecked = true;
|
|
RecurseArchivesChecked = Settings.Settings.CanDecompress;
|
|
RemoveFilesEnabled = false;
|
|
CanChoose = true;
|
|
}
|
|
|
|
public ICommand SelectFolderCommand { get; }
|
|
public ICommand CloseCommand { get; }
|
|
public ICommand StartCommand { get; }
|
|
public Window View { get; init; }
|
|
|
|
public bool RecurseArchivesEnabled => Settings.Settings.CanDecompress;
|
|
|
|
public bool RecurseArchivesChecked
|
|
{
|
|
get => _recurseArchivesChecked;
|
|
set
|
|
{
|
|
if(value) RemoveFilesChecked = false;
|
|
|
|
RemoveFilesEnabled = !value;
|
|
SetProperty(ref _recurseArchivesChecked, value);
|
|
}
|
|
}
|
|
|
|
public ObservableCollection<RomImporter> Importers { get; } = [];
|
|
|
|
void Start()
|
|
{
|
|
_rootImporter = new FileImporter(_ctx, _newFiles, _newDisks, _newMedias, KnownOnlyChecked, RemoveFilesChecked);
|
|
_rootImporter.SetMessage += SetMessage;
|
|
_rootImporter.SetIndeterminateProgress += SetIndeterminateProgress;
|
|
_rootImporter.SetProgress += SetProgress;
|
|
_rootImporter.SetProgressBounds += SetProgressBounds;
|
|
_rootImporter.Finished += EnumeratingFilesFinished;
|
|
ProgressIsIndeterminate = true;
|
|
ProgressVisible = true;
|
|
CanClose = false;
|
|
CanStart = false;
|
|
IsImporting = true;
|
|
IsReady = false;
|
|
CanChoose = false;
|
|
_mainStopwatch.Start();
|
|
|
|
_ = Task.Run(() => _rootImporter.FindFiles(FolderPath));
|
|
}
|
|
|
|
void SetProgressBounds(object sender, ProgressBoundsEventArgs e) => Dispatcher.UIThread.Post(() =>
|
|
{
|
|
ProgressIsIndeterminate = false;
|
|
ProgressMaximum = e.Maximum;
|
|
ProgressMinimum = e.Minimum;
|
|
});
|
|
|
|
void SetProgress(object sender, ProgressEventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() => ProgressValue = e.Value);
|
|
}
|
|
|
|
void SetIndeterminateProgress(object sender, EventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() => ProgressIsIndeterminate = true);
|
|
}
|
|
|
|
void SetProgress2Bounds(object sender, ProgressBoundsEventArgs e) => Dispatcher.UIThread.Post(() =>
|
|
{
|
|
Progress2IsIndeterminate = false;
|
|
Progress2Maximum = e.Maximum;
|
|
Progress2Minimum = e.Minimum;
|
|
});
|
|
|
|
void SetProgress2(object sender, ProgressEventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() => Progress2Value = e.Value);
|
|
}
|
|
|
|
void SetIndeterminateProgress2(object sender, EventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() => Progress2IsIndeterminate = true);
|
|
}
|
|
|
|
void EnumeratingFilesFinished(object sender, EventArgs e)
|
|
{
|
|
_rootImporter.Finished -= EnumeratingFilesFinished;
|
|
|
|
if(RecurseArchivesChecked)
|
|
{
|
|
Progress2Visible = true;
|
|
StatusMessage2Visible = true;
|
|
_rootImporter.SetMessage2 += SetMessage2;
|
|
_rootImporter.SetIndeterminateProgress2 += SetIndeterminateProgress2;
|
|
_rootImporter.SetProgress2 += SetProgress2;
|
|
_rootImporter.SetProgressBounds2 += SetProgress2Bounds;
|
|
|
|
_rootImporter.Finished += CheckArchivesFinished;
|
|
|
|
_ = Task.Run(() =>
|
|
{
|
|
_stopwatch.Restart();
|
|
|
|
if(Settings.Settings.Current.UseInternalDecompressor)
|
|
_rootImporter.SeparateFilesAndArchivesManaged();
|
|
else
|
|
_rootImporter.SeparateFilesAndArchives();
|
|
});
|
|
}
|
|
else
|
|
ProcessFiles();
|
|
}
|
|
|
|
void ProcessFiles()
|
|
{
|
|
_listPosition = 0;
|
|
ProgressMinimum = 0;
|
|
ProgressMaximum = _rootImporter.Files.Count;
|
|
ProgressValue = 0;
|
|
ProgressIsIndeterminate = false;
|
|
ProgressVisible = true;
|
|
CanClose = false;
|
|
CanStart = false;
|
|
IsReady = false;
|
|
IsImporting = true;
|
|
_stopwatch.Restart();
|
|
|
|
Parallel.ForEach(_rootImporter.Files,
|
|
new ParallelOptions
|
|
{
|
|
MaxDegreeOfParallelism = Environment.ProcessorCount
|
|
},
|
|
file =>
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
StatusMessage = string.Format(Localization.ImportingItem, Path.GetFileName(file));
|
|
ProgressValue = _listPosition;
|
|
});
|
|
|
|
var model = new RomImporter
|
|
{
|
|
Filename = Path.GetFileName(file),
|
|
Indeterminate = true
|
|
};
|
|
|
|
var worker = new FileImporter(_ctx,
|
|
_newFiles,
|
|
_newDisks,
|
|
_newMedias,
|
|
KnownOnlyChecked,
|
|
RemoveFilesChecked);
|
|
|
|
worker.SetIndeterminateProgress2 += model.OnSetIndeterminateProgress;
|
|
worker.SetMessage2 += model.OnSetMessage;
|
|
worker.SetProgress2 += model.OnSetProgress;
|
|
worker.SetProgressBounds2 += model.OnSetProgressBounds;
|
|
worker.ImportedRom += model.OnImportedRom;
|
|
worker.WorkFinished += model.OnWorkFinished;
|
|
|
|
Dispatcher.UIThread.Post(() => Importers.Add(model));
|
|
|
|
worker.ImportFile(file);
|
|
|
|
Interlocked.Increment(ref _listPosition);
|
|
});
|
|
|
|
_stopwatch.Stop();
|
|
Log.Debug("Took {TotalSeconds} seconds to process files", _stopwatch.Elapsed.TotalSeconds);
|
|
|
|
_rootImporter.SaveChanges();
|
|
|
|
_rootImporter.UpdateRomStats();
|
|
|
|
_listPosition = 0;
|
|
ProgressMinimum = 0;
|
|
ProgressMaximum = 1;
|
|
ProgressValue = 0;
|
|
ProgressIsIndeterminate = false;
|
|
ProgressVisible = false;
|
|
CanClose = true;
|
|
CanStart = false;
|
|
IsReady = false;
|
|
IsImporting = false;
|
|
StatusMessage = Localization.Finished;
|
|
_mainStopwatch.Stop();
|
|
|
|
Log.Debug("Took {TotalSeconds} seconds to import ROMs", _mainStopwatch.Elapsed.TotalSeconds);
|
|
}
|
|
|
|
void ProcessArchives()
|
|
{
|
|
// For each archive
|
|
ProgressMaximum = _rootImporter.Archives.Count;
|
|
ProgressMinimum = 0;
|
|
ProgressValue = 0;
|
|
ProgressIsIndeterminate = false;
|
|
Progress2Visible = false;
|
|
StatusMessage2Visible = false;
|
|
_listPosition = 0;
|
|
_stopwatch.Restart();
|
|
|
|
Parallel.ForEach(_rootImporter.Archives,
|
|
new ParallelOptions
|
|
{
|
|
MaxDegreeOfParallelism = Environment.ProcessorCount
|
|
},
|
|
archive =>
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
StatusMessage = "Processing archive: " + Path.GetFileName(archive);
|
|
ProgressValue = _listPosition;
|
|
});
|
|
|
|
// Create FileImporter
|
|
var archiveImporter = new FileImporter(_ctx,
|
|
_newFiles,
|
|
_newDisks,
|
|
_newMedias,
|
|
KnownOnlyChecked,
|
|
RemoveFilesChecked);
|
|
|
|
// Extract archive
|
|
bool ret = Settings.Settings.Current.UseInternalDecompressor
|
|
? archiveImporter.ExtractArchiveManaged(archive)
|
|
: archiveImporter.ExtractArchive(archive);
|
|
|
|
if(!ret) return;
|
|
|
|
// Process files in archive
|
|
foreach(string file in archiveImporter.Files)
|
|
{
|
|
var model = new RomImporter
|
|
{
|
|
Filename = Path.GetFileName(file),
|
|
Indeterminate = true
|
|
};
|
|
|
|
var worker = new FileImporter(_ctx,
|
|
_newFiles,
|
|
_newDisks,
|
|
_newMedias,
|
|
KnownOnlyChecked,
|
|
RemoveFilesChecked);
|
|
|
|
worker.SetIndeterminateProgress2 += model.OnSetIndeterminateProgress;
|
|
worker.SetMessage2 += model.OnSetMessage;
|
|
worker.SetProgress2 += model.OnSetProgress;
|
|
worker.SetProgressBounds2 += model.OnSetProgressBounds;
|
|
worker.ImportedRom += model.OnImportedRom;
|
|
worker.WorkFinished += model.OnWorkFinished;
|
|
|
|
Dispatcher.UIThread.Post(() => Importers.Add(model));
|
|
|
|
worker.ImportFile(file);
|
|
|
|
worker.Files.Clear();
|
|
}
|
|
|
|
// Remove temporary files
|
|
archiveImporter.CleanupExtractedArchive();
|
|
|
|
Interlocked.Increment(ref _listPosition);
|
|
});
|
|
|
|
_stopwatch.Stop();
|
|
Log.Debug("Took {TotalSeconds} seconds to process archives", _stopwatch.Elapsed.TotalSeconds);
|
|
|
|
Progress2Visible = false;
|
|
StatusMessage2Visible = false;
|
|
|
|
ProcessFiles();
|
|
}
|
|
|
|
void ProcessArchivesManaged()
|
|
{
|
|
// For each archive
|
|
ProgressMaximum = _rootImporter.Archives.Count;
|
|
ProgressMinimum = 0;
|
|
ProgressValue = 0;
|
|
ProgressIsIndeterminate = false;
|
|
Progress2Visible = false;
|
|
StatusMessage2Visible = false;
|
|
_listPosition = 0;
|
|
_stopwatch.Restart();
|
|
|
|
Parallel.ForEach(_rootImporter.Archives,
|
|
new ParallelOptions
|
|
{
|
|
MaxDegreeOfParallelism = Environment.ProcessorCount
|
|
},
|
|
archive =>
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
StatusMessage =
|
|
string.Format(Localization.ProcessingArchive, Path.GetFileName(archive));
|
|
|
|
ProgressValue = _listPosition;
|
|
});
|
|
|
|
// Create FileImporter
|
|
var archiveImporter = new FileImporter(_ctx,
|
|
_newFiles,
|
|
_newDisks,
|
|
_newMedias,
|
|
KnownOnlyChecked,
|
|
RemoveFilesChecked);
|
|
|
|
// Open archive
|
|
try
|
|
{
|
|
using var fs = new FileStream(archive, FileMode.Open, FileAccess.Read);
|
|
using IReader reader = ReaderFactory.Open(fs);
|
|
|
|
// Process files in archive
|
|
while(reader.MoveToNextEntry())
|
|
{
|
|
string filename = Path.GetFileName(reader.Entry.Key);
|
|
|
|
if(reader.Entry.IsDirectory) continue;
|
|
|
|
if(reader.Entry.Crc == 0 && KnownOnlyChecked ||
|
|
!archiveImporter.IsCrcInDb(reader.Entry.Crc) && KnownOnlyChecked)
|
|
{
|
|
Dispatcher.UIThread.Post(() => Importers.Add(new RomImporter
|
|
{
|
|
Filename = filename,
|
|
Indeterminate = false,
|
|
Progress = 1,
|
|
Maximum = 1,
|
|
Minimum = 0,
|
|
StatusMessage = Localization.UnknownFile
|
|
}));
|
|
|
|
continue;
|
|
}
|
|
|
|
// Do not import files that are already in the repository
|
|
if(archiveImporter.IsInRepo(reader.Entry.Crc))
|
|
{
|
|
Dispatcher.UIThread.Post(() => Importers.Add(new RomImporter
|
|
{
|
|
Filename = filename,
|
|
Indeterminate = false,
|
|
Progress = 1,
|
|
Maximum = 1,
|
|
Minimum = 0,
|
|
StatusMessage = Localization.FileAlreadyInRepository
|
|
}));
|
|
|
|
continue;
|
|
}
|
|
|
|
var model = new RomImporter
|
|
{
|
|
Filename = Path.GetFileName(reader.Entry.Key),
|
|
Indeterminate = true
|
|
};
|
|
|
|
var worker = new FileImporter(_ctx,
|
|
_newFiles,
|
|
_newDisks,
|
|
_newMedias,
|
|
KnownOnlyChecked,
|
|
RemoveFilesChecked);
|
|
|
|
worker.SetIndeterminateProgress2 += model.OnSetIndeterminateProgress;
|
|
worker.SetMessage2 += model.OnSetMessage;
|
|
worker.SetProgress2 += model.OnSetProgress;
|
|
worker.SetProgressBounds2 += model.OnSetProgressBounds;
|
|
worker.ImportedRom += model.OnImportedRom;
|
|
worker.WorkFinished += model.OnWorkFinished;
|
|
|
|
Dispatcher.UIThread.Post(() => Importers.Add(model));
|
|
|
|
string tmpFile = Path.Combine(Settings.Settings.Current.RepositoryPath,
|
|
Path.GetRandomFileName());
|
|
|
|
worker.ImportAndHashRom(reader.OpenEntryStream(),
|
|
reader.Entry.Key,
|
|
tmpFile,
|
|
reader.Entry.Size);
|
|
|
|
try
|
|
{
|
|
if(File.Exists(tmpFile)) File.Delete(tmpFile);
|
|
}
|
|
catch(IOException)
|
|
#pragma warning disable PH2098
|
|
{
|
|
// Ignore IO exceptions when deleting temporary files
|
|
}
|
|
#pragma warning restore PH2098
|
|
}
|
|
}
|
|
catch(Exception)
|
|
{
|
|
Dispatcher.UIThread.Post(() => Importers.Add(new RomImporter
|
|
{
|
|
Filename = Path.GetFileName(archive),
|
|
Indeterminate = false,
|
|
Progress = 1,
|
|
Maximum = 1,
|
|
Minimum = 0,
|
|
StatusMessage = Localization.ErrorProcessingArchive
|
|
}));
|
|
#pragma warning disable ERP022
|
|
}
|
|
#pragma warning restore ERP022
|
|
finally
|
|
{
|
|
Interlocked.Increment(ref _listPosition);
|
|
}
|
|
});
|
|
|
|
_stopwatch.Stop();
|
|
Log.Debug("Took {TotalSeconds} seconds to process archives", _stopwatch.Elapsed.TotalSeconds);
|
|
|
|
Progress2Visible = false;
|
|
StatusMessage2Visible = false;
|
|
|
|
ProcessFiles();
|
|
}
|
|
|
|
void CheckArchivesFinished(object sender, EventArgs e)
|
|
{
|
|
_stopwatch.Stop();
|
|
Log.Debug("Took {TotalSeconds} seconds to check archives", _stopwatch.Elapsed.TotalSeconds);
|
|
|
|
Progress2Visible = false;
|
|
StatusMessage2Visible = false;
|
|
|
|
_rootImporter.Finished -= CheckArchivesFinished;
|
|
|
|
if(Settings.Settings.Current.UseInternalDecompressor)
|
|
ProcessArchivesManaged();
|
|
else
|
|
ProcessArchives();
|
|
}
|
|
|
|
void SetMessage(object sender, MessageEventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() => StatusMessage = e.Message);
|
|
}
|
|
|
|
void SetMessage2(object sender, MessageEventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() => StatusMessage2 = e.Message);
|
|
}
|
|
|
|
void Close() => View.Close();
|
|
|
|
async Task SelectFolderAsync()
|
|
{
|
|
IReadOnlyList<IStorageFolder> result =
|
|
await View.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
|
{
|
|
Title = Localization.ImportRomsFolderDialogTitle
|
|
});
|
|
|
|
if(result.Count < 1) return;
|
|
|
|
FolderPath = result[0].TryGetLocalPath() ?? string.Empty;
|
|
|
|
IsReady = true;
|
|
CanStart = true;
|
|
CanClose = true;
|
|
}
|
|
} |