Skip to content

Commit

Permalink
Show Delete progress and new 'silent' option
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-s committed Jun 25, 2024
1 parent 5d03ab9 commit f19f77a
Show file tree
Hide file tree
Showing 15 changed files with 171 additions and 123 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ This is the same as the eample gif above.
WARNING: This will delete the Encrypted files if they are successfully decrypted.
Ensure you have backups as the files will not be recoverable!

This will prompt for confirmation.
This will prompt for confirmation (unless you specify -y / --silent)

- Windows
`QnapBackupDecryptor.exe -e c:\Files\Enc -d c:\Files\Dec --verbose --removeencrypted`
Expand Down Expand Up @@ -78,6 +78,7 @@ This will prompt for confirmation.
|-r|--removeencrypted|Delete encrypted files (will prompt)|false|
|-v|--verbose|Set output to verbose|false|
|-o|--overwrite|Overwrite file(s) in output|false|
|-y|--silent|Silent - 'Yes' to all confirmation prompts|false|
| |--help|Display this help screen.||
| |--version|Display version information.||

Expand Down
13 changes: 8 additions & 5 deletions src/QnapBackupDecryptor.Console/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@ internal sealed class Options
public required string OutputDestination { get; init; }

[Option('s', "subfolders", Required = false, HelpText = "Include Subfolders (default: false)")]
public bool IncludeSubfolders { get; init; }
public bool IncludeSubfolders { get; init; } = false;

[Option('v', "verbose", Required = false, HelpText = "Set output to verbose")]
public bool Verbose { get; init; }

[Option('o', "overwrite", Required = false, HelpText = "Overwrite file(s) in output (default: false)")]
public bool Overwrite { get; init; }
public bool Overwrite { get; init; } = false;

[Option('r', "removeencrypted", Required = false, HelpText = "Delete encrypted files when decrypted (default: false)")]
public bool RemoveEncrypted { get; init; }
public bool RemoveEncrypted { get; init; } = false;

[Option('i', "inplace", Required = false, HelpText = "Encrypt files in-place (default: false)")]
public bool InPlace { get; init; }
[Option('i', "inplace", Required = false, HelpText = "Decrypt files in-place (default: false)")]
public bool InPlace { get; init; } = false;

[Option('y', "silent", Required = false, HelpText = "Silent - 'Yes' to all confirmation prompts (default: false)")]
public bool Silent { get; init; } = false;

[Usage(ApplicationAlias = "QnapBackupDecryptor")]
// ReSharper disable once UnusedMember.Global // Used by the console
Expand Down
8 changes: 4 additions & 4 deletions src/QnapBackupDecryptor.Console/Output.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace QnapBackupDecryptor.Console;

