mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 11:42:22 +08:00
Core: Refactor file name templateing
This commit is contained in:
parent
bbada97219
commit
0b8cf27ce9
|
@ -18,7 +18,7 @@ namespace BililiveRecorder.Core.Config.V3
|
|||
public bool DisableConfigSave { get; set; } = false; // for CLI
|
||||
}
|
||||
|
||||
public partial class RoomConfig
|
||||
public partial class RoomConfig : IFileNameConfig
|
||||
{
|
||||
public RoomConfig() : base(x => x.AutoMap(p => new[] { "Has" + p.Name }))
|
||||
{ }
|
||||
|
@ -28,7 +28,7 @@ namespace BililiveRecorder.Core.Config.V3
|
|||
public string? WorkDirectory => this.GetPropertyValue<string>();
|
||||
}
|
||||
|
||||
public partial class GlobalConfig
|
||||
public partial class GlobalConfig : IFileNameConfig
|
||||
{
|
||||
public GlobalConfig() : base(x => x.AutoMap(p => new[] { "Has" + p.Name }))
|
||||
{
|
||||
|
|
9
BililiveRecorder.Core/Config/V3/IFileNameConfig.cs
Normal file
9
BililiveRecorder.Core/Config/V3/IFileNameConfig.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace BililiveRecorder.Core.Config.V3
|
||||
{
|
||||
public interface IFileNameConfig
|
||||
{
|
||||
public string? FileNameRecordTemplate { get; }
|
||||
|
||||
public string? WorkDirectory { get; }
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ using BililiveRecorder.Core.Config.V3;
|
|||
using BililiveRecorder.Core.Danmaku;
|
||||
using BililiveRecorder.Core.Recording;
|
||||
using BililiveRecorder.Core.Scripting;
|
||||
using BililiveRecorder.Core.Templating;
|
||||
using BililiveRecorder.Flv;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Polly.Registry;
|
||||
|
@ -46,7 +45,6 @@ namespace BililiveRecorder.DependencyInjection
|
|||
;
|
||||
|
||||
public static IServiceCollection AddRecorderRecording(this IServiceCollection services) => services
|
||||
.AddSingleton<FileNameGenerator>()
|
||||
.AddScoped<IRecordTaskFactory, RecordTaskFactory>()
|
||||
.AddScoped<IFlvProcessingContextWriterFactory, FlvProcessingContextWriterWithFileWriterFactory>()
|
||||
.AddScoped<IFlvTagReaderFactory, FlvTagReaderFactory>()
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
|||
using BililiveRecorder.Core.Api;
|
||||
using BililiveRecorder.Core.Event;
|
||||
using BililiveRecorder.Core.Scripting;
|
||||
using BililiveRecorder.Core.Templating;
|
||||
using Serilog;
|
||||
|
||||
namespace BililiveRecorder.Core.Recording
|
||||
|
@ -17,12 +16,10 @@ namespace BililiveRecorder.Core.Recording
|
|||
public RawDataRecordTask(IRoom room,
|
||||
ILogger logger,
|
||||
IApiClient apiClient,
|
||||
FileNameGenerator fileNameGenerator,
|
||||
UserScriptRunner userScriptRunner)
|
||||
: base(room: room,
|
||||
logger: logger?.ForContext<RawDataRecordTask>().ForContext(LoggingContext.RoomId, room.RoomConfig.RoomId)!,
|
||||
apiClient: apiClient,
|
||||
fileNameGenerator: fileNameGenerator,
|
||||
userScriptRunner: userScriptRunner)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -52,13 +52,14 @@ namespace BililiveRecorder.Core.Recording
|
|||
private DateTimeOffset ioStatsLastTrigger;
|
||||
private TimeSpan durationSinceNoDataReceived;
|
||||
|
||||
protected RecordTaskBase(IRoom room, ILogger logger, IApiClient apiClient, FileNameGenerator fileNameGenerator, UserScriptRunner userScriptRunner)
|
||||
protected RecordTaskBase(IRoom room, ILogger logger, IApiClient apiClient, UserScriptRunner userScriptRunner)
|
||||
{
|
||||
this.room = room ?? throw new ArgumentNullException(nameof(room));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.fileNameGenerator = fileNameGenerator ?? throw new ArgumentNullException(nameof(fileNameGenerator));
|
||||
this.userScriptRunner = userScriptRunner ?? throw new ArgumentNullException(nameof(userScriptRunner));
|
||||
|
||||
this.fileNameGenerator = new FileNameGenerator(room.RoomConfig, logger);
|
||||
this.ct = this.cts.Token;
|
||||
|
||||
this.timer.Elapsed += this.Timer_Elapsed_TriggerIOStats;
|
||||
|
@ -174,17 +175,22 @@ namespace BililiveRecorder.Core.Recording
|
|||
}
|
||||
}
|
||||
|
||||
protected (string fullPath, string relativePath) CreateFileName() => this.fileNameGenerator.CreateFilePath(new FileNameTemplateContext
|
||||
protected (string fullPath, string relativePath) CreateFileName()
|
||||
{
|
||||
Name = FileNameGenerator.RemoveInvalidFileName(this.room.Name, ignore_slash: false),
|
||||
Title = FileNameGenerator.RemoveInvalidFileName(this.room.Title, ignore_slash: false),
|
||||
RoomId = this.room.RoomConfig.RoomId,
|
||||
ShortId = this.room.ShortId,
|
||||
AreaParent = FileNameGenerator.RemoveInvalidFileName(this.room.AreaNameParent, ignore_slash: false),
|
||||
AreaChild = FileNameGenerator.RemoveInvalidFileName(this.room.AreaNameChild, ignore_slash: false),
|
||||
Qn = this.qn,
|
||||
Json = this.room.RawBilibiliApiJsonData,
|
||||
});
|
||||
var output = this.fileNameGenerator.CreateFilePath(new FileNameTemplateContext
|
||||
{
|
||||
Name = FileNameGenerator.RemoveInvalidFileName(this.room.Name, ignore_slash: false),
|
||||
Title = FileNameGenerator.RemoveInvalidFileName(this.room.Title, ignore_slash: false),
|
||||
RoomId = this.room.RoomConfig.RoomId,
|
||||
ShortId = this.room.ShortId,
|
||||
AreaParent = FileNameGenerator.RemoveInvalidFileName(this.room.AreaNameParent, ignore_slash: false),
|
||||
AreaChild = FileNameGenerator.RemoveInvalidFileName(this.room.AreaNameChild, ignore_slash: false),
|
||||
Qn = this.qn,
|
||||
Json = this.room.RawBilibiliApiJsonData,
|
||||
});
|
||||
|
||||
return (output.FullPath!, output.RelativePath);
|
||||
}
|
||||
|
||||
#region Api Requests
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ using BililiveRecorder.Core.Config;
|
|||
using BililiveRecorder.Core.Event;
|
||||
using BililiveRecorder.Core.ProcessingRules;
|
||||
using BililiveRecorder.Core.Scripting;
|
||||
using BililiveRecorder.Core.Templating;
|
||||
using BililiveRecorder.Flv;
|
||||
using BililiveRecorder.Flv.Amf;
|
||||
using BililiveRecorder.Flv.Pipeline;
|
||||
|
@ -44,12 +43,10 @@ namespace BililiveRecorder.Core.Recording
|
|||
IFlvTagReaderFactory flvTagReaderFactory,
|
||||
ITagGroupReaderFactory tagGroupReaderFactory,
|
||||
IFlvProcessingContextWriterFactory writerFactory,
|
||||
FileNameGenerator fileNameGenerator,
|
||||
UserScriptRunner userScriptRunner)
|
||||
: base(room: room,
|
||||
logger: logger?.ForContext<StandardRecordTask>().ForContext(LoggingContext.RoomId, room.RoomConfig.RoomId)!,
|
||||
apiClient: apiClient,
|
||||
fileNameGenerator: fileNameGenerator,
|
||||
userScriptRunner: userScriptRunner)
|
||||
{
|
||||
this.flvTagReaderFactory = flvTagReaderFactory ?? throw new ArgumentNullException(nameof(flvTagReaderFactory));
|
||||
|
|
|
@ -7,6 +7,7 @@ using Fluid.Ast;
|
|||
using Fluid.Values;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
|
||||
namespace BililiveRecorder.Core.Templating
|
||||
{
|
||||
|
@ -15,8 +16,6 @@ namespace BililiveRecorder.Core.Templating
|
|||
// TODO: 需要改得更通用一些
|
||||
// 日志不应该一定绑定到一个直播间上
|
||||
|
||||
private static readonly ILogger logger = Log.Logger.ForContext<FileNameGenerator>();
|
||||
|
||||
private static readonly FluidParser parser;
|
||||
private static readonly IFluidTemplate defaultTemplate;
|
||||
|
||||
|
@ -39,8 +38,8 @@ namespace BililiveRecorder.Core.Templating
|
|||
}
|
||||
}
|
||||
|
||||
private readonly GlobalConfig config;
|
||||
private IFluidTemplate? template;
|
||||
private readonly IFileNameConfig config;
|
||||
private readonly ILogger logger;
|
||||
|
||||
static FileNameGenerator()
|
||||
{
|
||||
|
@ -72,32 +71,22 @@ namespace BililiveRecorder.Core.Templating
|
|||
defaultTemplate = parser.Parse(DefaultConfig.Instance.FileNameRecordTemplate);
|
||||
}
|
||||
|
||||
public FileNameGenerator(GlobalConfig config)
|
||||
public FileNameGenerator(IFileNameConfig config, ILogger? logger)
|
||||
{
|
||||
this.config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
|
||||
config.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(config.FileNameRecordTemplate))
|
||||
{
|
||||
this.UpdateTemplate();
|
||||
}
|
||||
};
|
||||
|
||||
this.UpdateTemplate();
|
||||
this.logger = logger?.ForContext<FileNameGenerator>() ?? Logger.None;
|
||||
}
|
||||
|
||||
private void UpdateTemplate()
|
||||
public FileNameTemplateOutput CreateFilePath(FileNameTemplateContext data)
|
||||
{
|
||||
if (!parser.TryParse(this.config.FileNameRecordTemplate, out var template, out var error))
|
||||
{
|
||||
logger.Warning("文件名模板格式不正确,请修改: {ParserError}", error);
|
||||
}
|
||||
this.template = template;
|
||||
}
|
||||
var status = FileNameTemplateStatus.Success;
|
||||
string? errorMessage = null;
|
||||
string relativePath;
|
||||
string? fullPath;
|
||||
|
||||
var workDirectory = this.config.WorkDirectory;
|
||||
var skipFullPath = workDirectory is null;
|
||||
|
||||
public (string fullPath, string relativePath) CreateFilePath(FileNameTemplateContext data)
|
||||
{
|
||||
var now = DateTimeOffset.Now;
|
||||
var templateOptions = new TemplateOptions
|
||||
{
|
||||
|
@ -105,49 +94,57 @@ namespace BililiveRecorder.Core.Templating
|
|||
};
|
||||
templateOptions.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;
|
||||
templateOptions.ValueConverters.Add(o => o is JContainer j ? new JContainerValue(j) : null);
|
||||
templateOptions.Filters.AddFilter("format_qn", static (FluidValue input, FilterArguments arguments, TemplateContext context)
|
||||
=> new StringValue(StreamQualityNumber.MapToString((int)input.ToNumberValue()))
|
||||
);
|
||||
templateOptions.Filters.AddFilter("format_qn",
|
||||
static (FluidValue input, FilterArguments arguments, TemplateContext context) => new StringValue(StreamQualityNumber.MapToString((int)input.ToNumberValue())));
|
||||
|
||||
var context = new TemplateContext(data, templateOptions);
|
||||
|
||||
var workDirectory = this.config.WorkDirectory!;
|
||||
|
||||
if (this.template is not { } t)
|
||||
if (!parser.TryParse(this.config.FileNameRecordTemplate, out var template, out var error))
|
||||
{
|
||||
logger.ForContext(LoggingContext.RoomId, data.RoomId).Warning("文件名模板格式不正确,请检查设置。将写入到默认路径。");
|
||||
this.logger.Warning("文件名模板格式不正确,请修改: {ParserError}", error);
|
||||
errorMessage = "文件名模板格式不正确,请修改: " + error;
|
||||
status = FileNameTemplateStatus.TemplateError;
|
||||
goto returnDefaultPath;
|
||||
}
|
||||
|
||||
var relativePath = t.Render(context);
|
||||
relativePath = template.Render(context);
|
||||
relativePath = RemoveInvalidFileName(relativePath);
|
||||
var fullPath = Path.GetFullPath(Path.Combine(workDirectory, relativePath));
|
||||
|
||||
if (!CheckIsWithinPath(workDirectory!, Path.GetDirectoryName(fullPath)))
|
||||
fullPath = skipFullPath ? null : Path.GetFullPath(Path.Combine(workDirectory, relativePath));
|
||||
|
||||
if (!skipFullPath && !CheckIsWithinPath(workDirectory!, Path.GetDirectoryName(fullPath)))
|
||||
{
|
||||
logger.ForContext(LoggingContext.RoomId, data.RoomId).Warning("录制文件位置超出允许范围,请检查设置。将写入到默认路径。");
|
||||
this.logger.Warning("录制文件位置超出允许范围,请检查设置。将写入到默认路径。");
|
||||
status = FileNameTemplateStatus.OutOfRange;
|
||||
errorMessage = "录制文件位置超出允许范围";
|
||||
goto returnDefaultPath;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(fullPath);
|
||||
var ext = Path.GetExtension(relativePath);
|
||||
if (!ext.Equals(".flv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
logger.ForContext(LoggingContext.RoomId, data.RoomId).Warning("录播姬只支持 FLV 文件格式,将在录制文件后缀名 {ExtensionName} 后添加 .flv。", ext);
|
||||
this.logger.Warning("录播姬只支持 FLV 文件格式,将在录制文件后缀名 {ExtensionName} 后添加 {DotFlv}。", ext, ".flv");
|
||||
relativePath += ".flv";
|
||||
fullPath += ".flv";
|
||||
|
||||
if (!skipFullPath)
|
||||
fullPath += ".flv";
|
||||
}
|
||||
|
||||
if (File.Exists(fullPath))
|
||||
if (!skipFullPath && File.Exists(fullPath))
|
||||
{
|
||||
logger.ForContext(LoggingContext.RoomId, data.RoomId).Warning("录制文件名冲突,请检查设置。将写入到默认路径。");
|
||||
this.logger.Warning("录制文件名冲突,将写入到默认路径。");
|
||||
status = FileNameTemplateStatus.FileConflict;
|
||||
errorMessage = "录制文件名冲突";
|
||||
goto returnDefaultPath;
|
||||
}
|
||||
|
||||
return (fullPath, relativePath);
|
||||
return new FileNameTemplateOutput(status, errorMessage, relativePath, fullPath);
|
||||
|
||||
returnDefaultPath:
|
||||
var defaultRelativePath = RemoveInvalidFileName(defaultTemplate.Render(context));
|
||||
return (Path.GetFullPath(Path.Combine(this.config.WorkDirectory, defaultRelativePath)), defaultRelativePath);
|
||||
var defaultFullPath = skipFullPath ? null : Path.GetFullPath(Path.Combine(workDirectory, defaultRelativePath));
|
||||
|
||||
return new FileNameTemplateOutput(status, errorMessage, defaultRelativePath, defaultFullPath);
|
||||
}
|
||||
|
||||
private class JContainerValue : ObjectValueBase
|
||||
|
@ -238,22 +235,29 @@ namespace BililiveRecorder.Core.Templating
|
|||
return input;
|
||||
}
|
||||
|
||||
private static readonly char[] separator = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
|
||||
|
||||
internal static bool CheckIsWithinPath(string parent, string child)
|
||||
{
|
||||
if (parent is null || child is null)
|
||||
var fullParent = Path.GetFullPath(parent);
|
||||
var fullChild = Path.GetFullPath(child);
|
||||
|
||||
var parentSegments = fullParent.Split(separator, StringSplitOptions.None).AsSpan();
|
||||
if (parentSegments[parentSegments.Length - 1] == "")
|
||||
{
|
||||
parentSegments = parentSegments.Slice(0, parentSegments.Length - 1);
|
||||
}
|
||||
|
||||
var childSegments = fullChild.Split(separator, StringSplitOptions.None).AsSpan();
|
||||
if (childSegments[childSegments.Length - 1] == "")
|
||||
{
|
||||
childSegments = childSegments.Slice(0, childSegments.Length - 1);
|
||||
}
|
||||
|
||||
if (parentSegments.Length >= childSegments.Length)
|
||||
return false;
|
||||
|
||||
parent = parent.Replace('/', '\\');
|
||||
if (!parent.EndsWith("\\"))
|
||||
parent += "\\";
|
||||
parent = Path.GetFullPath(parent);
|
||||
|
||||
child = child.Replace('/', '\\');
|
||||
if (!child.EndsWith("\\"))
|
||||
child += "\\";
|
||||
child = Path.GetFullPath(child);
|
||||
|
||||
return child.StartsWith(parent, StringComparison.Ordinal);
|
||||
return childSegments.Slice(0, parentSegments.Length).SequenceEqual(parentSegments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
BililiveRecorder.Core/Templating/FileNameTemplateOutput.cs
Normal file
23
BililiveRecorder.Core/Templating/FileNameTemplateOutput.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace BililiveRecorder.Core.Templating
|
||||
{
|
||||
public readonly struct FileNameTemplateOutput
|
||||
{
|
||||
public FileNameTemplateOutput(FileNameTemplateStatus status, string? errorMessage, string relativePath, string? fullPath)
|
||||
{
|
||||
this.Status = status;
|
||||
this.ErrorMessage = errorMessage;
|
||||
this.RelativePath = relativePath ?? throw new ArgumentNullException(nameof(relativePath));
|
||||
this.FullPath = fullPath;
|
||||
}
|
||||
|
||||
public FileNameTemplateStatus Status { get; }
|
||||
|
||||
public string? ErrorMessage { get; }
|
||||
|
||||
public string RelativePath { get; }
|
||||
|
||||
public string? FullPath { get; }
|
||||
}
|
||||
}
|
10
BililiveRecorder.Core/Templating/FileNameTemplateStatus.cs
Normal file
10
BililiveRecorder.Core/Templating/FileNameTemplateStatus.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace BililiveRecorder.Core.Templating
|
||||
{
|
||||
public enum FileNameTemplateStatus
|
||||
{
|
||||
Success = 0,
|
||||
TemplateError,
|
||||
OutOfRange,
|
||||
FileConflict,
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
l:ResxLocalizationProvider.DefaultDictionary="Strings"
|
||||
xmlns:c="clr-namespace:BililiveRecorder.WPF.Controls"
|
||||
xmlns:m="clr-namespace:BililiveRecorder.WPF.Models"
|
||||
xmlns:t="clr-namespace:BililiveRecorder.Core.Templating;assembly=BililiveRecorder.Core"
|
||||
xmlns:local="clr-namespace:BililiveRecorder.WPF.Pages"
|
||||
xmlns:config="clr-namespace:BililiveRecorder.Core.Config;assembly=BililiveRecorder.Core"
|
||||
xmlns:configv3="clr-namespace:BililiveRecorder.Core.Config.V3;assembly=BililiveRecorder.Core"
|
||||
|
@ -96,7 +97,14 @@
|
|||
<TextBox Text="{Binding FileNameRecordTemplate,Delay=500}" ui:TextBoxHelper.IsDeleteButtonVisible="False"/>
|
||||
</c:SettingWithDefault>
|
||||
<Button Margin="0,10,0,5" Content="测试" Click="TestFileNameTemplate_Button_Click"/>
|
||||
<TextBox Visibility="Collapsed" IsReadOnly="True" x:Name="FileNameTestResult"/>
|
||||
<Border x:Name="FileNameTestResultArea" d:DataContext="{d:DesignInstance Type=t:FileNameTemplateOutput}" Visibility="Collapsed"
|
||||
BorderBrush="{DynamicResource SystemControlBackgroundBaseMediumBrush}" CornerRadius="5" BorderThickness="1" Padding="5">
|
||||
<StackPanel Orientation="Vertical" Margin="0">
|
||||
<TextBlock Text="测试结果:"/>
|
||||
<TextBlock Text="{Binding ErrorMessage,Mode=OneWay}" Margin="0,5"/>
|
||||
<TextBox IsReadOnly="True" Text="{Binding RelativePath,Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<GroupBox Header="录制画质">
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -23,19 +23,19 @@ namespace BililiveRecorder.Web.Api
|
|||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("generateFileName")]
|
||||
public ActionResult<string> GenerateFileName([FromBody] GenerateFileNameInput input)
|
||||
public ActionResult<FileNameTemplateOutput> GenerateFileName([FromBody] GenerateFileNameInput input)
|
||||
{
|
||||
var config = new GlobalConfig()
|
||||
{
|
||||
WorkDirectory = "/",
|
||||
FileNameRecordTemplate = input.Template
|
||||
};
|
||||
var generator = new FileNameGenerator(config);
|
||||
var generator = new FileNameGenerator(config, null);
|
||||
|
||||
var context = this.mapper.Map<FileNameTemplateContext>(input.Context);
|
||||
|
||||
var (_, relativePath) = generator.CreateFilePath(context);
|
||||
return relativePath;
|
||||
var output = generator.CreateFilePath(context);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ namespace BililiveRecorder.Core.Config.V3
|
|||
public bool WpfShowTitleAndArea { get; }
|
||||
}
|
||||
[Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)]
|
||||
public sealed class GlobalConfig : HierarchicalPropertyDefault.HierarchicalObject<BililiveRecorder.Core.Config.V3.DefaultConfig, BililiveRecorder.Core.Config.V3.GlobalConfig>
|
||||
public sealed class GlobalConfig : HierarchicalPropertyDefault.HierarchicalObject<BililiveRecorder.Core.Config.V3.DefaultConfig, BililiveRecorder.Core.Config.V3.GlobalConfig>, BililiveRecorder.Core.Config.V3.IFileNameConfig
|
||||
{
|
||||
public GlobalConfig() { }
|
||||
public string? Cookie { get; set; }
|
||||
|
@ -201,8 +201,13 @@ namespace BililiveRecorder.Core.Config.V3
|
|||
public string? WorkDirectory { get; set; }
|
||||
public bool WpfShowTitleAndArea { get; set; }
|
||||
}
|
||||
public interface IFileNameConfig
|
||||
{
|
||||
string? FileNameRecordTemplate { get; }
|
||||
string? WorkDirectory { get; }
|
||||
}
|
||||
[Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)]
|
||||
public sealed class RoomConfig : HierarchicalPropertyDefault.HierarchicalObject<BililiveRecorder.Core.Config.V3.GlobalConfig, BililiveRecorder.Core.Config.V3.RoomConfig>
|
||||
public sealed class RoomConfig : HierarchicalPropertyDefault.HierarchicalObject<BililiveRecorder.Core.Config.V3.GlobalConfig, BililiveRecorder.Core.Config.V3.RoomConfig>, BililiveRecorder.Core.Config.V3.IFileNameConfig
|
||||
{
|
||||
public RoomConfig() { }
|
||||
public bool AutoRecord { get; set; }
|
||||
|
@ -474,11 +479,8 @@ namespace BililiveRecorder.Core.Templating
|
|||
{
|
||||
public sealed class FileNameGenerator
|
||||
{
|
||||
public FileNameGenerator(BililiveRecorder.Core.Config.V3.GlobalConfig config) { }
|
||||
[return: System.Runtime.CompilerServices.TupleElementNames(new string[] {
|
||||
"fullPath",
|
||||
"relativePath"})]
|
||||
public System.ValueTuple<string, string> CreateFilePath(BililiveRecorder.Core.Templating.FileNameTemplateContext data) { }
|
||||
public FileNameGenerator(BililiveRecorder.Core.Config.V3.IFileNameConfig config, Serilog.ILogger? logger) { }
|
||||
public BililiveRecorder.Core.Templating.FileNameTemplateOutput CreateFilePath(BililiveRecorder.Core.Templating.FileNameTemplateContext data) { }
|
||||
}
|
||||
public class FileNameTemplateContext
|
||||
{
|
||||
|
@ -492,6 +494,21 @@ namespace BililiveRecorder.Core.Templating
|
|||
public int ShortId { get; set; }
|
||||
public string Title { get; set; }
|
||||
}
|
||||
public readonly struct FileNameTemplateOutput
|
||||
{
|
||||
public FileNameTemplateOutput(BililiveRecorder.Core.Templating.FileNameTemplateStatus status, string? errorMessage, string relativePath, string? fullPath) { }
|
||||
public string? ErrorMessage { get; }
|
||||
public string? FullPath { get; }
|
||||
public string RelativePath { get; }
|
||||
public BililiveRecorder.Core.Templating.FileNameTemplateStatus Status { get; }
|
||||
}
|
||||
public enum FileNameTemplateStatus
|
||||
{
|
||||
Success = 0,
|
||||
TemplateError = 1,
|
||||
OutOfRange = 2,
|
||||
FileConflict = 3,
|
||||
}
|
||||
}
|
||||
namespace BililiveRecorder.DependencyInjection
|
||||
{
|
||||
|
@ -502,4 +519,4 @@ namespace BililiveRecorder.DependencyInjection
|
|||
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddRecorderConfig(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, BililiveRecorder.Core.Config.V3.ConfigV3 config) { }
|
||||
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddRecorderRecording(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,70 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using BililiveRecorder.Core.Templating;
|
||||
using Xunit;
|
||||
|
||||
namespace BililiveRecorder.Core.UnitTests.Recording
|
||||
{
|
||||
public class CheckIsWithinPathTests
|
||||
{
|
||||
[Theory(Skip = "Path 差异")]
|
||||
[InlineData(@"C:\", @"C:\", false)]
|
||||
[InlineData(@"C:", @"C:\foo", true)]
|
||||
[InlineData(@"C:\", @"C:\foo", true)]
|
||||
[InlineData(@"C:\foo", @"C:\foo", false)]
|
||||
[InlineData(@"C:\foo\", @"C:\foo", false)]
|
||||
[InlineData(@"C:\foo", @"C:\foo\", true)]
|
||||
[InlineData(@"C:\foo\", @"C:\foo\bar\", true)]
|
||||
[InlineData(@"C:\foo\", @"C:\foo\bar", true)]
|
||||
[InlineData(@"C:\foo", @"C:\FOO\bar", false)]
|
||||
[InlineData(@"C:\foo", @"C:/foo/bar", true)]
|
||||
[InlineData(@"C:\foo", @"C:\foobar", false)]
|
||||
[InlineData(@"C:\foo", @"C:\foobar\baz", false)]
|
||||
[InlineData(@"C:\foo\", @"C:\foobar\baz", false)]
|
||||
[InlineData(@"C:\foobar", @"C:\foo\bar", false)]
|
||||
[InlineData(@"C:\foobar\", @"C:\foo\bar", false)]
|
||||
[InlineData(@"C:\foo", @"C:\foo\..\bar\baz", false)]
|
||||
[InlineData(@"C:\bar", @"C:\foo\..\bar\baz", true)]
|
||||
[InlineData(@"C:\barr", @"C:\foo\..\bar\baz", false)]
|
||||
[InlineData(@"C:\foo\", @"D:\foo\bar", false)]
|
||||
[InlineData(@"\\server1\vol1\foo", @"\\server1\vol1\foo", false)]
|
||||
[InlineData(@"\\server1\vol1\foo", @"\\server1\vol1\bar", false)]
|
||||
[InlineData(@"\\server1\vol1\foo", @"\\server1\vol1\foo\bar", true)]
|
||||
[InlineData(@"\\server1\vol1\foo", @"\\server1\vol1\foo\..\bar", false)]
|
||||
public void Test(string parent, string child, bool result)
|
||||
[Theory, MemberData(nameof(GetTestData))]
|
||||
public void RunTest(bool expectation, string parent, string child)
|
||||
{
|
||||
// TODO fix path tests
|
||||
Assert.Equal(result, Core.Templating.FileNameGenerator.CheckIsWithinPath(parent, Path.GetDirectoryName(child)!));
|
||||
Assert.Equal(expectation, FileNameGenerator.CheckIsWithinPath(parent, child));
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> GetTestData()
|
||||
{
|
||||
yield return new object[] { true, @"/path/a/", "/path/a/file.flv" };
|
||||
yield return new object[] { true, @"/path/a", "/path/a/file.flv" };
|
||||
|
||||
yield return new object[] { true, @"/path/a/", "/path/a/b/file.flv" };
|
||||
yield return new object[] { true, @"/path/a", "/path/a/b/file.flv" };
|
||||
|
||||
yield return new object[] { true, @"/path/a/", "/path/a/../a/file.flv" };
|
||||
yield return new object[] { true, @"/path/a", "/path/a/../a/file.flv" };
|
||||
|
||||
yield return new object[] { false, @"/path/a/", "/path/a/../b/file.flv" };
|
||||
yield return new object[] { false, @"/path/a", "/path/a/../b/file.flv" };
|
||||
|
||||
yield return new object[] { true, @"/", "/path/a/file.flv" };
|
||||
yield return new object[] { true, @"/", "/file.flv" };
|
||||
|
||||
yield return new object[] { false, @"/path", "/path/a/../../../../file.flv" };
|
||||
yield return new object[] { false, @"/path", "/path../../../../file.flv" };
|
||||
|
||||
yield return new object[] { true, @"/path/a/", "/path////a/file.flv" };
|
||||
yield return new object[] { true, @"/path/a", "/path////a/file.flv" };
|
||||
|
||||
yield return new object[] { false, @"/path/", "/path/" };
|
||||
yield return new object[] { false, @"/path", "/path" };
|
||||
yield return new object[] { false, @"/path/", "/path" };
|
||||
yield return new object[] { false, @"/path", "/path/" };
|
||||
|
||||
var isWindows = Path.DirectorySeparatorChar == '\\';
|
||||
|
||||
yield return new object[] { isWindows && false, @"C:\", @"C:\" };
|
||||
yield return new object[] { isWindows && true, @"C:\", @"C:\foo" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo", @"C:\foo" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo\", @"C:\foo" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo", @"C:\foo\" };
|
||||
yield return new object[] { isWindows && true, @"C:\foo\", @"C:\foo\bar\" };
|
||||
yield return new object[] { isWindows && true, @"C:\foo\", @"C:\foo\bar" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo", @"C:\FOO\bar" };
|
||||
yield return new object[] { isWindows && true, @"C:\foo", @"C:/foo/bar" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo", @"C:\foobar" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo", @"C:\foobar\baz" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo\", @"C:\foobar\baz" };
|
||||
yield return new object[] { isWindows && false, @"C:\foobar", @"C:\foo\bar" };
|
||||
yield return new object[] { isWindows && false, @"C:\foobar\", @"C:\foo\bar" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo", @"C:\foo\..\bar\baz" };
|
||||
yield return new object[] { isWindows && true, @"C:\bar", @"C:\foo\..\bar\baz" };
|
||||
yield return new object[] { isWindows && false, @"C:\barr", @"C:\foo\..\bar\baz" };
|
||||
yield return new object[] { isWindows && false, @"C:\foo\", @"D:\foo\bar" };
|
||||
yield return new object[] { isWindows && false, @"\\server1\vol1\foo", @"\\server1\vol1\foo" };
|
||||
yield return new object[] { isWindows && false, @"\\server1\vol1\foo", @"\\server1\vol1\bar" };
|
||||
yield return new object[] { isWindows && true, @"\\server1\vol1\foo", @"\\server1\vol1\foo\bar" };
|
||||
yield return new object[] { isWindows && false, @"\\server1\vol1\foo", @"\\server1\vol1\foo\..\bar" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user