From a9cf2ec6b8a7535742f22e936fee4618305e0afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= <116494706+ParovozTomas@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:19:34 +0300 Subject: [PATCH] Refactor project structure --- .gitignore | 8 +- .../Helpers/CsvParser.cs | 0 .../Helpers/DiscogsService.cs | 0 .../Helpers/TagEditor.cs | 0 .../Types/AlbumInfo.cs | 0 .../Types/TrackInfo.cs | 0 .../UniversalTagEditor.Core.csproj | 15 ++ UniversalTagEditor.Core/runner.cs | 104 +++++++++++++ UniversalTagEditor.sln | 7 + src/Program.cs | 140 +++++------------- src/UniversalTagEditor.csproj | 6 +- 11 files changed, 174 insertions(+), 106 deletions(-) rename {src => UniversalTagEditor.Core}/Helpers/CsvParser.cs (100%) rename {src => UniversalTagEditor.Core}/Helpers/DiscogsService.cs (100%) rename {src => UniversalTagEditor.Core}/Helpers/TagEditor.cs (100%) rename {src => UniversalTagEditor.Core}/Types/AlbumInfo.cs (100%) rename {src => UniversalTagEditor.Core}/Types/TrackInfo.cs (100%) create mode 100644 UniversalTagEditor.Core/UniversalTagEditor.Core.csproj create mode 100644 UniversalTagEditor.Core/runner.cs diff --git a/.gitignore b/.gitignore index fa06a6a..3493cd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ src/bin/ -src/obj/ \ No newline at end of file +src/obj/ + +.vs/ +bin/ +obj/ +*.user +*.suo \ No newline at end of file diff --git a/src/Helpers/CsvParser.cs b/UniversalTagEditor.Core/Helpers/CsvParser.cs similarity index 100% rename from src/Helpers/CsvParser.cs rename to UniversalTagEditor.Core/Helpers/CsvParser.cs diff --git a/src/Helpers/DiscogsService.cs b/UniversalTagEditor.Core/Helpers/DiscogsService.cs similarity index 100% rename from src/Helpers/DiscogsService.cs rename to UniversalTagEditor.Core/Helpers/DiscogsService.cs diff --git a/src/Helpers/TagEditor.cs b/UniversalTagEditor.Core/Helpers/TagEditor.cs similarity index 100% rename from src/Helpers/TagEditor.cs rename to UniversalTagEditor.Core/Helpers/TagEditor.cs diff --git a/src/Types/AlbumInfo.cs b/UniversalTagEditor.Core/Types/AlbumInfo.cs similarity index 100% rename from src/Types/AlbumInfo.cs rename to UniversalTagEditor.Core/Types/AlbumInfo.cs diff --git a/src/Types/TrackInfo.cs b/UniversalTagEditor.Core/Types/TrackInfo.cs similarity index 100% rename from src/Types/TrackInfo.cs rename to UniversalTagEditor.Core/Types/TrackInfo.cs diff --git a/UniversalTagEditor.Core/UniversalTagEditor.Core.csproj b/UniversalTagEditor.Core/UniversalTagEditor.Core.csproj new file mode 100644 index 0000000..bad939f --- /dev/null +++ b/UniversalTagEditor.Core/UniversalTagEditor.Core.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/UniversalTagEditor.Core/runner.cs b/UniversalTagEditor.Core/runner.cs new file mode 100644 index 0000000..b4a3c14 --- /dev/null +++ b/UniversalTagEditor.Core/runner.cs @@ -0,0 +1,104 @@ +using MetaBrainz.MusicBrainz; +using UniversalTagEditor.Helpers; +using UniversalTagEditor.Types; + +namespace UniversalTagEditor.Core; + +public sealed class UteOptions +{ + public required string WorkingDirectory { get; init; } + public string? AlbumInfoCsv { get; init; } // имя файла или путь + public string? AlbumCover { get; init; } // имя файла или путь + public string? Format { get; init; } // flac/mp3/... + public bool FixTags { get; init; } + public bool EnhanceStructure { get; init; } + public int MaxDegreeOfParallelism { get; init; } = 4; +} + +public sealed record ProgressInfo(int Done, int Total, string? CurrentFile); + +public static class Runner +{ + public static async Task RunAsync( + UteOptions opt, + IProgress? log = null, + IProgress? progress = null, + CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(opt.WorkingDirectory)) + throw new ArgumentException("WorkingDirectory is empty."); + + if (!Directory.Exists(opt.WorkingDirectory)) + throw new DirectoryNotFoundException(opt.WorkingDirectory); + + var format = (opt.Format ?? "").Trim(); + if (string.IsNullOrWhiteSpace(format)) + throw new ArgumentException("Format (-f) is required."); + + AlbumInfo album = new(); + + if (!string.IsNullOrWhiteSpace(opt.AlbumInfoCsv)) + { + var csvPath = MakePath(opt.WorkingDirectory, opt.AlbumInfoCsv!); + var records = CsvParser.ParseCsvTable(csvPath); + album = new AlbumInfo { Tracks = records }; + } + + if (!string.IsNullOrWhiteSpace(opt.AlbumCover)) + album.Cover = MakePath(opt.WorkingDirectory, opt.AlbumCover!); + + + var files = new List(); + files.AddRange(Directory.GetFiles(opt.WorkingDirectory, $"*.{format}")); + + if (!opt.EnhanceStructure) + { + foreach (var dir in Directory.GetDirectories(opt.WorkingDirectory)) + files.AddRange(Directory.GetFiles(dir, $"*.{format}")); + } + + if (files.Count == 0) + { + log?.Report("Файлы не найдены."); + return; + } + + var queries = new ThreadLocal(() => + new Query("NavidromeMetadataRecovery", "1.0")); + + int done = 0; + int total = files.Count; + + await Parallel.ForEachAsync( + files.Select((path, index) => (path, index)), + new ParallelOptions + { + MaxDegreeOfParallelism = Math.Max(1, opt.MaxDegreeOfParallelism), + CancellationToken = ct + }, + async (item, token) => + { + var (file, index) = item; + + if (opt.FixTags) + { + await TagEditor.Fix(file, queries.Value!, opt.EnhanceStructure, format); + } + else + { + TagEditor.Edit(file, album, index); + } + + var nowDone = Interlocked.Increment(ref done); + progress?.Report(new ProgressInfo(nowDone, total, file)); + log?.Report($"{nowDone}/{total}: {file} успешно обработан"); + }); + + log?.Report("Готово!"); + } + + private static string MakePath(string workDir, string maybeFileOrPath) + => Path.IsPathRooted(maybeFileOrPath) + ? maybeFileOrPath + : Path.Combine(workDir, maybeFileOrPath); +} \ No newline at end of file diff --git a/UniversalTagEditor.sln b/UniversalTagEditor.sln index 29b63f4..b34d5eb 100644 --- a/UniversalTagEditor.sln +++ b/UniversalTagEditor.sln @@ -6,6 +6,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalTagEditor", "src\UniversalTagEditor.csproj", "{F12A64BA-EF0D-DBA6-7B72-63FC4220F8EC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalTagEditor.Core", "UniversalTagEditor.Core\UniversalTagEditor.Core.csproj", "{A0248697-C889-481E-89C2-FB3C4DF6A3ED}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -16,12 +18,17 @@ Global {F12A64BA-EF0D-DBA6-7B72-63FC4220F8EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {F12A64BA-EF0D-DBA6-7B72-63FC4220F8EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {F12A64BA-EF0D-DBA6-7B72-63FC4220F8EC}.Release|Any CPU.Build.0 = Release|Any CPU + {A0248697-C889-481E-89C2-FB3C4DF6A3ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0248697-C889-481E-89C2-FB3C4DF6A3ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0248697-C889-481E-89C2-FB3C4DF6A3ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0248697-C889-481E-89C2-FB3C4DF6A3ED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {F12A64BA-EF0D-DBA6-7B72-63FC4220F8EC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {A0248697-C889-481E-89C2-FB3C4DF6A3ED} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D0E6B6A0-0573-41C6-88A8-2678B8799BA7} diff --git a/src/Program.cs b/src/Program.cs index 18aa4c3..f5099c6 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,121 +1,59 @@ -using UniversalTagEditor.Helpers; -using UniversalTagEditor.Types; - -using MetaBrainz.MusicBrainz; - -namespace UniversalTagEditor; +using System.Text; +using UniversalTagEditor.Core; class Program { static async Task Main(string[] args) { - string albumInfo = ""; - string albumCover = ""; - string format = ""; - string workingDirectory = ""; + var opt = ParseArgs(args); + + var log = new Progress(Console.WriteLine); + var prog = new Progress(p => + Console.Title = $"UniversalTagEditor {p.Done}/{p.Total}"); + + Console.OutputEncoding = Encoding.UTF8; + Console.InputEncoding = Encoding.UTF8; + + Console.WriteLine("WORKDIR RAW : " + opt.WorkingDirectory); + Console.WriteLine("WORKDIR FULL: " + Path.GetFullPath(opt.WorkingDirectory)); + Console.WriteLine("EXISTS : " + Directory.Exists(opt.WorkingDirectory)); + await Runner.RunAsync(opt, log, prog); + } + + static UteOptions ParseArgs(string[] args) + { + string? albumInfo = null; + string? albumCover = null; + string? format = null; + string? workingDirectory = null; bool fixTags = false; bool enhanceStructure = false; - List threads = new(); - - var query = new Query("NavidromeMetadataRecovery1", "1.0"); - var query2 = new Query("NavidromeMetadataRecovery2", "1.0"); - var query3 = new Query("NavidromeMetadataRecovery3", "1.0"); - var query4 = new Query("NavidromeMetadataRecovery4", "1.0"); - for (int i = 0; i < args.Length; i++) { switch (args[i]) { - case "-w": - if (i + 1 < args.Length) workingDirectory = args[i + 1]; - i++; - break; - - case "-a": - if (i + 1 < args.Length) albumInfo = args[i + 1]; - i++; - break; - - case "-f": - if (i + 1 < args.Length) format = args[i + 1]; - i++; - break; - - case "-c": - if (i + 1 < args.Length) albumCover = args[i + 1]; - i++; - break; - - case "--fix-tags": - fixTags = true; - break; - case "--enhance-structure": - enhanceStructure = true; - break; + case "-w": workingDirectory = args[++i]; break; + case "-a": albumInfo = args[++i]; break; + case "-f": format = args[++i]; break; + case "-c": albumCover = args[++i]; break; + case "--fix-tags": fixTags = true; break; + case "--enhance-structure": enhanceStructure = true; break; } } - TrackInfo[] records; - AlbumInfo album = new(); - if (albumInfo != "") - { - records = CsvParser.ParseCsvTable(Path.Combine(workingDirectory, albumInfo)); - album = new() - { - Tracks = records - }; - } - if (albumCover != "") - album.Cover = Path.Combine(workingDirectory, albumCover); - - List files = new(); - files.AddRange(Directory.GetFiles(workingDirectory, $"*.{format}")); + if (string.IsNullOrWhiteSpace(workingDirectory)) + throw new ArgumentException("Нужен -w "); - if (!enhanceStructure) { - string[] directories = Directory.GetDirectories(workingDirectory); - foreach(string directory in directories) - files.AddRange(Directory.GetFiles(directory, $"*.{format}")); - } - - Func, Query, int, Task> cycle = async (List chunk, Query query, int thread) => + return new UteOptions { - threads.Add(thread); - for (int i = 0; i < chunk.Count; i++) - { - if (fixTags) - await TagEditor.Fix(chunk[i], query, enhanceStructure, format); - else - TagEditor.Edit(chunk[i], album, i); - Console.WriteLine($"{i+1}. {chunk[i]} successfully edited"); - } - threads.Remove(thread); + WorkingDirectory = workingDirectory, + AlbumInfoCsv = albumInfo, + AlbumCover = albumCover, + Format = format, + FixTags = fixTags, + EnhanceStructure = enhanceStructure, + MaxDegreeOfParallelism = 4 }; - - if ((int)(files.Count / 4) > 1) - { - Thread thread = new Thread(async () => await cycle(files.GetRange(0, (int)(files.Count / 4)), query, 1)) { IsBackground = true }; - thread.Start(); - - Thread thread2 = new Thread(async () => await cycle(files.GetRange((int)(files.Count / 4), (int)(files.Count / 4)), query2, 2)) { IsBackground = true }; - thread2.Start(); - - if ((int)(files.Count / 4) >= 3) { - Thread thread3 = new Thread(async () => await cycle(files.GetRange(((int)(files.Count / 4) * 2), (int)(files.Count / 4)), query3, 3)) { IsBackground = true }; - thread3.Start(); - } - if ((int)(files.Count / 4) >= 4) { - Thread thread4 = new Thread(async () => await cycle(files.GetRange(((int)(files.Count / 4) * 3), files.Count - ((int)(files.Count / 4) * 3)), query4, 4)) { IsBackground = true }; - thread4.Start(); - } - - Console.WriteLine("Waiting for threads complete"); - while(threads.Count > 0) { } - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("\n\nTag fixing completed!"); - Console.ResetColor(); - } - else - await cycle(files, query, 1); } } \ No newline at end of file diff --git a/src/UniversalTagEditor.csproj b/src/UniversalTagEditor.csproj index 2b01bac..9d1ca48 100644 --- a/src/UniversalTagEditor.csproj +++ b/src/UniversalTagEditor.csproj @@ -2,15 +2,13 @@ Exe - net10.0 + net8.0 enable enable - - - +