internal static class Output
{
public static void ShowResults(IReadOnlyList<DecryptResult> decryptResults, IReadOnlyList<DeleteResult> deleteResults, bool verbose, TimeSpan swElapsed)
public static void ShowResults(IReadOnlyList<DecryptResult> decryptResults, IReadOnlyList<DeleteResult> deleteResults, bool verbose, TimeSpan elapsedTime)
{
if (verbose && decryptResults.Count > 1)
{
Expand All @@ -18,7 +18,7 @@ public static void ShowResults(IReadOnlyList<DecryptResult> decryptResults, IRea
AnsiConsole.MarkupLine("Add --verbose to see details.");
}

ShowTiming(swElapsed);
ShowTiming(elapsedTime);

if (decryptResults.Any(r => r.DecryptedOk == false) || deleteResults.Any(r => r.DeletedOk == false))
Environment.ExitCode = 1;
Expand Down Expand Up @@ -63,7 +63,7 @@ private static void ShowFileListResults(IReadOnlyList<DecryptResult> decryptResu
table.AddColumn("Error");
}

var deleteResultsLookup = deleteResults.ToDictionary(r => r.ToDelete, r => r);
var deleteResultsLookup = deleteResults.ToDictionary(r => r.FileToDelete, r => r);

foreach (var result in decryptResults)
{
Expand Down Expand Up @@ -97,7 +97,7 @@ private static List<string> DecryptResultToRow(DecryptResult decryptResult)
return row;
}

private static IEnumerable<string> DeleteResultToRow(DeleteResult? deleteResult)
private static List<string> DeleteResultToRow(DeleteResult? deleteResult)
{
if (deleteResult == null)
return [];
Expand Down
84 changes: 61 additions & 23 deletions src/QnapBackupDecryptor.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using CommandLine;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text;
using CommandLine;
using QnapBackupDecryptor.Core;
using QnapBackupDecryptor.Core.Models;
using Spectre.Console;
using System.Collections.Concurrent;
using System.Diagnostics;

namespace QnapBackupDecryptor.Console;

Expand All @@ -26,20 +27,27 @@ private static void Run(Options options)
if (Prompts.EnsureInPlaceWanted(options) == false)
return;

var password = Prompts.GetPassword(options);

var stopwatch = Stopwatch.StartNew();

// Decrypt Files
var decryptJobs = GetDecryptJobs(options);
var decryptResults = Decrypt(decryptJobs, GetPassword(options));

var (decryptResults, deleteResults) = DoDecrypt(decryptJobs, options, password);
// Delete Files (if requested)
var filesToDelete = GetFilesToDelete(decryptResults, options);
var deleteResults = Delete(filesToDelete);

stopwatch.Stop();

Output.ShowResults(decryptResults, deleteResults, options.Verbose, stopwatch.Elapsed);
}

private static IReadOnlyList<FileJob> GetDecryptJobs(Options options)
private static byte[] GetPassword(Options options)
=> string.IsNullOrEmpty(options.Password)
? Prompts.GetPassword()
: Encoding.UTF8.GetBytes(options.Password);

private static IReadOnlyList<DecryptJob> GetDecryptJobs(Options options)
{
return AnsiConsole
.Status()
Expand All @@ -48,19 +56,17 @@ private static IReadOnlyList<FileJob> GetDecryptJobs(Options options)
statusContext.Spinner(Spinner.Known.SimpleDots);
statusContext.SpinnerStyle(Style.Parse("green"));

return JobMaker.GetDecryptJobs(
encryptedSource: options.EncryptedSource,
decryptedTarget: options.OutputDestination,
overwrite: options.Overwrite,
includeSubFolders: options.IncludeSubfolders);
return JobMaker.CreateDecryptJobs(
encryptedSource: options.EncryptedSource,
decryptedTarget: options.OutputDestination,
overwrite: options.Overwrite,
includeSubFolders: options.IncludeSubfolders);
});
}

private static (IReadOnlyList<DecryptResult> DecryptResults, IReadOnlyList<DeleteResult> DeleteResults)
DoDecrypt(IReadOnlyCollection<FileJob> decryptJobs, Options options, byte[] password)
private static IReadOnlyList<DecryptResult> Decrypt(IReadOnlyCollection<DecryptJob> decryptJobs, byte[] password)
{
var decryptResults = new ConcurrentBag<DecryptResult>();
var deleteResults = new ConcurrentBag<DeleteResult>();

AnsiConsole.Progress()
.Columns(Output.GetProgressColumns())
Expand All @@ -71,17 +77,49 @@ private static (IReadOnlyList<DecryptResult> DecryptResults, IReadOnlyList<Delet
progressTask.MaxValue = decryptJobs.Count;

Parallel.ForEach(decryptJobs, job =>
{
var (decryptResult, deleteResult) = DecryptorService.Decrypt(options.RemoveEncrypted, password, job, progressTask.Increment);
decryptResults.Add(decryptResult);
if (deleteResult != null)
deleteResults.Add(deleteResult);
});

{
var decryptResult = DecryptorService.TryDecrypt(password, job);
decryptResults.Add(decryptResult);
progressTask.Increment(1);
});
});

return (decryptResults.ToList(), deleteResults.ToList());
return decryptResults.ToList();
}

private static IReadOnlyList<FileSystemInfo> GetFilesToDelete(IReadOnlyList<DecryptResult> deleteResults, Options options)
{
if (options.RemoveEncrypted == false)
return [];

return deleteResults
.Where(r => r.DecryptedOk)
.Select(r => r.Source)
.ToList();
}

private static IReadOnlyList<DeleteResult> Delete(IReadOnlyList<FileSystemInfo> filesToDelete)
{
var deleteResults = new ConcurrentBag<DeleteResult>();

AnsiConsole.Progress()
.Columns(Output.GetProgressColumns())
.AutoClear(true)
.Start(progressContext =>
{
var progressTask = progressContext.AddTask("[yellow]Deleting Files\t[/]");
progressTask.MaxValue = filesToDelete.Count;

Parallel.ForEach(filesToDelete, job =>
{
var deleteResult = FileService.TryDelete(job);
deleteResults.Add(deleteResult);
progressTask.Increment(1);
});

});

return deleteResults.ToList();
}

}
24 changes: 13 additions & 11 deletions src/QnapBackupDecryptor.Console/Prompts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,21 @@ internal static class Prompts
private const string NO = "n";
private const string INVALID_OPTION_ENTERED = "[yellow]That's not a valid option[/]";

public static byte[] GetPassword(Options options)
public static byte[] GetPassword()
{
if (string.IsNullOrEmpty(options.Password) == false)
return Encoding.UTF8.GetBytes(options.Password);
else
{
var password = AnsiConsole.Prompt(
new TextPrompt<string>("[bold]>> Enter password[/]")
.PromptStyle("yellow")
.Secret());
return Encoding.UTF8.GetBytes(password);
}
var password = AnsiConsole.Prompt(
new TextPrompt<string>("[bold]>> Enter password[/]")
.PromptStyle("yellow")
.Secret());

return Encoding.UTF8.GetBytes(password);
}

