using Serilog; using Spectre.Console; using System.Text.Json; using System.Text.RegularExpressions; namespace DownloadManager; record struct rule { public required string Name { get; init; } public required PatternType Type { get; init; } public required string Pattern { get; init; } public required string Destination { get; init; } } /* PatternType is included here for ease of use */ enum PatternType { ExactMatch = 1, Regex = 2, Glob = 3, Danbooru = 4, } /* Management class to make handing rules easier */ class RuleManager { private List _rules; private configuration _config; private static ImageProcessing IP = new(); public RuleManager(ref configuration config) { _rules = new(); /* Start with no rules and load them later */ _config = config; loadRules(); } // attempt to load as many rules as possible private void loadRules() { string[] ruleDirectories = _config.ruleDirectories; string[] ruleFiles = { }; Log.Information("Attempting to load rules"); if (ruleDirectories.Length == 0) { Log.Warning("No rule directories loaded"); return; } try { Log.Information("Attempting to find rules in {dir}", ruleDirectories.Last()); ruleFiles = Directory.GetFiles(ruleDirectories.Last()); /* Use last since it will be the users choice */ } catch (IOException e) { Log.Error(e, "{path} may be a file and not a directory", ruleDirectories.Last()); } catch (UnauthorizedAccessException e) { Log.Error(e, "You do not have permission to open {path}", ruleDirectories.Last()); } catch (Exception e) { Log.Fatal(e, "Unexpected exception occured, Please open a GitHub issue"); Environment.Exit(70); } Log.Information("found {count} rules in {path}", ruleFiles.Length, ruleDirectories.Last()); Log.Debug("{@rules}", ruleFiles); if (ruleFiles.Length == 0) { Log.Warning("No rules loaded"); return; } Log.Information("Loading rules from ruleFiles"); foreach (var file in ruleFiles) { try { Log.Information("Attempting to load rule {name}", Path.GetFileName(file)); using (var ruleStream = File.OpenText(file)) { string json = ruleStream.ReadToEnd(); Log.Debug("{json}", json); rule r = JsonSerializer.Deserialize(json); Log.Information("Deserialised rule {@rule}", r); _rules.Add(r); } } catch (JsonException e) { Log.Error(e, "Could not deserialise JSON rule"); } catch (UnauthorizedAccessException e) { Log.Error(e, "I/O error while reading rule"); } catch (Exception e) { Log.Fatal(e, "Unexpected exception occurred, Please open a GitHub issue"); Environment.Exit(70); } } _rules.Sort(delegate (rule x, rule y) { return x.Name.CompareTo(y.Name); }); } public bool ApplyRules() { if (_rules.Count() == 0) { Log.Warning("ApplyRules was called but there are no rules configured"); return false; } Log.Information("Attempting to run {count} rules, Dryrun:{dryrun}", _rules.Count(), _config.dryRun); foreach (var rule in _rules) { bool result = false; switch (rule.Type) { case PatternType.ExactMatch: result = ApplyExact(rule); break; case PatternType.Regex: result = ApplyRegex(rule); break; case PatternType.Glob: result = ApplyGlob(rule); break; case PatternType.Danbooru: result = ApplyDanbooru(rule); break; } if (result) { Log.Information("Successfully applied rule {name}", rule.Name); } } return false; } private bool ApplyExact(rule rule) { // this ruletype should only match one file string? file = Directory.GetFiles(_config.downloadDirectory) .Where(x => Path.GetFileName(x) == rule.Pattern) .FirstOrDefault(defaultValue: null); if (file is null) { Log.Information("Could not apply rule {name} as nothing matched {pattern}", rule.Name, rule.Pattern); return false; } safeMove(file, rule.Destination); return true; } private bool ApplyRegex(rule rule) { Regex rx = new Regex(rule.Pattern, RegexOptions.Compiled); string[] files = Directory.GetFiles(_config.downloadDirectory) .Where(path => rx.IsMatch(Path.GetFileName(path))) // Match against file name to make regex more logical .ToArray(); if (files.Length == 0) { Log.Information("Could not apply rule {name} as nothing matched {pattern}", rule.Name, rule.Pattern); return false; } foreach (var file in files) { safeMove(file, rule.Destination); } return false; } private bool ApplyGlob(rule rule) { return false; } private bool ApplyDanbooru(rule rule) { string[] files = Directory.GetFiles(_config.downloadDirectory) .Where(path => Path.GetFileName(path).Substring(0, 2) == "__") .ToArray(); if (files.Length == 0) { Log.Information("Could not apply rule {name}, no files applicable", rule.Name); } foreach (var file in files) { string? aspect = IP.GetAspectRatioString(file); if (aspect is null) { continue; // probably not an image since cannot calculate aspect ratio } string dest = Path.Combine(rule.Destination, aspect); safeMove(file, dest); } return false; } // TODO: add appropriate exception avoidance/handling here private void safeMove(string file, string targetDir) { Log.Information("Moving {file} to {targetdir}", file, targetDir); string target = Path.Combine(targetDir, Path.GetFileName(file)); if (File.Exists(target)) { Log.Warning("target {target} already exists", target); target = Path.Combine(targetDir, Path.GetFileNameWithoutExtension(file), DateTime.Today.ToString("yyyy-MM-dd"), Path.GetExtension(file)); Log.Warning("Saving as: {target}", target); } if (_config.dryRun) { return; } if (_config.confirm) { if (!AnsiConsole.Confirm("Move file?")) { return; } } Directory.CreateDirectory(targetDir); File.Move(file, target); } }