LocationContainerDistanceTolerance
This commit is contained in:
parent
a871868aaa
commit
6b940180fa
@ -4,10 +4,11 @@ using System.Text.Json;
|
|||||||
using View_by_Distance.Distance.Models.Stateless;
|
using View_by_Distance.Distance.Models.Stateless;
|
||||||
using View_by_Distance.FaceRecognitionDotNet;
|
using View_by_Distance.FaceRecognitionDotNet;
|
||||||
using View_by_Distance.Shared.Models;
|
using View_by_Distance.Shared.Models;
|
||||||
|
using View_by_Distance.Shared.Models.Methods;
|
||||||
|
|
||||||
namespace View_by_Distance.Distance.Models;
|
namespace View_by_Distance.Distance.Models;
|
||||||
|
|
||||||
public partial class E_Distance
|
public partial class E_Distance : IDistance<MetadataExtractor.Directory>
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly List<string> _Moved;
|
private readonly List<string> _Moved;
|
||||||
@ -411,7 +412,7 @@ public partial class E_Distance
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<SortingContainer> GetSortingContainers(Map.Models.Configuration mapConfiguration, Shared.Models.Methods.IDistanceLimits distanceLimits, Face face, FaceDistance faceDistanceEncoding, List<Sorting> sortingCollection)
|
private static List<SortingContainer> GetSortingContainers(Map.Models.Configuration mapConfiguration, IDistanceLimits distanceLimits, Face face, FaceDistance faceDistanceEncoding, List<Sorting> sortingCollection)
|
||||||
{
|
{
|
||||||
List<SortingContainer> results = new();
|
List<SortingContainer> results = new();
|
||||||
SortingContainer sortingContainer;
|
SortingContainer sortingContainer;
|
||||||
@ -516,7 +517,7 @@ public partial class E_Distance
|
|||||||
return results.ToArray();
|
return results.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SortingContainer[] SetFaceMappingSortingCollectionThenGetSortingContainers(int maxDegreeOfParallelism, Map.Models.Configuration mapConfiguration, long ticks, Map.Models.MapLogic mapLogic, Shared.Models.Methods.IDistanceLimits distanceLimits, List<FaceDistance> faceDistanceEncodings, FaceDistanceContainer[] filteredFaceDistanceContainers)
|
public static SortingContainer[] SetFaceMappingSortingCollectionThenGetSortingContainers(int maxDegreeOfParallelism, Map.Models.Configuration mapConfiguration, long ticks, Map.Models.MapLogic mapLogic, IDistanceLimits distanceLimits, List<FaceDistance> faceDistanceEncodings, FaceDistanceContainer[] filteredFaceDistanceContainers)
|
||||||
{
|
{
|
||||||
SortingContainer[] results;
|
SortingContainer[] results;
|
||||||
List<SortingContainer> collection = new();
|
List<SortingContainer> collection = new();
|
||||||
@ -552,4 +553,65 @@ public partial class E_Distance
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ReviewLocationContainerDistanceTolerance(float locationContainerDistanceTolerance, DateTime dateTime, List<(string, FaceRecognitionDotNet.FaceEncoding)> collection)
|
||||||
|
{
|
||||||
|
List<string> files = new();
|
||||||
|
FaceDistance? faceDistanceEncoding = null;
|
||||||
|
List<FaceDistance> faceDistanceEncodings = new();
|
||||||
|
foreach ((string _, FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding) in collection)
|
||||||
|
{
|
||||||
|
faceDistanceEncoding = new(faceRecognitionDotNetFaceEncoding);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
foreach ((string file, FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding) in collection)
|
||||||
|
{
|
||||||
|
files.Add(file);
|
||||||
|
faceDistanceEncodings.Add(new(faceRecognitionDotNetFaceEncoding));
|
||||||
|
}
|
||||||
|
if (faceDistanceEncoding is null)
|
||||||
|
throw new Exception();
|
||||||
|
List<FaceDistance> faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding);
|
||||||
|
if (faceDistanceLengths.Count != files.Count)
|
||||||
|
throw new Exception();
|
||||||
|
for (int i = 0; i < files.Count; i++)
|
||||||
|
{
|
||||||
|
if (!File.Exists(files[i]))
|
||||||
|
continue;
|
||||||
|
if (faceDistanceLengths[i].Length < locationContainerDistanceTolerance)
|
||||||
|
continue;
|
||||||
|
File.SetCreationTime(files[i], dateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDistance<MetadataExtractor.Directory>.ReviewLocationContainerDistanceTolerance(float locationContainerDistanceTolerance, ReadOnlyCollection<LocationContainer<MetadataExtractor.Directory>> locationContainers)
|
||||||
|
{
|
||||||
|
string? json;
|
||||||
|
int? lastDirectoryNumber = null;
|
||||||
|
DateTime dateTime = DateTime.Now;
|
||||||
|
Shared.Models.FaceEncoding? modelsFaceEncoding;
|
||||||
|
FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding;
|
||||||
|
List<(string, FaceRecognitionDotNet.FaceEncoding)> collection = new();
|
||||||
|
foreach (LocationContainer<MetadataExtractor.Directory>? locationContainer in locationContainers)
|
||||||
|
{
|
||||||
|
if (locationContainer.DirectoryNumber is null)
|
||||||
|
continue;
|
||||||
|
if (lastDirectoryNumber is not null && locationContainer.DirectoryNumber.Value != lastDirectoryNumber.Value)
|
||||||
|
{
|
||||||
|
ReviewLocationContainerDistanceTolerance(locationContainerDistanceTolerance, dateTime, collection);
|
||||||
|
collection.Clear();
|
||||||
|
}
|
||||||
|
json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.Directories);
|
||||||
|
if (json is null)
|
||||||
|
continue;
|
||||||
|
modelsFaceEncoding = JsonSerializer.Deserialize<Shared.Models.FaceEncoding>(json);
|
||||||
|
if (modelsFaceEncoding is null)
|
||||||
|
throw new NotSupportedException();
|
||||||
|
faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding);
|
||||||
|
collection.Add((locationContainer.File, faceRecognitionDotNetFaceEncoding));
|
||||||
|
lastDirectoryNumber = locationContainer.DirectoryNumber.Value;
|
||||||
|
}
|
||||||
|
if (collection.Count > 0)
|
||||||
|
ReviewLocationContainerDistanceTolerance(locationContainerDistanceTolerance, dateTime, collection);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -307,7 +307,7 @@ public class D_Face
|
|||||||
if (location is null)
|
if (location is null)
|
||||||
continue;
|
continue;
|
||||||
if (!results.Any(l => l.WholePercentages == locationContainer.WholePercentages))
|
if (!results.Any(l => l.WholePercentages == locationContainer.WholePercentages))
|
||||||
results.Add(new(locationContainer.FromDistanceContent, locationContainer.File, locationContainer.PersonKey, locationContainer.Id, locationContainer.WholePercentages, locationContainer.Directories, rectangle.Value, location));
|
results.Add(new(locationContainer.FromDistanceContent, locationContainer.DirectoryNumber, locationContainer.File, locationContainer.PersonKey, locationContainer.Id, locationContainer.WholePercentages, locationContainer.Directories, rectangle.Value, location));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (results.Count > 0)
|
if (results.Count > 0)
|
||||||
|
@ -265,6 +265,7 @@ public partial class DlibDotNet
|
|||||||
configuration.DistanceRenameToMatch,
|
configuration.DistanceRenameToMatch,
|
||||||
configuration.FaceConfidencePercent,
|
configuration.FaceConfidencePercent,
|
||||||
configuration.FaceDistancePermyriad,
|
configuration.FaceDistancePermyriad,
|
||||||
|
configuration.LocationContainerDistanceTolerance,
|
||||||
configuration.LocationDigits,
|
configuration.LocationDigits,
|
||||||
configuration.MappingDefaultName,
|
configuration.MappingDefaultName,
|
||||||
configuration.PersonBirthdayFirstYear,
|
configuration.PersonBirthdayFirstYear,
|
||||||
@ -1103,7 +1104,7 @@ public partial class DlibDotNet
|
|||||||
if (runToDoCollectionFirst)
|
if (runToDoCollectionFirst)
|
||||||
mapLogic = null;
|
mapLogic = null;
|
||||||
else
|
else
|
||||||
mapLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, personContainers, ticks, a2PeopleSingletonDirectory, eDistanceContentDirectory);
|
mapLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, _Distance, personContainers, ticks, a2PeopleSingletonDirectory, eDistanceContentDirectory);
|
||||||
foreach (string outputResolution in _Configuration.OutputResolutions)
|
foreach (string outputResolution in _Configuration.OutputResolutions)
|
||||||
{
|
{
|
||||||
if (outputResolution.Any(l => char.IsNumber(l)))
|
if (outputResolution.Any(l => char.IsNumber(l)))
|
||||||
@ -1176,7 +1177,7 @@ public partial class DlibDotNet
|
|||||||
}
|
}
|
||||||
fileNameToCollection = !Directory.Exists(fPhotoPrismSingletonDirectory) ? fileNameToCollection = new() : F_PhotoPrism.GetFileNameToCollection(fPhotoPrismSingletonDirectory);
|
fileNameToCollection = !Directory.Exists(fPhotoPrismSingletonDirectory) ? fileNameToCollection = new() : F_PhotoPrism.GetFileNameToCollection(fPhotoPrismSingletonDirectory);
|
||||||
B_Metadata metadata = new(_Configuration.PropertyConfiguration, _Configuration.ForceMetadataLastWriteTimeToCreationTime, _Configuration.PropertiesChangedForMetadata, bResultsFullGroupDirectory);
|
B_Metadata metadata = new(_Configuration.PropertyConfiguration, _Configuration.ForceMetadataLastWriteTimeToCreationTime, _Configuration.PropertiesChangedForMetadata, bResultsFullGroupDirectory);
|
||||||
mapLogic ??= new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, personContainers, ticks, a2PeopleSingletonDirectory, eDistanceContentDirectory);
|
mapLogic ??= new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, _Distance, personContainers, ticks, a2PeopleSingletonDirectory, eDistanceContentDirectory);
|
||||||
containers = Shared.Models.Stateless.Methods.IContainer.SortContainers(_Configuration.PropertyConfiguration, _Configuration.IgnoreRelativePaths, _ArgZeroIsConfigurationRootDirectory, argZero, containers);
|
containers = Shared.Models.Stateless.Methods.IContainer.SortContainers(_Configuration.PropertyConfiguration, _Configuration.IgnoreRelativePaths, _ArgZeroIsConfigurationRootDirectory, argZero, containers);
|
||||||
FullDoWork(argZero, propertyRoot, ticks, aResultsFullGroupDirectory, bResultsFullGroupDirectory, t, containers, propertyLogic, metadata, fileNameToCollection, mapLogic);
|
FullDoWork(argZero, propertyRoot, ticks, aResultsFullGroupDirectory, bResultsFullGroupDirectory, t, containers, propertyLogic, metadata, fileNameToCollection, mapLogic);
|
||||||
List<Item> distinctFilteredItems = Shared.Models.Stateless.Methods.IContainer.GetItems(_Configuration.PropertyConfiguration, containers, distinctItems: true, filterItems: true);
|
List<Item> distinctFilteredItems = Shared.Models.Stateless.Methods.IContainer.GetItems(_Configuration.PropertyConfiguration, containers, distinctItems: true, filterItems: true);
|
||||||
|
@ -36,6 +36,7 @@ public class Configuration
|
|||||||
[Display(Name = "Load Or Create Then Save Distance Results"), Required] public string[] LoadOrCreateThenSaveDistanceResultsForOutputResolutions { get; set; }
|
[Display(Name = "Load Or Create Then Save Distance Results"), Required] public string[] LoadOrCreateThenSaveDistanceResultsForOutputResolutions { get; set; }
|
||||||
[Display(Name = "Load Or Create Then Save Image Faces Results"), Required] public string[] LoadOrCreateThenSaveImageFacesResultsForOutputResolutions { get; set; }
|
[Display(Name = "Load Or Create Then Save Image Faces Results"), Required] public string[] LoadOrCreateThenSaveImageFacesResultsForOutputResolutions { get; set; }
|
||||||
[Display(Name = "Load PhotoPrism Locations"), Required] public bool? LoadPhotoPrismLocations { get; set; }
|
[Display(Name = "Load PhotoPrism Locations"), Required] public bool? LoadPhotoPrismLocations { get; set; }
|
||||||
|
[Display(Name = "Location Containers Distance Tolerance"), Required] public float? LocationContainerDistanceTolerance { get; set; }
|
||||||
[Display(Name = "Location Digits"), Required] public int? LocationDigits { get; set; }
|
[Display(Name = "Location Digits"), Required] public int? LocationDigits { get; set; }
|
||||||
[Display(Name = "Location Factor"), Required] public int? LocationFactor { get; set; }
|
[Display(Name = "Location Factor"), Required] public int? LocationFactor { get; set; }
|
||||||
[Display(Name = "Look for Abandoned"), Required] public bool? LookForAbandoned { get; set; }
|
[Display(Name = "Look for Abandoned"), Required] public bool? LookForAbandoned { get; set; }
|
||||||
@ -270,6 +271,7 @@ public class Configuration
|
|||||||
configuration.LoadOrCreateThenSaveDistanceResultsForOutputResolutions,
|
configuration.LoadOrCreateThenSaveDistanceResultsForOutputResolutions,
|
||||||
configuration.LoadOrCreateThenSaveImageFacesResultsForOutputResolutions,
|
configuration.LoadOrCreateThenSaveImageFacesResultsForOutputResolutions,
|
||||||
configuration.LoadPhotoPrismLocations.Value,
|
configuration.LoadPhotoPrismLocations.Value,
|
||||||
|
configuration.LocationContainerDistanceTolerance,
|
||||||
configuration.LocationDigits.Value,
|
configuration.LocationDigits.Value,
|
||||||
configuration.LocationFactor.Value,
|
configuration.LocationFactor.Value,
|
||||||
configuration.LookForAbandoned.Value,
|
configuration.LookForAbandoned.Value,
|
||||||
|
@ -35,6 +35,7 @@ public class Configuration
|
|||||||
public string[] LoadOrCreateThenSaveDistanceResultsForOutputResolutions { init; get; }
|
public string[] LoadOrCreateThenSaveDistanceResultsForOutputResolutions { init; get; }
|
||||||
public string[] LoadOrCreateThenSaveImageFacesResultsForOutputResolutions { init; get; }
|
public string[] LoadOrCreateThenSaveImageFacesResultsForOutputResolutions { init; get; }
|
||||||
public bool LoadPhotoPrismLocations { init; get; }
|
public bool LoadPhotoPrismLocations { init; get; }
|
||||||
|
public float? LocationContainerDistanceTolerance { init; get; }
|
||||||
public int LocationDigits { init; get; }
|
public int LocationDigits { init; get; }
|
||||||
public int LocationFactor { init; get; }
|
public int LocationFactor { init; get; }
|
||||||
public bool LookForAbandoned { init; get; }
|
public bool LookForAbandoned { init; get; }
|
||||||
@ -118,6 +119,7 @@ public class Configuration
|
|||||||
string[] loadOrCreateThenSaveDistanceResultsForOutputResolutions,
|
string[] loadOrCreateThenSaveDistanceResultsForOutputResolutions,
|
||||||
string[] loadOrCreateThenSaveImageFacesResultsForOutputResolutions,
|
string[] loadOrCreateThenSaveImageFacesResultsForOutputResolutions,
|
||||||
bool loadPhotoPrismLocations,
|
bool loadPhotoPrismLocations,
|
||||||
|
float? locationContainerDistanceTolerance,
|
||||||
int locationDigits,
|
int locationDigits,
|
||||||
int locationFactor,
|
int locationFactor,
|
||||||
bool lookForAbandoned,
|
bool lookForAbandoned,
|
||||||
@ -199,6 +201,7 @@ public class Configuration
|
|||||||
LoadOrCreateThenSaveDistanceResultsForOutputResolutions = loadOrCreateThenSaveDistanceResultsForOutputResolutions;
|
LoadOrCreateThenSaveDistanceResultsForOutputResolutions = loadOrCreateThenSaveDistanceResultsForOutputResolutions;
|
||||||
LoadOrCreateThenSaveImageFacesResultsForOutputResolutions = loadOrCreateThenSaveImageFacesResultsForOutputResolutions;
|
LoadOrCreateThenSaveImageFacesResultsForOutputResolutions = loadOrCreateThenSaveImageFacesResultsForOutputResolutions;
|
||||||
LoadPhotoPrismLocations = loadPhotoPrismLocations;
|
LoadPhotoPrismLocations = loadPhotoPrismLocations;
|
||||||
|
LocationContainerDistanceTolerance = locationContainerDistanceTolerance;
|
||||||
LocationDigits = locationDigits;
|
LocationDigits = locationDigits;
|
||||||
LocationFactor = locationFactor;
|
LocationFactor = locationFactor;
|
||||||
LookForAbandoned = lookForAbandoned;
|
LookForAbandoned = lookForAbandoned;
|
||||||
|
@ -14,6 +14,7 @@ public class Configuration
|
|||||||
public string FacePartsFileNameExtension { init; get; }
|
public string FacePartsFileNameExtension { init; get; }
|
||||||
public string FacesFileNameExtension { init; get; }
|
public string FacesFileNameExtension { init; get; }
|
||||||
public string FacesHiddenFileNameExtension { init; get; }
|
public string FacesHiddenFileNameExtension { init; get; }
|
||||||
|
public float? LocationContainerDistanceTolerance { init; get; }
|
||||||
public int LocationDigits { init; get; }
|
public int LocationDigits { init; get; }
|
||||||
public string MappingDefaultName { init; get; }
|
public string MappingDefaultName { init; get; }
|
||||||
public int PersonBirthdayFirstYear { init; get; }
|
public int PersonBirthdayFirstYear { init; get; }
|
||||||
@ -32,6 +33,7 @@ public class Configuration
|
|||||||
bool distanceRenameToMatch,
|
bool distanceRenameToMatch,
|
||||||
int faceConfidencePercent,
|
int faceConfidencePercent,
|
||||||
int faceDistancePermyriad,
|
int faceDistancePermyriad,
|
||||||
|
float? locationContainerDistanceTolerance,
|
||||||
int locationDigits,
|
int locationDigits,
|
||||||
string mappingDefaultName,
|
string mappingDefaultName,
|
||||||
int personBirthdayFirstYear,
|
int personBirthdayFirstYear,
|
||||||
@ -62,10 +64,11 @@ public class Configuration
|
|||||||
DeletePossibleDuplicates = deletePossibleDuplicates;
|
DeletePossibleDuplicates = deletePossibleDuplicates;
|
||||||
SaveSortingWithoutPerson = saveSortingWithoutPerson;
|
SaveSortingWithoutPerson = saveSortingWithoutPerson;
|
||||||
SortingMinimumToUseSigma = sortingMinimumToUseSigma;
|
SortingMinimumToUseSigma = sortingMinimumToUseSigma;
|
||||||
DistanceMoveUnableToMatch = distanceMoveUnableToMatch;
|
|
||||||
RangeDaysDeltaTolerance = rangeDaysDeltaTolerance[1];
|
RangeDaysDeltaTolerance = rangeDaysDeltaTolerance[1];
|
||||||
|
DistanceMoveUnableToMatch = distanceMoveUnableToMatch;
|
||||||
FacePartsFileNameExtension = facePartsFileNameExtension;
|
FacePartsFileNameExtension = facePartsFileNameExtension;
|
||||||
FacesHiddenFileNameExtension = facesHiddenFileNameExtension;
|
FacesHiddenFileNameExtension = facesHiddenFileNameExtension;
|
||||||
|
LocationContainerDistanceTolerance = locationContainerDistanceTolerance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
@ -26,7 +26,7 @@ public class MapLogic : Shared.Models.Methods.IMapLogic
|
|||||||
private readonly ReadOnlyDictionary<int, List<LocationContainer<MetadataExtractor.Directory>>> _IdToLocationContainers;
|
private readonly ReadOnlyDictionary<int, List<LocationContainer<MetadataExtractor.Directory>>> _IdToLocationContainers;
|
||||||
private readonly ReadOnlyDictionary<int, ReadOnlyDictionary<int, ReadOnlyCollection<PersonContainer>>> _IdThenWholePercentagesToPersonContainers;
|
private readonly ReadOnlyDictionary<int, ReadOnlyDictionary<int, ReadOnlyCollection<PersonContainer>>> _IdThenWholePercentagesToPersonContainers;
|
||||||
|
|
||||||
public MapLogic(int maxDegreeOfParallelism, Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, Configuration? configuration, ReadOnlyCollection<PersonContainer> personContainers, long ticks, string a2PeopleSingletonDirectory, string eDistanceContentDirectory)
|
public MapLogic(int maxDegreeOfParallelism, Shared.Models.Properties.IPropertyConfiguration propertyConfiguration, Configuration? configuration, Shared.Models.Methods.IDistance<MetadataExtractor.Directory> distance, ReadOnlyCollection<PersonContainer> personContainers, long ticks, string a2PeopleSingletonDirectory, string eDistanceContentDirectory)
|
||||||
{
|
{
|
||||||
_Ticks = ticks;
|
_Ticks = ticks;
|
||||||
_Configuration = configuration;
|
_Configuration = configuration;
|
||||||
@ -58,7 +58,7 @@ public class MapLogic : Shared.Models.Methods.IMapLogic
|
|||||||
ReadOnlyDictionary<long, List<PersonContainer>> readOnlyPersonKeyToPersonContainerCollection;
|
ReadOnlyDictionary<long, List<PersonContainer>> readOnlyPersonKeyToPersonContainerCollection;
|
||||||
Stateless.MapLogic.SetSkipCollections(configuration, personContainers, a2PeopleSingletonDirectory, skipCollection, skipNotSkipCollection);
|
Stateless.MapLogic.SetSkipCollections(configuration, personContainers, a2PeopleSingletonDirectory, skipCollection, skipNotSkipCollection);
|
||||||
List<Stateless.MapLogic.Record> records = Stateless.MapLogic.SetPersonCollectionsAndGetRecords(configuration, ticks, personContainers, eDistanceContentDirectory);
|
List<Stateless.MapLogic.Record> records = Stateless.MapLogic.SetPersonCollectionsAndGetRecords(configuration, ticks, personContainers, eDistanceContentDirectory);
|
||||||
locationContainers.AddRange(Stateless.MapLogic.GetLocationContainers(maxDegreeOfParallelism, configuration, ticks, personContainers, eDistanceContentDirectory, skipCollection, records));
|
locationContainers.AddRange(Stateless.MapLogic.GetLocationContainers(distance, maxDegreeOfParallelism, configuration, ticks, personContainers, eDistanceContentDirectory, skipCollection, records));
|
||||||
int lossCount = records.Count - locationContainers.Count;
|
int lossCount = records.Count - locationContainers.Count;
|
||||||
ReadOnlyCollection<Stateless.MapLogic.PersonKeyFormattedIdThenWholePercentages> personKeyFormattedIdThenWholePercentagesCollection = Stateless.MapLogic.GetPersonKeyFormattedIdThenWholePercentages(configuration, ticks, records);
|
ReadOnlyCollection<Stateless.MapLogic.PersonKeyFormattedIdThenWholePercentages> personKeyFormattedIdThenWholePercentagesCollection = Stateless.MapLogic.GetPersonKeyFormattedIdThenWholePercentages(configuration, ticks, records);
|
||||||
int unableToMatchCount = records.Count - personKeyFormattedIdThenWholePercentagesCollection.Count;
|
int unableToMatchCount = records.Count - personKeyFormattedIdThenWholePercentagesCollection.Count;
|
||||||
@ -236,6 +236,7 @@ public class MapLogic : Shared.Models.Methods.IMapLogic
|
|||||||
result += UpdateMappingFromPerson(locationContainersFiles, mapping);
|
result += UpdateMappingFromPerson(locationContainersFiles, mapping);
|
||||||
if (mapping.MappingFromPerson is null || mapping.MappingFromPerson.LocationContainersFiles.Count == 0)
|
if (mapping.MappingFromPerson is null || mapping.MappingFromPerson.LocationContainersFiles.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
if (_Configuration?.LocationContainerDistanceTolerance is null)
|
||||||
Stateless.MapLogic.MoveToDecade(propertyConfiguration, mapping.MappingFromItem, mapping.MappingFromPerson);
|
Stateless.MapLogic.MoveToDecade(propertyConfiguration, mapping.MappingFromItem, mapping.MappingFromPerson);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -15,7 +15,8 @@ internal abstract class MapLogic
|
|||||||
{
|
{
|
||||||
|
|
||||||
internal record Record(string PersonKeyFormatted,
|
internal record Record(string PersonKeyFormatted,
|
||||||
string[] PersonDisplayDirectoryNames,
|
int DirectoryNumber,
|
||||||
|
string PersonDisplayDirectoryName,
|
||||||
bool IsDefault,
|
bool IsDefault,
|
||||||
string MappedFaceFile);
|
string MappedFaceFile);
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ internal abstract class MapLogic
|
|||||||
float? TotalDays);
|
float? TotalDays);
|
||||||
|
|
||||||
internal record PersonKeyFormattedIdThenWholePercentages(string PersonKeyFormatted,
|
internal record PersonKeyFormattedIdThenWholePercentages(string PersonKeyFormatted,
|
||||||
string[] PersonDisplayDirectoryNames,
|
string PersonDisplayDirectoryName,
|
||||||
bool IsDefault,
|
bool IsDefault,
|
||||||
string MappedFaceFile,
|
string MappedFaceFile,
|
||||||
int Id,
|
int Id,
|
||||||
@ -257,6 +258,7 @@ internal abstract class MapLogic
|
|||||||
int totalSeconds;
|
int totalSeconds;
|
||||||
DateTime dateTime;
|
DateTime dateTime;
|
||||||
TimeSpan timeSpan;
|
TimeSpan timeSpan;
|
||||||
|
int directoryNumber;
|
||||||
int? wholePercentages;
|
int? wholePercentages;
|
||||||
string? checkDirectory;
|
string? checkDirectory;
|
||||||
ProgressBar progressBar;
|
ProgressBar progressBar;
|
||||||
@ -267,9 +269,9 @@ internal abstract class MapLogic
|
|||||||
List<string> distinct = new();
|
List<string> distinct = new();
|
||||||
string[] personNameDirectories;
|
string[] personNameDirectories;
|
||||||
string? newestPersonKeyFormatted;
|
string? newestPersonKeyFormatted;
|
||||||
|
string personDisplayDirectoryName;
|
||||||
string[] personNameLinkDirectories;
|
string[] personNameLinkDirectories;
|
||||||
string? personFirstInitialDirectory;
|
string? personFirstInitialDirectory;
|
||||||
string[] personDisplayDirectoryNames;
|
|
||||||
List<TicksDirectory> ticksDirectories;
|
List<TicksDirectory> ticksDirectories;
|
||||||
string[] personKeyFormattedDirectories;
|
string[] personKeyFormattedDirectories;
|
||||||
string manualCopyHumanized = nameof(Shared.Models.Stateless.IMapLogic.ManualCopy).Humanize(LetterCasing.Title);
|
string manualCopyHumanized = nameof(Shared.Models.Stateless.IMapLogic.ManualCopy).Humanize(LetterCasing.Title);
|
||||||
@ -280,6 +282,7 @@ internal abstract class MapLogic
|
|||||||
check = false;
|
check = false;
|
||||||
results.Clear();
|
results.Clear();
|
||||||
distinct.Clear();
|
distinct.Clear();
|
||||||
|
directoryNumber = 0;
|
||||||
ticksDirectories = UpdateDateVerifyAndGetTicksDirectories(eDistanceContentDirectory);
|
ticksDirectories = UpdateDateVerifyAndGetTicksDirectories(eDistanceContentDirectory);
|
||||||
totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
||||||
message = $"{i}) {ticksDirectories.Count:000} compile from and clean ticks Director(ies) - B - {totalSeconds} total second(s)";
|
message = $"{i}) {ticksDirectories.Count:000} compile from and clean ticks Director(ies) - B - {totalSeconds} total second(s)";
|
||||||
@ -316,11 +319,10 @@ internal abstract class MapLogic
|
|||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
foreach (string personNameDirectory in personNameDirectories)
|
foreach (string personNameDirectory in personNameDirectories)
|
||||||
{
|
{
|
||||||
personDisplayDirectoryNames = IPath.GetDirectoryNames(personNameDirectory);
|
directoryNumber++;
|
||||||
if (personDisplayDirectoryNames.Length == 0)
|
personDisplayDirectoryName = Path.GetFileName(personNameDirectory);
|
||||||
continue;
|
isDefault = personDisplayDirectoryName.First() == 'X' && IPersonBirthday.IsCounterPersonYear(personKeyFormatted[..4]);
|
||||||
isDefault = personDisplayDirectoryNames[^1].First() == 'X' && IPersonBirthday.IsCounterPersonYear(personKeyFormatted[..4]);
|
if (isDefault && personDisplayDirectoryName.Length == 1)
|
||||||
if (isDefault && personDisplayDirectoryNames[^1].Length == 1)
|
|
||||||
{
|
{
|
||||||
if (personKeyFormatted.Length != configuration.PersonBirthdayFormat.Length || !DateTime.TryParseExact(personKeyFormatted, configuration.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime))
|
if (personKeyFormatted.Length != configuration.PersonBirthdayFormat.Length || !DateTime.TryParseExact(personKeyFormatted, configuration.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime))
|
||||||
continue;
|
continue;
|
||||||
@ -373,11 +375,11 @@ internal abstract class MapLogic
|
|||||||
}
|
}
|
||||||
if (personKeyFormatted.Length != configuration.PersonBirthdayFormat.Length)
|
if (personKeyFormatted.Length != configuration.PersonBirthdayFormat.Length)
|
||||||
continue;
|
continue;
|
||||||
if (personDisplayDirectoryNames[^1].Length == 1 || isDefault || !personKeyFormattedCollection.Contains(personKeyFormatted))
|
if (personDisplayDirectoryName.Length == 1 || isDefault || !personKeyFormattedCollection.Contains(personKeyFormatted))
|
||||||
personFirstInitialDirectory = personNameDirectory;
|
personFirstInitialDirectory = personNameDirectory;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
personFirstInitial = personDisplayDirectoryNames[^1][..1];
|
personFirstInitial = personDisplayDirectoryName[..1];
|
||||||
if (personFirstInitial.All(l => char.IsDigit(l)))
|
if (personFirstInitial.All(l => char.IsDigit(l)))
|
||||||
{
|
{
|
||||||
foreach (string file in files)
|
foreach (string file in files)
|
||||||
@ -409,7 +411,7 @@ internal abstract class MapLogic
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
distinct.Add(fileName);
|
distinct.Add(fileName);
|
||||||
results.Add(new(personKeyFormatted, personDisplayDirectoryNames, isDefault, mappedFaceFile));
|
results.Add(new(personKeyFormatted, directoryNumber, personDisplayDirectoryName, isDefault, mappedFaceFile));
|
||||||
}
|
}
|
||||||
personNameLinkDirectories = Directory.GetDirectories(personFirstInitialDirectory, "*", SearchOption.TopDirectoryOnly);
|
personNameLinkDirectories = Directory.GetDirectories(personFirstInitialDirectory, "*", SearchOption.TopDirectoryOnly);
|
||||||
foreach (string personNameLinkDirectory in personNameLinkDirectories)
|
foreach (string personNameLinkDirectory in personNameLinkDirectories)
|
||||||
@ -454,7 +456,6 @@ internal abstract class MapLogic
|
|||||||
char status, sex, first;
|
char status, sex, first;
|
||||||
PersonDirectory personDirectory;
|
PersonDirectory personDirectory;
|
||||||
PersonContainer? personContainer;
|
PersonContainer? personContainer;
|
||||||
string personDisplayDirectoryName;
|
|
||||||
foreach (PersonKeyFormattedIdThenWholePercentages personKeyFormattedIdThenWholePercentages in personKeyFormattedIdThenWholePercentagesCollection)
|
foreach (PersonKeyFormattedIdThenWholePercentages personKeyFormattedIdThenWholePercentages in personKeyFormattedIdThenWholePercentagesCollection)
|
||||||
{
|
{
|
||||||
personBirthday = IPersonBirthday.GetPersonBirthday(configuration.PersonBirthdayFormat, personKeyFormattedIdThenWholePercentages.PersonKeyFormatted);
|
personBirthday = IPersonBirthday.GetPersonBirthday(configuration.PersonBirthdayFormat, personKeyFormattedIdThenWholePercentages.PersonKeyFormatted);
|
||||||
@ -462,14 +463,13 @@ internal abstract class MapLogic
|
|||||||
throw new Exception();
|
throw new Exception();
|
||||||
if (!personKeyFormattedToPersonContainer.TryGetValue(personKeyFormattedIdThenWholePercentages.PersonKeyFormatted, out personContainer))
|
if (!personKeyFormattedToPersonContainer.TryGetValue(personKeyFormattedIdThenWholePercentages.PersonKeyFormatted, out personContainer))
|
||||||
{
|
{
|
||||||
personDisplayDirectoryName = personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryNames[^1];
|
matches = configuration.PersonCharacters.Where(l => personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryName.Contains(l)).ToArray();
|
||||||
matches = configuration.PersonCharacters.Where(l => personDisplayDirectoryName.Contains(l)).ToArray();
|
|
||||||
if (matches.Length == 0)
|
if (matches.Length == 0)
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
group = IPerson.GetHourGroup(personDisplayDirectoryName, personBirthday.Value.Hour);
|
group = IPerson.GetHourGroup(personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryName, personBirthday.Value.Hour);
|
||||||
(status, sex, first) = IPerson.GetPersonHour(personDisplayDirectoryName, personBirthday.Value.Hour);
|
(status, sex, first) = IPerson.GetPersonHour(personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryName, personBirthday.Value.Hour);
|
||||||
personDirectory = new(matches.First(), group, status, sex, first);
|
personDirectory = new(matches.First(), group, status, sex, first);
|
||||||
personContainer = new(configuration.PersonCharacters.ToArray(), personBirthday, personDisplayDirectoryName, personDirectory);
|
personContainer = new(configuration.PersonCharacters.ToArray(), personBirthday, personKeyFormattedIdThenWholePercentages.PersonDisplayDirectoryName, personDirectory);
|
||||||
personKeyFormattedToPersonContainer.Add(personKeyFormattedIdThenWholePercentages.PersonKeyFormatted, personContainer);
|
personKeyFormattedToPersonContainer.Add(personKeyFormattedIdThenWholePercentages.PersonKeyFormatted, personContainer);
|
||||||
}
|
}
|
||||||
if (personContainer.Key is null)
|
if (personContainer.Key is null)
|
||||||
@ -588,7 +588,6 @@ internal abstract class MapLogic
|
|||||||
List<PersonKeyFormattedIdThenWholePercentages> results = new();
|
List<PersonKeyFormattedIdThenWholePercentages> results = new();
|
||||||
int? id;
|
int? id;
|
||||||
int? wholePercentages;
|
int? wholePercentages;
|
||||||
string personDisplayDirectoryName;
|
|
||||||
List<int> wholePercentagesCollection;
|
List<int> wholePercentagesCollection;
|
||||||
Dictionary<int, List<int>> idToWholePercentagesCollection = new();
|
Dictionary<int, List<int>> idToWholePercentagesCollection = new();
|
||||||
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
||||||
@ -606,10 +605,7 @@ internal abstract class MapLogic
|
|||||||
wholePercentagesCollection = idToWholePercentagesCollection[id.Value];
|
wholePercentagesCollection = idToWholePercentagesCollection[id.Value];
|
||||||
wholePercentagesCollection.Add(wholePercentages.Value);
|
wholePercentagesCollection.Add(wholePercentages.Value);
|
||||||
idToWholePercentagesCollection[id.Value].Add(wholePercentages.Value);
|
idToWholePercentagesCollection[id.Value].Add(wholePercentages.Value);
|
||||||
personDisplayDirectoryName = record.PersonDisplayDirectoryNames[^1];
|
results.Add(new(record.PersonKeyFormatted, record.PersonDisplayDirectoryName, record.IsDefault, record.MappedFaceFile, id.Value, wholePercentages.Value));
|
||||||
if (string.IsNullOrEmpty(personDisplayDirectoryName))
|
|
||||||
continue;
|
|
||||||
results.Add(new(record.PersonKeyFormatted, record.PersonDisplayDirectoryNames, record.IsDefault, record.MappedFaceFile, id.Value, wholePercentages.Value));
|
|
||||||
}
|
}
|
||||||
return new(results);
|
return new(results);
|
||||||
}
|
}
|
||||||
@ -821,9 +817,9 @@ internal abstract class MapLogic
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<(long, string)> GetDisplayDirectoryAllFiles(string fileNameExtension, ReadOnlyCollection<PersonContainer> personContainers)
|
private static List<(long, int?, string)> GetDisplayDirectoryAllFiles(string fileNameExtension, ReadOnlyCollection<PersonContainer> personContainers)
|
||||||
{
|
{
|
||||||
List<(long, string)> results = new();
|
List<(long, int?, string)> results = new();
|
||||||
string fileName;
|
string fileName;
|
||||||
List<string> distinct = new();
|
List<string> distinct = new();
|
||||||
foreach (PersonContainer personContainer in personContainers)
|
foreach (PersonContainer personContainer in personContainers)
|
||||||
@ -838,15 +834,15 @@ internal abstract class MapLogic
|
|||||||
if (distinct.Contains(fileName))
|
if (distinct.Contains(fileName))
|
||||||
continue;
|
continue;
|
||||||
distinct.Add(fileName);
|
distinct.Add(fileName);
|
||||||
results.Add(new(personContainer.Key.Value, displayDirectoryAllFile));
|
results.Add(new(personContainer.Key.Value, null, displayDirectoryAllFile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<(long PersonKey, string File)> GetCollection(Configuration configuration, ReadOnlyCollection<PersonContainer> personContainers, List<Record> records)
|
private static List<(long PersonKey, int? DirectoryNumber, string File)> GetCollection(Configuration configuration, ReadOnlyCollection<PersonContainer> personContainers, List<Record> records)
|
||||||
{
|
{
|
||||||
List<(long PersonKey, string File)> results = new();
|
List<(long PersonKey, int? DirectoryNumber, string File)> results = new();
|
||||||
string file;
|
string file;
|
||||||
long personKey;
|
long personKey;
|
||||||
string fileName;
|
string fileName;
|
||||||
@ -863,7 +859,7 @@ internal abstract class MapLogic
|
|||||||
continue;
|
continue;
|
||||||
distinct.Add(fileName);
|
distinct.Add(fileName);
|
||||||
personKey = personBirthday.Value.Ticks;
|
personKey = personBirthday.Value.Ticks;
|
||||||
results.Add(new(personKey, record.MappedFaceFile));
|
results.Add(new(personKey, record.DirectoryNumber, record.MappedFaceFile));
|
||||||
}
|
}
|
||||||
for (int i = results.Count - 1; i > -1; i--)
|
for (int i = results.Count - 1; i > -1; i--)
|
||||||
{
|
{
|
||||||
@ -878,12 +874,12 @@ internal abstract class MapLogic
|
|||||||
if (!File.Exists(file))
|
if (!File.Exists(file))
|
||||||
continue;
|
continue;
|
||||||
File.Move(file, file[..^4]);
|
File.Move(file, file[..^4]);
|
||||||
results[i] = new(results[i].PersonKey, file[..^4]);
|
results[i] = new(results[i].PersonKey, results[i].DirectoryNumber, file[..^4]);
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ParallelFor(Configuration configuration, string eDistanceContentDirectory, Dictionary<int, List<(string, int)>> skipCollection, List<LocationContainer<MetadataExtractor.Directory>> locationContainers, long personKey, string file)
|
private static void ParallelFor(Configuration configuration, string eDistanceContentDirectory, Dictionary<int, List<(string, int)>> skipCollection, List<LocationContainer<MetadataExtractor.Directory>> locationContainers, long personKey, int? directoryNumber, string file)
|
||||||
{
|
{
|
||||||
string[] fileMatches;
|
string[] fileMatches;
|
||||||
const string lnk = ".lnk";
|
const string lnk = ".lnk";
|
||||||
@ -912,7 +908,7 @@ internal abstract class MapLogic
|
|||||||
directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file);
|
directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file);
|
||||||
RectangleF? rectangle = ILocation.GetPercentagesRectangle(configuration.LocationDigits, wholePercentages.Value);
|
RectangleF? rectangle = ILocation.GetPercentagesRectangle(configuration.LocationDigits, wholePercentages.Value);
|
||||||
lock (locationContainers)
|
lock (locationContainers)
|
||||||
locationContainers.Add(new(fromDistanceContent, file, personKey, id.Value, wholePercentages.Value, directories, rectangle, null));
|
locationContainers.Add(new(fromDistanceContent, directoryNumber, file, personKey, id.Value, wholePercentages.Value, directories, rectangle, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OpenPossibleDuplicates(Configuration configuration, List<(long, int, string, float?)> duplicates)
|
private static void OpenPossibleDuplicates(Configuration configuration, List<(long, int, string, float?)> duplicates)
|
||||||
@ -987,10 +983,10 @@ internal abstract class MapLogic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static List<LocationContainer<MetadataExtractor.Directory>> GetLocationContainers(int maxDegreeOfParallelism, Configuration configuration, long ticks, ReadOnlyCollection<PersonContainer> personContainers, string eDistanceContentDirectory, Dictionary<int, List<(string, int)>> skipCollection, List<Record> records)
|
internal static List<LocationContainer<MetadataExtractor.Directory>> GetLocationContainers(Shared.Models.Methods.IDistance<MetadataExtractor.Directory> distance, int maxDegreeOfParallelism, Configuration configuration, long ticks, ReadOnlyCollection<PersonContainer> personContainers, string eDistanceContentDirectory, Dictionary<int, List<(string, int)>> skipCollection, List<Record> records)
|
||||||
{
|
{
|
||||||
List<LocationContainer<MetadataExtractor.Directory>> results = new();
|
List<LocationContainer<MetadataExtractor.Directory>> results = new();
|
||||||
List<(long PersonKey, string File)> collection = GetCollection(configuration, personContainers, records);
|
List<(long PersonKey, int? DirectoryNumber, string File)> collection = GetCollection(configuration, personContainers, records);
|
||||||
if (collection.Count > 0 && (configuration.DistanceMoveUnableToMatch || configuration.DistanceRenameToMatch))
|
if (collection.Count > 0 && (configuration.DistanceMoveUnableToMatch || configuration.DistanceRenameToMatch))
|
||||||
{
|
{
|
||||||
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
||||||
@ -1001,10 +997,13 @@ internal abstract class MapLogic
|
|||||||
_ = Parallel.For(0, collection.Count, parallelOptions, (i, state) =>
|
_ = Parallel.For(0, collection.Count, parallelOptions, (i, state) =>
|
||||||
{
|
{
|
||||||
progressBar.Tick();
|
progressBar.Tick();
|
||||||
ParallelFor(configuration, eDistanceContentDirectory, skipCollection, results, collection[i].PersonKey, collection[i].File);
|
ParallelFor(configuration, eDistanceContentDirectory, skipCollection, results, collection[i].PersonKey, collection[i].DirectoryNumber, collection[i].File);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
LookForPossibleDuplicates(configuration, new(results));
|
ReadOnlyCollection<LocationContainer<MetadataExtractor.Directory>> locationContainers = new(results.OrderBy(l => l.DirectoryNumber).ToArray());
|
||||||
|
LookForPossibleDuplicates(configuration, locationContainers);
|
||||||
|
if (configuration.LocationContainerDistanceTolerance is not null)
|
||||||
|
distance.ReviewLocationContainerDistanceTolerance(configuration.LocationContainerDistanceTolerance.Value, locationContainers);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,5 +2,5 @@ using System.Drawing;
|
|||||||
|
|
||||||
namespace View_by_Distance.Shared.Models;
|
namespace View_by_Distance.Shared.Models;
|
||||||
|
|
||||||
public record LocationContainer<T>(bool FromDistanceContent, string File, long PersonKey, int Id, int WholePercentages, IReadOnlyList<T> Directories, RectangleF? Rectangle, Location? Location)
|
public record LocationContainer<T>(bool FromDistanceContent, int? DirectoryNumber, string File, long PersonKey, int Id, int WholePercentages, IReadOnlyList<T> Directories, RectangleF? Rectangle, Location? Location)
|
||||||
{ }
|
{ }
|
11
Shared/Models/Methods/IDistance.cs
Normal file
11
Shared/Models/Methods/IDistance.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using View_by_Distance.Shared.Models.Properties;
|
||||||
|
|
||||||
|
namespace View_by_Distance.Shared.Models.Methods;
|
||||||
|
|
||||||
|
public interface IDistance<T>
|
||||||
|
{
|
||||||
|
|
||||||
|
void ReviewLocationContainerDistanceTolerance(float locationContainerDistanceTolerance, ReadOnlyCollection<LocationContainer<T>> locationContainers);
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user