This commit is contained in:
Mike Phares 2024-05-18 17:06:47 -07:00
parent abbe2feac0
commit 30b8e2f5a9
10 changed files with 326 additions and 170 deletions

View File

@ -82,28 +82,50 @@ csharp_style_var_elsewhere = false:warning
csharp_style_var_for_built_in_types = false:warning csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = false:warning csharp_style_var_when_type_is_apparent = false:warning
csharp_using_directive_placement = outside_namespace csharp_using_directive_placement = outside_namespace
dotnet_analyzer_diagnostic.category-Design.severity = error
dotnet_analyzer_diagnostic.category-Documentation.severity = error
dotnet_analyzer_diagnostic.category-Globalization.severity = none
dotnet_analyzer_diagnostic.category-Interoperability.severity = error
dotnet_analyzer_diagnostic.category-Maintainability.severity = error
dotnet_analyzer_diagnostic.category-Naming.severity = none
dotnet_analyzer_diagnostic.category-Performance.severity = none
dotnet_analyzer_diagnostic.category-Reliability.severity = error
dotnet_analyzer_diagnostic.category-Security.severity = error
dotnet_analyzer_diagnostic.category-SingleFile.severity = error
dotnet_analyzer_diagnostic.category-Style.severity = error
dotnet_analyzer_diagnostic.category-Usage.severity = error
dotnet_code_quality_unused_parameters = all dotnet_code_quality_unused_parameters = all
dotnet_code_quality_unused_parameters = non_public # IDE0060: Remove unused parameter dotnet_code_quality_unused_parameters = non_public # IDE0060: Remove unused parameter
dotnet_code_quality.CAXXXX.api_surface = private, internal dotnet_code_quality.CAXXXX.api_surface = private, internal
dotnet_diagnostic.CA1001.severity = error # CA1001: Types that own disposable fields should be disposable
dotnet_diagnostic.CA1051.severity = error # CA1051: Do not declare visible instance fields
dotnet_diagnostic.CA1825.severity = warning # CA1823: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning # CA1823: Avoid zero-length array allocations
dotnet_diagnostic.CA1829.severity = warning # CA1829: Use Length/Count property instead of Count() when available dotnet_diagnostic.CA1829.severity = warning # CA1829: Use Length/Count property instead of Count() when available
dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable
dotnet_diagnostic.CA1860.severity = error # CA1860: Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance dotnet_diagnostic.CA1860.severity = error # CA1860: Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance
dotnet_diagnostic.CA1869.severity = none # CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. dotnet_diagnostic.CA1861.severity = error # CA1861: Prefer 'static readonly' fields over constant array arguments
dotnet_diagnostic.CA2254.severity = none # CA2254: The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])' dotnet_diagnostic.CA1869.severity = error # CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead.
dotnet_diagnostic.CA2201.severity = none # CA2201: Exception type System.NullReferenceException is reserved by the runtime
dotnet_diagnostic.CA2254.severity = error # CA2254: The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])'
dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name
dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2"); dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2");
dotnet_diagnostic.IDE0004.severity = warning # IDE0004: Cast is redundant. dotnet_diagnostic.IDE0004.severity = warning # IDE0004: Cast is redundant.
dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary
dotnet_diagnostic.IDE0010.severity = error # Add missing cases to switch statement (IDE0010)
dotnet_diagnostic.IDE0028.severity = error # IDE0028: Collection initialization can be simplified dotnet_diagnostic.IDE0028.severity = error # IDE0028: Collection initialization can be simplified
dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031) dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031)
dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed
dotnet_diagnostic.IDE0048.severity = error # Parentheses preferences (IDE0047 and IDE0048)
dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references (IDE0049) dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references (IDE0049)
dotnet_diagnostic.IDE0051.severity = error # Private member '' is unused [, ]
dotnet_diagnostic.IDE0060.severity = warning # IDE0060: Remove unused parameter dotnet_diagnostic.IDE0060.severity = warning # IDE0060: Remove unused parameter
dotnet_diagnostic.IDE0290.severity = none # Use primary constructor [Distance]csharp(IDE0290) dotnet_diagnostic.IDE0130.severity = error # Namespace does not match folder structure (IDE0130)
dotnet_diagnostic.IDE0230.severity = warning # IDE0230: Use UTF-8 string literal
dotnet_diagnostic.IDE0290.severity = error # Use primary constructor [Distance]csharp(IDE0290)
dotnet_diagnostic.IDE0300.severity = error # IDE0300: Collection initialization can be simplified dotnet_diagnostic.IDE0300.severity = error # IDE0300: Collection initialization can be simplified
dotnet_diagnostic.IDE0301.severity = error #IDE0301: Collection initialization can be simplified dotnet_diagnostic.IDE0301.severity = error #IDE0301: Collection initialization can be simplified
dotnet_diagnostic.IDE0305.severity = none # IDE0305: Collection initialization can be simplified dotnet_diagnostic.IDE0305.severity = none # IDE0305: Collection initialization can be simplified
dotnet_diagnostic.JSON002.severity = warning # JSON002: Probable JSON string detected
dotnet_naming_rule.abstract_method_should_be_pascal_case.severity = warning dotnet_naming_rule.abstract_method_should_be_pascal_case.severity = warning
dotnet_naming_rule.abstract_method_should_be_pascal_case.style = pascal_case dotnet_naming_rule.abstract_method_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.abstract_method_should_be_pascal_case.symbols = abstract_method dotnet_naming_rule.abstract_method_should_be_pascal_case.symbols = abstract_method

