using CliWrap; using Microsoft.Extensions.Logging; using ShellProgressBar; using System.Collections.ObjectModel; using System.Data; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using System.Text.Json; using View_by_Distance.Metadata.Models; using View_by_Distance.Metadata.Models.Stateless; using View_by_Distance.Rename.Models; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Properties; using View_by_Distance.Shared.Models.Stateless; namespace View_by_Distance.Rename; public partial class Rename : IRename, IDisposable { private sealed record ToDo(string? Directory, FileInfo FileInfo, string File, bool JsonFile); private sealed record Record(DateTime DateTime, ExifDirectory ExifDirectory, bool FastForwardMovingPictureExpertsGroupUsed, FileHolder[] SidecarFiles, bool HasDateTimeOriginal, bool HasIgnoreKeyword, string JsonFile); private ProgressBar? _ProgressBar; private readonly ProgressBarOptions _ProgressBarOptions; public Rename(List args, ILogger? logger, AppSettings appSettings, bool isSilent, IConsole console) { if (isSilent) { } if (args is null) throw new NullReferenceException(nameof(args)); if (console is null) throw new NullReferenceException(nameof(console)); IRename rename = this; long ticks = DateTime.Now.Ticks; _ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; RenameWork(logger, appSettings, rename, ticks); } void IRename.Tick() => _ProgressBar?.Tick(); void IRename.ConstructProgressBar(int maxTicks, string message) { _ProgressBar?.Dispose(); _ProgressBar = new(maxTicks, message, _ProgressBarOptions); } void IDisposable.Dispose() { _ProgressBar?.Dispose(); GC.SuppressFinalize(this); } ReadOnlyCollection IRename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IRenameSettings renameSettings, FilePath filePath) { List results = []; bool isValidVideoFormatExtensions = renameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered); if (isValidVideoFormatExtensions) { bool check; try { CommandTask commandTask = Cli.Wrap("L:/Git/ffmpeg-2024-10-02-git-358fdf3083-full_build/bin/ffmpeg.exe") .WithArguments(["-i", filePath.FullName, "-vf", "select=eq(n\\,0)", "-q:v", "1", $"{filePath.Name}-%4d.jpg"]) .WithWorkingDirectory(filePath.DirectoryFullPath) .ExecuteAsync(); commandTask.Task.Wait(); check = true; } catch (Exception) { check = false; } if (check) { results.AddRange(Directory.GetFiles(filePath.DirectoryFullPath, $"{filePath.Name}-*.jpg", SearchOption.TopDirectoryOnly)); if (results.Count == 0) throw new Exception(); File.SetCreationTime(results[0], new(filePath.CreationTicks)); File.SetLastWriteTime(results[0], new(filePath.LastWriteTicks)); Thread.Sleep(100); } } return results.AsReadOnly(); } #pragma warning disable CA1416 DeterministicHashCode IRename.GetDeterministicHashCode(FilePath filePath) { DeterministicHashCode result; int? id; int? width; int? height; try { using Image image = Image.FromFile(filePath.FullName); width = image.Width; height = image.Height; using Bitmap bitmap = new(image); Rectangle rectangle = new(0, 0, image.Width, image.Height); BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, bitmap.PixelFormat); IntPtr intPtr = bitmapData.Scan0; int length = bitmapData.Stride * bitmap.Height; byte[] bytes = new byte[length]; Marshal.Copy(intPtr, bytes, 0, length); bitmap.UnlockBits(bitmapData); id = IId.GetDeterministicHashCode(bytes); } catch (Exception) { id = null; width = null; height = null; } result = new(height, id, width); return result; } #pragma warning restore CA1416 private void NonParallelismAndInPlace(AppSettings appSettings, IRename rename, ReadOnlyCollection ids, ExifDirectory exifDirectory, MinimumYearAndPathCombined minimumYearAndPathCombined, bool fastForwardMovingPictureExpertsGroupUsed, FileHolder[] sidecarFiles) { if (exifDirectory.FilePath.Id is null) throw new NotImplementedException(); int i = 0; ToDo toDo; const string jpg = ".jpg"; const string jpeg = ".jpeg"; List toDoCollection = []; DateTime? dateTime = IDate.GetDateTimeOriginal(exifDirectory); ReadOnlyCollection keywords = IMetadata.GetKeywords(exifDirectory); bool hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(keywords.Contains); string checkFileExtension = exifDirectory.FilePath.ExtensionLowered == jpeg ? jpg : exifDirectory.FilePath.ExtensionLowered; bool hasDateTimeOriginal = dateTime is not null; string paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, exifDirectory.FilePath.Id.Value, hasIgnoreKeyword, hasDateTimeOriginal, i); string checkDirectory = appSettings.RenameSettings.InPlaceWithOriginalName ? Path.Combine(exifDirectory.FilePath.DirectoryFullPath, exifDirectory.FilePath.FileNameFirstSegment) : exifDirectory.FilePath.DirectoryFullPath; string checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); if (checkFile != exifDirectory.FilePath.FullName) { if (File.Exists(checkFile)) { checkFile = string.Concat(checkFile, ".del"); if (File.Exists(checkFile)) throw new NotImplementedException(); } toDo = new(Directory: checkDirectory, FileInfo: new(exifDirectory.FilePath.FullName), File: checkFile, JsonFile: false); toDoCollection.Add(toDo); if (sidecarFiles.Length != 0) { if (appSettings.RenameSettings.InPlace) throw new NotSupportedException($"Must use {nameof(appSettings.RenameSettings.InPlaceWithOriginalName)} when sidecar file(s) are present!"); dateTime ??= IDate.GetMinimum(exifDirectory); Record record = new(DateTime: dateTime.Value, ExifDirectory: exifDirectory, FastForwardMovingPictureExpertsGroupUsed: fastForwardMovingPictureExpertsGroupUsed, SidecarFiles: sidecarFiles, HasDateTimeOriginal: hasDateTimeOriginal, HasIgnoreKeyword: hasIgnoreKeyword, JsonFile: minimumYearAndPathCombined.PathCombined); toDoCollection.AddRange(GetSidecarFiles(appSettings, record, [], checkDirectory, paddedId)); } _ = RenameFilesInDirectories(appSettings.RenameSettings, rename, new(toDoCollection)); string jsonFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}.json"); File.Move(minimumYearAndPathCombined.PathCombined, jsonFile, overwrite: true); if (appSettings.RenameSettings.InPlaceWithOriginalName && ids.Count > 0) { string contains = ids.Contains(exifDirectory.FilePath.Id.Value) ? "_ Exists _" : "_ New _"; string idCheck = Path.Combine(checkDirectory, contains, fastForwardMovingPictureExpertsGroupUsed ? "Video" : "Image"); if (!Directory.Exists(idCheck)) _ = Directory.CreateDirectory(idCheck); } } } private List GetFirstPassCollection(ILogger? logger, AppSettings appSettings, IRename rename, long ticks, ReadOnlyCollection ids, IEnumerable files, A_Metadata metadata) { List results = []; int index = -1; FilePath filePath; TimeSpan timeSpan; FirstPass firstPass; string directoryName; ExifDirectory exifDirectory; List sidecarFiles; DeterministicHashCode deterministicHashCode; bool fastForwardMovingPictureExpertsGroupUsed; MinimumYearAndPathCombined minimumYearAndPathCombined; FilePath? fastForwardMovingPictureExpertsGroupFilePath; ReadOnlyCollection? fastForwardMovingPictureExpertsGroupFiles; ReadOnlyDictionary> keyValuePairs = IMetadata.GetKeyValuePairs(files); foreach (KeyValuePair> keyValuePair in keyValuePairs) { index += 1; rename.Tick(); if (keyValuePair.Value.Count > 1 && !appSettings.RenameSettings.ForceNewId) { if (appSettings.RenameSettings.InPlaceMoveDirectory) continue; throw new NotSupportedException($"When sidecar files are present {nameof(appSettings.RenameSettings.ForceNewId)} must be true!"); } if (keyValuePair.Value.Count > 2) throw new NotSupportedException("Too many sidecar files!"); foreach (FileHolder fileHolder in keyValuePair.Value) { if (appSettings.RenameSettings.SidecarExtensions.Contains(fileHolder.ExtensionLowered)) continue; if (appSettings.RenameSettings.IgnoreExtensions.Contains(fileHolder.ExtensionLowered)) continue; filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index); if (appSettings.RenameSettings.SkipIdFiles && filePath.Id is not null && (filePath.IsIntelligentIdFormat || filePath.SortOrder is not null)) continue; if (!appSettings.RenameSettings.ForceNewId && filePath.Id is not null) { fastForwardMovingPictureExpertsGroupFiles = null; deterministicHashCode = new(null, filePath.Id, null); directoryName = Path.GetFileName(filePath.DirectoryFullPath); if (appSettings.RenameSettings.InPlaceWithOriginalName || (appSettings.RenameSettings.InPlace && directoryName.EndsWith(filePath.Id.Value.ToString()))) continue; } else { fastForwardMovingPictureExpertsGroupFiles = rename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(appSettings.RenameSettings, filePath); fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index); deterministicHashCode = fastForwardMovingPictureExpertsGroupFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(fastForwardMovingPictureExpertsGroupFilePath); } sidecarFiles = []; filePath = FilePath.Get(filePath, deterministicHashCode); for (int i = 0; i < keyValuePair.Value.Count; i++) { if (keyValuePair.Value[i].ExtensionLowered == fileHolder.ExtensionLowered) continue; sidecarFiles.Add(keyValuePair.Value[i]); } try { (minimumYearAndPathCombined, exifDirectory) = metadata.GetMetadataCollection(appSettings.ResultSettings, appSettings.MetadataSettings, filePath); } catch (Exception) { logger?.LogWarning("<{filePath}>", filePath.FullName); continue; } fastForwardMovingPictureExpertsGroupUsed = fastForwardMovingPictureExpertsGroupFiles is not null && fastForwardMovingPictureExpertsGroupFiles.Count > 0; if (fastForwardMovingPictureExpertsGroupUsed && fastForwardMovingPictureExpertsGroupFiles is not null) { foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles) File.Delete(fastForwardMovingPictureExpertsGroupFile); } if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName) NonParallelismAndInPlace(appSettings, rename, ids, exifDirectory, minimumYearAndPathCombined, fastForwardMovingPictureExpertsGroupUsed, sidecarFiles.ToArray()); if (!fastForwardMovingPictureExpertsGroupUsed && appSettings.RenameSettings.InPlaceMoveDirectory && appSettings.RenameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered)) fastForwardMovingPictureExpertsGroupUsed = true; firstPass = new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, minimumYearAndPathCombined, sidecarFiles.ToArray()); results.Add(firstPass); } timeSpan = new(DateTime.Now.Ticks - ticks); if (timeSpan.TotalMilliseconds > appSettings.RenameSettings.MaxMilliSecondsPerCall) break; } return results; } private static ReadOnlyCollection GetRecordCollection(AppSettings appSettings, List collection) { List results = []; Record record; DateTime? dateTime; bool hasIgnoreKeyword; bool hasDateTimeOriginal; ReadOnlyCollection keywords; foreach (FirstPass firstPass in collection) { dateTime = IDate.GetDateTimeOriginal(firstPass.ExifDirectory); hasDateTimeOriginal = dateTime is not null; dateTime ??= IDate.GetMinimum(firstPass.ExifDirectory); keywords = IMetadata.GetKeywords(firstPass.ExifDirectory); hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(l => keywords.Contains(l)); record = new(DateTime: dateTime.Value, ExifDirectory: firstPass.ExifDirectory, FastForwardMovingPictureExpertsGroupUsed: firstPass.FastForwardMovingPictureExpertsGroupUsed, SidecarFiles: firstPass.SidecarFiles, HasDateTimeOriginal: hasDateTimeOriginal, HasIgnoreKeyword: hasIgnoreKeyword, JsonFile: firstPass.MinimumYearAndPathCombined.PathCombined); results.Add(record); } return results.AsReadOnly(); } private ReadOnlyCollection GetRecordCollection(ILogger? logger, AppSettings appSettings, IRename rename, long ticks, ReadOnlyCollection ids, string sourceDirectory, ReadOnlyCollection files) { ReadOnlyCollection results; FirstPass firstPass; List collection; string? checkFile = string.IsNullOrEmpty(appSettings.RenameSettings.FirstPassFile) ? null : Path.Combine(sourceDirectory, appSettings.RenameSettings.FirstPassFile); if (!string.IsNullOrEmpty(checkFile) && File.Exists(checkFile)) { string json = File.ReadAllText(checkFile); collection = JsonSerializer.Deserialize(json, FirstPassCollectionSourceGenerationContext.Default.ListFirstPass) ?? throw new Exception(); } else { collection = []; A_Metadata metadata = new(appSettings.ResultSettings, appSettings.MetadataSettings); int appSettingsMaxDegreeOfParallelism = appSettings.RenameSettings.MaxDegreeOfParallelism; int filesCount = appSettingsMaxDegreeOfParallelism == 1 ? files.Count : 123000; rename.ConstructProgressBar(filesCount, "EnumerateFiles load"); if (appSettingsMaxDegreeOfParallelism == 1) collection.AddRange(GetFirstPassCollection(logger, appSettings, rename, ticks, ids, files, metadata)); else { List distinct = []; List metadataGroups = []; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism }; files.AsParallel().ForAll(IMetadata.SetExifDirectoryCollection(rename, appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.RenameSettings, metadata, distinct, metadataGroups)); Thread.Sleep(500); foreach (MetadataGroup metadataGroup in metadataGroups) { if (metadataGroup.FastForwardMovingPictureExpertsGroupUsed || !appSettings.RenameSettings.InPlaceMoveDirectory || !appSettings.RenameSettings.ValidVideoFormatExtensions.Contains(metadataGroup.FilePath.ExtensionLowered)) firstPass = new(metadataGroup.ExifDirectory, metadataGroup.FastForwardMovingPictureExpertsGroupUsed, metadataGroup.MinimumYearAndPathCombined, metadataGroup.SidecarFiles.ToArray()); else firstPass = new(metadataGroup.ExifDirectory, FastForwardMovingPictureExpertsGroupUsed: true, metadataGroup.MinimumYearAndPathCombined, metadataGroup.SidecarFiles.ToArray()); collection.Add(firstPass); } } if (!string.IsNullOrEmpty(checkFile)) { string json = JsonSerializer.Serialize(collection, FirstPassCollectionSourceGenerationContext.Default.ListFirstPass); File.WriteAllText(Path.Combine(sourceDirectory, $"{ticks}.json"), json); } } results = GetRecordCollection(appSettings, collection); return results; } private static void VerifyIntMinValueLength(MetadataSettings metadataSettings, ReadOnlyCollection recordCollection) { foreach (Record record in recordCollection) { if (record.ExifDirectory.FilePath.Id is null) continue; if (metadataSettings.IntMinValueLength < record.ExifDirectory.FilePath.Id.Value.ToString().Length) throw new NotSupportedException(); } } private static string GetTFW(Record record, bool? isWrongYear) => string.Concat(record.HasDateTimeOriginal ? "T" : "F", isWrongYear is not null && isWrongYear.Value ? "W" : record.FastForwardMovingPictureExpertsGroupUsed ? "V" : "I"); private static string GetDirectoryName(string year, string tfw, string prefix, string? splat, int seasonValue, string seasonName, string makerSplit) => splat is null ? $"{prefix}{year} {tfw}{year}.{seasonValue} {seasonName}{makerSplit}" : $"{prefix}{year} {tfw}{year}{splat}"; private static string? GetCheckDirectory(AppSettings appSettings, DirectoryInfo directoryInfo, Record record, ReadOnlyCollection ids, bool multipleDirectoriesWithFiles, string paddedId) { string? result; string year = record.DateTime.Year.ToString(); string checkDirectoryName = Path.GetFileName(record.ExifDirectory.FilePath.DirectoryFullPath); if (multipleDirectoriesWithFiles && !checkDirectoryName.Contains(year)) result = null; else { (bool? isWrongYear, string[] years) = IDate.IsWrongYear(directoryInfo, record.ExifDirectory.FilePath, record.ExifDirectory); if (appSettings.RenameSettings.InPlaceMoveDirectory && !record.ExifDirectory.FilePath.FileNameFirstSegment.Contains(paddedId)) result = null; else { if (record.ExifDirectory.FilePath.FullName[..2] != directoryInfo.FullName[..2]) isWrongYear = null; string tfw = GetTFW(record, isWrongYear); string? maker = IMetadata.GetMaker(record.ExifDirectory); string rootDirectory = appSettings.ResultSettings.RootDirectory; string[] segments = checkDirectoryName.Split(years, StringSplitOptions.None); (int seasonValue, string seasonName) = IDate.GetSeason(record.DateTime.DayOfYear); string? splat = checkDirectoryName.Length > 3 && checkDirectoryName[^3..][1] == '!' ? checkDirectoryName[^3..] : null; string contains = record.ExifDirectory.FilePath.Id is null || ids.Contains(record.ExifDirectory.FilePath.Id.Value) ? "_ Exists _" : "_ New-Destination _"; string makerSplit = string.IsNullOrEmpty(maker) ? string.IsNullOrEmpty(appSettings.RenameSettings.DefaultMaker) ? string.Empty : appSettings.RenameSettings.DefaultMaker : $" {maker.Split(' ')[0]}"; string directoryName = GetDirectoryName(year, tfw, segments[0], splat, seasonValue, seasonName, makerSplit); result = Path.GetFullPath(Path.Combine(rootDirectory, contains, directoryName)); } } return result; } private static List GetSidecarFiles(AppSettings appSettings, Record record, List distinct, string checkDirectory, string paddedId) { List results = []; ToDo toDo; string checkFile; FilePath filePath; string checkFileExtension; foreach (FileHolder fileHolder in record.SidecarFiles) { checkFileExtension = fileHolder.ExtensionLowered; filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null); checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); if (checkFile == filePath.FullName) continue; if (File.Exists(checkFile)) { checkFile = string.Concat(checkFile, ".del"); if (File.Exists(checkFile)) continue; } if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); toDo = new(Directory: checkDirectory, FileInfo: new(filePath.FullName), File: checkFile, JsonFile: false); results.Add(toDo); } return results; } private static bool? GetDirectoryCheck(ResultSettings resultSettings) { bool? result = null; IEnumerable files; string[] directories = Directory.GetDirectories(resultSettings.RootDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string directory in directories) { files = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories); foreach (string _ in files) { if (result is null) result = false; else if (result.Value) result = true; break; } if (result is not null && result.Value) break; } return result; } private static ReadOnlyCollection GetToDoCollection(AppSettings appSettings, DirectoryInfo directoryInfo, ReadOnlyCollection ids, ReadOnlyCollection files, ReadOnlyCollection recordCollection) { List results = []; ToDo? toDo; Record record; string jsonFile; string paddedId; string checkFile; FilePath filePath; FileInfo[] matches; string directoryName; FileHolder fileHolder; string? checkDirectory; const string jpg = ".jpg"; string checkFileExtension; List distinct = []; const string jpeg = ".jpeg"; string jsonFileSubDirectory; bool? directoryCheck = GetDirectoryCheck(appSettings.ResultSettings); VerifyIntMinValueLength(appSettings.MetadataSettings, recordCollection); bool multipleDirectoriesWithFiles = directoryCheck is not null && directoryCheck.Value; ReadOnlyCollection collection = (from l in files select new FileInfo(l)).ToArray().AsReadOnly(); ReadOnlyCollection sorted = (from l in recordCollection orderby l.DateTime select l).ToArray().AsReadOnly(); for (int i = 0; i < sorted.Count; i++) { record = sorted[i]; if (record.ExifDirectory.FilePath.Id is null) continue; paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, record.ExifDirectory.FilePath.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, i); checkDirectory = GetCheckDirectory(appSettings, directoryInfo, record, ids, multipleDirectoriesWithFiles, paddedId); if (string.IsNullOrEmpty(checkDirectory)) continue; checkFileExtension = record.ExifDirectory.FilePath.ExtensionLowered == jpeg ? jpg : record.ExifDirectory.FilePath.ExtensionLowered; jsonFileSubDirectory = Path.GetDirectoryName(Path.GetDirectoryName(record.JsonFile)) ?? throw new Exception(); checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); if (checkFile == record.ExifDirectory.FilePath.FullName) continue; if (File.Exists(checkFile)) { checkFile = string.Concat(checkFile, ".del"); if (File.Exists(checkFile)) continue; } (directoryName, _) = IPath.GetDirectoryNameAndIndex(appSettings.ResultSettings, record.ExifDirectory.FilePath); jsonFile = Path.Combine(jsonFileSubDirectory, directoryName, $"{record.ExifDirectory.FilePath.Id.Value}{checkFileExtension}.json"); if (record.JsonFile != jsonFile) { fileHolder = FileHolder.Get(record.JsonFile); if (fileHolder.Exists) { filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null); toDo = new(Directory: null, FileInfo: new(filePath.FullName), File: jsonFile, JsonFile: true); results.Add(toDo); } } if (distinct.Contains(checkFile)) continue; toDo = null; distinct.Add(checkFile); if (files.Contains(record.ExifDirectory.FilePath.FullName)) toDo = new(Directory: checkDirectory, FileInfo: new(record.ExifDirectory.FilePath.FullName), File: checkFile, JsonFile: false); else { matches = (from l in collection where record.ExifDirectory.FilePath.Name == l.Name select l).ToArray(); toDo = matches.Length != 1 ? null : new(Directory: checkDirectory, FileInfo: matches[0], File: checkFile, JsonFile: false); } if (toDo is null) continue; results.Add(toDo); if (record.SidecarFiles.Length == 0) continue; results.AddRange(GetSidecarFiles(appSettings, record, distinct, checkDirectory, paddedId)); } return results.AsReadOnly(); } private static void VerifyDirectories(ReadOnlyCollection toDoCollection) { List distinct = []; foreach (ToDo toDo in toDoCollection) { if (toDo.Directory is null || distinct.Contains(toDo.Directory)) continue; if (!Directory.Exists(toDo.Directory)) _ = Directory.CreateDirectory(toDo.Directory); distinct.Add(toDo.Directory); } } private ReadOnlyCollection RenameFilesInDirectories(RenameSettings renameSettings, IRename rename, ReadOnlyCollection toDoCollection) { List results = []; VerifyDirectories(toDoCollection); bool useProgressBar = !renameSettings.InPlace && !renameSettings.InPlaceWithOriginalName; if (useProgressBar) rename.ConstructProgressBar(toDoCollection.Count, "Move Files"); foreach (ToDo toDo in toDoCollection) { if (useProgressBar) _ProgressBar?.Tick(); if (!toDo.FileInfo.Exists) continue; if (toDo.JsonFile) { if (File.Exists(toDo.File)) File.Delete(toDo.File); try { File.Move(toDo.FileInfo.FullName, toDo.File); } catch (Exception) { continue; } } else if (toDo.Directory is null) throw new NotSupportedException(); else { if (File.Exists(toDo.File)) File.Delete(toDo.File); try { File.Move(toDo.FileInfo.FullName, toDo.File); } catch (Exception) { continue; } results.Add($"{toDo.FileInfo.FullName}\t{toDo.File}"); } } if (useProgressBar) _ProgressBar?.Dispose(); return results.AsReadOnly(); } private static void SaveIdentifiersToDisk(long ticks, AppSettings appSettings, ReadOnlyCollection recordCollection) { string paddedId; Identifier identifier; List identifiers = []; string aMetadataCollectionDirectory = IResult.GetResultsDateGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata), appSettings.ResultSettings.ResultCollection); foreach (Record record in recordCollection) { if (record.ExifDirectory.FilePath.Id is null) continue; paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, record.ExifDirectory.FilePath.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, index: null); identifier = new([], record.HasDateTimeOriginal, record.ExifDirectory.FilePath.Id.Value, record.ExifDirectory.FilePath.Length, paddedId, record.DateTime.Ticks); identifiers.Add(identifier); } string json = JsonSerializer.Serialize(identifiers.OrderBy(l => l.PaddedId).ToArray(), IdentifierCollectionSourceGenerationContext.Default.IdentifierArray); _ = IPath.WriteAllText(Path.Combine(aMetadataCollectionDirectory, $"{ticks}.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); } private static ReadOnlyCollection GetIds(RenameSettings renameSettings) { ReadOnlyCollection results; string? propertyCollectionFile = string.IsNullOrEmpty(renameSettings.RelativePropertyCollectionFile) ? null : renameSettings.RelativePropertyCollectionFile; string? json = !File.Exists(propertyCollectionFile) ? null : File.ReadAllText(propertyCollectionFile); Identifier[]? identifiers = json is null ? null : JsonSerializer.Deserialize(json, IdentifierCollectionSourceGenerationContext.Default.IdentifierArray); if (identifiers is null && !string.IsNullOrEmpty(renameSettings.RelativePropertyCollectionFile)) throw new Exception($"Invalid {nameof(renameSettings.RelativePropertyCollectionFile)}"); results = identifiers is null ? new([]) : new((from l in identifiers select l.Id).ToArray()); return results; } private void RenameWork(ILogger? logger, AppSettings appSettings, IRename rename, long ticks) { ReadOnlyCollection ids = GetIds(appSettings.RenameSettings); string sourceDirectory = Path.GetFullPath(appSettings.ResultSettings.RootDirectory); if (!Directory.Exists(sourceDirectory)) _ = Directory.CreateDirectory(sourceDirectory); logger?.LogInformation("{Ticks} {RootDirectory}", ticks, sourceDirectory); ReadOnlyCollection files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories).ToArray().AsReadOnly(); if (files.Count > 0) _ = IPath.DeleteEmptyDirectories(appSettings.ResultSettings.RootDirectory); ReadOnlyCollection recordCollection = GetRecordCollection(logger, appSettings, rename, ticks, ids, sourceDirectory, files); SaveIdentifiersToDisk(ticks, appSettings, recordCollection); if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName) { if (recordCollection.Count > 0) recordCollection = new([]); string aMetadataSingletonDirectory = IResult.GetResultsGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata)); _ = IPath.DeleteEmptyDirectories(aMetadataSingletonDirectory); } if (!appSettings.RenameSettings.OnlySaveIdentifiersToDisk) { DirectoryInfo directoryInfo = new(sourceDirectory); ReadOnlyCollection toDoCollection = GetToDoCollection(appSettings, directoryInfo, ids, files, recordCollection); ReadOnlyCollection lines = RenameFilesInDirectories(appSettings.RenameSettings, rename, toDoCollection); if (lines.Count != 0) { File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", lines); _ = IPath.DeleteEmptyDirectories(directoryInfo.FullName); } } } }