aa/Windows/Windows.cs

368 lines
18 KiB
C#

using CliWrap;
using Microsoft.Extensions.Logging;
using ShellProgressBar;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Drawing.Imaging;
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.Properties;
using View_by_Distance.Shared.Models.Stateless;
using View_by_Distance.Windows.Models;
namespace View_by_Distance.Windows;
public partial class Windows : IWindows, IDisposable
{
private ProgressBar? _ProgressBar;
private readonly ProgressBarOptions _ProgressBarOptions;
public Windows(List<string> args, ILogger<Program>? logger, AppSettings appSettings, bool isSilent, IConsole console)
{
if (isSilent)
{ }
if (args is null)
throw new NullReferenceException(nameof(args));
if (console is null)
throw new NullReferenceException(nameof(console));
IWindows windows = this;
long ticks = DateTime.Now.Ticks;
_ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
WindowsWork(logger, appSettings, windows, ticks);
}
void IWindows.Tick() =>
_ProgressBar?.Tick();
void IWindows.ConstructProgressBar(int maxTicks, string message)
{
_ProgressBar?.Dispose();
_ProgressBar = new(maxTicks, message, _ProgressBarOptions);
}
void IDisposable.Dispose()
{
_ProgressBar?.Dispose();
GC.SuppressFinalize(this);
}
ReadOnlyCollection<string> IWindows.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IWindowsSettings WindowsSettings, HttpClient? httpClient, FilePath filePath)
{
List<string> results = [];
bool isValidVideoFormatExtensions = WindowsSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered);
if (isValidVideoFormatExtensions)
{
bool check;
try
{
CommandTask<CommandResult> 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();
}
#pragma warning disable CA1416
private static DeterministicHashCode GetDeterministicHashCode(Stream stream)
{
DeterministicHashCode result;
int? id;
int? width;
int? height;
try
{
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);
id = IId.GetDeterministicHashCode(bytes);
}
catch (Exception)
{
id = null;
width = null;
height = null;
}
result = new(height, id, width);
return result;
}
private static Stream GetStream(HttpClient httpClient, Uri uri)
{
Stream result;
Task<Stream> task = httpClient.GetStreamAsync(uri);
task.Wait();
result = task.Result;
return result;
}
private static DeterministicHashCode GetDeterministicHashCode(HttpClient httpClient, Uri uri)
{
DeterministicHashCode result;
Stream stream = GetStream(httpClient, uri);
result = GetDeterministicHashCode(stream);
stream.Dispose();
return result;
}
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;
}
#pragma warning restore CA1416
private static ReadOnlyCollection<NginxFileSystem>? GetRecursiveCollection(HttpClient httpClient, string host, string page)
{
List<NginxFileSystem>? results;
Uri uri = new($"http://{host}/{page}");
string format = NginxFileSystem.GetFormat();
TimeZoneInfo timeZoneInfo = TimeZoneInfo.Local;
Task<HttpResponseMessage> taskHttpResponseMessage = httpClient.GetAsync(uri);
taskHttpResponseMessage.Wait();
if (!taskHttpResponseMessage.Result.IsSuccessStatusCode)
results = null;
else
{
Task<string> 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<NginxFileSystem>? 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();
}
private static void VerifyParallelFor(AppSettings appSettings, IWindows windows, HttpClient httpClient, NginxFileSystem nginxFileSystem, List<string> messages)
{
windows.Tick();
if (nginxFileSystem.URI is null)
return;
if (!nginxFileSystem.Name.EndsWith(".jpg"))
return;
DeterministicHashCode deterministicHashCode = windows.GetDeterministicHashCode(httpClient, nginxFileSystem.URI);
if (deterministicHashCode.Id is null)
{
messages.Add($"{nginxFileSystem.URI.OriginalString}");
return;
}
string paddedId = IId.GetPaddedId(resultSettings: appSettings.ResultSettings,
metadataSettings: appSettings.MetadataSettings,
id: deterministicHashCode.Id.Value,
hasIgnoreKeyword: null,
hasDateTimeOriginal: null,
index: null);
if (!nginxFileSystem.Name.StartsWith(paddedId))
messages.Add($"!{nginxFileSystem.Name}.StartsWith({paddedId})");
}
private static void Verify(ILogger<Program>? logger, AppSettings appSettings, IWindows windows, string host, string page)
{
List<string> messages = [];
HttpClient httpClient = new();
int appSettingsMaxDegreeOfParallelism = appSettings.WindowsSettings.MaxDegreeOfParallelism;
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism };
ReadOnlyCollection<NginxFileSystem> collection = GetRecursiveCollection(httpClient, host, page) ?? throw new Exception();
windows.ConstructProgressBar(collection.Count, nameof(Verify));
_ = Parallel.For(0, collection.Count, parallelOptions, (i, state) =>
VerifyParallelFor(appSettings, windows, httpClient, collection[i], messages));
httpClient.Dispose();
foreach (string message in messages)
logger?.LogWarning("{message}", message);
}
private static ReadOnlyCollection<NginxFileSystem> GetRecursiveCollection(string host, string page)
{
ReadOnlyCollection<NginxFileSystem> results;
HttpClient httpClient = new();
results = GetRecursiveCollection(httpClient, host, page) ?? throw new Exception();
httpClient.Dispose();
return results;
}
private List<FirstPass> GetCollection(ILogger<Program>? logger, AppSettings appSettings, IWindows windows, IEnumerable<string> files, A_Metadata metadata)
{
List<FirstPass> results = [];
int index = -1;
FilePath filePath;
FirstPass firstPass;
string directoryName;
ExifDirectory exifDirectory;
List<FileHolder> sidecarFiles;
DeterministicHashCode deterministicHashCode;
bool fastForwardMovingPictureExpertsGroupUsed;
MinimumYearAndPathCombined minimumYearAndPathCombined;
FilePath? fastForwardMovingPictureExpertsGroupFilePath;
ReadOnlyDictionary<string, List<FileHolder>> keyValuePairs;
ReadOnlyCollection<string>? fastForwardMovingPictureExpertsGroupFiles;
HttpClient? httpClient = string.IsNullOrEmpty(appSettings.WindowsSettings.Host) || string.IsNullOrEmpty(appSettings.WindowsSettings.Page) ? null : new();
if (string.IsNullOrEmpty(appSettings.WindowsSettings.Host) || string.IsNullOrEmpty(appSettings.WindowsSettings.Page))
keyValuePairs = IMetadata.GetKeyValuePairs(files);
else
{
ReadOnlyCollection<NginxFileSystem> collection = GetRecursiveCollection(appSettings.WindowsSettings.Host, appSettings.WindowsSettings.Page);
keyValuePairs = IMetadata.GetKeyValuePairs(collection);
}
foreach (KeyValuePair<string, List<FileHolder>> keyValuePair in keyValuePairs)
{
index += 1;
windows.Tick();
if (keyValuePair.Value.Count > 2)
throw new NotSupportedException("Too many sidecar files!");
foreach (FileHolder fileHolder in keyValuePair.Value)
{
if (appSettings.WindowsSettings.SidecarExtensions.Contains(fileHolder.ExtensionLowered))
continue;
if (appSettings.WindowsSettings.IgnoreExtensions.Contains(fileHolder.ExtensionLowered))
continue;
filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index);
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.WindowsSettings, httpClient, filePath);
fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index);
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.WindowsSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered))
fastForwardMovingPictureExpertsGroupUsed = true;
firstPass = new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, minimumYearAndPathCombined, sidecarFiles.ToArray());
results.Add(firstPass);
}
}
return results;
}
private void WindowsWork(ILogger<Program>? logger, AppSettings appSettings, IWindows windows, long ticks)
{
if (appSettings.WindowsSettings.VerifyOnly && !string.IsNullOrEmpty(appSettings.WindowsSettings.Host) && !string.IsNullOrEmpty(appSettings.WindowsSettings.Page))
Verify(logger, appSettings, windows, appSettings.WindowsSettings.Host, appSettings.WindowsSettings.Page);
else
{
FirstPass firstPass;
List<FirstPass> results = [];
string sourceDirectory = Path.GetFullPath(appSettings.ResultSettings.RootDirectory);
if (!Directory.Exists(sourceDirectory))
_ = Directory.CreateDirectory(sourceDirectory);
logger?.LogInformation("{Ticks} {RootDirectory}", ticks, sourceDirectory);
ReadOnlyCollection<string> files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories).ToArray().AsReadOnly();
if (files.Count > 0)
_ = IPath.DeleteEmptyDirectories(appSettings.ResultSettings.RootDirectory);
A_Metadata metadata = new(appSettings.ResultSettings, appSettings.MetadataSettings);
int appSettingsMaxDegreeOfParallelism = appSettings.WindowsSettings.MaxDegreeOfParallelism;
int filesCount = appSettingsMaxDegreeOfParallelism == 1 ? files.Count : 123000;
windows.ConstructProgressBar(filesCount, "EnumerateFiles load");
if (appSettingsMaxDegreeOfParallelism == 1)
results.AddRange(GetCollection(logger, appSettings, windows, files, metadata));
else
{
List<string> distinct = [];
List<MetadataGroup> metadataGroups = [];
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism };
files.AsParallel().ForAll(IMetadata.SetExifDirectoryCollection(windows, appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.WindowsSettings, metadata, distinct, metadataGroups));
if (_ProgressBar?.CurrentTick != results.Count)
throw new NotSupportedException();
foreach (MetadataGroup metadataGroup in metadataGroups)
{
if (metadataGroup.FastForwardMovingPictureExpertsGroupUsed || !appSettings.WindowsSettings.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);
}
}
string json = JsonSerializer.Serialize(results, FirstPassCollectionSourceGenerationContext.Default.ListFirstPass);
File.WriteAllText(Path.Combine(sourceDirectory, $"{ticks}.json"), json);
}
}
}