using System.IO; namespace EmbyToolbox.Services; /// Область применения snapshot: каталог эпизода и опционально общий корень батча. public static class SnapshotScopePaths { private static readonly StringComparer IC = StringComparer.OrdinalIgnoreCase; /// Абсолютный путь без завершающего разделителя. public static string NormalizeScopeDirectory(string directoryPath) { if (string.IsNullOrWhiteSpace(directoryPath)) { return string.Empty; } return Path.TrimEndingDirectorySeparator(Path.GetFullPath(directoryPath.Trim())); } public static string NormalizeScopeBatchRootNullable(string? path) => string.IsNullOrWhiteSpace(path) ? string.Empty : NormalizeScopeDirectory(path); public static string GetVideoScopeDirectory(string videoFileFullPath) { var dir = Path.GetDirectoryName(Path.GetFullPath(videoFileFullPath)); return dir is null ? string.Empty : NormalizeScopeDirectory(dir); } /// Узкий общий предок каталогов всех указанных видеофайлов (полные пути). public static string? TryGetLowestCommonAncestorDirectory(IReadOnlyList videoFileAbsolutePaths) { if (videoFileAbsolutePaths.Count == 0) { return null; } var dirs = videoFileAbsolutePaths .Select(static p => { var d = Path.GetDirectoryName(Path.GetFullPath(p)); return d is null ? string.Empty : NormalizeScopeDirectory(d); }) .Where(static d => d.Length > 0) .Distinct(IC) .ToList(); if (dirs.Count == 0) { return null; } var acc = dirs[0]; for (var i = 1; i < dirs.Count; i++) { acc = LowestCommonAncestorOfTwoDirectories(acc, dirs[i]); if (string.IsNullOrEmpty(acc)) { return null; } } return acc; } private static string LowestCommonAncestorOfTwoDirectories(string dirA, string dirB) { if (dirA.Length == 0 || dirB.Length == 0) { return string.Empty; } var ancestorsA = new HashSet(EnumerateAncestorDirectoriesInclusive(dirA), IC); foreach (var cand in EnumerateAncestorDirectoriesInclusive(dirB)) { if (ancestorsA.Contains(cand)) { return cand; } } return string.Empty; } /// От указанной папки вверх к корню включая сам каталог. private static IEnumerable EnumerateAncestorDirectoriesInclusive(string normalizedAbsoluteDir) { var d = NormalizeScopeDirectory(normalizedAbsoluteDir); while (!string.IsNullOrEmpty(d)) { yield return d; var parent = Path.GetDirectoryName(d); if (string.IsNullOrEmpty(parent)) { yield break; } parent = NormalizeScopeDirectory(parent); if (string.Equals(parent, d, StringComparison.OrdinalIgnoreCase)) { yield break; } d = parent; } } }