Overhaul LibationCli
Add version verb with option to check for upgrade Add Search verb to search the library Add export file type inference Add more set-status options Add console progress bar and ETA Add processable option to liberate specific book IDs Scan accounts by nickname or account ID Improve startup performance for halp and on parsing error More useful error messages
This commit is contained in:
parent
bb05847b25
commit
29803c6ba0
77
Source/LibationCli/ConsoleProgressBar.cs
Normal file
77
Source/LibationCli/ConsoleProgressBar.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LibationCli;
|
||||
|
||||
internal class ConsoleProgressBar
|
||||
{
|
||||
public TextWriter Output { get; }
|
||||
public int MaxWidth { get; }
|
||||
public char ProgressChar { get; }
|
||||
public char NoProgressChar { get; }
|
||||
|
||||
public double? Progress
|
||||
{
|
||||
get => m_Progress;
|
||||
set
|
||||
{
|
||||
m_Progress = value ?? 0;
|
||||
WriteProgress();
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan RemainingTime
|
||||
{
|
||||
get => m_RemainingTime;
|
||||
set
|
||||
{
|
||||
m_RemainingTime = value;
|
||||
WriteProgress();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private double m_Progress;
|
||||
private TimeSpan m_RemainingTime;
|
||||
private int m_LastWriteLength = 0;
|
||||
private const int MAX_ETA_LEN = 10;
|
||||
private readonly int m_NumProgressPieces;
|
||||
|
||||
public ConsoleProgressBar(
|
||||
TextWriter output,
|
||||
int maxWidth = 80,
|
||||
char progressCharr = '#',
|
||||
char noProgressChar = '.')
|
||||
{
|
||||
Output = output;
|
||||
MaxWidth = maxWidth;
|
||||
ProgressChar = progressCharr;
|
||||
NoProgressChar = noProgressChar;
|
||||
m_NumProgressPieces = MaxWidth - MAX_ETA_LEN - 4;
|
||||
}
|
||||
|
||||
private void WriteProgress()
|
||||
{
|
||||
var numCompleted = (int)Math.Round(double.Min(100, m_Progress) * m_NumProgressPieces / 100);
|
||||
var numRemaining = m_NumProgressPieces - numCompleted;
|
||||
var progressBar = $"[{new string(ProgressChar, numCompleted)}{new string(NoProgressChar, numRemaining)}] ";
|
||||
|
||||
progressBar += RemainingTime.TotalMinutes > 1000
|
||||
? "ETA ∞"
|
||||
: $"ETA {(int)RemainingTime.TotalMinutes}:{RemainingTime.Seconds:D2}";
|
||||
|
||||
Output.Write(new string('\b', m_LastWriteLength) + progressBar);
|
||||
if (progressBar.Length < m_LastWriteLength)
|
||||
{
|
||||
var extra = m_LastWriteLength - progressBar.Length;
|
||||
Output.Write(new string(' ', extra) + new string('\b', extra));
|
||||
}
|
||||
m_LastWriteLength = progressBar.Length;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
=> Output.Write(
|
||||
new string('\b', m_LastWriteLength)
|
||||
+ new string(' ', m_LastWriteLength)
|
||||
+ new string('\b', m_LastWriteLength));
|
||||
}
|
||||
52
Source/LibationCli/HelpVerb.cs
Normal file
52
Source/LibationCli/HelpVerb.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using AppScaffolding;
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
|
||||
namespace LibationCli;
|
||||
|
||||
[Verb("help", HelpText = "Display more information on a specific command.")]
|
||||
internal class HelpVerb
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the verb to get help about
|
||||
/// </summary>
|
||||
[Value(0, Default = "")]
|
||||
public string HelpType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a base <see cref="HelpText"/> for <see cref="LibationCli"/>
|
||||
/// </summary>
|
||||
public static HelpText CreateHelpText()
|
||||
{
|
||||
var auto = new HelpText
|
||||
{
|
||||
AutoVersion = false,
|
||||
AutoHelp = false,
|
||||
Heading = $"LibationCli v{LibationScaffolding.BuildVersion.ToString(3)}",
|
||||
AdditionalNewLineAfterOption = true,
|
||||
MaximumDisplayWidth = 80
|
||||
};
|
||||
return auto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="HelpType"/>'s <see cref="HelpText"/>
|
||||
/// </summary>
|
||||
public HelpText GetHelpText()
|
||||
{
|
||||
var helpText = CreateHelpText();
|
||||
var result = new Parser().ParseArguments(new string[] { HelpType }, Program.VerbTypes);
|
||||
if (result.TypeInfo.Current == typeof(NullInstance))
|
||||
{
|
||||
//HelpType is not a defined verb so get LibationCli usage
|
||||
helpText.AddVerbs(Program.VerbTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
helpText.AddDashesToOption = true;
|
||||
helpText.AutoHelp = true;
|
||||
helpText.AddOptions(result);
|
||||
}
|
||||
return helpText;
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommandLine;
|
||||
using System.Threading.Tasks;
|
||||
using CommandLine;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using ApplicationServices;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
@ -29,26 +27,38 @@ namespace LibationCli
|
||||
}
|
||||
*/
|
||||
#endregion
|
||||
[Option(shortName: 'x', longName: "xlsx", SetName = "xlsx", Required = true)]
|
||||
[Option(shortName: 'x', longName: "xlsx", HelpText = "Microsoft Excel Spreadsheet", SetName = "Export Format")]
|
||||
public bool xlsx { get; set; }
|
||||
|
||||
[Option(shortName: 'c', longName: "csv", SetName = "csv", Required = true)]
|
||||
[Option(shortName: 'c', longName: "csv", HelpText = "Comma-separated values", SetName = "Export Format")]
|
||||
public bool csv { get; set; }
|
||||
|
||||
[Option(shortName: 'j', longName: "json", SetName = "json", Required = true)]
|
||||
[Option(shortName: 'j', longName: "json", HelpText = "JavaScript Object Notation", SetName = "Export Format")]
|
||||
public bool json { get; set; }
|
||||
|
||||
protected override Task ProcessAsync()
|
||||
{
|
||||
if (xlsx)
|
||||
LibraryExporter.ToXlsx(FilePath);
|
||||
if (csv)
|
||||
LibraryExporter.ToCsv(FilePath);
|
||||
if (json)
|
||||
LibraryExporter.ToJson(FilePath);
|
||||
Action<string> exporter
|
||||
= csv ? LibraryExporter.ToCsv
|
||||
: json ? LibraryExporter.ToJson
|
||||
: xlsx ? LibraryExporter.ToXlsx
|
||||
: Path.GetExtension(FilePath)?.ToLower() switch
|
||||
{
|
||||
".xlsx" => LibraryExporter.ToXlsx,
|
||||
".csv" => LibraryExporter.ToCsv,
|
||||
".json" => LibraryExporter.ToJson,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (exporter is null)
|
||||
{
|
||||
PrintVerbUsage($"Undefined export format for file type \"{Path.GetExtension(FilePath)}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
exporter(FilePath);
|
||||
Console.WriteLine($"Library exported to: {FilePath}");
|
||||
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommandLine;
|
||||
using CommandLine;
|
||||
using DataLayer;
|
||||
using FileLiberator;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
using System;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using CommandLine;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
[Verb("scan", HelpText = "Scan library. Default: scan all accounts. Optional: use 'account' flag to specify a single account.")]
|
||||
public class ScanOptions : OptionsBase
|
||||
{
|
||||
[Value(0, MetaName = "Accounts", HelpText = "Optional: nicknames of accounts to scan.", Required = false)]
|
||||
public IEnumerable<string> AccountNicknames { get; set; }
|
||||
[Value(0, MetaName = "Accounts", HelpText = "Optional: user ID or nicknames of accounts to scan.", Required = false)]
|
||||
public IEnumerable<string> AccountNames { get; set; }
|
||||
|
||||
protected override async Task ProcessAsync()
|
||||
{
|
||||
@ -42,13 +42,19 @@ namespace LibationCli
|
||||
private Account[] getAccounts()
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.GetAll().ToArray();
|
||||
var allAccounts = persister.AccountsSettings.GetAll().ToArray();
|
||||
|
||||
if (!AccountNicknames.Any())
|
||||
return accounts;
|
||||
if (!AccountNames.Any())
|
||||
return allAccounts;
|
||||
|
||||
var found = accounts.Where(acct => AccountNicknames.Contains(acct.AccountName)).ToArray();
|
||||
var notFound = AccountNicknames.Except(found.Select(f => f.AccountName)).ToArray();
|
||||
var accountNames = AccountNames.Select(n => n.ToLower()).ToArray();
|
||||
|
||||
var found
|
||||
= allAccounts
|
||||
.Where(acct => accountNames.Contains(acct.AccountName.ToLower()) || accountNames.Contains(acct.AccountId.ToLower()))
|
||||
.ToArray();
|
||||
|
||||
var notFound = allAccounts.Except(found).ToArray();
|
||||
|
||||
// no accounts found. do not continue
|
||||
if (!found.Any())
|
||||
|
||||
50
Source/LibationCli/Options/SearchOptions.cs
Normal file
50
Source/LibationCli/Options/SearchOptions.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using ApplicationServices;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationCli.Options
|
||||
{
|
||||
[Verb("search", HelpText = "Search for books in your library")]
|
||||
internal class SearchOptions : OptionsBase
|
||||
{
|
||||
[Value(0, MetaName = "query", Required = true, HelpText = "Lucene query test to search")]
|
||||
public IEnumerable<string> Query { get; set; }
|
||||
|
||||
protected override Task ProcessAsync()
|
||||
{
|
||||
var query = string.Join(" ", Query).Trim('\"');
|
||||
var results = SearchEngineCommands.Search(query).Docs.ToList();
|
||||
|
||||
Console.WriteLine($"Found {results.Count} matching results.");
|
||||
|
||||
const string nextPrompt = "Press any key for the next 10 results or Esc for all results";
|
||||
bool waitForNextBatch = true;
|
||||
|
||||
for (int i = 0; i < results.Count; i += 10)
|
||||
{
|
||||
foreach (var doc in results.Skip(i).Take(10))
|
||||
Console.WriteLine(getDocDisplay(doc.Doc));
|
||||
|
||||
if (waitForNextBatch)
|
||||
{
|
||||
Console.Write(nextPrompt);
|
||||
waitForNextBatch = Console.ReadKey().Key != ConsoleKey.Escape;
|
||||
ReplaceConsoleText(Console.Out, nextPrompt.Length, "");
|
||||
Console.SetCursorPosition(0, Console.CursorTop);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static string getDocDisplay(Lucene.Net.Documents.Document doc)
|
||||
{
|
||||
var title = doc.GetField("title");
|
||||
var id = doc.GetField("_ID_");
|
||||
return $"[{id.StringValue}] - {title.StringValue}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,31 +1,62 @@
|
||||
using System;
|
||||
using ApplicationServices;
|
||||
using CommandLine;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using CommandLine;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
[Verb("set-status", HelpText = """
|
||||
Set download statuses throughout library based on whether each book's audio file can be found.
|
||||
Must include at least one flag: --downloaded , --not-downloaded.
|
||||
Downloaded: If the audio file can be found, set download status to 'Downloaded'.
|
||||
Not Downloaded: If the audio file cannot be found, set download status to 'Not Downloaded'
|
||||
""")]
|
||||
public class SetDownloadStatusOptions : OptionsBase
|
||||
{
|
||||
[Option(shortName: 'd', longName: "downloaded", Required = true)]
|
||||
[Option(shortName: 'd', longName: "downloaded", Group = "Download Status", HelpText = "set download status to 'Downloaded'")]
|
||||
public bool SetDownloaded { get; set; }
|
||||
|
||||
[Option(shortName: 'n', longName: "not-downloaded", Required = true)]
|
||||
[Option(shortName: 'n', longName: "not-downloaded", Group = "Download Status", HelpText = "set download status to 'Downloaded'")]
|
||||
public bool SetNotDownloaded { get; set; }
|
||||
|
||||
[Option("force", HelpText = "Set the download status regardless of whether the book's audio file can be found. Only one download status option may be used with this option.")]
|
||||
public bool Force { get; set; }
|
||||
|
||||
[Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books on which to set download status.")]
|
||||
public IEnumerable<string> Asins { get; set; }
|
||||
|
||||
protected override async Task ProcessAsync()
|
||||
{
|
||||
if (Force && SetDownloaded && SetNotDownloaded)
|
||||
{
|
||||
PrintVerbUsage("ERROR:\nWhen run with --force option, only one download status option may be used.");
|
||||
return;
|
||||
}
|
||||
|
||||
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
|
||||
if (Asins.Any())
|
||||
{
|
||||
var asins = Asins.Select(a => a.ToLower()).ToArray();
|
||||
libraryBooks = libraryBooks.Where(lb => lb.Book.AudibleProductId.ToLower().In(asins)).ToList();
|
||||
|
||||
if (libraryBooks.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine("Could not find any books matching asins");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Force)
|
||||
{
|
||||
var status = SetDownloaded ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
|
||||
|
||||
var num = libraryBooks.UpdateBookStatus(status);
|
||||
Console.WriteLine($"Set LiberatedStatus to '{status}' on {"book".PluralizeWithCount(num)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var bulkSetStatus = new BulkSetDownloadStatus(libraryBooks, SetDownloaded, SetNotDownloaded);
|
||||
await Task.Run(() => bulkSetStatus.Discover());
|
||||
bulkSetStatus.Execute();
|
||||
@ -34,4 +65,5 @@ namespace LibationCli
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
Source/LibationCli/Options/VersionOptions.cs
Normal file
59
Source/LibationCli/Options/VersionOptions.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using AppScaffolding;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationCli.Options;
|
||||
|
||||
[Verb("version", HelpText = "Display version information.")]
|
||||
internal class VersionOptions : OptionsBase
|
||||
{
|
||||
[Option('c', "check", Required = false, HelpText = "Check if an upgrade is available")]
|
||||
public bool CheckForUpgrade { get; set; }
|
||||
|
||||
protected override Task ProcessAsync()
|
||||
{
|
||||
const string checkingForUpgrade = "Checking for upgrade...";
|
||||
Console.WriteLine($"Libation {LibationScaffolding.Variety} v{LibationScaffolding.BuildVersion.ToString(3)}");
|
||||
|
||||
if (CheckForUpgrade)
|
||||
{
|
||||
Console.Write(checkingForUpgrade);
|
||||
|
||||
var origColor = Console.ForegroundColor;
|
||||
try
|
||||
{
|
||||
var upgradeProperties = LibationScaffolding.GetLatestRelease();
|
||||
|
||||
if (upgradeProperties is null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
ReplaceConsoleText(Console.Out, checkingForUpgrade.Length, "No available upgrade");
|
||||
Console.WriteLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
ReplaceConsoleText(Console.Out, checkingForUpgrade.Length, $"Upgrade Available: v{upgradeProperties.LatestRelease.ToString(3)}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(upgradeProperties.ZipUrl);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Release Notes");
|
||||
Console.WriteLine("=============");
|
||||
Console.WriteLine(upgradeProperties.Notes);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.Error.WriteLine("ERROR CHECKING FOR UPGRADE");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ForegroundColor = origColor;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using CommandLine;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
@ -17,15 +17,34 @@ namespace LibationCli
|
||||
catch (Exception ex)
|
||||
{
|
||||
Environment.ExitCode = (int)ExitCode.RunTimeError;
|
||||
|
||||
Console.Error.WriteLine("ERROR");
|
||||
Console.Error.WriteLine("=====");
|
||||
Console.Error.WriteLine(ex.Message);
|
||||
Console.Error.WriteLine();
|
||||
Console.Error.WriteLine(ex.StackTrace);
|
||||
PrintVerbUsage(new string[]
|
||||
{
|
||||
"ERROR",
|
||||
"=====",
|
||||
ex.Message,
|
||||
"",
|
||||
ex.StackTrace
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void PrintVerbUsage(params string[] linesBeforeUsage)
|
||||
{
|
||||
var verb = GetType().GetCustomAttribute<VerbAttribute>().Name;
|
||||
var helpText = new HelpVerb { HelpType = verb }.GetHelpText();
|
||||
helpText.AddPreOptionsLines(linesBeforeUsage);
|
||||
helpText.AddPreOptionsLine("");
|
||||
helpText.AddPreOptionsLine($"{verb} Usage:");
|
||||
Console.Error.WriteLine(helpText);
|
||||
}
|
||||
|
||||
protected static void ReplaceConsoleText(TextWriter writer, int previousLength, string newText)
|
||||
{
|
||||
writer.Write(new string('\b', previousLength));
|
||||
writer.Write(newText);
|
||||
writer.Write(new string(' ', int.Max(0, previousLength - newText.Length)));
|
||||
}
|
||||
|
||||
protected abstract Task ProcessAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +1,34 @@
|
||||
using System;
|
||||
using ApplicationServices;
|
||||
using CommandLine;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using FileLiberator;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ApplicationServices;
|
||||
using CommandLine;
|
||||
using DataLayer;
|
||||
using FileLiberator;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
public abstract class ProcessableOptionsBase : OptionsBase
|
||||
{
|
||||
|
||||
[Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books to process.")]
|
||||
public IEnumerable<string> Asins { get; set; }
|
||||
|
||||
protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook> completedAction = null)
|
||||
where TProcessable : Processable, new()
|
||||
{
|
||||
var progressBar = new ConsoleProgressBar(Console.Out);
|
||||
var strProc = new TProcessable();
|
||||
|
||||
strProc.Begin += (o, e) => Console.WriteLine($"{typeof(TProcessable).Name} Begin: {e}");
|
||||
strProc.Completed += (o, e) => Console.WriteLine($"{typeof(TProcessable).Name} Completed: {e}");
|
||||
|
||||
strProc.Completed += (o, e) =>
|
||||
{
|
||||
progressBar.Clear();
|
||||
Console.WriteLine($"{typeof(TProcessable).Name} Completed: {e}");
|
||||
};
|
||||
|
||||
strProc.Completed += (s, e) =>
|
||||
{
|
||||
@ -32,12 +43,23 @@ namespace LibationCli
|
||||
}
|
||||
};
|
||||
|
||||
strProc.StreamingTimeRemaining += (_, e) => progressBar.RemainingTime = e;
|
||||
strProc.StreamingProgressChanged += (_, e) => progressBar.Progress = e.ProgressPercentage;
|
||||
|
||||
return strProc;
|
||||
}
|
||||
|
||||
protected static async Task RunAsync(Processable Processable)
|
||||
protected async Task RunAsync(Processable Processable)
|
||||
{
|
||||
foreach (var libraryBook in Processable.GetValidLibraryBooks(DbContexts.GetLibrary_Flat_NoTracking()))
|
||||
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking().AsEnumerable();
|
||||
|
||||
if (Asins.Any())
|
||||
{
|
||||
var asinsLower = Asins.Select(a => a.ToLower()).ToArray();
|
||||
libraryBooks = libraryBooks.Where(lb => lb.Book.AudibleProductId.ToLower().In(asinsLower));
|
||||
}
|
||||
|
||||
foreach (var libraryBook in Processable.GetValidLibraryBooks(libraryBooks))
|
||||
await ProcessOneAsync(Processable, libraryBook, false);
|
||||
|
||||
var done = "Done. All books have been processed";
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommandLine;
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Collections;
|
||||
using Dinah.Core.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
@ -19,47 +16,63 @@ namespace LibationCli
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static async Task<int> Main(string[] args)
|
||||
public readonly static Type[] VerbTypes = Setup.LoadVerbs();
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
//***********************************************//
|
||||
// //
|
||||
// do not use Configuration before this line //
|
||||
// //
|
||||
//***********************************************//
|
||||
Setup.Initialize();
|
||||
|
||||
var types = Setup.LoadVerbs();
|
||||
|
||||
#if DEBUG
|
||||
string input = null;
|
||||
|
||||
//input = " set-status -n --force B017V4IM1G";
|
||||
//input = " liberate B017V4IM1G";
|
||||
//input = " convert B017V4IM1G";
|
||||
//input = " search \"-liberated\"";
|
||||
//input = " export --help";
|
||||
//input = " version --check";
|
||||
//input = " scan rmcrackan";
|
||||
//input = " help set-status";
|
||||
//input = " liberate ";
|
||||
|
||||
|
||||
// note: this hack will fail for quoted file paths with spaces because it will break on those spaces
|
||||
if (!string.IsNullOrWhiteSpace(input))
|
||||
args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var setBreakPointHere = args;
|
||||
#endif
|
||||
|
||||
var result = Parser.Default.ParseArguments(args, types);
|
||||
var result = new Parser(ConfigureParser).ParseArguments(args, VerbTypes);
|
||||
|
||||
if (result.Value is HelpVerb helper)
|
||||
Console.Error.WriteLine(helper.GetHelpText());
|
||||
else if (result.TypeInfo.Current == typeof(HelpVerb))
|
||||
{
|
||||
//Error parsing the command, but the verb type was identified as HelpVerb
|
||||
//Print LibationCli usage
|
||||
var helpText = HelpVerb.CreateHelpText();
|
||||
helpText.AddVerbs(VerbTypes);
|
||||
Console.Error.WriteLine(helpText);
|
||||
}
|
||||
else if (result.Errors.Any())
|
||||
HandleErrors(result);
|
||||
else
|
||||
{
|
||||
//Everything parsed correctly, so execute the command
|
||||
|
||||
//***********************************************//
|
||||
// //
|
||||
// do not use Configuration before this line //
|
||||
// //
|
||||
//***********************************************//
|
||||
Setup.Initialize();
|
||||
|
||||
// if successfully parsed
|
||||
// async: run parsed options
|
||||
await result.WithParsedAsync<OptionsBase>(opt => opt.Run());
|
||||
|
||||
// if not successfully parsed
|
||||
// sync: handle parse errors
|
||||
result.WithNotParsed(errors => HandleErrors(result, errors));
|
||||
|
||||
return Environment.ExitCode;
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleErrors(ParserResult<object> result, IEnumerable<Error> errors)
|
||||
private static void HandleErrors(ParserResult<object> result)
|
||||
{
|
||||
var errorsList = errors.ToList();
|
||||
var errorsList = result.Errors.ToList();
|
||||
if (errorsList.Any(e => e.Tag.In(ErrorType.HelpRequestedError, ErrorType.VersionRequestedError, ErrorType.HelpVerbRequestedError)))
|
||||
{
|
||||
Environment.ExitCode = (int)ExitCode.NonRunNonError;
|
||||
@ -67,17 +80,36 @@ namespace LibationCli
|
||||
}
|
||||
|
||||
Environment.ExitCode = (int)ExitCode.ParseError;
|
||||
var helpText = HelpVerb.CreateHelpText();
|
||||
|
||||
if (errorsList.Any(e => e.Tag.In(ErrorType.NoVerbSelectedError)))
|
||||
if (errorsList.OfType<NoVerbSelectedError>().Any())
|
||||
{
|
||||
Console.Error.WriteLine("No verb selected");
|
||||
return;
|
||||
//Print LibationCli usage
|
||||
helpText.AddPreOptionsLine("No verb selected");
|
||||
helpText.AddVerbs(VerbTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
//print the specified verb's usage
|
||||
helpText.AddDashesToOption = true;
|
||||
helpText.AutoHelp = true;
|
||||
|
||||
if (!errorsList.OfType<UnknownOptionError>().Any(o => o.Token.ToLower() == "help"))
|
||||
{
|
||||
//verb was not executed with the "--help" option,
|
||||
//so print verb option parsing error info.
|
||||
helpText = HelpText.DefaultParsingErrorsHandler(result, helpText);
|
||||
}
|
||||
|
||||
var helpText = HelpText.AutoBuild(result,
|
||||
h => HelpText.DefaultParsingErrorsHandler(result, h),
|
||||
e => e);
|
||||
Console.WriteLine(helpText);
|
||||
helpText.AddOptions(result);
|
||||
}
|
||||
Console.Error.WriteLine(helpText);
|
||||
}
|
||||
|
||||
private static void ConfigureParser(ParserSettings settings)
|
||||
{
|
||||
settings.AutoVersion = false;
|
||||
settings.AutoHelp = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AppScaffolding;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using AppScaffolding;
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Collections;
|
||||
using Dinah.Core.Collections.Generic;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
@ -16,6 +10,11 @@ namespace LibationCli
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
//Determine variety by the dlls present in the current directory.
|
||||
//Necessary to be able to check for upgrades.
|
||||
var variety = System.IO.File.Exists("System.Windows.Forms.dll") ? Variety.Classic : Variety.Chardonnay;
|
||||
LibationScaffolding.SetReleaseIdentifier(variety);
|
||||
|
||||
//***********************************************//
|
||||
// //
|
||||
// do not use Configuration before this line //
|
||||
@ -26,28 +25,6 @@ namespace LibationCli
|
||||
|
||||
LibationScaffolding.RunPostConfigMigrations(config);
|
||||
LibationScaffolding.RunPostMigrationScaffolding(config);
|
||||
|
||||
#if !DEBUG
|
||||
checkForUpdate();
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void checkForUpdate()
|
||||
{
|
||||
var upgradeProperties = LibationScaffolding.GetLatestRelease();
|
||||
if (upgradeProperties is null)
|
||||
return;
|
||||
|
||||
var origColor = Console.ForegroundColor;
|
||||
try
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"UPDATE AVAILABLE @ {upgradeProperties.ZipUrl}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ForegroundColor = origColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static Type[] LoadVerbs() => Assembly.GetExecutingAssembly()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user