Changed GetDimensions to handle a stream at the end and one exit Switched to using Action? over IDlibDotNet for Tick method Switched to using AsReadOnly over new() Moved Meta Base to Shared
254 lines
13 KiB
C#
254 lines
13 KiB
C#
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
using Phares.Shared;
|
|
using ShellProgressBar;
|
|
using System.Collections.ObjectModel;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using View_by_Distance.Duplicate.Search.Models;
|
|
using View_by_Distance.Property.Models;
|
|
using View_by_Distance.Shared.Models;
|
|
using View_by_Distance.Shared.Models.Methods;
|
|
using View_by_Distance.Shared.Models.Stateless.Methods;
|
|
|
|
namespace View_by_Distance.Duplicate.Search;
|
|
|
|
public class DuplicateSearch
|
|
{
|
|
|
|
public DuplicateSearch(List<string> args, ILogger<Program> logger, IsEnvironment isEnvironment, IConfigurationRoot configurationRoot, AppSettings appSettings, string workingDirectory, bool isSilent, IConsole console)
|
|
{
|
|
if (isSilent)
|
|
{ }
|
|
if (console is null)
|
|
{ }
|
|
string searchPattern = "*";
|
|
long ticks = DateTime.Now.Ticks;
|
|
Configuration configuration = Property.Models.Binder.Configuration.Get(isEnvironment, configurationRoot);
|
|
logger?.LogInformation(configuration.RootDirectory);
|
|
if (appSettings.IndexOnly)
|
|
WriteIndexData(ticks, configuration, searchPattern);
|
|
else
|
|
{
|
|
Configuration.Verify(configuration, requireExist: false);
|
|
Container.Models.Container[] containers = GetContainers(ticks, configuration);
|
|
string argZero = args.Count > 0 ? Path.GetFullPath(args[0]) : Path.GetFullPath(configuration.RootDirectory);
|
|
bool argZeroIsConfigurationRootDirectory = configuration.RootDirectory == argZero;
|
|
string destinationRoot = Property.Models.Stateless.IResult.GetResultsGroupDirectory(configuration, "Z) Moved");
|
|
List<int> preloadIds = GetPreloadIds(destinationRoot);
|
|
Dictionary<int, List<MappingFromItem?>> idToCollection = GetIdToCollection(argZero, configuration, argZeroIsConfigurationRootDirectory, containers, destinationRoot, preloadIds);
|
|
int duplicates = (from l in idToCollection where l.Value.Count > 1 select 1).Sum();
|
|
if (duplicates == 0)
|
|
logger?.LogInformation($"Found {duplicates} duplicate file(s)");
|
|
else
|
|
QuestionMove(ticks, logger, destinationRoot, idToCollection, duplicates);
|
|
}
|
|
}
|
|
|
|
private static void WriteIndexData(long ticks, Configuration configuration, string searchPattern)
|
|
{
|
|
string? alongSideDirectory;
|
|
string directoryName = Path.GetFileName(configuration.RootDirectory);
|
|
if (Path.GetPathRoot(configuration.RootDirectory) == configuration.RootDirectory)
|
|
alongSideDirectory = configuration.RootDirectory;
|
|
else
|
|
alongSideDirectory = Path.GetDirectoryName(configuration.RootDirectory);
|
|
if (alongSideDirectory is null)
|
|
throw new NullReferenceException(nameof(alongSideDirectory));
|
|
IEnumerable<(string, string[])> files = Shared.Models.Stateless.Methods.IFileHolder.GetFiles(configuration.RootDirectory, searchPattern);
|
|
List<FileHolder> fileHolders = Shared.Models.Stateless.Methods.IFileHolder.GetFileHolders(files.ToArray());
|
|
File.WriteAllLines(Path.Combine(alongSideDirectory, $"{directoryName}-{ticks}.tsv"), fileHolders.Select(l => l.FullName));
|
|
string json = JsonSerializer.Serialize(fileHolders);
|
|
File.WriteAllText(Path.Combine(alongSideDirectory, $"{directoryName}-{ticks}.json"), json);
|
|
}
|
|
|
|
private static Container.Models.Container[] GetContainers(long ticks, Configuration configuration)
|
|
{
|
|
int f;
|
|
Container.Models.Container[] containers;
|
|
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
|
string message = $") Building Container(s) - {totalSeconds} total second(s)";
|
|
ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
|
|
using (ProgressBar progressBar = new(1, message, options))
|
|
{
|
|
progressBar.Tick();
|
|
(f, containers) = Container.Models.Stateless.Methods.IContainer.GetContainers(configuration);
|
|
}
|
|
return containers;
|
|
}
|
|
|
|
private static List<int> GetPreloadIds(string destinationRoot)
|
|
{
|
|
List<int> results = [];
|
|
string[] lines;
|
|
string preloadDirectory = Path.Combine(destinationRoot, "Preload");
|
|
if (!Directory.Exists(preloadDirectory))
|
|
_ = Directory.CreateDirectory(preloadDirectory);
|
|
string[] files = Directory.GetFiles(preloadDirectory, "*.lsv", SearchOption.TopDirectoryOnly);
|
|
foreach (string file in files)
|
|
{
|
|
lines = File.ReadAllLines(file);
|
|
foreach (string line in lines)
|
|
{
|
|
if (string.IsNullOrEmpty(line) || !int.TryParse(line, out int id) || id == 0)
|
|
continue;
|
|
results.Add(id);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static Dictionary<int, List<MappingFromItem?>> GetIdToCollection(string argZero, Configuration configuration, bool argZeroIsConfigurationRootDirectory, Container.Models.Container[] containers, string destinationRoot, List<int> preloadIds)
|
|
{
|
|
Dictionary<int, List<MappingFromItem?>> results = [];
|
|
string? model;
|
|
string directory;
|
|
const int zero = 0;
|
|
DateTime? dateTime;
|
|
FileHolder resizedFileHolder;
|
|
DateTime[] containerDateTimes;
|
|
MappingFromItem? mappingFromItem;
|
|
List<MappingFromItem?>? collection;
|
|
ReadOnlyCollection<string> keywords;
|
|
ReadOnlyCollection<Item> validImageItems;
|
|
const string duplicates = "-Duplicate(s)";
|
|
if (containers.Length != 0)
|
|
{
|
|
foreach (int id in preloadIds)
|
|
results.Add(id, [null]);
|
|
}
|
|
foreach (Container.Models.Container container in containers)
|
|
{
|
|
if (container.Items.Count == 0)
|
|
continue;
|
|
if (!argZeroIsConfigurationRootDirectory && !container.SourceDirectory.StartsWith(argZero))
|
|
continue;
|
|
validImageItems = Container.Models.Stateless.Methods.IContainer.GetValidImageItems(configuration, container);
|
|
if (validImageItems.Count == 0)
|
|
continue;
|
|
containerDateTimes = Container.Models.Stateless.Methods.IContainer.GetContainerDateTimes(validImageItems);
|
|
foreach (Item item in validImageItems)
|
|
{
|
|
if (item.ExifDirectory?.FilePath?.Id is null)
|
|
{
|
|
if (int.TryParse(item.FilePath.NameWithoutExtension, out int id))
|
|
continue;
|
|
continue;
|
|
}
|
|
if (!results.TryGetValue(item.ExifDirectory.FilePath.Id.Value, out collection))
|
|
results.Add(item.ExifDirectory.FilePath.Id.Value, []);
|
|
if (collection is null && !results.TryGetValue(item.ExifDirectory.FilePath.Id.Value, out collection))
|
|
continue;
|
|
if (collection.Count == 0)
|
|
directory = $"0{duplicates}";
|
|
else
|
|
directory = $"{collection.Count + 1}{duplicates}";
|
|
if (collection.Count == 1)
|
|
{
|
|
mappingFromItem = collection[zero];
|
|
if (mappingFromItem is not null)
|
|
{
|
|
model = IMetaBase.GetModel(item.ExifDirectory);
|
|
dateTime = IDate.GetDateTimeOriginal(item.ExifDirectory);
|
|
keywords = IMetaBase.GetKeywords(item.ExifDirectory?.ExifBaseDirectories);
|
|
resizedFileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(mappingFromItem.ResizedFileHolder.FullName.Replace($"0{duplicates}", $"1{duplicates}"));
|
|
collection[0] = new(mappingFromItem.ContainerDateTimes, dateTime, mappingFromItem.Id, mappingFromItem.IsArchive, mappingFromItem.FilePath, mappingFromItem.IsWrongYear, keywords, mappingFromItem.MinimumDateTime, model, mappingFromItem.RelativePath, resizedFileHolder);
|
|
}
|
|
}
|
|
resizedFileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(string.Concat(Path.Combine(destinationRoot, directory), item.RelativePath));
|
|
mappingFromItem = IMappingFromItem.GetMappingFromItem(containerDateTimes, item, resizedFileHolder);
|
|
collection.Add(mappingFromItem);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static void QuestionMove(long ticks, ILogger<Program>? logger, string destinationRoot, Dictionary<int, List<MappingFromItem?>> idToCollection, int duplicates)
|
|
{
|
|
int[] ids = (from l in idToCollection orderby l.Key where l.Value.Any(m => m is not null) select l.Key).ToArray();
|
|
_ = Shared.Models.Stateless.Methods.IPath.WriteAllText(Path.Combine(destinationRoot, $"{ticks}-id(s).lsv"), string.Join(Environment.NewLine, ids), updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
|
|
string json = JsonSerializer.Serialize(idToCollection, new JsonSerializerOptions { WriteIndented = true });
|
|
_ = Shared.Models.Stateless.Methods.IPath.WriteAllText(Path.Combine(destinationRoot, $"{ticks}.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
|
|
logger?.LogInformation($"Found {duplicates} duplicate file(s)");
|
|
for (int y = 0; y < int.MaxValue; y++)
|
|
{
|
|
logger?.LogInformation("Press \"Y\" key to continue or close console to leave them moved");
|
|
if (System.Console.ReadKey().Key != ConsoleKey.Y)
|
|
continue;
|
|
logger?.LogInformation(". . .");
|
|
List<(FilePath FilePath, string Destination)> collection = GetCollectionAndCreateDirectories(idToCollection);
|
|
Move(logger, ticks, destinationRoot, collection);
|
|
}
|
|
}
|
|
|
|
private static List<(FilePath FilePath, string Destination)> GetCollectionAndCreateDirectories(Dictionary<int, List<MappingFromItem?>> idToCollection)
|
|
{
|
|
List<(FilePath FilePath, string Destination)> results = [];
|
|
List<string> collection = [];
|
|
foreach (KeyValuePair<int, List<MappingFromItem?>> keyValuePair in idToCollection)
|
|
{
|
|
foreach (MappingFromItem? mappingFromItem in keyValuePair.Value)
|
|
{
|
|
if (mappingFromItem?.ResizedFileHolder.DirectoryFullPath is null)
|
|
continue;
|
|
if (mappingFromItem.ResizedFileHolder.Exists)
|
|
continue;
|
|
collection.Add(mappingFromItem.ResizedFileHolder.DirectoryFullPath);
|
|
results.Add(new(mappingFromItem.FilePath, mappingFromItem.ResizedFileHolder.FullName));
|
|
}
|
|
}
|
|
foreach (string directory in collection.Distinct())
|
|
{
|
|
if (!Directory.Exists(directory))
|
|
_ = Directory.CreateDirectory(directory);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static void Move(ILogger<Program>? logger, long ticks, string destinationRoot, List<(FilePath FilePath, string Destination)> collection)
|
|
{
|
|
StringBuilder stringBuilder = new();
|
|
foreach ((FilePath fileHolder, string destination) in collection)
|
|
{
|
|
_ = stringBuilder.AppendLine(fileHolder.FullName);
|
|
_ = stringBuilder.AppendLine(destination);
|
|
}
|
|
_ = Shared.Models.Stateless.Methods.IPath.WriteAllText(Path.Combine(destinationRoot, $"{ticks}file(s).lsv"), stringBuilder.ToString(), updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null);
|
|
logger?.LogInformation($"Ready to move {collection.Count} file(s)?");
|
|
for (int y = 0; y < int.MaxValue; y++)
|
|
{
|
|
logger?.LogInformation("Press \"Y\" key to move file(s) or close console to not move files");
|
|
if (System.Console.ReadKey().Key == ConsoleKey.Y)
|
|
break;
|
|
}
|
|
logger?.LogInformation(". . .");
|
|
foreach ((FilePath filePath, string destination) in collection)
|
|
{
|
|
try
|
|
{ File.Move(filePath.FullName, destination); }
|
|
catch (Exception exception)
|
|
{ logger?.LogError(exception, $"Failed to move <{filePath.FullName}>"); }
|
|
}
|
|
logger?.LogInformation($"{collection.Count} file(s) moved");
|
|
for (int y = 0; y < int.MaxValue; y++)
|
|
{
|
|
logger?.LogInformation("Press \"Y\" key to move file(s) back or close console to leave them moved");
|
|
if (System.Console.ReadKey().Key != ConsoleKey.Y)
|
|
continue;
|
|
logger?.LogInformation(". . .");
|
|
foreach ((FilePath filePath, string destination) in collection)
|
|
{
|
|
if (!File.Exists(destination))
|
|
continue;
|
|
if (File.Exists(filePath.FullName))
|
|
continue;
|
|
try
|
|
{ File.Move(destination, filePath.FullName); }
|
|
catch (Exception exception)
|
|
{ logger?.LogError(exception, $"Failed to move <{destination}>"); }
|
|
}
|
|
}
|
|
logger?.LogInformation(". . .");
|
|
}
|
|
|
|
} |