This commit is contained in:
limil 2025-11-08 20:04:11 +08:00
parent 66756b555f
commit 5269f28c41
6 changed files with 245 additions and 172 deletions

View File

@ -21,6 +21,7 @@
<PackageReference Include="Serilog" Version="4.3.0" /> <PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" /> <PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="TMDbLib" Version="2.3.0" /> <PackageReference Include="TMDbLib" Version="2.3.0" />
</ItemGroup> </ItemGroup>

View File

@ -4,6 +4,14 @@ namespace BangumiRenamer.Tools;
public class EmbededResourceViewer public class EmbededResourceViewer
{ {
private static readonly Lazy<Log> _lazy = new (() =>
{
var log = new Log(new Log.LogConfig());
if (!log.Init()) return null;
return log;
});
static ILog Log => _lazy.Value;
public static void PrintResourceNames() public static void PrintResourceNames()
{ {
var names = ResourceLoader.GetAllResNames(); var names = ResourceLoader.GetAllResNames();

View File

@ -6,6 +6,14 @@ namespace BangumiRenamer.Tools;
public static class FolderCloner public static class FolderCloner
{ {
private static readonly Lazy<Log> _lazy = new (() =>
{
var log = new Log(new Log.LogConfig());
if (!log.Init()) return null;
return log;
});
static ILog Log => _lazy.Value;
public static void Run() public static void Run()
{ {
var result = Dialog.FolderPicker(); var result = Dialog.FolderPicker();

View File

@ -13,6 +13,22 @@ using System.Text.RegularExpressions;
public static class ShowCompletionChecker public static class ShowCompletionChecker
{ {
private static readonly Lazy<Log> _lazyLog = new (() =>
{
var log = new Log(new Log.LogConfig());
if (!log.Init()) return null;
return log;
});
static ILog Log => _lazyLog.Value;
private static readonly Lazy<Config> _lazyConfig = new (() =>
{
var config = new Config("config.json");
if (!config.Load()) return null;
return config;
});
static IConfig Config => _lazyConfig.Value;
static List<Show> FindShows(string checkPath) static List<Show> FindShows(string checkPath)
{ {
var shows = new List<Show>(); var shows = new List<Show>();
@ -59,8 +75,8 @@ public static class ShowCompletionChecker
Log.Info($"Total Shows: {shows.Count}"); Log.Info($"Total Shows: {shows.Count}");
var client = new TMDbClient( var client = new TMDbClient(
apiKey: Config.Default.Get<TMDBConfig>().ApiKey , apiKey: Config.Get<TMDBConfig>().ApiKey ,
proxy: new WebProxy(Config.Default.Get<ProxyConfig>().HttpProxy)); proxy: new WebProxy(Config.Get<ProxyConfig>().HttpProxy));
var output = new StringBuilder(); var output = new StringBuilder();
foreach (var t in shows) foreach (var t in shows)

View File

@ -1,35 +1,44 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Reflection; using System.Reflection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace BangumiRenamer.Utils; namespace BangumiRenamer.Utils;
public class ConfigItemAttribute : System.Attribute public class ConfigItemAttribute(string name) : Attribute
{ {
public string Name; public readonly string Name = name;
}
public ConfigItemAttribute(string name) public interface IConfigItem
{
public void BeforeSave()
{
}
public void AfterLoad()
{ {
Name = name;
} }
} }
public interface IConfigItem; public interface IConfig
public class Config(string configPath)
{ {
private static readonly Lazy<Config> _lazy = new (() => public T Get<T>() where T : class, IConfigItem, new();
public void Reset<T>() where T : class, IConfigItem, new();
public bool Save();
}
public sealed class Config(string configPath) : IConfig
{ {
var config = new Config("config.json"); public IConfig G => _G;
config.ReLoad(); public Config _G { private get; set; }
return config;
});
public static Config Default => _lazy.Value;
private static readonly ConcurrentDictionary<Type, string> Cache = new();
private static readonly Dictionary<Type, string> Cache = new(); private readonly ConcurrentDictionary<string, IConfigItem> _configObjects = new();
private readonly Dictionary<string, object> _configObjects = new(); private readonly ConcurrentDictionary<string, JObject> _configs = new();
private readonly Dictionary<string, JObject> _configs = new();
private static string GetConfigItemName(Type type) private static string GetConfigItemName(Type type)
{ {
@ -54,6 +63,7 @@ public class Config(string configPath)
if (_configs.TryGetValue(name, out var jObject)) if (_configs.TryGetValue(name, out var jObject))
{ {
result = jObject.ToObject<T>(); result = jObject.ToObject<T>();
result.AfterLoad();
} }
else else
{ {
@ -66,21 +76,17 @@ public class Config(string configPath)
public void Reset<T>() where T : class, IConfigItem, new() public void Reset<T>() where T : class, IConfigItem, new()
{ {
var name = GetConfigItemName(typeof(T)); var name = GetConfigItemName(typeof(T));
_configs.Remove(name); _configs.TryRemove(name, out _);
_configObjects.Remove(name); _configObjects.Remove(name, out _);
} }
public void Clear() public bool Load()
{ {
_configs.Clear(); _configs.Clear();
_configObjects.Clear(); _configObjects.Clear();
} if (!File.Exists(configPath)) return false;
try
public void ReLoad()
{ {
_configs.Clear();
_configObjects.Clear();
if (!File.Exists(configPath)) return;
var configJson = File.ReadAllText(configPath); var configJson = File.ReadAllText(configPath);
var config = JObject.Parse(configJson); var config = JObject.Parse(configJson);
foreach (var kv in config) foreach (var kv in config)
@ -88,15 +94,32 @@ public class Config(string configPath)
_configs[kv.Key] = kv.Value.ToObject<JObject>(); _configs[kv.Key] = kv.Value.ToObject<JObject>();
} }
} }
catch (Exception)
{
return false;
}
return true;
}
public void Save() public bool Save()
{
if (string.IsNullOrEmpty(configPath)) return false;
try
{ {
foreach (var config in _configObjects) foreach (var config in _configObjects)
{ {
config.Value.BeforeSave();
_configs[config.Key] = JObject.FromObject(config.Value); _configs[config.Key] = JObject.FromObject(config.Value);
} }
File.WriteAllText(configPath, JsonConvert.SerializeObject(_configs, Formatting.Indented)); File.WriteAllText(configPath, JsonConvert.SerializeObject(_configs, Formatting.Indented));
} }
catch (Exception)
{
return false;
}
return true;
}
public static void CreateEmptyConfig() public static void CreateEmptyConfig()
{ {
@ -117,12 +140,22 @@ public class Config(string configPath)
typeof(IConfigItem).IsAssignableFrom(type)) typeof(IConfigItem).IsAssignableFrom(type))
.ToList(); .ToList();
var configs = new Dictionary<string, JObject>(); var filePath = "config.json";
var config = new Config(filePath);
foreach (var type in configItemTypes) foreach (var type in configItemTypes)
{ {
var name = GetConfigItemName(type); var name = GetConfigItemName(type);
configs[name] = JObject.FromObject(Activator.CreateInstance(type)); config._configObjects[name] = (IConfigItem) Activator.CreateInstance(type);
}
config.Save();
if (File.Exists(filePath))
{
// 使用 explorer.exe 的 /select 命令行参数
string argument = $"/select,\"{filePath}\"";
// 启动 explorer.exe 进程
Process.Start("explorer.exe", argument);
} }
File.WriteAllText("config_default.json", JsonConvert.SerializeObject(configs, Formatting.Indented));
} }
} }

View File

@ -1,162 +1,169 @@
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using Serilog; using Serilog;
using Serilog.Context;
using Serilog.Core; using Serilog.Core;
using Serilog.Events; using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes; using Serilog.Sinks.SystemConsole.Themes;
namespace BangumiRenamer.Utils; namespace BangumiRenamer.Utils;
public sealed class Log public interface ILog
{ {
private static readonly Lazy<Log> _instance = new Lazy<Log>(() => new Log()); public void Debug(string message, [CallerFilePath] string callerFilePath = "",
[CallerMemberName] string callerMemberName = "");
public void Info(string message, [CallerFilePath] string callerFilePath = "",
[CallerMemberName] string callerMemberName = "");
public void Warn(string message, [CallerFilePath] string callerFilePath = "",
[CallerMemberName] string callerMemberName = "");
public void Error(string message, [CallerFilePath] string callerFilePath = "",
[CallerMemberName] string callerMemberName = "");
public void Error(Exception ex, string message);
}
public sealed class Log : ILog
{
public enum LogLevel
{
Error,
Warn,
Info,
Debug,
}
[ConfigItem("Log")]
public class LogConfig : IConfigItem
{
[JsonIgnore] public LogLevel Level = LogLevel.Debug;
[JsonProperty("Level")] private string _level;
public bool ToConsole = true;
public bool ToFile = false;
public string LogFilePath;
public void AfterLoad()
{
if (!string.IsNullOrEmpty(_level)) LogLevel.TryParse(_level, out Level);
}
public void BeforeSave()
{
_level = Level.ToString();
}
}
public static ILog G => _G;
public static Log _G { private get; set; }
private LogConfig _config;
private Logger _logger; private Logger _logger;
private bool _isInitialized = false;
private readonly object _lockObject = new object();
public static Log Instance => _instance.Value; public Log(LogConfig config)
public static ILogger Logger => Instance._logger;
private Log() { }
/// <summary>
/// 初始化日志配置
/// </summary>
public static void Initialize(Action<LoggerConfiguration> configure = null)
{ {
Instance.InitializeInternal(configure); _config = config;
} }
private void InitializeInternal(Action<LoggerConfiguration> configure = null) public bool Init()
{ {
lock (_lockObject) if (_config == null) return false;
var config = new LoggerConfiguration();
switch (_config.Level)
{ {
if (_isInitialized) case LogLevel.Error:
{ config.MinimumLevel.Error();
Warn("日志系统已经初始化过"); break;
return; case LogLevel.Warn:
config.MinimumLevel.Warning();
break;
case LogLevel.Debug:
config.MinimumLevel.Debug();
break;
case LogLevel.Info:
default:
config.MinimumLevel.Information();
break;
} }
var config = new LoggerConfiguration() var outputTemplate = "[{Timestamp:HH:mm:ss}][{CallerPlace}][{Level:u3}] {Message:lj}{NewLine}{Exception}";
.MinimumLevel.Debug()
.WriteTo.Console( if (_config.ToConsole)
outputTemplate: "[{Timestamp:HH:mm:ss}][{Level:u3}] {Message:lj}{NewLine}{Exception}", {
config.WriteTo.Console(
outputTemplate: outputTemplate,
theme: AnsiConsoleTheme.Sixteen theme: AnsiConsoleTheme.Sixteen
) );
.Enrich.FromLogContext() }
if (_config.ToFile)
{
if (string.IsNullOrEmpty(_config.LogFilePath))
{
return false;
}
config.WriteTo.File(
outputTemplate: outputTemplate,
path: _config.LogFilePath
);
}
config.Enrich.FromLogContext()
.Enrich.WithThreadId(); .Enrich.WithThreadId();
try
configure?.Invoke(config); {
_logger = config.CreateLogger(); _logger = config.CreateLogger();
_isInitialized = true; }
catch (Exception)
{
return false;
}
return true;
}
Info("日志系统初始化完成");
}
}
// 静态快捷方法 - 使用更简短的名称 // 静态快捷方法 - 使用更简短的名称
public static void Debug(string message, params object[] properties) public void Debug(string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
=> Instance.EnsureAndLog(LogEventLevel.Debug, message, properties);
public static void Info(string message, params object[] properties)
=> Instance.EnsureAndLog(LogEventLevel.Information, message, properties);
public static void Warn(string message, params object[] properties)
=> Instance.EnsureAndLog(LogEventLevel.Warning, message, properties);
public static void Error(string message, params object[] properties)
=> Instance.EnsureAndLog(LogEventLevel.Error, message, properties);
public static void Error(Exception ex, string message, params object[] properties)
=> Instance.EnsureAndLog(ex, message, properties);
public static void Fatal(string message, params object[] properties)
=> Instance.EnsureAndLog(LogEventLevel.Fatal, message, properties);
// 实例方法
private void EnsureAndLog(LogEventLevel level, string message, object[] properties)
{ {
EnsureInitialized(); using (LogContext.PushProperty("CallerPlace", Path.GetFileNameWithoutExtension(callerFilePath) + "::" + callerMemberName))
_logger.Write(level, message, properties);
}
private void EnsureAndLog(Exception ex, string message, object[] properties)
{ {
EnsureInitialized(); _logger.Write(LogEventLevel.Debug, message);
_logger.Error(ex, message, properties);
}
/// <summary>
/// 为特定类型创建Logger
/// </summary>
public static ILogger ForContext<T>()
{
Instance.EnsureInitialized();
return Instance._logger.ForContext<T>();
}
/// <summary>
/// 为特定源创建Logger
/// </summary>
public static ILogger ForContext(string source)
{
Instance.EnsureInitialized();
return Instance._logger.ForContext("SourceContext", source);
}
/// <summary>
/// 开始一个带属性的日志上下文
/// </summary>
public static IDisposable BeginScope(string propertyName, object value)
{
Instance.EnsureInitialized();
return Serilog.Context.LogContext.PushProperty(propertyName, value);
}
/// <summary>
/// 开始多个属性的日志上下文
/// </summary>
public static IDisposable BeginScope(params (string Name, object Value)[] properties)
{
Instance.EnsureInitialized();
var disposables = properties.Select(p =>
Serilog.Context.LogContext.PushProperty(p.Name, p.Value)).ToArray();
return new DisposableGroup(disposables);
}
/// <summary>
/// 关闭并刷新日志
/// </summary>
public static void Close()
{
Instance._logger?.Dispose();
Serilog.Log.CloseAndFlush();
Instance._isInitialized = false;
}
private void EnsureInitialized()
{
if (!_isInitialized)
{
InitializeInternal();
} }
} }
// 辅助类用于同时释放多个IDisposable public void Info(string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
private class DisposableGroup : IDisposable
{ {
private readonly IDisposable[] _disposables; using (LogContext.PushProperty("CallerPlace", Path.GetFileNameWithoutExtension(callerFilePath) + "::" + callerMemberName))
public DisposableGroup(IDisposable[] disposables)
{ {
_disposables = disposables; _logger.Write(LogEventLevel.Information, message);
}
} }
public void Dispose() public void Warn(string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
{ {
foreach (var disposable in _disposables) using (LogContext.PushProperty("CallerPlace", Path.GetFileNameWithoutExtension(callerFilePath) + "::" + callerMemberName))
{ {
disposable?.Dispose(); _logger.Write(LogEventLevel.Warning, message);
} }
} }
public void Error(string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
{
using (LogContext.PushProperty("CallerPlace", Path.GetFileNameWithoutExtension(callerFilePath) + "::" + callerMemberName))
{
_logger.Write(LogEventLevel.Error, message);
}
}
public void Error(Exception ex, string message)
=> WriteLog(ex, message);
private void WriteLog(Exception ex, string message)
{
_logger.Error(ex, message);
} }
} }