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.Methods; using View_by_Distance.Shared.Models.Stateless; 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; if (appSettings.MaxDegreeOfParallelism is null) throw new Exception($"{nameof(appSettings.MaxDegreeOfParallelism)} is null!"); _IsEnvironment = isEnvironment; _Exceptions = new List(); _Log = Serilog.Log.ForContext(); _FileKeyValuePairs = new List>(); _FilePropertiesKeyValuePairs = new Dictionary>>(); Property.Models.Configuration propertyConfiguration = Property.Models.Stateless.Configuration.Get(isEnvironment, configurationRoot, workingDirectory); Property.Models.Configuration.Verify(propertyConfiguration); Models.Configuration configuration = Models.Stateless.Configuration.Get(isEnvironment, configurationRoot, workingDirectory, propertyConfiguration); Verify(configuration); bool reverse = false; Model model = Model.Hog; PredictorModel predictorModel = PredictorModel.Large; _Configuration = configuration; if (configuration.ByHash is null) throw new Exception($"{nameof(configuration.ByHash)} is null!"); if (configuration.ByCreateDateShortcut is null) throw new Exception($"{nameof(configuration.ByCreateDateShortcut)} is null!"); if (propertyConfiguration.PopulatePropertyId is null) throw new Exception($"{nameof(propertyConfiguration.PopulatePropertyId)} is null!"); if (!_IsEnvironment.Development) throw new Exception("This program only allows development environments!"); long ticks = DateTime.Now.Ticks; PropertyLogic propertyLogic = GetPropertyLogic(); string[] dbFiles = Directory.GetFiles(propertyConfiguration.RootDirectory, "*.db", SearchOption.AllDirectories); foreach (string dbFile in dbFiles) File.Delete(dbFile); if (true || appSettings.MaxDegreeOfParallelism.Value < 2) ticks = LogDelta(ticks, nameof(File.Delete)); for (int i = 1; i < 10; i++) _ = Property.Models.Stateless.IPath.DeleteEmptyDirectories(propertyConfiguration.RootDirectory); if (true || appSettings.MaxDegreeOfParallelism.Value < 2) ticks = LogDelta(ticks, nameof(Property.Models.Stateless.IPath.DeleteEmptyDirectories)); List propertyHolderCollections = Property.Models.Stateless.A_Property.Get(propertyConfiguration, reverse, model, predictorModel, propertyLogic); if (configuration.ByCreateDateShortcut.HasValue && configuration.ByCreateDateShortcut.Value) CreateDateShortcut(propertyConfiguration, propertyHolderCollections); else { List topDirectories = new(); List directoryInfoCollection = new(); propertyLogic.ParallelWork(propertyConfiguration, model, predictorModel, ticks, propertyHolderCollections, firstPass: true); if (appSettings.MaxDegreeOfParallelism.Value < 2) ticks = LogDelta(ticks, nameof(PropertyLogic.ParallelWork)); if (propertyLogic.ExceptionsDirectories.Any()) throw new Exception(); if (propertyConfiguration.PopulatePropertyId.Value && (configuration.ByCreateDateShortcut.Value || configuration.ByHash.Value)) { if (Property.Models.Stateless.A_Property.Any(propertyHolderCollections)) propertyLogic.ParallelWork(propertyConfiguration, model, predictorModel, ticks, propertyHolderCollections, firstPass: false); if (appSettings.MaxDegreeOfParallelism.Value < 2) ticks = LogDelta(ticks, nameof(PropertyLogic.ParallelWork)); if (propertyLogic.ExceptionsDirectories.Any()) throw new Exception(); } if ((from l in directoryInfoCollection where l.Moved.Any(a => a) select true).Any()) throw new Exception(); MoveFiles(topDirectories, directoryInfoCollection); } } private static void Verify(Models.Configuration configuration) { if (configuration.ByCreateDateShortcut is null) throw new Exception($"{nameof(configuration.ByCreateDateShortcut)} is null!"); if (configuration.ByDay is null) throw new Exception($"{nameof(configuration.ByDay)} is null!"); if (configuration.ByHash is null) throw new Exception($"{nameof(configuration.ByHash)} is null!"); if (configuration.BySeason is null) throw new Exception($"{nameof(configuration.BySeason)} is null!"); if (configuration.ByWeek is null) throw new Exception($"{nameof(configuration.ByWeek)} is null!"); if (!configuration.ByCreateDateShortcut.Value && !configuration.ByDay.Value && !configuration.ByWeek.Value && !configuration.BySeason.Value && !configuration.ByHash.Value) throw new Exception("Change configuration!"); if (configuration.KeepFullPath is null) throw new Exception($"{nameof(configuration.KeepFullPath)} is null!"); if (configuration?.PropertyConfiguration?.PopulatePropertyId is null) throw new Exception($"{nameof(configuration.PropertyConfiguration.PopulatePropertyId)} must be set!"); if (configuration.PropertyConfiguration.PopulatePropertyId.Value && !configuration.ByCreateDateShortcut.Value && !configuration.ByHash.Value) throw new Exception("Change configuration!"); if (!configuration.PropertyConfiguration.PopulatePropertyId.Value && configuration.ByHash.Value) throw new Exception("Change configuration!"); if (configuration.ByCreateDateShortcut.Value && configuration.ByDay.Value && configuration.ByWeek.Value && configuration.BySeason.Value && configuration.ByHash.Value) 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 Exception($"{nameof(_Log)} is null!"); 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<(string Source, string[] Destination)> GetMoveFileCollection(string destinationDirectory, string topDirectory, Property.Models.DirectoryInfo group) { List<(string Source, string[] Destination)> results = new(); if (_Configuration.ByCreateDateShortcut is null) throw new Exception($"{nameof(_Configuration.ByCreateDateShortcut)} is null!"); if (_Configuration.ByDay is null) throw new Exception($"{nameof(_Configuration.ByDay)} is null!"); if (_Configuration.ByHash is null) throw new Exception($"{nameof(_Configuration.ByHash)} is null!"); if (_Configuration.BySeason is null) throw new Exception($"{nameof(_Configuration.BySeason)} is null!"); if (_Configuration.ByWeek is null) throw new Exception($"{nameof(_Configuration.ByWeek)} is null!"); if (_Configuration.KeepFullPath is null) throw new Exception($"{nameof(_Configuration.KeepFullPath)} is null!"); char flag; string day; int season; string year; string month; string? check; string fileName; string? pathRoot; string seasonName; string weekOfYear; string? directory; string seasonValue; A_Property? property; string directoryName; bool? propertyWrongYear; string topDirectoryName; string[]? matches = null; string[] directorySegments; DateTime? minimumDateTime = null; List destinationCollection; List directoryNames = new(); FileInfo filteredSourceDirectoryFileInfo; 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) = Property.Models.Stateless.A_Property.IsWrongYear(directorySegments, string.Empty); if (matches.Any()) break; } if (matches is not null && matches.Any()) break; } if (matches is null) matches = Array.Empty(); for (int i = 0; i < group.SourceDirectoryFileInfoCollection.Length; i++) { destinationCollection = new(); directoryNames.Clear(); _ = destinationDirectoryName.Clear(); property = group.PropertyCollection[i]; if (property is null) continue; filteredSourceDirectoryFileInfo = group.SourceDirectoryFileInfoCollection[i]; minimumDateTime = Property.Models.Stateless.A_Property.GetMinimumDateTime(property); directory = filteredSourceDirectoryFileInfo.DirectoryName; if (string.IsNullOrEmpty(directory)) continue; day = minimumDateTime.Value.ToString("MM-dd"); month = minimumDateTime.Value.ToString("MMMM"); (propertyWrongYear, _) = property.IsWrongYear(filteredSourceDirectoryFileInfo.FullName, minimumDateTime); if (propertyWrongYear is null) flag = '#'; else { if (propertyWrongYear.Value) flag = '~'; else { if (property.DateTimeOriginal.HasValue && minimumDateTime.Value.DayOfYear != property.DateTimeOriginal.Value.DayOfYear && Math.Abs(new TimeSpan(minimumDateTime.Value.Ticks - property.DateTimeOriginal.Value.Ticks).TotalHours) > 8) flag = '^'; else flag = '='; } } (season, seasonName) = Property.Models.Stateless.A_Property.GetSeason(minimumDateTime.Value.DayOfYear); if ((from l in topDirectorySegments where l == "Christmas" select true).Any()) seasonValue = string.Empty; else seasonValue = $".{season}"; if (propertyWrongYear is null || !propertyWrongYear.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.ByHash.Value) directoryNames.Add($"{year} {seasonName}"); else if (_Configuration.BySeason.Value && topDirectoryName.Length == 1 && topDirectoryName[0] == '_') directoryNames.Add($"{year} {seasonName}"); else { if (!_Configuration.KeepFullPath.Value) { _ = destinationDirectoryName.Append(topDirectoryName); if (_Configuration.BySeason.Value) directoryNames.AddRange(new string[] { $"{destinationDirectoryName} {year}", $"{year} {seasonName}" }); else if (_Configuration.ByDay.Value) directoryNames.AddRange(new string[] { $"{destinationDirectoryName} {year}", $"{weekOfYear}) {year}-{day}" }); else if (_Configuration.ByWeek.Value) 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.Value) directoryNames.Add($"{year} {seasonName}"); else if (_Configuration.ByDay.Value) directoryNames.Add($"{weekOfYear}) {year} {day}"); else if (_Configuration.ByWeek.Value) directoryNames.Add($"{weekOfYear}) {month} {year}"); else throw new Exception(); } } if (!_Configuration.ByHash.Value || property.Id is null) fileName = filteredSourceDirectoryFileInfo.Name; else fileName = $"{property.Id.Value}{filteredSourceDirectoryFileInfo.Extension.ToLower()}"; destinationCollection.Add(destinationDirectory); destinationCollection.AddRange(directoryNames); destinationCollection.Add(fileName); results.Add(new(filteredSourceDirectoryFileInfo.FullName, destinationCollection.ToArray())); } return results; } private PropertyLogic GetPropertyLogic() { PropertyLogic result; if (_AppSettings.MaxDegreeOfParallelism is null) throw new Exception($"{nameof(_AppSettings.MaxDegreeOfParallelism)} is null!"); if (_Configuration?.PropertyConfiguration is null) throw new Exception($"{nameof(_Configuration.PropertyConfiguration)} must be set!"); result = new(_AppSettings.MaxDegreeOfParallelism.Value, _Configuration.PropertyConfiguration); return result; } private List<(string Source, string[] Destination)> GetFileMoveCollectionAll(List topDirectories, List groupCollection) { List<(string Source, string[] Destination)> results = new(); if (_Configuration.KeepFullPath is null) throw new Exception($"{nameof(_Configuration.KeepFullPath)} is null!"); if (_Configuration?.PropertyConfiguration is null) throw new Exception($"{nameof(_Configuration.PropertyConfiguration)} must be set!"); string? topDirectory; string? checkDirectory; string sourceDirectory; string destinationDirectory; string destinationRoot = Property.Models.Stateless.IResult.GetResultsGroupDirectory(_Configuration.PropertyConfiguration, "Z) Moved"); List<(string Source, string[] Destination)> fileMoveCollectionDirectory; foreach (Property.Models.DirectoryInfo group in groupCollection) { sourceDirectory = group.SourceDirectory; if (!_Configuration.KeepFullPath.Value) destinationDirectory = destinationRoot; else destinationDirectory = string.Concat(destinationRoot, sourceDirectory[_Configuration.PropertyConfiguration.RootDirectory.Length..]); checkDirectory = Path.GetFullPath(sourceDirectory); for (int z = 0; z < int.MaxValue; z++) { if (checkDirectory == _Configuration.PropertyConfiguration.RootDirectory || topDirectories.Contains(checkDirectory)) break; checkDirectory = Path.GetDirectoryName(checkDirectory); if (string.IsNullOrEmpty(checkDirectory)) break; } if (string.IsNullOrEmpty(checkDirectory)) continue; topDirectory = checkDirectory; fileMoveCollectionDirectory = GetMoveFileCollection(destinationDirectory, topDirectory, group); results.AddRange(fileMoveCollectionDirectory); } return results; } private void MoveFiles(List topDirectories, List groupCollection) { if (_Log is null) throw new Exception($"{nameof(_Log)} is null!"); if (_Configuration?.PropertyConfiguration is null) throw new Exception($"{nameof(_Configuration.PropertyConfiguration)} must be set!"); string directoryName; List distinct = new(); List<(string Source, string[] Destination)> fileMoveCollectionAll = GetFileMoveCollectionAll(topDirectories, groupCollection); foreach ((string source, string[] destination) in fileMoveCollectionAll) { directoryName = Path.Combine(destination.Take(destination.Length - 1).ToArray()); if (distinct.Contains(directoryName)) continue; distinct.Add(directoryName); if (!Directory.Exists(directoryName)) _ = Directory.CreateDirectory(directoryName); } _Log.Information("Ready to move files?"); for (int y = 0; y < int.MaxValue; y++) { _Log.Information("Press \"Y\" key to move files back or close console to not move files"); if (Console.ReadKey().Key == ConsoleKey.Y) break; } _Log.Information(". . ."); int moved = 0; string fullFileName; foreach ((string source, string[] destination) in fileMoveCollectionAll) { fullFileName = Path.Combine(destination); if (File.Exists(fullFileName)) continue; File.Move(source, fullFileName); moved += 1; } _Log.Information($"{moved} file(s) moved"); for (int y = 0; y < int.MaxValue; y++) { _Log.Information("Press \"Y\" key to move files back or close console to leave them moved"); if (Console.ReadKey().Key == ConsoleKey.Y) break; } _Log.Information(". . ."); foreach ((string source, string[] destination) in fileMoveCollectionAll) { fullFileName = Path.Combine(destination); if (File.Exists(source)) continue; File.Move(fullFileName, source); moved += 1; } _Log.Information($"Done moving back {moved} file(s)"); for (int i = 1; i < 10; i++) _ = Property.Models.Stateless.IPath.DeleteEmptyDirectories(_Configuration.PropertyConfiguration.RootDirectory); } private static void CreateDateShortcut(Property.Models.Configuration configuration, List propertyHolderCollections) { string path; string fileName; string directory; int selectedTotal; const int minimum = 3; List dateTimes; const int maximumHours = 24; string? relativePathDirectory; WindowsShortcut windowsShortcut; TimeSpan threeStandardDeviationHigh; List selectedPropertyHolderCollection; string aPropertyContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(configuration, nameof(A_Property), "()"); foreach (PropertyHolder[] propertyHolderCollection in propertyHolderCollections) { if (!propertyHolderCollection.Any()) continue; selectedTotal = 0; threeStandardDeviationHigh = Property.Models.Stateless.A_Property.GetThreeStandardDeviationHigh(minimum, propertyHolderCollection); if (threeStandardDeviationHigh.TotalHours > maximumHours) threeStandardDeviationHigh = new(maximumHours, 0, 0); for (int i = 0; i < propertyHolderCollection.Length; i++) { (i, dateTimes, selectedPropertyHolderCollection) = Property.Models.Stateless.A_Property.Get(propertyHolderCollection, threeStandardDeviationHigh, i); selectedTotal += selectedPropertyHolderCollection.Count; foreach (PropertyHolder propertyHolder in selectedPropertyHolderCollection) { if (propertyHolder.Property is null || propertyHolder.MinimumDateTime is null) continue; relativePathDirectory = Path.GetDirectoryName(propertyHolder.RelativePath); if (string.IsNullOrEmpty(relativePathDirectory)) continue; path = Path.GetFullPath($"{configuration.RootDirectory}{propertyHolder.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(propertyHolder.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, propertyHolder.MinimumDateTime.Value); } } if (selectedTotal < propertyHolderCollection.Length && selectedTotal < (from l in propertyHolderCollection where l.Property is not null && l.MinimumDateTime.HasValue select true).Count()) continue; } } }