using CliWrap; using File_Watcher.Models; using ShellProgressBar; using System.Collections.ObjectModel; using System.Drawing; using System.Drawing.Imaging; using System.IO.Compression; using System.Runtime.InteropServices; using System.Text.Json; using View_by_Distance.Metadata.Models; using View_by_Distance.Metadata.Models.Stateless; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Stateless; namespace File_Watcher.Helpers; internal static partial class DeterministicHashCodeHelper { private class Windows : IWindows, IDisposable { private ProgressBar? _ProgressBar; private readonly ProgressBarOptions _ProgressBarOptions; public int CurrentTick { get; internal set; } public Windows() => _ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; DeterministicHashCode IWindows.GetDeterministicHashCode(HttpClient httpClient, Uri uri) => GetDeterministicHashCode(httpClient, uri); DeterministicHashCode IWindows.GetDeterministicHashCode(HttpClient? httpClient, FilePath filePath) { DeterministicHashCode result; if (httpClient is not null) result = GetDeterministicHashCode(httpClient, new Uri(filePath.FullName)); else { Stream stream = File.OpenRead(filePath.FullName); result = GetDeterministicHashCode(stream); stream.Dispose(); } return result; } private static DeterministicHashCode GetDeterministicHashCode(HttpClient httpClient, Uri uri) { DeterministicHashCode result; Stream stream = GetStream(httpClient, uri); result = GetDeterministicHashCode(stream); stream.Dispose(); return result; } private static Stream GetStream(HttpClient httpClient, Uri uri) { Stream result; Task task = httpClient.GetStreamAsync(uri); task.Wait(); result = task.Result; return result; } private static DeterministicHashCode GetDeterministicHashCode(Stream stream) { DeterministicHashCode result; int? id; int? width; int? height; try { #pragma warning disable CA1416 using Image image = Image.FromStream(stream); width = image.Width; height = image.Height; using Bitmap bitmap = new(image); Rectangle rectangle = new(0, 0, image.Width, image.Height); BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, bitmap.PixelFormat); IntPtr intPtr = bitmapData.Scan0; int length = bitmapData.Stride * bitmap.Height; byte[] bytes = new byte[length]; Marshal.Copy(intPtr, bytes, 0, length); bitmap.UnlockBits(bitmapData); #pragma warning restore CA1416 id = IId.GetDeterministicHashCode(bytes); } catch (Exception) { id = null; width = null; height = null; } result = new(height, id, width); return result; } void IWindows.Tick() => _ProgressBar?.Tick(); void IDisposable.Dispose() { _ProgressBar?.Dispose(); GC.SuppressFinalize(this); } void IWindows.ConstructProgressBar(int maxTicks, string message) { _ProgressBar?.Dispose(); _ProgressBar = new(maxTicks, message, _ProgressBarOptions); } ReadOnlyCollection IWindows.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(ResultSettings resultSettings, HttpClient? httpClient, FilePath filePath) { List results = []; bool isValidVideoFormatExtensions = resultSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered); if (isValidVideoFormatExtensions) { bool check; if (httpClient is not null) DownloadFile(httpClient, filePath); try { CommandTask commandTask = Cli.Wrap("L:/Git/ffmpeg-2024-10-02-git-358fdf3083-full_build/bin/ffmpeg.exe") .WithArguments(["-i", filePath.FullName, "-vf", "select=eq(n\\,0)", "-q:v", "1", $"{filePath.Name}-%4d.jpg"]) .WithWorkingDirectory(filePath.DirectoryFullPath) .ExecuteAsync(); commandTask.Task.Wait(); check = true; } catch (Exception) { check = false; } if (check) { results.AddRange(Directory.GetFiles(filePath.DirectoryFullPath, $"{filePath.Name}-*.jpg", SearchOption.TopDirectoryOnly)); if (results.Count == 0) throw new Exception(); File.SetCreationTime(results[0], new(filePath.CreationTicks)); File.SetLastWriteTime(results[0], new(filePath.LastWriteTicks)); Thread.Sleep(100); } } return results.AsReadOnly(); } private static void DownloadFile(HttpClient httpClient, FilePath filePath) { FileStream fileStream = new(filePath.FullName, FileMode.Truncate); Task httpResponseMessage = httpClient.GetAsync(filePath.FullName); httpResponseMessage.Wait(); Task task = httpResponseMessage.Result.Content.CopyToAsync(fileStream); task.Wait(); } } internal static bool WindowsWork(AppSettings appSettings, ILogger? logger) { string json; string jsonFile; string sourceDirectory; List check = []; string archiveEntryFile; long ticks = DateTime.Now.Ticks; ReadOnlyCollection collection; string rootDirectory = Path.GetFullPath(appSettings.ResultSettings.RootDirectory); if (!Directory.Exists(rootDirectory)) _ = Directory.CreateDirectory(rootDirectory); string[] zipFiles = Directory.GetFiles(rootDirectory, "*.zip", SearchOption.TopDirectoryOnly); if (zipFiles.Length > 0) _ = IPath.DeleteEmptyDirectories(rootDirectory); foreach (string zipFile in zipFiles) { check.Clear(); sourceDirectory = zipFile[..^4]; jsonFile = $"{sourceDirectory}.json"; if (Directory.Exists(sourceDirectory) || File.Exists(jsonFile)) continue; _ = Directory.CreateDirectory(sourceDirectory); using ZipArchive zip = ZipFile.Open(zipFile, ZipArchiveMode.Read); foreach (ZipArchiveEntry zipArchiveEntry in zip.Entries) { check.Add(zipArchiveEntry.Name); archiveEntryFile = Path.Combine(sourceDirectory, zipArchiveEntry.Name); zipArchiveEntry.ExtractToFile(archiveEntryFile); } collection = WindowsWork(logger, appSettings, ticks, sourceDirectory); if (check.Count == collection.Count) { json = JsonSerializer.Serialize(collection.ToList(), FirstPassCollectionSourceGenerationContext.Default.ListFirstPass); File.WriteAllText(jsonFile, json); } Directory.Delete(sourceDirectory, recursive: true); } return true; } private static ReadOnlyCollection WindowsWork(ILogger? logger, AppSettings appSettings, long ticks, string sourceDirectory) { ReadOnlyCollection results; Windows windows = new(); IWindows windowsInterface = windows; logger?.LogInformation("{Ticks} {RootDirectory}", ticks, sourceDirectory); A_Metadata metadata = new(appSettings.ResultSettings, appSettings.MetadataSettings); int appSettingsMaxDegreeOfParallelism = appSettings.DeterministicHashCodeConfiguration.MaxDegreeOfParallelism; ReadOnlyCollection files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories).AsReadOnly(); if (files.Count > 0) _ = IPath.DeleteEmptyDirectories(sourceDirectory); int filesCount = appSettingsMaxDegreeOfParallelism == 1 ? files.Count : 123000; windowsInterface.ConstructProgressBar(filesCount, "EnumerateFiles load"); if (appSettingsMaxDegreeOfParallelism == 1) results = WindowsSynchronousWork(logger, appSettings, windowsInterface, files, metadata); else results = WindowsAsynchronousWork(appSettings, windows, files, metadata, appSettingsMaxDegreeOfParallelism); return results; } private static ReadOnlyCollection WindowsSynchronousWork(ILogger? logger, AppSettings appSettings, IWindows windows, IEnumerable files, A_Metadata metadata) { List results = []; int index = -1; ReadOnlyDictionary> keyValuePairs = IMetadata.GetKeyValuePairs(files); foreach (KeyValuePair> keyValuePair in keyValuePairs) { if (keyValuePair.Value.Count > 2) throw new NotSupportedException("Too many sidecar files!"); index = WindowsSynchronousWork(logger, appSettings, windows, metadata, results, index, keyValuePair); } return results.AsReadOnly(); } private static int WindowsSynchronousWork(ILogger? logger, AppSettings appSettings, IWindows windows, A_Metadata metadata, List results, int index, KeyValuePair> keyValuePair) { int result = index + 1; windows.Tick(); FilePath filePath; FirstPass firstPass; string directoryName; ExifDirectory exifDirectory; HttpClient? httpClient = null; List sidecarFiles; DeterministicHashCode deterministicHashCode; bool fastForwardMovingPictureExpertsGroupUsed; MinimumYearAndPathCombined minimumYearAndPathCombined; FilePath? fastForwardMovingPictureExpertsGroupFilePath; ReadOnlyCollection? fastForwardMovingPictureExpertsGroupFiles; foreach (FileHolder fileHolder in keyValuePair.Value) { if (appSettings.DeterministicHashCodeConfiguration.SidecarExtensions.Contains(fileHolder.ExtensionLowered)) continue; if (appSettings.ResultSettings.IgnoreExtensions.Contains(fileHolder.ExtensionLowered)) continue; filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, result); if (filePath.Id is not null && (filePath.IsIntelligentIdFormat || filePath.SortOrder is not null)) continue; if (filePath.Id is not null) { fastForwardMovingPictureExpertsGroupFiles = null; deterministicHashCode = new(null, filePath.Id, null); directoryName = Path.GetFileName(filePath.DirectoryFullPath); if (directoryName.EndsWith(filePath.Id.Value.ToString())) continue; } else { fastForwardMovingPictureExpertsGroupFiles = windows.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(appSettings.ResultSettings, httpClient, filePath); fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), result); deterministicHashCode = fastForwardMovingPictureExpertsGroupFilePath is null ? windows.GetDeterministicHashCode(httpClient, filePath) : windows.GetDeterministicHashCode(httpClient, fastForwardMovingPictureExpertsGroupFilePath); } sidecarFiles = []; filePath = FilePath.Get(filePath, deterministicHashCode); for (int i = 0; i < keyValuePair.Value.Count; i++) { if (keyValuePair.Value[i].ExtensionLowered == fileHolder.ExtensionLowered) continue; sidecarFiles.Add(keyValuePair.Value[i]); } try { (minimumYearAndPathCombined, exifDirectory) = metadata.GetMetadataCollection(appSettings.ResultSettings, appSettings.MetadataSettings, httpClient, filePath); } catch (Exception) { logger?.LogWarning("<{filePath}>", filePath.FullName); continue; } fastForwardMovingPictureExpertsGroupUsed = fastForwardMovingPictureExpertsGroupFiles is not null && fastForwardMovingPictureExpertsGroupFiles.Count > 0; if (fastForwardMovingPictureExpertsGroupUsed && fastForwardMovingPictureExpertsGroupFiles is not null) { foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles) File.Delete(fastForwardMovingPictureExpertsGroupFile); } if (!fastForwardMovingPictureExpertsGroupUsed && appSettings.ResultSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered)) fastForwardMovingPictureExpertsGroupUsed = true; firstPass = new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, minimumYearAndPathCombined, sidecarFiles.ToArray()); results.Add(firstPass); } return result; } private static ReadOnlyCollection WindowsAsynchronousWork(AppSettings appSettings, Windows windows, ReadOnlyCollection files, A_Metadata metadata, int appSettingsMaxDegreeOfParallelism) { List results = []; FirstPass firstPass; List distinct = []; List metadataGroups = []; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism }; files.AsParallel().ForAll(IMetadata.SetExifDirectoryCollection(windows, appSettings.ResultSettings, appSettings.MetadataSettings, metadata, distinct, metadataGroups)); if (windows?.CurrentTick != results.Count) throw new NotSupportedException(); foreach (MetadataGroup metadataGroup in metadataGroups) { if (metadataGroup.FastForwardMovingPictureExpertsGroupUsed || !appSettings.ResultSettings.ValidVideoFormatExtensions.Contains(metadataGroup.FilePath.ExtensionLowered)) firstPass = new(metadataGroup.ExifDirectory, metadataGroup.FastForwardMovingPictureExpertsGroupUsed, metadataGroup.MinimumYearAndPathCombined, metadataGroup.SidecarFiles.ToArray()); else firstPass = new(metadataGroup.ExifDirectory, FastForwardMovingPictureExpertsGroupUsed: true, metadataGroup.MinimumYearAndPathCombined, metadataGroup.SidecarFiles.ToArray()); results.Add(firstPass); } return results.AsReadOnly(); } private static ReadOnlyCollection? GetRecursiveCollection(HttpClient httpClient, string host, string page) { List? results; Uri uri = new($"http://{host}/{page}"); string format = NginxFileSystem.GetFormat(); TimeZoneInfo timeZoneInfo = TimeZoneInfo.Local; Task taskHttpResponseMessage = httpClient.GetAsync(uri); taskHttpResponseMessage.Wait(); if (!taskHttpResponseMessage.Result.IsSuccessStatusCode) results = null; else { Task taskString = taskHttpResponseMessage.Result.Content.ReadAsStringAsync(); taskString.Wait(); NginxFileSystem[]? nginxFileSystems = JsonSerializer.Deserialize(taskString.Result, NginxFileSystemCollectionSourceGenerationContext.Default.NginxFileSystemArray); if (nginxFileSystems is null) results = null; else { results = []; NginxFileSystem nginxFileSystem; ReadOnlyCollection? directory; for (int i = 0; i < nginxFileSystems.Length; i++) { nginxFileSystem = NginxFileSystem.Get(format, timeZoneInfo, uri, nginxFileSystems[i]); if (nginxFileSystem.Type == "file") results.Add(nginxFileSystem); else { directory = GetRecursiveCollection(httpClient, host, $"{page}/{nginxFileSystem.Name}"); if (directory is null) continue; results.AddRange(directory); } } } } return results?.AsReadOnly(); } }