View File

@ -2,7 +2,6 @@ using System.Collections.ObjectModel;
using System.Text.Json; using System.Text.Json;
using View_by_Distance.Metadata.Models.Stateless; using View_by_Distance.Metadata.Models.Stateless;
using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Properties;
using View_by_Distance.Shared.Models.Stateless.Methods; using View_by_Distance.Shared.Models.Stateless.Methods;
namespace View_by_Distance.Metadata.Models; namespace View_by_Distance.Metadata.Models;
@ -132,42 +131,4 @@ public class A_Metadata
return (fileInfo, result); return (fileInfo, result);
} }
public static Action<string> SetExifDirectoryCollection(IRename rename, IRenameConfiguration renameConfiguration, A_Metadata metadata, List<(FilePath, FileInfo, ExifDirectory)> exifDirectories)
{
return file =>
{
rename.Tick();
FileInfo fileInfo;
FilePath? ffmpegFilePath;
ExifDirectory exifDirectory;
FileHolder fileHolder = new(file);
ReadOnlyCollection<string>? ffmpegFiles;
DeterministicHashCode deterministicHashCode;
FilePath filePath = FilePath.Get(renameConfiguration.MetadataConfiguration, fileHolder, index: null);
if (!renameConfiguration.SkipIdFiles || filePath.Id is null || !filePath.IsIntelligentIdFormat && filePath.SortOrder is not null)
{
if (filePath.Id is not null)
{
ffmpegFiles = null;
deterministicHashCode = new(null, filePath.Id, null);
}
else
{
ffmpegFiles = rename.ConvertAndGetFfmpegFiles(renameConfiguration, filePath);
ffmpegFilePath = ffmpegFiles.Count == 0 ? null : FilePath.Get(renameConfiguration.MetadataConfiguration, new(ffmpegFiles[0]), index: null);
deterministicHashCode = ffmpegFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(ffmpegFilePath);
}
(fileInfo, exifDirectory) = metadata.GetMetadataCollection(renameConfiguration.MetadataConfiguration, filePath, deterministicHashCode);
lock (exifDirectories)
exifDirectories.Add(new(filePath, fileInfo, exifDirectory));
if (ffmpegFiles is not null)
{
foreach (string ffmpegFile in ffmpegFiles)
File.Delete(ffmpegFile);
}
}
};
}
} }

View File

@ -0,0 +1,75 @@
using System.Collections.ObjectModel;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Properties;
using View_by_Distance.Shared.Models.Stateless.Methods;
namespace View_by_Distance.Metadata.Models.Stateless.Methods;
internal static class Get
{
internal static ReadOnlyDictionary<string, List<FileHolder>> GetKeyValuePairs(IEnumerable<string> files)
{
Dictionary<string, List<FileHolder>> results = [];
string key;
FileHolder fileHolder;
List<FileHolder>? fileHolders;
foreach (string file in files)
{
fileHolder = FileHolder.Get(file);
if (fileHolder.DirectoryName is null)
continue;
key = $"{Path.Combine(fileHolder.DirectoryName, fileHolder.NameWithoutExtension)}";
if (!results.TryGetValue(key, out fileHolders))
{
results.Add(key, []);
if (!results.TryGetValue(key, out fileHolders))
throw new NotImplementedException();
}
fileHolders.Add(fileHolder);
}
return new(results);
}
internal static Action<string> SetExifDirectoryCollection(IRename rename, IRenameConfiguration renameConfiguration, A_Metadata metadata, List<string> distinct, List<(FilePath, FileInfo, ExifDirectory, ReadOnlyCollection<FileHolder>)> collection)
{
return file =>
{
rename.Tick();
FileInfo fileInfo;
FilePath? ffmpegFilePath;
ExifDirectory exifDirectory;
ReadOnlyCollection<string>? ffmpegFiles;
DeterministicHashCode deterministicHashCode;
FileHolder fileHolder = FileHolder.Get(file);
FilePath filePath = FilePath.Get(renameConfiguration.MetadataConfiguration, fileHolder, index: null);
string key = $"{Path.Combine(fileHolder.DirectoryName ?? throw new NotSupportedException(), fileHolder.NameWithoutExtension)}";
if (distinct.Contains(key))
throw new NotSupportedException("Turn off parallelism when sidecar files are present!");
if (!renameConfiguration.SkipIdFiles || filePath.Id is null || !filePath.IsIntelligentIdFormat && filePath.SortOrder is not null)
{
if (filePath.Id is not null)
{
ffmpegFiles = null;
deterministicHashCode = new(null, filePath.Id, null);
}
else
{
ffmpegFiles = rename.ConvertAndGetFfmpegFiles(renameConfiguration, filePath);
ffmpegFilePath = ffmpegFiles.Count == 0 ? null : FilePath.Get(renameConfiguration.MetadataConfiguration, FileHolder.Get(ffmpegFiles[0]), index: null);
deterministicHashCode = ffmpegFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(ffmpegFilePath);
}
(fileInfo, exifDirectory) = metadata.GetMetadataCollection(renameConfiguration.MetadataConfiguration, filePath, deterministicHashCode);
lock (collection)
collection.Add(new(filePath, fileInfo, exifDirectory, new([])));
if (ffmpegFiles is not null)
{
foreach (string ffmpegFile in ffmpegFiles)
File.Delete(ffmpegFile);
}
}
};
}
}

