mirror of
https://github.com/claunia/romrepomgr.git
synced 2025-12-16 19:24:51 +00:00
Compare commits
43 Commits
8f789484ca
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
475da761a5
|
|||
|
5d1ef578a1
|
|||
|
0903d2b5cc
|
|||
|
6f54ed2e80
|
|||
|
244bf4c7a1
|
|||
|
4eef291174
|
|||
|
627c59bcd0
|
|||
|
0c5760e745
|
|||
|
34c8edbd91
|
|||
|
bdcb5e72f7
|
|||
|
8c5e0ef99e
|
|||
|
ab804239bb
|
|||
|
b62f495824
|
|||
|
7e7660bf6c
|
|||
|
cb4d1706f8
|
|||
|
2928ac3e1f
|
|||
|
8c116535c5
|
|||
|
a551e2474d
|
|||
|
396ee08030
|
|||
|
058b8e4b92
|
|||
|
a290996037
|
|||
|
e1ced26bc5
|
|||
|
9c91d76561
|
|||
|
cf7186adbb
|
|||
|
122e397d0a
|
|||
|
a2fc47cc5b
|
|||
|
80f1c0e28e
|
|||
|
f4b87f68ec
|
|||
|
dc4646512a
|
|||
|
00d005ba98
|
|||
|
9877f3886e
|
|||
|
5bb39929b4
|
|||
|
72b2eed55c
|
|||
|
cca4af8d6d
|
|||
|
8df9bb7211
|
|||
|
52946eca2e
|
|||
|
de9ad80eef
|
|||
|
a7985955b4
|
|||
|
054b9ffd0d
|
|||
|
a8921f640d
|
|||
|
3e731115f2
|
|||
|
dc630f3e78
|
|||
|
5937e0d83e
|
@@ -15,23 +15,26 @@
|
||||
<PackageVersion Include="ErrorProne.NET.Structs" Version="0.1.2"/>
|
||||
<PackageVersion Include="InclusivenessAnalyzer" Version="1.3.0"/>
|
||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.6"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6"/>
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.6"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.7"/>
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7"/>
|
||||
<PackageVersion Include="Microsoft.Extensions.Localization" Version="9.0.7"/>
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.7"/>
|
||||
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapter" Version="4.12.1"/>
|
||||
<PackageVersion Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.15"/>
|
||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0"/>
|
||||
<PackageVersion Include="Mono.Fuse.NETStandard" Version="1.1.0"/>
|
||||
<PackageVersion Include="Philips.CodeAnalysis.MaintainabilityAnalyzers" Version="1.6.3"/>
|
||||
<PackageVersion Include="plist-cil" Version="2.2.0"/>
|
||||
<PackageVersion Include="Roslynator.Analyzers" Version="4.13.1"/>
|
||||
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.13.1"/>
|
||||
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.13.1"/>
|
||||
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.0"/>
|
||||
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.0"/>
|
||||
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.14.0"/>
|
||||
<PackageVersion Include="SabreTools.Models" Version="1.5.8"/>
|
||||
<PackageVersion Include="Serilog" Version="4.3.0"/>
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0"/>
|
||||
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.2"/>
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0"/>
|
||||
<PackageVersion Include="SharpCompress" Version="0.39.0"/>
|
||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31"/>
|
||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0"/>
|
||||
@@ -46,5 +49,8 @@
|
||||
<PackageVersion Include="SharpCompress" Version="0.38.0"/>
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0"/>
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6"/>
|
||||
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.12.1"/>
|
||||
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.12.1"/>
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.116.1" Condition="$(RuntimeIdentifier.StartsWith('linux'))" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
88
README.md
Normal file
88
README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
<div style="text-align: center;">
|
||||
<img src="logo.png" alt="logo"/>
|
||||
</div>
|
||||
|
||||
# ROM Repository Manager
|
||||
|
||||
ROM Repository Manager is a versatile tool designed to manage your cold storage of ROM sets effortlessly. Whether you're a retro gaming enthusiast or a dedicated collector, ROM Repository Manager streamlines the process of organizing, compressing, and deduplicating your ROM sets.
|
||||
|
||||
## Features
|
||||
|
||||
* **Organize ROMs**: Automatically sort and categorize ROM files based on the metadata found in widely available DAT files.
|
||||
* **Deduplicate**: Optimize your storage by ensuring each ROM is stored only once, even if it appears in multiple sets or duplicates within the same set.
|
||||
* **Compress:** Reduce the size of your ROMs with advanced compression options. Choose LZIP for maximum space savings or ZSTD for faster performance, both surpassing the typical ZIP files used in ROM sets.
|
||||
* **Cross-Platform**: Compatible with Windows, macOS, and Linux.
|
||||
* **User-Friendly Interface**: Intuitive and easy-to-use interface for seamless management. Choose between the desktop application for direct access or deploy it on a server to use through your browser (e.g., in a NAS). The choice is yours!
|
||||
* **Virtual filesystem**: A standout feature of our desktop application is its ability to provide direct access to the ROM set from our repository. This is achieved through a virtual filesystem, allowing you to view and interact with the ROM set in its original folder structure without the need for extraction.
|
||||
|
||||
## Applications
|
||||
|
||||
ROM Repository Manager contains two different applications:
|
||||
|
||||
* **Desktop Application**: Built using .NET, this application provides a robust and feature-rich interface for managing ROM repositories on your computer.
|
||||
* **Blazor Application**: A web-based application built with .NET Blazor, offering seamless access to ROM management features through your browser.
|
||||
|
||||
Both applications are available as pre-compiled binaries in the releases section. Users do not need to compile anything themselves.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Launch the application.
|
||||
2. Import the DAT files to get started.
|
||||
3. Import your ROM sets.
|
||||
4. Let ROM Repository Manager store your ROMs deduplicated and compressed.
|
||||
|
||||
## How does it compare?
|
||||
|
||||
When comparing with the ROM set from MAME 0.278, which occupies 78.2Gb compressed, 169Gb uncompressed:
|
||||
|
||||
* Using LZIP compression, ROM Repository Manager reduces the size to **68.5Gb**.
|
||||
* Using ZSTD compression, the size is reduced to **71.4Gb**.
|
||||
|
||||
### Let the filesystem do it
|
||||
|
||||
A filesystem can improve our deduplication, adding block-level optimization on top of our file-level strategy, and achieving slightly better results. For example:
|
||||
|
||||
* **ZFS**: Reduces the repository to **71.3Gb** with ZSTD compression.
|
||||
* **btrfs**: Reduces the repository to **85.6Gb**.
|
||||
|
||||
While these options deliver marginal gains in space and speed, they come at the cost of significantly higher RAM usage. Consider them only if your system has the resources to support it.
|
||||
|
||||
## Blazor Application Configuration
|
||||
|
||||
To configure the Blazor application, you need to modify the `appsettings.json` file. Below are the valid fields you can change, along with their purposes:
|
||||
|
||||
- **LogLevel**: Adjusts the level of detail written to the logs. Valid values are`Information` and `Debug`
|
||||
|
||||
- **LogFile**: Specifies the location where the log is written.
|
||||
|
||||
- **Repository**: Defines the location where the repository resides.
|
||||
|
||||
- **ImportRoms**: Sets the location where the application will look for ROMs to import.
|
||||
|
||||
- **ImportDats**: Sets the location where the application will look for DATs to import.
|
||||
|
||||
- **ExportRoms**: Specifies the location where the application will export ROMs.
|
||||
|
||||
- **ExportDats**: Specifies the location where the application will export DATs.
|
||||
|
||||
- **Database**: Defines the location where the repository database will reside.
|
||||
|
||||
- **Temporary**: Specifies the location where the application will store temporary files.
|
||||
|
||||
- **CompressionType**: Determines the repository compression algorithm used. This setting only applies to newly added ROMs. Valid values are `Lzip`, `Zstd` and `None`
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! If you have ideas for new features or improvements, feel free to open an issue or submit a pull request.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GPL version 3. See the LICENSE file for details.
|
||||
|
||||
---
|
||||
|
||||
For more information, visit the [GitHub repository](https://github.com/claunia/RomRepoMgr).
|
||||
|
||||
© 2020-2025 [Nat Portillo](https://www.natportillo.es)
|
||||
|
||||
Happy ROM managing!
|
||||
20
RomRepoMgr.Blazor/Components/App.razor
Normal file
20
RomRepoMgr.Blazor/Components/App.razor
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<base href="/"/>
|
||||
<link href="@Assets["app.css"]" rel="stylesheet"/>
|
||||
<link href="@Assets["RomRepoMgr.Blazor.styles.css"]" rel="stylesheet"/>
|
||||
<ImportMap/>
|
||||
<link href="favicon.ico" rel="icon" type="image/x-icon"/>
|
||||
<HeadOutlet @rendermode="InteractiveServer"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes @rendermode="InteractiveServer"/>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
29
RomRepoMgr.Blazor/Components/Dialogs/ImportDats.razor
Normal file
29
RomRepoMgr.Blazor/Components/Dialogs/ImportDats.razor
Normal file
@@ -0,0 +1,29 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using RomRepoMgr.Blazor.Resources
|
||||
@implements IDialogContentComponent
|
||||
@inject ILogger<ImportDats> Logger
|
||||
@inject IStringLocalizer<Localization> Localizer
|
||||
@inject IConfiguration Configuration
|
||||
|
||||
<FluentDialog Width="800px" Height="400px" Title="Import DATs" Modal="true" TrapFocus="true">
|
||||
<FluentDialogBody>
|
||||
<div>
|
||||
<p hidden="@IsBusy">@string.Format(Localizer["DATs_will_be_imported_from"], path)</p>
|
||||
<FluentLabel Color="@StatusColor">@StatusMessage</FluentLabel>
|
||||
<FluentProgress Max="@ProgressMax" Min="@ProgressMin" Value="@ProgressValue" Visible="@ProgressVisible"/>
|
||||
</div>
|
||||
@if(Progress2Visible)
|
||||
{
|
||||
<div>
|
||||
<p>@StatusMessage2</p>
|
||||
<FluentProgress Max="@Progress2Max" Min="@Progress2Min" Value="@Progress2Value"/>
|
||||
</div>
|
||||
}
|
||||
</FluentDialogBody>
|
||||
<FluentDialogFooter>
|
||||
<FluentStack Orientation="Orientation.Horizontal">
|
||||
<FluentButton OnClick="@Start" Disabled="@IsBusy">@Localizer["Start"]</FluentButton>
|
||||
<FluentButton OnClick="@CloseAsync" Disabled="@CannotClose">@Localizer["Close"]</FluentButton>
|
||||
</FluentStack>
|
||||
</FluentDialogFooter>
|
||||
</FluentDialog>
|
||||
181
RomRepoMgr.Blazor/Components/Dialogs/ImportDats.razor.cs
Normal file
181
RomRepoMgr.Blazor/Components/Dialogs/ImportDats.razor.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using RomRepoMgr.Core.EventArgs;
|
||||
using RomRepoMgr.Core.Workers;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Logging;
|
||||
using ErrorEventArgs = RomRepoMgr.Core.EventArgs.ErrorEventArgs;
|
||||
|
||||
namespace RomRepoMgr.Blazor.Components.Dialogs;
|
||||
|
||||
public partial class ImportDats : ComponentBase
|
||||
{
|
||||
readonly Stopwatch _stopwatch = new();
|
||||
string[] _datFiles;
|
||||
int _listPosition;
|
||||
int _workers;
|
||||
string path;
|
||||
public string StatusMessage { get; set; }
|
||||
public bool IsBusy { get; set; }
|
||||
[CascadingParameter]
|
||||
public FluentDialog Dialog { get; set; }
|
||||
public int? ProgressMax { get; set; }
|
||||
public int? ProgressMin { get; set; }
|
||||
public int? ProgressValue { get; set; }
|
||||
public bool CannotClose { get; set; }
|
||||
public bool ProgressVisible { get; set; }
|
||||
public string StatusMessage2 { get; set; }
|
||||
public bool Progress2Visible { get; set; }
|
||||
public int? Progress2Max { get; set; }
|
||||
public int? Progress2Min { get; set; }
|
||||
public int? Progress2Value { get; set; }
|
||||
public Color? StatusColor { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
path = Configuration["DataFolders:ImportDats"] ?? "incoming-dats";
|
||||
StatusMessage = "";
|
||||
StatusMessage2 = "";
|
||||
IsBusy = false;
|
||||
CannotClose = false;
|
||||
ProgressVisible = false;
|
||||
Progress2Visible = false;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
IsBusy = true;
|
||||
CannotClose = true;
|
||||
ProgressVisible = true;
|
||||
ProgressValue = null;
|
||||
StatusMessage = Localizer["SearchingForFiles"];
|
||||
|
||||
_stopwatch.Restart();
|
||||
string[] dats = Directory.GetFiles(path, "*.dat", SearchOption.AllDirectories);
|
||||
|
||||
string[] xmls = Directory.GetFiles(path, "*.xml", SearchOption.AllDirectories);
|
||||
|
||||
_datFiles = dats.Concat(xmls).Order().ToArray();
|
||||
_stopwatch.Stop();
|
||||
|
||||
Logger.LogDebug("Took {TotalSeconds} to find {Length} DAT files",
|
||||
_stopwatch.Elapsed.TotalSeconds,
|
||||
_datFiles.Length);
|
||||
|
||||
StatusMessage = string.Format(Localizer["FoundFiles"], _datFiles.Length);
|
||||
|
||||
ProgressMin = 0;
|
||||
ProgressMax = _datFiles.Length;
|
||||
ProgressValue = 0;
|
||||
Progress2Visible = true;
|
||||
_listPosition = 0;
|
||||
_workers = 0;
|
||||
StateHasChanged();
|
||||
|
||||
_stopwatch.Restart();
|
||||
Logger.LogDebug("Starting to import DAT files...");
|
||||
Import();
|
||||
}
|
||||
|
||||
void Import()
|
||||
{
|
||||
if(_listPosition >= _datFiles.Length)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
ProgressVisible = false;
|
||||
Progress2Visible = false;
|
||||
StatusMessage = Localizer["Finished"];
|
||||
CannotClose = false;
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
_stopwatch.Stop();
|
||||
|
||||
Logger.LogDebug("Took {TotalSeconds} seconds to import {Length} DAT files",
|
||||
_stopwatch.Elapsed.TotalSeconds,
|
||||
_datFiles.Length);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
StatusMessage = string.Format(Localizer["ImportingItem"], Path.GetFileName(_datFiles[_listPosition]));
|
||||
ProgressValue = _listPosition;
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
var worker = new DatImporter(_datFiles[_listPosition], null, new SerilogLoggerFactory(Log.Logger));
|
||||
|
||||
worker.ErrorOccurred += OnWorkerOnErrorOccurred;
|
||||
worker.SetIndeterminateProgress += OnWorkerOnSetIndeterminateProgress;
|
||||
worker.SetMessage += OnWorkerOnSetMessage;
|
||||
worker.SetProgress += OnWorkerOnSetProgress;
|
||||
worker.SetProgressBounds += OnWorkerOnSetProgressBounds;
|
||||
worker.WorkFinished += OnWorkerOnWorkFinished;
|
||||
_ = Task.Run(worker.Import);
|
||||
}
|
||||
|
||||
void OnWorkerOnWorkFinished(object? sender, MessageEventArgs args)
|
||||
{
|
||||
_listPosition++;
|
||||
Import();
|
||||
}
|
||||
|
||||
void OnWorkerOnSetProgressBounds(object? sender, ProgressBoundsEventArgs args)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Value = 0;
|
||||
Progress2Max = (int?)args.Maximum;
|
||||
Progress2Min = (int?)args.Minimum;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void OnWorkerOnSetProgress(object? sender, ProgressEventArgs args)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Value = (int?)args.Value;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void OnWorkerOnSetMessage(object? sender, MessageEventArgs args)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
StatusMessage2 = args.Message;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void OnWorkerOnSetIndeterminateProgress(object? sender, EventArgs args)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Value = null;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void OnWorkerOnErrorOccurred(object? sender, ErrorEventArgs args)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
_listPosition++;
|
||||
Import();
|
||||
});
|
||||
}
|
||||
|
||||
Task CloseAsync() => Dialog.CloseAsync();
|
||||
}
|
||||
43
RomRepoMgr.Blazor/Components/Dialogs/ImportRoms.razor
Normal file
43
RomRepoMgr.Blazor/Components/Dialogs/ImportRoms.razor
Normal file
@@ -0,0 +1,43 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using RomRepoMgr.Blazor.Resources
|
||||
@using RomRepoMgr.Database
|
||||
@implements IDialogContentComponent
|
||||
@inject ILogger<ImportRoms> Logger
|
||||
@inject Context _ctx
|
||||
@inject IStringLocalizer<Localization> Localizer
|
||||
@inject IConfiguration Configuration
|
||||
|
||||
<FluentDialog Width="1600px" Height="800px" Title="Import DATs" Modal="true" TrapFocus="true">
|
||||
<FluentDialogBody>
|
||||
<p hidden="@IsBusy">@string.Format(Localizer["ROMs_will_be_imported_from"], FolderPath)</p>
|
||||
@if(NotYetStarted)
|
||||
{
|
||||
<FluentCheckbox @bind-Value="@RemoveFilesChecked" Label="@Localizer["RemoveFilesLabel"]"/>
|
||||
|
||||
<FluentCheckbox @bind-Value="@KnownOnlyChecked" Label="@Localizer["KnownOnlyLabel"]"/>
|
||||
|
||||
<FluentCheckbox @bind-Value="@RecurseArchivesChecked"
|
||||
Label=@Localizer["RecurseArchivesLabel"]/>
|
||||
}
|
||||
@if(Importing)
|
||||
{
|
||||
<div>
|
||||
<FluentLabel Color="@StatusMessageColor">@StatusMessage</FluentLabel>
|
||||
<FluentProgress Max="@ProgressMax" Min="@ProgressMin" Value="@ProgressValue"/>
|
||||
</div>
|
||||
}
|
||||
@if(Progress2Visible)
|
||||
{
|
||||
<div>
|
||||
<FluentLabel>@StatusMessage2</FluentLabel>
|
||||
<FluentProgress Max="@Progress2Max" Min="@Progress2Min" Value="@Progress2Value"/>
|
||||
</div>
|
||||
}
|
||||
</FluentDialogBody>
|
||||
<FluentDialogFooter>
|
||||
<FluentStack Orientation="Orientation.Horizontal">
|
||||
<FluentButton OnClick="@Start" Disabled="@IsBusy">@Localizer["Start"]</FluentButton>
|
||||
<FluentButton OnClick="@CloseAsync" Disabled="@CannotClose">@Localizer["Close"]</FluentButton>
|
||||
</FluentStack>
|
||||
</FluentDialogFooter>
|
||||
</FluentDialog>
|
||||
355
RomRepoMgr.Blazor/Components/Dialogs/ImportRoms.razor.cs
Normal file
355
RomRepoMgr.Blazor/Components/Dialogs/ImportRoms.razor.cs
Normal file
@@ -0,0 +1,355 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using RomRepoMgr.Core.EventArgs;
|
||||
using RomRepoMgr.Core.Workers;
|
||||
using RomRepoMgr.Database.Models;
|
||||
using Serilog;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace RomRepoMgr.Blazor.Components.Dialogs;
|
||||
|
||||
public partial class ImportRoms : ComponentBase
|
||||
{
|
||||
readonly Stopwatch _mainStopwatch = new();
|
||||
readonly ConcurrentBag<DbDisk> _newDisks = [];
|
||||
readonly ConcurrentBag<DbFile> _newFiles = [];
|
||||
readonly ConcurrentBag<DbMedia> _newMedias = [];
|
||||
readonly Stopwatch _stopwatch = new();
|
||||
int _listPosition;
|
||||
FileImporter _rootImporter;
|
||||
PaginationState? pagination;
|
||||
[CascadingParameter]
|
||||
public FluentDialog Dialog { get; set; }
|
||||
public bool IsBusy { get; set; }
|
||||
public bool NotYetStarted { get; set; }
|
||||
public bool RemoveFilesChecked { get; set; }
|
||||
public bool KnownOnlyChecked { get; set; }
|
||||
public bool RecurseArchivesChecked { get; set; }
|
||||
public Color? StatusMessageColor { get; set; }
|
||||
public string? StatusMessage { get; set; }
|
||||
public int? ProgressMax { get; set; }
|
||||
public int? ProgressMin { get; set; }
|
||||
public int? ProgressValue { get; set; }
|
||||
public string? StatusMessage2 { get; set; }
|
||||
public int? Progress2Max { get; set; }
|
||||
public int? Progress2Min { get; set; }
|
||||
public int? Progress2Value { get; set; }
|
||||
public string? FolderPath { get; set; }
|
||||
public bool Importing { get; set; }
|
||||
public bool Progress2Visible { get; set; }
|
||||
public bool DataGridVisible { get; set; }
|
||||
public bool CannotClose { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
FolderPath = Configuration["DataFolders:ImportRoms"] ?? "incoming";
|
||||
IsBusy = false;
|
||||
NotYetStarted = true;
|
||||
RemoveFilesChecked = false;
|
||||
KnownOnlyChecked = true;
|
||||
RecurseArchivesChecked = true;
|
||||
StatusMessage = "";
|
||||
Importing = false;
|
||||
Progress2Visible = false;
|
||||
DataGridVisible = false;
|
||||
|
||||
Logger.LogDebug("ImportRoms dialog initialized with Path: {Path}", FolderPath);
|
||||
}
|
||||
|
||||
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;
|
||||
ProgressValue = null;
|
||||
Importing = true;
|
||||
CannotClose = true;
|
||||
IsBusy = true;
|
||||
NotYetStarted = false;
|
||||
_mainStopwatch.Start();
|
||||
|
||||
_ = Task.Run(() => _rootImporter.FindFiles(FolderPath));
|
||||
}
|
||||
|
||||
void SetProgressBounds(object? sender, ProgressBoundsEventArgs e)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
ProgressValue = 0;
|
||||
ProgressMax = (int?)e.Maximum;
|
||||
ProgressMin = (int?)e.Minimum;
|
||||
});
|
||||
}
|
||||
|
||||
void SetProgress(object? sender, ProgressEventArgs e)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
ProgressValue = (int?)e.Value;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void SetIndeterminateProgress(object? sender, EventArgs e)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
ProgressValue = null;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void SetMessage(object? sender, MessageEventArgs e)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
StatusMessage = e.Message;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
Task CloseAsync() => Dialog.CloseAsync();
|
||||
|
||||
void EnumeratingFilesFinished(object? sender, EventArgs e)
|
||||
{
|
||||
_rootImporter.Finished -= EnumeratingFilesFinished;
|
||||
|
||||
if(RecurseArchivesChecked)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Visible = true;
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
_rootImporter.SetMessage2 += SetMessage2;
|
||||
_rootImporter.SetIndeterminateProgress2 += SetIndeterminateProgress2;
|
||||
_rootImporter.SetProgress2 += SetProgress2;
|
||||
_rootImporter.SetProgressBounds2 += SetProgress2Bounds;
|
||||
|
||||
_rootImporter.Finished += CheckArchivesFinished;
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
_rootImporter.SeparateFilesAndArchivesManaged();
|
||||
});
|
||||
}
|
||||
else
|
||||
ProcessFiles();
|
||||
}
|
||||
|
||||
void SetProgress2Bounds(object? sender, ProgressBoundsEventArgs e)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Value = 0;
|
||||
Progress2Max = (int?)e.Maximum;
|
||||
Progress2Min = (int?)e.Minimum;
|
||||
});
|
||||
}
|
||||
|
||||
void SetProgress2(object? sender, ProgressEventArgs e)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Value = (int?)e.Value;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void SetIndeterminateProgress2(object? sender, EventArgs e)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Value = null;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void SetMessage2(object? sender, MessageEventArgs e)
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
StatusMessage2 = e.Message;
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void ProcessFiles()
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
ProgressMin = 0;
|
||||
ProgressMax = _rootImporter.Files.Count;
|
||||
ProgressValue = 0;
|
||||
NotYetStarted = false;
|
||||
CannotClose = true;
|
||||
IsBusy = true;
|
||||
Importing = true;
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
_listPosition = 0;
|
||||
_stopwatch.Restart();
|
||||
|
||||
Parallel.ForEach(_rootImporter.Files,
|
||||
file =>
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
StatusMessage = string.Format(Localizer["ImportingItem"], Path.GetFileName(file));
|
||||
ProgressValue = _listPosition;
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
var worker = new FileImporter(_ctx,
|
||||
_newFiles,
|
||||
_newDisks,
|
||||
_newMedias,
|
||||
KnownOnlyChecked,
|
||||
RemoveFilesChecked);
|
||||
|
||||
worker.ImportFile(file);
|
||||
|
||||
Interlocked.Increment(ref _listPosition);
|
||||
});
|
||||
|
||||
_stopwatch.Stop();
|
||||
Log.Debug("Took {TotalSeconds} seconds to process files", _stopwatch.Elapsed.TotalSeconds);
|
||||
|
||||
_rootImporter.SaveChanges();
|
||||
|
||||
_rootImporter.UpdateRomStats();
|
||||
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
ProgressMin = 0;
|
||||
ProgressMax = 1;
|
||||
ProgressValue = 0;
|
||||
CannotClose = false;
|
||||
IsBusy = false;
|
||||
Importing = false;
|
||||
StatusMessage = Localizer["Finished"];
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
_listPosition = 0;
|
||||
_mainStopwatch.Stop();
|
||||
|
||||
Log.Debug("Took {TotalSeconds} seconds to import ROMs", _mainStopwatch.Elapsed.TotalSeconds);
|
||||
}
|
||||
|
||||
void CheckArchivesFinished(object? sender, EventArgs e)
|
||||
{
|
||||
_stopwatch.Stop();
|
||||
Log.Debug("Took {TotalSeconds} seconds to check archives", _stopwatch.Elapsed.TotalSeconds);
|
||||
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Visible = false;
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
_rootImporter.Finished -= CheckArchivesFinished;
|
||||
|
||||
ProcessArchivesManaged();
|
||||
}
|
||||
|
||||
void ProcessArchivesManaged()
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
ProgressMax = _rootImporter.Archives.Count;
|
||||
ProgressMin = 0;
|
||||
ProgressValue = 0;
|
||||
Progress2Visible = false;
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
_listPosition = 0;
|
||||
_stopwatch.Restart();
|
||||
|
||||
// For each archive
|
||||
Parallel.ForEach(_rootImporter.Archives,
|
||||
archive =>
|
||||
{
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
StatusMessage =
|
||||
string.Format(Localizer["ProcessingArchive"], Path.GetFileName(archive));
|
||||
|
||||
ProgressValue = _listPosition;
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
// 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())
|
||||
{
|
||||
if(reader.Entry.IsDirectory) continue;
|
||||
|
||||
if(reader.Entry.Crc == 0 && KnownOnlyChecked) continue;
|
||||
|
||||
if(!archiveImporter.IsCrcInDb(reader.Entry.Crc) && KnownOnlyChecked) continue;
|
||||
|
||||
var worker = new FileImporter(_ctx,
|
||||
_newFiles,
|
||||
_newDisks,
|
||||
_newMedias,
|
||||
KnownOnlyChecked,
|
||||
RemoveFilesChecked);
|
||||
|
||||
worker.ImportAndHashRom(reader.OpenEntryStream(),
|
||||
reader.Entry.Key,
|
||||
Path.Combine(Settings.Settings.Current.RepositoryPath,
|
||||
Path.GetFileName(Path.GetTempFileName())),
|
||||
reader.Entry.Size);
|
||||
}
|
||||
}
|
||||
catch(InvalidOperationException) {}
|
||||
finally
|
||||
{
|
||||
Interlocked.Increment(ref _listPosition);
|
||||
}
|
||||
});
|
||||
|
||||
_stopwatch.Stop();
|
||||
Log.Debug("Took {TotalSeconds} seconds to process archives", _stopwatch.Elapsed.TotalSeconds);
|
||||
|
||||
_ = InvokeAsync(() =>
|
||||
{
|
||||
Progress2Visible = false;
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
ProcessFiles();
|
||||
}
|
||||
}
|
||||
24
RomRepoMgr.Blazor/Components/Layout/MainLayout.razor
Normal file
24
RomRepoMgr.Blazor/Components/Layout/MainLayout.razor
Normal file
@@ -0,0 +1,24 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<FluentLayout>
|
||||
<FluentHeader>
|
||||
ROM Repository Manager
|
||||
</FluentHeader>
|
||||
<FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%">
|
||||
<FluentBodyContent Class="body-content">
|
||||
<div class="content">
|
||||
@Body
|
||||
</div>
|
||||
</FluentBodyContent>
|
||||
</FluentStack>
|
||||
<FluentFooter>
|
||||
<a href="https://www.natportillo.es" target="_blank">Copyright © 2020-2025 Natalia Portillo</a>
|
||||
</FluentFooter>
|
||||
<FluentDialogProvider/>
|
||||
</FluentLayout>
|
||||
|
||||
<div data-nosnippet id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a class="reload" href=".">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
37
RomRepoMgr.Blazor/Components/Pages/Error.razor
Normal file
37
RomRepoMgr.Blazor/Components/Pages/Error.razor
Normal file
@@ -0,0 +1,37 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if(ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that
|
||||
occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong>
|
||||
environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() => RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
40
RomRepoMgr.Blazor/Components/Pages/Home.razor
Normal file
40
RomRepoMgr.Blazor/Components/Pages/Home.razor
Normal file
@@ -0,0 +1,40 @@
|
||||
@page "/"
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using RomRepoMgr.Blazor.Resources
|
||||
@using RomRepoMgr.Database
|
||||
@rendermode InteractiveServer
|
||||
@inject IDialogService DialogService
|
||||
@inject Context ctx
|
||||
@inject IStringLocalizer<Localization> Localizer
|
||||
|
||||
|
||||
<PageTitle>ROM Repository Manager</PageTitle>
|
||||
|
||||
<FluentToolbar>
|
||||
<FluentButton OnClick="@ImportDatsAsync">@Localizer["ImportDats"]</FluentButton>
|
||||
<FluentButton Disabled="true">@Localizer["ExportDat"]</FluentButton>
|
||||
<FluentButton Disabled="true">@Localizer["RemoveDat"]</FluentButton>
|
||||
<FluentButton OnClick="@ImportRomsAsync">@Localizer["ImportRoms"]</FluentButton>
|
||||
<FluentButton Disabled="true">@Localizer["ExportRoms"]</FluentButton>
|
||||
</FluentToolbar>
|
||||
|
||||
<FluentDataGrid @ref="romSetsGrid" Items="@RomSets" Style="width: 100%;" AutoFit="true" Pagination="@pagination"
|
||||
AutoItemsPerPage="true" ResizableColumns="true">
|
||||
<PropertyColumn Property="@(p => p.Name)" Title="@Localizer["RomSetNameLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.Version)" Title="@Localizer["RomSetVersionLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.Author)" Title="@Localizer["RomSetAuthorLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.Category)" Title="@Localizer["RomSetCategoryLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.Date)" Title="@Localizer["RomSetDateLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.Description)" Title="@Localizer["RomSetDescriptionLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.Comment)" Title="@Localizer["RomSetCommentLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.Homepage)" Title="@Localizer["HomepageLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.TotalMachines)" Title="@Localizer["RomSetTotalMachinesLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.CompleteMachines)" Title="@Localizer["RomSetCompleteMachinesLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.IncompleteMachines)" Title="@Localizer["RomSetIncompleteMachinesLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.TotalRoms)" Title="@Localizer["RomSetTotalRomsLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.HaveRoms)" Title="@Localizer["RomSetHaveRomsLabel"]"/>
|
||||
<PropertyColumn Property="@(p => p.MissRoms)" Title="@Localizer["RomSetMissRomsLabel"]"/>
|
||||
|
||||
</FluentDataGrid>
|
||||
|
||||
<FluentPaginator State="@pagination"/>
|
||||
64
RomRepoMgr.Blazor/Components/Pages/Home.razor.cs
Normal file
64
RomRepoMgr.Blazor/Components/Pages/Home.razor.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using RomRepoMgr.Blazor.Components.Dialogs;
|
||||
using RomRepoMgr.Core.Models;
|
||||
|
||||
namespace RomRepoMgr.Blazor.Components.Pages;
|
||||
|
||||
public partial class Home : ComponentBase
|
||||
{
|
||||
readonly PaginationState pagination = new()
|
||||
{
|
||||
ItemsPerPage = 10
|
||||
};
|
||||
FluentDataGrid<RomSetModel>? romSetsGrid;
|
||||
|
||||
public IQueryable<RomSetModel>? RomSets { get; set; }
|
||||
|
||||
async Task ImportDatsAsync()
|
||||
{
|
||||
IDialogReference dialog = await DialogService.ShowDialogAsync<ImportDats>(new DialogParameters());
|
||||
}
|
||||
|
||||
async Task ImportRomsAsync()
|
||||
{
|
||||
IDialogReference dialog = await DialogService.ShowDialogAsync<ImportRoms>(new DialogParameters());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
romSetsGrid?.SetLoadingState(true);
|
||||
|
||||
RomSets = ctx.RomSets.OrderBy(r => r.Name)
|
||||
.ThenBy(r => r.Version)
|
||||
.ThenBy(r => r.Date)
|
||||
.ThenBy(r => r.Description)
|
||||
.ThenBy(r => r.Comment)
|
||||
.ThenBy(r => r.Filename)
|
||||
.Select(r => new RomSetModel
|
||||
{
|
||||
Id = r.Id,
|
||||
Author = r.Author,
|
||||
Comment = r.Comment,
|
||||
Date = r.Date,
|
||||
Description = r.Description,
|
||||
Filename = r.Filename,
|
||||
Homepage = r.Homepage,
|
||||
Name = r.Name,
|
||||
Sha384 = r.Sha384,
|
||||
Version = r.Version,
|
||||
TotalMachines = r.Statistics.TotalMachines,
|
||||
CompleteMachines = r.Statistics.CompleteMachines,
|
||||
IncompleteMachines = r.Statistics.IncompleteMachines,
|
||||
TotalRoms = r.Statistics.TotalRoms,
|
||||
HaveRoms = r.Statistics.HaveRoms,
|
||||
MissRoms = r.Statistics.MissRoms,
|
||||
Category = r.Category
|
||||
});
|
||||
|
||||
romSetsGrid?.SetLoadingState(false);
|
||||
}
|
||||
}
|
||||
9
RomRepoMgr.Blazor/Components/Routes.razor
Normal file
9
RomRepoMgr.Blazor/Components/Routes.razor
Normal file
@@ -0,0 +1,9 @@
|
||||
@using RomRepoMgr.Blazor.Components.Layout
|
||||
<FluentDesignTheme StorageName="theme" Mode="DesignThemeModes.Dark">
|
||||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView DefaultLayout="typeof(MainLayout)" RouteData="routeData"/>
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
||||
</Found>
|
||||
</Router>
|
||||
</FluentDesignTheme>
|
||||
12
RomRepoMgr.Blazor/Components/_Imports.razor
Normal file
12
RomRepoMgr.Blazor/Components/_Imports.razor
Normal file
@@ -0,0 +1,12 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.FluentUI.AspNetCore.Components
|
||||
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||
@using Microsoft.JSInterop
|
||||
@using RomRepoMgr.Blazor
|
||||
@using RomRepoMgr.Blazor.Components
|
||||
172
RomRepoMgr.Blazor/Program.cs
Normal file
172
RomRepoMgr.Blazor/Program.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using RomRepoMgr.Blazor.Components;
|
||||
using RomRepoMgr.Database;
|
||||
using RomRepoMgr.Settings;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
// Start the application
|
||||
Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().Enrich.FromLogContext().CreateLogger();
|
||||
|
||||
Log.Information("Welcome to ROM Repository Manager!");
|
||||
Log.Information("Copyright © 2020-2025 Natalia Portillo");
|
||||
|
||||
// Configuration and settings
|
||||
// We need the builder now
|
||||
Log.Debug("Creating the builder...");
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Access the full configuration
|
||||
Log.Debug("Creating the configuration reader...");
|
||||
ConfigurationManager config = builder.Configuration;
|
||||
|
||||
string logFile = config["LogFile"] ?? "logs/rom-repo-mgr.log";
|
||||
string defaultLogLevel = config["Logging:LogLevel:Default"] ?? "Information";
|
||||
|
||||
// Parse to LogEventLevel
|
||||
if(!Enum.TryParse(defaultLogLevel, true, out LogEventLevel level))
|
||||
{
|
||||
// Fallback if parsing fails
|
||||
#if DEBUG
|
||||
level = LogEventLevel.Debug;
|
||||
#else
|
||||
level = LogEventLevel.Information;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Now create a logger with the specified log level and log file
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
#if DEBUG
|
||||
.MinimumLevel.Debug()
|
||||
#else
|
||||
.MinimumLevel.Information()
|
||||
#endif
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File(logFile, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: 10 * 1048576)
|
||||
.Enrich.FromLogContext()
|
||||
.CreateLogger();
|
||||
|
||||
// Read the rest of the configuration and settings
|
||||
Log.Debug("Reading configuration settings...");
|
||||
string repoFolder = config["DataFolders:Repository"] ?? "repo";
|
||||
string importRoms = config["DataFolders:ImportRoms"] ?? "incoming";
|
||||
string importDats = config["DataFolders:ImportDats"] ?? "incoming-dats";
|
||||
string exportRoms = config["DataFolders:ExportRoms"] ?? "export";
|
||||
string exportDats = config["DataFolders:ExportDats"] ?? "export-dats";
|
||||
string databaseFolder = config["DataFolders:Database"] ?? "db";
|
||||
string temporaryFolder = config["DataFolders:Temporary"] ?? "tmp";
|
||||
string compressionTypeString = config["CompressionType"] ?? "Zstd";
|
||||
|
||||
// Parse the compression type
|
||||
if(!Enum.TryParse(compressionTypeString, true, out CompressionType compressionType))
|
||||
{
|
||||
// Fallback if parsing fails
|
||||
compressionType = CompressionType.Zstd;
|
||||
}
|
||||
|
||||
// Ensure the folders exist
|
||||
Log.Information("Ensuring folders exist...");
|
||||
|
||||
string[] folders = [repoFolder, importRoms, importDats, databaseFolder, exportRoms, exportDats, temporaryFolder];
|
||||
|
||||
foreach(string folder in folders)
|
||||
{
|
||||
// Check File.Exists for symlinks or junctions
|
||||
if(!Directory.Exists(folder) && !File.Exists(folder))
|
||||
{
|
||||
Log.Debug("Creating folder: {Folder}", folder);
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
else
|
||||
Log.Debug("Folder already exists: {Folder}", folder);
|
||||
}
|
||||
|
||||
// ✅ Plug Serilog into the host
|
||||
builder.Host.UseSerilog();
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||
builder.Services.AddFluentUIComponents();
|
||||
|
||||
// Localization
|
||||
builder.Services.AddLocalization();
|
||||
|
||||
Log.Debug("Creating database context...");
|
||||
|
||||
builder.Services.AddDbContextFactory<Context>(options =>
|
||||
{
|
||||
options.UseSqlite($"Data Source={databaseFolder}/database.db");
|
||||
#if DEBUG
|
||||
options.EnableSensitiveDataLogging();
|
||||
options.LogTo(Log.Debug);
|
||||
#else
|
||||
options.LogTo(Log.Information, LogLevel.Information);
|
||||
#endif
|
||||
});
|
||||
|
||||
builder.Services.AddDataGridEntityFrameworkAdapter();
|
||||
|
||||
Log.Debug("Setting the settings...");
|
||||
|
||||
Settings.Current = new SetSettings
|
||||
{
|
||||
DatabasePath = Path.Combine(Environment.CurrentDirectory, databaseFolder, "database.db"),
|
||||
TemporaryFolder = Path.Combine(Environment.CurrentDirectory, temporaryFolder),
|
||||
RepositoryPath = Path.Combine(Environment.CurrentDirectory, repoFolder),
|
||||
UseInternalDecompressor = true,
|
||||
Compression = compressionType
|
||||
};
|
||||
|
||||
Log.Debug("Building the application...");
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if(!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", true);
|
||||
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
|
||||
|
||||
// Localization
|
||||
string[] supportedCultures = ["en", "es"];
|
||||
|
||||
RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions().SetDefaultCulture("en")
|
||||
.AddSupportedCultures(supportedCultures)
|
||||
.AddSupportedUICultures(supportedCultures);
|
||||
|
||||
app.UseRequestLocalization(localizationOptions);
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
|
||||
using(IServiceScope scope = app.Services.CreateScope())
|
||||
{
|
||||
IServiceProvider services = scope.ServiceProvider;
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information("Updating the database...");
|
||||
stopwatch.Start();
|
||||
Context dbContext = services.GetRequiredService<Context>();
|
||||
await dbContext.Database.MigrateAsync();
|
||||
stopwatch.Stop();
|
||||
Log.Debug("Database migration: {Elapsed} seconds", stopwatch.Elapsed.TotalSeconds);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, "An error occurred while updating the database");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Debug("Running the application...");
|
||||
app.Run();
|
||||
23
RomRepoMgr.Blazor/Properties/launchSettings.json
Normal file
23
RomRepoMgr.Blazor/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5079",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7057;http://localhost:5079",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
228
RomRepoMgr.Blazor/Resources/Localization.Designer.cs
generated
Normal file
228
RomRepoMgr.Blazor/Resources/Localization.Designer.cs
generated
Normal file
@@ -0,0 +1,228 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace RomRepoMgr.Blazor.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Localization {
|
||||
|
||||
private static System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Localization() {
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.Equals(null, resourceMan)) {
|
||||
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("RomRepoMgr.Blazor.Resources.Localization", typeof(Localization).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ROMs_will_be_imported_from {
|
||||
get {
|
||||
return ResourceManager.GetString("ROMs_will_be_imported_from", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RemoveFilesLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RemoveFilesLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string KnownOnlyLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("KnownOnlyLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RecurseArchivesLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RecurseArchivesLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Start {
|
||||
get {
|
||||
return ResourceManager.GetString("Start", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Close {
|
||||
get {
|
||||
return ResourceManager.GetString("Close", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ImportingItem {
|
||||
get {
|
||||
return ResourceManager.GetString("ImportingItem", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ProcessingArchive {
|
||||
get {
|
||||
return ResourceManager.GetString("ProcessingArchive", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Finished {
|
||||
get {
|
||||
return ResourceManager.GetString("Finished", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DATs_will_be_imported_from {
|
||||
get {
|
||||
return ResourceManager.GetString("DATs_will_be_imported_from", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string SearchingForFiles {
|
||||
get {
|
||||
return ResourceManager.GetString("SearchingForFiles", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetNameLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetNameLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetVersionLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetVersionLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetAuthorLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetAuthorLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetCategoryLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetCategoryLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetDateLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetDateLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetDescriptionLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetDescriptionLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetCommentLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetCommentLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string HomepageLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("HomepageLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetTotalMachinesLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetTotalMachinesLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetCompleteMachinesLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetCompleteMachinesLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetIncompleteMachinesLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetIncompleteMachinesLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetTotalRomsLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetTotalRomsLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetHaveRomsLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetHaveRomsLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RomSetMissRomsLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RomSetMissRomsLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ImportDats {
|
||||
get {
|
||||
return ResourceManager.GetString("ImportDats", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ImportRoms {
|
||||
get {
|
||||
return ResourceManager.GetString("ImportRoms", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExportDat {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportDat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RemoveDat {
|
||||
get {
|
||||
return ResourceManager.GetString("RemoveDat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExportRoms {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportRoms", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
RomRepoMgr.Blazor/Resources/Localization.es.resx
Normal file
108
RomRepoMgr.Blazor/Resources/Localization.es.resx
Normal file
@@ -0,0 +1,108 @@
|
||||
<root>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="ROMs_will_be_imported_from" xml:space="preserve">
|
||||
<value>Las ROMs se importarán de {0}.</value>
|
||||
</data>
|
||||
<data name="DATs_will_be_imported_from" xml:space="preserve">
|
||||
<value>Los ficheros DAT se importarán de {0}.</value>
|
||||
</data>
|
||||
<data name="Close" xml:space="preserve">
|
||||
<value>Cerrar</value>
|
||||
</data>
|
||||
<data name="ExportDat" xml:space="preserve">
|
||||
<value>Exportar DAT</value>
|
||||
</data>
|
||||
<data name="ExportRoms" xml:space="preserve">
|
||||
<value>Exportar ROMs</value>
|
||||
</data>
|
||||
<data name="Finished" xml:space="preserve">
|
||||
<value>Terminado</value>
|
||||
</data>
|
||||
<data name="HomepageLabel" xml:space="preserve">
|
||||
<value>Página web</value>
|
||||
</data>
|
||||
<data name="ImportingItem" xml:space="preserve">
|
||||
<value>Importando {0}...</value>
|
||||
</data>
|
||||
<data name="ImportRoms" xml:space="preserve">
|
||||
<value>Importar ROMs</value>
|
||||
</data>
|
||||
<data name="KnownOnlyLabel" xml:space="preserve">
|
||||
<value>Importar solo archivos conocidos.</value>
|
||||
</data>
|
||||
<data name="ProcessingArchive" xml:space="preserve">
|
||||
<value>Procesando archivo: {0}</value>
|
||||
</data>
|
||||
<data name="RecurseArchivesLabel" xml:space="preserve">
|
||||
<value>Intentar detectar archivos comprimiedos e importar su contenido.</value>
|
||||
</data>
|
||||
<data name="RemoveDat" xml:space="preserve">
|
||||
<value>Eliminar DAT</value>
|
||||
</data>
|
||||
<data name="RemoveFilesLabel" xml:space="preserve">
|
||||
<value>Eliminar archivos después de importar satisfactoriamente.</value>
|
||||
</data>
|
||||
<data name="RomSetNameLabel" xml:space="preserve">
|
||||
<value>Nombre</value>
|
||||
</data>
|
||||
<data name="RomSetMissRomsLabel" xml:space="preserve">
|
||||
<value>Faltantes</value>
|
||||
</data>
|
||||
<data name="RomSetIncompleteMachinesLabel" xml:space="preserve">
|
||||
<value>Incompletos</value>
|
||||
</data>
|
||||
<data name="RomSetHaveRomsLabel" xml:space="preserve">
|
||||
<value>Presentes</value>
|
||||
</data>
|
||||
<data name="RomSetDescriptionLabel" xml:space="preserve">
|
||||
<value>Descripción</value>
|
||||
</data>
|
||||
<data name="RomSetDateLabel" xml:space="preserve">
|
||||
<value>Incompletos</value>
|
||||
</data>
|
||||
<data name="RomSetCompleteMachinesLabel" xml:space="preserve">
|
||||
<value>Completos</value>
|
||||
</data>
|
||||
<data name="RomSetCommentLabel" xml:space="preserve">
|
||||
<value>Comentario</value>
|
||||
</data>
|
||||
<data name="RomSetCategoryLabel" xml:space="preserve">
|
||||
<value>Categoría</value>
|
||||
</data>
|
||||
<data name="RomSetAuthorLabel" xml:space="preserve">
|
||||
<value>Categoría</value>
|
||||
</data>
|
||||
<data name="RomSetTotalMachinesLabel" xml:space="preserve">
|
||||
<value>Juegos</value>
|
||||
</data>
|
||||
<data name="RomSetTotalRomsLabel" xml:space="preserve">
|
||||
<value>ROMs</value>
|
||||
</data>
|
||||
<data name="RomSetVersionLabel" xml:space="preserve">
|
||||
<value>Versión</value>
|
||||
</data>
|
||||
<data name="SearchingForFiles" xml:space="preserve">
|
||||
<value>Buscando archivos...</value>
|
||||
</data>
|
||||
<data name="Start" xml:space="preserve">
|
||||
<value>Comenzar</value>
|
||||
</data>
|
||||
<data name="ImportDats" xml:space="preserve">
|
||||
<value>Importar DATs</value>
|
||||
</data>
|
||||
</root>
|
||||
117
RomRepoMgr.Blazor/Resources/Localization.resx
Normal file
117
RomRepoMgr.Blazor/Resources/Localization.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<root>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
|
||||
id="root"
|
||||
xmlns="">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="ROMs_will_be_imported_from" xml:space="preserve">
|
||||
<value>ROM files will be imported from {0}.</value>
|
||||
</data>
|
||||
<data name="RemoveFilesLabel" xml:space="preserve">
|
||||
<value>Remove files after import successful.</value>
|
||||
</data>
|
||||
<data name="KnownOnlyLabel" xml:space="preserve">
|
||||
<value>Only import known files.</value>
|
||||
</data>
|
||||
<data name="RecurseArchivesLabel" xml:space="preserve">
|
||||
<value>Try to detect archives and import their contents.</value>
|
||||
</data>
|
||||
<data name="Start" xml:space="preserve">
|
||||
<value>Start</value>
|
||||
</data>
|
||||
<data name="Close" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
</data>
|
||||
<data name="ImportingItem" xml:space="preserve">
|
||||
<value>Importing {0}...</value>
|
||||
</data>
|
||||
<data name="ProcessingArchive" xml:space="preserve">
|
||||
<value>Processing archive: {0}</value>
|
||||
</data>
|
||||
<data name="Finished" xml:space="preserve">
|
||||
<value>Finished</value>
|
||||
</data>
|
||||
<data name="DATs_will_be_imported_from" xml:space="preserve">
|
||||
<value>DAT files will be imported from {0}.</value>
|
||||
</data>
|
||||
<data name="SearchingForFiles" xml:space="preserve">
|
||||
<value>Searching for files...</value>
|
||||
</data>
|
||||
<data name="RomSetNameLabel" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="RomSetVersionLabel" xml:space="preserve">
|
||||
<value>Version</value>
|
||||
</data>
|
||||
<data name="RomSetAuthorLabel" xml:space="preserve">
|
||||
<value>Author</value>
|
||||
</data>
|
||||
<data name="RomSetCategoryLabel" xml:space="preserve">
|
||||
<value>Category</value>
|
||||
</data>
|
||||
<data name="RomSetDateLabel" xml:space="preserve">
|
||||
<value>Date</value>
|
||||
</data>
|
||||
<data name="RomSetDescriptionLabel" xml:space="preserve">
|
||||
<value>Description</value>
|
||||
</data>
|
||||
<data name="RomSetCommentLabel" xml:space="preserve">
|
||||
<value>Comment</value>
|
||||
</data>
|
||||
<data name="HomepageLabel" xml:space="preserve">
|
||||
<value>Homepage</value>
|
||||
</data>
|
||||
<data name="RomSetTotalMachinesLabel" xml:space="preserve">
|
||||
<value>Games</value>
|
||||
</data>
|
||||
<data name="RomSetCompleteMachinesLabel" xml:space="preserve">
|
||||
<value>Complete</value>
|
||||
</data>
|
||||
<data name="RomSetIncompleteMachinesLabel" xml:space="preserve">
|
||||
<value>Incomplete</value>
|
||||
</data>
|
||||
<data name="RomSetTotalRomsLabel" xml:space="preserve">
|
||||
<value>ROMs</value>
|
||||
</data>
|
||||
<data name="RomSetHaveRomsLabel" xml:space="preserve">
|
||||
<value>Have</value>
|
||||
</data>
|
||||
<data name="RomSetMissRomsLabel" xml:space="preserve">
|
||||
<value>Miss</value>
|
||||
</data>
|
||||
<data name="ImportDats" xml:space="preserve">
|
||||
<value>Import DATs</value>
|
||||
</data>
|
||||
<data name="ImportRoms" xml:space="preserve">
|
||||
<value>Import ROMs</value>
|
||||
</data>
|
||||
<data name="ExportDat" xml:space="preserve">
|
||||
<value>Export DAT</value>
|
||||
</data>
|
||||
<data name="RemoveDat" xml:space="preserve">
|
||||
<value>Remove DAT</value>
|
||||
</data>
|
||||
<data name="ExportRoms" xml:space="preserve">
|
||||
<value>Export ROMs</value>
|
||||
</data>
|
||||
</root>
|
||||
54
RomRepoMgr.Blazor/RomRepoMgr.Blazor.csproj
Normal file
54
RomRepoMgr.Blazor/RomRepoMgr.Blazor.csproj
Normal file
@@ -0,0 +1,54 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0.0-beta.1</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
|
||||
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
|
||||
<ApplicationIcon>romrepomgr.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization"/>
|
||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components"/>
|
||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapter"/>
|
||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons"/>
|
||||
<PackageReference Include="Serilog"/>
|
||||
<PackageReference Include="Serilog.AspNetCore"/>
|
||||
<PackageReference Include="Serilog.Sinks.Console"/>
|
||||
<PackageReference Include="Serilog.Sinks.File"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RomRepoMgr.Core\RomRepoMgr.Core.csproj"/>
|
||||
<ProjectReference Include="..\RomRepoMgr.Database\RomRepoMgr.Database.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\Localization.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Localization.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\Localization.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Localization.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
13
RomRepoMgr.Blazor/appsettings.Development.json
Normal file
13
RomRepoMgr.Blazor/appsettings.Development.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft": "Debug",
|
||||
"Microsoft.Extensions.Localization": "Debug"
|
||||
}
|
||||
},
|
||||
"CircuitOptions": {
|
||||
"DetailedErrors": true
|
||||
}
|
||||
}
|
||||
20
RomRepoMgr.Blazor/appsettings.json
Normal file
20
RomRepoMgr.Blazor/appsettings.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"LogFile": "logs/romrepomgr.log",
|
||||
"AllowedHosts": "*",
|
||||
"DataFolders": {
|
||||
"Repository": "repo",
|
||||
"ImportRoms": "incoming",
|
||||
"ImportDats": "incoming-dats",
|
||||
"ExportRoms": "export",
|
||||
"ExportDats": "export-dats",
|
||||
"Database": "db",
|
||||
"Temporary": "tmp"
|
||||
},
|
||||
"CompressionType": "Zstd"
|
||||
}
|
||||
BIN
RomRepoMgr.Blazor/romrepomgr.ico
Normal file
BIN
RomRepoMgr.Blazor/romrepomgr.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 317 KiB |
191
RomRepoMgr.Blazor/wwwroot/app.css
Normal file
191
RomRepoMgr.Blazor/wwwroot/app.css
Normal file
@@ -0,0 +1,191 @@
|
||||
@import '_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css';
|
||||
|
||||
body {
|
||||
--body-font: "Segoe UI Variable", "Segoe UI", sans-serif;
|
||||
font-family: var(--body-font);
|
||||
font-size: var(--type-ramp-base-font-size);
|
||||
line-height: var(--type-ramp-base-line-height);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navmenu-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: calc(100dvh - 86px);
|
||||
color: var(--neutral-foreground-rest);
|
||||
align-items: stretch !important;
|
||||
}
|
||||
|
||||
.body-content {
|
||||
align-self: stretch;
|
||||
height: calc(100dvh - 86px) !important;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0.5rem 1.5rem;
|
||||
align-self: stretch !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.manage {
|
||||
width: 100dvw;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--neutral-layer-4);
|
||||
color: var(--neutral-foreground-rest);
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--neutral-foreground-rest);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer a:focus {
|
||||
outline: 1px dashed;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border: 1px dashed var(--accent-fill-rest);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::before {
|
||||
content: "An error has occurred. "
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
margin: 20vh auto 1rem auto;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
}
|
||||
|
||||
.loading-progress-text:after {
|
||||
content: var(--blazor-load-percentage-text, "Loading");
|
||||
}
|
||||
|
||||
code {
|
||||
color: #c02d76;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.header-gutters {
|
||||
margin: 0.5rem 3rem 0.5rem 1.5rem !important;
|
||||
}
|
||||
|
||||
[dir="rtl"] .header-gutters {
|
||||
margin: 0.5rem 1.5rem 0.5rem 3rem !important;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex-direction: column !important;
|
||||
row-gap: 0 !important;
|
||||
}
|
||||
|
||||
nav.sitenav {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#main-menu {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#main-menu > div:first-child:is(.expander) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navmenu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#navmenu-toggle {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
#navmenu-toggle ~ nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#navmenu-toggle:checked ~ nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navmenu-icon {
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: unset;
|
||||
right: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
[dir="rtl"] .navmenu-icon {
|
||||
left: 20px;
|
||||
right: unset;
|
||||
}
|
||||
}
|
||||
BIN
RomRepoMgr.Blazor/wwwroot/favicon.ico
Normal file
BIN
RomRepoMgr.Blazor/wwwroot/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -74,17 +74,17 @@ public static class FAT
|
||||
byte bpbSignature;
|
||||
byte fat32Signature;
|
||||
ulong hugeSectors;
|
||||
var fat32Id = new byte[8];
|
||||
var msxId = new byte[6];
|
||||
var dosOem = new byte[8];
|
||||
var atariOem = new byte[6];
|
||||
byte[] fat32Id = new byte[8];
|
||||
byte[] msxId = new byte[6];
|
||||
byte[] dosOem = new byte[8];
|
||||
byte[] atariOem = new byte[6];
|
||||
ushort bootable = 0;
|
||||
|
||||
var bpbSector = new byte[512];
|
||||
var fatSector = new byte[512];
|
||||
byte[] bpbSector = new byte[512];
|
||||
byte[] fatSector = new byte[512];
|
||||
imageStream.Position = 0;
|
||||
imageStream.EnsureRead(bpbSector, 0, 512);
|
||||
imageStream.EnsureRead(fatSector, 0, 512);
|
||||
imageStream.ReadExactly(bpbSector, 0, 512);
|
||||
imageStream.ReadExactly(fatSector, 0, 512);
|
||||
|
||||
Array.Copy(bpbSector, 0x02, atariOem, 0, 6);
|
||||
Array.Copy(bpbSector, 0x03, dosOem, 0, 8);
|
||||
@@ -111,13 +111,13 @@ public static class FAT
|
||||
|
||||
string oemString = Encoding.ASCII.GetString(dosOem);
|
||||
|
||||
var apricotBps = BitConverter.ToUInt16(bpbSector, 0x50);
|
||||
byte apricotSpc = bpbSector[0x52];
|
||||
var apricotReservedSecs = BitConverter.ToUInt16(bpbSector, 0x53);
|
||||
byte apricotFatsNo = bpbSector[0x55];
|
||||
var apricotRootEntries = BitConverter.ToUInt16(bpbSector, 0x56);
|
||||
var apricotSectors = BitConverter.ToUInt16(bpbSector, 0x58);
|
||||
var apricotFatSectors = BitConverter.ToUInt16(bpbSector, 0x5B);
|
||||
ushort apricotBps = BitConverter.ToUInt16(bpbSector, 0x50);
|
||||
byte apricotSpc = bpbSector[0x52];
|
||||
ushort apricotReservedSecs = BitConverter.ToUInt16(bpbSector, 0x53);
|
||||
byte apricotFatsNo = bpbSector[0x55];
|
||||
ushort apricotRootEntries = BitConverter.ToUInt16(bpbSector, 0x56);
|
||||
ushort apricotSectors = BitConverter.ToUInt16(bpbSector, 0x58);
|
||||
ushort apricotFatSectors = BitConverter.ToUInt16(bpbSector, 0x5B);
|
||||
|
||||
bool apricotCorrectSpc = apricotSpc is 1 or 2 or 4 or 8 or 16 or 32 or 64;
|
||||
|
||||
@@ -197,20 +197,20 @@ public static class FAT
|
||||
byte z80Di = bpbSector[0];
|
||||
|
||||
// First FAT1 sector resides at LBA 0x14
|
||||
var fat1Sector0 = new byte[512];
|
||||
byte[] fat1Sector0 = new byte[512];
|
||||
imageStream.Position = 0x14 * 512;
|
||||
imageStream.EnsureRead(fat1Sector0, 0, 512);
|
||||
imageStream.ReadExactly(fat1Sector0, 0, 512);
|
||||
|
||||
// First FAT2 sector resides at LBA 0x1A
|
||||
var fat2Sector0 = new byte[512];
|
||||
byte[] fat2Sector0 = new byte[512];
|
||||
imageStream.Position = 0x1A * 512;
|
||||
imageStream.EnsureRead(fat2Sector0, 0, 512);
|
||||
imageStream.ReadExactly(fat2Sector0, 0, 512);
|
||||
bool equalFatIds = fat1Sector0[0] == fat2Sector0[0] && fat1Sector0[1] == fat2Sector0[1];
|
||||
|
||||
// Volume is software interleaved 2:1
|
||||
var rootMs = new MemoryStream();
|
||||
|
||||
var tmp = new byte[512];
|
||||
byte[] tmp = new byte[512];
|
||||
|
||||
foreach(long position in new long[]
|
||||
{
|
||||
@@ -218,17 +218,17 @@ public static class FAT
|
||||
})
|
||||
{
|
||||
imageStream.Position = position * 512;
|
||||
imageStream.EnsureRead(tmp, 0, 512);
|
||||
imageStream.ReadExactly(tmp, 0, 512);
|
||||
rootMs.Write(tmp, 0, tmp.Length);
|
||||
}
|
||||
|
||||
byte[] rootDir = rootMs.ToArray();
|
||||
var validRootDir = true;
|
||||
bool validRootDir = true;
|
||||
|
||||
// Iterate all root directory
|
||||
for(var e = 0; e < 96 * 32; e += 32)
|
||||
for(int e = 0; e < 96 * 32; e += 32)
|
||||
{
|
||||
for(var c = 0; c < 11; c++)
|
||||
for(int c = 0; c < 11; c++)
|
||||
{
|
||||
if((rootDir[c + e] >= 0x20 || rootDir[c + e] == 0x00 || rootDir[c + e] == 0x05) &&
|
||||
rootDir[c + e] != 0xFF &&
|
||||
|
||||
@@ -38,17 +38,17 @@ public static class Base32
|
||||
var builder = new StringBuilder(bytes.Length * _inByteSize / _outByteSize);
|
||||
|
||||
// Position in the input buffer
|
||||
var bytesPosition = 0;
|
||||
int bytesPosition = 0;
|
||||
|
||||
// Offset inside a single byte that <bytesPosition> points to (from left to right)
|
||||
// 0 - highest bit, 7 - lowest bit
|
||||
var bytesSubPosition = 0;
|
||||
int bytesSubPosition = 0;
|
||||
|
||||
// Byte to look up in the dictionary
|
||||
byte outputBase32Byte = 0;
|
||||
|
||||
// The number of bits filled in the current output byte
|
||||
var outputBase32BytePosition = 0;
|
||||
int outputBase32BytePosition = 0;
|
||||
|
||||
// Iterate through input buffer until we reach past the end of it
|
||||
while(bytesPosition < bytes.Length)
|
||||
@@ -119,22 +119,22 @@ public static class Base32
|
||||
string base32StringUpperCase = base32String.ToUpperInvariant();
|
||||
|
||||
// Prepare output byte array
|
||||
var outputBytes = new byte[base32StringUpperCase.Length * _outByteSize / _inByteSize];
|
||||
byte[] outputBytes = new byte[base32StringUpperCase.Length * _outByteSize / _inByteSize];
|
||||
|
||||
// Check the size
|
||||
if(outputBytes.Length == 0) throw new ArgumentException(Localization.Base32_Not_enought_data);
|
||||
|
||||
// Position in the string
|
||||
var base32Position = 0;
|
||||
int base32Position = 0;
|
||||
|
||||
// Offset inside the character in the string
|
||||
var base32SubPosition = 0;
|
||||
int base32SubPosition = 0;
|
||||
|
||||
// Position within outputBytes array
|
||||
var outputBytePosition = 0;
|
||||
int outputBytePosition = 0;
|
||||
|
||||
// The number of bits filled in the current output byte
|
||||
var outputByteSubPosition = 0;
|
||||
int outputByteSubPosition = 0;
|
||||
|
||||
// Normally we would iterate on the input array but in this case we actually iterate on the output array
|
||||
// We do it because output array doesn't have overflow bits, while input does and it will cause output array overflow if we don''t stop in time
|
||||
|
||||
@@ -56,7 +56,7 @@ static class ArmSimd
|
||||
{
|
||||
uint c = crc;
|
||||
|
||||
var bufPos = 0;
|
||||
int bufPos = 0;
|
||||
|
||||
while(len >= 64)
|
||||
{
|
||||
@@ -95,7 +95,7 @@ static class ArmSimd
|
||||
{
|
||||
uint c = crc;
|
||||
|
||||
var bufPos = 0;
|
||||
int bufPos = 0;
|
||||
|
||||
while(len >= 32)
|
||||
{
|
||||
|
||||
@@ -110,9 +110,9 @@ static class Clmul
|
||||
Vector128<uint> xmmCRC1 = Vector128<uint>.Zero;
|
||||
Vector128<uint> xmmCRC2 = Vector128<uint>.Zero;
|
||||
Vector128<uint> xmmCRC3 = Vector128<uint>.Zero;
|
||||
var bufPos = 0;
|
||||
int bufPos = 0;
|
||||
|
||||
var first = true;
|
||||
bool first = true;
|
||||
|
||||
/* fold 512 to 32 step variable declarations for ISO-C90 compat. */
|
||||
var xmmMask = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000);
|
||||
|
||||
@@ -379,15 +379,15 @@ public sealed partial class Crc32Context : IChecksum
|
||||
|
||||
static uint[][] GenerateTable(uint polynomial)
|
||||
{
|
||||
var table = new uint[8][];
|
||||
uint[][] table = new uint[8][];
|
||||
|
||||
for(var i = 0; i < 8; i++) table[i] = new uint[256];
|
||||
for(int i = 0; i < 8; i++) table[i] = new uint[256];
|
||||
|
||||
for(var i = 0; i < 256; i++)
|
||||
for(int i = 0; i < 256; i++)
|
||||
{
|
||||
var entry = (uint)i;
|
||||
uint entry = (uint)i;
|
||||
|
||||
for(var j = 0; j < 8; j++)
|
||||
for(int j = 0; j < 8; j++)
|
||||
{
|
||||
if((entry & 1) == 1)
|
||||
entry = entry >> 1 ^ polynomial;
|
||||
@@ -398,9 +398,9 @@ public sealed partial class Crc32Context : IChecksum
|
||||
table[0][i] = entry;
|
||||
}
|
||||
|
||||
for(var slice = 1; slice < 8; slice++)
|
||||
for(int slice = 1; slice < 8; slice++)
|
||||
{
|
||||
for(var i = 0; i < 256; i++)
|
||||
for(int i = 0; i < 256; i++)
|
||||
table[slice][i] = table[slice - 1][i] >> 8 ^ table[0][table[slice - 1][i] & 0xFF];
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ public sealed partial class Crc32Context : IChecksum
|
||||
return;
|
||||
}
|
||||
|
||||
var currentPos = 0;
|
||||
int currentPos = 0;
|
||||
|
||||
if(useIso)
|
||||
{
|
||||
@@ -467,7 +467,7 @@ public sealed partial class Crc32Context : IChecksum
|
||||
{
|
||||
uint one = BitConverter.ToUInt32(data, currentPos) ^ crc;
|
||||
currentPos += 4;
|
||||
var two = BitConverter.ToUInt32(data, currentPos);
|
||||
uint two = BitConverter.ToUInt32(data, currentPos);
|
||||
currentPos += 4;
|
||||
|
||||
crc = table[0][two >> 24 & 0xFF] ^
|
||||
@@ -528,8 +528,8 @@ public sealed partial class Crc32Context : IChecksum
|
||||
|
||||
uint[][] localTable = GenerateTable(polynomial);
|
||||
|
||||
var buffer = new byte[65536];
|
||||
int read = fileStream.EnsureRead(buffer, 0, 65536);
|
||||
byte[] buffer = new byte[65536];
|
||||
int read = fileStream.EnsureRead(buffer, 0, 65536);
|
||||
|
||||
while(read > 0)
|
||||
{
|
||||
@@ -661,7 +661,7 @@ public sealed partial class Crc32Context : IChecksum
|
||||
crc32_free(_nativeContext);
|
||||
}
|
||||
|
||||
for(var i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++)
|
||||
for(int i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++)
|
||||
crc32Output.Append(BigEndianBitConverter.GetBytes(crc)[i].ToString("x2"));
|
||||
|
||||
return crc32Output.ToString();
|
||||
|
||||
@@ -72,7 +72,7 @@ static class Clmul
|
||||
|
||||
internal static ulong Step(ulong crc, byte[] data, uint length)
|
||||
{
|
||||
var bufPos = 16;
|
||||
int bufPos = 16;
|
||||
const ulong k1 = 0xe05dd497ca393ae4;
|
||||
const ulong k2 = 0xdabe95afc7875f40;
|
||||
const ulong mu = 0x9c3e466c172963d5;
|
||||
|
||||
@@ -324,15 +324,15 @@ public sealed partial class Crc64Context : IChecksum
|
||||
|
||||
static ulong[][] GenerateTable(ulong polynomial)
|
||||
{
|
||||
var table = new ulong[8][];
|
||||
ulong[][] table = new ulong[8][];
|
||||
|
||||
for(var i = 0; i < 8; i++) table[i] = new ulong[256];
|
||||
for(int i = 0; i < 8; i++) table[i] = new ulong[256];
|
||||
|
||||
for(var i = 0; i < 256; i++)
|
||||
for(int i = 0; i < 256; i++)
|
||||
{
|
||||
var entry = (ulong)i;
|
||||
ulong entry = (ulong)i;
|
||||
|
||||
for(var j = 0; j < 8; j++)
|
||||
for(int j = 0; j < 8; j++)
|
||||
{
|
||||
if((entry & 1) == 1)
|
||||
entry = entry >> 1 ^ polynomial;
|
||||
@@ -343,9 +343,9 @@ public sealed partial class Crc64Context : IChecksum
|
||||
table[0][i] = entry;
|
||||
}
|
||||
|
||||
for(var slice = 1; slice < 4; slice++)
|
||||
for(int slice = 1; slice < 4; slice++)
|
||||
{
|
||||
for(var i = 0; i < 256; i++)
|
||||
for(int i = 0; i < 256; i++)
|
||||
table[slice][i] = table[slice - 1][i] >> 8 ^ table[0][table[slice - 1][i] & 0xFF];
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ public sealed partial class Crc64Context : IChecksum
|
||||
return;
|
||||
}
|
||||
|
||||
var dataOff = 0;
|
||||
int dataOff = 0;
|
||||
|
||||
if(useEcma && Pclmulqdq.IsSupported && Sse41.IsSupported && Ssse3.IsSupported && Sse2.IsSupported)
|
||||
{
|
||||
@@ -393,7 +393,7 @@ public sealed partial class Crc64Context : IChecksum
|
||||
|
||||
while(dataOff < limit)
|
||||
{
|
||||
var tmp = (uint)(crc ^ BitConverter.ToUInt32(data, dataOff));
|
||||
uint tmp = (uint)(crc ^ BitConverter.ToUInt32(data, dataOff));
|
||||
dataOff += 4;
|
||||
|
||||
crc = table[3][tmp & 0xFF] ^
|
||||
@@ -449,8 +449,8 @@ public sealed partial class Crc64Context : IChecksum
|
||||
|
||||
ulong[][] localTable = GenerateTable(polynomial);
|
||||
|
||||
var buffer = new byte[65536];
|
||||
int read = fileStream.EnsureRead(buffer, 0, 65536);
|
||||
byte[] buffer = new byte[65536];
|
||||
int read = fileStream.EnsureRead(buffer, 0, 65536);
|
||||
|
||||
while(read > 0)
|
||||
{
|
||||
@@ -582,7 +582,7 @@ public sealed partial class Crc64Context : IChecksum
|
||||
crc64_free(_nativeContext);
|
||||
}
|
||||
|
||||
for(var i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++)
|
||||
for(int i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++)
|
||||
crc64Output.Append(BigEndianBitConverter.GetBytes(crc)[i].ToString("x2"));
|
||||
|
||||
return crc64Output.ToString();
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed class SpamSumContext : IChecksum
|
||||
Bh = new BlockhashContext[NUM_BLOCKHASHES]
|
||||
};
|
||||
|
||||
for(var i = 0; i < NUM_BLOCKHASHES; i++) _self.Bh[i].Digest = new byte[SPAMSUM_LENGTH];
|
||||
for(int i = 0; i < NUM_BLOCKHASHES; i++) _self.Bh[i].Digest = new byte[SPAMSUM_LENGTH];
|
||||
|
||||
_self.Bhstart = 0;
|
||||
_self.Bhend = 1;
|
||||
@@ -240,7 +240,7 @@ public sealed class SpamSumContext : IChecksum
|
||||
var sb = new StringBuilder();
|
||||
uint bi = _self.Bhstart;
|
||||
uint h = roll_sum();
|
||||
var remain = (int)(FUZZY_MAX_RESULT - 1); /* Exclude terminating '\0'. */
|
||||
int remain = (int)(FUZZY_MAX_RESULT - 1); /* Exclude terminating '\0'. */
|
||||
result = new byte[FUZZY_MAX_RESULT];
|
||||
|
||||
/* Verify that our elimination was not overeager. */
|
||||
@@ -423,7 +423,7 @@ public sealed class SpamSumContext : IChecksum
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static string CToString(byte[] cString)
|
||||
{
|
||||
var count = 0;
|
||||
int count = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
// LINQ is six times slower
|
||||
@@ -506,7 +506,7 @@ public sealed class SpamSumContext : IChecksum
|
||||
{
|
||||
_self.TotalSize += len;
|
||||
|
||||
for(var i = 0; i < len; i++) fuzzy_engine_step(data[i]);
|
||||
for(int i = 0; i < len; i++) fuzzy_engine_step(data[i]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -58,7 +58,7 @@ public static class Extensions
|
||||
/// </returns>
|
||||
public static int EnsureRead(this Stream s, byte[] buffer, int offset, int count)
|
||||
{
|
||||
var pos = 0;
|
||||
int pos = 0;
|
||||
int read;
|
||||
|
||||
do
|
||||
|
||||
@@ -509,7 +509,7 @@ public sealed class Fuse : FileSystem
|
||||
{
|
||||
xattr = new byte[hash.Length / 2];
|
||||
|
||||
for(var i = 0; i < xattr.Length; i++)
|
||||
for(int i = 0; i < xattr.Length; i++)
|
||||
{
|
||||
if(hash[i * 2] >= 0x30 && hash[i * 2] <= 0x39)
|
||||
xattr[i] = (byte)((hash[i * 2] - 0x30) * 0x10);
|
||||
@@ -823,8 +823,8 @@ public sealed class Fuse : FileSystem
|
||||
|
||||
public void Umount()
|
||||
{
|
||||
var rnd = new Random();
|
||||
var token = new byte[64];
|
||||
var rnd = new Random();
|
||||
byte[] token = new byte[64];
|
||||
rnd.NextBytes(token);
|
||||
_umountToken = Base32.ToBase32String(token);
|
||||
setxattr(Path.Combine(MountPoint, ".fuse_umount"), _umountToken, IntPtr.Zero, 0, 0);
|
||||
|
||||
@@ -369,7 +369,7 @@ public class Winfsp(Vfs vfs) : FileSystemBase
|
||||
|
||||
if(fileNode is not FileNode { Handle: > 0 } node) return STATUS_INVALID_HANDLE;
|
||||
|
||||
var buf = new byte[length];
|
||||
byte[] buf = new byte[length];
|
||||
|
||||
int ret = vfs.Read(node.Handle, buf, (long)offset);
|
||||
|
||||
@@ -575,10 +575,10 @@ public class Winfsp(Vfs vfs) : FileSystemBase
|
||||
|
||||
if(securityDescriptor == null) return STATUS_SUCCESS;
|
||||
|
||||
var rootSddl = "O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)";
|
||||
string rootSddl = "O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)";
|
||||
|
||||
var rootSecurityDescriptor = new RawSecurityDescriptor(rootSddl);
|
||||
var fileSecurity = new byte[rootSecurityDescriptor.BinaryLength];
|
||||
var rootSecurityDescriptor = new RawSecurityDescriptor(rootSddl);
|
||||
byte[] fileSecurity = new byte[rootSecurityDescriptor.BinaryLength];
|
||||
rootSecurityDescriptor.GetBinaryForm(fileSecurity, 0);
|
||||
securityDescriptor = fileSecurity;
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ internal sealed class ForcedSeekStream<T> : Stream where T : Stream
|
||||
|
||||
do
|
||||
{
|
||||
var buffer = new byte[BUFFER_LEN];
|
||||
byte[] buffer = new byte[BUFFER_LEN];
|
||||
read = _baseStream.Read(buffer, 0, BUFFER_LEN);
|
||||
_backStream.Write(buffer, 0, read);
|
||||
} while(read == BUFFER_LEN);
|
||||
@@ -111,19 +111,19 @@ internal sealed class ForcedSeekStream<T> : Stream where T : Stream
|
||||
|
||||
_backStream.Position = _backStream.Length;
|
||||
long toPosition = position - _backStream.Position;
|
||||
var fullBufferReads = (int)(toPosition / BUFFER_LEN);
|
||||
var restToRead = (int)(toPosition % BUFFER_LEN);
|
||||
int fullBufferReads = (int)(toPosition / BUFFER_LEN);
|
||||
int restToRead = (int)(toPosition % BUFFER_LEN);
|
||||
byte[] buffer;
|
||||
|
||||
for(var i = 0; i < fullBufferReads; i++)
|
||||
for(int i = 0; i < fullBufferReads; i++)
|
||||
{
|
||||
buffer = new byte[BUFFER_LEN];
|
||||
_baseStream.EnsureRead(buffer, 0, BUFFER_LEN);
|
||||
_baseStream.ReadExactly(buffer, 0, BUFFER_LEN);
|
||||
_backStream.Write(buffer, 0, BUFFER_LEN);
|
||||
}
|
||||
|
||||
buffer = new byte[restToRead];
|
||||
_baseStream.EnsureRead(buffer, 0, restToRead);
|
||||
_baseStream.ReadExactly(buffer, 0, restToRead);
|
||||
_backStream.Write(buffer, 0, restToRead);
|
||||
}
|
||||
|
||||
|
||||
@@ -392,5 +392,11 @@ namespace RomRepoMgr.Core.Resources {
|
||||
return ResourceManager.GetString("DatImportSuccess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ImportingFile {
|
||||
get {
|
||||
return ResourceManager.GetString("ImportingFile", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,4 +186,7 @@
|
||||
<data name="AddingMedias" xml:space="preserve">
|
||||
<value>Añadiendo medios...</value>
|
||||
</data>
|
||||
<data name="ImportingFile" xml:space="preserve">
|
||||
<value>Importando fichero...</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -196,4 +196,7 @@
|
||||
<data name="DatImportSuccess" xml:space="preserve">
|
||||
<value>Imported {0} machines with {1} ROMs.</value>
|
||||
</data>
|
||||
<data name="ImportingFile" xml:space="preserve">
|
||||
<value>Importing file...</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,5 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0.0-beta.1</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
|
||||
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aaru.Checksums.Native"/>
|
||||
<PackageReference Include="DotNetZip"/>
|
||||
|
||||
@@ -90,7 +90,7 @@ public sealed class Compression
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
zStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public sealed class Compression
|
||||
Maximum = inFs.Length
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
zStream.Write(buffer, 0, buffer.Length);
|
||||
|
||||
inFs.Close();
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class DatImporter
|
||||
Maximum = machineNames.Count
|
||||
});
|
||||
|
||||
var position = 0;
|
||||
int position = 0;
|
||||
var machines = new Dictionary<string, Machine>();
|
||||
|
||||
foreach(string name in machineNames)
|
||||
@@ -223,29 +223,29 @@ public sealed class DatImporter
|
||||
var disks = new List<Disk>();
|
||||
var medias = new List<Media>();
|
||||
|
||||
var tmpRomCrc32Table = Guid.NewGuid().ToString();
|
||||
var tmpRomMd5Table = Guid.NewGuid().ToString();
|
||||
var tmpRomSha1Table = Guid.NewGuid().ToString();
|
||||
var tmpRomSha256Table = Guid.NewGuid().ToString();
|
||||
var tmpRomSha384Table = Guid.NewGuid().ToString();
|
||||
var tmpRomSha512Table = Guid.NewGuid().ToString();
|
||||
var tmpDiskMd5Table = Guid.NewGuid().ToString();
|
||||
var tmpDiskSha1Table = Guid.NewGuid().ToString();
|
||||
var tmpMediaMd5Table = Guid.NewGuid().ToString();
|
||||
var tmpMediaSha1Table = Guid.NewGuid().ToString();
|
||||
var tmpMediaSha256Table = Guid.NewGuid().ToString();
|
||||
string tmpRomCrc32Table = Guid.NewGuid().ToString();
|
||||
string tmpRomMd5Table = Guid.NewGuid().ToString();
|
||||
string tmpRomSha1Table = Guid.NewGuid().ToString();
|
||||
string tmpRomSha256Table = Guid.NewGuid().ToString();
|
||||
string tmpRomSha384Table = Guid.NewGuid().ToString();
|
||||
string tmpRomSha512Table = Guid.NewGuid().ToString();
|
||||
string tmpDiskMd5Table = Guid.NewGuid().ToString();
|
||||
string tmpDiskSha1Table = Guid.NewGuid().ToString();
|
||||
string tmpMediaMd5Table = Guid.NewGuid().ToString();
|
||||
string tmpMediaSha1Table = Guid.NewGuid().ToString();
|
||||
string tmpMediaSha256Table = Guid.NewGuid().ToString();
|
||||
|
||||
var romsHaveCrc = false;
|
||||
var romsHaveMd5 = false;
|
||||
var romsHaveSha1 = false;
|
||||
var romsHaveSha256 = false;
|
||||
var romsHaveSha384 = false;
|
||||
var romsHaveSha512 = false;
|
||||
var disksHaveMd5 = false;
|
||||
var disksHaveSha1 = false;
|
||||
var mediasHaveMd5 = false;
|
||||
var mediasHaveSha1 = false;
|
||||
var mediasHaveSha256 = false;
|
||||
bool romsHaveCrc = false;
|
||||
bool romsHaveMd5 = false;
|
||||
bool romsHaveSha1 = false;
|
||||
bool romsHaveSha256 = false;
|
||||
bool romsHaveSha384 = false;
|
||||
bool romsHaveSha512 = false;
|
||||
bool disksHaveMd5 = false;
|
||||
bool disksHaveSha1 = false;
|
||||
bool mediasHaveMd5 = false;
|
||||
bool mediasHaveSha1 = false;
|
||||
bool mediasHaveSha256 = false;
|
||||
|
||||
DbConnection dbConnection = ctx.Database.GetDbConnection();
|
||||
dbConnection.Open();
|
||||
@@ -626,7 +626,7 @@ public sealed class DatImporter
|
||||
|
||||
foreach(Rom rom in roms)
|
||||
{
|
||||
var hashCollision = false;
|
||||
bool hashCollision = false;
|
||||
|
||||
SetProgress?.Invoke(this,
|
||||
new ProgressEventArgs
|
||||
@@ -648,7 +648,7 @@ public sealed class DatImporter
|
||||
return;
|
||||
}
|
||||
|
||||
var uSize = (ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey);
|
||||
ulong uSize = (ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey);
|
||||
|
||||
DbFile file = null;
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ public class FileExporter(long romSetId, string outPath, ILoggerFactory loggerFa
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
outFs.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ public class FileExporter(long romSetId, string outPath, ILoggerFactory loggerFa
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
outFs.Write(buffer, 0, buffer.Length);
|
||||
|
||||
inFs.Close();
|
||||
@@ -464,7 +464,7 @@ public class FileExporter(long romSetId, string outPath, ILoggerFactory loggerFa
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
outFs.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ public class FileExporter(long romSetId, string outPath, ILoggerFactory loggerFa
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
outFs.Write(buffer, 0, buffer.Length);
|
||||
|
||||
inFs.Close();
|
||||
|
||||
@@ -15,13 +15,15 @@ using RomRepoMgr.Core.Models;
|
||||
using RomRepoMgr.Core.Resources;
|
||||
using RomRepoMgr.Database;
|
||||
using RomRepoMgr.Database.Models;
|
||||
using RomRepoMgr.Settings;
|
||||
using SabreTools.FileTypes.Aaru;
|
||||
using SabreTools.FileTypes.CHD;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using SharpCompress.Readers;
|
||||
using ZstdSharp;
|
||||
using ZstdSharp.Unsafe;
|
||||
using CompressionMode = SharpCompress.Compressors.CompressionMode;
|
||||
using CompressionType = RomRepoMgr.Settings.CompressionType;
|
||||
|
||||
namespace RomRepoMgr.Core.Workers;
|
||||
|
||||
@@ -84,6 +86,77 @@ public sealed class FileImporter
|
||||
Finished?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SeparateFilesAndArchivesManaged()
|
||||
{
|
||||
SetProgressBounds?.Invoke(this,
|
||||
new ProgressBoundsEventArgs
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = Files.Count
|
||||
});
|
||||
|
||||
ConcurrentBag<string> files = [];
|
||||
ConcurrentBag<string> archives = [];
|
||||
|
||||
Parallel.ForEach(Files,
|
||||
file =>
|
||||
{
|
||||
SetProgress?.Invoke(this,
|
||||
new ProgressEventArgs
|
||||
{
|
||||
Value = _position
|
||||
});
|
||||
|
||||
SetMessage?.Invoke(this,
|
||||
new MessageEventArgs
|
||||
{
|
||||
Message = "Checking archives. Found " +
|
||||
archives.Count +
|
||||
" archives and " +
|
||||
files.Count +
|
||||
" files."
|
||||
});
|
||||
|
||||
SetMessage2?.Invoke(this,
|
||||
new MessageEventArgs
|
||||
{
|
||||
Message =
|
||||
$"Checking if file {Path.GetFileName(file)} is an archive..."
|
||||
});
|
||||
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(file, FileMode.Open, FileAccess.Read);
|
||||
using IReader reader = ReaderFactory.Open(fs);
|
||||
|
||||
archives.Add(file);
|
||||
}
|
||||
catch(InvalidOperationException)
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _position);
|
||||
});
|
||||
|
||||
Files = files.Order().ToList();
|
||||
Archives = archives.Order().ToList();
|
||||
|
||||
SetMessage?.Invoke(this,
|
||||
new MessageEventArgs
|
||||
{
|
||||
Message = "Finished checking archives. Found " +
|
||||
Archives.Count +
|
||||
" archives and " +
|
||||
Files.Count +
|
||||
" files."
|
||||
});
|
||||
|
||||
Finished?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SeparateFilesAndArchives()
|
||||
{
|
||||
SetProgressBounds?.Invoke(this,
|
||||
@@ -278,11 +351,8 @@ public sealed class FileImporter
|
||||
}
|
||||
}
|
||||
|
||||
public bool ExtractArchive(string archive)
|
||||
public bool ExtractArchiveManaged(string archive)
|
||||
{
|
||||
string archiveFormat = null;
|
||||
long archiveFiles = 0;
|
||||
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
SetMessage2?.Invoke(this,
|
||||
@@ -291,7 +361,60 @@ public sealed class FileImporter
|
||||
Message = Localization.CheckingIfFIleIsAnArchive
|
||||
});
|
||||
|
||||
archiveFormat = GetArchiveFormat(archive, out archiveFiles);
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(archive, FileMode.Open, FileAccess.Read);
|
||||
using IReader reader = ReaderFactory.Open(fs);
|
||||
|
||||
if(!Directory.Exists(Settings.Settings.Current.TemporaryFolder))
|
||||
Directory.CreateDirectory(Settings.Settings.Current.TemporaryFolder);
|
||||
|
||||
_archiveFolder = Path.Combine(Settings.Settings.Current.TemporaryFolder, Path.GetRandomFileName());
|
||||
|
||||
Directory.CreateDirectory(_archiveFolder);
|
||||
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
SetMessage2?.Invoke(this,
|
||||
new MessageEventArgs
|
||||
{
|
||||
Message = Localization.ExtractingArchive
|
||||
});
|
||||
|
||||
reader.WriteAllToDirectory(_archiveFolder,
|
||||
new ExtractionOptions
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
|
||||
Files = Directory.GetFiles(_archiveFolder, "*", SearchOption.AllDirectories).Order().ToList();
|
||||
|
||||
SetMessage2?.Invoke(this,
|
||||
new MessageEventArgs
|
||||
{
|
||||
Message = "Finished extracting files. Extracted " + Files.Count + " files."
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
catch(InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ExtractArchive(string archive)
|
||||
{
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
SetMessage2?.Invoke(this,
|
||||
new MessageEventArgs
|
||||
{
|
||||
Message = Localization.CheckingIfFIleIsAnArchive
|
||||
});
|
||||
|
||||
string archiveFormat = GetArchiveFormat(archive, out long archiveFiles);
|
||||
|
||||
// If a floppy contains only the archive, unar will recognize it, on its skipping of SFXs.
|
||||
if(archiveFormat != null && FAT.Identify(archive)) archiveFormat = null;
|
||||
@@ -417,6 +540,295 @@ public sealed class FileImporter
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCrcInDb(long crc32)
|
||||
{
|
||||
lock(DbLock)
|
||||
{
|
||||
return _ctx.Files.Any(f => f.Crc32 == crc32.ToString("x8"));
|
||||
}
|
||||
}
|
||||
|
||||
public void ImportAndHashRom(Stream stream, string filename, string tempPath, long size)
|
||||
{
|
||||
try
|
||||
{
|
||||
var outFs = new FileStream(tempPath, FileMode.Create, FileAccess.Write);
|
||||
|
||||
SetMessage2?.Invoke(this,
|
||||
new MessageEventArgs
|
||||
{
|
||||
Message = Localization.ImportingFile
|
||||
});
|
||||
|
||||
byte[] buffer;
|
||||
var checksumWorker = new Checksum();
|
||||
Stream zStream;
|
||||
|
||||
switch(Settings.Settings.Current.Compression)
|
||||
{
|
||||
case CompressionType.Zstd:
|
||||
{
|
||||
var zstdStream = new CompressionStream(outFs, 15);
|
||||
zstdStream.SetParameter(ZSTD_cParameter.ZSTD_c_nbWorkers, Environment.ProcessorCount);
|
||||
zStream = zstdStream;
|
||||
|
||||
break;
|
||||
}
|
||||
case CompressionType.None:
|
||||
zStream = outFs;
|
||||
|
||||
break;
|
||||
default:
|
||||
zStream = new LZipStream(outFs, CompressionMode.Compress);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if(size > BUFFER_SIZE)
|
||||
{
|
||||
SetProgressBounds2?.Invoke(this,
|
||||
new ProgressBoundsEventArgs
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = size
|
||||
});
|
||||
|
||||
long offset;
|
||||
long remainder = size % BUFFER_SIZE;
|
||||
|
||||
for(offset = 0; offset < size - remainder; offset += (int)BUFFER_SIZE)
|
||||
{
|
||||
SetProgress2?.Invoke(this,
|
||||
new ProgressEventArgs
|
||||
{
|
||||
Value = offset
|
||||
});
|
||||
|
||||
buffer = new byte[BUFFER_SIZE];
|
||||
stream.ReadExactly(buffer, 0, (int)BUFFER_SIZE);
|
||||
checksumWorker.Update(buffer);
|
||||
zStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
SetProgress2?.Invoke(this,
|
||||
new ProgressEventArgs
|
||||
{
|
||||
Value = offset
|
||||
});
|
||||
|
||||
buffer = new byte[remainder];
|
||||
stream.ReadExactly(buffer, 0, (int)remainder);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
buffer = new byte[size];
|
||||
stream.ReadExactly(buffer, 0, (int)size);
|
||||
}
|
||||
|
||||
checksumWorker.Update(buffer);
|
||||
zStream.Write(buffer, 0, buffer.Length);
|
||||
|
||||
Dictionary<ChecksumType, string> checksums = checksumWorker.End();
|
||||
|
||||
ulong uSize = (ulong)size;
|
||||
bool fileInDb = true;
|
||||
|
||||
bool knownFile = _pendingFiles.TryGetValue(checksums[ChecksumType.Sha512], out DbFile dbFile);
|
||||
|
||||
lock(DbLock)
|
||||
{
|
||||
dbFile ??= _ctx.Files.FirstOrDefault(f => (f.Sha512 == checksums[ChecksumType.Sha512] ||
|
||||
f.Sha384 == checksums[ChecksumType.Sha384] ||
|
||||
f.Sha256 == checksums[ChecksumType.Sha256] ||
|
||||
f.Sha1 == checksums[ChecksumType.Sha1] ||
|
||||
f.Md5 == checksums[ChecksumType.Md5] ||
|
||||
f.Crc32 == checksums[ChecksumType.Crc32]) &&
|
||||
f.Size == uSize);
|
||||
}
|
||||
|
||||
if(dbFile == null)
|
||||
{
|
||||
if(onlyKnown)
|
||||
{
|
||||
ImportedRom?.Invoke(this,
|
||||
new ImportedRomItemEventArgs
|
||||
{
|
||||
Item = new ImportRomItem
|
||||
{
|
||||
Filename = filename,
|
||||
Status = Localization.UnknownFile
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dbFile = new DbFile
|
||||
{
|
||||
Crc32 = checksums[ChecksumType.Crc32],
|
||||
Md5 = checksums[ChecksumType.Md5],
|
||||
Sha1 = checksums[ChecksumType.Sha1],
|
||||
Sha256 = checksums[ChecksumType.Sha256],
|
||||
Sha384 = checksums[ChecksumType.Sha384],
|
||||
Sha512 = checksums[ChecksumType.Sha512],
|
||||
Size = uSize,
|
||||
CreatedOn = DateTime.UtcNow,
|
||||
UpdatedOn = DateTime.UtcNow,
|
||||
OriginalFileName = Path.GetFileName(filename)
|
||||
};
|
||||
|
||||
fileInDb = false;
|
||||
}
|
||||
|
||||
if(!knownFile) _pendingFiles[checksums[ChecksumType.Sha512]] = dbFile;
|
||||
|
||||
byte[] sha384Bytes = new byte[48];
|
||||
string sha384 = checksums[ChecksumType.Sha384];
|
||||
|
||||
for(int i = 0; i < 48; i++)
|
||||
{
|
||||
if(sha384[i * 2] >= 0x30 && sha384[i * 2] <= 0x39)
|
||||
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x30) * 0x10);
|
||||
else if(sha384[i * 2] >= 0x41 && sha384[i * 2] <= 0x46)
|
||||
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x37) * 0x10);
|
||||
else if(sha384[i * 2] >= 0x61 && sha384[i * 2] <= 0x66)
|
||||
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x57) * 0x10);
|
||||
|
||||
if(sha384[i * 2 + 1] >= 0x30 && sha384[i * 2 + 1] <= 0x39)
|
||||
sha384Bytes[i] += (byte)(sha384[i * 2 + 1] - 0x30);
|
||||
else if(sha384[i * 2 + 1] >= 0x41 && sha384[i * 2 + 1] <= 0x46)
|
||||
sha384Bytes[i] += (byte)(sha384[i * 2 + 1] - 0x37);
|
||||
else if(sha384[i * 2 + 1] >= 0x61 && sha384[i * 2 + 1] <= 0x66)
|
||||
sha384Bytes[i] += (byte)(sha384[i * 2 + 1] - 0x57);
|
||||
}
|
||||
|
||||
string sha384B32 = Base32.ToBase32String(sha384Bytes);
|
||||
|
||||
string repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath,
|
||||
"files",
|
||||
sha384B32[0].ToString(),
|
||||
sha384B32[1].ToString(),
|
||||
sha384B32[2].ToString(),
|
||||
sha384B32[3].ToString(),
|
||||
sha384B32[4].ToString());
|
||||
|
||||
if(!Directory.Exists(repoPath)) Directory.CreateDirectory(repoPath);
|
||||
|
||||
repoPath = Settings.Settings.Current.Compression switch
|
||||
{
|
||||
CompressionType.Zstd => Path.Combine(repoPath, sha384B32 + ".zst"),
|
||||
CompressionType.None => Path.Combine(repoPath, sha384B32),
|
||||
_ => Path.Combine(repoPath, sha384B32 + ".lz")
|
||||
};
|
||||
|
||||
if(dbFile.Crc32 == null)
|
||||
{
|
||||
dbFile.Crc32 = checksums[ChecksumType.Crc32];
|
||||
dbFile.UpdatedOn = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if(dbFile.Md5 == null)
|
||||
{
|
||||
dbFile.Md5 = checksums[ChecksumType.Md5];
|
||||
dbFile.UpdatedOn = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if(dbFile.Sha1 == null)
|
||||
{
|
||||
dbFile.Sha1 = checksums[ChecksumType.Sha1];
|
||||
dbFile.UpdatedOn = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if(dbFile.Sha256 == null)
|
||||
{
|
||||
dbFile.Sha256 = checksums[ChecksumType.Sha256];
|
||||
dbFile.UpdatedOn = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if(dbFile.Sha384 == null)
|
||||
{
|
||||
dbFile.Sha384 = checksums[ChecksumType.Sha384];
|
||||
dbFile.UpdatedOn = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if(dbFile.Sha512 == null)
|
||||
{
|
||||
dbFile.Sha512 = checksums[ChecksumType.Sha512];
|
||||
dbFile.UpdatedOn = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if(File.Exists(repoPath))
|
||||
{
|
||||
dbFile.IsInRepo = true;
|
||||
dbFile.UpdatedOn = DateTime.UtcNow;
|
||||
|
||||
if(!fileInDb) _newFiles.Add(dbFile);
|
||||
|
||||
zStream.Close();
|
||||
outFs.Dispose();
|
||||
|
||||
File.Delete(tempPath);
|
||||
|
||||
ImportedRom?.Invoke(this,
|
||||
new ImportedRomItemEventArgs
|
||||
{
|
||||
Item = new ImportRomItem
|
||||
{
|
||||
Filename = filename,
|
||||
Status = Localization.OK
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
SetMessage2?.Invoke(this,
|
||||
new MessageEventArgs
|
||||
{
|
||||
Message = Localization.Finishing
|
||||
});
|
||||
|
||||
zStream.Close();
|
||||
outFs.Dispose();
|
||||
|
||||
File.Move(tempPath, repoPath, true);
|
||||
|
||||
dbFile.IsInRepo = true;
|
||||
dbFile.UpdatedOn = DateTime.UtcNow;
|
||||
|
||||
if(!fileInDb) _newFiles.Add(dbFile);
|
||||
|
||||
ImportedRom?.Invoke(this,
|
||||
new ImportedRomItemEventArgs
|
||||
{
|
||||
Item = new ImportRomItem
|
||||
{
|
||||
Filename = filename,
|
||||
Status = Localization.OK
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
ImportedRom?.Invoke(this,
|
||||
new ImportedRomItemEventArgs
|
||||
{
|
||||
Item = new ImportRomItem
|
||||
{
|
||||
Filename = filename,
|
||||
Status = Localization.UnhandledExceptionWhenImporting
|
||||
}
|
||||
});
|
||||
|
||||
#pragma warning disable ERP022
|
||||
}
|
||||
#pragma warning restore ERP022
|
||||
}
|
||||
|
||||
bool ImportRom(string path)
|
||||
{
|
||||
try
|
||||
@@ -454,7 +866,7 @@ public sealed class FileImporter
|
||||
});
|
||||
|
||||
buffer = new byte[BUFFER_SIZE];
|
||||
inFs.EnsureRead(buffer, 0, (int)BUFFER_SIZE);
|
||||
inFs.ReadExactly(buffer, 0, (int)BUFFER_SIZE);
|
||||
checksumWorker.Update(buffer);
|
||||
}
|
||||
|
||||
@@ -465,13 +877,13 @@ public sealed class FileImporter
|
||||
});
|
||||
|
||||
buffer = new byte[remainder];
|
||||
inFs.EnsureRead(buffer, 0, (int)remainder);
|
||||
inFs.ReadExactly(buffer, 0, (int)remainder);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
buffer = new byte[inFs.Length];
|
||||
inFs.EnsureRead(buffer, 0, (int)inFs.Length);
|
||||
inFs.ReadExactly(buffer, 0, (int)inFs.Length);
|
||||
}
|
||||
|
||||
checksumWorker.Update(buffer);
|
||||
@@ -660,7 +1072,7 @@ public sealed class FileImporter
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
zStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
@@ -672,7 +1084,7 @@ public sealed class FileImporter
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
zStream.Write(buffer, 0, buffer.Length);
|
||||
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
@@ -696,7 +1108,7 @@ public sealed class FileImporter
|
||||
|
||||
return true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch(Exception)
|
||||
{
|
||||
_lastMessage = Localization.UnhandledExceptionWhenImporting;
|
||||
|
||||
@@ -940,7 +1352,7 @@ public sealed class FileImporter
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
outFs.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
@@ -952,7 +1364,7 @@ public sealed class FileImporter
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
outFs.Write(buffer, 0, buffer.Length);
|
||||
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
@@ -1282,7 +1694,7 @@ public sealed class FileImporter
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
outFs.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
@@ -1294,7 +1706,7 @@ public sealed class FileImporter
|
||||
Value = inFs.Position
|
||||
});
|
||||
|
||||
inFs.EnsureRead(buffer, 0, buffer.Length);
|
||||
inFs.ReadExactly(buffer, 0, buffer.Length);
|
||||
outFs.Write(buffer, 0, buffer.Length);
|
||||
|
||||
SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Version>1.0.0-beta.1</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
|
||||
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console"/>
|
||||
<PackageReference Include="AsyncFixer"/>
|
||||
<PackageReference Include="ErrorProne.NET.CoreAnalyzers"/>
|
||||
|
||||
@@ -232,7 +232,7 @@ public static class DetectOS
|
||||
/// <returns>Current operating system version</returns>
|
||||
public static string GetVersion()
|
||||
{
|
||||
var environ = Environment.OSVersion.Version.ToString();
|
||||
string environ = Environment.OSVersion.Version.ToString();
|
||||
|
||||
switch(GetRealPlatformID())
|
||||
{
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Version>1.0.0-beta.1</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
|
||||
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry"/>
|
||||
|
||||
@@ -42,11 +42,12 @@ public enum CompressionType
|
||||
|
||||
public sealed class SetSettings
|
||||
{
|
||||
public string DatabasePath { get; set; }
|
||||
public string RepositoryPath { get; set; }
|
||||
public string TemporaryFolder { get; set; }
|
||||
public string UnArchiverPath { get; set; }
|
||||
public CompressionType Compression { get; set; }
|
||||
public string DatabasePath { get; set; }
|
||||
public string RepositoryPath { get; set; }
|
||||
public string TemporaryFolder { get; set; }
|
||||
public string UnArchiverPath { get; set; }
|
||||
public CompressionType Compression { get; set; }
|
||||
public bool UseInternalDecompressor { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Manages statistics</summary>
|
||||
@@ -56,7 +57,7 @@ public static class Settings
|
||||
const string XDG_CONFIG_HOME_RESOLVED = ".config";
|
||||
/// <summary>Current statistics</summary>
|
||||
public static SetSettings Current;
|
||||
public static bool UnArUsable { get; set; }
|
||||
public static bool CanDecompress { get; set; }
|
||||
|
||||
/// <summary>Loads settings</summary>
|
||||
public static void LoadSettings()
|
||||
@@ -118,6 +119,10 @@ public static class Settings
|
||||
((NSNumber)obj).ToString())
|
||||
: CompressionType.Lzip;
|
||||
|
||||
Current.UseInternalDecompressor =
|
||||
parsedPreferences.TryGetValue("UseInternalDecompressor", out obj) &&
|
||||
((NSNumber)obj).ToBool();
|
||||
|
||||
prefsFs.Close();
|
||||
}
|
||||
else
|
||||
@@ -159,10 +164,11 @@ public static class Settings
|
||||
return;
|
||||
}
|
||||
|
||||
Current.DatabasePath = key.GetValue("DatabasePath") as string;
|
||||
Current.RepositoryPath = key.GetValue("RepositoryPath") as string;
|
||||
Current.TemporaryFolder = key.GetValue("TemporaryFolder") as string;
|
||||
Current.UnArchiverPath = key.GetValue("UnArchiverPath") as string;
|
||||
Current.DatabasePath = key.GetValue("DatabasePath") as string;
|
||||
Current.RepositoryPath = key.GetValue("RepositoryPath") as string;
|
||||
Current.TemporaryFolder = key.GetValue("TemporaryFolder") as string;
|
||||
Current.UnArchiverPath = key.GetValue("UnArchiverPath") as string;
|
||||
Current.UseInternalDecompressor = key.GetValue("UseInternalDecompressor") as int? > 0;
|
||||
|
||||
if(key.GetValue("Compression") is int compression)
|
||||
Current.Compression = (CompressionType)compression;
|
||||
@@ -243,6 +249,9 @@ public static class Settings
|
||||
},
|
||||
{
|
||||
"Compression", (NSNumber)(int)Current.Compression
|
||||
},
|
||||
{
|
||||
"UseInternalDecompressor", new NSNumber(Current.UseInternalDecompressor ? 1 : 0)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -273,11 +282,12 @@ public static class Settings
|
||||
|
||||
RegistryKey key = parentKey?.CreateSubKey("RomRepoMgr");
|
||||
|
||||
key?.SetValue("DatabasePath", Current.DatabasePath);
|
||||
key?.SetValue("RepositoryPath", Current.RepositoryPath);
|
||||
key?.SetValue("TemporaryFolder", Current.TemporaryFolder);
|
||||
key?.SetValue("UnArchiverPath", Current.UnArchiverPath);
|
||||
key?.SetValue("Compression", (int)Current.Compression);
|
||||
key?.SetValue("DatabasePath", Current.DatabasePath);
|
||||
key?.SetValue("RepositoryPath", Current.RepositoryPath);
|
||||
key?.SetValue("TemporaryFolder", Current.TemporaryFolder);
|
||||
key?.SetValue("UnArchiverPath", Current.UnArchiverPath);
|
||||
key?.SetValue("Compression", (int)Current.Compression);
|
||||
key?.SetValue("UseInternalDecompressor", Current.UseInternalDecompressor ? 1 : 0);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -26,12 +26,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
global.json = global.json
|
||||
README.md = README.md
|
||||
logo.png = logo.png
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.DatTools", "SabreTools\SabreTools.DatTools\SabreTools.DatTools.csproj", "{20CC9ED6-9F56-49B5-A265-DC246C424F27}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Reports", "SabreTools\SabreTools.Reports\SabreTools.Reports.csproj", "{E73767A7-0A65-4F89-B149-A520874F7B32}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RomRepoMgr.Blazor", "RomRepoMgr.Blazor\RomRepoMgr.Blazor.csproj", "{30DA0637-76C5-43DE-8203-403AECF5F859}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -78,5 +83,9 @@ Global
|
||||
{E73767A7-0A65-4F89-B149-A520874F7B32}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E73767A7-0A65-4F89-B149-A520874F7B32}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E73767A7-0A65-4F89-B149-A520874F7B32}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{30DA0637-76C5-43DE-8203-403AECF5F859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{30DA0637-76C5-43DE-8203-403AECF5F859}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{30DA0637-76C5-43DE-8203-403AECF5F859}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{30DA0637-76C5-43DE-8203-403AECF5F859}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xml:space="preserve">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Aaru/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Claunia/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=datfiles/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=dlclose/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=HKLM/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=libdl/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lsar/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepo/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepombgrfs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepomgr/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=setxattr/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Umounted/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unar/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Winfsp/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=xattr/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=xattrs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=_0020_007B_000A_0020_0020_0020_0020/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Claunia/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=datfiles/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=dlclose/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=HKLM/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=libdl/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lsar/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepo/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepombgrfs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepomgr/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=setxattr/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Umounted/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unar/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Winfsp/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=xattr/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=xattrs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean
|
||||
x:Key="/Default/UserDictionary/Words/=_0020_007B_000A_0020_0020_0020_0020/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
BIN
RomRepoMgr/Assets/RomRepoMgr.icns
Normal file
BIN
RomRepoMgr/Assets/RomRepoMgr.icns
Normal file
Binary file not shown.
BIN
RomRepoMgr/Assets/romrepomgr.ico
Normal file
BIN
RomRepoMgr/Assets/romrepomgr.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 317 KiB |
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using RomRepoMgr.Core.EventArgs;
|
||||
|
||||
@@ -24,7 +25,7 @@ public partial class DatImporter : ObservableObject
|
||||
public Task Task { get; set; }
|
||||
public bool Running { get; private set; } = true;
|
||||
|
||||
internal void OnErrorOccurred(object sender, ErrorEventArgs e)
|
||||
internal void OnErrorOccurred(object sender, ErrorEventArgs e) => Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
StatusMessage = e.Message;
|
||||
StatusColor = Colors.Red;
|
||||
@@ -33,31 +34,25 @@ public partial class DatImporter : ObservableObject
|
||||
|
||||
Indeterminate = false;
|
||||
Progress = 0;
|
||||
}
|
||||
});
|
||||
|
||||
internal void OnSetIndeterminateProgress(object sender, EventArgs e)
|
||||
{
|
||||
Indeterminate = true;
|
||||
}
|
||||
internal void OnSetIndeterminateProgress(object sender, EventArgs e) =>
|
||||
Dispatcher.UIThread.Post(() => Indeterminate = true);
|
||||
|
||||
internal void OnSetMessage(object sender, MessageEventArgs e)
|
||||
{
|
||||
StatusMessage = e.Message;
|
||||
}
|
||||
internal void OnSetMessage(object sender, MessageEventArgs e) =>
|
||||
Dispatcher.UIThread.Post(() => StatusMessage = e.Message);
|
||||
|
||||
internal void OnSetProgress(object sender, ProgressEventArgs e)
|
||||
{
|
||||
Progress = e.Value;
|
||||
}
|
||||
internal void OnSetProgress(object sender, ProgressEventArgs e) =>
|
||||
Dispatcher.UIThread.Post(() => Progress = e.Value);
|
||||
|
||||
internal void OnSetProgressBounds(object sender, ProgressBoundsEventArgs e)
|
||||
internal void OnSetProgressBounds(object sender, ProgressBoundsEventArgs e) => Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
Indeterminate = false;
|
||||
Maximum = e.Maximum;
|
||||
Minimum = e.Minimum;
|
||||
}
|
||||
});
|
||||
|
||||
internal void OnWorkFinished(object sender, MessageEventArgs e)
|
||||
internal void OnWorkFinished(object sender, MessageEventArgs e) => Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
Indeterminate = false;
|
||||
Maximum = 1;
|
||||
@@ -65,5 +60,5 @@ public partial class DatImporter : ObservableObject
|
||||
Progress = 1;
|
||||
StatusMessage = e.Message;
|
||||
Running = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -49,7 +49,6 @@ internal static class Program
|
||||
try
|
||||
{
|
||||
Log.Information("Starting up");
|
||||
Log.Debug("Testing debug logging");
|
||||
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
12
RomRepoMgr/Resources/Localization.Designer.cs
generated
12
RomRepoMgr/Resources/Localization.Designer.cs
generated
@@ -776,5 +776,17 @@ namespace RomRepoMgr.Resources {
|
||||
return ResourceManager.GetString("CompressionType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string UseInternalDecompressorLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("UseInternalDecompressorLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ProcessingArchive {
|
||||
get {
|
||||
return ResourceManager.GetString("ProcessingArchive", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,4 +384,10 @@ Tardará mucho tiempo...</value>
|
||||
<data name="CompressionType" xml:space="preserve">
|
||||
<value>Compresión</value>
|
||||
</data>
|
||||
<data name="UseInternalDecompressorLabel" xml:space="preserve">
|
||||
<value>Usar decompresor interno (soporta menos formatos)</value>
|
||||
</data>
|
||||
<data name="ProcessingArchive" xml:space="preserve">
|
||||
<value>Procesando archivo: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -392,4 +392,10 @@ This will take a long time...</value>
|
||||
<data name="CompressionType" xml:space="preserve">
|
||||
<value>Compression</value>
|
||||
</data>
|
||||
<data name="UseInternalDecompressorLabel" xml:space="preserve">
|
||||
<value>Use internal decompressor (supports less formats)</value>
|
||||
</data>
|
||||
<data name="ProcessingArchive" xml:space="preserve">
|
||||
<value>Processing archive: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,6 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version>1.0.0-beta.1</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
|
||||
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
|
||||
<ApplicationIcon>Assets/romrepomgr.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="**\*.xaml.cs">
|
||||
@@ -36,6 +48,7 @@
|
||||
<PackageReference Include="Serilog.Sinks.Console"/>
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer"/>
|
||||
<PackageReference Include="Text.Analyzers"/>
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Condition="$(RuntimeIdentifier.StartsWith('linux'))"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RomRepoMgr.Database\RomRepoMgr.Database.csproj"/>
|
||||
@@ -48,4 +61,39 @@
|
||||
<LastGenOutput>Localization.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CreateMacAppBundle" AfterTargets="Publish" Condition="'$(RuntimeIdentifier)' == 'osx-x64' Or '$(RuntimeIdentifier)' == 'osx-arm64'">
|
||||
<!-- Create Directory Structure -->
|
||||
<MakeDir Directories="
|
||||
$(PublishDir)MacPackage/RomRepoMgr.app/Contents/MacOS;
|
||||
$(PublishDir)MacPackage/RomRepoMgr.app/Contents/Resources" />
|
||||
|
||||
<!-- Copy Executable -->
|
||||
<Copy SourceFiles="$(PublishDir)RomRepoMgr" DestinationFiles="$(PublishDir)MacPackage/RomRepoMgr.app/Contents/MacOS/RomRepoMgr" />
|
||||
|
||||
<!-- Copy .dylib files -->
|
||||
<ItemGroup>
|
||||
<DylibFiles Include="$(PublishDir)**\*.dylib" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(DylibFiles)" DestinationFolder="$(PublishDir)MacPackage/RomRepoMgr.app/Contents/MacOS\%(RecursiveDir)" />
|
||||
|
||||
<!-- Copy Icon (optional) -->
|
||||
<Copy SourceFiles="Assets/RomRepoMgr.icns" DestinationFiles="$(PublishDir)MacPackage/RomRepoMgr.app/Contents/Resources/RomRepoMgr.icns" />
|
||||
|
||||
<!-- Generate Info.plist -->
|
||||
<WriteLinesToFile File="$(PublishDir)MacPackage/RomRepoMgr.app/Contents/Info.plist" Lines="
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key><string>RomRepoMgr</string>
|
||||
<key>CFBundleIdentifier</key><string>es.natportillo.romrepomgr</string>
|
||||
<key>CFBundleVersion</key><string>1.0</string>
|
||||
<key>CFBundleExecutable</key><string>RomRepoMgr</string>
|
||||
<key>CFBundleIconFile</key><string>RomRepoMgr.icns</string>
|
||||
<key>NSHumanReadableCopyright</key><string>© 2025 Nat Portillo.</string>
|
||||
</dict>
|
||||
</plist>" Overwrite="true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -14,23 +14,27 @@ public class SerilogSink(LogEventLevel minimumLevel, IList<string>? areas = null
|
||||
public void Log(LogEventLevel level, string area, object? source, string messageTemplate)
|
||||
{
|
||||
if(IsEnabled(level, area))
|
||||
{
|
||||
Serilog.Log.Write(LogLevelToSerilogLevel(level),
|
||||
"[{Area} {Source}] {MessageTemplate}",
|
||||
area,
|
||||
source,
|
||||
messageTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogEventLevel level, string area, object? source, string messageTemplate,
|
||||
params object?[] propertyValues)
|
||||
{
|
||||
if(IsEnabled(level, area))
|
||||
{
|
||||
Serilog.Log.Write(LogLevelToSerilogLevel(level),
|
||||
"[{Area} {Source}] {MessageTemplate}",
|
||||
propertyValues,
|
||||
area,
|
||||
source,
|
||||
messageTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
private static Serilog.Events.LogEventLevel LogLevelToSerilogLevel(LogEventLevel level)
|
||||
|
||||
@@ -95,9 +95,9 @@ public sealed partial class ExportDatViewModel : ViewModelBase
|
||||
ProgressVisible = true;
|
||||
StatusMessage = Localization.DecompressingDat;
|
||||
|
||||
var sha384Bytes = new byte[48];
|
||||
byte[] sha384Bytes = new byte[48];
|
||||
|
||||
for(var i = 0; i < 48; i++)
|
||||
for(int i = 0; i < 48; i++)
|
||||
{
|
||||
if(_datHash[i * 2] >= 0x30 && _datHash[i * 2] <= 0x39)
|
||||
sha384Bytes[i] = (byte)((_datHash[i * 2] - 0x30) * 0x10);
|
||||
|
||||
@@ -154,6 +154,13 @@ public sealed partial class ImportDatFolderViewModel : ViewModelBase
|
||||
if(_workers < Environment.ProcessorCount) Import();
|
||||
};
|
||||
|
||||
worker.ErrorOccurred += (_, _) =>
|
||||
{
|
||||
_workers--;
|
||||
|
||||
if(_workers < Environment.ProcessorCount) Import();
|
||||
};
|
||||
|
||||
Importers.Add(model);
|
||||
|
||||
model.Task = Task.Run(worker.Import);
|
||||
|
||||
@@ -20,6 +20,7 @@ using RomRepoMgr.Models;
|
||||
using RomRepoMgr.Resources;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Logging;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace RomRepoMgr.ViewModels;
|
||||
|
||||
@@ -27,10 +28,11 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
{
|
||||
readonly Context _ctx =
|
||||
Context.Create(Settings.Settings.Current.DatabasePath, new SerilogLoggerFactory(Log.Logger));
|
||||
readonly ConcurrentBag<DbDisk> _newDisks = [];
|
||||
readonly ConcurrentBag<DbFile> _newFiles = [];
|
||||
readonly ConcurrentBag<DbMedia> _newMedias = [];
|
||||
readonly Stopwatch _stopwatch = new();
|
||||
readonly Stopwatch _mainStopwatch = new();
|
||||
readonly ConcurrentBag<DbDisk> _newDisks = [];
|
||||
readonly ConcurrentBag<DbFile> _newFiles = [];
|
||||
readonly ConcurrentBag<DbMedia> _newMedias = [];
|
||||
readonly Stopwatch _stopwatch = new();
|
||||
[ObservableProperty]
|
||||
bool _canChoose;
|
||||
[ObservableProperty]
|
||||
@@ -87,7 +89,7 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
CanClose = true;
|
||||
RemoveFilesChecked = false;
|
||||
KnownOnlyChecked = true;
|
||||
RecurseArchivesChecked = Settings.Settings.UnArUsable;
|
||||
RecurseArchivesChecked = Settings.Settings.CanDecompress;
|
||||
RemoveFilesEnabled = false;
|
||||
CanChoose = true;
|
||||
}
|
||||
@@ -97,7 +99,7 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
public ICommand StartCommand { get; }
|
||||
public Window View { get; init; }
|
||||
|
||||
public bool RecurseArchivesEnabled => Settings.Settings.UnArUsable;
|
||||
public bool RecurseArchivesEnabled => Settings.Settings.CanDecompress;
|
||||
|
||||
public bool RecurseArchivesChecked
|
||||
{
|
||||
@@ -128,6 +130,7 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
IsImporting = true;
|
||||
IsReady = false;
|
||||
CanChoose = false;
|
||||
_mainStopwatch.Start();
|
||||
|
||||
_ = Task.Run(() => _rootImporter.FindFiles(FolderPath));
|
||||
}
|
||||
@@ -184,7 +187,11 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
_rootImporter.SeparateFilesAndArchives();
|
||||
|
||||
if(Settings.Settings.Current.UseInternalDecompressor)
|
||||
_rootImporter.SeparateFilesAndArchivesManaged();
|
||||
else
|
||||
_rootImporter.SeparateFilesAndArchives();
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -259,6 +266,9 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
IsReady = false;
|
||||
IsImporting = false;
|
||||
StatusMessage = Localization.Finished;
|
||||
_mainStopwatch.Stop();
|
||||
|
||||
Log.Debug("Took {TotalSeconds} seconds to import ROMs", _mainStopwatch.Elapsed.TotalSeconds);
|
||||
}
|
||||
|
||||
void ProcessArchives()
|
||||
@@ -291,7 +301,9 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
RemoveFilesChecked);
|
||||
|
||||
// Extract archive
|
||||
bool ret = archiveImporter.ExtractArchive(archive);
|
||||
bool ret = Settings.Settings.Current.UseInternalDecompressor
|
||||
? archiveImporter.ExtractArchiveManaged(archive)
|
||||
: archiveImporter.ExtractArchive(archive);
|
||||
|
||||
if(!ret) return;
|
||||
|
||||
@@ -340,6 +352,97 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
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,
|
||||
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())
|
||||
{
|
||||
if(reader.Entry.IsDirectory) continue;
|
||||
|
||||
if(reader.Entry.Crc == 0 && KnownOnlyChecked) continue;
|
||||
|
||||
if(!archiveImporter.IsCrcInDb(reader.Entry.Crc) && KnownOnlyChecked) 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));
|
||||
|
||||
worker.ImportAndHashRom(reader.OpenEntryStream(),
|
||||
reader.Entry.Key,
|
||||
Path.Combine(Settings.Settings.Current.RepositoryPath,
|
||||
Path.GetFileName(Path.GetTempFileName())),
|
||||
reader.Entry.Size);
|
||||
}
|
||||
}
|
||||
catch(InvalidOperationException) {}
|
||||
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();
|
||||
@@ -350,7 +453,10 @@ public sealed partial class ImportRomFolderViewModel : ViewModelBase
|
||||
|
||||
_rootImporter.Finished -= CheckArchivesFinished;
|
||||
|
||||
ProcessArchives();
|
||||
if(Settings.Settings.Current.UseInternalDecompressor)
|
||||
ProcessArchivesManaged();
|
||||
else
|
||||
ProcessArchives();
|
||||
}
|
||||
|
||||
void SetMessage(object sender, MessageEventArgs e)
|
||||
|
||||
@@ -76,10 +76,10 @@ public sealed partial class RemoveDatViewModel : ViewModelBase
|
||||
|
||||
Dispatcher.UIThread.Post(() => StatusMessage = Localization.RemovingDatFileFromRepo);
|
||||
|
||||
var sha384Bytes = new byte[48];
|
||||
byte[] sha384Bytes = new byte[48];
|
||||
string sha384 = romSet.Sha384;
|
||||
|
||||
for(var i = 0; i < 48; i++)
|
||||
for(int i = 0; i < 48; i++)
|
||||
{
|
||||
if(sha384[i * 2] >= 0x30 && sha384[i * 2] <= 0x39)
|
||||
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x30) * 0x10);
|
||||
|
||||
@@ -65,6 +65,8 @@ public sealed partial class SettingsViewModel : ViewModelBase
|
||||
string _unArPath;
|
||||
[ObservableProperty]
|
||||
string _unArVersion;
|
||||
bool _useInternalDecompressor;
|
||||
bool _useInternalDecompressorChanged;
|
||||
|
||||
// Mock
|
||||
public SettingsViewModel() {}
|
||||
@@ -84,11 +86,12 @@ public sealed partial class SettingsViewModel : ViewModelBase
|
||||
DatabaseCommand = new AsyncRelayCommand(ExecuteDatabaseCommandAsync);
|
||||
SaveCommand = new RelayCommand(ExecuteSaveCommand);
|
||||
|
||||
DatabasePath = Settings.Settings.Current.DatabasePath;
|
||||
RepositoryPath = Settings.Settings.Current.RepositoryPath;
|
||||
TemporaryPath = Settings.Settings.Current.TemporaryFolder;
|
||||
UnArPath = Settings.Settings.Current.UnArchiverPath;
|
||||
Compression = Settings.Settings.Current.Compression;
|
||||
DatabasePath = Settings.Settings.Current.DatabasePath;
|
||||
RepositoryPath = Settings.Settings.Current.RepositoryPath;
|
||||
TemporaryPath = Settings.Settings.Current.TemporaryFolder;
|
||||
UnArPath = Settings.Settings.Current.UnArchiverPath;
|
||||
Compression = Settings.Settings.Current.Compression;
|
||||
UseInternalDecompressor = Settings.Settings.Current.UseInternalDecompressor;
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(UnArPath)) CheckUnAr();
|
||||
}
|
||||
@@ -145,6 +148,16 @@ public sealed partial class SettingsViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseInternalDecompressor
|
||||
{
|
||||
get => _useInternalDecompressor;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _useInternalDecompressor, value);
|
||||
_useInternalDecompressorChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CheckUnAr()
|
||||
{
|
||||
var worker = new Compression();
|
||||
@@ -347,12 +360,19 @@ public sealed partial class SettingsViewModel : ViewModelBase
|
||||
if(_unArChanged)
|
||||
{
|
||||
Settings.Settings.Current.UnArchiverPath = UnArPath;
|
||||
Settings.Settings.UnArUsable = true;
|
||||
Settings.Settings.CanDecompress = true;
|
||||
}
|
||||
|
||||
if(_compressionChanged) Settings.Settings.Current.Compression = Compression;
|
||||
|
||||
if(_databaseChanged || _repositoryChanged || _temporaryChanged || _unArChanged || _compressionChanged)
|
||||
if(_useInternalDecompressorChanged) Settings.Settings.Current.UseInternalDecompressor = UseInternalDecompressor;
|
||||
|
||||
if(_databaseChanged ||
|
||||
_repositoryChanged ||
|
||||
_temporaryChanged ||
|
||||
_unArChanged ||
|
||||
_compressionChanged ||
|
||||
_useInternalDecompressorChanged)
|
||||
Settings.Settings.SaveSettings();
|
||||
|
||||
_view.Close();
|
||||
|
||||
@@ -144,7 +144,9 @@ public sealed partial class SplashWindowViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
var worker = new Compression();
|
||||
Settings.Settings.UnArUsable = worker.CheckUnAr(Settings.Settings.Current.UnArchiverPath);
|
||||
|
||||
Settings.Settings.CanDecompress = worker.CheckUnAr(Settings.Settings.Current.UnArchiverPath) ||
|
||||
Settings.Settings.Current.UseInternalDecompressor;
|
||||
|
||||
Dispatcher.UIThread.Post(LoadDatabase);
|
||||
}
|
||||
|
||||
@@ -40,106 +40,106 @@
|
||||
<Design.DataContext>
|
||||
<vm:AboutViewModel />
|
||||
</Design.DataContext>
|
||||
<Border Padding="15">
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
<Grid Grid.Row="0"
|
||||
ColumnDefinitions="Auto,*">
|
||||
<Border Grid.Column="0"
|
||||
BorderThickness="5">
|
||||
<Image Source="/Assets/avalonia-logo.ico"
|
||||
Width="48"
|
||||
Height="48" />
|
||||
</Border>
|
||||
<Grid Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
RowDefinitions="Auto,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding SoftwareName, Mode=OneWay}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Binding VersionText, Mode=OneWay}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Grid RowDefinitions="Auto,*,Auto"
|
||||
Margin="16"
|
||||
RowSpacing="8">
|
||||
<Grid Grid.Row="0"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="Auto,*">
|
||||
<Image Source="/Assets/avalonia-logo.ico"
|
||||
Width="48"
|
||||
Height="48" />
|
||||
<Grid Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
RowDefinitions="Auto,Auto"
|
||||
RowSpacing="8">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding SoftwareName, Mode=OneWay}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Binding VersionText, Mode=OneWay}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<TabControl Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.AboutLabel}" />
|
||||
</TabItem.Header>
|
||||
<Grid RowDefinitions="Auto,12,Auto,12,Auto,Auto,*">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding SuiteName, Mode=OneWay}" />
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding Copyright, Mode=OneWay}" />
|
||||
<Button Grid.Row="4"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Padding="0"
|
||||
Command="{Binding WebsiteCommand, Mode=OneWay}">
|
||||
<!-- TODO: TextDecorations="Underline" in next Avalonia UI version -->
|
||||
<TextBlock Text="{Binding Website, Mode=OneWay}"
|
||||
Foreground="Blue" />
|
||||
</Button>
|
||||
<Button Grid.Row="5"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Padding="0"
|
||||
Command="{Binding LicenseCommand, Mode=OneWay}">
|
||||
<!-- TODO: TextDecorations="Underline" in next Avalonia UI version -->
|
||||
<TextBlock Text="{x:Static resources:Localization.LicenseLabel}"
|
||||
Foreground="Blue" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.LibrariesLabel}" />
|
||||
</TabItem.Header>
|
||||
<DataGrid ItemsSource="{Binding Assemblies, Mode=OneWay}"
|
||||
HorizontalScrollBarVisibility="Visible">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.AssembliesLibraryText}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Version, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.AssembliesVersionText}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.AuthorsLabel}" />
|
||||
</TabItem.Header>
|
||||
<TextBox IsReadOnly="True"
|
||||
Text="{x:Static resources:Localization.AuthorsText}" />
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
<Button Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<TabControl Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.AboutLabel}" />
|
||||
</TabItem.Header>
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,*"
|
||||
RowSpacing="8">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding SuiteName, Mode=OneWay}" />
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Binding Copyright, Mode=OneWay}" />
|
||||
<Button Grid.Row="2"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Padding="0"
|
||||
BorderThickness="0"
|
||||
Command="{Binding WebsiteCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{Binding Website, Mode=OneWay}"
|
||||
Foreground="Blue"
|
||||
TextDecorations="Underline" />
|
||||
</Button>
|
||||
<Button Grid.Row="3"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Padding="0"
|
||||
BorderThickness="0"
|
||||
Command="{Binding LicenseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.LicenseLabel}"
|
||||
Foreground="Blue"
|
||||
TextDecorations="Underline" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.LibrariesLabel}" />
|
||||
</TabItem.Header>
|
||||
<DataGrid ItemsSource="{Binding Assemblies, Mode=OneWay}"
|
||||
HorizontalScrollBarVisibility="Visible">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.AssembliesLibraryText}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Version, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.AssembliesVersionText}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.AuthorsLabel}" />
|
||||
</TabItem.Header>
|
||||
<TextBox IsReadOnly="True"
|
||||
Text="{x:Static resources:Localization.AuthorsText}" />
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
<Button Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -41,226 +41,212 @@
|
||||
<Design.DataContext>
|
||||
<vm:EditDatViewModel />
|
||||
</Design.DataContext>
|
||||
<Border Padding="15">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
<Grid Grid.Row="0"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetNameLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name, Mode=TwoWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="1"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetVersionLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Version, Mode=TwoWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="2"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetAuthorLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Author, Mode=TwoWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="3"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetCategoryLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Category, Mode=TwoWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="4"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetCommentLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Comment, Mode=TwoWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="5"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetDateLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Date, Mode=TwoWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="6"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetDescriptionLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Description, Mode=TwoWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="7"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.HomepageLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Homepage, Mode=TwoWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.TotalMachinesLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TotalMachines, Mode=OneWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="9"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.CompleteMachinesLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding CompleteMachines, Mode=OneWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="10"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.IncompleteMachinesLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding IncompleteMachines, Mode=OneWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="11"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.TotalRomsLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TotalRoms, Mode=OneWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="12"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.HaveRomsLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding HaveRoms, Mode=OneWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="13"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.MissRomsLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding MissRoms, Mode=OneWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="14"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding SaveCommand, Mode=OneWay}"
|
||||
IsVisible="{Binding Modified, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.SaveLabel}" />
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CancelCommand, Mode=OneWay}"
|
||||
IsVisible="{Binding Modified, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CancelLabel}" />
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}"
|
||||
IsVisible="{Binding !Modified, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"
|
||||
Margin="16"
|
||||
RowSpacing="8">
|
||||
<Grid Grid.Row="0"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetNameLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid Grid.Row="1"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetVersionLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Version, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="2"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetAuthorLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Author, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="3"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetCategoryLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Category, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="4"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetCommentLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Comment, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="5"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetDateLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Date, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="6"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RomSetDescriptionLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Description, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="7"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.HomepageLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Homepage, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="8"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.TotalMachinesLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TotalMachines, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="9"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.CompleteMachinesLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding CompleteMachines, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="10"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.IncompleteMachinesLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding IncompleteMachines, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="11"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.TotalRomsLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TotalRoms, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="12"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.HaveRomsLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding HaveRoms, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="13"
|
||||
ColumnSpacing="8"
|
||||
ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.MissRomsLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding MissRoms, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="14"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding SaveCommand, Mode=OneWay}"
|
||||
IsVisible="{Binding Modified, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.SaveLabel}" />
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CancelCommand, Mode=OneWay}"
|
||||
IsVisible="{Binding Modified, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CancelLabel}" />
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}"
|
||||
IsVisible="{Binding !Modified, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -41,27 +41,27 @@
|
||||
<Design.DataContext>
|
||||
<vm:ExportDatViewModel />
|
||||
</Design.DataContext>
|
||||
<Border Padding="15">
|
||||
<Grid RowDefinitions="Auto,auto,Auto,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="1"
|
||||
IsIndeterminate="True"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding ErrorMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="Red"
|
||||
IsVisible="{Binding ErrorVisible, Mode=OneWay}" />
|
||||
<Button Grid.Row="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClose, Mode=OneWay}"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid RowDefinitions="Auto,auto,Auto,Auto"
|
||||
Margin="16"
|
||||
RowSpacing="8">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="1"
|
||||
IsIndeterminate="True"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding ErrorMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="Red"
|
||||
IsVisible="{Binding ErrorVisible, Mode=OneWay}" />
|
||||
<Button Grid.Row="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClose, Mode=OneWay}"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -41,48 +41,51 @@
|
||||
<Design.DataContext>
|
||||
<vm:ExportRomsViewModel />
|
||||
</Design.DataContext>
|
||||
<Border Padding="15">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,*,Auto">
|
||||
<StackPanel Grid.Row="0"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{x:Static resources:Localization.PathLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding FolderPath, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="2"
|
||||
Minimum="{Binding ProgressMinimum, Mode=OneWay}"
|
||||
Maximum="{Binding ProgressMaximum, Mode=OneWay}"
|
||||
Value="{Binding ProgressValue, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding ProgressIsIndeterminate, Mode=OneWay}"
|
||||
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
|
||||
<StackPanel Grid.Row="3"
|
||||
IsVisible="{Binding Progress2Visible, Mode=OneWay}">
|
||||
<TextBlock Text="{Binding Status2Message, Mode=OneWay}" />
|
||||
<ProgressBar Minimum="{Binding Progress2Minimum, Mode=OneWay}"
|
||||
Maximum="{Binding Progress2Maximum, Mode=OneWay}"
|
||||
Value="{Binding Progress2Value, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding Progress2IsIndeterminate, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="4"
|
||||
IsVisible="{Binding Progress3Visible, Mode=OneWay}">
|
||||
<TextBlock Text="{Binding Status3Message, Mode=OneWay}" />
|
||||
<ProgressBar Minimum="{Binding Progress3Minimum, Mode=OneWay}"
|
||||
Maximum="{Binding Progress3Maximum, Mode=OneWay}"
|
||||
Value="{Binding Progress3Value, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding Progress3IsIndeterminate, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Row="5"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClose, Mode=OneWay}"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,*,Auto"
|
||||
Margin="16"
|
||||
RowSpacing="8">
|
||||
<StackPanel Grid.Row="0"
|
||||
Spacing="8"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{x:Static resources:Localization.PathLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding FolderPath, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="2"
|
||||
Minimum="{Binding ProgressMinimum, Mode=OneWay}"
|
||||
Maximum="{Binding ProgressMaximum, Mode=OneWay}"
|
||||
Value="{Binding ProgressValue, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding ProgressIsIndeterminate, Mode=OneWay}"
|
||||
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
|
||||
<StackPanel Grid.Row="3"
|
||||
Spacing="8"
|
||||
IsVisible="{Binding Progress2Visible, Mode=OneWay}">
|
||||
<TextBlock Text="{Binding Status2Message, Mode=OneWay}" />
|
||||
<ProgressBar Minimum="{Binding Progress2Minimum, Mode=OneWay}"
|
||||
Maximum="{Binding Progress2Maximum, Mode=OneWay}"
|
||||
Value="{Binding Progress2Value, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding Progress2IsIndeterminate, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="4"
|
||||
Spacing="8"
|
||||
IsVisible="{Binding Progress3Visible, Mode=OneWay}">
|
||||
<TextBlock Text="{Binding Status3Message, Mode=OneWay}" />
|
||||
<ProgressBar Minimum="{Binding Progress3Minimum, Mode=OneWay}"
|
||||
Maximum="{Binding Progress3Maximum, Mode=OneWay}"
|
||||
Value="{Binding Progress3Value, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding Progress3IsIndeterminate, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Row="5"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClose, Mode=OneWay}"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -41,30 +41,30 @@
|
||||
<Design.DataContext>
|
||||
<vm:ImportDatViewModel />
|
||||
</Design.DataContext>
|
||||
<Border Padding="15">
|
||||
<Grid RowDefinitions="Auto,auto,Auto,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="1"
|
||||
IsIndeterminate="{Binding IndeterminateProgress, Mode=OneWay}"
|
||||
Maximum="{Binding MaximumValue, Mode=OneWay}"
|
||||
Minimum="{Binding MinimumValue, Mode=OneWay}"
|
||||
Value="{Binding CurrentValue, Mode=OneWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding ErrorMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="Red"
|
||||
IsVisible="{Binding ErrorVisible, Mode=OneWay}" />
|
||||
<Button Grid.Row="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClose, Mode=OneWay}"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid RowDefinitions="Auto,auto,Auto,Auto"
|
||||
Margin="16"
|
||||
RowSpacing="8">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="1"
|
||||
IsIndeterminate="{Binding IndeterminateProgress, Mode=OneWay}"
|
||||
Maximum="{Binding MaximumValue, Mode=OneWay}"
|
||||
Minimum="{Binding MinimumValue, Mode=OneWay}"
|
||||
Value="{Binding CurrentValue, Mode=OneWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding ErrorMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="Red"
|
||||
IsVisible="{Binding ErrorVisible, Mode=OneWay}" />
|
||||
<Button Grid.Row="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClose, Mode=OneWay}"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -10,8 +10,7 @@
|
||||
Height="600"
|
||||
x:Class="RomRepoMgr.Views.ImportDatFolder"
|
||||
Title="{x:Static resources:Localization.ImportDatFolderTitle}"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
CanResize="False">
|
||||
Icon="/Assets/avalonia-logo.ico">
|
||||
<Design.DataContext>
|
||||
<vm:ImportDatFolderViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
Height="768"
|
||||
x:Class="RomRepoMgr.Views.ImportRomFolder"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
CanResize="False"
|
||||
Title="{x:Static resources:Localization.ImportRomFolderTitle}"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Design.DataContext>
|
||||
|
||||
@@ -41,14 +41,14 @@
|
||||
<Design.DataContext>
|
||||
<vm:RemoveDatViewModel />
|
||||
</Design.DataContext>
|
||||
<Border Padding="15">
|
||||
<Grid RowDefinitions="Auto,auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="1"
|
||||
IsIndeterminate="True"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid RowDefinitions="Auto,auto"
|
||||
Margin="16"
|
||||
RowSpacing="8">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="1"
|
||||
IsIndeterminate="True"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -31,7 +31,7 @@
|
||||
xmlns:vm="clr-namespace:RomRepoMgr.ViewModels;assembly=RomRepoMgr"
|
||||
xmlns:resources="clr-namespace:RomRepoMgr.Resources"
|
||||
mc:Ignorable="d"
|
||||
Width="480"
|
||||
Width="540"
|
||||
Height="320"
|
||||
x:Class="RomRepoMgr.Views.SettingsDialog"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
@@ -40,129 +40,129 @@
|
||||
<Design.DataContext>
|
||||
<vm:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
<Border Padding="15">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
<Grid Grid.Row="0"
|
||||
ColumnDefinitions="*,250,Auto">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.DatabaseFileLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding DatabasePath, Mode=TwoWay}"
|
||||
IsReadOnly="True"
|
||||
Padding="5" />
|
||||
<Button Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding DatabaseCommand, Mode=OneWay}"
|
||||
Padding="5">
|
||||
<TextBlock Text="{x:Static resources:Localization.ChooseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1"
|
||||
ColumnDefinitions="*,250,Auto">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RepositoryFolderLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding RepositoryPath, Mode=TwoWay}"
|
||||
IsReadOnly="True"
|
||||
Padding="5" />
|
||||
<Button Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding RepositoryCommand, Mode=OneWay}"
|
||||
Padding="5">
|
||||
<TextBlock Text="{x:Static resources:Localization.ChooseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2"
|
||||
ColumnDefinitions="*,250,Auto">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.TemporaryFolderLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TemporaryPath, Mode=TwoWay}"
|
||||
IsReadOnly="True"
|
||||
Padding="5" />
|
||||
<Button Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding TemporaryCommand, Mode=OneWay}"
|
||||
Padding="5">
|
||||
<TextBlock Text="{x:Static resources:Localization.ChooseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="3"
|
||||
ColumnDefinitions="*,250,Auto">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.UnArPathLabel}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding UnArPath, Mode=TwoWay}"
|
||||
IsReadOnly="True"
|
||||
Padding="5" />
|
||||
<Button Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding UnArCommand, Mode=OneWay}"
|
||||
Padding="5">
|
||||
<TextBlock Text="{x:Static resources:Localization.ChooseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="4"
|
||||
HorizontalAlignment="Left"
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"
|
||||
Margin="16"
|
||||
RowSpacing="8">
|
||||
<Grid Grid.Row="0"
|
||||
ColumnDefinitions="*,250,Auto"
|
||||
ColumnSpacing="8">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding UnArVersion, Mode=OneWay}"
|
||||
Text="{x:Static resources:Localization.DatabaseFileLabel}"
|
||||
FontWeight="Bold" />
|
||||
<Grid Grid.Row="5"
|
||||
ColumnDefinitions="Auto, *">
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.CompressionType}"
|
||||
FontWeight="Bold"
|
||||
Padding="5" />
|
||||
<ComboBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
SelectedItem="{Binding Compression, Mode=TwoWay}"
|
||||
ItemsSource="{Binding CompressionTypes, Mode=OneWay}"
|
||||
Padding="5" />
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="6"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding SaveCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.SaveLabel}" />
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding DatabasePath, Mode=TwoWay}"
|
||||
IsReadOnly="True" />
|
||||
<Button Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding DatabaseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.ChooseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid Grid.Row="1"
|
||||
ColumnDefinitions="*,250,Auto"
|
||||
ColumnSpacing="8">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.RepositoryFolderLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding RepositoryPath, Mode=TwoWay}"
|
||||
IsReadOnly="True" />
|
||||
<Button Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding RepositoryCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.ChooseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2"
|
||||
ColumnDefinitions="*,250,Auto"
|
||||
ColumnSpacing="8">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.TemporaryFolderLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TemporaryPath, Mode=TwoWay}"
|
||||
IsReadOnly="True" />
|
||||
<Button Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding TemporaryCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.ChooseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<CheckBox Grid.Row="3"
|
||||
IsChecked="{Binding UseInternalDecompressor, Mode=TwoWay}">
|
||||
<CheckBox.Content>
|
||||
<TextBlock Text="{x:Static resources:Localization.UseInternalDecompressorLabel}" />
|
||||
</CheckBox.Content>
|
||||
</CheckBox>
|
||||
<Grid Grid.Row="4"
|
||||
ColumnDefinitions="*,250,Auto"
|
||||
ColumnSpacing="8"
|
||||
IsVisible="{Binding !UseInternalDecompressor}">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.UnArPathLabel}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding UnArPath, Mode=TwoWay}"
|
||||
IsReadOnly="True" />
|
||||
<Button Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding UnArCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.ChooseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding UnArVersion, Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
IsVisible="{Binding !UseInternalDecompressor}" />
|
||||
<Grid Grid.Row="6"
|
||||
ColumnDefinitions="Auto, *"
|
||||
ColumnSpacing="8">
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resources:Localization.CompressionType}"
|
||||
FontWeight="Bold" />
|
||||
<ComboBox Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
SelectedItem="{Binding Compression, Mode=TwoWay}"
|
||||
ItemsSource="{Binding CompressionTypes, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="7"
|
||||
Spacing="8"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding SaveCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.SaveLabel}" />
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -11,8 +11,8 @@
|
||||
Title="ROM Repository Manager"
|
||||
SystemDecorations="BorderOnly"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Width="250"
|
||||
Height="175">
|
||||
Width="320"
|
||||
Height="240">
|
||||
<Design.DataContext>
|
||||
<vm:SplashWindowViewModel />
|
||||
</Design.DataContext>
|
||||
@@ -20,12 +20,14 @@
|
||||
<StackPanel HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Margin="5">
|
||||
Margin="16"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding LoadingText, Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top" />
|
||||
<StackPanel HorizontalAlignment="Left"
|
||||
Spacing="8"
|
||||
Orientation="Horizontal">
|
||||
<Image MaxWidth="24"
|
||||
MaxHeight="24"
|
||||
@@ -52,6 +54,7 @@
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Left"
|
||||
Spacing="8"
|
||||
Orientation="Horizontal">
|
||||
<Image MaxWidth="24"
|
||||
MaxHeight="24"
|
||||
@@ -78,6 +81,7 @@
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Left"
|
||||
Spacing="8"
|
||||
Orientation="Horizontal">
|
||||
<Image MaxWidth="24"
|
||||
MaxHeight="24"
|
||||
@@ -104,6 +108,7 @@
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Left"
|
||||
Spacing="8"
|
||||
Orientation="Horizontal">
|
||||
<Image MaxWidth="24"
|
||||
MaxHeight="24"
|
||||
@@ -130,6 +135,7 @@
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Left"
|
||||
Spacing="8"
|
||||
Orientation="Horizontal">
|
||||
<Image MaxWidth="24"
|
||||
MaxHeight="24"
|
||||
|
||||
@@ -33,138 +33,137 @@
|
||||
mc:Ignorable="d"
|
||||
x:Class="RomRepoMgr.Views.UpdateStats"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
CanResize="False"
|
||||
Title="{x:Static resources:Localization.UpdateStatsTitle}"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Design.DataContext>
|
||||
<vm:UpdateStatsViewModel />
|
||||
</Design.DataContext>
|
||||
<Border Padding="15">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="1"
|
||||
IsIndeterminate="{Binding IndeterminateProgress, Mode=OneWay}"
|
||||
Maximum="{Binding MaximumValue, Mode=OneWay}"
|
||||
Minimum="{Binding MinimumValue, Mode=OneWay}"
|
||||
Value="{Binding CurrentValue, Mode=OneWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
|
||||
<DataGrid Grid.Row="2"
|
||||
ItemsSource="{Binding RomSets, Mode=OneWay}"
|
||||
HorizontalScrollBarVisibility="Visible"
|
||||
SelectedItem="{Binding SelectedRomSet, Mode=TwoWay}"
|
||||
CanUserSortColumns="True"
|
||||
CanUserResizeColumns="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetNameLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Version, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetVersionLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Author, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetAuthorLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Category, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetCategoryLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Date, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetDateLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Description, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetDescriptionLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Comment, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetCommentLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Homepage, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.HomepageLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding TotalMachines, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetTotalMachinesLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding CompleteMachines, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetCompleteMachinesLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding IncompleteMachines, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetIncompleteMachinesLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding TotalRoms, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetTotalRomsLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding HaveRoms, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetHaveRomsLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding MissRoms, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetMissRomsLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<Button Grid.Row="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClose, Mode=OneWay}"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto"
|
||||
Margin="16"
|
||||
RowSpacing="8">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding StatusMessage, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" />
|
||||
<ProgressBar Grid.Row="1"
|
||||
IsIndeterminate="{Binding IndeterminateProgress, Mode=OneWay}"
|
||||
Maximum="{Binding MaximumValue, Mode=OneWay}"
|
||||
Minimum="{Binding MinimumValue, Mode=OneWay}"
|
||||
Value="{Binding CurrentValue, Mode=OneWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
|
||||
<DataGrid Grid.Row="2"
|
||||
ItemsSource="{Binding RomSets, Mode=OneWay}"
|
||||
HorizontalScrollBarVisibility="Visible"
|
||||
SelectedItem="{Binding SelectedRomSet, Mode=TwoWay}"
|
||||
CanUserSortColumns="True"
|
||||
CanUserResizeColumns="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetNameLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Version, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetVersionLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Author, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetAuthorLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Category, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetCategoryLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Date, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetDateLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Description, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetDescriptionLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Comment, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetCommentLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding Homepage, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.HomepageLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding TotalMachines, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetTotalMachinesLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding CompleteMachines, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetCompleteMachinesLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding IncompleteMachines, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetIncompleteMachinesLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding TotalRoms, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetTotalRomsLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding HaveRoms, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetHaveRomsLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding MissRoms, Mode=OneWay}"
|
||||
Width="Auto"
|
||||
IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{x:Static resources:Localization.RomSetMissRomsLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<Button Grid.Row="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClose, Mode=OneWay}"
|
||||
Command="{Binding CloseCommand, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1 +1 @@
|
||||
{"projectId":"ad616503-97fa-41cc-8470-2fd4ac56d06f","projectName":"RomRepoMgr"}
|
||||
{"projectId": "ad616503-97fa-41cc-8470-2fd4ac56d06f", "projectName": "RomRepoMgr"}
|
||||
5
global.json
Normal file
5
global.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.301"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user