using Microsoft.Extensions.Configuration; using Phares.Shared; using System.Globalization; using System.Text; using View_by_Distance.Date.Group.Models; using View_by_Distance.Property.Models; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Methods; using WindowsShortcutFactory; namespace View_by_Distance.Date.Group; public class DateGroup { private readonly Serilog.ILogger? _Log; private readonly AppSettings _AppSettings; private readonly List _Exceptions; private readonly IsEnvironment _IsEnvironment; private readonly Models.Configuration _Configuration; private readonly List> _FileKeyValuePairs; private readonly Dictionary>> _FilePropertiesKeyValuePairs; public DateGroup(List args, IsEnvironment isEnvironment, IConfigurationRoot configurationRoot, AppSettings appSettings, string workingDirectory, bool isSilent, IConsole console) { if (isSilent) { } if (args is null) { } if (console is null) { } _AppSettings = appSettings; _IsEnvironment = isEnvironment; _Exceptions = new List(); _Log = Serilog.Log.ForContext(); _FileKeyValuePairs = new List>(); _FilePropertiesKeyValuePairs = new Dictionary>>(); Property.Models.Configuration propertyConfiguration = Property.Models.Binder.Configuration.Get(isEnvironment, configurationRoot); Property.Models.Configuration.Verify(propertyConfiguration, requireExist: true); Models.Configuration configuration = Models.Binder.Configuration.Get(isEnvironment, configurationRoot, propertyConfiguration); Verify(configuration); bool reverse = false; _Configuration = configuration; string outputExtension = ".jpg"; if (!_IsEnvironment.Development) throw new Exception("This program only allows development environments!"); long ticks = DateTime.Now.Ticks; A_Property propertyLogic = GetPropertyLogic(reverse, outputExtension); string[] dbFiles = Directory.GetFiles(propertyConfiguration.RootDirectory, "*.db", SearchOption.AllDirectories); foreach (string dbFile in dbFiles) File.Delete(dbFile); if (true || appSettings.MaxDegreeOfParallelism < 2) ticks = LogDelta(ticks, nameof(File.Delete)); for (int i = 1; i < 10; i++) _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(propertyConfiguration.RootDirectory); if (true || appSettings.MaxDegreeOfParallelism < 2) ticks = LogDelta(ticks, nameof(Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories)); string destinationRoot = Property.Models.Stateless.IResult.GetResultsGroupDirectory(propertyConfiguration, "Z) Moved"); // string[] moveBackFileNames = Directory.GetFiles(destinationRoot, "*", SearchOption.AllDirectories); // if (moveBackFileNames is not null) // { // string checkFile; // string? directory; // bool check = false; // string directoryName; // string checkDirectory; // string moveBackFileNameWithExtension; // foreach (string moveBackFileName in moveBackFileNames) // { // moveBackFileNameWithExtension = Path.GetFileName(moveBackFileName); // foreach ((int g, string sourceDirectory, string[] sourceDirectoryFiles) in jsonCollection) // { // foreach (string sourceDirectoryFile in sourceDirectoryFiles) // { // check = false; // if (!sourceDirectoryFile.Contains(moveBackFileNameWithExtension)) // continue; // directory = Path.GetDirectoryName(sourceDirectoryFile); // if (directory is null) // continue; // directoryName = Path.GetFileName(directory); // checkDirectory = Path.Combine(configuration.RootDirectory, directoryName); // if (!Directory.Exists(checkDirectory)) // continue; // checkFile = Path.Combine(checkDirectory, moveBackFileNameWithExtension); // if (File.Exists(checkFile)) // continue; // File.Move(moveBackFileName, checkFile); // check = true; // break; // } // if (check) // break; // } // if (!check) // continue; // } // // string destinationRoot = Property.Models.Stateless.IResult.GetResultsGroupDirectory(propertyConfiguration, "Z) Moved"); // // for (int i = 1; i < 10; i++) // // _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(destinationRoot); // } // string[] moveBackFileNames = Directory.GetFiles(aPropertySingletonDirectory, "*.jpg", SearchOption.AllDirectories); // foreach (string moveBackFileName in moveBackFileNames) // { // string? directory = Path.GetDirectoryName(moveBackFileName); // if (directory is null) // continue; // string directoryName = Path.GetFileName(directory); // string moveBackFileNameWithExtension = Path.GetFileName(moveBackFileName); // string checkDirectory = Path.Combine(configuration.RootDirectory, directoryName); // if (!Directory.Exists(checkDirectory)) // continue; // string checkFile = Path.Combine(checkDirectory, moveBackFileNameWithExtension); // if (File.Exists(checkFile)) // continue; // File.Move(moveBackFileName, checkFile); // } (int j, int f, int t, Container[] containers) = Property.Models.Stateless.Container.GetContainers(propertyConfiguration, propertyLogic); if (propertyLogic.ExceptionsDirectories.Any()) throw new Exception(); if (propertyConfiguration.PopulatePropertyId && (configuration.ByCreateDateShortcut || configuration.ByHash) && Shared.Models.Stateless.Methods.IProperty.Any(containers)) { propertyLogic.SavePropertyParallelWork(ticks, containers); if (appSettings.MaxDegreeOfParallelism < 2) ticks = LogDelta(ticks, nameof(A_Property.SavePropertyParallelWork)); if (propertyLogic.ExceptionsDirectories.Any()) throw new Exception(); } if (configuration.ByCreateDateShortcut) CreateDateShortcut(propertyConfiguration, containers); else MoveFiles(propertyConfiguration, destinationRoot, containers); } private static void Verify(Models.Configuration configuration) { int check = 0; if (configuration.ByCreateDateShortcut) check += 1; if (configuration.ByDay) check += 1; if (configuration.ByHash) check += 1; if (configuration.ByNone) check += 1; if (configuration.BySeason) check += 1; if (configuration.ByWeek) check += 1; if (check != 1) throw new Exception("Change configuration!"); if (configuration?.PropertyConfiguration?.PopulatePropertyId is null) throw new NullReferenceException(nameof(configuration.PropertyConfiguration.PopulatePropertyId)); if (configuration.PropertyConfiguration.PopulatePropertyId && !configuration.ByCreateDateShortcut && !configuration.ByHash) throw new Exception("Change configuration!"); if (!configuration.PropertyConfiguration.PopulatePropertyId && configuration.ByHash) throw new Exception("Change configuration!"); } private static bool WriteAllText(string path, string contents, bool compareBeforeWrite) { bool result; string text; if (!compareBeforeWrite) result = true; else { if (!File.Exists(path)) text = string.Empty; else text = File.ReadAllText(path); result = text != contents; } if (result) { if (path.Contains("()")) File.WriteAllText(path, contents); else if (path.Contains("{}") && !path.EndsWith(".json")) File.WriteAllText(path, contents); else if (path.Contains("[]") && !path.EndsWith(".json")) File.WriteAllText(path, contents); else if (path.Contains("{}") && path.EndsWith(".json") && contents[0] == '{') File.WriteAllText(path, contents); else if (path.Contains("[]") && path.EndsWith(".json") && contents[0] == '[') File.WriteAllText(path, contents); else File.WriteAllText(path, contents); } return result; } private long LogDelta(long ticks, string? methodName) { long result; if (_Log is null) throw new NullReferenceException(nameof(_Log)); double delta = new TimeSpan(DateTime.Now.Ticks - ticks).TotalMilliseconds; _Log.Debug($"{methodName} took {Math.Floor(delta)} millisecond(s)"); result = DateTime.Now.Ticks; return result; } private List<(Item Item, long LastWriteTimeTicks, long MinimumDateTimeTicks, string[] Destination)> GetMoveFileCollection(string destinationDirectory, string topDirectory, Item[] filteredItems) { List<(Item Item, long LastWriteTimeTicks, long MinimumDateTimeTicks, string[] Destination)> results = new(); char flag; string day; int season; string year; string month; string? check; string fileName; string? pathRoot; string seasonName; string weekOfYear; bool? isWrongYear; string seasonValue; string directoryName; string topDirectoryName; string[]? matches = null; string[] directorySegments; DateTime? minimumDateTime = null; List destinationCollection; List directoryNames = new(); List topDirectorySegments = new(); StringBuilder destinationDirectoryName = new(); Calendar calendar = new CultureInfo("en-US").Calendar; for (int z = 1; z < 3; z++) { if (z == 1) { check = Path.Combine(destinationDirectory, "."); pathRoot = Path.GetPathRoot(destinationDirectory); } else if (z == 2) { check = Path.Combine(topDirectory, "."); pathRoot = Path.GetPathRoot(topDirectory); } else throw new Exception(); if (string.IsNullOrEmpty(pathRoot)) continue; for (int i = 0; i < int.MaxValue; i++) { check = Path.GetDirectoryName(check); if (string.IsNullOrEmpty(check) || check == pathRoot) break; directoryName = Path.GetFileName(check); directorySegments = directoryName.Split(' '); topDirectorySegments.AddRange(directorySegments); (_, matches) = Shared.Models.Stateless.Methods.IProperty.IsWrongYear(directorySegments, string.Empty); if (matches.Any()) break; } if (matches is not null && matches.Any()) break; } matches ??= Array.Empty(); foreach (Item item in filteredItems) { if (item.Property is null || (_Configuration.PropertyConfiguration.PopulatePropertyId && item.Property.Id is null)) continue; directoryNames.Clear(); destinationCollection = new(); _ = destinationDirectoryName.Clear(); minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); day = minimumDateTime.Value.ToString("MM-dd"); month = minimumDateTime.Value.ToString("MMMM"); if (item.Property.Id is null) { flag = '#'; isWrongYear = null; } else { (isWrongYear, _) = item.Property.IsWrongYear(item.ImageFileHolder, minimumDateTime); if (isWrongYear is null) flag = '#'; else if (isWrongYear.Value) flag = '~'; else { if (item.Property.DateTimeOriginal.HasValue && minimumDateTime.Value.DayOfYear != item.Property.DateTimeOriginal.Value.DayOfYear && Math.Abs(new TimeSpan(minimumDateTime.Value.Ticks - item.Property.DateTimeOriginal.Value.Ticks).TotalHours) > 8) flag = '^'; else flag = '='; } } (season, seasonName) = Shared.Models.Stateless.Methods.IProperty.GetSeason(minimumDateTime.Value.DayOfYear); if ((from l in topDirectorySegments where l == "Christmas" select true).Any()) seasonValue = string.Empty; else seasonValue = $".{season}"; if (isWrongYear is null || !isWrongYear.Value) year = $"{flag}{minimumDateTime.Value:yyyy}{seasonValue}"; else { if (matches[0][0] != '~') year = $"{flag}{matches[0].Split('.')[0]}{seasonValue}"; else year = $"{flag}{matches[0][1..].Split('.')[0]}{seasonValue}"; } topDirectoryName = Path.GetFileName(topDirectory); weekOfYear = calendar.GetWeekOfYear(minimumDateTime.Value, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00"); if (_Configuration.ByNone) directoryNames.Clear(); else if (_Configuration.ByHash) directoryNames.Add($"{year} {seasonName}"); else if (_Configuration.BySeason && topDirectoryName.Length == 1 && topDirectoryName[0] == '_') directoryNames.Add($"{year} {seasonName}"); else { if (!_Configuration.KeepFullPath) { if (topDirectoryName.Length > 1) _ = destinationDirectoryName.Append(topDirectoryName); if (_Configuration.BySeason) directoryNames.AddRange(new string[] { $"{destinationDirectoryName} {year} {seasonName}" }); else if (_Configuration.ByDay) directoryNames.AddRange(new string[] { $"{destinationDirectoryName} {year}", $"{weekOfYear}) {year}-{day}" }); else if (_Configuration.ByWeek) directoryNames.AddRange(new string[] { $"{destinationDirectoryName} {year}", $"{weekOfYear}) {year} {month}" }); else throw new Exception(); } else { foreach (string sourceDirectoryNameSegment in topDirectorySegments) { if (matches.Contains(sourceDirectoryNameSegment)) _ = destinationDirectoryName.Append(year); else _ = destinationDirectoryName.Append(sourceDirectoryNameSegment); } if (_Configuration.BySeason) directoryNames.Add($"{year} {seasonName}"); else if (_Configuration.ByDay) directoryNames.Add($"{weekOfYear}) {year} {day}"); else if (_Configuration.ByWeek) directoryNames.Add($"{weekOfYear}) {month} {year}"); else throw new Exception(); } } if (!_Configuration.ByHash || item.Property.Id is null) fileName = item.ImageFileHolder.Name; else fileName = $"{item.Property.Id.Value}{item.ImageFileHolder.ExtensionLowered}"; destinationCollection.Add(destinationDirectory); destinationCollection.AddRange(directoryNames); destinationCollection.Add(fileName); results.Add(new(item, item.Property.LastWriteTime.Ticks, minimumDateTime.Value.Ticks, destinationCollection.ToArray())); } return results; } private A_Property GetPropertyLogic(bool reverse, string outputExtension) { A_Property result; if (_Configuration?.PropertyConfiguration is null) throw new NullReferenceException(nameof(_Configuration.PropertyConfiguration)); result = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, outputExtension, reverse); return result; } private static Item[] GetFilterItems(Container container) { List results = new(); foreach (Item item in container.Items) { if (item.ImageFileHolder is not null && (item.Abandoned is null || !item.Abandoned.Value)) results.Add(item); } return results.ToArray(); } private (Item Item, long LastWriteTimeTicks, long MinimumDateTimeTicks, string[] Destination)[] GetFileMoveCollectionAll(Property.Models.Configuration configuration, string destinationRoot, Container[] containers) { (Item Item, long LastWriteTimeTicks, long MinimumDateTimeTicks, string[] Destination)[] results; string? topDirectory; string? checkDirectory; string destinationDirectory; Item[] filteredItems; List<(Item Item, long LastWriteTimeTicks, long MinimumDateTimeTicks, string[] Destination)> fileMoveCollection = new(); List<(Item Item, long LastWriteTimeTicks, long MinimumDateTimeTicks, string[] Destination)> fileMoveCollectionDirectory; foreach (Container container in containers) { if (!container.Items.Any()) continue; if (!_Configuration.KeepFullPath) destinationDirectory = destinationRoot; else destinationDirectory = string.Concat(destinationRoot, container.SourceDirectory[configuration.RootDirectory.Length..]); checkDirectory = Path.GetFullPath(container.SourceDirectory); for (int z = 0; z < int.MaxValue; z++) { if (checkDirectory == configuration.RootDirectory) break; checkDirectory = Path.GetDirectoryName(checkDirectory); if (string.IsNullOrEmpty(checkDirectory)) break; } if (string.IsNullOrEmpty(checkDirectory)) continue; topDirectory = checkDirectory; filteredItems = GetFilterItems(container); if (!filteredItems.Any()) continue; fileMoveCollectionDirectory = GetMoveFileCollection(destinationDirectory, topDirectory, filteredItems); fileMoveCollection.AddRange(fileMoveCollectionDirectory); } results = (from l in fileMoveCollection orderby l.MinimumDateTimeTicks descending, l.LastWriteTimeTicks descending select l).ToArray(); return results; } private void MoveFiles(Property.Models.Configuration configuration, string destinationRoot, Container[] containers) { if (_Log is null) throw new NullReferenceException(nameof(_Log)); bool hasDuplicate; string fullFileName; string directoryName; WindowsShortcut windowsShortcut; string duplicate = "-Duplicate"; List filesDistinct = new(); List filesDuplicate = new(); List directoriesDistinct = new(); (Item Item, long LastWriteTimeTicks, long MinimumDateTimeTicks, string[] Destination)[] fileMoveCollectionAll = GetFileMoveCollectionAll(configuration, destinationRoot, containers); foreach ((Item item, long lastWriteTimeTicks, long minimumDateTimeTicks, string[] destination) in fileMoveCollectionAll) { fullFileName = Path.Combine(destination); if (filesDistinct.Contains(fullFileName)) filesDuplicate.Add(fullFileName); filesDistinct.Add(fullFileName); directoryName = Path.Combine(destination.Take(destination.Length - 1).ToArray()); if (directoriesDistinct.Contains(directoryName)) continue; directoriesDistinct.Add(directoryName); if (!Directory.Exists(directoryName)) _ = Directory.CreateDirectory(directoryName); if (!Directory.Exists(string.Concat(directoryName, duplicate, " I"))) _ = Directory.CreateDirectory(string.Concat(directoryName, duplicate, " I")); if (!Directory.Exists(string.Concat(directoryName, duplicate, " II"))) _ = Directory.CreateDirectory(string.Concat(directoryName, duplicate, " II")); } _Log.Information("Ready to move files?"); for (int y = 0; y < int.MaxValue; y++) { _Log.Information("Press \"Y\" key to move file(s) or close console to not move files"); if (System.Console.ReadKey().Key == ConsoleKey.Y) break; } _Log.Information(". . ."); int moved = 0; foreach ((Item item, long lastWriteTimeTicks, long minimumDateTimeTicks, string[] destination) in fileMoveCollectionAll) { fullFileName = Path.Combine(destination); hasDuplicate = filesDuplicate.Contains(fullFileName); if (hasDuplicate) { destination[1] = string.Concat(destination[1], duplicate, " I"); fullFileName = Path.Combine(destination); } for (int i = 0; i < 256 - destination[1].Length; i++) { if (!File.Exists(fullFileName)) break; else { destination[1] = string.Concat(destination[1], "I"); fullFileName = Path.Combine(destination); if (File.Exists(fullFileName)) continue; } } File.Move(item.ImageFileHolder.FullName, fullFileName); moved += 1; if (hasDuplicate) { try { windowsShortcut = new() { Path = item.ImageFileHolder.DirectoryName, Description = item.ImageFileHolder.Name }; windowsShortcut.Save(string.Concat(fullFileName, ".lnk")); windowsShortcut.Dispose(); } catch (Exception) { } } } _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(destinationRoot); _Log.Information($"{moved} file(s) moved"); for (int y = 0; y < int.MaxValue; y++) { _Log.Information("Press \"Y\" key to move file(s) back or close console to leave them moved"); if (System.Console.ReadKey().Key == ConsoleKey.Y) break; } _Log.Information(". . ."); foreach ((Item item, long lastWriteTimeTicks, long minimumDateTimeTicks, string[] destination) in fileMoveCollectionAll) { fullFileName = Path.Combine(destination); if (File.Exists(item.ImageFileHolder.FullName)) continue; if (!File.Exists(fullFileName)) continue; File.Move(fullFileName, item.ImageFileHolder.FullName); moved += 1; } _Log.Information($"Done moving back {moved} file(s)"); for (int i = 1; i < 10; i++) _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(configuration.RootDirectory); } private static void CreateDateShortcut(Property.Models.Configuration configuration, Container[] containers) { string path; string fileName; string directory; int selectedTotal; const int minimum = 3; List dateTimes; List selectedItems; DateTime? minimumDateTime; const int maximumHours = 24; string? relativePathDirectory; WindowsShortcut windowsShortcut; TimeSpan threeStandardDeviationHigh; string aPropertyContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(configuration, nameof(A_Property), "()"); foreach (Container container in containers) { if (!container.Items.Any()) continue; selectedTotal = 0; threeStandardDeviationHigh = Shared.Models.Stateless.Methods.IProperty.GetThreeStandardDeviationHigh(minimum, container); if (threeStandardDeviationHigh.TotalHours > maximumHours) threeStandardDeviationHigh = new(maximumHours, 0, 0); for (int i = 0; i < container.Items.Count; i++) { (i, dateTimes, selectedItems) = Shared.Models.Stateless.Methods.IProperty.Get(container, threeStandardDeviationHigh, i); selectedTotal += selectedItems.Count; foreach (Item item in selectedItems) { if (item.Property is null) continue; relativePathDirectory = Path.GetDirectoryName(item.RelativePath); if (string.IsNullOrEmpty(relativePathDirectory)) continue; minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); if (minimumDateTime is null) continue; path = Path.GetFullPath($"{configuration.RootDirectory}{item.RelativePath[..^5]}"); directory = Path.Combine($"{aPropertyContentDirectory}{relativePathDirectory}", $"{dateTimes.Min():yyyy-MM-dd_HH-mm-ss}---{dateTimes.Max():yyyy-MM-dd_HH-mm-ss}"); if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); fileName = Path.Combine(directory, $"{Path.GetFileName(item.RelativePath[..^5])}.lnk"); if (File.Exists(fileName)) continue; windowsShortcut = new() { Path = path }; windowsShortcut.Save(fileName); windowsShortcut.Dispose(); if (!File.Exists(fileName)) continue; File.SetLastWriteTime(fileName, minimumDateTime.Value); } } if (selectedTotal < container.Items.Count && selectedTotal < (from l in container.Items where l.Property is not null select true).Count()) continue; } } }