mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 11:42:22 +08:00
Toolbox & CLI: Redo console output, Add configure
subcommand
This commit is contained in:
parent
e2d5a3fd47
commit
9c33d64734
202
BililiveRecorder.Cli/Configure/ConfigureCommand.cs
Normal file
202
BililiveRecorder.Cli/Configure/ConfigureCommand.cs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
using System;
|
||||||
|
using System.CommandLine;
|
||||||
|
using System.CommandLine.Invocation;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using BililiveRecorder.Core.Config;
|
||||||
|
using BililiveRecorder.Core.Config.V2;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Cli.Configure
|
||||||
|
{
|
||||||
|
public class ConfigureCommand : Command
|
||||||
|
{
|
||||||
|
public ConfigureCommand() : base("configure", "Interactively configure config.json")
|
||||||
|
{
|
||||||
|
this.AddArgument(new Argument("path") { Description = "Path to work directory or config.json" });
|
||||||
|
this.Handler = CommandHandler.Create<string>(Run);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Run(string path)
|
||||||
|
{
|
||||||
|
if (!FindConfig(path, out var config, out var fullPath))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
ShowRootMenu(config, fullPath);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShowRootMenu(ConfigV2 config, string fullPath)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var selection = PromptEnumSelection<RootMenuSelection>();
|
||||||
|
AnsiConsole.Clear();
|
||||||
|
switch (selection)
|
||||||
|
{
|
||||||
|
case RootMenuSelection.ListRooms:
|
||||||
|
ListRooms(config);
|
||||||
|
break;
|
||||||
|
case RootMenuSelection.AddRoom:
|
||||||
|
AddRoom(config);
|
||||||
|
break;
|
||||||
|
case RootMenuSelection.DeleteRoom:
|
||||||
|
DeleteRoom(config);
|
||||||
|
break;
|
||||||
|
case RootMenuSelection.SetRoomConfig:
|
||||||
|
// TODO
|
||||||
|
AnsiConsole.MarkupLine("[bold red]Not Implemented Yet[/]");
|
||||||
|
break;
|
||||||
|
case RootMenuSelection.SetGlobalConfig:
|
||||||
|
// TODO
|
||||||
|
AnsiConsole.MarkupLine("[bold red]Not Implemented Yet[/]");
|
||||||
|
break;
|
||||||
|
case RootMenuSelection.SetJsonSchema:
|
||||||
|
SetJsonSchema(config);
|
||||||
|
break;
|
||||||
|
case RootMenuSelection.Exit:
|
||||||
|
return;
|
||||||
|
case RootMenuSelection.SaveAndExit:
|
||||||
|
if (SaveConfig(config, fullPath))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ListRooms(ConfigV2 config)
|
||||||
|
{
|
||||||
|
var table = new Table()
|
||||||
|
.AddColumns("Roomid", "AutoRecord")
|
||||||
|
.Border(TableBorder.Rounded);
|
||||||
|
|
||||||
|
foreach (var room in config.Rooms)
|
||||||
|
{
|
||||||
|
table.AddRow(room.RoomId.ToString(), room.AutoRecord ? "[green]Enabled[/]" : "[red]Disabled[/]");
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.Render(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddRoom(ConfigV2 config)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var roomid = AnsiConsole.Prompt(new TextPrompt<int>("[grey](type 0 to cancel)[/] [green]Roomid[/]:").Validate(x => x switch
|
||||||
|
{
|
||||||
|
< 0 => ValidationResult.Error("[red]Roomid can't be negative[/]"),
|
||||||
|
_ => ValidationResult.Success(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (roomid == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (config.Rooms.Any(x => x.RoomId == roomid))
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[red]Room already exist[/]");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var autoRecord = AnsiConsole.Confirm("Enable auto record?");
|
||||||
|
|
||||||
|
config.Rooms.Add(new RoomConfig { RoomId = roomid, AutoRecord = autoRecord });
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine("[green]Room {0} added to config[/]", roomid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeleteRoom(ConfigV2 config)
|
||||||
|
{
|
||||||
|
var toBeDeleted = AnsiConsole.Prompt(new MultiSelectionPrompt<RoomConfig>()
|
||||||
|
.Title("Delete rooms")
|
||||||
|
.NotRequired()
|
||||||
|
.UseConverter(r => r.RoomId.ToString())
|
||||||
|
.PageSize(15)
|
||||||
|
.MoreChoicesText("[grey](Move up and down to reveal more rooms)[/]")
|
||||||
|
.InstructionsText("[grey](Press [blue]<space>[/] to toggle selection, [green]<enter>[/] to delete)[/]")
|
||||||
|
.AddChoices(config.Rooms));
|
||||||
|
|
||||||
|
for (var i = 0; i < toBeDeleted.Count; i++)
|
||||||
|
config.Rooms.Remove(toBeDeleted[i]);
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine("[green]{0} rooms deleted[/]", toBeDeleted.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetJsonSchema(ConfigV2 config)
|
||||||
|
{
|
||||||
|
var selection = PromptEnumSelection<JsonSchemaSelection>();
|
||||||
|
switch (selection)
|
||||||
|
{
|
||||||
|
case JsonSchemaSelection.Default:
|
||||||
|
config.DollarSignSchema = "https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/BililiveRecorder.Core/Config/V2/config.schema.json";
|
||||||
|
break;
|
||||||
|
case JsonSchemaSelection.Custom:
|
||||||
|
config.DollarSignSchema = AnsiConsole.Prompt(new TextPrompt<string>("[green]JSON Schema[/]:").AllowEmpty());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SaveConfig(ConfigV2 config, string fullPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = ConfigParser.SaveJson(config);
|
||||||
|
using var file = new StreamWriter(File.Open(fullPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None));
|
||||||
|
file.Write(json);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[red]Write config failed[/]");
|
||||||
|
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenPaths | ExceptionFormats.ShowLinks);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool FindConfig(string path, [NotNullWhen(true)] out ConfigV2? config, out string fullPath)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
fullPath = Path.GetFullPath(path);
|
||||||
|
goto readFile;
|
||||||
|
}
|
||||||
|
else if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
fullPath = Path.GetFullPath(Path.Combine(path, ConfigParser.CONFIG_FILE_NAME));
|
||||||
|
goto readFile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[red]Path does not exist.[/]");
|
||||||
|
config = null;
|
||||||
|
fullPath = string.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile:
|
||||||
|
config = ConfigParser.LoadJson(File.ReadAllText(fullPath, Encoding.UTF8));
|
||||||
|
var result = config != null;
|
||||||
|
if (!result)
|
||||||
|
AnsiConsole.MarkupLine("[red]Load failed.\nBroken or corrupted file, or no permission.[/]");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EnumToDescriptionConverter<T>(T value) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var type = typeof(T);
|
||||||
|
var attrs = type.GetMember(Enum.GetName(type, value)!)[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||||
|
return (attrs.Length > 0) ? ((DescriptionAttribute)attrs[0]).Description : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T PromptEnumSelection<T>() where T : struct, Enum => AnsiConsole.Prompt(new SelectionPrompt<T>().AddChoices(Enum.GetValues<T>()).UseConverter(EnumToDescriptionConverter));
|
||||||
|
}
|
||||||
|
}
|
13
BililiveRecorder.Cli/Configure/JsonSchemaSelection.cs
Normal file
13
BililiveRecorder.Cli/Configure/JsonSchemaSelection.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Cli.Configure
|
||||||
|
{
|
||||||
|
public enum JsonSchemaSelection
|
||||||
|
{
|
||||||
|
[Description("https://raw.githubusercontent.com/.../config.schema.json")]
|
||||||
|
Default,
|
||||||
|
|
||||||
|
[Description("Custom")]
|
||||||
|
Custom
|
||||||
|
}
|
||||||
|
}
|
31
BililiveRecorder.Cli/Configure/RootMenuSelection.cs
Normal file
31
BililiveRecorder.Cli/Configure/RootMenuSelection.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Cli.Configure
|
||||||
|
{
|
||||||
|
public enum RootMenuSelection
|
||||||
|
{
|
||||||
|
[Description("List rooms")]
|
||||||
|
ListRooms,
|
||||||
|
|
||||||
|
[Description("Add room")]
|
||||||
|
AddRoom,
|
||||||
|
|
||||||
|
[Description("Delete room")]
|
||||||
|
DeleteRoom,
|
||||||
|
|
||||||
|
[Description("Update room config")]
|
||||||
|
SetRoomConfig,
|
||||||
|
|
||||||
|
[Description("Update global config")]
|
||||||
|
SetGlobalConfig,
|
||||||
|
|
||||||
|
[Description("Update JSON Schema")]
|
||||||
|
SetJsonSchema,
|
||||||
|
|
||||||
|
[Description("Exit and discard all changes")]
|
||||||
|
Exit,
|
||||||
|
|
||||||
|
[Description("Save and Exit")]
|
||||||
|
SaveAndExit,
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ using System.CommandLine.Invocation;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using BililiveRecorder.Cli.Configure;
|
||||||
using BililiveRecorder.Core;
|
using BililiveRecorder.Core;
|
||||||
using BililiveRecorder.Core.Config;
|
using BililiveRecorder.Core.Config;
|
||||||
using BililiveRecorder.Core.Config.V2;
|
using BililiveRecorder.Core.Config.V2;
|
||||||
|
@ -50,6 +51,7 @@ namespace BililiveRecorder.Cli
|
||||||
{
|
{
|
||||||
cmd_run,
|
cmd_run,
|
||||||
cmd_portable,
|
cmd_portable,
|
||||||
|
new ConfigureCommand(),
|
||||||
new ToolCommand()
|
new ToolCommand()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace BililiveRecorder.Core.Config
|
||||||
{
|
{
|
||||||
public class ConfigParser
|
public class ConfigParser
|
||||||
{
|
{
|
||||||
private const string CONFIG_FILE_NAME = "config.json";
|
public const string CONFIG_FILE_NAME = "config.json";
|
||||||
private static readonly ILogger logger = Log.ForContext<ConfigParser>();
|
private static readonly ILogger logger = Log.ForContext<ConfigParser>();
|
||||||
private static readonly JsonSerializerSettings settings = new JsonSerializerSettings()
|
private static readonly JsonSerializerSettings settings = new JsonSerializerSettings()
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.1.2" />
|
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.1.2" />
|
||||||
|
<PackageReference Include="Spectre.Console" Version="0.40.0" />
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.21216.1" />
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.21216.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@ using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
namespace BililiveRecorder.ToolBox
|
namespace BililiveRecorder.ToolBox
|
||||||
{
|
{
|
||||||
public class CommandResponse<TResponse> where TResponse : class
|
public class CommandResponse<TResponseData> where TResponseData : IResponseData
|
||||||
{
|
{
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public ResponseStatus Status { get; set; }
|
public ResponseStatus Status { get; set; }
|
||||||
|
|
||||||
public TResponse? Result { get; set; }
|
public TResponseData? Data { get; set; }
|
||||||
|
|
||||||
public string? ErrorMessage { get; set; }
|
public string? ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BililiveRecorder.ToolBox
|
namespace BililiveRecorder.ToolBox
|
||||||
{
|
{
|
||||||
public interface ICommandHandler<TRequest, TResponse>
|
public interface ICommandHandler<TRequest, TResponse>
|
||||||
where TRequest : ICommandRequest<TResponse>
|
where TRequest : ICommandRequest<TResponse>
|
||||||
where TResponse : class
|
where TResponse : IResponseData
|
||||||
{
|
{
|
||||||
Task<CommandResponse<TResponse>> Handle(TRequest request);
|
string Name { get; }
|
||||||
void PrintResponse(TResponse response);
|
Task<CommandResponse<TResponse>> Handle(TRequest request, CancellationToken cancellationToken, ProgressCallback? progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace BililiveRecorder.ToolBox
|
namespace BililiveRecorder.ToolBox
|
||||||
{
|
{
|
||||||
public interface ICommandRequest<TResponse>
|
public interface ICommandRequest<TResponse>
|
||||||
where TResponse : class
|
where TResponse : IResponseData
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
|
7
BililiveRecorder.ToolBox/IResponseData.cs
Normal file
7
BililiveRecorder.ToolBox/IResponseData.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace BililiveRecorder.ToolBox
|
||||||
|
{
|
||||||
|
public interface IResponseData
|
||||||
|
{
|
||||||
|
void PrintToConsole();
|
||||||
|
}
|
||||||
|
}
|
6
BililiveRecorder.ToolBox/ProgressCallback.cs
Normal file
6
BililiveRecorder.ToolBox/ProgressCallback.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.ToolBox
|
||||||
|
{
|
||||||
|
public delegate Task ProgressCallback(double progress);
|
||||||
|
}
|
|
@ -24,9 +24,9 @@ namespace BililiveRecorder.ToolBox.Tool.Analyze
|
||||||
{
|
{
|
||||||
private static readonly ILogger logger = Log.ForContext<AnalyzeHandler>();
|
private static readonly ILogger logger = Log.ForContext<AnalyzeHandler>();
|
||||||
|
|
||||||
public Task<CommandResponse<AnalyzeResponse>> Handle(AnalyzeRequest request) => this.Handle(request, default, null);
|
public string Name => "Analyze";
|
||||||
|
|
||||||
public async Task<CommandResponse<AnalyzeResponse>> Handle(AnalyzeRequest request, CancellationToken cancellationToken, Func<double, Task>? progress)
|
public async Task<CommandResponse<AnalyzeResponse>> Handle(AnalyzeRequest request, CancellationToken cancellationToken, ProgressCallback? progress)
|
||||||
{
|
{
|
||||||
FileStream? flvFileStream = null;
|
FileStream? flvFileStream = null;
|
||||||
try
|
try
|
||||||
|
@ -152,7 +152,7 @@ namespace BililiveRecorder.ToolBox.Tool.Analyze
|
||||||
return new CommandResponse<AnalyzeResponse>
|
return new CommandResponse<AnalyzeResponse>
|
||||||
{
|
{
|
||||||
Status = ResponseStatus.OK,
|
Status = ResponseStatus.OK,
|
||||||
Result = response
|
Data = response
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
|
@ -192,31 +192,6 @@ namespace BililiveRecorder.ToolBox.Tool.Analyze
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrintResponse(AnalyzeResponse response)
|
|
||||||
{
|
|
||||||
Console.Write("Input: ");
|
|
||||||
Console.WriteLine(response.InputPath);
|
|
||||||
|
|
||||||
Console.WriteLine(response.NeedFix ? "File needs repair" : "File doesn't need repair");
|
|
||||||
|
|
||||||
if (response.Unrepairable)
|
|
||||||
Console.WriteLine("File contains error(s) that are unrepairable (yet), please send sample to the author of this program.");
|
|
||||||
|
|
||||||
Console.WriteLine("Will output {0} file(s) if repaired", response.OutputFileCount);
|
|
||||||
|
|
||||||
Console.WriteLine("Types of error:");
|
|
||||||
Console.Write("Other: ");
|
|
||||||
Console.WriteLine(response.IssueTypeOther);
|
|
||||||
Console.Write("Unrepairable: ");
|
|
||||||
Console.WriteLine(response.IssueTypeUnrepairable);
|
|
||||||
Console.Write("TimestampJump: ");
|
|
||||||
Console.WriteLine(response.IssueTypeTimestampJump);
|
|
||||||
Console.Write("DecodingHeader: ");
|
|
||||||
Console.WriteLine(response.IssueTypeDecodingHeader);
|
|
||||||
Console.Write("RepeatingData: ");
|
|
||||||
Console.WriteLine(response.IssueTypeRepeatingData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AnalyzeMockFlvTagWriter : IFlvTagWriter
|
private class AnalyzeMockFlvTagWriter : IFlvTagWriter
|
||||||
{
|
{
|
||||||
public long FileSize => 0;
|
public long FileSize => 0;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
using BililiveRecorder.ToolBox.ProcessingRules;
|
using BililiveRecorder.ToolBox.ProcessingRules;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace BililiveRecorder.ToolBox.Tool.Analyze
|
namespace BililiveRecorder.ToolBox.Tool.Analyze
|
||||||
{
|
{
|
||||||
public class AnalyzeResponse
|
public class AnalyzeResponse : IResponseData
|
||||||
{
|
{
|
||||||
public string InputPath { get; set; } = string.Empty;
|
public string InputPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
@ -20,5 +21,44 @@ namespace BililiveRecorder.ToolBox.Tool.Analyze
|
||||||
public int IssueTypeTimestampOffset { get; set; }
|
public int IssueTypeTimestampOffset { get; set; }
|
||||||
public int IssueTypeDecodingHeader { get; set; }
|
public int IssueTypeDecodingHeader { get; set; }
|
||||||
public int IssueTypeRepeatingData { get; set; }
|
public int IssueTypeRepeatingData { get; set; }
|
||||||
|
|
||||||
|
public void PrintToConsole()
|
||||||
|
{
|
||||||
|
if (this.NeedFix)
|
||||||
|
AnsiConsole.Render(new FigletText("Need Fix").Color(Color.Red));
|
||||||
|
else
|
||||||
|
AnsiConsole.Render(new FigletText("All Good").Color(Color.Green));
|
||||||
|
|
||||||
|
if (this.Unrepairable)
|
||||||
|
{
|
||||||
|
AnsiConsole.Render(new Panel("This file contains error(s) that are identified as unrepairable (yet).\n" +
|
||||||
|
"Please check if you're using the newest version.\n" +
|
||||||
|
"Please consider send a sample file to the developer.")
|
||||||
|
{
|
||||||
|
Header = new PanelHeader("Important Note"),
|
||||||
|
Border = BoxBorder.Rounded,
|
||||||
|
BorderStyle = new Style(foreground: Color.Red)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.Render(new Panel(this.InputPath.EscapeMarkup())
|
||||||
|
{
|
||||||
|
Header = new PanelHeader("Input"),
|
||||||
|
Border = BoxBorder.Rounded
|
||||||
|
});
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine("Will output [lime]{0}[/] file(s) if repaired", this.OutputFileCount);
|
||||||
|
|
||||||
|
AnsiConsole.Render(new Table()
|
||||||
|
.Border(TableBorder.Rounded)
|
||||||
|
.AddColumns("Category", "Count")
|
||||||
|
.AddRow("Unrepairable", this.IssueTypeUnrepairable.ToString())
|
||||||
|
.AddRow("Other", this.IssueTypeOther.ToString())
|
||||||
|
.AddRow("TimestampJump", this.IssueTypeTimestampJump.ToString())
|
||||||
|
.AddRow("TimestampOffset", this.IssueTypeTimestampOffset.ToString())
|
||||||
|
.AddRow("DecodingHeader", this.IssueTypeDecodingHeader.ToString())
|
||||||
|
.AddRow("RepeatingData", this.IssueTypeRepeatingData.ToString())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ namespace BililiveRecorder.ToolBox.Tool.Export
|
||||||
{
|
{
|
||||||
private static readonly ILogger logger = Log.ForContext<ExportHandler>();
|
private static readonly ILogger logger = Log.ForContext<ExportHandler>();
|
||||||
|
|
||||||
public Task<CommandResponse<ExportResponse>> Handle(ExportRequest request) => this.Handle(request, default, null);
|
public string Name => "Export";
|
||||||
|
|
||||||
public async Task<CommandResponse<ExportResponse>> Handle(ExportRequest request, CancellationToken cancellationToken, Func<double, Task>? progress)
|
public async Task<CommandResponse<ExportResponse>> Handle(ExportRequest request, CancellationToken cancellationToken, ProgressCallback? progress)
|
||||||
{
|
{
|
||||||
FileStream? inputStream = null, outputStream = null;
|
FileStream? inputStream = null, outputStream = null;
|
||||||
try
|
try
|
||||||
|
@ -92,7 +92,7 @@ namespace BililiveRecorder.ToolBox.Tool.Export
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return new CommandResponse<ExportResponse> { Status = ResponseStatus.OK, Result = new ExportResponse() };
|
return new CommandResponse<ExportResponse> { Status = ResponseStatus.OK, Data = new ExportResponse() };
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
@ -131,7 +131,5 @@ namespace BililiveRecorder.ToolBox.Tool.Export
|
||||||
outputStream?.Dispose();
|
outputStream?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrintResponse(ExportResponse response) => Console.WriteLine("OK");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
namespace BililiveRecorder.ToolBox.Tool.Export
|
namespace BililiveRecorder.ToolBox.Tool.Export
|
||||||
{
|
{
|
||||||
public class ExportResponse
|
public class ExportResponse : IResponseData
|
||||||
{
|
{
|
||||||
|
public void PrintToConsole() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,9 @@ namespace BililiveRecorder.ToolBox.Tool.Fix
|
||||||
{
|
{
|
||||||
private static readonly ILogger logger = Log.ForContext<FixHandler>();
|
private static readonly ILogger logger = Log.ForContext<FixHandler>();
|
||||||
|
|
||||||
public Task<CommandResponse<FixResponse>> Handle(FixRequest request) => this.Handle(request, default, null);
|
public string Name => "Fix";
|
||||||
|
|
||||||
public async Task<CommandResponse<FixResponse>> Handle(FixRequest request, CancellationToken cancellationToken, Func<double, Task>? progress)
|
public async Task<CommandResponse<FixResponse>> Handle(FixRequest request, CancellationToken cancellationToken, ProgressCallback? progress)
|
||||||
{
|
{
|
||||||
FileStream? flvFileStream = null;
|
FileStream? flvFileStream = null;
|
||||||
try
|
try
|
||||||
|
@ -194,7 +194,7 @@ namespace BililiveRecorder.ToolBox.Tool.Fix
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return new CommandResponse<FixResponse> { Status = ResponseStatus.OK, Result = response };
|
return new CommandResponse<FixResponse> { Status = ResponseStatus.OK, Data = response };
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
@ -233,37 +233,6 @@ namespace BililiveRecorder.ToolBox.Tool.Fix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrintResponse(FixResponse response)
|
|
||||||
{
|
|
||||||
Console.Write("Input: ");
|
|
||||||
Console.WriteLine(response.InputPath);
|
|
||||||
|
|
||||||
Console.WriteLine(response.NeedFix ? "File needs repair" : "File doesn't need repair");
|
|
||||||
|
|
||||||
if (response.Unrepairable)
|
|
||||||
Console.WriteLine("File contains error(s) that are unrepairable (yet), please send sample to the author of this program.");
|
|
||||||
|
|
||||||
Console.WriteLine("{0} file(s) written", response.OutputFileCount);
|
|
||||||
|
|
||||||
foreach (var path in response.OutputPaths)
|
|
||||||
{
|
|
||||||
Console.Write(" ");
|
|
||||||
Console.WriteLine(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Types of error:");
|
|
||||||
Console.Write("Other: ");
|
|
||||||
Console.WriteLine(response.IssueTypeOther);
|
|
||||||
Console.Write("Unrepairable: ");
|
|
||||||
Console.WriteLine(response.IssueTypeUnrepairable);
|
|
||||||
Console.Write("TimestampJump: ");
|
|
||||||
Console.WriteLine(response.IssueTypeTimestampJump);
|
|
||||||
Console.Write("DecodingHeader: ");
|
|
||||||
Console.WriteLine(response.IssueTypeDecodingHeader);
|
|
||||||
Console.Write("RepeatingData: ");
|
|
||||||
Console.WriteLine(response.IssueTypeRepeatingData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AutoFixFlvWriterTargetProvider : IFlvWriterTargetProvider
|
private class AutoFixFlvWriterTargetProvider : IFlvWriterTargetProvider
|
||||||
{
|
{
|
||||||
private readonly string pathTemplate;
|
private readonly string pathTemplate;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using BililiveRecorder.ToolBox.ProcessingRules;
|
using BililiveRecorder.ToolBox.ProcessingRules;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace BililiveRecorder.ToolBox.Tool.Fix
|
namespace BililiveRecorder.ToolBox.Tool.Fix
|
||||||
{
|
{
|
||||||
public class FixResponse
|
public class FixResponse : IResponseData
|
||||||
{
|
{
|
||||||
public string InputPath { get; set; } = string.Empty;
|
public string InputPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
@ -23,5 +24,48 @@ namespace BililiveRecorder.ToolBox.Tool.Fix
|
||||||
public int IssueTypeTimestampOffset { get; set; }
|
public int IssueTypeTimestampOffset { get; set; }
|
||||||
public int IssueTypeDecodingHeader { get; set; }
|
public int IssueTypeDecodingHeader { get; set; }
|
||||||
public int IssueTypeRepeatingData { get; set; }
|
public int IssueTypeRepeatingData { get; set; }
|
||||||
|
|
||||||
|
public void PrintToConsole()
|
||||||
|
{
|
||||||
|
AnsiConsole.Render(new FigletText("Done").Color(Color.Green));
|
||||||
|
|
||||||
|
if (this.Unrepairable)
|
||||||
|
{
|
||||||
|
AnsiConsole.Render(new Panel("This file contains error(s) that are identified as unrepairable (yet).\n" +
|
||||||
|
"Please check if you're using the newest version.\n" +
|
||||||
|
"Please consider send a sample file to the developer.")
|
||||||
|
{
|
||||||
|
Header = new PanelHeader("Important Note"),
|
||||||
|
Border = BoxBorder.Rounded,
|
||||||
|
BorderStyle = new Style(foreground: Color.Red)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.Render(new Panel(this.InputPath.EscapeMarkup())
|
||||||
|
{
|
||||||
|
Header = new PanelHeader("Input"),
|
||||||
|
Border = BoxBorder.Rounded
|
||||||
|
});
|
||||||
|
|
||||||
|
var table_output = new Table()
|
||||||
|
.Border(TableBorder.Rounded)
|
||||||
|
.AddColumns("Output");
|
||||||
|
|
||||||
|
for (var i = 0; i < this.OutputPaths.Length; i++)
|
||||||
|
table_output.AddRow(this.OutputPaths[i]);
|
||||||
|
|
||||||
|
AnsiConsole.Render(table_output);
|
||||||
|
|
||||||
|
AnsiConsole.Render(new Table()
|
||||||
|
.Border(TableBorder.Rounded)
|
||||||
|
.AddColumns("Category", "Count")
|
||||||
|
.AddRow("Unrepairable", this.IssueTypeUnrepairable.ToString())
|
||||||
|
.AddRow("Other", this.IssueTypeOther.ToString())
|
||||||
|
.AddRow("TimestampJump", this.IssueTypeTimestampJump.ToString())
|
||||||
|
.AddRow("TimestampOffset", this.IssueTypeTimestampOffset.ToString())
|
||||||
|
.AddRow("DecodingHeader", this.IssueTypeDecodingHeader.ToString())
|
||||||
|
.AddRow("RepeatingData", this.IssueTypeRepeatingData.ToString())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ using BililiveRecorder.ToolBox.Tool.Analyze;
|
||||||
using BililiveRecorder.ToolBox.Tool.Export;
|
using BililiveRecorder.ToolBox.Tool.Export;
|
||||||
using BililiveRecorder.ToolBox.Tool.Fix;
|
using BililiveRecorder.ToolBox.Tool.Fix;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace BililiveRecorder.ToolBox
|
namespace BililiveRecorder.ToolBox
|
||||||
{
|
{
|
||||||
|
@ -34,7 +35,7 @@ namespace BililiveRecorder.ToolBox
|
||||||
private void RegisterCommand<THandler, TRequest, TResponse>(string name, string? description, Action<Command> configure)
|
private void RegisterCommand<THandler, TRequest, TResponse>(string name, string? description, Action<Command> configure)
|
||||||
where THandler : ICommandHandler<TRequest, TResponse>
|
where THandler : ICommandHandler<TRequest, TResponse>
|
||||||
where TRequest : ICommandRequest<TResponse>
|
where TRequest : ICommandRequest<TResponse>
|
||||||
where TResponse : class
|
where TResponse : IResponseData
|
||||||
{
|
{
|
||||||
var cmd = new Command(name, description)
|
var cmd = new Command(name, description)
|
||||||
{
|
{
|
||||||
|
@ -49,32 +50,77 @@ namespace BililiveRecorder.ToolBox
|
||||||
private static async Task<int> RunSubCommand<THandler, TRequest, TResponse>(TRequest request, bool json, bool jsonIndented)
|
private static async Task<int> RunSubCommand<THandler, TRequest, TResponse>(TRequest request, bool json, bool jsonIndented)
|
||||||
where THandler : ICommandHandler<TRequest, TResponse>
|
where THandler : ICommandHandler<TRequest, TResponse>
|
||||||
where TRequest : ICommandRequest<TResponse>
|
where TRequest : ICommandRequest<TResponse>
|
||||||
where TResponse : class
|
where TResponse : IResponseData
|
||||||
{
|
{
|
||||||
|
var isInteractive = !(json || jsonIndented);
|
||||||
var handler = Activator.CreateInstance<THandler>();
|
var handler = Activator.CreateInstance<THandler>();
|
||||||
|
|
||||||
var response = await handler.Handle(request).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (json || jsonIndented)
|
CommandResponse<TResponse>? response;
|
||||||
|
if (isInteractive)
|
||||||
{
|
{
|
||||||
var json_str = JsonConvert.SerializeObject(response, jsonIndented ? Formatting.Indented : Formatting.None);
|
response = await AnsiConsole
|
||||||
Console.WriteLine(json_str);
|
.Progress()
|
||||||
|
.Columns(new ProgressColumn[]
|
||||||
|
{
|
||||||
|
new TaskDescriptionColumn(),
|
||||||
|
new ProgressBarColumn(),
|
||||||
|
new PercentageColumn(),
|
||||||
|
new SpinnerColumn(Spinner.Known.Dots10),
|
||||||
|
})
|
||||||
|
.StartAsync(async ctx =>
|
||||||
|
{
|
||||||
|
var t = ctx.AddTask(handler.Name);
|
||||||
|
t.MaxValue = 1d;
|
||||||
|
var r = await handler.Handle(request, default, async p => t.Value = p).ConfigureAwait(false);
|
||||||
|
t.Value = 1d;
|
||||||
|
return r;
|
||||||
|
})
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
response = await handler.Handle(request, default, null).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInteractive)
|
||||||
{
|
{
|
||||||
if (response.Status == ResponseStatus.OK)
|
if (response.Status == ResponseStatus.OK)
|
||||||
{
|
{
|
||||||
handler.PrintResponse(response.Result!);
|
response.Data?.PrintToConsole();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.Write("Error: ");
|
|
||||||
Console.WriteLine(response.Status);
|
|
||||||
Console.WriteLine(response.ErrorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AnsiConsole.Render(new FigletText("Error").Color(Color.Red));
|
||||||
|
|
||||||
|
var errorInfo = new Table
|
||||||
|
{
|
||||||
|
Border = TableBorder.Rounded
|
||||||
|
};
|
||||||
|
errorInfo.AddColumn(new TableColumn("Error Code").Centered());
|
||||||
|
errorInfo.AddColumn(new TableColumn("Error Message").Centered());
|
||||||
|
errorInfo.AddRow("[red]" + response.Status.ToString().EscapeMarkup() + "[/]", "[red]" + (response.ErrorMessage ?? string.Empty) + "[/]");
|
||||||
|
AnsiConsole.Render(errorInfo);
|
||||||
|
|
||||||
|
if (response.Exception is not null)
|
||||||
|
AnsiConsole.Render(new Panel(response.Exception.GetRenderable(ExceptionFormats.ShortenPaths | ExceptionFormats.ShowLinks))
|
||||||
|
{
|
||||||
|
Header = new PanelHeader("Exception Info"),
|
||||||
|
Border = BoxBorder.Rounded
|
||||||
|
});
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var json_str = JsonConvert.SerializeObject(response, jsonIndented ? Formatting.Indented : Formatting.None);
|
||||||
|
Console.WriteLine(json_str);
|
||||||
|
|
||||||
|
return response.Status == ResponseStatus.OK ? 0 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user