From cf32f38c0e962d193efc088e34d2bf5df90a90f8 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 24 Jun 2024 10:37:05 -0400 Subject: [PATCH] Add preliminary MPF.CLI --- .github/workflows/build_cli.yml | 50 ++++++++++ CHANGELIST.md | 1 + MPF.CLI/ConsoleLogger.cs | 25 +++++ MPF.CLI/MPF.CLI.csproj | 54 +++++++++++ MPF.CLI/Program.cs | 167 ++++++++++++++++++++++++++++++++ MPF.Check/Program.cs | 2 +- MPF.sln | 6 ++ publish-nix.sh | 71 ++++++++++++++ publish-win.ps1 | 64 ++++++++++++ 9 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build_cli.yml create mode 100644 MPF.CLI/ConsoleLogger.cs create mode 100644 MPF.CLI/MPF.CLI.csproj create mode 100644 MPF.CLI/Program.cs diff --git a/.github/workflows/build_cli.yml b/.github/workflows/build_cli.yml new file mode 100644 index 00000000..7dc74639 --- /dev/null +++ b/.github/workflows/build_cli.yml @@ -0,0 +1,50 @@ +name: MPF CLI + +on: + push: + branches: [ "master" ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + project: [MPF.CLI] + runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64] + framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0] + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c Debug --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}} + + - name: Archive build + run: zip -r ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip ${{ matrix.project }}/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/ + + - name: Upload build + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug + path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip + + - name: Upload to rolling + uses: ncipollo/release-action@v1.14.0 + with: + allowUpdates: True + artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip + body: 'Last built commit: ${{ github.sha }}' + name: 'Rolling Release' + prerelease: True + replacesArtifacts: True + tag: "rolling" + updateOnlyUnreleased: True diff --git a/CHANGELIST.md b/CHANGELIST.md index e529bb6f..3bc78695 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -15,6 +15,7 @@ - Purple - Separate themes into own namespace and files - Seal all theme classes +- Add preliminary MPF.CLI ### 3.2.0 (2024-06-20) diff --git a/MPF.CLI/ConsoleLogger.cs b/MPF.CLI/ConsoleLogger.cs new file mode 100644 index 00000000..0b070c1c --- /dev/null +++ b/MPF.CLI/ConsoleLogger.cs @@ -0,0 +1,25 @@ +using System; +using BinaryObjectScanner; +using MPF.Frontend; + +namespace MPF.CLI +{ + public static class ConsoleLogger + { + /// + /// Simple process counter to write to console + /// + public static void ProgressUpdated(object? sender, ResultEventArgs value) + { + Console.WriteLine(value.Message); + } + + /// + /// Simple process counter to write to console + /// + public static void ProgressUpdated(object? sender, ProtectionProgress value) + { + Console.WriteLine($"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}"); + } + } +} diff --git a/MPF.CLI/MPF.CLI.csproj b/MPF.CLI/MPF.CLI.csproj new file mode 100644 index 00000000..85eefe5d --- /dev/null +++ b/MPF.CLI/MPF.CLI.csproj @@ -0,0 +1,54 @@ + + + + + net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + Exe + false + false + latest + enable + true + true + 3.2.0 + + + MPF CLI + Matt Nadareski;ReignStumble;Jakz + CLI frontend for various dumping programs + Copyright (c) Matt Nadareski 2019-2024 + https://github.com/SabreTools/ + https://github.com/SabreTools/MPF + git + + + + + win-x86;win-x64 + + + win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64 + + + win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64 + + + net6.0;net7.0;net8.0 + + + + + + + + + runtime; compile; build; native; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/MPF.CLI/Program.cs b/MPF.CLI/Program.cs new file mode 100644 index 00000000..b0d6ba45 --- /dev/null +++ b/MPF.CLI/Program.cs @@ -0,0 +1,167 @@ +using System; +using System.IO; +using BinaryObjectScanner; +using MPF.Frontend; +using MPF.Frontend.Tools; +using SabreTools.RedumpLib.Data; +using SabreTools.RedumpLib.Web; + +namespace MPF.CLI +{ + public class Program + { + public static void Main(string[] args) + { + // Try processing the standalone arguments + bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args); + if (standaloneProcessed != false) + { + if (standaloneProcessed == null) + DisplayHelp(); + return; + } + + // Check for the minimum number of arguments + if (args.Length < 5) + { + DisplayHelp("Not enough arguments have been provided, exiting..."); + return; + } + + // Try processing the common arguments + (bool success, MediaType mediaType, RedumpSystem? knownSystem, var error) = OptionsLoader.ProcessCommonArguments(args); + if (!success) + { + DisplayHelp(error); + return; + } + + // Load options from the config file + var options = OptionsLoader.LoadFromConfig(); + if (options.FirstRun) + { + // Application paths + options.AaruPath = "FILL ME IN"; + options.DiscImageCreatorPath = "FILL ME IN"; + options.RedumperPath = "FILL ME IN"; + options.InternalProgram = InternalProgram.NONE; + + // Reset first run + options.FirstRun = false; + OptionsLoader.SaveToConfig(options); + } + + // Validate the internal program + switch (options.InternalProgram) + { + case InternalProgram.Aaru: + if (!File.Exists(options.AaruPath)) + { + DisplayHelp("A path needs to be supplied for Aaru, exiting..."); + return; + } + break; + + case InternalProgram.DiscImageCreator: + if (!File.Exists(options.DiscImageCreatorPath)) + { + DisplayHelp("A path needs to be supplied for DIC, exiting..."); + return; + } + break; + + case InternalProgram.Redumper: + if (!File.Exists(options.RedumperPath)) + { + DisplayHelp("A path needs to be supplied for Redumper, exiting..."); + return; + } + break; + + default: + DisplayHelp($"{options.InternalProgram} is not a supported dumping program, exiting..."); + break; + } + + // Make new Progress objects + var resultProgress = new Progress(); + resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated; + var protectionProgress = new Progress(); + protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated; + + // Validate the supplied credentials +#if NETFRAMEWORK + (bool? _, string? message) = RedumpWebClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty); +#else + (bool? _, string? message) = RedumpHttpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).ConfigureAwait(false).GetAwaiter().GetResult(); +#endif + if (!string.IsNullOrEmpty(message)) + Console.WriteLine(message); + + // Get the explicit output options + string path = args[2].Trim('"'); + string speedStr = args[3].Trim('"'); + if (!int.TryParse(speedStr, out int speed)) + speed = 8; // Reasonable default for most media types + string filepath = args[4].Trim('"'); + + // TODO: Additional arguments get passed to dumping program? + // - Should it be an explicit option, like --params? + // - Should it be that everything after is treated like an addition to default? + // - Should it be that everything after is a replacement of default? + // - Two separate options for replace and add params? + + // Now populate an environment + var drive = Drive.Create(null, path); + var env = new DumpEnvironment(options, filepath, drive, knownSystem, mediaType, options.InternalProgram, parameters: null); + string? paramStr = env.GetFullParameters(speed); + if (string.IsNullOrEmpty(paramStr)) + { + DisplayHelp("No valid environment could be created, exiting..."); + return; + } + env.SetExecutionContext(paramStr); + + // Invoke the dumping program +#if NET40 + var dumpResult = env.Run(resultProgress); +#else + var dumpResult = env.Run(resultProgress).GetAwaiter().GetResult(); +#endif + Console.WriteLine(dumpResult.Message); + if (!dumpResult) + return; + + // Finally, attempt to do the output dance +#if NET40 + var verifyTask = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress); + verifyTask.Wait(); + var verifyResult = verifyTask.Result; +#else + var verifyResult = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult(); +#endif + Console.WriteLine(verifyResult.Message); + } + + /// + /// Display help for MPF.CLI + /// + /// Error string to prefix the help text with + private static void DisplayHelp(string? error = null) + { + if (error != null) + Console.WriteLine(error); + + Console.WriteLine("Usage:"); + Console.WriteLine("MPF.CLI "); + Console.WriteLine(); + Console.WriteLine("Standalone Options:"); + Console.WriteLine("-h, -? Show this help text"); + Console.WriteLine("-lc, --listcodes List supported comment/content site codes"); + Console.WriteLine("-lm, --listmedia List supported media types"); + Console.WriteLine("-ls, --listsystems List supported system types"); + Console.WriteLine("-lp, --listprograms List supported dumping program outputs"); + Console.WriteLine(); + } + } +} diff --git a/MPF.Check/Program.cs b/MPF.Check/Program.cs index f79a4852..68ffecad 100644 --- a/MPF.Check/Program.cs +++ b/MPF.Check/Program.cs @@ -94,7 +94,7 @@ namespace MPF.Check Console.WriteLine(error); Console.WriteLine("Usage:"); - Console.WriteLine("MPF.Check.exe [options] ..."); + Console.WriteLine("MPF.Check [options] ..."); Console.WriteLine(); Console.WriteLine("Standalone Options:"); Console.WriteLine("-h, -? Show this help text"); diff --git a/MPF.sln b/MPF.sln index 604fdb98..35cdd371 100644 --- a/MPF.sln +++ b/MPF.sln @@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPF.Processors", "MPF.Proce EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPF.ExecutionContexts", "MPF.ExecutionContexts\MPF.ExecutionContexts.csproj", "{75609610-CE85-486E-8F42-9525A52B80BF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MPF.CLI", "MPF.CLI\MPF.CLI.csproj", "{A825771B-98AC-44A5-8076-C9AB3D469751}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,6 +70,10 @@ Global {75609610-CE85-486E-8F42-9525A52B80BF}.Debug|Any CPU.Build.0 = Debug|Any CPU {75609610-CE85-486E-8F42-9525A52B80BF}.Release|Any CPU.ActiveCfg = Release|Any CPU {75609610-CE85-486E-8F42-9525A52B80BF}.Release|Any CPU.Build.0 = Release|Any CPU + {A825771B-98AC-44A5-8076-C9AB3D469751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A825771B-98AC-44A5-8076-C9AB3D469751}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A825771B-98AC-44A5-8076-C9AB3D469751}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A825771B-98AC-44A5-8076-C9AB3D469751}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/publish-nix.sh b/publish-nix.sh index 17ee9eeb..a7db4d93 100755 --- a/publish-nix.sh +++ b/publish-nix.sh @@ -117,6 +117,45 @@ if [ $NO_BUILD = false ]; then done done + # Build CLI + for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"; do + for RUNTIME in "${CHECK_RUNTIMES[@]}"; do + # Output the current build + echo "===== Build CLI - $FRAMEWORK, $RUNTIME =====" + + # If we have an invalid combination of framework and runtime + if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then + if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then + echo "Skipped due to invalid combination" + continue + fi + fi + + # If we have Apple silicon but an unsupported framework + if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then + if [ $RUNTIME = "osx-arm64" ]; then + echo "Skipped due to no Apple Silicon support" + continue + fi + fi + + # Only .NET 5 and above can publish to a single file + if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]; then + # Only include Debug if building all + if [ $USE_ALL = true ]; then + dotnet publish MPF.CLI/MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true + fi + dotnet publish MPF.CLI/MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false + else + # Only include Debug if building all + if [ $USE_ALL = true ]; then + dotnet publish MPF.CLI/MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT + fi + dotnet publish MPF.CLI/MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false + fi + done + done + # Build Check for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"; do for RUNTIME in "${CHECK_RUNTIMES[@]}"; do @@ -199,6 +238,38 @@ if [ $NO_ARCHIVE = false ]; then done done + # Create CLI archives + for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"; do + for RUNTIME in "${CHECK_RUNTIMES[@]}"; do + # Output the current build + echo "===== Archive CLI - $FRAMEWORK, $RUNTIME =====" + + # If we have an invalid combination of framework and runtime + if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then + if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then + echo "Skipped due to invalid combination" + continue + fi + fi + + # If we have Apple silicon but an unsupported framework + if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then + if [ $RUNTIME = "osx-arm64" ]; then + echo "Skipped due to no Apple Silicon support" + continue + fi + fi + + # Only include Debug if building all + if [ $USE_ALL = true ]; then + cd $BUILD_FOLDER/MPF.CLI/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/ + zip -r $BUILD_FOLDER/MPF.CLI_${FRAMEWORK}_${RUNTIME}_debug.zip . + fi + cd $BUILD_FOLDER/MPF.CLI/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/ + zip -r $BUILD_FOLDER/MPF.CLI_${FRAMEWORK}_${RUNTIME}_release.zip . + done + done + # Create Check archives for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"; do for RUNTIME in "${CHECK_RUNTIMES[@]}"; do diff --git a/publish-win.ps1 b/publish-win.ps1 index e42192aa..bf5b2f5e 100644 --- a/publish-win.ps1 +++ b/publish-win.ps1 @@ -105,6 +105,42 @@ if (!$NO_BUILD.IsPresent) { } } + # Build CLI + foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) { + foreach ($RUNTIME in $CHECK_RUNTIMES) { + # Output the current build + Write-Host "===== Build CLI - $FRAMEWORK, $RUNTIME =====" + + # If we have an invalid combination of framework and runtime + if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) { + Write-Host "Skipped due to invalid combination" + continue + } + + # If we have Apple silicon but an unsupported framework + if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') { + Write-Host "Skipped due to no Apple Silicon support" + continue + } + + # Only .NET 5 and above can publish to a single file + if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) { + # Only include Debug if building all + if ($USE_ALL.IsPresent) { + dotnet publish MPF.CLI\MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true + } + dotnet publish MPF.CLI\MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false + } + else { + # Only include Debug if building all + if ($USE_ALL.IsPresent) { + dotnet publish MPF.CLI\MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT + } + dotnet publish MPF.CLI\MPF.CLI.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false + } + } + } + # Build Check foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) { foreach ($RUNTIME in $CHECK_RUNTIMES) { @@ -183,6 +219,34 @@ if (!$NO_ARCHIVE.IsPresent) { } } + # Create CLI archives + foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) { + foreach ($RUNTIME in $CHECK_RUNTIMES) { + # Output the current build + Write-Host "===== Archive CLI - $FRAMEWORK, $RUNTIME =====" + + # If we have an invalid combination of framework and runtime + if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) { + Write-Host "Skipped due to invalid combination" + continue + } + + # If we have Apple silicon but an unsupported framework + if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') { + Write-Host "Skipped due to no Apple Silicon support" + continue + } + + # Only include Debug if building all + if ($USE_ALL.IsPresent) { + Set-Location -Path $BUILD_FOLDER\MPF.CLI\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\ + 7z a -tzip $BUILD_FOLDER\MPF.CLI_${FRAMEWORK}_${RUNTIME}_debug.zip * + } + Set-Location -Path $BUILD_FOLDER\MPF.CLI\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\ + 7z a -tzip $BUILD_FOLDER\MPF.CLI_${FRAMEWORK}_${RUNTIME}_release.zip * + } + } + # Create Check archives foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) { foreach ($RUNTIME in $CHECK_RUNTIMES) {