diff --git a/BangumiRenamer.csproj b/BangumiRenamer.csproj
index 6c442a9..9604884 100644
--- a/BangumiRenamer.csproj
+++ b/BangumiRenamer.csproj
@@ -21,6 +21,7 @@
+
diff --git a/Src/Tools/EmbededResourceViewer.cs b/Src/Tools/EmbededResourceViewer.cs
index 3314497..7704d51 100644
--- a/Src/Tools/EmbededResourceViewer.cs
+++ b/Src/Tools/EmbededResourceViewer.cs
@@ -4,6 +4,14 @@ namespace BangumiRenamer.Tools;
public class EmbededResourceViewer
{
+ private static readonly Lazy _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()
{
var names = ResourceLoader.GetAllResNames();
diff --git a/Src/Tools/FolderCloner.cs b/Src/Tools/FolderCloner.cs
index da3b8d4..aa6d183 100644
--- a/Src/Tools/FolderCloner.cs
+++ b/Src/Tools/FolderCloner.cs
@@ -6,6 +6,14 @@ namespace BangumiRenamer.Tools;
public static class FolderCloner
{
+ private static readonly Lazy _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()
{
var result = Dialog.FolderPicker();
diff --git a/Src/Tools/ShowCompletionChecker.cs b/Src/Tools/ShowCompletionChecker.cs
index f33d296..65451c5 100644
--- a/Src/Tools/ShowCompletionChecker.cs
+++ b/Src/Tools/ShowCompletionChecker.cs
@@ -13,6 +13,22 @@ using System.Text.RegularExpressions;
public static class ShowCompletionChecker
{
+ private static readonly Lazy _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 _lazyConfig = new (() =>
+ {
+ var config = new Config("config.json");
+ if (!config.Load()) return null;
+ return config;
+ });
+ static IConfig Config => _lazyConfig.Value;
+
static List FindShows(string checkPath)
{
var shows = new List();
@@ -59,8 +75,8 @@ public static class ShowCompletionChecker
Log.Info($"Total Shows: {shows.Count}");
var client = new TMDbClient(
- apiKey: Config.Default.Get().ApiKey ,
- proxy: new WebProxy(Config.Default.Get().HttpProxy));
+ apiKey: Config.Get().ApiKey ,
+ proxy: new WebProxy(Config.Get().HttpProxy));
var output = new StringBuilder();
foreach (var t in shows)
diff --git a/Src/Utils/Config.cs b/Src/Utils/Config.cs
index d785809..35979ba 100644
--- a/Src/Utils/Config.cs
+++ b/Src/Utils/Config.cs
@@ -1,35 +1,44 @@
+using System.Collections.Concurrent;
+using System.Diagnostics;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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 class Config(string configPath)
+public interface IConfig
{
- private static readonly Lazy _lazy = new (() =>
- {
- var config = new Config("config.json");
- config.ReLoad();
- return config;
- });
- public static Config Default => _lazy.Value;
+ public T Get() where T : class, IConfigItem, new();
+ public void Reset() where T : class, IConfigItem, new();
+
+ public bool Save();
+}
+
+public sealed class Config(string configPath) : IConfig
+{
+ public IConfig G => _G;
+ public Config _G { private get; set; }
- private static readonly Dictionary Cache = new();
- private readonly Dictionary _configObjects = new();
- private readonly Dictionary _configs = new();
+ private static readonly ConcurrentDictionary Cache = new();
+ private readonly ConcurrentDictionary _configObjects = new();
+ private readonly ConcurrentDictionary _configs = new();
private static string GetConfigItemName(Type type)
{
@@ -54,6 +63,7 @@ public class Config(string configPath)
if (_configs.TryGetValue(name, out var jObject))
{
result = jObject.ToObject();
+ result.AfterLoad();
}
else
{
@@ -66,36 +76,49 @@ public class Config(string configPath)
public void Reset() where T : class, IConfigItem, new()
{
var name = GetConfigItemName(typeof(T));
- _configs.Remove(name);
- _configObjects.Remove(name);
+ _configs.TryRemove(name, out _);
+ _configObjects.Remove(name, out _);
}
- public void Clear()
+ public bool Load()
{
_configs.Clear();
_configObjects.Clear();
+ if (!File.Exists(configPath)) return false;
+ try
+ {
+ var configJson = File.ReadAllText(configPath);
+ var config = JObject.Parse(configJson);
+ foreach (var kv in config)
+ {
+ _configs[kv.Key] = kv.Value.ToObject();
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ return true;
}
- public void ReLoad()
+ public bool Save()
{
- _configs.Clear();
- _configObjects.Clear();
- if (!File.Exists(configPath)) return;
- var configJson = File.ReadAllText(configPath);
- var config = JObject.Parse(configJson);
- foreach (var kv in config)
+ if (string.IsNullOrEmpty(configPath)) return false;
+ try
{
- _configs[kv.Key] = kv.Value.ToObject();
- }
- }
+ foreach (var config in _configObjects)
+ {
+ config.Value.BeforeSave();
+ _configs[config.Key] = JObject.FromObject(config.Value);
+ }
- public void Save()
- {
- foreach (var config in _configObjects)
- {
- _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()
@@ -116,13 +139,23 @@ public class Config(string configPath)
!type.IsAbstract &&
typeof(IConfigItem).IsAssignableFrom(type))
.ToList();
-
- var configs = new Dictionary();
+
+ var filePath = "config.json";
+ var config = new Config(filePath);
foreach (var type in configItemTypes)
{
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));
}
}
\ No newline at end of file
diff --git a/Src/Utils/Log.cs b/Src/Utils/Log.cs
index 274760c..94475bd 100644
--- a/Src/Utils/Log.cs
+++ b/Src/Utils/Log.cs
@@ -1,162 +1,169 @@
+using System.Runtime.CompilerServices;
+using Newtonsoft.Json;
using Serilog;
+using Serilog.Context;
using Serilog.Core;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;
namespace BangumiRenamer.Utils;
-public sealed class Log
+public interface ILog
{
- private static readonly Lazy _instance = new Lazy(() => 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 bool _isInitialized = false;
- private readonly object _lockObject = new object();
-
- public static Log Instance => _instance.Value;
- public static ILogger Logger => Instance._logger;
- private Log() { }
-
- ///
- /// 初始化日志配置
- ///
- public static void Initialize(Action configure = null)
+ public Log(LogConfig config)
{
- Instance.InitializeInternal(configure);
+ _config = config;
}
-
- private void InitializeInternal(Action 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();
+ break;
+ case LogLevel.Warn:
+ config.MinimumLevel.Warning();
+ break;
+ case LogLevel.Debug:
+ config.MinimumLevel.Debug();
+ break;
+ case LogLevel.Info:
+ default:
+ config.MinimumLevel.Information();
+ break;
+ }
+
+ var outputTemplate = "[{Timestamp:HH:mm:ss}][{CallerPlace}][{Level:u3}] {Message:lj}{NewLine}{Exception}";
+
+ if (_config.ToConsole)
+ {
+ config.WriteTo.Console(
+ outputTemplate: outputTemplate,
+ theme: AnsiConsoleTheme.Sixteen
+ );
+ }
+
+ if (_config.ToFile)
+ {
+ if (string.IsNullOrEmpty(_config.LogFilePath))
{
- Warn("日志系统已经初始化过");
- return;
+ return false;
}
-
- var config = new LoggerConfiguration()
- .MinimumLevel.Debug()
- .WriteTo.Console(
- outputTemplate: "[{Timestamp:HH:mm:ss}][{Level:u3}] {Message:lj}{NewLine}{Exception}",
- theme: AnsiConsoleTheme.Sixteen
- )
- .Enrich.FromLogContext()
- .Enrich.WithThreadId();
-
- configure?.Invoke(config);
-
+ config.WriteTo.File(
+ outputTemplate: outputTemplate,
+ path: _config.LogFilePath
+ );
+ }
+
+ config.Enrich.FromLogContext()
+ .Enrich.WithThreadId();
+ try
+ {
_logger = config.CreateLogger();
- _isInitialized = true;
-
- Info("日志系统初始化完成");
}
+ catch (Exception)
+ {
+ return false;
+ }
+ return true;
}
-
+
+
// 静态快捷方法 - 使用更简短的名称
- public static void Debug(string message, params object[] properties)
- => 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)
+ public void Debug(string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
{
- EnsureInitialized();
- _logger.Write(level, message, properties);
- }
-
- private void EnsureAndLog(Exception ex, string message, object[] properties)
- {
- EnsureInitialized();
- _logger.Error(ex, message, properties);
- }
-
- ///
- /// 为特定类型创建Logger
- ///
- public static ILogger ForContext()
- {
- Instance.EnsureInitialized();
- return Instance._logger.ForContext();
- }
-
- ///
- /// 为特定源创建Logger
- ///
- public static ILogger ForContext(string source)
- {
- Instance.EnsureInitialized();
- return Instance._logger.ForContext("SourceContext", source);
- }
-
- ///
- /// 开始一个带属性的日志上下文
- ///
- public static IDisposable BeginScope(string propertyName, object value)
- {
- Instance.EnsureInitialized();
- return Serilog.Context.LogContext.PushProperty(propertyName, value);
- }
-
- ///
- /// 开始多个属性的日志上下文
- ///
- 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);
- }
-
- ///
- /// 关闭并刷新日志
- ///
- public static void Close()
- {
- Instance._logger?.Dispose();
- Serilog.Log.CloseAndFlush();
- Instance._isInitialized = false;
- }
-
- private void EnsureInitialized()
- {
- if (!_isInitialized)
+ using (LogContext.PushProperty("CallerPlace", Path.GetFileNameWithoutExtension(callerFilePath) + "::" + callerMemberName))
{
- InitializeInternal();
+ _logger.Write(LogEventLevel.Debug, message);
}
}
- // 辅助类:用于同时释放多个IDisposable
- private class DisposableGroup : IDisposable
+ public void Info(string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
{
- private readonly IDisposable[] _disposables;
-
- public DisposableGroup(IDisposable[] disposables)
+ using (LogContext.PushProperty("CallerPlace", Path.GetFileNameWithoutExtension(callerFilePath) + "::" + callerMemberName))
{
- _disposables = disposables;
+ _logger.Write(LogEventLevel.Information, message);
}
-
- public void Dispose()
+ }
+
+ public void Warn(string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
+ {
+ using (LogContext.PushProperty("CallerPlace", Path.GetFileNameWithoutExtension(callerFilePath) + "::" + callerMemberName))
{
- foreach (var disposable in _disposables)
- {
- 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);
+ }
}
\ No newline at end of file