Skip to content

Commit

Permalink
added docs
Browse files Browse the repository at this point in the history
  • Loading branch information
DerEffi committed Feb 3, 2024
1 parent cf3870c commit 0df5d0d
Show file tree
Hide file tree
Showing 19 changed files with 326 additions and 25 deletions.
3 changes: 3 additions & 0 deletions ImageComparison/Models/Action.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

namespace ImageComparison.Models
{
/// <summary>
/// Console Action for found matches
/// </summary>
public enum Action
{
Search,
Expand Down
3 changes: 3 additions & 0 deletions ImageComparison/Models/CacheItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

namespace ImageComparison.Models
{
/// <summary>
/// Data Structure for stored analysis items in database
/// </summary>
public class CacheItem
{
public string path;
Expand Down
3 changes: 3 additions & 0 deletions ImageComparison/Models/ImageAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public class ImageAnalysis
public FileInfo Image { get; set; }
public ulong[] Hash { get; set; }

/// <summary>
/// Blob converted hash value for disk/database/cache storage
/// </summary>
public byte[] HashBlob {
get {
byte[] blob = new byte[Hash.Length * 8];
Expand Down
33 changes: 30 additions & 3 deletions ImageComparison/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ internal class Program
public static bool force = false;
public static ProgressBar? progress = null;


/*
* Helptext option definitions
*/
private static readonly List<string> preOptions =
new List<string>(){
"\n\nUSAGE:",
Expand All @@ -29,21 +33,23 @@ internal class Program
" 127 - Bad Request"
};


public static int Main(string[] args)
{
ParserResult<Options> parser = new Parser(with => {
with.HelpWriter = null;
with.CaseInsensitiveEnumValues = true;
}).ParseArguments<Options>(args);

// argument parser success
parser.WithParsed(options =>
{
logLevel = options.LogLevel;
force = options.Force;
LogService.OnLog += OnLog;
CompareService.OnProgress += OnProgress;

// custom validation
// custom validation of argument dependecies not covered by library
List<string> customErrors = new();

if (options.Processors.Count() == 0 && (options.Action == Models.Action.Move || options.Action == Models.Action.Bin || options.Action == Models.Action.Delete))
Expand All @@ -61,6 +67,7 @@ public static int Main(string[] args)
if (options.Cache.Length == 0 && options.Action == Models.Action.NoMatch)
customErrors.Add($"Required option 'c, cache' missing on action '{options.Action}'");

// console output for custom errors
if (customErrors.Count > 0)
{
ParserResult<Options> errorParser = new Parser(with =>
Expand All @@ -87,6 +94,7 @@ public static int Main(string[] args)
.AddPostOptionsLines(postOptions)
);
});

return;
}

Expand All @@ -95,22 +103,27 @@ public static int Main(string[] args)
string hashVersion = HashService.GetIdentifier(options.HashDetail, options.HashAlgorithm);
ulong scantime = (ulong)(DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds;

// scan directories for files
options.Locations = options.Locations.Select(l => Path.IsPathRooted(l) ? l : Path.Combine(Assembly.GetExecutingAssembly().Location, l));
List<List<FileInfo>> searchLocations = FileService.SearchProcessableFiles(options.Locations.ToArray(), options.Recursive);

// get already analysed images from cache (if specified)
FileService.DataDirectory = Path.IsPathRooted(options.Cache) ? (Path.GetDirectoryName(options.Cache) ?? FileService.DataDirectory) : Path.Combine(Assembly.GetExecutingAssembly().Location, Path.GetDirectoryName(options.Cache) ?? ".\\");
FileService.CacheFile = Path.GetFileName(options.Cache).NullIfEmpty() ?? FileService.CacheFile;
if (options.Cache.Length >= 1)
CacheService.Init();
List<CacheItem> cachedAnalysis = options.Cache.Length >= 1 ? CacheService.GetImages(hashVersion) : new();

// analyse all new or modified images
List<List<ImageAnalysis>> analysedImages = CompareService.AnalyseImages(searchLocations, options.HashDetail, options.HashAlgorithm, cachedAnalysis);
if (options.Cache.Length >= 1)
CacheService.UpdateImages(analysedImages.SelectMany(i => i).ToList(), hashVersion, scantime);

// sort out all duplicates or nomatches
List<NoMatch> nomatches = options.Cache.Length >= 1 ? CacheService.GetNoMatches() : new();
List<ImageMatch> Matches = CompareService.SearchForDuplicates(analysedImages, options.Similarity, options.SearchMode, nomatches);

// output csv Search results
if (options.Action == Models.Action.Search)
{
if (logLevel < LogLevel.Warning)
Expand All @@ -119,24 +132,28 @@ public static int Main(string[] args)
{
Console.WriteLine($"\"{m.Image1.Image.FullName}\",\"{m.Image2.Image.FullName}\",{m.Similarity}");
});

return;
}

// fill cache with found matches
if (options.Action == Models.Action.NoMatch)
{
LogService.Log("Filling no-match cache with current results due to user request", LogLevel.Warning);
CacheService.AddNoMatches(Matches);

return;
}

// just to be sure that files dont get processed on an unexpected action input
// check action again just to be sure that files dont get processed on an unexpected action input
if (options.Action == Models.Action.Move || options.Action == Models.Action.Bin || options.Action == Models.Action.Delete) {
DeleteAction deleteAction;

switch (options.Action)
{
case Models.Action.Move:
deleteAction = DeleteAction.Move;
// trailing slash because target path needs to be directory
if (!options.Target.EndsWith("\\") && !options.Target.EndsWith("/"))
options.Target += "\\";
break;
Expand All @@ -151,11 +168,13 @@ public static int Main(string[] args)
List<string> processedFiles = new();
Matches.ForEach(m =>
{
// don't delete files if the match or the file itself already has been deleted
if(processedFiles.Any(p => p == m.Image1.Image.FullName || p == m.Image2.Image.FullName))
return;

for (int i = 0; i < options.Processors.Count(); i++)
{
// check first distinguishable property in given processors and take action if possible
int processingResult = AutoProcessorService.Processors.First(p => p.DisplayName == options.Processors.ElementAt(i)).Process(m.Image1.Image, m.Image2.Image);
if (processingResult != 0)
{
Expand All @@ -182,6 +201,8 @@ public static int Main(string[] args)
{
LogService.Log($"Error Auto-Processing current match: '{m.Image1.Image.FullName}' - '{m.Image2.Image.FullName}'", LogLevel.Error);
}

break;
}
}
});
Expand All @@ -194,6 +215,7 @@ public static int Main(string[] args)
}

})
// console argument parser failure (or help requested)
.WithNotParsed(errors =>
{
Console.WriteLine(
Expand All @@ -215,7 +237,11 @@ public static int Main(string[] args)
return (int)exit;
}

public static void OnProgress(object? sender, ImageComparerEventArgs e)

/*
* Progress ticker for image analysation progress bar
*/
private static void OnProgress(object? sender, ImageComparerEventArgs e)
{
if (e.Target > 0)
{
Expand All @@ -230,6 +256,7 @@ public static void OnProgress(object? sender, ImageComparerEventArgs e)
}
}


/*
* Log implementation for console output
*/
Expand Down
10 changes: 10 additions & 0 deletions ImageComparison/Services/AutoProcessorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@

namespace ImageComparison.Services
{
/// <summary>
/// Automatic processing of image matches to determine what image to move/delete
/// </summary>
public static class AutoProcessorService
{
/// <summary>
/// Names of the implemented processors
/// </summary>
public static List<string> Supported { get => Processors.Select((p) => p.DisplayName).ToList(); }

/// <summary>
/// Processor Implementations
/// </summary>
public readonly static List<AutoProcessor> Processors = new()
{
new(){
Expand Down
38 changes: 38 additions & 0 deletions ImageComparison/Services/CacheService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@

namespace ImageComparison.Services
{
/// <summary>
/// Interface Service to disk/database cache
/// </summary>
public static class CacheService
{
private static SqliteConnection connection;

/// <summary>
/// Ensure Directory and File for the Cache exist
/// </summary>
public static void Init()
{
try
Expand All @@ -26,6 +32,11 @@ public static void Init()
}
}

/// <summary>
/// Retrieve already analysed images from cache for given hashfunction
/// </summary>
/// <param name="hashtype"></param>
/// <returns></returns>
public static List<CacheItem> GetImages(string hashtype)
{
List<CacheItem> images = new();
Expand All @@ -45,6 +56,12 @@ public static List<CacheItem> GetImages(string hashtype)
return images;
}

/// <summary>
/// Update/Add analysation data
/// </summary>
/// <param name="images"></param>
/// <param name="hashtype"></param>
/// <param name="scantime"></param>
public static void UpdateImages(List<ImageAnalysis> images, string hashtype, ulong scantime)
{
try
Expand Down Expand Up @@ -82,10 +99,16 @@ public static void UpdateImages(List<ImageAnalysis> images, string hashtype, ulo
connection.Close();
}

/// <summary>
/// Add image pair determined as No-Match to cache
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
public static void AddNoMatch(string a, string b)
{
try
{
// ensure that the images in found pair are always in the same order to prevent duplicates
int order = string.Compare(a, b);
if (order == 0)
return;
Expand All @@ -105,6 +128,10 @@ public static void AddNoMatch(string a, string b)
connection.Close();
}

/// <summary>
/// Add list of image pairs as No-Matches to cache
/// </summary>
/// <param name="nomatches"></param>
public static void AddNoMatches(List<ImageMatch> nomatches)
{
try
Expand All @@ -119,6 +146,7 @@ public static void AddNoMatches(List<ImageMatch> nomatches)

nomatches.ForEach(nomatch =>
{
// ensure that the images in found pair are always in the same order to prevent duplicates
string a, b;
int order = string.Compare(nomatch.Image1.Image.FullName, nomatch.Image2.Image.FullName);
if (order == 0)
Expand Down Expand Up @@ -146,6 +174,10 @@ public static void AddNoMatches(List<ImageMatch> nomatches)
connection.Close();
}

/// <summary>
/// Retrieve stored No-matches from cache
/// </summary>
/// <returns></returns>
public static List<NoMatch> GetNoMatches()
{
List<NoMatch> nomatches = new();
Expand All @@ -168,6 +200,9 @@ public static List<NoMatch> GetNoMatches()
return nomatches;
}

/// <summary>
/// Remove all analysed image data from cache (excluding no-matches)
/// </summary>
public static void ClearImageCache()
{
try
Expand All @@ -185,6 +220,9 @@ public static void ClearImageCache()
connection.Close();
}

/// <summary>
/// Remove List of image pairs determined as No-Matches
/// </summary>
public static void ClearNoMatchCache()
{
try
Expand Down
Loading

0 comments on commit 0df5d0d

Please sign in to comment.