diff --git a/.kanbn/index.md b/.kanbn/index.md index 199afaf..c653826 100644 --- a/.kanbn/index.md +++ b/.kanbn/index.md @@ -22,6 +22,7 @@ taskTemplate: '^+^_${overdue ? ''^R'' : ''''}${name}^: ${relations ? (''\n^-^/^g - [photoview-in-docker-for-a-viewer-only](tasks/photoview-in-docker-for-a-viewer-only.md) - [family-tree-as-markdown-files](tasks/family-tree-as-markdown-files.md) - [image-size-distribution-per-exif-model-directory-when-no-model](tasks/image-size-distribution-per-exif-model-directory-when-no-model.md) +- [cluster-questioning](tasks/cluster-questioning.md) ## Todo diff --git a/.kanbn/tasks/cluster-questioning.md b/.kanbn/tasks/cluster-questioning.md new file mode 100644 index 0000000..b99cbfe --- /dev/null +++ b/.kanbn/tasks/cluster-questioning.md @@ -0,0 +1,13 @@ +--- +created: 2023-07-09T04:12:44.673Z +updated: 2023-07-09T04:12:44.667Z +assigned: "" +progress: 0 +tags: [] +--- + +# Cluster Questioning + +Ask Logan for more details + +- [ ] [k-means-clustering-introduction](https://www.geeksforgeeks.org/k-means-clustering-introduction/) \ No newline at end of file diff --git a/.kanbn/tasks/merge-kristy-files.md b/.kanbn/tasks/merge-kristy-files.md index d5348cc..a1a12d4 100644 --- a/.kanbn/tasks/merge-kristy-files.md +++ b/.kanbn/tasks/merge-kristy-files.md @@ -11,10 +11,16 @@ started: 2023-07-08T21:44:14.665Z # Merge Kristy Files +```c# +string[] test = (from l in new string[] { "Mikes", "Mike", "Mik-e" } orderby l.EndsWith('s'), l.Contains('-') select l).ToArray(); +return new(result, (from l in results orderby l.FileHolder.DirectoryName?.EndsWith('s'), l.FileHolder.DirectoryName?.Contains('-'), l.FileHolder.DirectoryName?.Length == 12 select l).ToArray()); +``` + ## Sub-tasks - [x] Convert .tiff to .jpg with Nikon Nx Studio at 100% -- [ ] Keep .tiff files in 2-Images-B -- [ ] Rotate the .jpg only -- [ ] Does original name matter to Kristy -- [ ] Use Rename console app to rename for storage +- [/] Keep .tiff files in 2-Images-B +- [ ] Copy to each backup from question +- [x] Rotate the .jpg only +- [x] Use Rename console app to rename for storage +- [ ] Move to production ... diff --git a/Copy-Distinct/CopyDistinct.cs b/Copy-Distinct/CopyDistinct.cs index 68580f5..e57cc97 100644 --- a/Copy-Distinct/CopyDistinct.cs +++ b/Copy-Distinct/CopyDistinct.cs @@ -87,6 +87,8 @@ public class CopyDistinct } } } + if (move && _AppSettings.IfCanUseId) + throw new NotSupportedException("Not allowed because it would irreversible!"); return (move, filesCollection, anyLenFiles, moveBack); } @@ -128,9 +130,6 @@ public class CopyDistinct distinctFound.Add(file); } } - if (distinctNeeded.Count != distinctFound.Count) - continue; - break; } } foreach (string[] files in filesCollection) @@ -176,7 +175,7 @@ public class CopyDistinct progressBar = new(count, message, options); string key = string.IsNullOrEmpty(_AppSettings.ResultDirectoryKey) ? _PropertyConfiguration.ResultAllInOne : _AppSettings.ResultDirectoryKey; string[] directories = _FileGroups[key]; - (distinctDirectories, toDoCollection) = Shared.Models.Stateless.Methods.IDirectory.GetToDoCollection(_PropertyConfiguration, _AppSettings.CopyDuplicates, filesCollection, directories, () => progressBar.Tick()); + (distinctDirectories, toDoCollection) = Shared.Models.Stateless.Methods.IDirectory.GetToDoCollection(_PropertyConfiguration, _AppSettings.CopyDuplicates, _AppSettings.IfCanUseId, filesCollection, directories, () => progressBar.Tick()); progressBar.Dispose(); } foreach (string distinctDirectory in distinctDirectories) diff --git a/Copy-Distinct/Models/AppSettings.cs b/Copy-Distinct/Models/AppSettings.cs index 3aac425..3ed64e0 100644 --- a/Copy-Distinct/Models/AppSettings.cs +++ b/Copy-Distinct/Models/AppSettings.cs @@ -1,10 +1,12 @@ using System.Text.Json; +using System.Text.Json.Serialization; namespace View_by_Distance.Copy.Distinct.Models; public record AppSettings(string Company, bool CopyDuplicates, string CopyTo, + bool IfCanUseId, int MaxDegreeOfParallelism, string ResultDirectoryKey, string WorkingDirectoryName) @@ -12,8 +14,14 @@ public record AppSettings(string Company, public override string ToString() { - string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + string result = JsonSerializer.Serialize(this, AppSettingsSourceGenerationContext.Default.AppSettings); return result; } -} \ No newline at end of file +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AppSettings))] +internal partial class AppSettingsSourceGenerationContext : JsonSerializerContext +{ +} diff --git a/Copy-Distinct/Models/Binder/AppSettings.cs b/Copy-Distinct/Models/Binder/AppSettings.cs index 9ea4b19..d445599 100644 --- a/Copy-Distinct/Models/Binder/AppSettings.cs +++ b/Copy-Distinct/Models/Binder/AppSettings.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using System.Text.Json; +using System.Text.Json.Serialization; namespace View_by_Distance.Copy.Distinct.Models.Binder; @@ -7,15 +8,16 @@ public class AppSettings { public string? Company { get; set; } - public int? MaxDegreeOfParallelism { get; set; } public bool? CopyDuplicates { get; set; } public string? CopyTo { get; set; } - public string? ResultDirectoryKey { init; get; } + public bool? IfCanUseId { get; set; } + public int? MaxDegreeOfParallelism { get; set; } + public string? ResultDirectoryKey { get; set; } public string? WorkingDirectoryName { get; set; } public override string ToString() { - string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + string result = JsonSerializer.Serialize(this, BinderAppSettingsSourceGenerationContext.Default.AppSettings); return result; } @@ -28,6 +30,8 @@ public class AppSettings throw new NullReferenceException(nameof(appSettings.CopyDuplicates)); if (appSettings?.CopyTo is null) throw new NullReferenceException(nameof(appSettings.CopyTo)); + if (appSettings?.IfCanUseId is null) + throw new NullReferenceException(nameof(appSettings.IfCanUseId)); if (appSettings?.MaxDegreeOfParallelism is null) throw new NullReferenceException(nameof(appSettings.MaxDegreeOfParallelism)); if (appSettings?.ResultDirectoryKey is null) @@ -38,6 +42,7 @@ public class AppSettings appSettings.Company, appSettings.CopyDuplicates.Value, appSettings.CopyTo, + appSettings.IfCanUseId.Value, appSettings.MaxDegreeOfParallelism.Value, appSettings.ResultDirectoryKey, appSettings.WorkingDirectoryName @@ -48,9 +53,17 @@ public class AppSettings public static Models.AppSettings Get(IConfigurationRoot configurationRoot) { Models.AppSettings result; +#pragma warning disable IL3050, IL2026 AppSettings? appSettings = configurationRoot.Get(); +#pragma warning restore IL3050, IL2026 result = Get(appSettings); return result; } -} \ No newline at end of file +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AppSettings))] +internal partial class BinderAppSettingsSourceGenerationContext : JsonSerializerContext +{ +} diff --git a/Copy-Distinct/appsettings.json b/Copy-Distinct/appsettings.json index d3768b3..0d94920 100644 --- a/Copy-Distinct/appsettings.json +++ b/Copy-Distinct/appsettings.json @@ -3,6 +3,7 @@ "Company": "Mike Phares", "CopyDuplicates": true, "CopyTo": "", + "IfCanUseId": true, "Linux": {}, "Logging": { "LogLevel": { diff --git a/Rename/Models/AppSettings.cs b/Rename/Models/AppSettings.cs index a3df1aa..6351b6f 100644 --- a/Rename/Models/AppSettings.cs +++ b/Rename/Models/AppSettings.cs @@ -1,8 +1,10 @@ using System.Text.Json; +using System.Text.Json.Serialization; namespace View_by_Distance.Rename.Models; public record AppSettings(string Company, + string DefaultUnknownDirectoryName, bool ForceIdName, int MaxDegreeOfParallelism, int MaxMinutesDelta, @@ -12,8 +14,14 @@ public record AppSettings(string Company, public override string ToString() { - string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + string result = JsonSerializer.Serialize(this, AppSettingsSourceGenerationContext.Default.AppSettings); return result; } -} \ No newline at end of file +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AppSettings))] +internal partial class AppSettingsSourceGenerationContext : JsonSerializerContext +{ +} diff --git a/Rename/Models/Binder/AppSettings.cs b/Rename/Models/Binder/AppSettings.cs index 02d4b0e..ef998f0 100644 --- a/Rename/Models/Binder/AppSettings.cs +++ b/Rename/Models/Binder/AppSettings.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using System.Text.Json; +using System.Text.Json.Serialization; namespace View_by_Distance.Rename.Models.Binder; @@ -7,6 +8,7 @@ public class AppSettings { public string? Company { get; set; } + public string? DefaultUnknownDirectoryName { get; set; } public bool? ForceIdName { get; set; } public int? MaxDegreeOfParallelism { get; set; } public int? MaxMinutesDelta { get; set; } @@ -15,7 +17,7 @@ public class AppSettings public override string ToString() { - string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + string result = JsonSerializer.Serialize(this, BinderAppSettingsSourceGenerationContext.Default.AppSettings); return result; } @@ -24,6 +26,8 @@ public class AppSettings Models.AppSettings result; if (appSettings?.Company is null) throw new NullReferenceException(nameof(appSettings.Company)); + if (appSettings?.DefaultUnknownDirectoryName is null) + throw new NullReferenceException(nameof(appSettings.DefaultUnknownDirectoryName)); if (appSettings?.ForceIdName is null) throw new NullReferenceException(nameof(appSettings.ForceIdName)); if (appSettings?.MaxDegreeOfParallelism is null) @@ -36,6 +40,7 @@ public class AppSettings throw new NullReferenceException(nameof(appSettings.WorkingDirectoryName)); result = new( appSettings.Company, + appSettings.DefaultUnknownDirectoryName, appSettings.ForceIdName.Value, appSettings.MaxDegreeOfParallelism.Value, appSettings.MaxMinutesDelta.Value, @@ -48,9 +53,17 @@ public class AppSettings public static Models.AppSettings Get(IConfigurationRoot configurationRoot) { Models.AppSettings result; +#pragma warning disable IL3050, IL2026 AppSettings? appSettings = configurationRoot.Get(); +#pragma warning restore IL3050, IL2026 result = Get(appSettings); return result; } -} \ No newline at end of file +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AppSettings))] +internal partial class BinderAppSettingsSourceGenerationContext : JsonSerializerContext +{ +} diff --git a/Rename/Rename.cs b/Rename/Rename.cs index 6d879ac..3da3976 100644 --- a/Rename/Rename.cs +++ b/Rename/Rename.cs @@ -12,6 +12,15 @@ namespace View_by_Distance.Rename; public class Rename { + private record Record(int Index, + bool IsIgnoreExtension, + bool IsValidImageFormatExtension, + List FileHolders, + bool FfmpegFilesPresent, + DateTime? DateTimeOriginal, + DateTime?[] DateTimes, + int? Id); + private readonly AppSettings _AppSettings; private readonly string _WorkingDirectory; private readonly Configuration _Configuration; @@ -90,44 +99,27 @@ public class Rename return results; } - private List<(FileHolder, string, string)> GetToDoCollection(ProgressBar progressBar, string[] files, bool nefPresent) + private List GetRecords(int offset, ProgressBar progressBar, string[] files) { - List<(FileHolder, string, string)> results = new(); + List results = new(); int? id; - int season; string? message; - string checkFile; - DateTime dateTime; - bool? isWrongYear; - string seasonName; string? directory; - TimeSpan? timeSpan; - string directoryName; DateTime?[] dateTimes; FileHolder fileHolder; string[]? ffmpegFiles; bool isIgnoreExtension; - string? seasonDirectory; - const string jpg = ".jpg"; - DateTime? minimumDateTime; - string checkFileExtension; - DateTime? dateTimeFromName; DateTime? dateTimeOriginal; - const string jpeg = ".jpeg"; - DateTime?[] metadataDateTimes; - List distinct = new(); - string[] directoryNameSegments; bool isValidImageFormatExtension; - DateTime? metadataDateTimeOriginal; bool nameWithoutExtensionIsIdFormat; IReadOnlyList directories; - foreach (string file in files) + for (int i = 0; i < files.Length; i++) { progressBar.Tick(); - fileHolder = new(file); + fileHolder = new(files[i]); if (!fileHolder.Exists) continue; - directory = Path.GetDirectoryName(file); + directory = Path.GetDirectoryName(files[i]); if (string.IsNullOrEmpty(directory)) continue; if (fileHolder.ExtensionLowered == ".id" || fileHolder.ExtensionLowered == ".lsv" || fileHolder.DirectoryName is null) @@ -137,7 +129,6 @@ public class Rename nameWithoutExtensionIsIdFormat = Shared.Models.Stateless.Methods.IProperty.NameWithoutExtensionIsIdFormat(fileHolder); if (nameWithoutExtensionIsIdFormat) continue; - dateTimeFromName = Shared.Models.Stateless.Methods.IProperty.GetDateTimeFromName(fileHolder); isValidImageFormatExtension = _PropertyConfiguration.ValidImageFormatExtensions.Contains(fileHolder.ExtensionLowered); isIgnoreExtension = isValidImageFormatExtension && _PropertyConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered); if (!isIgnoreExtension && isValidImageFormatExtension) @@ -145,11 +136,11 @@ public class Rename else { try - { directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file); } + { directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(files[i]); } catch (Exception) { continue; } CommandTask result = Cli.Wrap("ffmpeg.exe") - // .WithArguments(new[] { "-ss", "00:00:00", "-t", "00:00:00", "-i", file, "-qscale:v", "2", "-r", "0.01", $"{fileHolder.Name}-%4d.jpg" }) - .WithArguments(new[] { "-i", file, "-vframes", "1", $"{fileHolder.Name}-%4d.jpg" }) + // .WithArguments(new[] { "-ss", "00:00:00", "-t", "00:00:00", "-i", files[i], "-qscale:v", "2", "-r", "0.01", $"{fileHolder.Name}-%4d.jpg" }) + .WithArguments(new[] { "-i", files[i], "-vframes", "1", $"{fileHolder.Name}-%4d.jpg" }) .WithWorkingDirectory(fileHolder.DirectoryName) .ExecuteAsync(); result.Task.Wait(); @@ -166,9 +157,57 @@ public class Rename if (fileHolder.DirectoryName is null) continue; } + (dateTimeOriginal, dateTimes, id, message) = Shared.Models.Stateless.Methods.IProperty.Get(fileHolder, isIgnoreExtension, isValidImageFormatExtension, _PropertyConfiguration.PopulatePropertyId); + if (ffmpegFiles is not null) + { + fileHolder = new(files[i]); + foreach (string ffmpegFile in ffmpegFiles) + File.Delete(ffmpegFile); + } + if (message is not null) + throw new Exception(message); + results.Add(new(i + offset, isIgnoreExtension, isValidImageFormatExtension, new() { fileHolder }, ffmpegFiles is null, dateTimeOriginal, dateTimes, id)); + } + return results; + } + + private List<(FileHolder, string, string)> GetToDoCollection(ProgressBar progressBar, bool nefPresent, List records, int length) + { + List<(FileHolder, string, string)> results = new(); + string id; + int season; + string checkFile; + bool? isWrongYear; + DateTime dateTime; + string seasonName; + TimeSpan? timeSpan; + string directoryName; + FileHolder fileHolder; + string? seasonDirectory; + const string jpg = ".jpg"; + DateTime? minimumDateTime; + string checkFileExtension; + DateTime? dateTimeFromName; + const string jpeg = ".jpeg"; + DateTime?[] metadataDateTimes; + List distinct = new(); + string[] directoryNameSegments; + DateTime? dateTimeOriginalByLogic; + DateTime? metadataDateTimeOriginal; + IReadOnlyList directories; + foreach (Record record in records) + { + progressBar.Tick(); + if (record.FileHolders.Count != 1) + continue; + fileHolder = record.FileHolders.First(); + if (!fileHolder.Exists) + continue; + if (string.IsNullOrEmpty(fileHolder.DirectoryName)) + continue; if (fileHolder.ExtensionLowered == jpeg) { - if (!isIgnoreExtension && isValidImageFormatExtension) + if (!record.IsIgnoreExtension && record.IsValidImageFormatExtension) { if (File.Exists($"{fileHolder.FullName}.id")) { @@ -178,7 +217,7 @@ public class Rename if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); - results.Add(new(new($"{fileHolder.FullName}.id"), directory, checkFile)); + results.Add(new(new($"{fileHolder.FullName}.id"), fileHolder.DirectoryName, checkFile)); } checkFile = Path.Combine(fileHolder.DirectoryName, $"{fileHolder.NameWithoutExtension}{jpg}"); if (File.Exists(checkFile)) @@ -186,11 +225,11 @@ public class Rename if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); - results.Add(new(fileHolder, directory, checkFile)); + results.Add(new(fileHolder, fileHolder.DirectoryName, checkFile)); if (nefPresent) - results.Add(new(new($"{fileHolder.FullName[..^4]}.tif"), directory, $"{checkFile[..^4]}.tif")); + results.Add(new(new($"{fileHolder.FullName[..^4]}.tif"), fileHolder.DirectoryName, $"{checkFile[..^4]}.tif")); if (nefPresent) - results.Add(new(new($"{fileHolder.FullName[..^4]}.nef"), directory, $"{checkFile[..^4]}.nef")); + results.Add(new(new($"{fileHolder.FullName[..^4]}.nef"), fileHolder.DirectoryName, $"{checkFile[..^4]}.nef")); if (File.Exists(checkFile)) continue; File.Move(fileHolder.FullName, checkFile); @@ -199,24 +238,16 @@ public class Rename continue; } } - (dateTimeOriginal, dateTimes, id, message) = Shared.Models.Stateless.Methods.IProperty.Get(fileHolder, isIgnoreExtension, isValidImageFormatExtension, _PropertyConfiguration.PopulatePropertyId); - if (ffmpegFiles is not null) - { - fileHolder = new(file); - foreach (string ffmpegFile in ffmpegFiles) - File.Delete(ffmpegFile); - } - if (fileHolder.DirectoryName is null) - continue; - minimumDateTime = dateTimes.Min(); + dateTimeFromName = Shared.Models.Stateless.Methods.IProperty.GetDateTimeFromName(fileHolder); + minimumDateTime = record.DateTimes.Min(); if (minimumDateTime is null) throw new NotSupportedException(); - if (dateTimeOriginal is null) + if (record.DateTimeOriginal is null) timeSpan = null; else - timeSpan = new(Math.Abs(minimumDateTime.Value.Ticks - dateTimeOriginal.Value.Ticks)); + timeSpan = new(Math.Abs(minimumDateTime.Value.Ticks - record.DateTimeOriginal.Value.Ticks)); if (timeSpan is not null && timeSpan.Value.TotalMinutes < _AppSettings.MaxMinutesDelta) - (metadataDateTimeOriginal, metadataDateTimes) = (null, Array.Empty()); + (dateTimeOriginalByLogic, metadataDateTimeOriginal, metadataDateTimes) = (record.DateTimeOriginal, null, Array.Empty()); else { if (_PropertyConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered)) @@ -225,16 +256,21 @@ public class Rename { directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(fileHolder.FullName); } catch (Exception) { continue; } (metadataDateTimeOriginal, metadataDateTimes) = Metadata.Models.Stateless.Methods.IMetadata.GetDateTimes(fileHolder, directories); - dateTimeOriginal ??= metadataDateTimeOriginal; - if (ffmpegFiles is not null && dateTimeOriginal is not null) - minimumDateTime = dateTimeOriginal.Value; - if (dateTimeOriginal is null) + dateTimeOriginalByLogic = record.DateTimeOriginal is not null ? record.DateTimeOriginal : metadataDateTimeOriginal; + if (record.FfmpegFilesPresent && dateTimeOriginalByLogic is not null) + minimumDateTime = dateTimeOriginalByLogic.Value; + if (dateTimeOriginalByLogic is null) timeSpan = null; else - timeSpan = new(Math.Abs(minimumDateTime.Value.Ticks - dateTimeOriginal.Value.Ticks)); + timeSpan = new(Math.Abs(minimumDateTime.Value.Ticks - dateTimeOriginalByLogic.Value.Ticks)); } if (timeSpan is null || timeSpan.Value.TotalMinutes > _AppSettings.MaxMinutesDelta) - (isWrongYear, seasonDirectory) = (null, !_AppSettings.ForceIdName ? null : Path.Combine(fileHolder.DirectoryName, "Unknown")); + { + if (string.IsNullOrEmpty(_AppSettings.DefaultUnknownDirectoryName)) + (isWrongYear, seasonDirectory) = (null, !_AppSettings.ForceIdName ? null : fileHolder.DirectoryName); + else + (isWrongYear, seasonDirectory) = (null, !_AppSettings.ForceIdName ? null : Path.Combine(fileHolder.DirectoryName, _AppSettings.DefaultUnknownDirectoryName)); + } else { directoryName = Path.GetFileName(fileHolder.DirectoryName); @@ -251,12 +287,10 @@ public class Rename { if (dateTimeFromName is not null && isWrongYear is not null && isWrongYear.Value) minimumDateTime = dateTimeFromName.Value; - else if (dateTimeOriginal is not null) - minimumDateTime = dateTimeOriginal.Value; - else if (metadataDateTimeOriginal is not null) - minimumDateTime = metadataDateTimeOriginal.Value; + else if (dateTimeOriginalByLogic is not null) + minimumDateTime = dateTimeOriginalByLogic.Value; else - minimumDateTime = new DateTime?[] { dateTimes.Where(l => l is not null).Min(), metadataDateTimes.Where(l => l is not null).Min() }.Min(); + minimumDateTime = new DateTime?[] { record.DateTimes.Where(l => l is not null).Min(), metadataDateTimes.Where(l => l is not null).Min() }.Min(); if (minimumDateTime is null) continue; checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; @@ -266,18 +300,19 @@ public class Rename if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); - results.Add(new(fileHolder, directory, checkFile)); + results.Add(new(fileHolder, fileHolder.DirectoryName, checkFile)); if (nefPresent) - results.Add(new(new($"{fileHolder.FullName[..^4]}.tif"), directory, $"{checkFile[..^4]}.tif")); + results.Add(new(new($"{fileHolder.FullName[..^4]}.tif"), fileHolder.DirectoryName, $"{checkFile[..^4]}.tif")); if (nefPresent) - results.Add(new(new($"{fileHolder.FullName[..^4]}.nef"), directory, $"{checkFile[..^4]}.nef")); + results.Add(new(new($"{fileHolder.FullName[..^4]}.nef"), fileHolder.DirectoryName, $"{checkFile[..^4]}.nef")); } else { - if (id is null) + if (record.Id is null) continue; + id = Shared.Models.Stateless.Methods.IDirectory.GetPaddedId(length, record.Index, record.Id.Value); checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; - checkFile = Path.Combine(seasonDirectory, $"{id.Value}{checkFileExtension}"); + checkFile = Path.Combine(seasonDirectory, $"{id}{checkFileExtension}"); if (checkFile == fileHolder.FullName) continue; if (File.Exists(checkFile)) @@ -317,13 +352,16 @@ public class Rename { List results = new(); string message; - bool nefPresent; + bool nefPresentCheck; + bool nefPresent = false; ProgressBar progressBar; + List records = new(); const string fileSearchFilter = "*"; const string directorySearchFilter = "*"; List distinctDirectories = new(); List<(FileHolder, string)> verifiedToDoCollection = new(); List<(FileHolder, string, string)> toDoCollection = new(); + int offset = Shared.Models.Stateless.Methods.IDirectory.GetOffset(); ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; List filesCollection = Shared.Models.Stateless.Methods.IDirectory.GetFilesCollection(_PropertyConfiguration.RootDirectory, directorySearchFilter, fileSearchFilter); int count = filesCollection.Select(l => l.Length).Sum(); @@ -331,28 +369,52 @@ public class Rename { if (!files.Any()) continue; - // foreach (string file in files) + // foreach (string files[i] in files) // { - // if (!file.EndsWith(".del")) + // if (!files[i].EndsWith(".del")) // continue; - // File.Move(file, file[..^4]); + // File.Move(files[i], files[i][..^4]); // } // continue; distinctDirectories.Clear(); - message = $") Renaming files for <{files.FirstOrDefault()}>"; - progressBar = new(files.Length, message, options); if (_AppSettings.RenameUndo) + { + message = $") Undo renaming files for <{files.FirstOrDefault()}>"; + progressBar = new(files.Length, message, options); toDoCollection.AddRange(GetRenameUndoToDoCollection(progressBar, files)); + } else { - nefPresent = files.Any(l => l.EndsWith(".NEF")); - if (!nefPresent) - toDoCollection.AddRange(GetToDoCollection(progressBar, files, nefPresent)); + message = $"{records.Count:00000}) Gathering records for files next to <{files.FirstOrDefault()}>"; + progressBar = new(files.Length, message, options); + nefPresentCheck = files.Any(l => l.EndsWith(".NEF")); + if (!nefPresentCheck) + records.AddRange(GetRecords(offset + records.Count, progressBar, files)); else - toDoCollection.AddRange(GetToDoCollection(progressBar, (from l in files where l.EndsWith(".JPG") select l).ToArray(), nefPresent)); + { + if (!nefPresent) + nefPresent = true; + records.AddRange(GetRecords(offset + records.Count, progressBar, (from l in files where l.EndsWith(".JPG") select l).ToArray())); + } } progressBar.Dispose(); } + if (records.Any()) + { + int length = 0; + foreach (Record record in records) + { + if (record.Id is null) + continue; + if (length > record.Id.Value.ToString().Length) + continue; + length = record.Id.Value.ToString().Length; + } + message = $"{length}) comparing records"; + progressBar = new(records.Count, message, options); + toDoCollection.AddRange(GetToDoCollection(progressBar, nefPresent, records, length)); + progressBar.Dispose(); + } foreach ((FileHolder fileHolder, string directory, string to) in toDoCollection) { if (distinctDirectories.Contains(directory)) @@ -372,10 +434,10 @@ public class Rename File.WriteAllText($"{to}.id", $"{to}{Environment.NewLine}{fileHolder.FullName}"); } ConsoleKey? consoleKey = null; - log.Information($"Ready to Move {verifiedToDoCollection.Count} file(s)?"); + log.Information($"Ready to Move {verifiedToDoCollection.Count} files[i](s)?"); for (int y = 0; y < int.MaxValue; y++) { - log.Information("Press \"Y\" key to move file(s), \"N\" key to log file(s) or close console to not move files"); + log.Information("Press \"Y\" key to move files[i](s), \"N\" key to log files[i](s) or close console to not move files"); consoleKey = System.Console.ReadKey().Key; if (consoleKey is ConsoleKey.Y or ConsoleKey.N) break; diff --git a/Shared/Models/Stateless/Methods/IDirectory.cs b/Shared/Models/Stateless/Methods/IDirectory.cs index 3eadc5b..2597fbf 100644 --- a/Shared/Models/Stateless/Methods/IDirectory.cs +++ b/Shared/Models/Stateless/Methods/IDirectory.cs @@ -3,6 +3,9 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IDirectory { + static int GetOffset() => + 1000000; + char TestStatic_GetDirectory(string fileName) => GetDirectory(fileName); static char GetDirectory(string fileName) => @@ -13,6 +16,11 @@ public interface IDirectory static int GetDirectory(char directory) => directory == '-' ? 10 : int.TryParse(directory.ToString(), out int value) ? value : 11; + string TestStatic_GetPaddedId(int length, int index, int id) => + GetPaddedId(length, index, id); + static string GetPaddedId(int length, int index, int id) => + id > -1 ? $"{index}070{id.ToString().PadLeft(length, '0')}" : $"{index}030{id.ToString()[1..].PadLeft(length, '0')}"; + List TestStatic_GetFilesCollection(string directory, string directorySearchFilter, string fileSearchFilter) => GetFilesCollection(directory, directorySearchFilter, fileSearchFilter); static List GetFilesCollection(string directory, string directorySearchFilter, string fileSearchFilter) => @@ -43,10 +51,10 @@ public interface IDirectory static void MoveFiles(List files, string find, string replace) => XDirectory.MoveFiles(files, find, replace); - (string[], List<(Models.FileHolder, string)>) TestStatic_GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, List filesCollection, string[] directories, Action? tick) => - GetToDoCollection(propertyConfiguration, copyDuplicates, filesCollection, directories, tick); - static (string[], List<(Models.FileHolder, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, List filesCollection, string[] directories, Action? tick) => - XDirectory.GetToDoCollection(propertyConfiguration, copyDuplicates, filesCollection, directories, tick); + (string[], List<(Models.FileHolder, string)>) TestStatic_GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, List filesCollection, string[] directories, Action? tick) => + GetToDoCollection(propertyConfiguration, copyDuplicates, ifCanUseId, filesCollection, directories, tick); + static (string[], List<(Models.FileHolder, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, List filesCollection, string[] directories, Action? tick) => + XDirectory.GetToDoCollection(propertyConfiguration, copyDuplicates, ifCanUseId, filesCollection, directories, tick); void TestStatic_CopyOrMove(List<(Models.FileHolder, string)> toDoCollection, bool move, bool moveBack, Action? tick) => CopyOrMove(toDoCollection, move, moveBack, tick); diff --git a/Shared/Models/Stateless/Methods/XDirectory.cs b/Shared/Models/Stateless/Methods/XDirectory.cs index 36d93a0..89271d2 100644 --- a/Shared/Models/Stateless/Methods/XDirectory.cs +++ b/Shared/Models/Stateless/Methods/XDirectory.cs @@ -3,6 +3,8 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; internal abstract partial class XDirectory { + private record SortedRecord(int? Id, Models.FileHolder FileHolder); + private static int GetCeilingAverage(List fileCollection) { List counts = new(); @@ -278,15 +280,59 @@ internal abstract partial class XDirectory } } - private static List GetFileHolders(List filesCollection) + private static (bool, SortedRecord[]) GetSortedRecords(List filesCollection) { - List results = new(); + bool result = true; + List results = new(); + int? id; + short? multiplier; + char negativeMarker; + int absoluteValueOfId; + Models.FileHolder fileHolder; + int offset = IDirectory.GetOffset(); + int offsetLength = offset.ToString().Length; + int sortOrderOnlyLengthIndex = offsetLength + 3; foreach (string[] files in filesCollection) - results.AddRange(files.Select(l => new Models.FileHolder(l))); - return results; + { + foreach (string file in files) + { + fileHolder = new Models.FileHolder(file); + if (!result) + multiplier = null; + else + { + if (fileHolder.NameWithoutExtension.Length < sortOrderOnlyLengthIndex) + { + result = false; + multiplier = null; + } + else + { + negativeMarker = fileHolder.NameWithoutExtension[sortOrderOnlyLengthIndex - 2]; + if (negativeMarker == '7') + multiplier = 1; + else if (negativeMarker == '3') + multiplier = -1; + else + { + result = false; + multiplier = null; + } + } + } + if (!result) + id = null; + else if (!int.TryParse(fileHolder.NameWithoutExtension[sortOrderOnlyLengthIndex..], out absoluteValueOfId)) + id = null; + else + id = absoluteValueOfId * multiplier; + results.Add(new(id, fileHolder)); + } + } + return new(result, (from l in results orderby l.FileHolder.LastWriteTime, l.FileHolder.FullName.Length descending select l).ToArray()); } - internal static (string[], List<(Models.FileHolder, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, List filesCollection, string[] directories, Action? tick) + internal static (string[], List<(Models.FileHolder, string)>) GetToDoCollection(Properties.IPropertyConfiguration propertyConfiguration, bool copyDuplicates, bool ifCanUseId, List filesCollection, string[] directories, Action? tick) { List<(Models.FileHolder, string)> results = new(); string checkFile; @@ -295,18 +341,18 @@ internal abstract partial class XDirectory int directoryIndex; string directoryName; bool wrapped = false; + List distinctIds = new(); List distinct = new(); List distinctDirectories = new(); - List fileHolders = GetFileHolders(filesCollection); - Models.FileHolder[] sortedFileHolders = (from l in fileHolders orderby l.LastWriteTime, l.FullName.Length descending select l).ToArray(); - foreach (Models.FileHolder fileHolder in sortedFileHolders) + (bool all, SortedRecord[] sortedRecords) = GetSortedRecords(filesCollection); + foreach (SortedRecord sortedRecord in sortedRecords) { tick?.Invoke(); - if (fileHolder.Name.EndsWith("len") || fileHolder.ExtensionLowered == ".id" || fileHolder.ExtensionLowered == ".lsv" || fileHolder.DirectoryName is null) + if (sortedRecord.FileHolder.Name.EndsWith("len") || sortedRecord.FileHolder.ExtensionLowered == ".id" || sortedRecord.FileHolder.ExtensionLowered == ".lsv" || sortedRecord.FileHolder.DirectoryName is null) continue; - (_, directoryIndex) = IPath.GetDirectoryNameAndIndex(propertyConfiguration.ResultAllInOneSubdirectoryLength, fileHolder.NameWithoutExtension); - directoryName = Path.GetFileName(fileHolder.DirectoryName); - if (directoryName.Length < propertyConfiguration.ResultAllInOneSubdirectoryLength + 3 || !fileHolder.Name.StartsWith(directoryName)) + (_, directoryIndex) = IPath.GetDirectoryNameAndIndex(propertyConfiguration.ResultAllInOneSubdirectoryLength, sortedRecord.FileHolder.NameWithoutExtension); + directoryName = Path.GetFileName(sortedRecord.FileHolder.DirectoryName); + if (directoryName.Length < propertyConfiguration.ResultAllInOneSubdirectoryLength + 3 || !sortedRecord.FileHolder.Name.StartsWith(directoryName)) { if (wrapped) continue; @@ -318,22 +364,38 @@ internal abstract partial class XDirectory wrapped = true; directory = Path.Combine(directories[directoryIndex], directoryName); } - checkFile = Path.Combine(directory, $"{fileHolder.NameWithoutExtension}{fileHolder.ExtensionLowered}"); - if (distinct.Contains(checkFile)) + if (all) { + if (sortedRecord.Id is null || !sortedRecord.FileHolder.NameWithoutExtension.EndsWith(sortedRecord.Id.Value.ToString()[1..])) + throw new NotSupportedException(); + } + if (all && ifCanUseId) + checkFile = Path.Combine(directory, $"{sortedRecord.Id}{sortedRecord.FileHolder.ExtensionLowered}"); + else + checkFile = Path.Combine(directory, $"{sortedRecord.FileHolder.NameWithoutExtension}{sortedRecord.FileHolder.ExtensionLowered}"); + if ((sortedRecord.Id is not null && distinctIds.Contains(sortedRecord.Id.Value)) || distinct.Contains(checkFile)) + { + if (string.IsNullOrEmpty(sortedRecord.FileHolder.DirectoryName)) + continue; if (!copyDuplicates) continue; for (int i = 1; i < int.MaxValue; i++) { fileInfo = new(checkFile); - if (!fileInfo.Exists || fileHolder.Length == fileInfo.Length && fileHolder.LastWriteTime == fileInfo.LastWriteTime) - checkFile = Path.Combine(directory, $"{fileHolder.NameWithoutExtension}.{i}dup{fileHolder.ExtensionLowered}"); + if (!fileInfo.Exists || sortedRecord.FileHolder.Length == fileInfo.Length && sortedRecord.FileHolder.LastWriteTime == fileInfo.LastWriteTime) + checkFile = Path.Combine(directory, $"{sortedRecord.FileHolder.NameWithoutExtension}.{i}dup{sortedRecord.FileHolder.ExtensionLowered}"); else - checkFile = Path.Combine(directory, $"{fileHolder.NameWithoutExtension}.{i}why{fileHolder.ExtensionLowered}"); + checkFile = Path.Combine(directory, $"{sortedRecord.FileHolder.NameWithoutExtension}.{i}why{sortedRecord.FileHolder.ExtensionLowered}"); + if (sortedRecord.Id is not null) + { + if (distinctIds.Contains(sortedRecord.Id.Value)) + continue; + distinctIds.Add(sortedRecord.Id.Value); + } if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); - results.Add(new(fileHolder, checkFile)); + results.Add(new(sortedRecord.FileHolder, checkFile)); if (!distinctDirectories.Contains(directory)) distinctDirectories.Add(directory); break; @@ -341,7 +403,9 @@ internal abstract partial class XDirectory continue; } distinct.Add(checkFile); - results.Add(new(fileHolder, checkFile)); + if (sortedRecord.Id is not null) + distinctIds.Add(sortedRecord.Id.Value); + results.Add(new(sortedRecord.FileHolder, checkFile)); if (!distinctDirectories.Contains(directory)) distinctDirectories.Add(directory); }