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 { private readonly Dictionary _ExifDirectoriesById; // 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(); } const string extension = ".json"; const string fileSearchFilter = "*"; string filesCollectionRootDirectory; const string directorySearchFilter = "*"; Dictionary exifDirectoriesById = []; int maxDegreeOfParallelism = Environment.ProcessorCount; Action? tick = dlibDotNet is null ? null : dlibDotNet.Tick; filesCollectionRootDirectory = propertyConfiguration.RootDirectory; 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); ReadOnlyCollection filePairs = IFilePair.GetFilePairs(propertyConfiguration, directorySearchFilter, extension, jsonGroupSingletonDirectory, filePathsCollection); string message = $") {nameof(B_Metadata)} - 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(filePairs[i], exifDirectoriesById, tick)); _ExifDirectoriesById = exifDirectoriesById; } public ReadOnlyDictionary GetKeyValuePairsAndClear() { Dictionary results = []; foreach (KeyValuePair keyValuePair in _ExifDirectoriesById) results.Add(keyValuePair.Key, keyValuePair.Value); _ExifDirectoriesById.Clear(); return results.AsReadOnly(); } private void ParallelFor(FilePair filePair, Dictionary results, Action? tick) { tick?.Invoke(); if (filePair.FilePath.Id is null) return; ExifDirectory? exifDirectory = GetExifDirectory(filePair); if (exifDirectory is null) return; lock (results) { if (!results.ContainsKey(filePair.FilePath.Id.Value)) results.Add(filePair.FilePath.Id.Value, exifDirectory); } } private static ExifDirectory? GetExifDirectory(FilePair filePair) { ExifDirectory? result; if (filePair.Match is null) result = null; else { string json; json = File.ReadAllText(filePair.Match.FullName); if (string.IsNullOrEmpty(json)) result = null; else { result = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory); if (result?.FilePath?.Id is null) { try { result = Stateless.Methods.IMetadata.GetExifDirectory(filePair.FilePath); json = JsonSerializer.Serialize(result, ExifDirectorySourceGenerationContext.Default.ExifDirectory); _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(filePair.Match.FullName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); } catch (Exception) { result = null; } } } } 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 = IMetaBase.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 = IMetaBase.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 = IMetaBase.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 = IMetaBase.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 = IMetaBase.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 = IMetaBase.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 = IMetaBase.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]); } }