View File

@ -1,6 +1,8 @@
using MetadataExtractor; using MetadataExtractor;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Properties;
using View_by_Distance.Shared.Models.Stateless.Methods;
namespace View_by_Distance.Metadata.Models.Stateless.Methods; namespace View_by_Distance.Metadata.Models.Stateless.Methods;
@ -55,4 +57,14 @@ public interface IMetadata
static double? GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, DistanceUnit distanceUnit = DistanceUnit.Miles) => static double? GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, DistanceUnit distanceUnit = DistanceUnit.Miles) =>
GPS.GetDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude, decimalPlaces, distanceUnit); GPS.GetDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude, decimalPlaces, distanceUnit);
Action<string> TestStatic_SetExifDirectoryCollection(IRename rename, IRenameConfiguration renameConfiguration, A_Metadata metadata, List<string> distinct, List<(FilePath, FileInfo, ExifDirectory, ReadOnlyCollection<FileHolder>)> collection) =>
SetExifDirectoryCollection(rename, renameConfiguration, metadata, distinct, collection);
static Action<string> SetExifDirectoryCollection(IRename rename, IRenameConfiguration renameConfiguration, A_Metadata metadata, List<string> distinct, List<(FilePath, FileInfo, ExifDirectory, ReadOnlyCollection<FileHolder>)> collection) =>
Get.SetExifDirectoryCollection(rename, renameConfiguration, metadata, distinct, collection);
ReadOnlyDictionary<string, List<FileHolder>> TestStatic_GetKeyValuePairs(IEnumerable<string> files) =>
GetKeyValuePairs(files);
static ReadOnlyDictionary<string, List<FileHolder>> GetKeyValuePairs(IEnumerable<string> files) =>
Get.GetKeyValuePairs(files);
} }

View File

@ -11,6 +11,7 @@ public class RenameConfiguration
public bool? ForceNewId { get; set; } public bool? ForceNewId { get; set; }
public string[]? IgnoreExtensions { get; set; } public string[]? IgnoreExtensions { get; set; }
public string? RelativePropertyCollectionFile { get; set; } public string? RelativePropertyCollectionFile { get; set; }
public string[]? SidecarExtensions { get; set; }
public bool? SkipIdFiles { get; set; } public bool? SkipIdFiles { get; set; }
public string[]? ValidImageFormatExtensions { get; set; } public string[]? ValidImageFormatExtensions { get; set; }
public string[]? ValidVideoFormatExtensions { get; set; } public string[]? ValidVideoFormatExtensions { get; set; }
@ -53,6 +54,7 @@ public class RenameConfiguration
if (configuration.ForceNewId is null) throw new NullReferenceException(nameof(configuration.ForceNewId)); if (configuration.ForceNewId is null) throw new NullReferenceException(nameof(configuration.ForceNewId));
if (configuration.IgnoreExtensions is null) throw new NullReferenceException(nameof(configuration.IgnoreExtensions)); if (configuration.IgnoreExtensions is null) throw new NullReferenceException(nameof(configuration.IgnoreExtensions));
if (configuration.RelativePropertyCollectionFile is null) throw new NullReferenceException(nameof(configuration.RelativePropertyCollectionFile)); if (configuration.RelativePropertyCollectionFile is null) throw new NullReferenceException(nameof(configuration.RelativePropertyCollectionFile));
if (configuration.SidecarExtensions is null) throw new NullReferenceException(nameof(configuration.SidecarExtensions));
if (configuration.SkipIdFiles is null) throw new NullReferenceException(nameof(configuration.SkipIdFiles)); if (configuration.SkipIdFiles is null) throw new NullReferenceException(nameof(configuration.SkipIdFiles));
if (configuration.ValidImageFormatExtensions is null) throw new NullReferenceException(nameof(configuration.ValidImageFormatExtensions)); if (configuration.ValidImageFormatExtensions is null) throw new NullReferenceException(nameof(configuration.ValidImageFormatExtensions));
if (configuration.ValidVideoFormatExtensions is null) throw new NullReferenceException(nameof(configuration.ValidVideoFormatExtensions)); if (configuration.ValidVideoFormatExtensions is null) throw new NullReferenceException(nameof(configuration.ValidVideoFormatExtensions));
@ -62,6 +64,7 @@ public class RenameConfiguration
configuration.ForceNewId.Value, configuration.ForceNewId.Value,
configuration.IgnoreExtensions, configuration.IgnoreExtensions,
configuration.RelativePropertyCollectionFile, configuration.RelativePropertyCollectionFile,
configuration.SidecarExtensions,
configuration.SkipIdFiles.Value, configuration.SkipIdFiles.Value,
configuration.ValidImageFormatExtensions, configuration.ValidImageFormatExtensions,
configuration.ValidVideoFormatExtensions); configuration.ValidVideoFormatExtensions);

View File

