using CliWrap; using Microsoft.Extensions.Configuration; using Phares.Shared; using Serilog; using ShellProgressBar; using System.Text.Json; using View_by_Distance.Copy.Distinct.Models; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Methods; namespace View_by_Distance.Copy.Distinct; public class CopyDistinct { private readonly AppSettings _AppSettings; private readonly string _WorkingDirectory; private readonly Configuration _Configuration; private readonly IsEnvironment _IsEnvironment; private readonly IConfigurationRoot _ConfigurationRoot; private readonly Property.Models.Configuration _PropertyConfiguration; public CopyDistinct(List args, IsEnvironment isEnvironment, IConfigurationRoot configurationRoot, AppSettings appSettings, string workingDirectory, bool isSilent, IConsole console) { if (isSilent) { } if (console is null) { } bool any = false; _AppSettings = appSettings; _IsEnvironment = isEnvironment; _WorkingDirectory = workingDirectory; _ConfigurationRoot = configurationRoot; ILogger? log = Log.ForContext(); Property.Models.Configuration propertyConfiguration = Property.Models.Binder.Configuration.Get(isEnvironment, configurationRoot); Configuration configuration = Models.Binder.Configuration.Get(isEnvironment, configurationRoot, propertyConfiguration); _PropertyConfiguration = propertyConfiguration; _Configuration = configuration; propertyConfiguration.Update(); string? comparePathRoot = Path.GetDirectoryName(appSettings.ComparePathsFile); if (comparePathRoot is null || comparePathRoot == propertyConfiguration.RootDirectory) throw new Exception("Nested isn't allowed!"); log.Information(propertyConfiguration.RootDirectory); Verify(); string json = File.ReadAllText(appSettings.ComparePathsFile); MatchNginx[]? matchNginxCollection = JsonSerializer.Deserialize(json); if (matchNginxCollection is null) throw new NullReferenceException(nameof(matchNginxCollection)); if (matchNginxCollection.Any()) { bool deleted; for (int i = 1; i < 5; i++) { deleted = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(matchNginxCollection.First().ConvertedPath); if (deleted && !any) any = true; } } if (!any) { List lines = CopyDistinctFilesInDirectories(log, matchNginxCollection); File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", lines); if (comparePathRoot != Path.GetPathRoot(matchNginxCollection[0].ConvertedPath)) _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(comparePathRoot); } } private void Verify() { if (_AppSettings is null) { } if (_IsEnvironment is null) { } if (_Configuration is null) { } if (_ConfigurationRoot is null) { } if (_WorkingDirectory is null) { } if (_PropertyConfiguration is null) { } } private List<(FileHolder, string)> GetToDoCollection(ProgressBar progressBar, string[] files) { List<(FileHolder, string)> results = new(); int? id; string fileName; string? message; string checkFile; string? directory; TimeSpan timeSpan; DateTime? dateTime; DateTime?[] dateTimes; FileHolder fileHolder; string[]? ffmpegFiles; bool isIgnoreExtension; string checkFileExtension; DateTime? minimumDateTime; const string jpg = ".jpg"; const string jpeg = ".jpeg"; List distinct = new(); bool isValidImageFormatExtension; bool nameWithoutExtensionIsIdFormat; IReadOnlyList directories; foreach (string file in files) { progressBar.Tick(); fileHolder = new(file); if (file.EndsWith(".rename")) { directory = Path.GetDirectoryName(file); if (string.IsNullOrEmpty(directory)) continue; checkFile = Path.Combine(directory, $"rename_{Path.GetFileName(file[..^7])}"); if (File.Exists(checkFile)) continue; if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(fileHolder, checkFile)); continue; } if (file.EndsWith(".jpg.del")) { checkFile = file[..^4]; if (File.Exists(checkFile)) continue; if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(fileHolder, checkFile)); continue; } if (fileHolder.ExtensionLowered == ".id" || fileHolder.ExtensionLowered == ".lsv" || fileHolder.DirectoryName is null) continue; if (files.Contains($"{fileHolder.FullName}.id")) continue; nameWithoutExtensionIsIdFormat = Shared.Models.Stateless.Methods.IProperty.NameWithoutExtensionIsIdFormat(fileHolder); if (nameWithoutExtensionIsIdFormat) continue; isValidImageFormatExtension = _PropertyConfiguration.ValidImageFormatExtensions.Contains(fileHolder.ExtensionLowered); isIgnoreExtension = isValidImageFormatExtension && _PropertyConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered); if (!isIgnoreExtension && isValidImageFormatExtension) ffmpegFiles = null; else { try { directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file); } catch (Exception) { continue; } CommandTask result = Cli.Wrap("ffmpeg.exe") // .WithArguments(new[] { "-ss", "00:00:00", "-t", "00:00:00", "-i", file, "-qscale:v", "2", "-r", "0.01", $"{fileHolder.Name}-%4d.jpg" }) .WithArguments(new[] { "-i", file, "-vframes", "1", $"{fileHolder.Name}-%4d.jpg" }) .WithWorkingDirectory(fileHolder.DirectoryName) .ExecuteAsync(); result.Task.Wait(); ffmpegFiles = Directory.GetFiles(fileHolder.DirectoryName, $"{fileHolder.Name}-*.jpg", SearchOption.TopDirectoryOnly); if (!ffmpegFiles.Any()) continue; fileHolder = new(ffmpegFiles.First()); if (!fileHolder.Name.EndsWith("-0001.jpg")) throw new Exception(); isValidImageFormatExtension = _PropertyConfiguration.ValidImageFormatExtensions.Contains(fileHolder.ExtensionLowered); isIgnoreExtension = isValidImageFormatExtension && _PropertyConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered); if (isIgnoreExtension || !isValidImageFormatExtension) continue; if (fileHolder.DirectoryName is null) continue; } if (!isIgnoreExtension && isValidImageFormatExtension) { if (fileHolder.ExtensionLowered == jpeg) { if (File.Exists($"{fileHolder.FullName}.id")) { checkFile = Path.Combine(fileHolder.DirectoryName, $"{fileHolder.NameWithoutExtension}{jpg}.id"); if (File.Exists(checkFile)) continue; if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(new($"{fileHolder.FullName}.id"), checkFile)); } checkFile = Path.Combine(fileHolder.DirectoryName, $"{fileHolder.NameWithoutExtension}{jpg}"); if (File.Exists(checkFile)) continue; if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(fileHolder, checkFile)); if (File.Exists(checkFile)) continue; File.Move(fileHolder.FullName, checkFile); fileHolder = new(checkFile); if (fileHolder.DirectoryName is null) continue; } } (dateTimes, id, message) = Shared.Models.Stateless.Methods.IProperty.Get(fileHolder, isIgnoreExtension, isValidImageFormatExtension); minimumDateTime = dateTimes.Min(); if (minimumDateTime is null || !isIgnoreExtension && isValidImageFormatExtension) { dateTime = Shared.Models.Stateless.Methods.IProperty.GetDateTimeFromName(fileHolder); if (dateTime is null || minimumDateTime is null) timeSpan = new TimeSpan(0); else timeSpan = new(Math.Abs(minimumDateTime.Value.Ticks - dateTime.Value.Ticks)); } else { fileName = Path.GetFileName(fileHolder.DirectoryName); if (fileName.Length < 4 || !int.TryParse(fileName[..4], out int year)) year = minimumDateTime.Value.Year; if (_PropertyConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered)) continue; try { directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file); } catch (Exception) { continue; } dateTime = Metadata.Models.Stateless.Methods.IMetadata.GetMinimumDateTime(dateTimes, year, directories); timeSpan = new TimeSpan(int.MaxValue); } if (dateTime is not null && string.IsNullOrEmpty(_AppSettings.CopyTo)) { checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; checkFile = Path.Combine(fileHolder.DirectoryName, $"{dateTime.Value:yyyy-MM-dd}.{dateTime.Value.Ticks}.{fileHolder.Length}{checkFileExtension}"); if (checkFile == fileHolder.FullName) continue; if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(fileHolder, checkFile)); if (ffmpegFiles is not null) { foreach (string ffmpegFile in ffmpegFiles) File.Delete(ffmpegFile); } continue; } if (id is null) continue; if (ffmpegFiles is not null) { foreach (string ffmpegFile in ffmpegFiles) File.Delete(ffmpegFile); fileHolder = new(file); if (fileHolder.DirectoryName is null) continue; } checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; checkFile = Path.Combine(fileHolder.DirectoryName, $"{id.Value}{checkFileExtension}"); if (checkFile == fileHolder.FullName) continue; if (File.Exists(checkFile)) { checkFile = string.Concat(checkFile, ".del"); if (File.Exists(checkFile)) continue; } if (distinct.Contains(checkFile)) continue; distinct.Add(checkFile); results.Add(new(fileHolder, checkFile)); } return results; } private static List GetAllFiles(MatchNginx[] matchNginxCollection) { List allFiles = new(); string[] files; string directoryName; ReadOnlySpan span; foreach (MatchNginx matchNginx in matchNginxCollection) { if (matchNginx.ConvertedPath.Length < 6) continue; directoryName = Path.GetFileName(matchNginx.ConvertedPath); if (matchNginx.ConvertedPath.Contains("!---")) span = "0"; else span = matchNginx.ConvertedPath.AsSpan(matchNginx.ConvertedPath.Length - 5, 3); if (directoryName.Length == 1 && int.TryParse(span, out int age)) continue; if (File.Exists(matchNginx.ConvertedPath)) continue; if (!Directory.Exists(matchNginx.ConvertedPath)) continue; files = Directory.GetFiles(matchNginx.ConvertedPath, "*", SearchOption.AllDirectories); if (files.All(l => l.EndsWith(".id"))) { foreach (string file in files) File.Delete(file); continue; } allFiles.AddRange(files); } return allFiles; } private static void CreateDirectories(List directories) { foreach (string directory in directories) { if (Directory.Exists(directory)) continue; _ = Directory.CreateDirectory(directory); } } private List CopyInstead(ILogger log, List<(FileHolder, string)> verifiedToDoCollection) { List results = new(); string copyTo; string? directory; ConsoleKey? consoleKey = null; List distinctDirectories = new(); List<(FileHolder, string)> copyCollection = new(); foreach ((FileHolder fileHolder, string to) in verifiedToDoCollection) { copyTo = $"{_AppSettings.CopyTo}{to[1..]}"; directory = Path.GetDirectoryName(copyTo); if (directory is null) continue; copyCollection.Add(new(fileHolder, copyTo)); if (distinctDirectories.Contains(directory)) continue; distinctDirectories.Add(directory); } CreateDirectories(distinctDirectories); log.Information($"Ready to Copy {verifiedToDoCollection.Count} file(s)?"); for (int y = 0; y < int.MaxValue; y++) { log.Information("Press \"Y\" key to copy file(s), \"N\" key to log file(s) or close console to not copy files"); consoleKey = System.Console.ReadKey().Key; if (consoleKey is ConsoleKey.Y or ConsoleKey.N) break; } log.Information(". . ."); if (consoleKey is null || consoleKey.Value != ConsoleKey.Y) log.Information("Nothing Copied!"); else { foreach ((FileHolder fileHolder, string to) in verifiedToDoCollection) { results.Add(fileHolder.NameWithoutExtension); File.Copy(fileHolder.FullName, to); } log.Information("Done Copying"); } return results; } private static List Move(ILogger log, List<(FileHolder, string)> verifiedToDoCollection) { List results = new(); ConsoleKey? consoleKey = null; log.Information($"Ready to Move {verifiedToDoCollection.Count} file(s)?"); for (int y = 0; y < int.MaxValue; y++) { log.Information("Press \"Y\" key to move file(s), \"N\" key to log file(s) or close console to not move files"); consoleKey = System.Console.ReadKey().Key; if (consoleKey is ConsoleKey.Y or ConsoleKey.N) break; } log.Information(". . ."); if (consoleKey is null || consoleKey.Value != ConsoleKey.Y) log.Information("Nothing moved!"); else { foreach ((FileHolder fileHolder, string to) in verifiedToDoCollection) { results.Add(fileHolder.NameWithoutExtension); try { File.Move(fileHolder.FullName, to); } catch (Exception) { } } log.Information("Done Moving"); } return results; } // { // "_App": "Copy-Distinct", // "_UserSecretsId": "d862524f-2b48-4f47-b4c3-5a8615814ec2", // "ComparePathsFile": "C:/Users/lphar/AppData/Local/PharesApps/Drag-Drop-Explorer/2023_24/638220924947919275.json", // "CopyTo": "", // "MaxDegreeOfParallelism": 6, // "Windows": { // "Configuration": { // "RootDirectory": "C:/", // "VerifyToSeason": [] // } // } // } private List CopyDistinctFilesInDirectories(ILogger log, MatchNginx[] matchNginxCollection) { List results = new(); string[] files; string message; List allFiles; ProgressBar progressBar; List<(FileHolder, string)> toDoCollection; allFiles = GetAllFiles(matchNginxCollection); List<(FileHolder, string)> verifiedToDoCollection; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; for (int i = 1; i < 3; i++) { if (!allFiles.Any()) continue; message = $"{i}) Renaming files"; files = i == 2 ? allFiles.ToArray() : (from l in allFiles where l.Contains("Rename", StringComparison.OrdinalIgnoreCase) select l).ToArray(); progressBar = new(files.Length, message, options); if (!files.Any()) continue; toDoCollection = GetToDoCollection(progressBar, files); verifiedToDoCollection = new(); foreach ((FileHolder fileHolder, string to) in toDoCollection) { if (File.Exists(to)) continue; verifiedToDoCollection.Add(new(fileHolder, to)); if (string.IsNullOrEmpty(_AppSettings.CopyTo)) File.WriteAllText($"{to}.id", $"{to}{Environment.NewLine}{fileHolder.FullName}"); } if (!verifiedToDoCollection.Any()) continue; if (string.IsNullOrEmpty(_AppSettings.CopyTo)) results.AddRange(Move(log, toDoCollection)); else results.AddRange(CopyInstead(log, toDoCollection)); allFiles = GetAllFiles(matchNginxCollection); progressBar.Dispose(); } return results; } }