emby-toolbox/EmbyToolbox/Services/FileDiscoveryService.cs
Emby Toolbox 6264b487fe Initial commit: Emby Toolbox (conversion scroll fix, bulk Del for tracks).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:33:47 +05:00

116 lines
3.7 KiB
C#

using System.IO;
using System.Linq;
namespace EmbyToolbox.Services;
public sealed class FileDiscoveryService
{
/// <summary>Стабильная сортировка списка видеофайлов по полному нормализованному пути (без учёта регистра).</summary>
public static StringComparer QueuePathOrderComparer { get; } = StringComparer.OrdinalIgnoreCase;
/// <summary>
/// Собирает пути к поддерживаемым видео: отдельные файлы, из каталогов — рекурсивно, без дублей.
/// </summary>
public IReadOnlyList<string> CollectVideoFilesFromFileSystemEntries(IEnumerable<string>? entryPaths, Action<string>? onError = null)
{
if (entryPaths is null)
{
return [];
}
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var entry in entryPaths)
{
if (string.IsNullOrWhiteSpace(entry))
{
continue;
}
var full = Path.GetFullPath(entry);
if (File.Exists(full))
{
if (SupportedVideoFormats.IsSupportedVideoFile(full))
{
set.Add(full);
}
continue;
}
if (Directory.Exists(full))
{
foreach (var file in DiscoverVideoFiles(full, onError))
{
set.Add(file);
}
}
}
return SortVideoPathsByFullPath(set);
}
/// <summary>Возвращает новый список путей, отсортированный по <see cref="QueuePathOrderComparer"/> (стабильно).</summary>
public static IReadOnlyList<string> SortVideoPathsByFullPath(IEnumerable<string> paths) =>
paths.OrderBy(p => p, QueuePathOrderComparer).ToList();
public IReadOnlyList<string> DiscoverVideoFiles(string rootDirectory, Action<string>? onError = null)
{
if (string.IsNullOrWhiteSpace(rootDirectory) || !Directory.Exists(rootDirectory))
{
return [];
}
var result = new List<string>();
var pending = new Stack<string>();
pending.Push(rootDirectory);
while (pending.Count > 0)
{
var current = pending.Pop();
try
{
foreach (var file in Directory.EnumerateFiles(current))
{
if (!SupportedVideoFormats.IsSupportedVideoFile(file))
{
continue;
}
string normalized;
try
{
normalized = Path.GetFullPath(file);
}
catch
{
continue;
}
result.Add(normalized);
}
}
catch (Exception ex)
{
onError?.Invoke($"ошибка чтения каталога '{current}': {ex.Message}");
}
try
{
foreach (var childDirectory in Directory.EnumerateDirectories(current))
{
pending.Push(childDirectory);
}
}
catch (Exception ex)
{
onError?.Invoke($"ошибка чтения вложенных каталогов '{current}': {ex.Message}");
}
}
return SortVideoPathsByFullPath(result);
}
public bool IsSupportedVideoFile(string path) => SupportedVideoFormats.IsSupportedVideoFile(path);
}