using MetadataExtractor; using System.Collections.ObjectModel; using System.Text.Json; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Methods; using View_by_Distance.Shared.Models.Properties; using View_by_Distance.Shared.Models.Stateless.Methods; namespace View_by_Distance.Metadata.Models; /// // Dictionary>> /// public class B_Metadata : IMetadata { public string DateGroupDirectory { get; init; } public ReadOnlyCollection Collection { get; private set; } public ReadOnlyDictionary> SingletonById { get; private set; } public ReadOnlyDictionary ExifDirectoriesById { get; private set; } // First // Set DateGroupDirectory // Create a directories for Singleton // Populate Collection // Populate Singleton // Populate existing ExifDirectories // Run similar for resize // Second // Populate needed ExifDirectories Dictionary can't be init only private readonly bool _PropertiesChangedForMetadata; private readonly IPropertyConfiguration _PropertyConfiguration; private readonly bool _ForceMetadataLastWriteTimeToCreationTime; private readonly JsonSerializerOptions _WriteIndentedJsonSerializerOptions; private readonly ReadOnlyDictionary>[] _ResultSingletonFileGroups; public B_Metadata(IDlibDotNet? dlibDotNet, IPropertyConfiguration propertyConfiguration, bool forceMetadataLastWriteTimeToCreationTime, bool propertiesChangedForMetadata, long ticks, string bResultsFullGroupDirectory) { _PropertyConfiguration = propertyConfiguration; _PropertiesChangedForMetadata = propertiesChangedForMetadata; _ForceMetadataLastWriteTimeToCreationTime = forceMetadataLastWriteTimeToCreationTime; _ResultSingletonFileGroups = [new(new Dictionary>())]; _WriteIndentedJsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true }; ReadOnlyDictionary>> keyValuePairs = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(propertyConfiguration, bResultsFullGroupDirectory, [propertyConfiguration.ResultSingleton]); foreach (KeyValuePair>> keyValuePair in keyValuePairs) { if (keyValuePair.Key == _PropertyConfiguration.ResultSingleton) _ResultSingletonFileGroups[0] = keyValuePair.Value; else throw new Exception(); } List results = []; string jsonGroupDirectory; const string extension = ".json"; const string fileSearchFilter = "*"; string filesCollectionRootDirectory; const string directorySearchFilter = "*"; int maxDegreeOfParallelism = Environment.ProcessorCount; filesCollectionRootDirectory = propertyConfiguration.RootDirectory; Dictionary exifDirectoriesById = []; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; string jsonGroupSingletonDirectory = Path.Combine(bResultsFullGroupDirectory, propertyConfiguration.ResultSingleton); string jsonGroupCollectionDirectory = Path.Combine(bResultsFullGroupDirectory, propertyConfiguration.ResultCollection); ReadOnlyCollection directories = new([jsonGroupSingletonDirectory, jsonGroupCollectionDirectory]); Shared.Models.Stateless.Methods.IPath.CreateDirectories(directories); ReadOnlyCollection> filePathsCollection = IDirectory.GetFilePathCollections(propertyConfiguration, directorySearchFilter, fileSearchFilter, filesCollectionRootDirectory, useIgnoreExtensions: true, useCeilingAverage: false); ReadOnlyDictionary> fileNamesToFiles = FilePath.GetFilesKeyValuePairs(filePathsCollection); ReadOnlyCollection filePairs = IFilePair.GetFilePairs(propertyConfiguration, directorySearchFilter, extension, jsonGroupSingletonDirectory, filePathsCollection, fileNamesToFiles); string message = $") Preloading ExifDirectory Dictionary - {(int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds)} total second(s)"; dlibDotNet?.ConstructProgressBar(filePairs.Count, message); _ = Parallel.For(0, filePairs.Count, parallelOptions, (i, state) => ParallelFor(dlibDotNet, filePairs[i], results)); jsonGroupDirectory = Path.Combine(bResultsFullGroupDirectory, propertyConfiguration.ResultCollection); foreach (ExifDirectory exifDirectory in results) { if (exifDirectory.FilePath.Id is null || exifDirectoriesById.ContainsKey(exifDirectory.FilePath.Id.Value)) continue; exifDirectoriesById.Add(exifDirectory.FilePath.Id.Value, exifDirectory); } ExifDirectoriesById = new(exifDirectoriesById); DateGroupDirectory = bResultsFullGroupDirectory; SingletonById = fileNamesToFiles; filesCollectionRootDirectory = jsonGroupCollectionDirectory; ReadOnlyCollection> filePathsSingletonCollection = IDirectory.GetFilePathCollections(propertyConfiguration, directorySearchFilter, fileSearchFilter, filesCollectionRootDirectory, useIgnoreExtensions: false, useCeilingAverage: false); Collection = filePathsSingletonCollection[0]; } private void ParallelFor(IDlibDotNet? dlibDotNet, FilePair filePair, List results) { dlibDotNet?.Tick(); if (filePair.FilePath.Id is null) return; ExifDirectory? exifDirectory = GetExifDirectory(filePair); if (exifDirectory is null) return; lock (results) results.Add(exifDirectory); } private static ExifDirectory? GetExifDirectory(FilePair filePair) { ExifDirectory? result; if (filePair.Match is null) result = null; else { string json = File.ReadAllText(filePair.Match.FullName); if (string.IsNullOrEmpty(json)) result = null; else result = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory); } return result; } public ExifDirectory GetMetadataCollection(FilePath filePath, List> subFileTuples, List parseExceptions, string[] changesFrom, MappingFromItem mappingFromItem) { ExifDirectory? result = null; List dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList(); CombinedEnumAndIndex cei = Shared.Models.Stateless.Methods.IPath.GetCombinedEnumAndIndex(_PropertyConfiguration, filePath); string fileName = $"{mappingFromItem.FilePath.NameWithoutExtension}{mappingFromItem.FilePath.ExtensionLowered}.json"; string directory = _ResultSingletonFileGroups[0][cei.Enum][cei.Index]; FileInfo fileInfo = new(Path.Combine(directory, fileName)); MoveIf(fileName, cei, directory, fileInfo); if (_ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete"))) { File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName); fileInfo.Refresh(); } if (_ForceMetadataLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime) { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); } if (_PropertiesChangedForMetadata) result = null; else if (!fileInfo.Exists) result = null; else if (!fileInfo.FullName.EndsWith(".json") && !fileInfo.FullName.EndsWith(".old")) throw new ArgumentException("must be a *.json file"); else if (dateTimes.Count != 0 && dateTimes.Max() > fileInfo.LastWriteTime) result = null; else { string json = File.ReadAllText(fileInfo.FullName); try { result = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory); if (result is null) throw new Exception(); subFileTuples.Add(new Tuple(nameof(B_Metadata), fileInfo.LastWriteTime)); } catch (Exception) { result = null; parseExceptions.Add(nameof(B_Metadata)); } } if (result is null) { result = Stateless.Methods.IMetadata.GetExifDirectory(filePath); string json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory); bool updateDateWhenMatches = dateTimes.Count != 0 && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime; DateTime? dateTime = !updateDateWhenMatches ? null : dateTimes.Max(); if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: dateTime)) { if (!_ForceMetadataLastWriteTimeToCreationTime) subFileTuples.Add(new Tuple(nameof(B_Metadata), DateTime.Now)); else { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); subFileTuples.Add(new Tuple(nameof(B_Metadata), fileInfo.CreationTime)); } } } return result; } private static void MoveIf(string fileName, CombinedEnumAndIndex cei, string directory, FileInfo fileInfo) { string[] segments = directory.Split(cei.Combined); string? checkDirectory = segments.Length == 1 ? Path.Combine(segments[0], $"{cei.Combined[2..]}") : segments.Length == 2 ? $"{segments[0]}{cei.Combined[2..]}{segments[1]}" : null; if (checkDirectory is not null && System.IO.Directory.Exists(checkDirectory)) { string checkFile = Path.Combine(checkDirectory, fileName); if (File.Exists(checkFile)) { File.Move(checkFile, fileInfo.FullName); fileInfo.Refresh(); } } } (DateTime?, DateTime?[]) IMetadata.GetDateTimes(FilePath filePath, IReadOnlyList directories) { List results = []; DateTime? result = null; DateTime? dateTime; DateTime checkDateTime; string dateTimeFormat = Stateless.Methods.IMetadata.DateTimeFormat(); MetadataExtractor.Formats.Exif.ExifDirectoryBase? exifDirectoryBase = directories.OfType().FirstOrDefault(); results.Add(new DateTime(filePath.CreationTicks)); results.Add(new DateTime(filePath.LastWriteTicks)); if (exifDirectoryBase is not null) { if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTime, out checkDateTime)) results.Add(checkDateTime); else { dateTime = Stateless.Methods.IMetadata.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTime)); if (dateTime is not null) results.Add(dateTime.Value); } if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized, out checkDateTime)) results.Add(checkDateTime); else { dateTime = Stateless.Methods.IMetadata.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized)); if (dateTime is not null) results.Add(dateTime.Value); } if (exifDirectoryBase.TryGetDateTime(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal, out checkDateTime)) { result ??= checkDateTime; results.Add(checkDateTime); } else { dateTime = Stateless.Methods.IMetadata.GetDateTime(dateTimeFormat, exifDirectoryBase.GetString(MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeOriginal)); if (dateTime is not null) { result ??= dateTime.Value; results.Add(dateTime.Value); } } } MetadataExtractor.Formats.Avi.AviDirectory? aviDirectory = directories.OfType().FirstOrDefault(); if (aviDirectory is not null) { if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal, out checkDateTime)) { result ??= checkDateTime; results.Add(checkDateTime); } else { dateTime = Stateless.Methods.IMetadata.GetDateTime(dateTimeFormat, aviDirectory.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal)); if (dateTime is not null) { result ??= dateTime.Value; results.Add(dateTime.Value); } } } MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory? quickTimeMovieHeaderDirectory = directories.OfType().FirstOrDefault(); if (quickTimeMovieHeaderDirectory is not null) { if (quickTimeMovieHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated, out checkDateTime)) { result ??= checkDateTime; results.Add(checkDateTime); } else { dateTime = Stateless.Methods.IMetadata.GetDateTime(dateTimeFormat, quickTimeMovieHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated)); if (dateTime is not null) { result ??= dateTime.Value; results.Add(dateTime.Value); } } } MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory? quickTimeTrackHeaderDirectory = directories.OfType().FirstOrDefault(); if (quickTimeTrackHeaderDirectory is not null) { if (quickTimeTrackHeaderDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated, out checkDateTime)) { result ??= checkDateTime; results.Add(checkDateTime); } else { dateTime = Stateless.Methods.IMetadata.GetDateTime(dateTimeFormat, quickTimeTrackHeaderDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated)); if (dateTime is not null) { result ??= dateTime.Value; results.Add(dateTime.Value); } } } return new(result, [.. results]); } }