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
-
-
-
+