DirectoryDictionary Add http file .7-Question JustMediaDate CombinedEnumAndIndex
395 lines
19 KiB
C#
395 lines
19 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);
|
|
}
|
|
|
|
private static void DownloadFile(HttpClient httpClient, FilePath filePath)
|
|
{
|
|
FileStream fileStream = new(filePath.FullName, FileMode.Truncate);
|
|
Task<HttpResponseMessage> httpResponseMessage = httpClient.GetAsync(filePath.FullName);
|
|
httpResponseMessage.Wait();
|
|
Task task = httpResponseMessage.Result.Content.CopyToAsync(fileStream);
|
|
task.Wait();
|
|
}
|
|
|
|
ReadOnlyCollection<string> IWindows.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IWindowsSettings WindowsSettings, HttpClient? httpClient, FilePath filePath)
|
|
{
|
|
List<string> results = [];
|
|
bool isValidVideoFormatExtensions = WindowsSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered);
|
|
if (isValidVideoFormatExtensions)
|
|
{
|
|
bool check;
|
|
if (httpClient is not null)
|
|
DownloadFile(httpClient, filePath);
|
|
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 static int WindowsSynchronousWork(ILogger<Program>? logger, AppSettings appSettings, IWindows windows, A_Metadata metadata, List<FirstPass> results, int index, KeyValuePair<string, List<FileHolder>> keyValuePair)
|
|
{
|
|
int result = index + 1;
|
|
windows.Tick();
|
|
FilePath filePath;
|
|
FirstPass firstPass;
|
|
string directoryName;
|
|
ExifDirectory exifDirectory;
|
|
List<FileHolder> sidecarFiles;
|
|
DeterministicHashCode deterministicHashCode;
|
|
bool fastForwardMovingPictureExpertsGroupUsed;
|
|
MinimumYearAndPathCombined minimumYearAndPathCombined;
|
|
FilePath? fastForwardMovingPictureExpertsGroupFilePath;
|
|
ReadOnlyCollection<string>? fastForwardMovingPictureExpertsGroupFiles;
|
|
HttpClient? httpClient = string.IsNullOrEmpty(appSettings.WindowsSettings.Host) || string.IsNullOrEmpty(appSettings.WindowsSettings.Page) ? null : new();
|
|
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, 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.WindowsSettings, 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.WindowsSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered))
|
|
fastForwardMovingPictureExpertsGroupUsed = true;
|
|
firstPass = new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, minimumYearAndPathCombined, sidecarFiles.ToArray());
|
|
results.Add(firstPass);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static ReadOnlyCollection<FirstPass> WindowsSynchronousWork(ILogger<Program>? logger, AppSettings appSettings, IWindows windows, IEnumerable<string> files, A_Metadata metadata)
|
|
{
|
|
List<FirstPass> results = [];
|
|
int index = -1;
|
|
ReadOnlyDictionary<string, List<FileHolder>> keyValuePairs;
|
|
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)
|
|
{
|
|
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 ReadOnlyCollection<FirstPass> WindowsAsynchronousWork(AppSettings appSettings, IWindows windows, ReadOnlyCollection<string> files, A_Metadata metadata, int appSettingsMaxDegreeOfParallelism)
|
|
{
|
|
List<FirstPass> results = [];
|
|
FirstPass firstPass;
|
|
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);
|
|
}
|
|
return results.AsReadOnly();
|
|
}
|
|
|
|
private void WindowsWork(ILogger<Program>? logger, AppSettings appSettings, IWindows windows, long ticks, string sourceDirectory)
|
|
{
|
|
ReadOnlyCollection<FirstPass> results;
|
|
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 = WindowsSynchronousWork(logger, appSettings, windows, files, metadata);
|
|
else
|
|
results = WindowsAsynchronousWork(appSettings, windows, files, metadata, appSettingsMaxDegreeOfParallelism);
|
|
string json = JsonSerializer.Serialize(results, FirstPassCollectionSourceGenerationContext.Default.ListFirstPass);
|
|
File.WriteAllText(Path.Combine(sourceDirectory, $"{ticks}.json"), json);
|
|
}
|
|
|
|
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
|
|
{
|
|
string sourceDirectory = Path.GetFullPath(appSettings.ResultSettings.RootDirectory);
|
|
WindowsWork(logger, appSettings, windows, ticks, sourceDirectory);
|
|
}
|
|
}
|
|
|
|
} |