109 lines
3.4 KiB
C#
109 lines
3.4 KiB
C#
using System.IO;
|
|
|
|
namespace EmbyToolbox.Services;
|
|
|
|
/// <summary>Область применения snapshot: каталог эпизода и опционально общий корень батча.</summary>
|
|
public static class SnapshotScopePaths
|
|
{
|
|
private static readonly StringComparer IC = StringComparer.OrdinalIgnoreCase;
|
|
|
|
/// <summary>Абсолютный путь без завершающего разделителя.</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>Узкий общий предок каталогов всех указанных видеофайлов (полные пути).</summary>
|
|
public static string? TryGetLowestCommonAncestorDirectory(IReadOnlyList<string> 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<string>(EnumerateAncestorDirectoriesInclusive(dirA), IC);
|
|
foreach (var cand in EnumerateAncestorDirectoriesInclusive(dirB))
|
|
{
|
|
if (ancestorsA.Contains(cand))
|
|
{
|
|
return cand;
|
|
}
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <summary>От указанной папки вверх к корню включая сам каталог.</summary>
|
|
private static IEnumerable<string> 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;
|
|
}
|
|
}
|
|
}
|