#33 Tapeti.Cmd confirm overwrite existing files when exporting

Also fixed issue with input remaining in buffer which can cause accidental confirms
This commit is contained in:
Mark van Renswoude 2021-09-15 19:56:24 +02:00
parent 71df3e3f22
commit 0fb2c48083
4 changed files with 115 additions and 24 deletions

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Console = System.Console;
namespace Tapeti.Cmd.ConsoleHelper
{
@ -138,39 +140,79 @@ namespace Tapeti.Cmd.ConsoleHelper
public abstract bool Enabled { get; }
public abstract void WriteCaptured(string value, Action processInput);
public abstract void WriteLine(string value);
public void Confirm(string message)
{
WriteLine(message);
TryReadKey(false, out _);
// Clear any previous key entered before this confirmation
while (!Owner.Cancelled && Console.KeyAvailable)
Console.ReadKey(true);
while (!Owner.Cancelled && !Console.KeyAvailable)
Thread.Sleep(50);
if (Owner.Cancelled)
return;
Console.ReadKey(true);
}
public bool ConfirmYesNo(string message)
{
WriteLine($"{message} (Y/N) ");
if (!TryReadKey(true, out var key))
return false;
var confirmed = false;
return key.KeyChar == 'y' || key.KeyChar == 'Y';
}
private bool TryReadKey(bool showKeyOutput, out ConsoleKeyInfo keyInfo)
{
while (!Owner.Cancelled && !Console.KeyAvailable)
Thread.Sleep(50);
if (Owner.Cancelled)
WriteCaptured($"{message} (Y/N) ", () =>
{
keyInfo = default;
return false;
}
// Clear any previous key entered before this confirmation
while (!Owner.Cancelled && Console.KeyAvailable)
Console.ReadKey(true);
keyInfo = Console.ReadKey(!showKeyOutput);
return true;
var input = new StringBuilder();
while (!Owner.Cancelled)
{
if (!Console.KeyAvailable)
{
Thread.Sleep(50);
continue;
}
var keyInfo = Console.ReadKey(false);
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - by design
switch (keyInfo.Key)
{
case ConsoleKey.Enter:
Console.WriteLine();
confirmed = input.ToString().Equals("Y", StringComparison.CurrentCultureIgnoreCase);
return;
case ConsoleKey.Backspace:
if (input.Length > 0)
{
input.Remove(input.Length - 1, 1);
// We need to handle erasing the character ourselves, as we want to use ReadKey so that we can monitor Cancelled
Console.Write(" \b");
}
break;
default:
if (keyInfo.KeyChar != -1)
input.Append(keyInfo.KeyChar);
break;
}
}
});
return confirmed;
}
}
@ -185,6 +227,22 @@ namespace Tapeti.Cmd.ConsoleHelper
public override bool Enabled => true;
public override void WriteCaptured(string value, Action waitForInput)
{
Owner.AcquirePermanent();
try
{
Console.Write(value);
waitForInput();
}
finally
{
Owner.ReleasePermanent();
}
}
public override void WriteLine(string value)
{
Owner.AcquirePermanent();
@ -217,6 +275,13 @@ namespace Tapeti.Cmd.ConsoleHelper
public override bool Enabled => !Console.IsOutputRedirected;
public override void WriteCaptured(string value, Action waitForInput)
{
WriteLine(value);
waitForInput();
}
public override void WriteLine(string value)
{
if (!Enabled)

View File

@ -31,6 +31,12 @@ namespace Tapeti.Cmd.Serialization
}
public static bool OutputExists(string path)
{
return Directory.Exists(path) && Directory.GetFiles(path, "*.message.txt").Length > 0;
}
public void Dispose()
{
}

View File

@ -2,7 +2,6 @@
using System.IO;
using System.Text;
using CommandLine;
using RabbitMQ.Client;
using Tapeti.Cmd.ConsoleHelper;
using Tapeti.Cmd.Serialization;
@ -17,6 +16,9 @@ namespace Tapeti.Cmd.Verbs
[Option('o', "output", Required = true, HelpText = "Path or filename (depending on the chosen serialization method) where the messages will be output to.")]
public string OutputPath { get; set; }
[Option('y', "overwrite", HelpText = "If the output exists, do not ask to overwrite.")]
public bool Overwrite { get; set; }
[Option('r', "remove", HelpText = "If specified messages are acknowledged and removed from the queue. If not messages are kept.")]
public bool RemoveMessages { get; set; }
@ -40,8 +42,12 @@ namespace Tapeti.Cmd.Verbs
public void Execute(IConsole console)
{
var consoleWriter = console.GetPermanentWriter();
using var messageSerializer = GetMessageSerializer(options, consoleWriter);
if (messageSerializer == null)
return;
var factory = options.CreateConnectionFactory(console);
using var messageSerializer = GetMessageSerializer(options);
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
@ -86,16 +92,27 @@ namespace Tapeti.Cmd.Verbs
}
private static IMessageSerializer GetMessageSerializer(ExportOptions options)
private static IMessageSerializer GetMessageSerializer(ExportOptions options, IConsoleWriter consoleWriter)
{
switch (options.SerializationMethod)
{
case SerializationMethod.SingleFileJSON:
// ReSharper disable once InvertIf - causes two lines of "new SingleFileJSONMessageSerializer". DRY ReSharper.
if (!options.Overwrite && File.Exists(options.OutputPath))
{
if (!consoleWriter.ConfirmYesNo($"The output file '{options.OutputPath}' already exists, do you want to overwrite it?"))
return null;
}
return new SingleFileJSONMessageSerializer(new FileStream(options.OutputPath, FileMode.Create, FileAccess.Write, FileShare.Read), true, Encoding.UTF8);
case SerializationMethod.EasyNetQHosepipe:
if (string.IsNullOrEmpty(options.OutputPath))
throw new ArgumentException("An output path must be provided when using EasyNetQHosepipe serialization");
// ReSharper disable once InvertIf - causes two lines of "new SingleFileJSONMessageSerializer". DRY ReSharper.
if (!options.Overwrite && EasyNetQMessageSerializer.OutputExists(options.OutputPath))
{
if (!consoleWriter.ConfirmYesNo($"The output path '{options.OutputPath}' already contains a previous export, do you want to overwrite it?"))
return null;
}
return new EasyNetQMessageSerializer(options.OutputPath);

View File

@ -45,6 +45,9 @@ Fetches messages from a queue and writes them to disk.
-o <target>, --output <target>
*Required*. Path or filename (depending on the chosen serialization method) where the messages will be output to.
-y, --overwrite
If the output exists, do not ask to overwrite.
-r, --remove
If specified messages are acknowledged and removed from the queue. If not messages are kept.