@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace View_by_Distance.Rename.Models; namespace View_by_Distance.Rename.Models;
internal record Identifier(int Id, string PaddedId) internal sealed record Identifier(int Id, string PaddedId)
{ {
public override string ToString() public override string ToString()

View File

@ -9,6 +9,7 @@ public record RenameConfiguration(Shared.Models.MetadataConfiguration MetadataCo
bool ForceNewId, bool ForceNewId,
string[] IgnoreExtensions, string[] IgnoreExtensions,
string RelativePropertyCollectionFile, string RelativePropertyCollectionFile,
string[] SidecarExtensions,
bool SkipIdFiles, bool SkipIdFiles,
string[] ValidImageFormatExtensions, string[] ValidImageFormatExtensions,
string[] ValidVideoFormatExtensions) : Shared.Models.Properties.IRenameConfiguration string[] ValidVideoFormatExtensions) : Shared.Models.Properties.IRenameConfiguration

View File

@ -17,11 +17,14 @@ using View_by_Distance.Shared.Models.Stateless.Methods;
namespace View_by_Distance.Rename; namespace View_by_Distance.Rename;
public partial class Rename : IRename public partial class Rename : IRename, IDisposable
{ {
private record ToDo(string? Directory, FilePath FilePath, string File, bool JsonFile); private sealed record ToDo(string? Directory, FilePath FilePath, string File, bool JsonFile);
private record Record(DateTime DateTime, ExifDirectory ExifDirectory, FilePath FilePath, bool HasDateTimeOriginal, bool HasIgnoreKeyword, string JsonFile);
private sealed record RecordA(ExifDirectory ExifDirectory, FileInfo FileInfo, FilePath FilePath, ReadOnlyCollection<FileHolder> SidecarFiles);
private sealed record RecordB(DateTime DateTime, ExifDirectory ExifDirectory, FilePath FilePath, ReadOnlyCollection<FileHolder> SidecarFiles, bool HasDateTimeOriginal, bool HasIgnoreKeyword, string JsonFile);
private ProgressBar? _ProgressBar; private ProgressBar? _ProgressBar;
@ -44,6 +47,12 @@ public partial class Rename : IRename
void IRename.Tick() => void IRename.Tick() =>
_ProgressBar?.Tick(); _ProgressBar?.Tick();
void IDisposable.Dispose()
{
_ProgressBar?.Dispose();
GC.SuppressFinalize(this);
}
ReadOnlyCollection<string> IRename.ConvertAndGetFfmpegFiles(IRenameConfiguration renameConfiguration, FilePath filePath) ReadOnlyCollection<string> IRename.ConvertAndGetFfmpegFiles(IRenameConfiguration renameConfiguration, FilePath filePath)
{ {
List<string> results = []; List<string> results = [];
@ -114,10 +123,11 @@ public partial class Rename : IRename
private static void RenameUrl(FilePath filePath) private static void RenameUrl(FilePath filePath)
{ {
FileHolder fileHolder;
string[] lines = File.ReadAllLines(filePath.FullName); string[] lines = File.ReadAllLines(filePath.FullName);
if (lines.Length == 1) if (lines.Length == 1)
{ {
FileHolder fileHolder = new(lines[0]); fileHolder = FileHolder.Get(lines[0]);
if (fileHolder.Exists) if (fileHolder.Exists)
{ {
string checkFile; string checkFile;
@ -130,22 +140,30 @@ public partial class Rename : IRename
} }
} }
private static List<(FilePath, FileInfo, ExifDirectory)> GetExifDirectoryCollection(IRename rename, RenameConfiguration renameConfiguration, IEnumerable<string> files, A_Metadata metadata) private static List<RecordA> GetRecordACollection(IRename rename, RenameConfiguration renameConfiguration, IEnumerable<string> files, A_Metadata metadata)
{ {
List<(FilePath, FileInfo, ExifDirectory)> results = []; List<RecordA> results = [];
int index = -1; int index = -1;
FileInfo fileInfo; FileInfo fileInfo;
FilePath filePath; FilePath filePath;
FileHolder fileHolder;
FilePath? ffmpegFilePath; FilePath? ffmpegFilePath;
ExifDirectory exifDirectory; ExifDirectory exifDirectory;
List<FileHolder> sidecarFiles;
ReadOnlyCollection<string>? ffmpegFiles; ReadOnlyCollection<string>? ffmpegFiles;
DeterministicHashCode deterministicHashCode; DeterministicHashCode deterministicHashCode;
foreach (string file in files) ReadOnlyDictionary<string, List<FileHolder>> keyValuePairs = IMetadata.GetKeyValuePairs(files);
foreach (KeyValuePair<string, List<FileHolder>> keyValuePair in keyValuePairs)
{ {
index += 1; index += 1;
rename.Tick(); rename.Tick();
fileHolder = new(file); if (keyValuePair.Value.Count > 1 && !renameConfiguration.ForceNewId)
throw new NotSupportedException($"When sidecar files are present {nameof(renameConfiguration.ForceNewId)} must be true!");
if (keyValuePair.Value.Count > 2)
throw new NotSupportedException("Too many sidecar files!");
foreach (FileHolder fileHolder in keyValuePair.Value)
{
if (renameConfiguration.SidecarExtensions.Contains(fileHolder.ExtensionLowered))
continue;
if (renameConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered)) if (renameConfiguration.IgnoreExtensions.Contains(fileHolder.ExtensionLowered))
continue; continue;
filePath = FilePath.Get(renameConfiguration.MetadataConfiguration, fileHolder, index); filePath = FilePath.Get(renameConfiguration.MetadataConfiguration, fileHolder, index);
@ -164,65 +182,77 @@ public partial class Rename : IRename
else else
{ {
ffmpegFiles = rename.ConvertAndGetFfmpegFiles(renameConfiguration, filePath); ffmpegFiles = rename.ConvertAndGetFfmpegFiles(renameConfiguration, filePath);
ffmpegFilePath = ffmpegFiles.Count == 0 ? null : FilePath.Get(renameConfiguration.MetadataConfiguration, new(ffmpegFiles[0]), index); ffmpegFilePath = ffmpegFiles.Count == 0 ? null : FilePath.Get(renameConfiguration.MetadataConfiguration, FileHolder.Get(ffmpegFiles[0]), index);
deterministicHashCode = ffmpegFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(ffmpegFilePath); deterministicHashCode = ffmpegFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(ffmpegFilePath);
} }
sidecarFiles = [];
for (int i = 0; i < keyValuePair.Value.Count; i++)
{
if (keyValuePair.Value[i].ExtensionLowered == fileHolder.ExtensionLowered)
continue;
sidecarFiles.Add(keyValuePair.Value[i]);
}
(fileInfo, exifDirectory) = metadata.GetMetadataCollection(renameConfiguration.MetadataConfiguration, filePath, deterministicHashCode); (fileInfo, exifDirectory) = metadata.GetMetadataCollection(renameConfiguration.MetadataConfiguration, filePath, deterministicHashCode);
results.Add(new(filePath, fileInfo, exifDirectory)); results.Add(new(exifDirectory, fileInfo, filePath, new(sidecarFiles)));
if (ffmpegFiles is not null) if (ffmpegFiles is not null)
{ {
foreach (string ffmpegFile in ffmpegFiles) foreach (string ffmpegFile in ffmpegFiles)
File.Delete(ffmpegFile); File.Delete(ffmpegFile);
} }
} }
}
return results; return results;
} }
private static ReadOnlyCollection<Record> GetExifDirectoryCollection(MetadataConfiguration metadataConfiguration, List<(FilePath, FileInfo, ExifDirectory)> exifDirectories) private static ReadOnlyCollection<RecordB> GetRecordBCollection(MetadataConfiguration metadataConfiguration, List<RecordA> recordACollection)
{ {
List<Record> results = []; List<RecordB> results = [];
DateTime? dateTime; DateTime? dateTime;
bool hasIgnoreKeyword; bool hasIgnoreKeyword;
bool hasDateTimeOriginal; bool hasDateTimeOriginal;
ReadOnlyCollection<string> keywords; ReadOnlyCollection<string> keywords;
foreach ((FilePath filePath, FileInfo fileInfo, ExifDirectory exifDirectory) in exifDirectories) foreach (RecordA recordA in recordACollection)
{ {
dateTime = IDate.GetDateTimeOriginal(exifDirectory); dateTime = IDate.GetDateTimeOriginal(recordA.ExifDirectory);
hasDateTimeOriginal = dateTime is not null; hasDateTimeOriginal = dateTime is not null;
dateTime ??= IDate.GetMinimum(exifDirectory); dateTime ??= IDate.GetMinimum(recordA.ExifDirectory);
keywords = IMetadata.GetKeywords(exifDirectory); keywords = IMetadata.GetKeywords(recordA.ExifDirectory);
hasIgnoreKeyword = metadataConfiguration.IgnoreRulesKeyWords.Any(l => keywords.Contains(l)); hasIgnoreKeyword = metadataConfiguration.IgnoreRulesKeyWords.Any(l => keywords.Contains(l));
results.Add(new(dateTime.Value, exifDirectory, filePath, hasDateTimeOriginal, hasIgnoreKeyword, fileInfo.FullName)); results.Add(new(dateTime.Value, recordA.ExifDirectory, recordA.FilePath, recordA.SidecarFiles, hasDateTimeOriginal, hasIgnoreKeyword, recordA.FileInfo.FullName));
} }
return new(results); return new(results);
} }
private ReadOnlyCollection<Record> GetExifDirectoryCollection(IRename rename, AppSettings appSettings, RenameConfiguration renameConfiguration, DirectoryInfo directoryInfo) private ReadOnlyCollection<RecordB> GetRecordBCollection(IRename rename, AppSettings appSettings, RenameConfiguration renameConfiguration, DirectoryInfo directoryInfo)
{ {
ReadOnlyCollection<Record> results; ReadOnlyCollection<RecordB> results;
List<(FilePath, FileInfo, ExifDirectory)> exifDirectories = []; List<RecordA> recordACollection = [];
A_Metadata metadata = new(renameConfiguration.MetadataConfiguration); A_Metadata metadata = new(renameConfiguration.MetadataConfiguration);
int appSettingsMaxDegreeOfParallelism = appSettings.MaxDegreeOfParallelism; int appSettingsMaxDegreeOfParallelism = appSettings.MaxDegreeOfParallelism;
IEnumerable<string> files = appSettingsMaxDegreeOfParallelism == 1 ? Directory.GetFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories); IEnumerable<string> files = appSettingsMaxDegreeOfParallelism == 1 ? Directory.GetFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories);
int filesCount = appSettingsMaxDegreeOfParallelism == 1 ? files.Count() : 123000; int filesCount = appSettingsMaxDegreeOfParallelism == 1 ? files.Count() : 123000;
_ProgressBar = new(filesCount, "EnumerateFiles load", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); _ProgressBar = new(filesCount, "EnumerateFiles load", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true });
if (appSettingsMaxDegreeOfParallelism == 1) if (appSettingsMaxDegreeOfParallelism == 1)
exifDirectories.AddRange(GetExifDirectoryCollection(rename, renameConfiguration, files, metadata)); recordACollection.AddRange(GetRecordACollection(rename, renameConfiguration, files, metadata));
else else
{ {
List<string> distinct = [];
List<(FilePath, FileInfo, ExifDirectory, ReadOnlyCollection<FileHolder>)> collection = [];
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism }; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism };
files.AsParallel().ForAll(A_Metadata.SetExifDirectoryCollection(rename, renameConfiguration, metadata, exifDirectories)); files.AsParallel().ForAll(IMetadata.SetExifDirectoryCollection(rename, renameConfiguration, metadata, distinct, collection));
if (_ProgressBar.CurrentTick != exifDirectories.Count) if (_ProgressBar.CurrentTick != recordACollection.Count)
throw new NotSupportedException(); throw new NotSupportedException();
foreach ((FilePath filePath, FileInfo fileInfo, ExifDirectory exifDirectory, ReadOnlyCollection<FileHolder> sidecarFiles) in collection)
recordACollection.Add(new(exifDirectory, fileInfo, filePath, sidecarFiles));
} }
_ProgressBar.Dispose(); _ProgressBar.Dispose();
results = GetExifDirectoryCollection(renameConfiguration.MetadataConfiguration, exifDirectories); results = GetRecordBCollection(renameConfiguration.MetadataConfiguration, recordACollection);
return results; return results;
} }
private static void VerifyIntMinValueLength(MetadataConfiguration metadataConfiguration, ReadOnlyCollection<Record> exifDirectories) private static void VerifyIntMinValueLength(MetadataConfiguration metadataConfiguration, ReadOnlyCollection<RecordB> recordBCollection)
{ {
foreach ((DateTime _, ExifDirectory exifDirectory, FilePath _, bool _, bool _, string _) in exifDirectories) foreach ((DateTime _, ExifDirectory exifDirectory, FilePath _, ReadOnlyCollection<FileHolder> _, bool _, bool _, string _) in recordBCollection)
{ {
if (exifDirectory.Id is null) if (exifDirectory.Id is null)
continue; continue;
@ -231,7 +261,7 @@ public partial class Rename : IRename
} }
} }
private static string? GetCheckDirectory(RenameConfiguration renameConfiguration, Record record, FilePath filePath, ReadOnlyCollection<int> ids, bool multipleDirectoriesWithFiles) private static string? GetCheckDirectory(RenameConfiguration renameConfiguration, RecordB record, FilePath filePath, ReadOnlyCollection<int> ids, bool multipleDirectoriesWithFiles)
{ {
string? result; string? result;
if (filePath.DirectoryName is null) if (filePath.DirectoryName is null)
@ -252,10 +282,37 @@ public partial class Rename : IRename
return result; return result;
} }
private static ReadOnlyCollection<ToDo> GetToDoCollection(RenameConfiguration renameConfiguration, Identifier[]? identifiers, ReadOnlyCollection<Record> records) private static List<ToDo> GetSidecarFiles(MetadataConfiguration metadataConfiguration, RecordB record, List<string> distinct, string checkDirectory, string paddedId)
{ {
List<ToDo> results = []; List<ToDo> results = [];
Record record; string checkFile;
FilePath filePath;
string checkFileExtension;
foreach (FileHolder fileHolder in record.SidecarFiles)
{
checkFileExtension = fileHolder.ExtensionLowered;
filePath = FilePath.Get(metadataConfiguration, fileHolder, index: null);
checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}");
if (checkFile == filePath.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(checkDirectory, filePath, checkFile, JsonFile: false));
}
return results;
}
private static ReadOnlyCollection<ToDo> GetToDoCollection(RenameConfiguration renameConfiguration, Identifier[]? identifiers, ReadOnlyCollection<RecordB> recordBCollection)
{
List<ToDo> results = [];
RecordB record;
string jsonFile; string jsonFile;
string paddedId; string paddedId;
string checkFile; string checkFile;
@ -265,11 +322,11 @@ public partial class Rename : IRename
string? checkDirectory; string? checkDirectory;
const string jpg = ".jpg"; const string jpg = ".jpg";
string checkFileExtension; string checkFileExtension;
bool multipleDirectoriesWithFiles;
List<string> distinct = []; List<string> distinct = [];
bool? directoryCheck = null; bool? directoryCheck = null;
const string jpeg = ".jpeg"; const string jpeg = ".jpeg";
string jsonFileSubDirectory; string jsonFileSubDirectory;
bool multipleDirectoriesWithFiles;
foreach (string directory in Directory.GetDirectories(renameConfiguration.MetadataConfiguration.ResultConfiguration.RootDirectory, "*", SearchOption.TopDirectoryOnly)) foreach (string directory in Directory.GetDirectories(renameConfiguration.MetadataConfiguration.ResultConfiguration.RootDirectory, "*", SearchOption.TopDirectoryOnly))
{ {
foreach (string _ in Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories)) foreach (string _ in Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories))
@ -283,14 +340,14 @@ public partial class Rename : IRename
if (directoryCheck is not null && directoryCheck.Value) if (directoryCheck is not null && directoryCheck.Value)
break; break;
} }
VerifyIntMinValueLength(renameConfiguration.MetadataConfiguration, records); VerifyIntMinValueLength(renameConfiguration.MetadataConfiguration, recordBCollection);
multipleDirectoriesWithFiles = directoryCheck is not null && directoryCheck.Value; multipleDirectoriesWithFiles = directoryCheck is not null && directoryCheck.Value;
ReadOnlyCollection<Record> collection = new((from l in records orderby l.DateTime select l).ToArray());
ResultConfiguration resultConfiguration = renameConfiguration.MetadataConfiguration.ResultConfiguration; ResultConfiguration resultConfiguration = renameConfiguration.MetadataConfiguration.ResultConfiguration;
ReadOnlyCollection<int> ids = identifiers is null ? new([]) : new((from l in identifiers select l.Id).ToArray()); ReadOnlyCollection<int> ids = identifiers is null ? new([]) : new((from l in identifiers select l.Id).ToArray());
for (int i = 0; i < collection.Count; i++) ReadOnlyCollection<RecordB> sorted = new((from l in recordBCollection orderby l.DateTime select l).ToArray());
for (int i = 0; i < sorted.Count; i++)
{ {
record = collection[i]; record = sorted[i];
if (record.ExifDirectory.Id is null) if (record.ExifDirectory.Id is null)
continue; continue;
if (record.FilePath.DirectoryName is null) if (record.FilePath.DirectoryName is null)
@ -314,7 +371,7 @@ public partial class Rename : IRename
jsonFile = Path.Combine(jsonFileSubDirectory, directoryName, $"{record.ExifDirectory.Id.Value}{checkFileExtension}.json"); jsonFile = Path.Combine(jsonFileSubDirectory, directoryName, $"{record.ExifDirectory.Id.Value}{checkFileExtension}.json");
if (record.JsonFile != jsonFile) if (record.JsonFile != jsonFile)
{ {
fileHolder = new(record.JsonFile); fileHolder = FileHolder.Get(record.JsonFile);
filePath = FilePath.Get(renameConfiguration.MetadataConfiguration, fileHolder, index: null); filePath = FilePath.Get(renameConfiguration.MetadataConfiguration, fileHolder, index: null);
results.Add(new(null, filePath, jsonFile, JsonFile: true)); results.Add(new(null, filePath, jsonFile, JsonFile: true));
} }
@ -322,6 +379,9 @@ public partial class Rename : IRename
continue; continue;
distinct.Add(checkFile); distinct.Add(checkFile);
results.Add(new(checkDirectory, record.FilePath, checkFile, JsonFile: false)); results.Add(new(checkDirectory, record.FilePath, checkFile, JsonFile: false));
if (record.SidecarFiles.Count == 0)
continue;
results.AddRange(GetSidecarFiles(renameConfiguration.MetadataConfiguration, record, distinct, checkDirectory, paddedId));
} }
return new(results); return new(results);
} }
@ -370,11 +430,11 @@ public partial class Rename : IRename
return new(results); return new(results);
} }
private static void SaveIdentifiersToDisk(long ticks, RenameConfiguration renameConfiguration, string aMetadataCollectionDirectory, ReadOnlyCollection<Record> records) private static void SaveIdentifiersToDisk(long ticks, RenameConfiguration renameConfiguration, string aMetadataCollectionDirectory, ReadOnlyCollection<RecordB> recordBCollection)
{ {
string paddedId; string paddedId;
List<Identifier> identifiers = []; List<Identifier> identifiers = [];
foreach (Record record in records) foreach (RecordB record in recordBCollection)
{ {
if (record.ExifDirectory.Id is null) if (record.ExifDirectory.Id is null)
continue; continue;
@ -395,9 +455,9 @@ public partial class Rename : IRename
throw new Exception($"Invalid {nameof(renameConfiguration.RelativePropertyCollectionFile)}"); throw new Exception($"Invalid {nameof(renameConfiguration.RelativePropertyCollectionFile)}");
DirectoryInfo directoryInfo = new(Path.GetFullPath(renameConfiguration.MetadataConfiguration.ResultConfiguration.RootDirectory)); DirectoryInfo directoryInfo = new(Path.GetFullPath(renameConfiguration.MetadataConfiguration.ResultConfiguration.RootDirectory));
logger?.LogInformation("{Ticks} {RootDirectory}", ticks, directoryInfo.FullName); logger?.LogInformation("{Ticks} {RootDirectory}", ticks, directoryInfo.FullName);
ReadOnlyCollection<Record> records = GetExifDirectoryCollection(rename, appSettings, renameConfiguration, directoryInfo); ReadOnlyCollection<RecordB> recordBCollection = GetRecordBCollection(rename, appSettings, renameConfiguration, directoryInfo);
SaveIdentifiersToDisk(ticks, renameConfiguration, aMetadataCollectionDirectory, records); SaveIdentifiersToDisk(ticks, renameConfiguration, aMetadataCollectionDirectory, recordBCollection);
ReadOnlyCollection<ToDo> toDoCollection = GetToDoCollection(renameConfiguration, identifiers, records); ReadOnlyCollection<ToDo> toDoCollection = GetToDoCollection(renameConfiguration, identifiers, recordBCollection);
ReadOnlyCollection<string> lines = RenameFilesInDirectories(toDoCollection); ReadOnlyCollection<string> lines = RenameFilesInDirectories(toDoCollection);
if (lines.Count != 0) if (lines.Count != 0)
{ {

View File

@ -3,75 +3,97 @@ using System.Text.Json.Serialization;
namespace View_by_Distance.Shared.Models; namespace View_by_Distance.Shared.Models;
public class FileHolder public record FileHolder(DateTime? CreationTime,
string? DirectoryName,
bool Exists,
string ExtensionLowered,
string FullName,
int? Id,
DateTime? LastWriteTime,
long? Length,
string Name,
string NameWithoutExtension)
{ {
protected readonly DateTime? _CreationTime;
protected readonly string? _DirectoryName;
protected readonly bool _Exists;
protected readonly string _ExtensionLowered;
protected readonly string _FullName;
protected readonly int? _Id;
protected readonly DateTime? _LastWriteTime;
protected readonly long? _Length;
protected readonly string _Name;
protected readonly string _NameWithoutExtension;
public DateTime? CreationTime => _CreationTime;
public string? DirectoryName => _DirectoryName;
public bool Exists => _Exists;
public string ExtensionLowered => _ExtensionLowered;
public string FullName => _FullName;
public int? Id => _Id;
public DateTime? LastWriteTime => _LastWriteTime;
public long? Length => _Length;
public string Name => _Name;
public string NameWithoutExtension => _NameWithoutExtension;
public FileHolder(DateTime? creationTime, string? directoryName, bool exists, string extensionLowered, string fullName, int? id, DateTime? lastWriteTime, long? length, string name, string nameWithoutExtension)
{
_CreationTime = creationTime;
_DirectoryName = directoryName;
_Exists = exists;
_ExtensionLowered = extensionLowered;
_FullName = fullName;
_Id = id;
_LastWriteTime = lastWriteTime;
_Length = length;
_Name = name;
_NameWithoutExtension = nameWithoutExtension;
}
public FileHolder(FileInfo fileInfo, int? id)
{
if (fileInfo.Exists)
{
_CreationTime = fileInfo.CreationTime;
_LastWriteTime = fileInfo.LastWriteTime;
_Length = fileInfo.Length;
}
_DirectoryName = fileInfo.DirectoryName;
_Exists = fileInfo.Exists;
_ExtensionLowered = fileInfo.Extension.ToLower();
_Id = id;
_FullName = fileInfo.FullName;
_Name = fileInfo.Name;
_NameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName);
}
public FileHolder(string fileName) :
this(new FileInfo(fileName), null)
{ }
public FileHolder(string fileName, int? id) :
this(new FileInfo(fileName), id)
{ }
public override string ToString() public override string ToString()
{ {
string result = JsonSerializer.Serialize(this, FileHolderSourceGenerationContext.Default.FileHolder); string result = JsonSerializer.Serialize(this, FileHolderSourceGenerationContext.Default.FileHolder);
return result; return result;
} }
private static FileHolder GetExisting(FileInfo fileInfo, int? id) =>
new(fileInfo.CreationTime,
fileInfo.DirectoryName,
fileInfo.Exists,
fileInfo.Extension.ToLower(),
fileInfo.FullName,
id,
fileInfo.LastWriteTime,
fileInfo.Length,
fileInfo.Name,
Path.GetFileNameWithoutExtension(fileInfo.FullName));
private static FileHolder GetNonExisting(FileInfo fileInfo, int? id) =>
new(null,
fileInfo.DirectoryName,
fileInfo.Exists,
fileInfo.Extension.ToLower(),
fileInfo.FullName,
id,
null,
null,
fileInfo.Name,
Path.GetFileNameWithoutExtension(fileInfo.FullName));
public static FileHolder Get(FileInfo fileInfo, int? id) =>
fileInfo.Exists ? GetExisting(fileInfo, id) : GetNonExisting(fileInfo, id);
public static FileHolder Get(FilePath filePath, int? id)
{
FileHolder result;
result = new(new(filePath.CreationTicks),
filePath.DirectoryName,
true,
filePath.ExtensionLowered,
filePath.FullName,
id,
new(filePath.LastWriteTicks),
filePath.Length,
filePath.Name,
Path.GetFileNameWithoutExtension(filePath.FullName));
return result;
}
private static FileHolder GetExisting(FileHolder fileHolder) =>
new(fileHolder.CreationTime,
fileHolder.DirectoryName,
fileHolder.Exists,
fileHolder.ExtensionLowered,
fileHolder.FullName,
Id: null,
fileHolder.LastWriteTime,
fileHolder.Length,
fileHolder.Name,
Path.GetFileNameWithoutExtension(fileHolder.FullName));
private static FileHolder GetNonExisting(FileHolder fileHolder) =>
new(null,
fileHolder.DirectoryName,
fileHolder.Exists,
fileHolder.ExtensionLowered,
fileHolder.FullName,
Id: null,
null,
null,
fileHolder.Name,
Path.GetFileNameWithoutExtension(fileHolder.FullName));
public static FileHolder Get(FileHolder fileHolder) =>
fileHolder.Exists ? GetExisting(fileHolder) : GetNonExisting(fileHolder);
public static FileHolder Get(string file) =>
Get(new FileInfo(file), id: null);
} }
[JsonSourceGenerationOptions(WriteIndented = true)] [JsonSourceGenerationOptions(WriteIndented = true)]

View File

@ -28,7 +28,7 @@ public record FilePath(long CreationTicks,
public static FilePath Get(MetadataConfiguration metadataConfiguration, FileHolder fileHolder, int? index) public static FilePath Get(MetadataConfiguration metadataConfiguration, FileHolder fileHolder, int? index)
{ {
if (fileHolder.CreationTime is null) if (fileHolder.CreationTime is null)
fileHolder = new(fileHolder.FullName); fileHolder = FileHolder.Get(fileHolder);
if (fileHolder.CreationTime is null) if (fileHolder.CreationTime is null)
throw new NullReferenceException(nameof(fileHolder.CreationTime)); throw new NullReferenceException(nameof(fileHolder.CreationTime));
if (fileHolder.LastWriteTime is null) if (fileHolder.LastWriteTime is null)