diff --git a/Tapeti.Cmd/ConsoleHelper/ConsoleWrapper.cs b/Tapeti.Cmd/ConsoleHelper/ConsoleWrapper.cs index 4f5aaee..6c2bcc8 100644 --- a/Tapeti.Cmd/ConsoleHelper/ConsoleWrapper.cs +++ b/Tapeti.Cmd/ConsoleHelper/ConsoleWrapper.cs @@ -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) diff --git a/Tapeti.Cmd/Serialization/EasyNetQMessageSerializer.cs b/Tapeti.Cmd/Serialization/EasyNetQMessageSerializer.cs index 570fb02..cabbcb3 100644 --- a/Tapeti.Cmd/Serialization/EasyNetQMessageSerializer.cs +++ b/Tapeti.Cmd/Serialization/EasyNetQMessageSerializer.cs @@ -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() { } diff --git a/Tapeti.Cmd/Verbs/ExportVerb.cs b/Tapeti.Cmd/Verbs/ExportVerb.cs index d6313ef..9e62c9d 100644 --- a/Tapeti.Cmd/Verbs/ExportVerb.cs +++ b/Tapeti.Cmd/Verbs/ExportVerb.cs @@ -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); diff --git a/docs/tapeticmd.rst b/docs/tapeticmd.rst index a474baf..5b9b876 100644 --- a/docs/tapeticmd.rst +++ b/docs/tapeticmd.rst @@ -45,6 +45,9 @@ Fetches messages from a queue and writes them to disk. -o , --output *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.