public static bool EnsureDeleteWanted(Options options)
{
if (options.Silent)
return true;

if (options.RemoveEncrypted == false)
return true;

Expand All @@ -37,6 +36,9 @@ public static bool EnsureDeleteWanted(Options options)

public static bool EnsureInPlaceWanted(Options options)
{
if (options.Silent)
return true;

if (options.InPlace == false)
return true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;CA1859</NoWarn>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;CA1859</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Spectre.Console" Version="0.49.1" />
Expand Down
14 changes: 7 additions & 7 deletions src/QnapBackupDecryptor.Core.Tests/JobMakerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public void GetDecryptJobs_TwoFolders_SourceDoesntExist_Error()
{
// Arrange
// Act
var result = JobMaker.GetDecryptJobs("/somefolder1", "/somefolder2", false, false);
var result = JobMaker.CreateDecryptJobs("/somefolder1", "/somefolder2", false, false);

// Assert
result.Count.ShouldBe(1);
Expand All @@ -21,11 +21,11 @@ public void GetDecryptJobs_TwoFolders_SourceDoesntExist_Error()
public void GetDecryptJobs_TwoFolders_DestDoesntExist_ErrorAssumesFileOutput()
{
// Arrange
string path = Directory.GetCurrentDirectory();
string destPath = Path.Combine(path, "somefolder");
var path = Directory.GetCurrentDirectory();
var destPath = Path.Combine(path, "somefolder");

// Act
var result = JobMaker.GetDecryptJobs(path, destPath, false, false);
var result = JobMaker.CreateDecryptJobs(path, destPath, false, false);

// Assert
result.Count.ShouldBe(1);
Expand All @@ -41,7 +41,7 @@ public void GetDecryptJobs_EncryptedFileExists_TargetDoesntExist_ProducesValidJo
var decFile = Path.Combine("TestFiles", "dec.txt");

// Act
var result = JobMaker.GetDecryptJobs(encFile, decFile, false, false);
var result = JobMaker.CreateDecryptJobs(encFile, decFile, false, false);

// Assert
result.Count.ShouldBe(1);
Expand All @@ -58,7 +58,7 @@ public void GetDecryptJobs_EncryptedFileDoesntExist_TargetExists_OverwriteTrue_P
var decFile = Path.GetTempFileName();

// Act
var result = JobMaker.GetDecryptJobs(encFile, decFile, overwrite: true, false);
var result = JobMaker.CreateDecryptJobs(encFile, decFile, overwrite: true, false);

// Assert
result.Count.ShouldBe(1);
Expand All @@ -74,7 +74,7 @@ public void GetDecryptJobs_EncryptedFileExists_TargetExists_OverwriteFalse_Produ
var decFile = Path.GetTempFileName();

// Act
var result = JobMaker.GetDecryptJobs(encFile, decFile, overwrite: false, false);
var result = JobMaker.CreateDecryptJobs(encFile, decFile, overwrite: false, false);

// Assert
result.Count.ShouldBe(1);
Expand Down
43 changes: 11 additions & 32 deletions src/QnapBackupDecryptor.Core/DecryptorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,18 @@

namespace QnapBackupDecryptor.Core;

public class DecryptorService
public static class DecryptorService
{
public static (DecryptResult decryptResult, DeleteResult? deleteResult) Decrypt(
bool removeEncrypted,
byte[] password,
FileJob job,
Action<double> progressUpdate)
public static DecryptResult TryDecrypt(byte[] password, DecryptJob job)
{

DecryptResult decrypted;
DeleteResult? deleted =null;

if (job.IsValid)
{
var decryptionResult = OpenSsl.Decrypt(
encryptedFile: new FileInfo(job.EncryptedFile.FullName),
password: password,
outputFile: new FileInfo(job.OutputFile.FullName));

decrypted = new DecryptResult(job.EncryptedFile, job.OutputFile, decryptionResult.IsSuccess, decryptionResult.ErrorMessage);

// Delete encrypted file only if success and option chosen
if (decryptionResult.IsSuccess && removeEncrypted)
deleted = FileService.TryDelete(job.EncryptedFile);
}
else
{
decrypted = new DecryptResult(job.EncryptedFile, job.OutputFile, job.IsValid, job.ErrorMessage);
}

progressUpdate(1);

return (decrypted, deleted);

if (job.IsValid == false)
return new DecryptResult(job.EncryptedFile, job.OutputFile, job.IsValid, job.ErrorMessage);

var decryptionResult = OpenSsl.Decrypt(
encryptedFile: new FileInfo(job.EncryptedFile.FullName),
password: password,
outputFile: new FileInfo(job.OutputFile.FullName));

return new DecryptResult(job.EncryptedFile, job.OutputFile, decryptionResult.IsSuccess, decryptionResult.ErrorMessage);
}
}
2 changes: 1 addition & 1 deletion src/QnapBackupDecryptor.Core/FileJobExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace QnapBackupDecryptor.Core;

internal static class FileJobExtensions
{
internal static List<FileJob> ToList(this FileJob job)
internal static IReadOnlyList<DecryptJob> ToJobs(this DecryptJob job)
=> [job];
}
Loading

0 comments on commit f19f77a

Please sign in to comment.