From e6acce3852010de6bcb930a9edee1ef3df61361a Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Wed, 3 May 2023 18:12:50 -0700 Subject: [PATCH] Drag-Drop-Move --- .vscode/launch.json | 17 ++ Drag-Drop-Move/.vscode/launch.json | 26 ++ Drag-Drop-Move/.vscode/tasks.json | 41 +++ Drag-Drop-Move/Drag-Drop-Move.csproj | 50 ++++ Drag-Drop-Move/DragDropMove.Designer.cs | 39 +++ Drag-Drop-Move/DragDropMove.cs | 195 +++++++++++++ Drag-Drop-Move/Models/AppSettings.cs | 27 ++ Drag-Drop-Move/Models/Binder/AppSettings.cs | 44 +++ Drag-Drop-Move/Program.cs | 15 + Drag-Drop-Move/appsettings.Development.json | 11 + Drag-Drop-Move/appsettings.json | 50 ++++ Instance/DlibDotNet.cs | 273 ++++++++++-------- Map/Models/MapLogic.cs | 46 +-- Map/Models/Stateless/MapLogic.cs | 29 +- Map/Models/Stateless/Methods/IMapLogic.cs | 18 +- .../Models/Stateless/Methods/IMetadata.cs | 11 +- Metadata/Models/Stateless/Methods/Metadata.cs | 14 +- Shared/Models/Stateless/Methods/Container.cs | 44 ++- Shared/Models/Stateless/Methods/IContainer.cs | 21 +- Shared/Models/Stateless/Methods/ILocation.cs | 5 + Shared/Models/Stateless/Methods/IMapping.cs | 5 + Shared/Models/Stateless/Methods/IPath.cs | 5 + Shared/Models/Stateless/Methods/XPath.cs | 21 ++ View-by-Distance-MKLink-Console.sln | 6 + 24 files changed, 853 insertions(+), 160 deletions(-) create mode 100644 Drag-Drop-Move/.vscode/launch.json create mode 100644 Drag-Drop-Move/.vscode/tasks.json create mode 100644 Drag-Drop-Move/Drag-Drop-Move.csproj create mode 100644 Drag-Drop-Move/DragDropMove.Designer.cs create mode 100644 Drag-Drop-Move/DragDropMove.cs create mode 100644 Drag-Drop-Move/Models/AppSettings.cs create mode 100644 Drag-Drop-Move/Models/Binder/AppSettings.cs create mode 100644 Drag-Drop-Move/Program.cs create mode 100644 Drag-Drop-Move/appsettings.Development.json create mode 100644 Drag-Drop-Move/appsettings.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 3558e0d..adca1e6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -125,6 +125,23 @@ "stopAtEntry": false, "requireExactSource": false }, + { + "name": "Drag-Drop-Move", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Drag-Drop-Move/bin/Debug/net7.0-windows/win-x64/Drag-Drop-Move.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, { "name": "Instance", "type": "coreclr", diff --git a/Drag-Drop-Move/.vscode/launch.json b/Drag-Drop-Move/.vscode/launch.json new file mode 100644 index 0000000..de44254 --- /dev/null +++ b/Drag-Drop-Move/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/net7.0-windows/win-x64/Drag-Drop-Explorer.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/Drag-Drop-Move/.vscode/tasks.json b/Drag-Drop-Move/.vscode/tasks.json new file mode 100644 index 0000000..570fdd9 --- /dev/null +++ b/Drag-Drop-Move/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Drag-Drop-Explorer.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Drag-Drop-Explorer.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Drag-Drop-Explorer.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Drag-Drop-Move/Drag-Drop-Move.csproj b/Drag-Drop-Move/Drag-Drop-Move.csproj new file mode 100644 index 0000000..3737d46 --- /dev/null +++ b/Drag-Drop-Move/Drag-Drop-Move.csproj @@ -0,0 +1,50 @@ + + + enable + 10.0 + enable + WinExe + win-x64 + net7.0-windows + true + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + \ No newline at end of file diff --git a/Drag-Drop-Move/DragDropMove.Designer.cs b/Drag-Drop-Move/DragDropMove.Designer.cs new file mode 100644 index 0000000..e272c77 --- /dev/null +++ b/Drag-Drop-Move/DragDropMove.Designer.cs @@ -0,0 +1,39 @@ +namespace View_by_Distance.Drag_Drop_Explorer; + +partial class DragDropMove +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(640, 100); + this.StartPosition = FormStartPosition.CenterScreen; + this.Text = "Drag Drop Explorer"; + } + + #endregion +} diff --git a/Drag-Drop-Move/DragDropMove.cs b/Drag-Drop-Move/DragDropMove.cs new file mode 100644 index 0000000..c406f06 --- /dev/null +++ b/Drag-Drop-Move/DragDropMove.cs @@ -0,0 +1,195 @@ +using Microsoft.Extensions.Configuration; +using Phares.Shared; +using Serilog; +using System.Diagnostics; +using System.Reflection; +using System.Text.Json; +using View_by_Distance.Drag_Drop_Explorer.Models; +using View_by_Distance.Shared.Models.Stateless.Methods; +using WindowsShortcutFactory; + +namespace View_by_Distance.Drag_Drop_Explorer; + +public partial class DragDropMove : Form +{ + + private readonly ILogger _Logger; + private readonly TextBox _PathTextBox; + private readonly TextBox _JsonTextBox; + private readonly TextBox _FirstTextBox; + private readonly AppSettings _AppSettings; + private readonly ProgressBar _ProgressBar; + private readonly string _WorkingDirectory; + private readonly IsEnvironment _IsEnvironment; + + public DragDropMove() + { + InitializeComponent(); + ILogger logger; + AppSettings appSettings; + string workingDirectory; + IsEnvironment isEnvironment; + IConfigurationRoot configurationRoot; + LoggerConfiguration loggerConfiguration = new(); + Assembly assembly = Assembly.GetExecutingAssembly(); + bool debuggerWasAttachedAtLineZero = Debugger.IsAttached || assembly.Location.Contains(@"\bin\Debug"); + isEnvironment = new(processesCount: null, nullASPNetCoreEnvironmentIsDevelopment: debuggerWasAttachedAtLineZero, nullASPNetCoreEnvironmentIsProduction: !debuggerWasAttachedAtLineZero); + IConfigurationBuilder configurationBuilder = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile(isEnvironment.AppSettingsFileName, optional: false, reloadOnChange: true); + configurationRoot = configurationBuilder.Build(); + appSettings = Models.Binder.AppSettings.Get(configurationRoot); + if (string.IsNullOrEmpty(appSettings.WorkingDirectoryName)) + throw new Exception("Working path name must have parentDirectory value!"); + workingDirectory = IWorkingDirectory.GetWorkingDirectory(assembly.GetName().Name, appSettings.WorkingDirectoryName); + Environment.SetEnvironmentVariable(nameof(workingDirectory), workingDirectory); + _ = ConfigurationLoggerConfigurationExtensions.Configuration(loggerConfiguration.ReadFrom, configurationRoot); + Log.Logger = loggerConfiguration.CreateLogger(); + logger = Log.ForContext(); + logger.Information("Complete"); + _Logger = logger; + _AppSettings = appSettings; + _IsEnvironment = isEnvironment; + _WorkingDirectory = workingDirectory; + _ProgressBar = new() { TabIndex = 4, Location = new(5, 5), Dock = DockStyle.Top, Visible = false }; + string json = JsonSerializer.Serialize(_AppSettings, new JsonSerializerOptions { WriteIndented = true }); + _FirstTextBox = new() { TabIndex = 1, Text = _IsEnvironment.Profile, Location = new(5, 5), Dock = DockStyle.Top }; + _PathTextBox = new() { TabIndex = 2, Text = _AppSettings.WorkingDirectoryName, Location = new(5, 5), Dock = DockStyle.Top }; + _JsonTextBox = new() { TabIndex = 3, Text = json, Multiline = true, MinimumSize = new(1, 80), Location = new(5, 5), Dock = DockStyle.Top }; + Load += new EventHandler(Form1_Load); + Controls.Add(_ProgressBar); + Controls.Add(_JsonTextBox); + Controls.Add(_PathTextBox); + Controls.Add(_FirstTextBox); + } + + void Form1_Load(object? sender, EventArgs e) + { + try + { + AllowDrop = true; + DragDrop += new DragEventHandler(Form1_DragDrop); + DragEnter += new DragEventHandler(Form1_DragEnter); + _FirstTextBox.LostFocus += new EventHandler(TextBox_LostFocus); + _PathTextBox.LostFocus += new EventHandler(TextBox_LostFocus); + } + catch (Exception) + { + throw; + } + } + + private static string GetConverted(string value) + { + string result = value.Length < 2 || value[1] != ':' ? value : value.Replace("\\\\", "/").Replace('\\', '/'); + return result; + } + + void TextBox_LostFocus(object? sender, EventArgs e) + { + try + { + if (sender is TextBox textBox) + { + textBox.Text = GetConverted(textBox.Text); + if (textBox.Text == "ps") + throw new NotImplementedException(); + + } + } + catch (Exception) + { + throw; + } + } + + void Form1_DragEnter(object? sender, DragEventArgs e) + { + try + { + if (e.Data is not null && e.Data.GetDataPresent(DataFormats.FileDrop)) + e.Effect = DragDropEffects.Copy; + } + catch (Exception) + { + throw; + } + } + + private void MovePaths(string[] paths) + { + string file; + string checkFile; + string? directory; + string? relativePath; + string[] directories; + string? checkDirectory; + string? shellDirectory; + int rootDirectoryLength; + WindowsShortcut windowsShortcut; + _PathTextBox.Text = string.Empty; + _FirstTextBox.Text = GetConverted(paths[0]); + foreach (string path in paths.OrderBy(l => l)) + { + relativePath = null; + shellDirectory = null; + if (!path.EndsWith(".lnk")) + file = path; + else + { + windowsShortcut = WindowsShortcut.Load(path); + if (windowsShortcut.Path is null) + continue; + file = windowsShortcut.Path; + windowsShortcut.Dispose(); + } + directory = Path.GetDirectoryName(file); + if (string.IsNullOrEmpty(directory)) + continue; + directories = IPath.GetDirectories(directory); + foreach (string directoryName in directories) + { + checkDirectory = $"{directoryName}-Shell"; + if (!Directory.Exists(checkDirectory)) + continue; + shellDirectory = checkDirectory; + rootDirectoryLength = directoryName.Length; + relativePath = file[rootDirectoryLength..]; + break; + } + if (string.IsNullOrEmpty(shellDirectory) || string.IsNullOrEmpty(relativePath)) + continue; + checkFile = string.Concat(shellDirectory, relativePath); + checkDirectory = Path.GetDirectoryName(checkFile); + if (string.IsNullOrEmpty(checkDirectory)) + continue; + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + if (File.Exists(checkFile)) + continue; + File.Move(file, checkFile); + } + } + + void Form1_DragDrop(object? sender, DragEventArgs e) + { + try + { + if (e.Data is not null && e.Data.GetData(DataFormats.FileDrop) is string[] paths && paths.Any()) + MovePaths(paths); + else + { + _FirstTextBox.Text = string.Empty; + _PathTextBox.Text = string.Empty; + _JsonTextBox.Text = string.Empty; + _Logger.Information("No data"); + } + } + catch (Exception) + { + throw; + } + } + +} \ No newline at end of file diff --git a/Drag-Drop-Move/Models/AppSettings.cs b/Drag-Drop-Move/Models/AppSettings.cs new file mode 100644 index 0000000..e7114a7 --- /dev/null +++ b/Drag-Drop-Move/Models/AppSettings.cs @@ -0,0 +1,27 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Drag_Drop_Explorer.Models; + +public class AppSettings +{ + + public string Company { init; get; } + public int MaxDegreeOfParallelism { init; get; } + public string WorkingDirectoryName { init; get; } + + [JsonConstructor] + public AppSettings(string company, int maxDegreeOfParallelism, string workingDirectoryName) + { + Company = company; + MaxDegreeOfParallelism = maxDegreeOfParallelism; + WorkingDirectoryName = workingDirectoryName; + } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + return result; + } + +} \ No newline at end of file diff --git a/Drag-Drop-Move/Models/Binder/AppSettings.cs b/Drag-Drop-Move/Models/Binder/AppSettings.cs new file mode 100644 index 0000000..3f35cb0 --- /dev/null +++ b/Drag-Drop-Move/Models/Binder/AppSettings.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Configuration; +using System.Text.Json; + +namespace View_by_Distance.Drag_Drop_Explorer.Models.Binder; + +public class AppSettings +{ + +#nullable disable + + public string Company { get; set; } + public int? MaxDegreeOfParallelism { get; set; } + public string WorkingDirectoryName { get; set; } + +#nullable restore + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + return result; + } + + private static Models.AppSettings Get(AppSettings? appSettings) + { + Models.AppSettings result; + if (appSettings?.MaxDegreeOfParallelism is null) + throw new NullReferenceException(nameof(appSettings.MaxDegreeOfParallelism)); + result = new( + appSettings.Company, + appSettings.MaxDegreeOfParallelism.Value, + appSettings.WorkingDirectoryName + ); + return result; + } + + public static Models.AppSettings Get(IConfigurationRoot configurationRoot) + { + Models.AppSettings result; + AppSettings? appSettings = configurationRoot.Get(); + result = Get(appSettings); + return result; + } + +} \ No newline at end of file diff --git a/Drag-Drop-Move/Program.cs b/Drag-Drop-Move/Program.cs new file mode 100644 index 0000000..a50a30d --- /dev/null +++ b/Drag-Drop-Move/Program.cs @@ -0,0 +1,15 @@ +namespace View_by_Distance.Drag_Drop_Explorer; + +static class Program +{ + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + ApplicationConfiguration.Initialize(); + Application.Run(new DragDropMove()); + } + +} \ No newline at end of file diff --git a/Drag-Drop-Move/appsettings.Development.json b/Drag-Drop-Move/appsettings.Development.json new file mode 100644 index 0000000..be482be --- /dev/null +++ b/Drag-Drop-Move/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Log4netProvider": "Debug" + } + }, + "MaxDegreeOfParallelism": 6, + "Serilog": { + "MinimumLevel": "Debug" + } +} \ No newline at end of file diff --git a/Drag-Drop-Move/appsettings.json b/Drag-Drop-Move/appsettings.json new file mode 100644 index 0000000..4be1117 --- /dev/null +++ b/Drag-Drop-Move/appsettings.json @@ -0,0 +1,50 @@ +{ + "Company": "Mike Phares", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Log4netProvider": "Debug", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "MaxDegreeOfParallelism": 6, + "Serilog": { + "Using": [ + "Serilog.Sinks.Console", + "Serilog.Sinks.File" + ], + "MinimumLevel": "Information", + "WriteTo": [ + { + "Name": "Debug", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}" + } + }, + { + "Name": "Console", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "%workingDirectory% - Log/log-.txt", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}", + "rollingInterval": "Hour" + } + } + ], + "Enrich": [ + "FromLogContext", + "WithMachineName", + "WithThreadId" + ], + "Properties": { + "Application": "Sample" + } + }, + "WorkingDirectoryName": "PharesApps" +} \ No newline at end of file diff --git a/Instance/DlibDotNet.cs b/Instance/DlibDotNet.cs index b230b1f..45d5aa9 100644 --- a/Instance/DlibDotNet.cs +++ b/Instance/DlibDotNet.cs @@ -271,26 +271,18 @@ public partial class DlibDotNet return result; } - private void SetMapping(ReadOnlyDictionary>> idToLocationContainers, MapLogic mapLogic, Item item, bool? isFocusRelativePath, bool? isIgnoreRelativePath, MappingFromItem mappingFromItem, List? mappingFromPhotoPrismCollection, List faces) + private bool? GetIsFocusModel(ReadOnlyDictionary>> idToLocationContainers, MappingFromItem mappingFromItem) { + bool? result; string? model; - Mapping mapping; - bool? isFocusModel; - int faceAreaPermyriad; - int confidencePercent; - bool? inSkipCollection; - int normalizedRectangle; - string deterministicHashCodeKey; - MappingFromFilter mappingFromFilter; - MappingFromLocation? mappingFromLocation; - IReadOnlyList directories; List>? locationContainers; + IReadOnlyList directories; if (string.IsNullOrEmpty(_Configuration.FocusModel)) - isFocusModel = null; + result = null; else { if (!idToLocationContainers.TryGetValue(mappingFromItem.Id, out locationContainers) || !locationContainers.Any()) - isFocusModel = false; + result = false; else { directories = locationContainers.First().Directories; @@ -298,11 +290,25 @@ public partial class DlibDotNet if (model is null) directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(mappingFromItem.ResizedFileHolder.FullName); model = Metadata.Models.Stateless.Methods.IMetadata.GetModel(directories); - isFocusModel = model is not null && model.Contains(_Configuration.FocusModel); - if (isFocusModel.Value) - isFocusModel = true; + result = model is not null && model.Contains(_Configuration.FocusModel); + if (result.Value) + result = true; } } + return result; + } + + private void SetMapping(ReadOnlyDictionary>> idToLocationContainers, MapLogic mapLogic, Item item, bool? isFocusRelativePath, bool? isIgnoreRelativePath, MappingFromItem mappingFromItem, List? mappingFromPhotoPrismCollection, List faces) + { + Mapping mapping; + int faceAreaPermyriad; + int confidencePercent; + bool? inSkipCollection; + int normalizedRectangle; + string deterministicHashCodeKey; + MappingFromFilter mappingFromFilter; + MappingFromLocation? mappingFromLocation; + bool? isFocusModel = GetIsFocusModel(idToLocationContainers, mappingFromItem); foreach (Shared.Models.Face face in faces) { if (item.Property?.Id is null || face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) @@ -327,6 +333,35 @@ public partial class DlibDotNet } } + private Mapping GetMapping(ReadOnlyDictionary>> idToLocationContainers, MapLogic mapLogic, Item item, bool? isFocusRelativePath, bool? isIgnoreRelativePath, MappingFromItem mappingFromItem) + { + Mapping result; + bool? inSkipCollection; + int normalizedRectangle; + int faceAreaPermyriad = 0; + int confidencePercent = 0; + string deterministicHashCodeKey; + MappingFromFilter mappingFromFilter; + MappingFromLocation? mappingFromLocation; + bool? isFocusModel = GetIsFocusModel(idToLocationContainers, mappingFromItem); + if (item.Property?.Id is null) + { + inSkipCollection = null; + mappingFromLocation = null; + mappingFromFilter = new(isFocusModel, isFocusRelativePath, isIgnoreRelativePath, inSkipCollection); + } + else + { + normalizedRectangle = Shared.Models.Stateless.Methods.ILocation.GetNormalizedRectangle(Shared.Models.Stateless.ILocation.Digits); + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item.Property.Id.Value, Shared.Models.Stateless.ILocation.Digits); + mappingFromLocation = new(faceAreaPermyriad, confidencePercent, deterministicHashCodeKey, normalizedRectangle); + inSkipCollection = mapLogic.InSkipCollection(item.Property.Id.Value, mappingFromLocation); + mappingFromFilter = new(isFocusModel, isFocusRelativePath, isIgnoreRelativePath, inSkipCollection); + } + result = new(mappingFromItem, mappingFromFilter, mappingFromLocation, mappingFromPhotoPrismCollection: null); + return result; + } + private void FullParallelForWork(A_Property propertyLogic, B_Metadata metadata, ReadOnlyDictionary>> idToLocationContainers, @@ -677,61 +712,6 @@ public partial class DlibDotNet } } - private List GetFilteredDistinct(string argZero, Container[] containers) - { - List results = new(); - Item[] filteredItems; - List distinct = new(); - foreach (Container container in containers) - { - if (!container.Items.Any()) - continue; - if (!_ArgZeroIsConfigurationRootDirectory && !container.SourceDirectory.StartsWith(argZero)) - continue; - filteredItems = Shared.Models.Stateless.Methods.IContainer.GetFilterItems(_Configuration.PropertyConfiguration, container); - if (!filteredItems.Any()) - continue; - foreach (Item item in filteredItems) - { - if (item.Property?.Id is null || item.ResizedFileHolder is null) - continue; - if (distinct.Contains(item.Property.Id.Value)) - continue; - distinct.Add(item.Property.Id.Value); - foreach (Shared.Models.Face face in item.Faces) - { - if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) - continue; - results.Add(face); - } - } - } - return results; - } - - private List GetItems(string argZero, Container[] containers) - { - List items = new(); - Item[] filteredItems; - foreach (Container container in containers) - { - if (!container.Items.Any()) - continue; - if (!_ArgZeroIsConfigurationRootDirectory && !container.SourceDirectory.StartsWith(argZero)) - continue; - filteredItems = Shared.Models.Stateless.Methods.IContainer.GetFilterItems(_Configuration.PropertyConfiguration, container); - if (!filteredItems.Any()) - continue; - foreach (Item item in filteredItems) - { - if (item.Property?.Id is null || item.ResizedFileHolder is null) - continue; - items.Add(item); - } - } - return items; - } - private void SaveFaceDistances(long ticks, MapLogic mapLogic, Mapping[] mappingCollection, string dFacesContentDirectory, string d2FacePartsContentDirectory, string d2FacePartsContentCollectionDirectory, Dictionary> idToNormalizedRectangleToMapping, List faceDistanceEncodings, FaceDistanceContainer[] faceDistanceContainers) { if (_Log is null) @@ -785,36 +765,33 @@ public partial class DlibDotNet SaveFaceDistances(ticks, mapLogic, mappingCollection, dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, idToNormalizedRectangleToMapping, faceDistanceEncodings, faceDistanceContainers); } - private void MapLogic(string argZero, long ticks, Container[] containers, string a2PeopleSingletonDirectory, string dResultsFullGroupDirectory, string d2ResultsFullGroupDirectory, string fPhotoPrismContentDirectory, MapLogic mapLogic, string outputResolution, Dictionary> personKeyToIds, List distinctFilteredFaces, Mapping[] mappingCollection, int totalNotMapped) + private void MapLogic(long ticks, Container[] containers, string a2PeopleSingletonDirectory, string dResultsFullGroupDirectory, string d2ResultsFullGroupDirectory, string fPhotoPrismContentDirectory, MapLogic mapLogic, string outputResolution, Dictionary> personKeyToIds, List distinctFilteredFaces, Mapping[] distinctFilteredMappingCollection, int totalNotMapped) { string dFacesContentDirectory = Path.Combine(dResultsFullGroupDirectory, "()"); string d2FacePartsContentDirectory = Path.Combine(d2ResultsFullGroupDirectory, "()"); string d2FacePartsContentCollectionDirectory = Path.Combine(d2ResultsFullGroupDirectory, "[()]"); string dFacesCollectionDirectory = Path.Combine(dResultsFullGroupDirectory, "[]", _Configuration.PropertyConfiguration.ResultAllInOne); - if (mappingCollection.Any()) + if (distinctFilteredMappingCollection.Any()) { Shared.Models.Stateless.Methods.IPath.ChangeDateForEmptyDirectories(d2FacePartsContentDirectory, ticks); Shared.Models.Stateless.Methods.IPath.MakeHiddenIfAllItemsAreHidden(d2FacePartsContentCollectionDirectory); } - Dictionary> idToNormalizedRectangleToMapping = Map.Models.Stateless.Methods.IMapLogic.GetIdToNormalizedRectangleToFace(mappingCollection); + Dictionary> idToNormalizedRectangleToMapping = Map.Models.Stateless.Methods.IMapLogic.GetIdToNormalizedRectangleToFace(distinctFilteredMappingCollection); if (Directory.Exists(fPhotoPrismContentDirectory)) F_PhotoPrism.WriteMatches(fPhotoPrismContentDirectory, _Configuration.PersonBirthdayFormat, ticks, distinctFilteredFaces, mapLogic); if (_Configuration.SaveShortcutsForOutputResolutions.Contains(outputResolution)) - { - List filteredItems = GetItems(argZero, containers); - mapLogic.SaveShortcutsForOutputResolutionsDuringMapLogic(personKeyToIds, dFacesContentDirectory, filteredItems, mappingCollection); - } + mapLogic.SaveShortcutsForOutputResolutionsDuringMapLogic(containers, personKeyToIds, dFacesContentDirectory, distinctFilteredMappingCollection); if (!string.IsNullOrEmpty(_Configuration.PersonCharacters)) { if (_Configuration.PersonCharactersCopyCount > 0) - mapLogic.CopyAtLeastOneMappedFiles(_Configuration.PersonCharactersCopyCount, dFacesContentDirectory, a2PeopleSingletonDirectory, mappingCollection); - mapLogic.SavePersonKeyToCount(dFacesContentDirectory, a2PeopleSingletonDirectory, mappingCollection); + mapLogic.CopyAtLeastOneMappedFiles(_Configuration.PersonCharactersCopyCount, dFacesContentDirectory, a2PeopleSingletonDirectory, distinctFilteredMappingCollection); + mapLogic.SavePersonKeyToCount(dFacesContentDirectory, a2PeopleSingletonDirectory, distinctFilteredMappingCollection); } mapLogic.CopyManualFiles(dFacesContentDirectory, idToNormalizedRectangleToMapping); if (_Configuration.SaveMappedForOutputResolutions.Contains(outputResolution)) - mapLogic.SaveMapped(dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, personKeyToIds, mappingCollection, idToNormalizedRectangleToMapping, totalNotMapped); + mapLogic.SaveMapped(dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, personKeyToIds, distinctFilteredMappingCollection, idToNormalizedRectangleToMapping, totalNotMapped); if (_Configuration.SaveFaceDistancesForOutputResolutions.Contains(outputResolution)) - SaveFaceDistances(ticks, mapLogic, distinctFilteredFaces, mappingCollection, dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, dFacesCollectionDirectory, idToNormalizedRectangleToMapping); + SaveFaceDistances(ticks, mapLogic, distinctFilteredFaces, distinctFilteredMappingCollection, dFacesContentDirectory, d2FacePartsContentDirectory, d2FacePartsContentCollectionDirectory, dFacesCollectionDirectory, idToNormalizedRectangleToMapping); } private string SaveUrlAndGetNewRootDirectory(Container container) @@ -860,24 +837,6 @@ public partial class DlibDotNet return result; } - private static void LookForAbandoned(ReadOnlyDictionary>> idToLocationContainers, List distinctFilteredIds) - { - List renameCollection = new(); - foreach (KeyValuePair>> idToCollection in idToLocationContainers) - { - if (distinctFilteredIds.Contains(idToCollection.Key)) - continue; - foreach (LocationContainer locationContainer in idToCollection.Value) - { - if (locationContainer.File.Contains('!')) - continue; - renameCollection.Add(locationContainer.File); - } - } - if (renameCollection.Any()) - Shared.Models.Stateless.Methods.IDirectory.MoveFiles(renameCollection, "()", "(abd)"); - } - private static void LookForAbandoned(List distinctFilteredIds, string directory, string directoryName) { string fileNameWithoutExtension; @@ -898,21 +857,52 @@ public partial class DlibDotNet if (directoryName.Length == 2) Shared.Models.Stateless.Methods.IDirectory.MoveFiles(renameCollection, directoryName, $"{directoryName[0]}abd{directoryName[^1]}"); else if (directoryName.Length == 4) - Shared.Models.Stateless.Methods.IDirectory.MoveFiles(renameCollection, directoryName, $"{directoryName[..2]}abd{directoryName[^2]}"); + Shared.Models.Stateless.Methods.IDirectory.MoveFiles(renameCollection, directoryName, $"{directoryName[..2]}abd{directoryName[^2..]}"); else throw new NotSupportedException(); } } - private void LookForAbandoned(Container[] containers, ReadOnlyDictionary>> idToLocationContainers) + private static void LookForAbandoned(string bResultsFullGroupDirectory, List distinctFilteredIds) + { + string[] directories = Directory.GetDirectories(bResultsFullGroupDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) + { + string? directoryName = Path.GetFileName(directory); + if (string.IsNullOrEmpty(directoryName) || (directoryName.Length != 2 && directoryName.Length != 4)) + continue; + LookForAbandoned(distinctFilteredIds, directory, directoryName); + } + } + + private static void LookForAbandoned(ReadOnlyDictionary>> idToLocationContainers, List distinctFilteredIds) + { + List renameCollection = new(); + foreach (KeyValuePair>> idToCollection in idToLocationContainers) + { + if (distinctFilteredIds.Contains(idToCollection.Key)) + continue; + foreach (LocationContainer locationContainer in idToCollection.Value) + { + if (locationContainer.File.Contains('!')) + continue; + renameCollection.Add(locationContainer.File); + } + } + if (renameCollection.Any()) + Shared.Models.Stateless.Methods.IDirectory.MoveFiles(renameCollection, "()", "(abd)"); + } + + private void LookForAbandoned(string bResultsFullGroupDirectory, Container[] containers, ReadOnlyDictionary>> idToLocationContainers) { string[] directories; string? directoryName; string cResultsFullGroupDirectory; string dResultsFullGroupDirectory; string d2ResultsFullGroupDirectory; - List distinctFilteredIds = Shared.Models.Stateless.Methods.IContainer.GetFilteredDistinct(_Configuration.PropertyConfiguration, containers); + List distinctFilteredIds = Shared.Models.Stateless.Methods.IContainer.GetFilteredDistinctIds(_Configuration.PropertyConfiguration, containers); LookForAbandoned(idToLocationContainers, distinctFilteredIds); + LookForAbandoned(bResultsFullGroupDirectory, distinctFilteredIds); foreach (string outputResolution in _Configuration.OutputResolutions) { (cResultsFullGroupDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory) = GetResultsFullGroupDirectories(outputResolution); @@ -943,6 +933,62 @@ public partial class DlibDotNet } } + private Mapping[] GetMappings(Property.Models.Configuration propertyConfiguration, Container[] containers, MapLogic mapLogic, ReadOnlyDictionary>> idToLocationContainers, bool distinctItems) + { + Mapping[] results; + int count = 0; + Mapping mapping; + bool anyValidFaces; + string focusRelativePath; + bool? isFocusRelativePath; + bool? isIgnoreRelativePath; + List distinct = new(); + DateTime[] containerDateTimes; + IEnumerable filteredItems; + MappingFromItem mappingFromItem; + List mappingCollection = new(); + foreach (Container container in containers) + { + if (!container.Items.Any()) + continue; + filteredItems = Shared.Models.Stateless.Methods.IContainer.GetFilterItems(propertyConfiguration, container); + if (!filteredItems.Any()) + continue; + containerDateTimes = Shared.Models.Stateless.Methods.IContainer.GetContainerDateTimes(filteredItems); + focusRelativePath = Path.GetFullPath(string.Concat(_Configuration.PropertyConfiguration.RootDirectory, _Configuration.FocusDirectory)); + isFocusRelativePath = string.IsNullOrEmpty(_Configuration.FocusDirectory) ? null : container.SourceDirectory.StartsWith(focusRelativePath); + isIgnoreRelativePath = !_Configuration.IgnoreRelativePaths.Any() ? null : _Configuration.IgnoreRelativePaths.Any(l => container.SourceDirectory.Contains(l)) && Shared.Models.Stateless.Methods.IContainer.IsIgnoreRelativePath(_Configuration.PropertyConfiguration, _Configuration.IgnoreRelativePaths, container.SourceDirectory); + foreach (Item item in filteredItems) + { + if (item.Property?.Id is null || item.ResizedFileHolder is null) + continue; + mappingFromItem = Shared.Models.Stateless.Methods.IMappingFromItem.GetMappingFromItem(containerDateTimes, item, item.ResizedFileHolder); + if (distinctItems) + { + if (distinct.Contains(item.Property.Id.Value)) + continue; + distinct.Add(item.Property.Id.Value); + } + count++; + anyValidFaces = false; + foreach (Shared.Models.Face face in item.Faces) + { + if (face.Mapping is null) + continue; + anyValidFaces = true; + mappingCollection.Add(face.Mapping); + } + if (!anyValidFaces) + { + mapping = GetMapping(idToLocationContainers, mapLogic, item, isFocusRelativePath, isIgnoreRelativePath, mappingFromItem); + mappingCollection.Add(mapping); + } + } + } + results = (from l in mappingCollection orderby l.MappingFromItem.Id select l).ToArray(); + return results; + } + private void Search(long ticks, string argZero, string propertyRoot) { int t; @@ -1006,14 +1052,15 @@ public partial class DlibDotNet ReadOnlyDictionary>> idToLocationContainers = mapLogic.GetIdToLocationContainers(); fileNameToCollection = !Directory.Exists(fPhotoPrismSingletonDirectory) ? fileNameToCollection = new() : F_PhotoPrism.GetFileNameToCollection(fPhotoPrismSingletonDirectory); FullDoWork(argZero, propertyRoot, ticks, aResultsFullGroupDirectory, bResultsFullGroupDirectory, t, containers, propertyLogic, metadata, eDistanceContentDirectory, fileNameToCollection, idToLocationContainers, mapLogic); - LookForAbandoned(containers, idToLocationContainers); + LookForAbandoned(bResultsFullGroupDirectory, containers, idToLocationContainers); _Distance.Clear(); if (!personKeyToIds.Any()) personKeyToIds = mapLogic.GetPersonKeyToIds(); - List distinctFilteredFaces = GetFilteredDistinct(argZero, containers); - Mapping[] mappingCollection = Map.Models.Stateless.Methods.IMapLogic.GetSelectedMappingCollection(distinctFilteredFaces); - int totalNotMapped = mapLogic.UpdateMappingFromPerson(mappingCollection); - string json = System.Text.Json.JsonSerializer.Serialize(mappingCollection); + Mapping[] distinctFilteredMappingCollection = GetMappings(_Configuration.PropertyConfiguration, containers, mapLogic, idToLocationContainers, distinctItems: true); + List distinctFilteredItems = Shared.Models.Stateless.Methods.IContainer.GetItems(_Configuration.PropertyConfiguration, containers, distinctItems: true, filterItems: true); + List distinctFilteredFaces = Map.Models.Stateless.Methods.IMapLogic.GetFaces(distinctFilteredItems); + int totalNotMapped = mapLogic.UpdateMappingFromPerson(distinctFilteredMappingCollection); + string json = System.Text.Json.JsonSerializer.Serialize(distinctFilteredMappingCollection); File.WriteAllText(Path.Combine(eDistanceContentDirectory, $"{ticks}.json"), json); for (int i = 1; i < 5; i++) _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(eDistanceContentDirectory); @@ -1022,18 +1069,18 @@ public partial class DlibDotNet if (_PropertyRootExistedBefore) break; if (!string.IsNullOrEmpty(a2PeopleContentDirectory) && _Configuration.SaveShortcutsForOutputResolutions.Contains(outputResolution)) - mapLogic.SaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, idToLocationContainers, mappingCollection); + mapLogic.SaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, distinctFilteredMappingCollection); if (!string.IsNullOrEmpty(a2PeopleContentDirectory) && _Configuration.SaveFilteredOriginalImagesFromJLinksForOutputResolutions.Contains(outputResolution)) - mapLogic.SaveFilteredOriginalImagesFromJLinks(_Configuration.JLinks, _PersonContainers, a2PeopleContentDirectory, personKeyToIds, mappingCollection, totalNotMapped); + mapLogic.SaveFilteredOriginalImagesFromJLinks(_Configuration.JLinks, _PersonContainers, a2PeopleContentDirectory, personKeyToIds, distinctFilteredMappingCollection, totalNotMapped); (cResultsFullGroupDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory) = GetResultsFullGroupDirectories(outputResolution); if (_ArgZeroIsConfigurationRootDirectory && _Configuration.SaveResizedSubfiles && outputResolution == _Configuration.OutputResolutions[0] && _Configuration.LoadOrCreateThenSaveDistanceResultsForOutputResolutions.Contains(outputResolution) && _Exceptions.Count == 0) - MapLogic(argZero, ticks, containers, a2PeopleSingletonDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory, fPhotoPrismContentDirectory, mapLogic, outputResolution, personKeyToIds, distinctFilteredFaces, mappingCollection, totalNotMapped); - if (_Configuration.SaveRandomForOutputResolutions.Contains(outputResolution) && personKeyToIds.Any() && mappingCollection.Any()) - _Random.Random(_Configuration.PropertyConfiguration, outputResolution, personKeyToIds, mappingCollection); + MapLogic(ticks, containers, a2PeopleSingletonDirectory, dResultsFullGroupDirectory, d2ResultsFullGroupDirectory, fPhotoPrismContentDirectory, mapLogic, outputResolution, personKeyToIds, distinctFilteredFaces, distinctFilteredMappingCollection, totalNotMapped); + if (_Configuration.SaveRandomForOutputResolutions.Contains(outputResolution) && personKeyToIds.Any() && distinctFilteredMappingCollection.Any()) + _Random.Random(_Configuration.PropertyConfiguration, outputResolution, personKeyToIds, distinctFilteredMappingCollection); if (_IsEnvironment.Development) continue; if (!_IsEnvironment.Development) diff --git a/Map/Models/MapLogic.cs b/Map/Models/MapLogic.cs index c83133c..0f84b74 100644 --- a/Map/Models/MapLogic.cs +++ b/Map/Models/MapLogic.cs @@ -1,6 +1,7 @@ using Humanizer; using ShellProgressBar; using System.Collections.ObjectModel; +using System.Globalization; using System.Text.Json; using System.Text.RegularExpressions; using View_by_Distance.Shared.Models; @@ -1122,35 +1123,43 @@ public class MapLogic : Shared.Models.Methods.IMapLogic SaveContainers(saveIndividually, totalNotMapped, null, saveContainers); } - private List GetCollectionForSaveShortcutsForOutputResolutionsPreMapLogic(string eDistanceContentDirectory, Dictionary> personKeyToIds, ReadOnlyDictionary>> idToLocationContainers, Mapping[] mappingCollection) + private List GetCollectionForSaveShortcutsForOutputResolutionsPreMapLogic(string eDistanceContentDirectory, Dictionary> personKeyToIds, Mapping[] mappingCollection) { List results = new(); if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); + int season; string? model; long personKey; string fileName; string directory; + int dateCount = 0; + string weekOfYear; + string description; + DateTime? dateTime; string directoryName; List? personKeys; string personKeyFormatted; + Calendar calendar = new CultureInfo("en-US").Calendar; IReadOnlyList directories; - List>? locationContainers; Dictionary> idToPersonKeys = Stateless.Methods.IMapLogic.GetIdToPersonKeys(personKeyToIds); foreach (Mapping mapping in mappingCollection) { - if (mapping.MappingFromLocation is null || mapping.MappingFromItem.ImageFileHolder.DirectoryName is null || mapping.MappingFromPerson is null) - continue; - if (!idToLocationContainers.TryGetValue(mapping.MappingFromItem.Id, out locationContainers) || !locationContainers.Any()) - directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(mapping.MappingFromItem.ResizedFileHolder.FullName); - else + directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(mapping.MappingFromItem.ResizedFileHolder.FullName); + dateTime = Metadata.Models.Stateless.Methods.IMetadata.GetMinimumDateTime(directories); + description = mapping.MappingFromLocation is null ? mapping.MappingFromItem.Id.ToString() : mapping.MappingFromLocation.DeterministicHashCodeKey; + if (dateTime is not null) { - directories = locationContainers.First().Directories; - model = Metadata.Models.Stateless.Methods.IMetadata.GetModel(directories); - if (model is null) - directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(mapping.MappingFromItem.ResizedFileHolder.FullName); + dateCount++; + (season, _) = IProperty.GetSeason(dateTime.Value.DayOfYear); + weekOfYear = calendar.GetWeekOfYear(dateTime.Value, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00"); + directory = Path.Combine($"{eDistanceContentDirectory}---", "Date Shortcuts", $"{dateTime.Value.Year}.{season}-MM{dateTime.Value.Month:00}-WW{weekOfYear}"); + fileName = Path.Combine(directory, $"{mapping.MappingFromItem.ImageFileHolder.Name}.lnk"); + results.Add(new(mapping.MappingFromItem.ImageFileHolder.FullName, directory, mapping.MappingFromItem.MinimumDateTime, fileName, description, MakeAllHidden: false)); } model = Metadata.Models.Stateless.Methods.IMetadata.GetModel(directories); + if (mapping.MappingFromItem.ImageFileHolder.DirectoryName is null) + continue; directoryName = Path.GetFileName(mapping.MappingFromItem.ImageFileHolder.DirectoryName); if (!string.IsNullOrEmpty(model) && !string.IsNullOrEmpty(model.Trim())) { @@ -1158,8 +1167,10 @@ public class MapLogic : Shared.Models.Methods.IMapLogic model = Regex.Replace(model.Trim(), @"[\\,\/,\:,\*,\?,\"",\<,\>,\|]", "_"); directory = Path.Combine($"{eDistanceContentDirectory}---", "Model Shortcuts", model, directoryName); fileName = Path.Combine(directory, $"{mapping.MappingFromItem.ImageFileHolder.Name}.lnk"); - results.Add(new(mapping.MappingFromItem.ImageFileHolder.FullName, directory, mapping.MappingFromItem.MinimumDateTime, fileName, mapping.MappingFromLocation.DeterministicHashCodeKey, MakeAllHidden: false)); + results.Add(new(mapping.MappingFromItem.ImageFileHolder.FullName, directory, mapping.MappingFromItem.MinimumDateTime, fileName, description, MakeAllHidden: false)); } + if (mapping.MappingFromPerson is null) + continue; if (!idToPersonKeys.TryGetValue(mapping.MappingFromItem.Id, out personKeys)) continue; personKey = mapping.MappingFromPerson.PersonBirthday.Value.Ticks; @@ -1168,22 +1179,22 @@ public class MapLogic : Shared.Models.Methods.IMapLogic personKeyFormatted = IPersonBirthday.GetFormatted(_Configuration.PersonBirthdayFormat, mapping.MappingFromPerson.PersonBirthday); directory = Path.Combine($"{eDistanceContentDirectory}---", "Person Key Shortcuts", personKeyFormatted, directoryName); fileName = Path.Combine(directory, $"{mapping.MappingFromItem.ImageFileHolder.Name}.lnk"); - results.Add(new(mapping.MappingFromItem.ImageFileHolder.FullName, directory, mapping.MappingFromItem.MinimumDateTime, fileName, mapping.MappingFromLocation.DeterministicHashCodeKey, MakeAllHidden: false)); + results.Add(new(mapping.MappingFromItem.ImageFileHolder.FullName, directory, mapping.MappingFromItem.MinimumDateTime, fileName, description, MakeAllHidden: false)); if (IPerson.IsDefaultName(_Configuration.MappingDefaultName, mapping.MappingFromPerson.DisplayDirectoryName)) continue; directory = Path.Combine($"{eDistanceContentDirectory}---", "Name Shortcuts", mapping.MappingFromPerson.DisplayDirectoryName, directoryName); fileName = Path.Combine(directory, $"{mapping.MappingFromItem.ImageFileHolder.Name}.lnk"); - results.Add(new(mapping.MappingFromItem.ImageFileHolder.FullName, directory, mapping.MappingFromItem.MinimumDateTime, fileName, mapping.MappingFromLocation.DeterministicHashCodeKey, MakeAllHidden: false)); + results.Add(new(mapping.MappingFromItem.ImageFileHolder.FullName, directory, mapping.MappingFromItem.MinimumDateTime, fileName, description, MakeAllHidden: false)); } return results; } - public void SaveShortcutsForOutputResolutionsPreMapLogic(string eDistanceContentDirectory, Dictionary> personKeyToIds, ReadOnlyDictionary>> idToLocationContainers, Mapping[] mappingCollection) + public void SaveShortcutsForOutputResolutionsPreMapLogic(string eDistanceContentDirectory, Dictionary> personKeyToIds, Mapping[] mappingCollection) { string hiddenFile; WindowsShortcut windowsShortcut; List collection = new(); - collection = GetCollectionForSaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, idToLocationContainers, mappingCollection); + collection = GetCollectionForSaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, mappingCollection); string[] distinctDirectories = (from l in collection select l.Directory).Distinct().ToArray(); foreach (string directory in distinctDirectories) { @@ -1308,13 +1319,14 @@ public class MapLogic : Shared.Models.Methods.IMapLogic return new(directoriesAndDateTimes, collection); } - public void SaveShortcutsForOutputResolutionsDuringMapLogic(Dictionary> personKeyToIds, string dFacesContentDirectory, List filteredItems, Mapping[] mappingCollection) + public void SaveShortcutsForOutputResolutionsDuringMapLogic(Container[] containers, Dictionary> personKeyToIds, string dFacesContentDirectory, Mapping[] mappingCollection) { if (_Configuration is null) throw new NullReferenceException(nameof(_Configuration)); WindowsShortcut windowsShortcut; List<(string, DateTime[])> directoriesAndDateTimes; List collection; + List filteredItems = IContainer.GetItems(_PropertyConfiguration, containers, distinctItems: true, filterItems: true); (directoriesAndDateTimes, collection) = GetCollectionForSaveShortcutsForOutputResolutionsDuringMapLogic(personKeyToIds, dFacesContentDirectory, filteredItems, mappingCollection); string[] directories = (from l in collection select l.Directory).Distinct().ToArray(); foreach (string directory in directories) diff --git a/Map/Models/Stateless/MapLogic.cs b/Map/Models/Stateless/MapLogic.cs index 26ae7d6..71e1871 100644 --- a/Map/Models/Stateless/MapLogic.cs +++ b/Map/Models/Stateless/MapLogic.cs @@ -993,14 +993,39 @@ internal abstract class MapLogic return results; } - internal static Mapping[] GetSelectedMappingCollection(List distinctFilteredFaces) + internal static Mapping[] GetSelectedMappingCollection(List faces) { Mapping[] results; - IEnumerable collection = from l in distinctFilteredFaces orderby l.Mapping?.MappingFromItem.Id select l.Mapping; + IEnumerable collection = from l in faces orderby l.Mapping?.MappingFromItem.Id select l.Mapping; results = (from l in collection where l is not null select l).ToArray(); return results; } + internal static List GetFaces(List items) + { + List results = new(); + foreach (Item item in items) + { + if (item.Property?.Id is null || item.ResizedFileHolder is null) + continue; + foreach (Face face in item.Faces) + { + if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) + continue; + results.Add(face); + } + } + return results; + } + + internal static Mapping[] GetSelectedMappingCollection(List items) + { + Mapping[] results; + List faces = GetFaces(items); + results = GetSelectedMappingCollection(faces); + return results; + } + internal static Dictionary> GetIdToNormalizedRectangleToFace(Mapping[] mappingCollection) { Dictionary> results = new(); diff --git a/Map/Models/Stateless/Methods/IMapLogic.cs b/Map/Models/Stateless/Methods/IMapLogic.cs index 6947c9b..84f322b 100644 --- a/Map/Models/Stateless/Methods/IMapLogic.cs +++ b/Map/Models/Stateless/Methods/IMapLogic.cs @@ -8,10 +8,20 @@ public interface IMapLogic static Dictionary> GetIdToPersonKeys(Dictionary> personKeyToIds) => MapLogic.GetIdToPersonKeys(personKeyToIds); - Shared.Models.Mapping[] TestStatic_GetSelectedMappingCollection(List distinctFilteredFaces) => - GetSelectedMappingCollection(distinctFilteredFaces); - static Shared.Models.Mapping[] GetSelectedMappingCollection(List distinctFilteredFaces) => - MapLogic.GetSelectedMappingCollection(distinctFilteredFaces); + List TestStatic_GetFaces(List items) => + GetFaces(items); + static List GetFaces(List items) => + MapLogic.GetFaces(items); + + Shared.Models.Mapping[] TestStatic_GetSelectedMappingCollection(List items) => + GetSelectedMappingCollection(items); + static Shared.Models.Mapping[] GetSelectedMappingCollection(List items) => + MapLogic.GetSelectedMappingCollection(items); + + Shared.Models.Mapping[] TestStatic_GetSelectedMappingCollection(List faces) => + GetSelectedMappingCollection(faces); + static Shared.Models.Mapping[] GetSelectedMappingCollection(List faces) => + MapLogic.GetSelectedMappingCollection(faces); Dictionary> TestStatic_GetIdToNormalizedRectangleToFace(Shared.Models.Mapping[] mappingCollection) => GetIdToNormalizedRectangleToFace(mappingCollection); diff --git a/Metadata/Models/Stateless/Methods/IMetadata.cs b/Metadata/Models/Stateless/Methods/IMetadata.cs index eb4926b..0fd4c02 100644 --- a/Metadata/Models/Stateless/Methods/IMetadata.cs +++ b/Metadata/Models/Stateless/Methods/IMetadata.cs @@ -3,10 +3,15 @@ namespace View_by_Distance.Metadata.Models.Stateless.Methods; public interface IMetadata { - DateTime TestStatic_GetMinimumDateTime(DateTime?[] dateTimes, int year, IReadOnlyList directories) => + DateTime? TestStatic_GetMinimumDateTime(DateTime?[] dateTimes, int year, IReadOnlyList directories) => GetMinimumDateTime(dateTimes, year, directories); - static DateTime GetMinimumDateTime(DateTime?[] dateTimes, int year, IReadOnlyList directories) => - Metadata.GetMinimumDateTime(dateTimes, year, directories); + static DateTime? GetMinimumDateTime(DateTime?[] dateTimes, int year, IReadOnlyList directories) => + Metadata.GetMinimumDateTime(directories, dateTimes, year); + + DateTime? TestStatic_GetMinimumDateTime(IReadOnlyList directories) => + GetMinimumDateTime(directories); + static DateTime? GetMinimumDateTime(IReadOnlyList directories) => + Metadata.GetMinimumDateTime(directories, dateTimes: null, year: null); string? TestStatic_GetModel(IReadOnlyList directories) => GetModel(directories); diff --git a/Metadata/Models/Stateless/Methods/Metadata.cs b/Metadata/Models/Stateless/Methods/Metadata.cs index 1e8b15f..8894774 100644 --- a/Metadata/Models/Stateless/Methods/Metadata.cs +++ b/Metadata/Models/Stateless/Methods/Metadata.cs @@ -53,11 +53,11 @@ internal class Metadata return result; } - internal static DateTime GetMinimumDateTime(DateTime?[] dateTimes, int year, IReadOnlyList directories) + internal static DateTime? GetMinimumDateTime(IReadOnlyList directories, DateTime?[]? dateTimes, int? year) { - DateTime result; + DateTime? result; DateTime dateTime; - List results = (from l in dateTimes where l is not null select l.Value).ToList(); + List results = dateTimes is null ? new() : (from l in dateTimes where l is not null select l.Value).ToList(); ExifDirectoryBase? exifDirectoryBase = directories.OfType().FirstOrDefault(); if (exifDirectoryBase is not null) { @@ -86,11 +86,13 @@ internal class Metadata if (quickTimeTrackHeaderDirectory.TryGetDateTime(QuickTimeTrackHeaderDirectory.TagCreated, out dateTime)) results.Add(dateTime); } - DateTime[] filtered = (from l in results where l.Year >= year select l).ToArray(); - if (!filtered.Any()) + DateTime[] filtered = year is null ? results.ToArray() : (from l in results where l.Year >= year select l).ToArray(); + if (filtered.Any()) + result = filtered.Min(); + else if (results.Any()) result = results.Min(); else - result = filtered.Min(); + result = null; return result; } diff --git a/Shared/Models/Stateless/Methods/Container.cs b/Shared/Models/Stateless/Methods/Container.cs index 79eaa56..158794a 100644 --- a/Shared/Models/Stateless/Methods/Container.cs +++ b/Shared/Models/Stateless/Methods/Container.cs @@ -7,16 +7,16 @@ internal abstract class Container private record FilePair(string Path, string? Directory, bool IsUnique, List Collection, Models.Item Item) { } - internal static DateTime[] GetContainerDateTimes(Models.Item[] filteredItems) + internal static DateTime[] GetContainerDateTimes(IEnumerable items) { DateTime[] results; DateTime? containerMinimumDateTime; DateTime? containerMaximumDateTime; - containerMinimumDateTime = (from l in filteredItems select l.ImageFileHolder.LastWriteTime).Min(); + containerMinimumDateTime = (from l in items select l.ImageFileHolder.LastWriteTime).Min(); if (containerMinimumDateTime is null) containerMaximumDateTime = null; else - containerMaximumDateTime = (from l in filteredItems select l.ImageFileHolder.LastWriteTime).Max(); + containerMaximumDateTime = (from l in items select l.ImageFileHolder.LastWriteTime).Max(); if (containerMinimumDateTime is null || containerMaximumDateTime is null) results = Array.Empty(); else @@ -94,10 +94,7 @@ internal abstract class Container filePairs = IDirectory.GetFiles(filesCollection, fileNamesToFiles, extension, compareFileNamesToFiles); renamed += IDirectory.MaybeMove(propertyConfiguration.RootDirectory, propertyConfiguration.ResultAllInOne, filePairs, aPropertySingletonDirectory, extension); if (renamed == 0) - { - _ = IPath.DeleteEmptyDirectories(aPropertySingletonDirectory); break; - } } if (filePairs is null || jsonFilesCollection is null || compareFileNamesToFiles is null) throw new NullReferenceException(nameof(filePairs)); @@ -228,7 +225,7 @@ internal abstract class Container return (collection.Count, results.ToArray()); } - internal static List GetFilteredDistinct(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers) + internal static List GetFilteredDistinctIds(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers) { List results = new(); Models.Item[] filteredItems; @@ -251,4 +248,37 @@ internal abstract class Container return results; } + internal static List GetItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers, bool distinctItems, bool filterItems) + { + List results = new(); + List distinct = new(); + IEnumerable filteredItems; + foreach (Models.Container container in containers) + { + if (!container.Items.Any()) + continue; + if (!filterItems) + filteredItems = container.Items; + else + { + filteredItems = GetFilterItems(propertyConfiguration, container); + if (!filteredItems.Any()) + continue; + } + foreach (Models.Item item in filteredItems) + { + if (item.Property?.Id is null || item.ResizedFileHolder is null) + continue; + if (distinctItems) + { + if (distinct.Contains(item.Property.Id.Value)) + continue; + distinct.Add(item.Property.Id.Value); + } + results.Add(item); + } + } + return results; + } + } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IContainer.cs b/Shared/Models/Stateless/Methods/IContainer.cs index 6a5b59d..df4a141 100644 --- a/Shared/Models/Stateless/Methods/IContainer.cs +++ b/Shared/Models/Stateless/Methods/IContainer.cs @@ -3,10 +3,10 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IContainer { - DateTime[] TestStatic_GetContainerDateTimes(Models.Item[] filteredItems) => - GetContainerDateTimes(filteredItems); - static DateTime[] GetContainerDateTimes(Models.Item[] filteredItems) => - Container.GetContainerDateTimes(filteredItems); + DateTime[] TestStatic_GetContainerDateTimes(IEnumerable items) => + GetContainerDateTimes(items); + static DateTime[] GetContainerDateTimes(IEnumerable items) => + Container.GetContainerDateTimes(items); Models.Item[] TestStatic_GetFilterItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container container) => GetFilterItems(propertyConfiguration, container); @@ -33,9 +33,14 @@ public interface IContainer static (int, Models.Container[]) GetContainers(Properties.IPropertyConfiguration propertyConfiguration, string aPropertySingletonDirectory) => Container.GetContainers(propertyConfiguration, aPropertySingletonDirectory); - List TestStatic_GetFilteredDistinct(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers) => - GetFilteredDistinct(propertyConfiguration, containers); - static List GetFilteredDistinct(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers) => - Container.GetFilteredDistinct(propertyConfiguration, containers); + List TestStatic_GetFilteredDistinctIds(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers) => + GetFilteredDistinctIds(propertyConfiguration, containers); + static List GetFilteredDistinctIds(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers) => + Container.GetFilteredDistinctIds(propertyConfiguration, containers); + + List TestStatic_GetItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers, bool distinctItems, bool filterItems) => + GetItems(propertyConfiguration, containers, distinctItems, filterItems); + static List GetItems(Properties.IPropertyConfiguration propertyConfiguration, Models.Container[] containers, bool distinctItems, bool filterItems) => + Container.GetItems(propertyConfiguration, containers, distinctItems, filterItems); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/ILocation.cs b/Shared/Models/Stateless/Methods/ILocation.cs index 95a1bec..09dc0b7 100644 --- a/Shared/Models/Stateless/Methods/ILocation.cs +++ b/Shared/Models/Stateless/Methods/ILocation.cs @@ -95,6 +95,11 @@ public interface ILocation static int GetNormalizedRectangle(Models.Location location, int locationDigits, Models.OutputResolution outputResolution) => Location.GetNormalizedRectangle(location.Bottom, OutputResolution.Get(outputResolution).Height, location.Left, locationDigits, location.Right, location.Top, OutputResolution.Get(outputResolution).Width, zCount: 1); + int TestStatic_GetNormalizedRectangle(int locationDigits) => + GetNormalizedRectangle(locationDigits); + static int GetNormalizedRectangle(int locationDigits) => + Location.GetNormalizedRectangle(1, 1, 0, locationDigits, 1, 0, 1, zCount: 1); + int TestStatic_GetNormalizedRectangle(int bottom, int height, int left, int locationDigits, int right, int top, int width) => GetNormalizedRectangle(bottom, height, left, locationDigits, right, top, width); static int GetNormalizedRectangle(int bottom, int height, int left, int locationDigits, int right, int top, int width) => diff --git a/Shared/Models/Stateless/Methods/IMapping.cs b/Shared/Models/Stateless/Methods/IMapping.cs index 8f92099..e988ffd 100644 --- a/Shared/Models/Stateless/Methods/IMapping.cs +++ b/Shared/Models/Stateless/Methods/IMapping.cs @@ -28,6 +28,11 @@ public interface IMapping static string GetDeterministicHashCodeKey(int id, Models.Location location, int locationDigits, Models.OutputResolution outputResolution) => $"{id}.{ILocation.GetLeftPadded(locationDigits, ILocation.GetNormalizedRectangle(location, locationDigits, outputResolution))}"; + string TestStatic_GetDeterministicHashCodeKey(int id, int locationDigits) + => GetDeterministicHashCodeKey(id, locationDigits); + static string GetDeterministicHashCodeKey(int id, int locationDigits) + => $"{id}.{ILocation.GetLeftPadded(locationDigits, ILocation.GetNormalizedRectangle(locationDigits))}"; + (int?, int?) TestStatic_GetConverted(string facesFileNameExtension, string file) => GetConverted(facesFileNameExtension, file); static (int?, int?) GetConverted(string facesFileNameExtension, string file) => diff --git a/Shared/Models/Stateless/Methods/IPath.cs b/Shared/Models/Stateless/Methods/IPath.cs index 47c6a36..164f727 100644 --- a/Shared/Models/Stateless/Methods/IPath.cs +++ b/Shared/Models/Stateless/Methods/IPath.cs @@ -34,6 +34,11 @@ public interface IPath static string[] GetDirectoryNames(string directory) => XPath.GetDirectoryNames(directory).ToArray(); + string[] TestStatic_GetDirectories(string directory) => + GetDirectories(directory); + static string[] GetDirectories(string directory) => + XPath.GetDirectories(directory).ToArray(); + string TestStatic_GetRelativePath(string path, int length, bool forceExtensionToLower) => GetRelativePath(path, length, forceExtensionToLower); static string GetRelativePath(string path, int length, bool forceExtensionToLower) => diff --git a/Shared/Models/Stateless/Methods/XPath.cs b/Shared/Models/Stateless/Methods/XPath.cs index 8a41e6b..12f4395 100644 --- a/Shared/Models/Stateless/Methods/XPath.cs +++ b/Shared/Models/Stateless/Methods/XPath.cs @@ -138,6 +138,27 @@ internal abstract class XPath return results; } + internal static List GetDirectories(string directory) + { + List results = new(); + string? checkDirectory = directory; + string? pathRoot = Path.GetPathRoot(directory); + if (string.IsNullOrEmpty(pathRoot)) + throw new NullReferenceException(nameof(pathRoot)); + if (Directory.Exists(directory)) + results.Add(directory); + for (int i = 0; i < int.MaxValue; i++) + { + checkDirectory = Path.GetDirectoryName(checkDirectory); + if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot) + break; + results.Add(checkDirectory); + } + results.Add(pathRoot); + results.Reverse(); + return results; + } + internal static (int level, List directories) Get(string rootDirectory, string sourceDirectory) { int result = 0; diff --git a/View-by-Distance-MKLink-Console.sln b/View-by-Distance-MKLink-Console.sln index 41c9d4d..e14d3af 100644 --- a/View-by-Distance-MKLink-Console.sln +++ b/View-by-Distance-MKLink-Console.sln @@ -51,6 +51,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Move-By-Id", "Move-By-Id\Mo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Person", "Person\Person.csproj", "{C5003A39-334B-444B-9873-39B26E58D667}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drag-Drop-Move", "Drag-Drop-Move\Drag-Drop-Move.csproj", "{CF05EFAC-C212-4EE0-A644-3F728991AA54}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -160,5 +162,9 @@ Global {C5003A39-334B-444B-9873-39B26E58D667}.Debug|Any CPU.Build.0 = Debug|Any CPU {C5003A39-334B-444B-9873-39B26E58D667}.Release|Any CPU.ActiveCfg = Release|Any CPU {C5003A39-334B-444B-9873-39B26E58D667}.Release|Any CPU.Build.0 = Release|Any CPU + {CF05EFAC-C212-4EE0-A644-3F728991AA54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF05EFAC-C212-4EE0-A644-3F728991AA54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF05EFAC-C212-4EE0-A644-3F728991AA54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF05EFAC-C212-4EE0-A644-3F728991AA54}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal