diff --git a/publish.bat b/publish.bat index f25a812..c919e17 100644 --- a/publish.bat +++ b/publish.bat @@ -23,5 +23,11 @@ if exist slsk-batchdl\bin\Release\net6.0\linux-x64\publish\*.pdb del /F /Q slsk- if exist slsk-batchdl\bin\zips\sldl_linux-x64.zip del /F /Q slsk-batchdl\bin\zips\sldl_linux-x64.zip powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::CreateFromDirectory('slsk-batchdl\bin\Release\net6.0\linux-x64\publish', 'slsk-batchdl\bin\zips\sldl_linux-x64.zip'); }" +REM linux-arm +dotnet publish -c Release -r linux-arm -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true +if exist slsk-batchdl\bin\Release\net6.0\linux-arm\publish\*.pdb del /F /Q slsk-batchdl\bin\Release\net6.0\linux-arm\publish\*.pdb +if exist slsk-batchdl\bin\zips\sldl_linux-arm.zip del /F /Q slsk-batchdl\bin\zips\sldl_linux-arm.zip +powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::CreateFromDirectory('slsk-batchdl\bin\Release\net6.0\linux-arm\publish', 'slsk-batchdl\bin\zips\sldl_linux-arm.zip'); }" + endlocal diff --git a/slsk-batchdl/Config.cs b/slsk-batchdl/Config.cs index 3e2f07b..031fa4b 100644 --- a/slsk-batchdl/Config.cs +++ b/slsk-batchdl/Config.cs @@ -1,5 +1,4 @@ - -using AngleSharp.Css; +using AngleSharp.Css; using Enums; using Models; using System.Text; @@ -89,7 +88,6 @@ public class Config public int maxTracks = int.MaxValue; public int offset = 0; public int maxStaleTime = 50000; - public int updateDelay = 100; public int searchTimeout = 6000; public int concurrentProcesses = 2; public int unknownErrorRetries = 2; @@ -116,36 +114,19 @@ public class Config public bool DeleteAlbumOnFail => failedAlbumPath == "delete"; public bool IgnoreAlbumFail => failedAlbumPath == "disable"; - readonly Dictionary args, string? cond)> configProfiles = new(); - readonly HashSet appliedProfiles = new(); + private Dictionary args, string? cond)> configProfiles; + private HashSet appliedProfiles; + private string[] arguments; bool hasConfiguredIndex = false; bool confPathChanged = false; - string[] arguments; FileConditions? undoTempConds = null; FileConditions? undoTempPrefConds = null; - private static Config Instance = new(); - - public static Config I { get { return Instance; } } - - private Config() { } - - private Config(Dictionary args, string? cond)> cfg, string[] args) - { - configProfiles = cfg; - arguments = args; - } - - - public void LoadAndParse(string[] args) + public Config(string[] args) { - int helpIdx = Array.FindLastIndex(args, x => x == "--help" || x == "-h"); - if (args.Length == 0 || helpIdx >= 0) - { - string option = helpIdx + 1 < args.Length ? args[helpIdx + 1] : ""; - Help.PrintHelp(option); - Environment.Exit(0); - } + configProfiles = new Dictionary args, string? cond)>(); + appliedProfiles = new HashSet(); + arguments = args; arguments = args.SelectMany(arg => { @@ -189,6 +170,28 @@ public class Config ProcessArgs(arguments); } + public Config Copy() // deep copies all fields except configProfiles and arguments + { + var copy = (Config)this.MemberwiseClone(); + + copy.necessaryCond = new FileConditions(necessaryCond); + copy.preferredCond = new FileConditions(preferredCond); + + if (undoTempConds != null) + copy.undoTempConds = new FileConditions(undoTempConds); + if (undoTempPrefConds != null) + copy.undoTempPrefConds = new FileConditions(undoTempPrefConds); + + copy.regexToReplace = new Track(regexToReplace); + copy.regexReplaceBy = new Track(regexReplaceBy); + + copy.appliedProfiles = new HashSet(appliedProfiles); + + copy.configProfiles = configProfiles; + copy.arguments = arguments; + + return copy; + } void SetConfigPath(string[] args) { @@ -308,23 +311,50 @@ public class Config } - public static bool UpdateProfiles(TrackListEntry tle) + public bool NeedUpdateProfiles(TrackListEntry tle) { - if (I.DoNotDownload) + if (DoNotDownload) return false; - if (!I.HasAutoProfiles) + if (!HasAutoProfiles) return false; - bool needUpdate = false; - var toApply = new List<(string name, List args)>(); - - foreach ((var key, var val) in I.configProfiles) + foreach ((var key, var val) in configProfiles) { if (key == "default" || val.cond == null) continue; - bool condSatisfied = I.ProfileConditionSatisfied(val.cond, tle); - bool alreadyApplied = I.appliedProfiles.Contains(key); + bool condSatisfied = ProfileConditionSatisfied(val.cond, tle); + bool alreadyApplied = appliedProfiles.Contains(key); + + if (condSatisfied && !alreadyApplied) + return true; + if (!condSatisfied && alreadyApplied) + return true; + } + + return false; + } + + + public bool NeedUpdateProfiles(TrackListEntry tle, out List<(string name, List args)>? toApply) + { + toApply = null; + + if (DoNotDownload) + return false; + if (!HasAutoProfiles) + return false; + + bool needUpdate = false; + toApply = new List<(string name, List args)>(); + + foreach ((var key, var val) in configProfiles) + { + if (key == "default" || val.cond == null) + continue; + + bool condSatisfied = ProfileConditionSatisfied(val.cond, tle); + bool alreadyApplied = appliedProfiles.Contains(key); if (condSatisfied && !alreadyApplied) needUpdate = true; @@ -335,26 +365,27 @@ public class Config toApply.Add((key, val.args)); } - if (!needUpdate) - return false; + return needUpdate; + } - // this means that auto profiles can't change --profile and --config - var profile = I.profile; - Instance = new Config(I.configProfiles, I.arguments); - I.ApplyDefaultConfig(); - I.ApplyProfiles(profile); + + public void UpdateProfiles(TrackListEntry tle) + { + if (!NeedUpdateProfiles(tle, out var toApply)) + return; + + ApplyDefaultConfig(); + ApplyProfiles(profile); foreach (var (name, args) in toApply) { Console.WriteLine($"Applying auto profile: {name}"); - I.ProcessArgs(args); - I.appliedProfiles.Add(name); + ProcessArgs(args); + appliedProfiles.Add(name); } - I.ProcessArgs(I.arguments); - I.PostProcessArgs(); - - return true; + ProcessArgs(arguments); + PostProcessArgs(); } @@ -504,6 +535,7 @@ public class Config public void AddTemporaryConditions(FileConditions? cond, FileConditions? prefCond) { + throw new NotImplementedException("Code has been refactored; probably does not work."); if (cond != null) undoTempConds = necessaryCond.AddConditions(cond); if (prefCond != null) @@ -513,6 +545,7 @@ public class Config public void RestoreConditions() { + throw new NotImplementedException("Code has been refactored; probably does not work."); if (undoTempConds != null) necessaryCond.AddConditions(undoTempConds); if (undoTempPrefConds != null) diff --git a/slsk-batchdl/Download.cs b/slsk-batchdl/Download.cs index 7f000d3..d6b664a 100644 --- a/slsk-batchdl/Download.cs +++ b/slsk-batchdl/Download.cs @@ -11,17 +11,14 @@ using SearchResponse = Soulseek.SearchResponse; static class Download { - public static async Task DownloadFile(SearchResponse response, Soulseek.File file, string filePath, Track track, ProgressBar progress, CancellationToken? ct = null, CancellationTokenSource? searchCts = null) + public static async Task DownloadFile(SearchResponse response, Soulseek.File file, string filePath, Track track, ProgressBar progress, Config config, CancellationToken? ct = null, CancellationTokenSource? searchCts = null) { - if (Config.I.DoNotDownload) - throw new Exception(); - - await Program.WaitForLogin(); + await Program.WaitForLogin(config); Directory.CreateDirectory(Path.GetDirectoryName(filePath)); string origPath = filePath; filePath += ".incomplete"; - Printing.WriteLine($"Downloading: {track} to '{filePath}'", debugOnly: true); + Printing.WriteLineIf($"Downloading: {track} to '{filePath}'", config.debugInfo); var transferOptions = new TransferOptions( stateChanged: (state) => @@ -63,12 +60,12 @@ static class Download { retryCount++; - Printing.WriteLine($"Error while downloading: {e}", ConsoleColor.DarkYellow, debugOnly: true); + Printing.WriteLineIf($"Error while downloading: {e}", config.debugInfo, ConsoleColor.DarkYellow); if (retryCount >= maxRetries || IsConnectedAndLoggedIn()) throw; - await WaitForLogin(); + await WaitForLogin(config); } } } diff --git a/slsk-batchdl/Extractors/Bandcamp.cs b/slsk-batchdl/Extractors/Bandcamp.cs index 863a9ed..42042af 100644 --- a/slsk-batchdl/Extractors/Bandcamp.cs +++ b/slsk-batchdl/Extractors/Bandcamp.cs @@ -15,7 +15,7 @@ namespace Extractors return input.IsInternetUrl() && input.Contains("bandcamp.com"); } - public async Task GetTracks(string input, int maxTracks, int offset, bool reverse) + public async Task GetTracks(string input, int maxTracks, int offset, bool reverse, Config config) { var trackLists = new TrackLists(); bool isTrack = input.Contains("/track/"); @@ -77,15 +77,15 @@ namespace Extractors var track = new Track() { Artist = artist, Album = name, Type = TrackType.Album }; trackLists.AddEntry(new TrackListEntry(track)); - if (Config.I.setAlbumMinTrackCount || Config.I.setAlbumMaxTrackCount) + if (config.setAlbumMinTrackCount || config.setAlbumMaxTrackCount) { var trackTable = doc.DocumentNode.SelectSingleNode("//*[@id='track_table']"); int n = trackTable.SelectNodes(".//tr").Count; - if (Config.I.setAlbumMinTrackCount) + if (config.setAlbumMinTrackCount) track.MinAlbumTrackCount = n; - if (Config.I.setAlbumMaxTrackCount) + if (config.setAlbumMaxTrackCount) track.MaxAlbumTrackCount = n; } } diff --git a/slsk-batchdl/Extractors/Csv.cs b/slsk-batchdl/Extractors/Csv.cs index b0d36ad..3433e69 100644 --- a/slsk-batchdl/Extractors/Csv.cs +++ b/slsk-batchdl/Extractors/Csv.cs @@ -15,15 +15,15 @@ namespace Extractors return !input.IsInternetUrl() && input.EndsWith(".csv"); } - public async Task GetTracks(string input, int maxTracks, int offset, bool reverse) + public async Task GetTracks(string input, int maxTracks, int offset, bool reverse, Config config) { csvFilePath = Utils.ExpandUser(input); if (!File.Exists(csvFilePath)) throw new FileNotFoundException($"CSV file '{csvFilePath}' not found"); - var tracks = await ParseCsvIntoTrackInfo(csvFilePath, Config.I.artistCol, Config.I.titleCol, Config.I.lengthCol, - Config.I.albumCol, Config.I.descCol, Config.I.ytIdCol, Config.I.trackCountCol, Config.I.timeUnit, Config.I.ytParse); + var tracks = await ParseCsvIntoTrackInfo(csvFilePath, config.artistCol, config.titleCol, config.lengthCol, + config.albumCol, config.descCol, config.ytIdCol, config.trackCountCol, config.timeUnit, config.ytParse); if (reverse) tracks.Reverse(); @@ -58,7 +58,7 @@ namespace Extractors } catch (Exception e) { - Printing.WriteLine($"Error removing from source: {e}", debugOnly: true); + Printing.WriteLine($"Error removing from source: {e}"); } } } diff --git a/slsk-batchdl/Extractors/_Extractor.cs b/slsk-batchdl/Extractors/IExtractor.cs similarity index 97% rename from slsk-batchdl/Extractors/_Extractor.cs rename to slsk-batchdl/Extractors/IExtractor.cs index 5580228..a92224a 100644 --- a/slsk-batchdl/Extractors/_Extractor.cs +++ b/slsk-batchdl/Extractors/IExtractor.cs @@ -6,7 +6,7 @@ namespace Extractors { public interface IExtractor { - Task GetTracks(string input, int maxTracks, int offset, bool reverse); + Task GetTracks(string input, int maxTracks, int offset, bool reverse, Config config); Task RemoveTrackFromSource(Track track) => Task.CompletedTask; } diff --git a/slsk-batchdl/Extractors/List.cs b/slsk-batchdl/Extractors/List.cs index 9cad984..ec4ffed 100644 --- a/slsk-batchdl/Extractors/List.cs +++ b/slsk-batchdl/Extractors/List.cs @@ -14,7 +14,7 @@ namespace Extractors return !input.IsInternetUrl(); } - public async Task GetTracks(string input, int maxTracks, int offset, bool reverse) + public async Task GetTracks(string input, int maxTracks, int offset, bool reverse, Config config) { listFilePath = Utils.ExpandUser(input); @@ -44,28 +44,31 @@ namespace Extractors if (added >= maxTracks) break; - bool savedVal = Config.I.album; + bool isAlbum = false; if (line.StartsWith("a:")) { line = line[2..]; - Config.I.album = true; + isAlbum = true; } var fields = ParseLine(line); + if (isAlbum) + { + fields[0] = "album://" + fields[0]; + } + var (_, ex) = ExtractorRegistry.GetMatchingExtractor(fields[0]); - var tl = await ex.GetTracks(fields[0], int.MaxValue, 0, false); - - Config.I.album = savedVal; + var tl = await ex.GetTracks(fields[0], int.MaxValue, 0, false, config); foreach (var tle in tl.lists) { if (fields.Count >= 2) - tle.additionalConds = Config.ParseConditions(fields[1]); + tle.extractorCond = Config.ParseConditions(fields[1]); if (fields.Count >= 3) - tle.additionalPrefConds = Config.ParseConditions(fields[2]); + tle.extractorPrefCond = Config.ParseConditions(fields[2]); tle.defaultFolderName = foldername; tle.enablesIndexByDefault = true; @@ -143,7 +146,7 @@ namespace Extractors } catch (Exception e) { - Printing.WriteLine($"Error removing from source: {e}", debugOnly: true); + Printing.WriteLine($"Error removing from source: {e}"); } } } diff --git a/slsk-batchdl/Extractors/Spotify.cs b/slsk-batchdl/Extractors/Spotify.cs index ea85f62..699f310 100644 --- a/slsk-batchdl/Extractors/Spotify.cs +++ b/slsk-batchdl/Extractors/Spotify.cs @@ -18,22 +18,22 @@ namespace Extractors return input == "spotify-likes" || input.IsInternetUrl() && input.Contains("spotify.com"); } - public async Task GetTracks(string input, int maxTracks, int offset, bool reverse) + public async Task GetTracks(string input, int maxTracks, int offset, bool reverse, Config config) { var trackLists = new TrackLists(); int max = reverse ? int.MaxValue : maxTracks; int off = reverse ? 0 : offset; - bool needLogin = input == "spotify-likes" || Config.I.removeTracksFromSource; + bool needLogin = input == "spotify-likes" || config.removeTracksFromSource; - if (needLogin && Config.I.spotifyToken.Length == 0 && (Config.I.spotifyId.Length == 0 || Config.I.spotifySecret.Length == 0)) + if (needLogin && config.spotifyToken.Length == 0 && (config.spotifyId.Length == 0 || config.spotifySecret.Length == 0)) { Console.WriteLine("Error: Credentials are required when downloading liked music or removing from source playlists."); Environment.Exit(1); } - spotifyClient = new Spotify(Config.I.spotifyId, Config.I.spotifySecret, Config.I.spotifyToken, Config.I.spotifyRefresh); - await spotifyClient.Authorize(needLogin, Config.I.removeTracksFromSource); + spotifyClient = new Spotify(config.spotifyId, config.spotifySecret, config.spotifyToken, config.spotifyRefresh); + await spotifyClient.Authorize(needLogin, config.removeTracksFromSource); TrackListEntry? tle = null; @@ -53,10 +53,10 @@ namespace Extractors tle = new TrackListEntry(TrackType.Album); tle.source = source; - if (Config.I.setAlbumMinTrackCount) + if (config.setAlbumMinTrackCount) source.MinAlbumTrackCount = tracks.Count; - if (Config.I.setAlbumMaxTrackCount) + if (config.setAlbumMaxTrackCount) source.MaxAlbumTrackCount = tracks.Count; } else if (input.Contains("/artist/")) @@ -81,7 +81,7 @@ namespace Extractors { if (!needLogin && !spotifyClient.UsedDefaultCredentials) { - await spotifyClient.Authorize(true, Config.I.removeTracksFromSource); + await spotifyClient.Authorize(true, config.removeTracksFromSource); (playlistName, playlistUri, tracks) = await spotifyClient.GetPlaylist(input, max, off); } else if (!needLogin) @@ -117,7 +117,7 @@ namespace Extractors } catch (Exception e) { - Printing.WriteLine($"Error removing from source: {e}", debugOnly: true); + Printing.WriteLine($"Error removing from source: {e}"); } } } diff --git a/slsk-batchdl/Extractors/String.cs b/slsk-batchdl/Extractors/String.cs index 4f668b8..810d154 100644 --- a/slsk-batchdl/Extractors/String.cs +++ b/slsk-batchdl/Extractors/String.cs @@ -11,13 +11,21 @@ namespace Extractors return !input.IsInternetUrl(); } - public async Task GetTracks(string input, int maxTracks, int offset, bool reverse) + public async Task GetTracks(string input, int maxTracks, int offset, bool reverse, Config config) { + bool isAlbum = config.album; + + if (input.StartsWith("album://")) + { + isAlbum = true; + input = input[8..]; + } + var trackLists = new TrackLists(); - var music = ParseTrackArg(input, Config.I.album); + var music = ParseTrackArg(input, isAlbum); TrackListEntry tle; - if (Config.I.album || (music.Title.Length == 0 && music.Album.Length > 0)) + if (isAlbum || (music.Title.Length == 0 && music.Album.Length > 0)) { music.Type = TrackType.Album; tle = new TrackListEntry(music); diff --git a/slsk-batchdl/Extractors/YouTube.cs b/slsk-batchdl/Extractors/YouTube.cs index 131ad3b..3bb7ea8 100644 --- a/slsk-batchdl/Extractors/YouTube.cs +++ b/slsk-batchdl/Extractors/YouTube.cs @@ -21,24 +21,24 @@ namespace Extractors return input.IsInternetUrl() && (input.Contains("youtu.be") || input.Contains("youtube.com")); } - public async Task GetTracks(string input, int maxTracks, int offset, bool reverse) + public async Task GetTracks(string input, int maxTracks, int offset, bool reverse, Config config) { var trackLists = new TrackLists(); int max = reverse ? int.MaxValue : maxTracks; int off = reverse ? 0 : offset; - YouTube.apiKey = Config.I.ytKey; + YouTube.apiKey = config.ytKey; string name; List? deleted = null; List tracks = new(); - if (Config.I.getDeleted) + if (config.getDeleted) { Console.WriteLine("Getting deleted videos.."); var archive = new YouTube.YouTubeArchiveRetriever(); - deleted = await archive.RetrieveDeleted(input, printFailed: Config.I.deletedOnly); + deleted = await archive.RetrieveDeleted(input, printFailed: config.deletedOnly); } - if (!Config.I.deletedOnly) + if (!config.deletedOnly) { if (YouTube.apiKey.Length > 0) { diff --git a/slsk-batchdl/FileManager.cs b/slsk-batchdl/FileManager.cs index 5dd5eb8..947a574 100644 --- a/slsk-batchdl/FileManager.cs +++ b/slsk-batchdl/FileManager.cs @@ -10,10 +10,12 @@ public class FileManager readonly HashSet organized = new(); public string? remoteCommonDir { get; private set; } public string? defaultFolderName { get; private set; } + private readonly Config config; - public FileManager(TrackListEntry tle) + public FileManager(TrackListEntry tle, Config config) { this.tle = tle; + this.config = config; } public string GetSavePath(string sourceFname) @@ -23,7 +25,7 @@ public class FileManager public string GetSavePathNoExt(string sourceFname) { - string parent = Config.I.parentDir; + string parent = config.parentDir; string name = Utils.GetFileNameWithoutExtSlsk(sourceFname); if (tle.defaultFolderName != null) @@ -39,7 +41,7 @@ public class FileManager parent = Path.Join(parent, dirname, Path.GetDirectoryName(relpath) ?? ""); } - return Path.Join(parent, name).CleanPath(Config.I.invalidReplaceStr); + return Path.Join(parent, name).CleanPath(config.invalidReplaceStr); } public void SetRemoteCommonDir(string? remoteCommonDir) @@ -62,7 +64,7 @@ public class FileManager OrganizeAudio(track, track.FirstDownload); } - bool onlyAdditionalImages = Config.I.nameFormat.Length == 0; + bool onlyAdditionalImages = config.nameFormat.Length == 0; var nonAudioToOrganize = onlyAdditionalImages ? additionalImages : tracks.Where(t => t.IsNotAudio); @@ -86,14 +88,14 @@ public class FileManager if (track.DownloadPath.Length == 0 || !Utils.IsMusicFile(track.DownloadPath)) return; - if (Config.I.nameFormat.Length == 0) + if (config.nameFormat.Length == 0) { organized.Add(track); return; } - string pathPart = SubstituteValues(Config.I.nameFormat, track, file); - string newFilePath = Path.Join(Config.I.parentDir, pathPart + Path.GetExtension(track.DownloadPath)); + string pathPart = SubstituteValues(config.nameFormat, track, file); + string newFilePath = Path.Join(config.parentDir, pathPart + Path.GetExtension(track.DownloadPath)); try { @@ -132,13 +134,13 @@ public class FileManager organized.Add(track); } - static void MoveAndDeleteParent(string oldPath, string newPath) + void MoveAndDeleteParent(string oldPath, string newPath) { if (Utils.NormalizedPath(oldPath) != Utils.NormalizedPath(newPath)) { Directory.CreateDirectory(Path.GetDirectoryName(newPath)); Utils.Move(oldPath, newPath); - Utils.DeleteAncestorsIfEmpty(Path.GetDirectoryName(oldPath), Config.I.parentDir); + Utils.DeleteAncestorsIfEmpty(Path.GetDirectoryName(oldPath), config.parentDir); } } @@ -182,7 +184,7 @@ public class FileManager chosenOpt = Regex.Replace(chosenOpt, @"\([^()]*\)|[^()]+", match => { if (match.Value.StartsWith("(") && match.Value.EndsWith(")")) - return match.Value[1..^1].ReplaceInvalidChars(Config.I.invalidReplaceStr, removeSlash: false); + return match.Value[1..^1].ReplaceInvalidChars(config.invalidReplaceStr, removeSlash: false); else { TryGetVarValue(match.Value, file, slfile, track, out string res); @@ -203,7 +205,7 @@ public class FileManager char dirsep = Path.DirectorySeparatorChar; newName = newName.Replace('/', dirsep).Replace('\\', dirsep); var x = newName.Split(dirsep, StringSplitOptions.RemoveEmptyEntries); - newName = string.Join(dirsep, x.Select(x => x.ReplaceInvalidChars(Config.I.invalidReplaceStr).Trim(' ', '.'))); + newName = string.Join(dirsep, x.Select(x => x.ReplaceInvalidChars(config.invalidReplaceStr).Trim(' ', '.'))); return newName; } @@ -257,15 +259,14 @@ public class FileManager } return true; case "extractor": - res = Config.I.inputType.ToString(); break; + res = config.inputType.ToString(); break; case "default-folder": res = tle.defaultFolderName ?? tle.source.ToString(false); break; default: res = x; return false; } - res = res.ReplaceInvalidChars(Config.I.invalidReplaceStr); + res = res.ReplaceInvalidChars(config.invalidReplaceStr); return true; } } - diff --git a/slsk-batchdl/Help.cs b/slsk-batchdl/Help.cs index 06db8e1..c1fc984 100644 --- a/slsk-batchdl/Help.cs +++ b/slsk-batchdl/Help.cs @@ -473,7 +473,7 @@ public static class Help interactive (bool) "; - public static void PrintHelp(string option = "") + public static void PrintHelp(string? option = null) { string text = helpText; @@ -487,11 +487,11 @@ public static class Help { "config", configHelp }, }; - if (dict.ContainsKey(option)) + if (option != null && dict.ContainsKey(option)) text = dict[option]; else if (option == "all") text = $"{helpText}\n{string.Join('\n', dict.Values)}"; - else if (option.Length > 0) + else if (option != null) Console.WriteLine($"Unrecognized help option '{option}'"); var lines = text.Split('\n').Skip(1); @@ -499,5 +499,15 @@ public static class Help text = string.Join("\n", lines.Select(line => line.Length > minIndent ? line[minIndent..] : line)); Console.WriteLine(text); } + + public static void PrintHelpAndExitIfNeeded(string[] args) + { + int helpIdx = Array.FindLastIndex(args, x => x == "--help" || x == "-h"); + if (args.Length == 0 || helpIdx >= 0) + { + PrintHelp(helpIdx + 1 < args.Length ? args[helpIdx + 1] : null); + Environment.Exit(0); + } + } } diff --git a/slsk-batchdl/Models/TrackListEntry.cs b/slsk-batchdl/Models/TrackListEntry.cs index 6db4dfe..e7b39ea 100644 --- a/slsk-batchdl/Models/TrackListEntry.cs +++ b/slsk-batchdl/Models/TrackListEntry.cs @@ -1,4 +1,5 @@ using Enums; +using FileSkippers; namespace Models { @@ -12,8 +13,14 @@ namespace Models public bool gotoNextAfterSearch = false; public bool enablesIndexByDefault = false; public string? defaultFolderName = null; - public FileConditions? additionalConds = null; - public FileConditions? additionalPrefConds = null; + + public Config config = null!; + public FileConditions? extractorCond = null; + public FileConditions? extractorPrefCond = null; + public M3uEditor? playlistEditor = null; + public M3uEditor? indexEditor = null; + public FileSkipper? outputDirSkipper = null; + public FileSkipper? musicDirSkipper = null; public TrackListEntry(TrackType trackType) { diff --git a/slsk-batchdl/Models/TrackLists.cs b/slsk-batchdl/Models/TrackLists.cs index 158f284..8229377 100644 --- a/slsk-batchdl/Models/TrackLists.cs +++ b/slsk-batchdl/Models/TrackLists.cs @@ -5,6 +5,7 @@ namespace Models public class TrackLists { public List lists = new(); + public int Count => lists.Count; public TrackLists() { } diff --git a/slsk-batchdl/Program.cs b/slsk-batchdl/Program.cs index 749f7ba..a4e4ca8 100644 --- a/slsk-batchdl/Program.cs +++ b/slsk-batchdl/Program.cs @@ -18,57 +18,56 @@ using SlFile = Soulseek.File; static partial class Program { + const int updateInterval = 100; + private static bool initialized = false; public static bool skipUpdate = false; - public static bool initialized = false; - public static IExtractor? extractor; - public static SoulseekClient? client; - public static TrackLists? trackLists; - public static M3uEditor? playlistEditor; - public static M3uEditor? indexEditor; - public static FileSkipper? outputDirSkipper = null; - public static FileSkipper? musicDirSkipper = null; + + public static IExtractor extractor = null!; + public static TrackLists trackLists = null!; + public static SoulseekClient client = null!; + public static readonly ConcurrentDictionary searches = new(); public static readonly ConcurrentDictionary downloads = new(); - public static readonly ConcurrentDictionary userSuccessCount = new(); + public static readonly ConcurrentDictionary userSuccessCounts = new(); static async Task Main(string[] args) { Console.ResetColor(); Console.OutputEncoding = System.Text.Encoding.UTF8; + Help.PrintHelpAndExitIfNeeded(args); - Config.I.LoadAndParse(args); + var config = new Config(args); - if (Config.I.input.Length == 0) + if (config.input.Length == 0) throw new ArgumentException($"No input provided"); - (Config.I.inputType, extractor) = ExtractorRegistry.GetMatchingExtractor(Config.I.input, Config.I.inputType); + (config.inputType, extractor) = ExtractorRegistry.GetMatchingExtractor(config.input, config.inputType); - WriteLine($"Using extractor: {Config.I.inputType}", debugOnly: true); + WriteLineIf($"Using extractor: {config.inputType}", config.debugInfo); - trackLists = await extractor.GetTracks(Config.I.input, Config.I.maxTracks, Config.I.offset, Config.I.reverse); + trackLists = await extractor.GetTracks(config.input, config.maxTracks, config.offset, config.reverse, config); - WriteLine("Got tracks", debugOnly: true); + WriteLineIf("Got tracks", config.debugInfo); - Config.I.PostProcessArgs(); + config.PostProcessArgs(); - trackLists.UpgradeListTypes(Config.I.aggregate, Config.I.album); + trackLists.UpgradeListTypes(config.aggregate, config.album); trackLists.SetListEntryOptions(); - playlistEditor = new M3uEditor(trackLists, Config.I.writePlaylist ? M3uOption.Playlist : M3uOption.None, Config.I.offset); - indexEditor = new M3uEditor(trackLists, Config.I.writeIndex ? M3uOption.Index : M3uOption.None); + InitConfigs(config); await MainLoop(); - WriteLine("Mainloop done", debugOnly: true); + WriteLineIf("Mainloop done", config.debugInfo); } - public static async Task InitClientAndUpdateIfNeeded() + public static async Task InitClientAndUpdateIfNeeded(Config config) { if (initialized) return; - bool needLogin = !Config.I.PrintTracks; + bool needLogin = !config.PrintTracks; if (needLogin) { var connectionOptions = new ConnectionOptions(configureSocket: (socket) => @@ -82,61 +81,149 @@ static partial class Program var clientOptions = new SoulseekClientOptions( transferConnectionOptions: connectionOptions, serverConnectionOptions: connectionOptions, - listenPort: Config.I.listenPort + listenPort: config.listenPort ); client = new SoulseekClient(clientOptions); - if (!Config.I.useRandomLogin && (string.IsNullOrEmpty(Config.I.username) || string.IsNullOrEmpty(Config.I.password))) + if (!config.useRandomLogin && (string.IsNullOrEmpty(config.username) || string.IsNullOrEmpty(config.password))) throw new ArgumentException("No soulseek username or password"); - await Login(Config.I.useRandomLogin); + await Login(config, config.useRandomLogin); - Search.searchSemaphore = new RateLimitedSemaphore(Config.I.searchesPerTime, TimeSpan.FromSeconds(Config.I.searchRenewTime)); + Search.searchSemaphore = new RateLimitedSemaphore(config.searchesPerTime, TimeSpan.FromSeconds(config.searchRenewTime)); } bool needUpdate = needLogin; if (needUpdate) { - var UpdateTask = Task.Run(() => Update()); - WriteLine("Update started", debugOnly: true); + var UpdateTask = Task.Run(() => Update(config)); + WriteLineIf("Update started", config.debugInfo); } initialized = true; } - static void InitFileSkippers() + static void InitConfigs(Config defaultConfig) { - if (Config.I.skipExisting) + if (trackLists.Count == 0) + return; + + void initEditors(TrackListEntry tle, Config config) { - FileConditions? cond = null; + tle.playlistEditor = new M3uEditor(trackLists, config.writePlaylist ? M3uOption.Playlist : M3uOption.None, config.offset); + tle.indexEditor = new M3uEditor(trackLists, config.writeIndex ? M3uOption.Index : M3uOption.None); + } - if (Config.I.skipCheckPrefCond) + void initFileSkippers(TrackListEntry tle, Config config) + { + if (config.skipExisting) { - cond = Config.I.necessaryCond.With(Config.I.preferredCond); - } - else if (Config.I.skipCheckCond) - { - cond = Config.I.necessaryCond; - } + FileConditions? cond = null; - outputDirSkipper = FileSkipperRegistry.GetSkipper(Config.I.skipMode, Config.I.parentDir, cond, indexEditor); + if (config.skipCheckPrefCond) + { + cond = config.necessaryCond.With(config.preferredCond); + } + else if (config.skipCheckCond) + { + cond = config.necessaryCond; + } - if (Config.I.skipMusicDir.Length > 0) - { - if (!Directory.Exists(Config.I.skipMusicDir)) - Console.WriteLine("Error: Music directory does not exist"); - else - musicDirSkipper = FileSkipperRegistry.GetSkipper(Config.I.skipModeMusicDir, Config.I.skipMusicDir, cond, indexEditor); + tle.outputDirSkipper = FileSkipperRegistry.GetSkipper(config.skipMode, config.parentDir, cond, tle.indexEditor); + + if (config.skipMusicDir.Length > 0) + { + if (!Directory.Exists(config.skipMusicDir)) + Console.WriteLine("Error: Music directory does not exist"); + else + tle.musicDirSkipper = FileSkipperRegistry.GetSkipper(config.skipModeMusicDir, config.skipMusicDir, cond, tle.indexEditor); + } } } + + foreach (var tle in trackLists.lists) + { + tle.config = defaultConfig.Copy(); + tle.config.UpdateProfiles(tle); + + if (tle.extractorCond != null) + { + tle.config.necessaryCond = tle.config.necessaryCond.With(tle.extractorCond); + tle.extractorCond = null; + } + if (tle.extractorPrefCond != null) + { + tle.config.preferredCond = tle.config.preferredCond.With(tle.extractorPrefCond); + tle.extractorPrefCond = null; + } + + initEditors(tle, tle.config); + initFileSkippers(tle, tle.config); + } + + //defaultConfig.UpdateProfiles(trackLists[0]); + //trackLists[0].config = defaultConfig; + //initEditors(trackLists[0], defaultConfig); + //initFileSkippers(trackLists[0], defaultConfig); + + //var configs = new Dictionary() { { defaultConfig, trackLists[0] } }; + + //// configs, skippers, and editors are assigned to every individual tle (since they may change based + //// on auto-profiles). This loop re-uses existing configs/skippers/editors whenever autoprofiles + //// don't change. Otherwise, a new file skipper would be created for every tle, and would require + //// indexing every time, even if the directory to be indexed is unchanged. + //foreach (var tle in trackLists.lists.Skip(1)) + //{ + // bool needUpdate = true; + + // foreach (var (config, exampleTle) in configs) + // { + // if (!config.NeedUpdateProfiles(tle)) + // { + // tle.config = config; + + // if (exampleTle == null) + // { + // initEditors(tle, config); + // initFileSkippers(tle, config); + // configs[config] = tle; + // } + // else + // { + // tle.playlistEditor = exampleTle.playlistEditor; + // tle.indexEditor = exampleTle.indexEditor; + // tle.outputDirSkipper = exampleTle.outputDirSkipper; + // tle.musicDirSkipper = exampleTle.musicDirSkipper; + // } + + // needUpdate = false; + // break; + // } + // } + + // bool hasExtractorConditions = tle.extractorCond != null || tle.extractorPrefCond != null; + + // if (!needUpdate) + // continue; + + // var newConfig = defaultConfig.Copy(); + // newConfig.UpdateProfiles(tle); + // configs[newConfig] = tle; + + // tle.config = newConfig; + + // // todo: only create new instances if a relevant config item has changed + // initEditors(tle, newConfig); + // initFileSkippers(tle, newConfig); + //} } - static void PreprocessTracks(TrackListEntry tle) + static void PreprocessTracks(Config config, TrackListEntry tle) { - PreprocessTrack(tle.source); + PreprocessTrack(config, tle.source); for (int k = 0; k < tle.list.Count; k++) { @@ -144,31 +231,31 @@ static partial class Program { for (int i = 0; i < ls.Count; i++) { - PreprocessTrack(ls[i]); + PreprocessTrack(config, ls[i]); } } } } - static void PreprocessTrack(Track track) + static void PreprocessTrack(Config config, Track track) { - if (Config.I.removeFt) + if (config.removeFt) { track.Title = track.Title.RemoveFt(); track.Artist = track.Artist.RemoveFt(); } - if (Config.I.removeBrackets) + if (config.removeBrackets) { track.Title = track.Title.RemoveSquareBrackets(); } - if (Config.I.regexToReplace.Title.Length + Config.I.regexToReplace.Artist.Length + Config.I.regexToReplace.Album.Length > 0) + if (config.regexToReplace.Title.Length + config.regexToReplace.Artist.Length + config.regexToReplace.Album.Length > 0) { - track.Title = Regex.Replace(track.Title, Config.I.regexToReplace.Title, Config.I.regexReplaceBy.Title); - track.Artist = Regex.Replace(track.Artist, Config.I.regexToReplace.Artist, Config.I.regexReplaceBy.Artist); - track.Album = Regex.Replace(track.Album, Config.I.regexToReplace.Album, Config.I.regexReplaceBy.Album); + track.Title = Regex.Replace(track.Title, config.regexToReplace.Title, config.regexReplaceBy.Title); + track.Artist = Regex.Replace(track.Artist, config.regexToReplace.Artist, config.regexReplaceBy.Artist); + track.Album = Regex.Replace(track.Album, config.regexToReplace.Album, config.regexReplaceBy.Album); } - if (Config.I.artistMaybeWrong) + if (config.artistMaybeWrong) { track.ArtistMaybeWrong = true; } @@ -179,45 +266,26 @@ static partial class Program } - static void PrepareListEntry(TrackListEntry tle, bool isFirstEntry) + static void PrepareListEntry(Config config, TrackListEntry tle, bool isFirstEntry) { - Config.I.RestoreConditions(); - - bool changed = Config.UpdateProfiles(tle); - - Config.I.AddTemporaryConditions(tle.additionalConds, tle.additionalPrefConds); - string m3uPath, indexPath; - if (Config.I.m3uFilePath.Length > 0) - m3uPath = Config.I.m3uFilePath; + if (config.m3uFilePath.Length > 0) + m3uPath = config.m3uFilePath; else - m3uPath = Path.Join(Config.I.parentDir, tle.defaultFolderName, "_playlist.m3u8"); + m3uPath = Path.Join(config.parentDir, tle.defaultFolderName, "_playlist.m3u8"); - if (Config.I.indexFilePath.Length > 0) - indexPath = Config.I.indexFilePath; + if (config.indexFilePath.Length > 0) + indexPath = config.indexFilePath; else - indexPath = Path.Join(Config.I.parentDir, tle.defaultFolderName, "_index.sldl"); + indexPath = Path.Join(config.parentDir, tle.defaultFolderName, "_index.sldl"); - indexEditor.option = Config.I.writeIndex ? M3uOption.Index : M3uOption.None; - indexEditor.SetPathAndLoad(indexPath); // does nothing if the path is unchanged + if (config.writePlaylist) + tle.playlistEditor?.SetPathAndLoad(m3uPath); + if (config.writeIndex) + tle.indexEditor?.SetPathAndLoad(indexPath); - if (Config.I.writePlaylist) - { - playlistEditor.option = M3uOption.Playlist; - playlistEditor.SetPathAndLoad(m3uPath); - } - else - { - playlistEditor.option = M3uOption.None; - } - - if (changed || isFirstEntry) - { - InitFileSkippers(); // todo: only do this when a relevant config item changes - } - - PreprocessTracks(tle); + PreprocessTracks(config, tle); } @@ -228,47 +296,48 @@ static partial class Program Console.WriteLine(); var tle = trackLists[i]; + var config = tle.config; - PrepareListEntry(tle, isFirstEntry: i == 0); + PrepareListEntry(config, tle, isFirstEntry: i == 0); var existing = new List(); var notFound = new List(); - if (Config.I.skipNotFound && !Config.I.PrintResults) + if (config.skipNotFound && !config.PrintResults) { - if (tle.sourceCanBeSkipped && SetNotFoundLastTime(tle.source)) + if (tle.sourceCanBeSkipped && SetNotFoundLastTime(config, tle.source, tle.indexEditor)) notFound.Add(tle.source); if (tle.source.State != TrackState.NotFoundLastTime && !tle.needSourceSearch) { foreach (var tracks in tle.list) - notFound.AddRange(DoSkipNotFound(tracks)); + notFound.AddRange(DoSkipNotFound(config, tracks, tle.indexEditor)); } } - if (Config.I.skipExisting && !Config.I.PrintResults && tle.source.State != TrackState.NotFoundLastTime) + if (config.skipExisting && !config.PrintResults && tle.source.State != TrackState.NotFoundLastTime) { - if (tle.sourceCanBeSkipped && SetExisting(tle.source)) + if (tle.sourceCanBeSkipped && SetExisting(tle, config, tle.source)) existing.Add(tle.source); if (tle.source.State != TrackState.AlreadyExists && !tle.needSourceSearch) { foreach (var tracks in tle.list) - existing.AddRange(DoSkipExisting(tracks)); + existing.AddRange(DoSkipExisting(tle, config, tracks)); } } - if (Config.I.PrintTracks) + if (config.PrintTracks) { if (tle.source.Type == TrackType.Normal) { - PrintTracksTbd(tle.list[0].Where(t => t.State == TrackState.Initial).ToList(), existing, notFound, tle.source.Type); + PrintTracksTbd(tle.list[0].Where(t => t.State == TrackState.Initial).ToList(), existing, notFound, tle.source.Type, config); } else { var tl = new List(); if (tle.source.State == TrackState.Initial) tl.Add(tle.source); - PrintTracksTbd(tl, existing, notFound, tle.source.Type, summary: false); + PrintTracksTbd(tl, existing, notFound, tle.source.Type, config, summary: false); } continue; } @@ -290,7 +359,7 @@ static partial class Program if (tle.needSourceSearch) { - await InitClientAndUpdateIfNeeded(); + await InitClientAndUpdateIfNeeded(config); Console.WriteLine($"{tle.source.Type} download: {tle.source.ToString(true)}, searching.."); @@ -299,17 +368,17 @@ static partial class Program if (tle.source.Type == TrackType.Album) { - tle.list = await Search.GetAlbumDownloads(tle.source, responseData); + tle.list = await Search.GetAlbumDownloads(tle.source, responseData, config); foundSomething = tle.list.Count > 0 && tle.list[0].Count > 0; } else if (tle.source.Type == TrackType.Aggregate) { - tle.list.Insert(0, await Search.GetAggregateTracks(tle.source, responseData)); + tle.list.Insert(0, await Search.GetAggregateTracks(tle.source, responseData, config)); foundSomething = tle.list.Count > 0 && tle.list[0].Count > 0; } else if (tle.source.Type == TrackType.AlbumAggregate) { - var res = await Search.GetAggregateAlbums(tle.source, responseData); + var res = await Search.GetAggregateAlbums(tle.source, responseData, config); foreach (var item in res) { @@ -327,20 +396,20 @@ static partial class Program var lockedFiles = responseData.lockedFilesCount > 0 ? $" (Found {responseData.lockedFilesCount} locked files)" : ""; Console.WriteLine($"No results.{lockedFiles}"); - if (!Config.I.PrintResults) + if (!config.PrintResults) { tle.source.State = TrackState.Failed; tle.source.FailureReason = FailureReason.NoSuitableFileFound; - indexEditor.Update(); + tle.indexEditor?.Update(); } continue; } - if (Config.I.skipExisting && tle.needSkipExistingAfterSearch) + if (config.skipExisting && tle.needSkipExistingAfterSearch) { foreach (var tracks in tle.list) - existing.AddRange(DoSkipExisting(tracks)); + existing.AddRange(DoSkipExisting(tle, config, tracks)); } if (tle.gotoNextAfterSearch) @@ -349,18 +418,18 @@ static partial class Program } } - if (Config.I.PrintResults) + if (config.PrintResults) { - await PrintResults(tle, existing, notFound); + await PrintResults(tle, existing, notFound, config); continue; } - indexEditor.Update(); - playlistEditor.Update(); + tle.indexEditor?.Update(); + tle.playlistEditor?.Update(); if (tle.source.Type != TrackType.Album) { - PrintTracksTbd(tle.list[0].Where(t => t.State == TrackState.Initial).ToList(), existing, notFound, tle.source.Type); + PrintTracksTbd(tle.list[0].Where(t => t.State == TrackState.Initial).ToList(), existing, notFound, tle.source.Type, config); } if (notFound.Count + existing.Count >= tle.list.Sum(x => x.Count)) @@ -368,35 +437,35 @@ static partial class Program continue; } - await InitClientAndUpdateIfNeeded(); + await InitClientAndUpdateIfNeeded(config); if (tle.source.Type == TrackType.Normal) { - await DownloadNormal(tle); + await DownloadNormal(config, tle); } else if (tle.source.Type == TrackType.Album) { - await DownloadAlbum(tle); + await DownloadAlbum(config, tle); } else if (tle.source.Type == TrackType.Aggregate) { - await DownloadNormal(tle); + await DownloadNormal(config, tle); } } - if (!Config.I.DoNotDownload && (trackLists.lists.Count > 0 || trackLists.Flattened(false, false).Skip(1).Any())) + if (!trackLists[^1].config.DoNotDownload && (trackLists.lists.Count > 0 || trackLists.Flattened(false, false).Skip(1).Any())) { PrintComplete(trackLists); } } - static List DoSkipExisting(List tracks) + static List DoSkipExisting(TrackListEntry tle, Config config, List tracks) { var existing = new List(); foreach (var track in tracks) { - if (SetExisting(track)) + if (SetExisting(tle, config, track)) { existing.Add(track); } @@ -405,27 +474,27 @@ static partial class Program } - static bool SetExisting(Track track) + static bool SetExisting(TrackListEntry tle, Config config, Track track) { string? path = null; - if (outputDirSkipper != null) + if (tle.outputDirSkipper != null) { - if (!outputDirSkipper.IndexIsBuilt) - outputDirSkipper.BuildIndex(); + if (!tle.outputDirSkipper.IndexIsBuilt) + tle.outputDirSkipper.BuildIndex(); - outputDirSkipper.TrackExists(track, out path); + tle.outputDirSkipper.TrackExists(track, out path); } - if (path == null && musicDirSkipper != null) + if (path == null && tle.musicDirSkipper != null) { - if (!musicDirSkipper.IndexIsBuilt) + if (!tle.musicDirSkipper.IndexIsBuilt) { Console.WriteLine($"Building music directory index.."); - musicDirSkipper.BuildIndex(); + tle.musicDirSkipper.BuildIndex(); } - musicDirSkipper.TrackExists(track, out path); + tle.musicDirSkipper.TrackExists(track, out path); } if (path != null) @@ -438,12 +507,12 @@ static partial class Program } - static List DoSkipNotFound(List tracks) + static List DoSkipNotFound(Config config, List tracks, M3uEditor indexEditor) { var notFound = new List(); foreach (var track in tracks) { - if (SetNotFoundLastTime(track)) + if (SetNotFoundLastTime(config, track, indexEditor)) { notFound.Add(track); } @@ -452,7 +521,7 @@ static partial class Program } - static bool SetNotFoundLastTime(Track track) + static bool SetNotFoundLastTime(Config config, Track track, M3uEditor indexEditor) { if (indexEditor.TryGetPreviousRunResult(track, out var prevTrack)) { @@ -466,47 +535,47 @@ static partial class Program } - static async Task DownloadNormal(TrackListEntry tle) + static async Task DownloadNormal(Config config, TrackListEntry tle) { var tracks = tle.list[0]; - var semaphore = new SemaphoreSlim(Config.I.concurrentProcesses); + var semaphore = new SemaphoreSlim(config.concurrentProcesses); - var organizer = new FileManager(tle); + var organizer = new FileManager(tle, config); var downloadTasks = tracks.Select(async (track, index) => { using var cts = new CancellationTokenSource(); - await DownloadTask(tle, track, semaphore, organizer, cts, false, true, true); - indexEditor.Update(); - playlistEditor.Update(); + await DownloadTask(config, tle, track, semaphore, organizer, cts, false, true, true); + tle.indexEditor?.Update(); + tle.playlistEditor?.Update(); }); await Task.WhenAll(downloadTasks); - if (Config.I.removeTracksFromSource && tracks.All(t => t.State == TrackState.Downloaded || t.State == TrackState.AlreadyExists)) + if (config.removeTracksFromSource && tracks.All(t => t.State == TrackState.Downloaded || t.State == TrackState.AlreadyExists)) await extractor.RemoveTrackFromSource(tle.source); } - static async Task DownloadAlbum(TrackListEntry tle) + static async Task DownloadAlbum(Config config, TrackListEntry tle) { - var organizer = new FileManager(tle); + var organizer = new FileManager(tle, config); List? tracks = null; var retrievedFolders = new HashSet(); bool succeeded = false; string? soulseekDir = null; int index = 0; - while (tle.list.Count > 0 && !Config.I.albumArtOnly) + while (tle.list.Count > 0 && !config.albumArtOnly) { - bool wasInteractive = Config.I.interactiveMode; + bool wasInteractive = config.interactiveMode; bool retrieveCurrent = true; index = 0; - if (Config.I.interactiveMode) + if (config.interactiveMode) { - (index, tracks, retrieveCurrent) = await InteractiveModeAlbum(tle.list, !Config.I.noBrowseFolder, retrievedFolders); + (index, tracks, retrieveCurrent) = await InteractiveModeAlbum(config, tle.list, !config.noBrowseFolder, retrievedFolders); if (index == -1) break; } else @@ -518,7 +587,7 @@ static partial class Program organizer.SetRemoteCommonDir(soulseekDir); - if (!Config.I.interactiveMode && !wasInteractive) + if (!config.interactiveMode && !wasInteractive) { Console.WriteLine(); PrintAlbum(tracks); @@ -529,9 +598,9 @@ static partial class Program try { - await RunAlbumDownloads(tle, organizer, tracks, semaphore, cts); + await RunAlbumDownloads(config, tle, organizer, tracks, semaphore, cts); - if (!Config.I.noBrowseFolder && retrieveCurrent && !retrievedFolders.Contains(soulseekDir)) + if (!config.noBrowseFolder && retrieveCurrent && !retrievedFolders.Contains(soulseekDir)) { Console.WriteLine("Getting all files in folder..."); @@ -541,7 +610,7 @@ static partial class Program if (newFilesFound > 0) { Console.WriteLine($"Found {newFilesFound} more files in the directory, downloading:"); - await RunAlbumDownloads(tle, organizer, tracks, semaphore, cts); + await RunAlbumDownloads(config, tle, organizer, tracks, semaphore, cts); } else { @@ -554,7 +623,7 @@ static partial class Program } catch (OperationCanceledException) { - OnAlbumFail(tracks); + OnAlbumFail(config, tracks); } organizer.SetRemoteCommonDir(null); @@ -563,15 +632,15 @@ static partial class Program if (succeeded) { - await OnAlbumSuccess(tle, tracks); + await OnAlbumSuccess(config, tle, tracks); } List? additionalImages = null; - if (Config.I.albumArtOnly || succeeded && Config.I.albumArtOption != AlbumArtOption.Default) + if (config.albumArtOnly || succeeded && config.albumArtOption != AlbumArtOption.Default) { Console.WriteLine($"\nDownloading additional images:"); - additionalImages = await DownloadImages(tle, tle.list, Config.I.albumArtOption, tle.list[index]); + additionalImages = await DownloadImages(config, tle, tle.list, config.albumArtOption, tle.list[index]); tracks?.AddRange(additionalImages); } @@ -580,22 +649,22 @@ static partial class Program organizer.OrganizeAlbum(tracks, additionalImages); } - indexEditor.Update(); - playlistEditor.Update(); + tle.indexEditor?.Update(); + tle.playlistEditor?.Update(); } - static async Task RunAlbumDownloads(TrackListEntry tle, FileManager organizer, List tracks, SemaphoreSlim semaphore, CancellationTokenSource cts) + static async Task RunAlbumDownloads(Config config, TrackListEntry tle, FileManager organizer, List tracks, SemaphoreSlim semaphore, CancellationTokenSource cts) { var downloadTasks = tracks.Select(async track => { - await DownloadTask(tle, track, semaphore, organizer, cts, true, true, true); + await DownloadTask(config, tle, track, semaphore, organizer, cts, true, true, true); }); await Task.WhenAll(downloadTasks); } - static async Task OnAlbumSuccess(TrackListEntry tle, List? tracks) + static async Task OnAlbumSuccess(Config config, TrackListEntry tle, List? tracks) { if (tracks == null) return; @@ -607,7 +676,7 @@ static partial class Program tle.source.State = TrackState.Downloaded; tle.source.DownloadPath = Utils.GreatestCommonDirectory(downloadedAudio.Select(t => t.DownloadPath)); - if (Config.I.removeTracksFromSource) + if (config.removeTracksFromSource) { await extractor.RemoveTrackFromSource(tle.source); } @@ -615,9 +684,9 @@ static partial class Program } - static void OnAlbumFail(List? tracks) + static void OnAlbumFail(Config config, List? tracks) { - if (tracks == null || Config.I.IgnoreAlbumFail) + if (tracks == null || config.IgnoreAlbumFail) return; foreach (var track in tracks) @@ -626,18 +695,18 @@ static partial class Program { try { - if (Config.I.DeleteAlbumOnFail) + if (config.DeleteAlbumOnFail) { File.Delete(track.DownloadPath); } - else if (Config.I.failedAlbumPath.Length > 0) + else if (config.failedAlbumPath.Length > 0) { - var newPath = Path.Join(Config.I.failedAlbumPath, Path.GetRelativePath(Config.I.parentDir, track.DownloadPath)); + var newPath = Path.Join(config.failedAlbumPath, Path.GetRelativePath(config.parentDir, track.DownloadPath)); Directory.CreateDirectory(Path.GetDirectoryName(newPath)); Utils.Move(track.DownloadPath, newPath); } - Utils.DeleteAncestorsIfEmpty(Path.GetDirectoryName(track.DownloadPath), Config.I.parentDir); + Utils.DeleteAncestorsIfEmpty(Path.GetDirectoryName(track.DownloadPath), config.parentDir); } catch (Exception e) { @@ -648,13 +717,13 @@ static partial class Program } - static async Task> DownloadImages(TrackListEntry tle, List> downloads, AlbumArtOption option, List? chosenAlbum) + static async Task> DownloadImages(Config config, TrackListEntry tle, List> downloads, AlbumArtOption option, List? chosenAlbum) { var downloadedImages = new List(); long mSize = 0; int mCount = 0; - var fileManager = new FileManager(tle); + var fileManager = new FileManager(tle, config); if (chosenAlbum != null) { @@ -727,12 +796,12 @@ static partial class Program while (albumArtLists.Count > 0) { int index = 0; - bool wasInteractive = Config.I.interactiveMode; + bool wasInteractive = config.interactiveMode; List tracks; - if (Config.I.interactiveMode) + if (config.interactiveMode) { - (index, tracks, _) = await InteractiveModeAlbum(albumArtLists, false, null); + (index, tracks, _) = await InteractiveModeAlbum(config, albumArtLists, false, null); if (index == -1) break; } else @@ -748,7 +817,7 @@ static partial class Program return downloadedImages; } - if (!Config.I.interactiveMode && !wasInteractive) + if (!config.interactiveMode && !wasInteractive) { Console.WriteLine(); PrintAlbum(tracks); @@ -762,7 +831,7 @@ static partial class Program foreach (var track in tracks) { using var cts = new CancellationTokenSource(); - await DownloadTask(null, track, semaphore, fileManager, cts, false, false, false); + await DownloadTask(config, tle, track, semaphore, fileManager, cts, false, false, false); if (track.State == TrackState.Downloaded) downloadedImages.Add(track); @@ -778,30 +847,30 @@ static partial class Program } - static async Task DownloadTask(TrackListEntry? tle, Track track, SemaphoreSlim semaphore, FileManager organizer, CancellationTokenSource? cts, bool cancelOnFail, bool removeFromSource, bool organize) + static async Task DownloadTask(Config config, TrackListEntry? tle, Track track, SemaphoreSlim semaphore, FileManager organizer, CancellationTokenSource? cts, bool cancelOnFail, bool removeFromSource, bool organize) { if (track.State != TrackState.Initial) return; await semaphore.WaitAsync(cts.Token); - int tries = Config.I.unknownErrorRetries; + int tries = config.unknownErrorRetries; string savedFilePath = ""; SlFile? chosenFile = null; while (tries > 0) { - await WaitForLogin(); + await WaitForLogin(config); cts.Token.ThrowIfCancellationRequested(); try { - (savedFilePath, chosenFile) = await Search.SearchAndDownload(track, organizer, cts); + (savedFilePath, chosenFile) = await Search.SearchAndDownload(track, organizer, config, cts); } catch (Exception ex) { - WriteLine($"Error: {ex}", debugOnly: true); + WriteLineIf($"Error: {ex}", config.debugInfo); if (!IsConnectedAndLoggedIn()) { continue; @@ -844,7 +913,7 @@ static partial class Program track.DownloadPath = savedFilePath; } - if (removeFromSource && Config.I.removeTracksFromSource) + if (removeFromSource && config.removeTracksFromSource) { try { @@ -865,16 +934,16 @@ static partial class Program } } - if (Config.I.onComplete.Length > 0) + if (config.onComplete.Length > 0) { - OnComplete(Config.I.onComplete, track); + OnComplete(config, config.onComplete, track); } semaphore.Release(); } - static async Task<(int index, List tracks, bool retrieveFolder)> InteractiveModeAlbum(List> list, bool retrieveFolder, HashSet? retrievedFolders) + static async Task<(int index, List tracks, bool retrieveFolder)> InteractiveModeAlbum(Config config, List> list, bool retrieveFolder, HashSet? retrievedFolders) { int aidx = 0; static string interactiveModeLoop() // bug: characters don't disappear when backspacing @@ -944,7 +1013,7 @@ static partial class Program case "s": return (-1, new List(), false); case "q": - Config.I.interactiveMode = false; + config.interactiveMode = false; return (aidx, tracks, true); case "r": if (!retrieveFolder) @@ -1002,7 +1071,7 @@ static partial class Program } - static async Task Update() + static async Task Update(Config config) { while (true) { @@ -1024,7 +1093,7 @@ static partial class Program { lock (val) { - if ((DateTime.Now - val.UpdateLastChangeTime()).TotalMilliseconds > Config.I.maxStaleTime) + if ((DateTime.Now - val.UpdateLastChangeTime()).TotalMilliseconds > config.maxStaleTime) { val.stalled = true; val.UpdateText(); @@ -1051,10 +1120,10 @@ static partial class Program && !client.State.HasFlag(SoulseekClientStates.Connecting)) { WriteLine($"\nDisconnected, logging in\n", ConsoleColor.DarkYellow, true); - try { await Login(Config.I.useRandomLogin); } + try { await Login(config, config.useRandomLogin); } catch (Exception ex) { - string banMsg = Config.I.useRandomLogin ? "" : " (possibly a 30-minute ban caused by frequent searches)"; + string banMsg = config.useRandomLogin ? "" : " (possibly a 30-minute ban caused by frequent searches)"; WriteLine($"{ex.Message}{banMsg}", ConsoleColor.DarkYellow, true); } } @@ -1074,14 +1143,14 @@ static partial class Program } } - await Task.Delay(Config.I.updateDelay); + await Task.Delay(updateInterval); } } - static async Task Login(bool random = false, int tries = 3) + static async Task Login(Config config, bool random = false, int tries = 3) { - string user = Config.I.username, pass = Config.I.password; + string user = config.username, pass = config.password; if (random) { var r = new Random(); @@ -1096,30 +1165,30 @@ static partial class Program { try { - WriteLine($"Connecting {user}", debugOnly: true); + WriteLineIf($"Connecting {user}", config.debugInfo); await client.ConnectAsync(user, pass); - if (!Config.I.noModifyShareCount) + if (!config.noModifyShareCount) { - WriteLine($"Setting share count", debugOnly: true); + WriteLineIf($"Setting share count", config.debugInfo); await client.SetSharedCountsAsync(20, 100); } break; } catch (Exception e) { - WriteLine($"Exception while logging in: {e}", debugOnly: true); + WriteLineIf($"Exception while logging in: {e}", config.debugInfo); if (!(e is Soulseek.AddressException || e is System.TimeoutException) && --tries == 0) throw; } await Task.Delay(500); - WriteLine($"Retry login {user}", debugOnly: true); + WriteLineIf($"Retry login {user}", config.debugInfo); } - WriteLine($"Logged in {user}", debugOnly: true); + WriteLineIf($"Logged in {user}", config.debugInfo); } - static void OnComplete(string onComplete, Track track) + static void OnComplete(Config config, string onComplete, Track track) { if (onComplete.Length == 0) return; @@ -1159,7 +1228,7 @@ static partial class Program .Replace("{failure-reason}", track.FailureReason.ToString()) .Replace("{path}", track.DownloadPath) .Replace("{state}", track.State.ToString()) - .Replace("{extractor}", Config.I.inputType.ToString()) + .Replace("{extractor}", config.inputType.ToString()) .Trim(); if (onComplete[0] == '"') @@ -1191,7 +1260,7 @@ static partial class Program startInfo.UseShellExecute = useShellExecute; process.StartInfo = startInfo; - WriteLine($"on-complete: FileName={startInfo.FileName}, Arguments={startInfo.Arguments}", debugOnly: true); + WriteLineIf($"on-complete: FileName={startInfo.FileName}, Arguments={startInfo.Arguments}", config.debugInfo); process.Start(); @@ -1205,11 +1274,11 @@ static partial class Program } - public static async Task WaitForLogin() + public static async Task WaitForLogin(Config config) { while (true) { - WriteLine($"Wait for login, state: {client.State}", debugOnly: true); + WriteLineIf($"Wait for login, state: {client.State}", config.debugInfo); if (IsConnectedAndLoggedIn()) break; await Task.Delay(1000); @@ -1222,5 +1291,3 @@ static partial class Program return client != null && client.State.HasFlag(SoulseekClientStates.Connected) && client.State.HasFlag(SoulseekClientStates.LoggedIn); } } - - diff --git a/slsk-batchdl/Search.cs b/slsk-batchdl/Search.cs index 605d205..4c69632 100644 --- a/slsk-batchdl/Search.cs +++ b/slsk-batchdl/Search.cs @@ -16,14 +16,14 @@ static class Search public static RateLimitedSemaphore? searchSemaphore; // very messy function that does everything - public static async Task<(string, SlFile?)> SearchAndDownload(Track track, FileManager organizer, CancellationTokenSource? cts = null) + public static async Task<(string, SlFile?)> SearchAndDownload(Track track, FileManager organizer, Config config, CancellationTokenSource? cts = null) { - if (Config.I.DoNotDownload) + if (config.DoNotDownload) throw new Exception(); IEnumerable<(SlResponse response, SlFile file)>? orderedResults = null; var responseData = new ResponseData(); - var progress = Printing.GetProgressBar(); + var progress = Printing.GetProgressBar(config); var results = new SlDictionary(); var fsResults = new SlDictionary(); using var searchCts = new CancellationTokenSource(); @@ -58,7 +58,7 @@ static class Search saveFilePath = organizer.GetSavePath(f.Filename); fsUser = r.Username; chosenFile = f; - downloadTask = Download.DownloadFile(r, f, saveFilePath, track, progress, cts?.Token, searchCts); + downloadTask = Download.DownloadFile(r, f, saveFilePath, track, progress, config, cts?.Token, searchCts); } } } @@ -72,17 +72,17 @@ static class Search foreach (var file in r.Files) results.TryAdd(r.Username + '\\' + file.Filename, (r, file)); - if (Config.I.fastSearch && userSuccessCount.GetValueOrDefault(r.Username, 0) > Config.I.downrankOn) + if (config.fastSearch && userSuccessCounts.GetValueOrDefault(r.Username, 0) > config.downrankOn) { var f = r.Files.First(); - if (r.HasFreeUploadSlot && r.UploadSpeed / 1024.0 / 1024.0 >= Config.I.fastSearchMinUpSpeed - && FileConditions.BracketCheck(track, InferTrack(f.Filename, track)) && Config.I.preferredCond.FileSatisfies(f, track, r)) + if (r.HasFreeUploadSlot && r.UploadSpeed / 1024.0 / 1024.0 >= config.fastSearchMinUpSpeed + && FileConditions.BracketCheck(track, InferTrack(f.Filename, track)) && config.preferredCond.FileSatisfies(f, track, r)) { fsResults.TryAdd(r.Username + '\\' + f.Filename, (r, f)); if (Interlocked.Exchange(ref fsResultsStarted, 1) == 0) { - Task.Delay(Config.I.fastSearchDelay).ContinueWith(tt => fastSearchDownload()); + Task.Delay(config.fastSearchDelay).ContinueWith(tt => fastSearchDownload()); } } } @@ -94,8 +94,8 @@ static class Search return new SearchOptions( minimumResponseFileCount: 1, minimumPeerUploadSpeed: 1, - searchTimeout: Config.I.searchTimeout, - removeSingleCharacterSearchTerms: Config.I.removeSingleCharacterSearchTerms, + searchTimeout: config.searchTimeout, + removeSingleCharacterSearchTerms: config.removeSingleCharacterSearchTerms, responseFilter: (response) => { return response.UploadSpeed > 0 && necCond.BannedUsersSatisfies(response); @@ -107,13 +107,13 @@ static class Search } void onSearch() => Printing.RefreshOrPrint(progress, 0, $"Searching: {track}", true); - await RunSearches(track, results, getSearchOptions, responseHandler, searchCts.Token, onSearch); + await RunSearches(track, results, getSearchOptions, responseHandler, config, searchCts.Token, onSearch); searches.TryRemove(track, out _); searchEnded = true; lock (fsDownloadLock) { } - if (downloading == 0 && results.IsEmpty && !Config.I.useYtdlp) + if (downloading == 0 && results.IsEmpty && !config.useYtdlp) { notFound = true; } @@ -124,7 +124,7 @@ static class Search if (downloadTask == null || downloadTask.IsFaulted || downloadTask.IsCanceled) throw new TaskCanceledException(); await downloadTask; - userSuccessCount.AddOrUpdate(fsUser, 1, (k, v) => v + 1); + userSuccessCounts.AddOrUpdate(fsUser, 1, (k, v) => v + 1); } catch { @@ -133,7 +133,7 @@ static class Search if (chosenFile != null && fsUser != null) { results.TryRemove(fsUser + '\\' + chosenFile.Filename, out _); - userSuccessCount.AddOrUpdate(fsUser, -1, (k, v) => v - 1); + userSuccessCounts.AddOrUpdate(fsUser, -1, (k, v) => v - 1); } } } @@ -145,9 +145,9 @@ static class Search if (downloading == 0 && (!results.IsEmpty || orderedResults != null)) { if (orderedResults == null) - orderedResults = OrderedResults(results, track, useInfer: true); + orderedResults = OrderedResults(results, track, config, useInfer: true); - int trackTries = Config.I.maxRetriesPerTrack; + int trackTries = config.maxRetriesPerTrack; async Task process(SlResponse response, SlFile file) { saveFilePath = organizer.GetSavePath(file.Filename); @@ -155,13 +155,13 @@ static class Search try { downloading = 1; - await Download.DownloadFile(response, file, saveFilePath, track, progress, cts?.Token); - userSuccessCount.AddOrUpdate(response.Username, 1, (k, v) => v + 1); + await Download.DownloadFile(response, file, saveFilePath, track, progress, config, cts?.Token); + userSuccessCounts.AddOrUpdate(response.Username, 1, (k, v) => v + 1); return true; } catch (Exception e) { - Printing.WriteLine($"Error: Download Error: {e}", ConsoleColor.DarkYellow, debugOnly: true); + Printing.WriteLineIf($"Error: Download Error: {e}", config.debugInfo, ConsoleColor.DarkYellow); chosenFile = null; saveFilePath = ""; @@ -170,7 +170,7 @@ static class Search if (!IsConnectedAndLoggedIn()) throw; - userSuccessCount.AddOrUpdate(response.Username, -1, (k, v) => v - 1); + userSuccessCounts.AddOrUpdate(response.Username, -1, (k, v) => v - 1); if (--trackTries <= 0) { Printing.RefreshOrPrint(progress, 0, $"Out of download retries: {track}", true); @@ -190,7 +190,7 @@ static class Search fr = orderedResults.Skip(1).FirstOrDefault(); if (fr != default) { - if (userSuccessCount.GetValueOrDefault(fr.response.Username, 0) > Config.I.ignoreOn) + if (userSuccessCounts.GetValueOrDefault(fr.response.Username, 0) > config.ignoreOn) { success = await process(fr.response, fr.file); } @@ -198,7 +198,7 @@ static class Search { foreach (var (response, file) in orderedResults.Skip(2)) { - if (userSuccessCount.GetValueOrDefault(response.Username, 0) <= Config.I.ignoreOn) + if (userSuccessCounts.GetValueOrDefault(response.Username, 0) <= config.ignoreOn) continue; success = await process(response, file); if (success) break; @@ -208,7 +208,7 @@ static class Search } } - if (downloading == 0 && Config.I.useYtdlp) + if (downloading == 0 && config.useYtdlp) { notFound = false; try @@ -220,12 +220,12 @@ static class Search { foreach (var (length, id, title) in ytResults) { - if (Config.I.necessaryCond.LengthToleranceSatisfies(length, track.Length)) + if (config.necessaryCond.LengthToleranceSatisfies(length, track.Length)) { string saveFilePathNoExt = organizer.GetSavePathNoExt(title); downloading = 1; Printing.RefreshOrPrint(progress, 0, $"yt-dlp download: {track}", true); - saveFilePath = await Extractors.YouTube.YtdlpDownload(id, saveFilePathNoExt, Config.I.ytdlpArgument); + saveFilePath = await Extractors.YouTube.YtdlpDownload(id, saveFilePathNoExt, config.ytdlpArgument); Printing.RefreshOrPrint(progress, 100, $"Succeded: yt-dlp completed download for {track}", true); break; } @@ -260,14 +260,14 @@ static class Search } - public static async Task>> GetAlbumDownloads(Track track, ResponseData responseData) + public static async Task>> GetAlbumDownloads(Track track, ResponseData responseData, Config config) { var results = new ConcurrentDictionary(); SearchOptions getSearchOptions(int timeout, FileConditions nec, FileConditions prf) => new SearchOptions( minimumResponseFileCount: 1, minimumPeerUploadSpeed: 1, - removeSingleCharacterSearchTerms: Config.I.removeSingleCharacterSearchTerms, + removeSingleCharacterSearchTerms: config.removeSingleCharacterSearchTerms, searchTimeout: timeout, responseFilter: (response) => { @@ -290,11 +290,11 @@ static class Search } using var cts = new CancellationTokenSource(); - await RunSearches(track, results, getSearchOptions, handler, cts.Token); + await RunSearches(track, results, getSearchOptions, handler, config, cts.Token); string fullPath((SearchResponse r, Soulseek.File f) x) { return x.r.Username + '\\' + x.f.Filename; } - var orderedResults = OrderedResults(results, track, false, false, albumMode: true); + var orderedResults = OrderedResults(results, track, config, false, false, albumMode: true); var discPattern = new Regex(@"^(?i)(dis[c|k]|cd)\s*\d{1,2}$"); bool canMatchDiscPattern = !discPattern.IsMatch(track.Album) && !discPattern.IsMatch(track.Artist); @@ -354,10 +354,10 @@ static class Search } int min, max; - if (Config.I.minAlbumTrackCount > -1 || Config.I.maxAlbumTrackCount > -1) + if (config.minAlbumTrackCount > -1 || config.maxAlbumTrackCount > -1) { - min = Config.I.minAlbumTrackCount; - max = Config.I.maxAlbumTrackCount; + min = config.minAlbumTrackCount; + max = config.maxAlbumTrackCount; } else { @@ -403,14 +403,14 @@ static class Search } - public static async Task> GetAggregateTracks(Track track, ResponseData responseData) + public static async Task> GetAggregateTracks(Track track, ResponseData responseData, Config config) { var results = new SlDictionary(); SearchOptions getSearchOptions(int timeout, FileConditions nec, FileConditions prf) => new( minimumResponseFileCount: 1, minimumPeerUploadSpeed: 1, - removeSingleCharacterSearchTerms: Config.I.removeSingleCharacterSearchTerms, + removeSingleCharacterSearchTerms: config.removeSingleCharacterSearchTerms, searchTimeout: timeout, responseFilter: (response) => { @@ -433,16 +433,16 @@ static class Search } using var cts = new CancellationTokenSource(); - await RunSearches(track, results, getSearchOptions, handler, cts.Token); + await RunSearches(track, results, getSearchOptions, handler, config, cts.Token); string artistName = track.Artist.Trim(); string trackName = track.Title.Trim(); string albumName = track.Album.Trim(); - var equivalentFiles = EquivalentFiles(track, results.Select(x => x.Value)) - .Select(x => (x.Item1, OrderedResults(x.Item2, track, false, false, false))).ToList(); + var equivalentFiles = EquivalentFiles(track, results.Select(x => x.Value), config) + .Select(x => (x.Item1, OrderedResults(x.Item2, track, config, false, false, false))).ToList(); - if (!Config.I.relax) + if (!config.relax) { equivalentFiles = equivalentFiles .Where(x => FileConditions.StrictString(x.Item1.Title, track.Title, ignoreCase: true) @@ -463,9 +463,9 @@ static class Search } - public static async Task>>> GetAggregateAlbums(Track track, ResponseData responseData) + public static async Task>>> GetAggregateAlbums(Track track, ResponseData responseData, Config config) { - int maxDiff = Config.I.aggregateLengthTol; + int maxDiff = config.aggregateLengthTol; bool lengthsAreSimilar(int[] sorted1, int[] sorted2) { @@ -481,7 +481,7 @@ static class Search return true; } - var albums = await GetAlbumDownloads(track, responseData); + var albums = await GetAlbumDownloads(track, responseData, config); var sortedLengthLists = new List<(int[] lengths, List album, string username)>(); @@ -547,7 +547,7 @@ static class Search } res = res.Select((x, i) => (x, i)) - .Where(x => usernamesList[x.i].Count >= Config.I.minSharesAggregate) + .Where(x => usernamesList[x.i].Count >= config.minSharesAggregate) .OrderByDescending(x => usernamesList[x.i].Count) .Select(x => x.x) .ToList(); @@ -624,11 +624,14 @@ static class Search } - public static IEnumerable<(Track, IEnumerable<(SlResponse response, SlFile file)>)> EquivalentFiles(Track track, - IEnumerable<(SlResponse, SlFile)> fileResponses, int minShares = -1) + public static IEnumerable<(Track, IEnumerable<(SlResponse response, SlFile file)>)> EquivalentFiles( + Track track, + IEnumerable<(SlResponse, SlFile)> fileResponses, + Config config, + int minShares = -1) { if (minShares == -1) - minShares = Config.I.minSharesAggregate; + minShares = config.minSharesAggregate; Track inferTrack((SearchResponse r, Soulseek.File f) x) { @@ -638,7 +641,7 @@ static class Search } var groups = fileResponses - .GroupBy(inferTrack, new TrackComparer(ignoreCase: true, Config.I.aggregateLengthTol)) + .GroupBy(inferTrack, new TrackComparer(ignoreCase: true, config.aggregateLengthTol)) .Select(x => (x, x.Select(y => y.Item1.Username).Distinct().Count())) .Where(x => x.Item2 >= minShares) .OrderByDescending(x => x.Item2) @@ -654,15 +657,25 @@ static class Search } - public static IOrderedEnumerable<(SlResponse response, SlFile file)> OrderedResults(IEnumerable> results, - Track track, bool useInfer = false, bool useLevenshtein = true, bool albumMode = false) + public static IOrderedEnumerable<(SlResponse response, SlFile file)> OrderedResults( + IEnumerable> results, + Track track, + Config config, + bool useInfer = false, + bool useLevenshtein = true, + bool albumMode = false) { - return OrderedResults(results.Select(x => x.Value), track, useInfer, useLevenshtein, albumMode); + return OrderedResults(results.Select(x => x.Value), track, config, useInfer, useLevenshtein, albumMode); } - public static IOrderedEnumerable<(SlResponse response, SlFile file)> OrderedResults(IEnumerable<(SlResponse, SlFile)> results, - Track track, bool useInfer = false, bool useLevenshtein = true, bool albumMode = false) + public static IOrderedEnumerable<(SlResponse response, SlFile file)> OrderedResults( + IEnumerable<(SlResponse, SlFile)> results, + Track track, + Config config, + bool useInfer = false, + bool useLevenshtein = true, + bool albumMode = false) { bool useBracketCheck = true; if (albumMode) @@ -676,7 +689,7 @@ static class Search if (useInfer) // this is very slow { - var equivalentFiles = EquivalentFiles(track, results, 1); + var equivalentFiles = EquivalentFiles(track, results, config, 1); infTracksAndCounts = equivalentFiles .SelectMany(t => t.Item2, (t, f) => new { t.Item1, f.response.Username, f.file.Filename, Count = t.Item2.Count() }) .ToSafeDictionary(x => $"{x.Username}\\{x.Filename}", y => (y.Item1, y.Count)); @@ -700,22 +713,22 @@ static class Search var random = new Random(); return results.Select(x => (response: x.Item1, file: x.Item2)) - .Where(x => userSuccessCount.GetValueOrDefault(x.response.Username, 0) > Config.I.ignoreOn) - .OrderByDescending(x => userSuccessCount.GetValueOrDefault(x.response.Username, 0) > Config.I.downrankOn) - .ThenByDescending(x => Config.I.necessaryCond.FileSatisfies(x.file, track, x.response)) - .ThenByDescending(x => Config.I.preferredCond.BannedUsersSatisfies(x.response)) - .ThenByDescending(x => (x.file.Length != null && x.file.Length > 0) || Config.I.preferredCond.AcceptNoLength == null || Config.I.preferredCond.AcceptNoLength.Value) + .Where(x => userSuccessCounts.GetValueOrDefault(x.response.Username, 0) > config.ignoreOn) + .OrderByDescending(x => userSuccessCounts.GetValueOrDefault(x.response.Username, 0) > config.downrankOn) + .ThenByDescending(x => config.necessaryCond.FileSatisfies(x.file, track, x.response)) + .ThenByDescending(x => config.preferredCond.BannedUsersSatisfies(x.response)) + .ThenByDescending(x => (x.file.Length != null && x.file.Length > 0) || config.preferredCond.AcceptNoLength == null || config.preferredCond.AcceptNoLength.Value) .ThenByDescending(x => !useBracketCheck || FileConditions.BracketCheck(track, inferredTrack(x).Item1)) // downrank result if it contains '(' or '[' and the title does not (avoid remixes) - .ThenByDescending(x => Config.I.preferredCond.StrictTitleSatisfies(x.file.Filename, track.Title)) - .ThenByDescending(x => !albumMode || Config.I.preferredCond.StrictAlbumSatisfies(x.file.Filename, track.Album)) - .ThenByDescending(x => Config.I.preferredCond.StrictArtistSatisfies(x.file.Filename, track.Title)) - .ThenByDescending(x => Config.I.preferredCond.LengthToleranceSatisfies(x.file, track.Length)) - .ThenByDescending(x => Config.I.preferredCond.FormatSatisfies(x.file.Filename)) - .ThenByDescending(x => albumMode || Config.I.preferredCond.StrictAlbumSatisfies(x.file.Filename, track.Album)) - .ThenByDescending(x => Config.I.preferredCond.BitrateSatisfies(x.file)) - .ThenByDescending(x => Config.I.preferredCond.SampleRateSatisfies(x.file)) - .ThenByDescending(x => Config.I.preferredCond.BitDepthSatisfies(x.file)) - .ThenByDescending(x => Config.I.preferredCond.FileSatisfies(x.file, track, x.response)) + .ThenByDescending(x => config.preferredCond.StrictTitleSatisfies(x.file.Filename, track.Title)) + .ThenByDescending(x => !albumMode || config.preferredCond.StrictAlbumSatisfies(x.file.Filename, track.Album)) + .ThenByDescending(x => config.preferredCond.StrictArtistSatisfies(x.file.Filename, track.Title)) + .ThenByDescending(x => config.preferredCond.LengthToleranceSatisfies(x.file, track.Length)) + .ThenByDescending(x => config.preferredCond.FormatSatisfies(x.file.Filename)) + .ThenByDescending(x => albumMode || config.preferredCond.StrictAlbumSatisfies(x.file.Filename, track.Album)) + .ThenByDescending(x => config.preferredCond.BitrateSatisfies(x.file)) + .ThenByDescending(x => config.preferredCond.SampleRateSatisfies(x.file)) + .ThenByDescending(x => config.preferredCond.BitDepthSatisfies(x.file)) + .ThenByDescending(x => config.preferredCond.FileSatisfies(x.file, track, x.response)) .ThenByDescending(x => x.response.HasFreeUploadSlot) .ThenByDescending(x => x.response.UploadSpeed / 1024 / 650) .ThenByDescending(x => albumMode || FileConditions.StrictString(x.file.Filename, track.Title)) @@ -730,7 +743,7 @@ static class Search public static async Task RunSearches(Track track, SlDictionary results, Func getSearchOptions, - Action responseHandler, CancellationToken? ct = null, Action? onSearch = null) + Action responseHandler, Config config, CancellationToken? ct = null, Action? onSearch = null) { bool artist = track.Artist.Length > 0; bool title = track.Title.Length > 0; @@ -739,27 +752,27 @@ static class Search string search = GetSearchString(track); var searchTasks = new List(); - var defaultSearchOpts = getSearchOptions(Config.I.searchTimeout, Config.I.necessaryCond, Config.I.preferredCond); - searchTasks.Add(DoSearch(search, defaultSearchOpts, responseHandler, ct, onSearch)); + var defaultSearchOpts = getSearchOptions(config.searchTimeout, config.necessaryCond, config.preferredCond); + searchTasks.Add(DoSearch(search, defaultSearchOpts, responseHandler, config, ct, onSearch)); if (search.RemoveDiacriticsIfExist(out string noDiacrSearch) && !track.ArtistMaybeWrong) { - searchTasks.Add(DoSearch(noDiacrSearch, defaultSearchOpts, responseHandler, ct, onSearch)); + searchTasks.Add(DoSearch(noDiacrSearch, defaultSearchOpts, responseHandler, config, ct, onSearch)); } await Task.WhenAll(searchTasks); if (results.IsEmpty && track.ArtistMaybeWrong && title) { - var cond = new FileConditions(Config.I.necessaryCond); + var cond = new FileConditions(config.necessaryCond); var infTrack = InferTrack(track.Title, new Track()); cond.StrictTitle = infTrack.Title == track.Title; cond.StrictArtist = false; - var opts = getSearchOptions(Math.Min(Config.I.searchTimeout, 5000), cond, Config.I.preferredCond); - searchTasks.Add(DoSearch($"{infTrack.Artist} {infTrack.Title}", opts, responseHandler, ct, onSearch)); + var opts = getSearchOptions(Math.Min(config.searchTimeout, 5000), cond, config.preferredCond); + searchTasks.Add(DoSearch($"{infTrack.Artist} {infTrack.Title}", opts, responseHandler, config, ct, onSearch)); } - if (Config.I.desperateSearch) + if (config.desperateSearch) { await Task.WhenAll(searchTasks); @@ -767,24 +780,24 @@ static class Search { if (artist && album && title) { - var cond = new FileConditions(Config.I.necessaryCond) + var cond = new FileConditions(config.necessaryCond) { StrictTitle = true, StrictAlbum = true }; - var opts = getSearchOptions(Math.Min(Config.I.searchTimeout, 5000), cond, Config.I.preferredCond); - searchTasks.Add(DoSearch($"{track.Artist} {track.Album}", opts, responseHandler, ct, onSearch)); + var opts = getSearchOptions(Math.Min(config.searchTimeout, 5000), cond, config.preferredCond); + searchTasks.Add(DoSearch($"{track.Artist} {track.Album}", opts, responseHandler, config, ct, onSearch)); } - if (artist && title && track.Length != -1 && Config.I.necessaryCond.LengthTolerance != -1) + if (artist && title && track.Length != -1 && config.necessaryCond.LengthTolerance != -1) { - var cond = new FileConditions(Config.I.necessaryCond) + var cond = new FileConditions(config.necessaryCond) { LengthTolerance = -1, StrictTitle = true, StrictArtist = true }; - var opts = getSearchOptions(Math.Min(Config.I.searchTimeout, 5000), cond, Config.I.preferredCond); - searchTasks.Add(DoSearch($"{track.Artist} {track.Title}", opts, responseHandler, ct, onSearch)); + var opts = getSearchOptions(Math.Min(config.searchTimeout, 5000), cond, config.preferredCond); + searchTasks.Add(DoSearch($"{track.Artist} {track.Title}", opts, responseHandler, config, ct, onSearch)); } } @@ -796,37 +809,37 @@ static class Search if (track.Album.Length > 3 && album) { - var cond = new FileConditions(Config.I.necessaryCond) + var cond = new FileConditions(config.necessaryCond) { StrictAlbum = true, StrictTitle = !track.ArtistMaybeWrong, StrictArtist = !track.ArtistMaybeWrong, LengthTolerance = -1 }; - var opts = getSearchOptions(Math.Min(Config.I.searchTimeout, 5000), cond, Config.I.preferredCond); - searchTasks.Add(DoSearch($"{track.Album}", opts, responseHandler, ct, onSearch)); + var opts = getSearchOptions(Math.Min(config.searchTimeout, 5000), cond, config.preferredCond); + searchTasks.Add(DoSearch($"{track.Album}", opts, responseHandler, config, ct, onSearch)); } if (track2.Title.Length > 3 && artist) { - var cond = new FileConditions(Config.I.necessaryCond) + var cond = new FileConditions(config.necessaryCond) { StrictTitle = !track.ArtistMaybeWrong, StrictArtist = !track.ArtistMaybeWrong, LengthTolerance = -1 }; - var opts = getSearchOptions(Math.Min(Config.I.searchTimeout, 5000), cond, Config.I.preferredCond); - searchTasks.Add(DoSearch($"{track2.Title}", opts, responseHandler, ct, onSearch)); + var opts = getSearchOptions(Math.Min(config.searchTimeout, 5000), cond, config.preferredCond); + searchTasks.Add(DoSearch($"{track2.Title}", opts, responseHandler, config, ct, onSearch)); } if (track2.Artist.Length > 3 && title) { - var cond = new FileConditions(Config.I.necessaryCond) + var cond = new FileConditions(config.necessaryCond) { StrictTitle = !track.ArtistMaybeWrong, StrictArtist = !track.ArtistMaybeWrong, LengthTolerance = -1 }; - var opts = getSearchOptions(Math.Min(Config.I.searchTimeout, 5000), cond, Config.I.preferredCond); - searchTasks.Add(DoSearch($"{track2.Artist}", opts, responseHandler, ct, onSearch)); + var opts = getSearchOptions(Math.Min(config.searchTimeout, 5000), cond, config.preferredCond); + searchTasks.Add(DoSearch($"{track2.Artist}", opts, responseHandler, config, ct, onSearch)); } } } @@ -835,12 +848,12 @@ static class Search } - static async Task DoSearch(string search, SearchOptions opts, Action rHandler, CancellationToken? ct = null, Action? onSearch = null) + static async Task DoSearch(string search, SearchOptions opts, Action rHandler, Config config, CancellationToken? ct = null, Action? onSearch = null) { await searchSemaphore.WaitAsync(); try { - search = CleanSearchString(search); + search = CleanSearchString(search, !config.noRemoveSpecialChars); var q = SearchQuery.FromText(search); onSearch?.Invoke(); await client.SearchAsync(q, options: opts, cancellationToken: ct, responseHandler: rHandler); @@ -849,7 +862,7 @@ static class Search } - public static async Task SearchAndPrintResults(List tracks) + public static async Task SearchAndPrintResults(List tracks, Config config) { foreach (var track in tracks) { @@ -860,15 +873,15 @@ static class Search return new SearchOptions( minimumResponseFileCount: 1, minimumPeerUploadSpeed: 1, - searchTimeout: Config.I.searchTimeout, - removeSingleCharacterSearchTerms: Config.I.removeSingleCharacterSearchTerms, + searchTimeout: config.searchTimeout, + removeSingleCharacterSearchTerms: config.removeSingleCharacterSearchTerms, responseFilter: (response) => { return response.UploadSpeed > 0 && necCond.BannedUsersSatisfies(response); }, fileFilter: (file) => { - return Utils.IsMusicFile(file.Filename) && (necCond.FileSatisfies(file, track, null) || Config.I.PrintResultsFull); + return Utils.IsMusicFile(file.Filename) && (necCond.FileSatisfies(file, track, null) || config.PrintResultsFull); }); } @@ -883,22 +896,22 @@ static class Search } } - await RunSearches(track, results, getSearchOptions, responseHandler); + await RunSearches(track, results, getSearchOptions, responseHandler, config); - if (Config.I.DoNotDownload && results.IsEmpty) + if (config.DoNotDownload && results.IsEmpty) { Printing.WriteLine($"No results", ConsoleColor.Yellow); } else { - var orderedResults = OrderedResults(results, track, useInfer: true); + var orderedResults = OrderedResults(results, track, config, useInfer: true); int count = 0; Console.WriteLine(); foreach (var (response, file) in orderedResults) { Console.WriteLine(Printing.DisplayString(track, file, response, - Config.I.PrintResultsFull ? Config.I.necessaryCond : null, Config.I.PrintResultsFull ? Config.I.preferredCond : null, - fullpath: Config.I.PrintResultsFull, infoFirst: true, showSpeed: Config.I.PrintResultsFull)); + config.PrintResultsFull ? config.necessaryCond : null, config.PrintResultsFull ? config.preferredCond : null, + fullpath: config.PrintResultsFull, infoFirst: true, showSpeed: config.PrintResultsFull)); count += 1; } Printing.WriteLine($"Total: {count}\n", ConsoleColor.Yellow); @@ -930,10 +943,10 @@ static class Search } - static string CleanSearchString(string str) + static string CleanSearchString(string str, bool removeSpecialChars) { string old; - if (!Config.I.noRemoveSpecialChars) + if (removeSpecialChars) { old = str; str = str.ReplaceSpecialChars(" ").Trim().RemoveConsecutiveWs(); @@ -1156,5 +1169,3 @@ public class SearchAndDownloadException : Exception public FailureReason reason; public SearchAndDownloadException(FailureReason reason, string text = "") : base(text) { this.reason = reason; } } - - diff --git a/slsk-batchdl/Tests/Test.cs b/slsk-batchdl/Tests/Test.cs index 01ddf10..39756f4 100644 --- a/slsk-batchdl/Tests/Test.cs +++ b/slsk-batchdl/Tests/Test.cs @@ -13,10 +13,11 @@ namespace Tests public static async Task RunAllTests() { TestStringUtils(); - TestAutoProfiles(); - TestProfileConditions(); - await TestStringExtractor(); - TestM3uEditor(); + throw new NotImplementedException("Outdated test code"); + //TestAutoProfiles(); + //TestProfileConditions(); + //await TestStringExtractor(); + //TestM3uEditor(); Console.WriteLine('\n' + new string('#', 50) + '\n' + "All tests passed."); } @@ -75,366 +76,366 @@ namespace Tests Passed(); } - public static void TestAutoProfiles() - { - SetCurrentTest("TestAutoProfiles"); - - ResetConfig(); - Config.I.inputType = InputType.YouTube; - Config.I.interactiveMode = true; - Config.I.aggregate = false; - Config.I.maxStaleTime = 50000; - - string path = Path.Join(Directory.GetCurrentDirectory(), "test_conf.conf"); - - string content = - "max-stale-time = 5" + - "\nfast-search = true" + - "\nformat = flac" + - - "\n[profile-true-1]" + - "\nprofile-cond = input-type == \"youtube\" && download-mode == \"album\"" + - "\nmax-stale-time = 10" + - - "\n[profile-true-2]" + - "\nprofile-cond = !aggregate" + - "\nfast-search = false" + - - "\n[profile-false-1]" + - "\nprofile-cond = input-type == \"string\"" + - "\nformat = mp3" + - - "\n[profile-no-cond]" + - "\nformat = opus"; - - File.WriteAllText(path, content); - - Config.I.LoadAndParse(new string[] { "-c", path }); - - var tle = new TrackListEntry(TrackType.Album); - Config.UpdateProfiles(tle); - - Assert(Config.I.maxStaleTime == 10 && !Config.I.fastSearch && Config.I.necessaryCond.Formats[0] == "flac"); - - ResetConfig(); - Config.I.inputType = InputType.CSV; - Config.I.album = true; - Config.I.interactiveMode = true; - Config.I.useYtdlp = false; - Config.I.maxStaleTime = 50000; - content = - "\n[no-stale]" + - "\nprofile-cond = interactive && download-mode == \"album\"" + - "\nmax-stale-time = 999999" + - "\n[youtube]" + - "\nprofile-cond = input-type == \"youtube\"" + - "\nyt-dlp = true"; - - File.WriteAllText(path, content); - - - Config.I.LoadAndParse(new string[] { "-c", path }); - Config.UpdateProfiles(tle); - Assert(Config.I.maxStaleTime == 999999 && !Config.I.useYtdlp); - - ResetConfig(); - Config.I.inputType = InputType.YouTube; - Config.I.album = false; - Config.I.interactiveMode = true; - Config.I.useYtdlp = false; - Config.I.maxStaleTime = 50000; - content = - "\n[no-stale]" + - "\nprofile-cond = interactive && download-mode == \"album\"" + - "\nmax-stale-time = 999999" + - "\n[youtube]" + - "\nprofile-cond = input-type == \"youtube\"" + - "\nyt-dlp = true"; - - File.WriteAllText(path, content); - Config.I.LoadAndParse(new string[] { "-c", path }); - Config.UpdateProfiles(new TrackListEntry(TrackType.Normal)); - - Assert(Config.I.maxStaleTime == 50000 && Config.I.useYtdlp); - - if (File.Exists(path)) - File.Delete(path); - - Passed(); - } - - public static void TestProfileConditions() - { - SetCurrentTest("TestProfileConditions"); - - Config.I.inputType = InputType.YouTube; - Config.I.interactiveMode = true; - Config.I.album = true; - Config.I.aggregate = false; - - var conds = new (bool, string)[] - { - (true, "input-type == \"youtube\""), - (true, "download-mode == \"album\""), - (false, "aggregate"), - (true, "interactive"), - (true, "album"), - (false, "!interactive"), - (true, "album && input-type == \"youtube\""), - (false, "album && input-type != \"youtube\""), - (false, "(interactive && aggregate)"), - (true, "album && (interactive || aggregate)"), - (true, "input-type == \"spotify\" || aggregate || input-type == \"csv\" || interactive && album"), - (true, " input-type!=\"youtube\"||(album&&!interactive ||(aggregate || interactive ) )"), - (false, " input-type!=\"youtube\"||(album&&!interactive ||(aggregate || !interactive ) )"), - }; - - foreach ((var b, var c) in conds) - { - Console.WriteLine(c); - Assert(b == Config.I.ProfileConditionSatisfied(c)); - } - - Passed(); - } - - public static async Task TestStringExtractor() - { - SetCurrentTest("TestStringExtractor"); - - var strings = new List() - { - "Some Title", - "Some, Title", - "artist = Some artist, title = some title", - "Artist - Title, length = 42", - "title=Some, Title, artist=Some, Artist, album = Some, Album, length= 42", - "Some, Artist = a - Some, Title = b, album = Some, Album, length = 42", - - "Foo Bar", - "Foo - Bar", - "Artist - Title, length=42", - "title=Title, artist=Artist, length=42", - }; - - var tracks = new List() - { - new Track() { Title="Some Title" }, - new Track() { Title="Some, Title" }, - new Track() { Title = "some title", Artist = "Some artist" }, - new Track() { Title = "Title", Artist = "Artist", Length = 42 }, - new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42 }, - new Track() { Title="Some, Title = b", Artist = "Some, Artist = a", Album = "Some, Album", Length = 42 }, - - new Track() { Title = "Foo Bar" }, - new Track() { Title = "Bar", Artist = "Foo" }, - new Track() { Title = "Title", Artist = "Artist", Length = 42 }, - new Track() { Title = "Title", Artist = "Artist", Length = 42 }, - }; - - var albums = new List() - { - new Track() { Album="Some Title", Type = TrackType.Album }, - new Track() { Album="Some, Title", Type = TrackType.Album }, - new Track() { Title = "some title", Artist = "Some artist", Type = TrackType.Album }, - new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, - new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42, Type = TrackType.Album }, - new Track() { Artist = "Some, Artist = a", Album = "Some, Album", Length = 42, Type = TrackType.Album }, - - new Track() { Album = "Foo Bar", Type = TrackType.Album }, - new Track() { Album = "Bar", Artist = "Foo", Type = TrackType.Album }, - new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, - new Track() { Title = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, - }; - - var extractor = new Extractors.StringExtractor(); - - Config.I.aggregate = false; - Config.I.album = false; - - Console.WriteLine("Testing songs: "); - for (int i = 0; i < strings.Count; i++) - { - Config.I.input = strings[i]; - Console.WriteLine(Config.I.input); - var res = await extractor.GetTracks(Config.I.input, 0, 0, false); - var t = res[0].list[0][0]; - Assert(Extractors.StringExtractor.InputMatches(Config.I.input)); - Assert(t.ToKey() == tracks[i].ToKey()); - } - - Console.WriteLine(); - Console.WriteLine("Testing albums"); - Config.I.album = true; - for (int i = 0; i < strings.Count; i++) - { - Config.I.input = strings[i]; - Console.WriteLine(Config.I.input); - var t = (await extractor.GetTracks(Config.I.input, 0, 0, false))[0].source; - Assert(Extractors.StringExtractor.InputMatches(Config.I.input)); - Assert(t.ToKey() == albums[i].ToKey()); - } - - Passed(); - } - - public static void TestM3uEditor() - { - SetCurrentTest("TestM3uEditor"); - - Config.I.skipMode = SkipMode.Index; - Config.I.skipMusicDir = ""; - Config.I.printOption = PrintOption.Tracks | PrintOption.Full; - Config.I.skipExisting = true; - - string path = Path.Join(Directory.GetCurrentDirectory(), "test_m3u.m3u8"); - - if (File.Exists(path)) - File.Delete(path); - - File.WriteAllText(path, $"#SLDL:" + - $"{Path.Join(Directory.GetCurrentDirectory(), "file1.5")},\"Artist, 1.5\",,\"Title, , 1.5\",-1,0,3,0;" + - $"path/to/file1,\"Artist, 1\",,\"Title, , 1\",-1,0,3,0;" + - $"path/to/file2,\"Artist, 2\",,Title2,-1,0,3,0;" + - $",\"Artist; ,3\",,Title3 ;a,-1,0,4,0;" + - $",\"Artist,,, ;4\",,Title4,-1,0,4,3;" + - $",,,,-1,0,0,0;"); - - var notFoundInitial = new List() - { - new() { Artist = "Artist; ,3", Title = "Title3 ;a" }, - new() { Artist = "Artist,,, ;4", Title = "Title4", State = TrackState.Failed, FailureReason = FailureReason.NoSuitableFileFound } - }; - var existingInitial = new List() - { - new() { Artist = "Artist, 1", Title = "Title, , 1", DownloadPath = "path/to/file1", State = TrackState.Downloaded }, - new() { Artist = "Artist, 1.5", Title = "Title, , 1.5", DownloadPath = Path.Join(Directory.GetCurrentDirectory(), "file1.5"), State = TrackState.Downloaded }, - new() { Artist = "Artist, 2", Title = "Title2", DownloadPath = "path/to/file2", State = TrackState.AlreadyExists } - }; - var toBeDownloadedInitial = new List() - { - new() { Artist = "ArtistA", Album = "Albumm", Title = "TitleA" }, - new() { Artist = "ArtistB", Album = "Albumm", Title = "TitleB" } - }; - - var trackLists = new TrackLists(); - trackLists.AddEntry(new TrackListEntry(TrackType.Normal)); - foreach (var t in notFoundInitial) - trackLists.AddTrackToLast(t); - foreach (var t in existingInitial) - trackLists.AddTrackToLast(t); - foreach (var t in toBeDownloadedInitial) - trackLists.AddTrackToLast(t); - - Program.indexEditor = new M3uEditor(path, trackLists, M3uOption.All); - - Program.outputDirSkipper = new IndexSkipper(Program.indexEditor, false); - - var notFound = (List)ProgramInvoke("DoSkipNotFound", new object[] { trackLists[0].list[0] }); - var existing = (List)ProgramInvoke("DoSkipExisting", new object[] { trackLists[0].list[0] }); - var toBeDownloaded = trackLists[0].list[0].Where(t => t.State == TrackState.Initial).ToList(); - - Assert(notFound.SequenceEqualUpToPermutation(notFoundInitial)); - Assert(existing.SequenceEqualUpToPermutation(existingInitial)); - Assert(toBeDownloaded.SequenceEqualUpToPermutation(toBeDownloadedInitial)); - - Printing.PrintTracksTbd(toBeDownloaded, existing, notFound, TrackType.Normal); - - Program.indexEditor.Update(); - string output = File.ReadAllText(path); - string need = - "#SLDL:./file1.5,\"Artist, 1.5\",,\"Title, , 1.5\",-1,0,3,0;path/to/file1,\"Artist, 1\",,\"Title, , 1\",-1,0,3,0;path/to/file2,\"Artist, 2\",,Title2,-1,0,3,0;,\"Artist; ,3\",,Title3 ;a,-1,0,4,0;,\"Artist,,, ;4\",,Title4,-1,0,4,3;,,,,-1,0,0,0;" + - "\n" + - "\n#FAIL: Artist; ,3 - Title3 ;a [NoSuitableFileFound]" + - "\n#FAIL: Artist,,, ;4 - Title4 [NoSuitableFileFound]" + - "\npath/to/file1" + - "\nfile1.5" + - "\npath/to/file2" + - "\n"; - Assert(output == need); - - toBeDownloaded[0].State = TrackState.Downloaded; - toBeDownloaded[0].DownloadPath = "new/file/path"; - toBeDownloaded[1].State = TrackState.Failed; - toBeDownloaded[1].FailureReason = FailureReason.NoSuitableFileFound; - existing[1].DownloadPath = "/other/new/file/path"; - - Program.indexEditor.Update(); - output = File.ReadAllText(path); - need = - "#SLDL:/other/new/file/path,\"Artist, 1.5\",,\"Title, , 1.5\",-1,0,3,0;path/to/file1,\"Artist, 1\",,\"Title, , 1\",-1,0,3,0;path/to/file2,\"Artist, 2\",,Title2,-1,0,3,0;,\"Artist; ,3\",,Title3 ;a,-1,0,4,0;,\"Artist,,, ;4\",,Title4,-1,0,4,3;" + - ",,,,-1,0,0,0;new/file/path,ArtistA,Albumm,TitleA,-1,0,1,0;,ArtistB,Albumm,TitleB,-1,0,2,3;" + - "\n" + - "\n#FAIL: Artist; ,3 - Title3 ;a [NoSuitableFileFound]" + - "\n#FAIL: Artist,,, ;4 - Title4 [NoSuitableFileFound]" + - "\npath/to/file1" + - "\n/other/new/file/path" + - "\npath/to/file2" + - "\nnew/file/path" + - "\n#FAIL: ArtistB - TitleB [NoSuitableFileFound]" + - "\n"; - Assert(output == need); - - Console.WriteLine(); - Console.WriteLine(output); - - Program.indexEditor = new M3uEditor(path, trackLists, M3uOption.All); - - foreach (var t in trackLists.Flattened(false, false)) - { - Program.indexEditor.TryGetPreviousRunResult(t, out var prev); - Assert(prev != null); - Assert(prev.ToKey() == t.ToKey()); - Assert(prev.DownloadPath == t.DownloadPath); - Assert(prev.State == t.State || prev.State == TrackState.NotFoundLastTime); - Assert(prev.FailureReason == t.FailureReason); - } - - Program.indexEditor.Update(); - output = File.ReadAllText(path); - Assert(output == need); - - - var test = new List - { - new() { Artist = "ArtistA", Album = "AlbumA", Type = TrackType.Album }, - new() { Artist = "ArtistB", Album = "AlbumB", Type = TrackType.Album }, - new() { Artist = "ArtistC", Album = "AlbumC", Type = TrackType.Album }, - }; - - trackLists = new TrackLists(); - foreach (var t in test) - trackLists.AddEntry(new TrackListEntry(t)); - - File.WriteAllText(path, ""); - Program.indexEditor = new M3uEditor(path, trackLists, M3uOption.Index); - Program.indexEditor.Update(); - - Assert(File.ReadAllText(path) == ""); - - test[0].State = TrackState.Downloaded; - test[0].DownloadPath = "download/path"; - test[1].State = TrackState.Failed; - test[1].FailureReason = FailureReason.NoSuitableFileFound; - test[2].State = TrackState.AlreadyExists; - - Program.indexEditor.Update(); - - Program.indexEditor = new M3uEditor(path, trackLists, M3uOption.Index); - - foreach (var t in test) - { - Program.indexEditor.TryGetPreviousRunResult(t, out var tt); - Assert(tt != null); - Assert(tt.ToKey() == t.ToKey()); - t.DownloadPath = "this should not change tt.DownloadPath"; - Assert(t.DownloadPath != tt.DownloadPath); - } - - File.Delete(path); - - Passed(); - } + //public static void TestAutoProfiles() + //{ + // SetCurrentTest("TestAutoProfiles"); + + // ResetConfig(); + // Config.I.inputType = InputType.YouTube; + // Config.I.interactiveMode = true; + // Config.I.aggregate = false; + // Config.I.maxStaleTime = 50000; + + // string path = Path.Join(Directory.GetCurrentDirectory(), "test_conf.conf"); + + // string content = + // "max-stale-time = 5" + + // "\nfast-search = true" + + // "\nformat = flac" + + + // "\n[profile-true-1]" + + // "\nprofile-cond = input-type == \"youtube\" && download-mode == \"album\"" + + // "\nmax-stale-time = 10" + + + // "\n[profile-true-2]" + + // "\nprofile-cond = !aggregate" + + // "\nfast-search = false" + + + // "\n[profile-false-1]" + + // "\nprofile-cond = input-type == \"string\"" + + // "\nformat = mp3" + + + // "\n[profile-no-cond]" + + // "\nformat = opus"; + + // File.WriteAllText(path, content); + + // Config.I.LoadAndParse(new string[] { "-c", path }); + + // var tle = new TrackListEntry(TrackType.Album); + // Config.UpdateProfiles(tle); + + // Assert(Config.I.maxStaleTime == 10 && !Config.I.fastSearch && Config.I.necessaryCond.Formats[0] == "flac"); + + // ResetConfig(); + // Config.I.inputType = InputType.CSV; + // Config.I.album = true; + // Config.I.interactiveMode = true; + // Config.I.useYtdlp = false; + // Config.I.maxStaleTime = 50000; + // content = + // "\n[no-stale]" + + // "\nprofile-cond = interactive && download-mode == \"album\"" + + // "\nmax-stale-time = 999999" + + // "\n[youtube]" + + // "\nprofile-cond = input-type == \"youtube\"" + + // "\nyt-dlp = true"; + + // File.WriteAllText(path, content); + + + // Config.I.LoadAndParse(new string[] { "-c", path }); + // Config.UpdateProfiles(tle); + // Assert(Config.I.maxStaleTime == 999999 && !Config.I.useYtdlp); + + // ResetConfig(); + // Config.I.inputType = InputType.YouTube; + // Config.I.album = false; + // Config.I.interactiveMode = true; + // Config.I.useYtdlp = false; + // Config.I.maxStaleTime = 50000; + // content = + // "\n[no-stale]" + + // "\nprofile-cond = interactive && download-mode == \"album\"" + + // "\nmax-stale-time = 999999" + + // "\n[youtube]" + + // "\nprofile-cond = input-type == \"youtube\"" + + // "\nyt-dlp = true"; + + // File.WriteAllText(path, content); + // Config.I.LoadAndParse(new string[] { "-c", path }); + // Config.UpdateProfiles(new TrackListEntry(TrackType.Normal)); + + // Assert(Config.I.maxStaleTime == 50000 && Config.I.useYtdlp); + + // if (File.Exists(path)) + // File.Delete(path); + + // Passed(); + //} + + //public static void TestProfileConditions() + //{ + // SetCurrentTest("TestProfileConditions"); + + // Config.I.inputType = InputType.YouTube; + // Config.I.interactiveMode = true; + // Config.I.album = true; + // Config.I.aggregate = false; + + // var conds = new (bool, string)[] + // { + // (true, "input-type == \"youtube\""), + // (true, "download-mode == \"album\""), + // (false, "aggregate"), + // (true, "interactive"), + // (true, "album"), + // (false, "!interactive"), + // (true, "album && input-type == \"youtube\""), + // (false, "album && input-type != \"youtube\""), + // (false, "(interactive && aggregate)"), + // (true, "album && (interactive || aggregate)"), + // (true, "input-type == \"spotify\" || aggregate || input-type == \"csv\" || interactive && album"), + // (true, " input-type!=\"youtube\"||(album&&!interactive ||(aggregate || interactive ) )"), + // (false, " input-type!=\"youtube\"||(album&&!interactive ||(aggregate || !interactive ) )"), + // }; + + // foreach ((var b, var c) in conds) + // { + // Console.WriteLine(c); + // Assert(b == Config.I.ProfileConditionSatisfied(c)); + // } + + // Passed(); + //} + + //public static async Task TestStringExtractor() + //{ + // SetCurrentTest("TestStringExtractor"); + + // var strings = new List() + // { + // "Some Title", + // "Some, Title", + // "artist = Some artist, title = some title", + // "Artist - Title, length = 42", + // "title=Some, Title, artist=Some, Artist, album = Some, Album, length= 42", + // "Some, Artist = a - Some, Title = b, album = Some, Album, length = 42", + + // "Foo Bar", + // "Foo - Bar", + // "Artist - Title, length=42", + // "title=Title, artist=Artist, length=42", + // }; + + // var tracks = new List() + // { + // new Track() { Title="Some Title" }, + // new Track() { Title="Some, Title" }, + // new Track() { Title = "some title", Artist = "Some artist" }, + // new Track() { Title = "Title", Artist = "Artist", Length = 42 }, + // new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42 }, + // new Track() { Title="Some, Title = b", Artist = "Some, Artist = a", Album = "Some, Album", Length = 42 }, + + // new Track() { Title = "Foo Bar" }, + // new Track() { Title = "Bar", Artist = "Foo" }, + // new Track() { Title = "Title", Artist = "Artist", Length = 42 }, + // new Track() { Title = "Title", Artist = "Artist", Length = 42 }, + // }; + + // var albums = new List() + // { + // new Track() { Album="Some Title", Type = TrackType.Album }, + // new Track() { Album="Some, Title", Type = TrackType.Album }, + // new Track() { Title = "some title", Artist = "Some artist", Type = TrackType.Album }, + // new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, + // new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42, Type = TrackType.Album }, + // new Track() { Artist = "Some, Artist = a", Album = "Some, Album", Length = 42, Type = TrackType.Album }, + + // new Track() { Album = "Foo Bar", Type = TrackType.Album }, + // new Track() { Album = "Bar", Artist = "Foo", Type = TrackType.Album }, + // new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, + // new Track() { Title = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, + // }; + + // var extractor = new Extractors.StringExtractor(); + + // Config.I.aggregate = false; + // Config.I.album = false; + + // Console.WriteLine("Testing songs: "); + // for (int i = 0; i < strings.Count; i++) + // { + // Config.I.input = strings[i]; + // Console.WriteLine(Config.I.input); + // var res = await extractor.GetTracks(Config.I.input, 0, 0, false); + // var t = res[0].list[0][0]; + // Assert(Extractors.StringExtractor.InputMatches(Config.I.input)); + // Assert(t.ToKey() == tracks[i].ToKey()); + // } + + // Console.WriteLine(); + // Console.WriteLine("Testing albums"); + // Config.I.album = true; + // for (int i = 0; i < strings.Count; i++) + // { + // Config.I.input = strings[i]; + // Console.WriteLine(Config.I.input); + // var t = (await extractor.GetTracks(Config.I.input, 0, 0, false))[0].source; + // Assert(Extractors.StringExtractor.InputMatches(Config.I.input)); + // Assert(t.ToKey() == albums[i].ToKey()); + // } + + // Passed(); + //} + + //public static void TestM3uEditor() + //{ + // SetCurrentTest("TestM3uEditor"); + + // Config.I.skipMode = SkipMode.Index; + // Config.I.skipMusicDir = ""; + // Config.I.printOption = PrintOption.Tracks | PrintOption.Full; + // Config.I.skipExisting = true; + + // string path = Path.Join(Directory.GetCurrentDirectory(), "test_m3u.m3u8"); + + // if (File.Exists(path)) + // File.Delete(path); + + // File.WriteAllText(path, $"#SLDL:" + + // $"{Path.Join(Directory.GetCurrentDirectory(), "file1.5")},\"Artist, 1.5\",,\"Title, , 1.5\",-1,0,3,0;" + + // $"path/to/file1,\"Artist, 1\",,\"Title, , 1\",-1,0,3,0;" + + // $"path/to/file2,\"Artist, 2\",,Title2,-1,0,3,0;" + + // $",\"Artist; ,3\",,Title3 ;a,-1,0,4,0;" + + // $",\"Artist,,, ;4\",,Title4,-1,0,4,3;" + + // $",,,,-1,0,0,0;"); + + // var notFoundInitial = new List() + // { + // new() { Artist = "Artist; ,3", Title = "Title3 ;a" }, + // new() { Artist = "Artist,,, ;4", Title = "Title4", State = TrackState.Failed, FailureReason = FailureReason.NoSuitableFileFound } + // }; + // var existingInitial = new List() + // { + // new() { Artist = "Artist, 1", Title = "Title, , 1", DownloadPath = "path/to/file1", State = TrackState.Downloaded }, + // new() { Artist = "Artist, 1.5", Title = "Title, , 1.5", DownloadPath = Path.Join(Directory.GetCurrentDirectory(), "file1.5"), State = TrackState.Downloaded }, + // new() { Artist = "Artist, 2", Title = "Title2", DownloadPath = "path/to/file2", State = TrackState.AlreadyExists } + // }; + // var toBeDownloadedInitial = new List() + // { + // new() { Artist = "ArtistA", Album = "Albumm", Title = "TitleA" }, + // new() { Artist = "ArtistB", Album = "Albumm", Title = "TitleB" } + // }; + + // var trackLists = new TrackLists(); + // trackLists.AddEntry(new TrackListEntry(TrackType.Normal)); + // foreach (var t in notFoundInitial) + // trackLists.AddTrackToLast(t); + // foreach (var t in existingInitial) + // trackLists.AddTrackToLast(t); + // foreach (var t in toBeDownloadedInitial) + // trackLists.AddTrackToLast(t); + + // Program.indexEditor = new M3uEditor(path, trackLists, M3uOption.All); + + // Program.outputDirSkipper = new IndexSkipper(Program.indexEditor, false); + + // var notFound = (List)ProgramInvoke("DoSkipNotFound", new object[] { trackLists[0].list[0] }); + // var existing = (List)ProgramInvoke("DoSkipExisting", new object[] { trackLists[0].list[0] }); + // var toBeDownloaded = trackLists[0].list[0].Where(t => t.State == TrackState.Initial).ToList(); + + // Assert(notFound.SequenceEqualUpToPermutation(notFoundInitial)); + // Assert(existing.SequenceEqualUpToPermutation(existingInitial)); + // Assert(toBeDownloaded.SequenceEqualUpToPermutation(toBeDownloadedInitial)); + + // Printing.PrintTracksTbd(toBeDownloaded, existing, notFound, TrackType.Normal); + + // Program.indexEditor.Update(); + // string output = File.ReadAllText(path); + // string need = + // "#SLDL:./file1.5,\"Artist, 1.5\",,\"Title, , 1.5\",-1,0,3,0;path/to/file1,\"Artist, 1\",,\"Title, , 1\",-1,0,3,0;path/to/file2,\"Artist, 2\",,Title2,-1,0,3,0;,\"Artist; ,3\",,Title3 ;a,-1,0,4,0;,\"Artist,,, ;4\",,Title4,-1,0,4,3;,,,,-1,0,0,0;" + + // "\n" + + // "\n#FAIL: Artist; ,3 - Title3 ;a [NoSuitableFileFound]" + + // "\n#FAIL: Artist,,, ;4 - Title4 [NoSuitableFileFound]" + + // "\npath/to/file1" + + // "\nfile1.5" + + // "\npath/to/file2" + + // "\n"; + // Assert(output == need); + + // toBeDownloaded[0].State = TrackState.Downloaded; + // toBeDownloaded[0].DownloadPath = "new/file/path"; + // toBeDownloaded[1].State = TrackState.Failed; + // toBeDownloaded[1].FailureReason = FailureReason.NoSuitableFileFound; + // existing[1].DownloadPath = "/other/new/file/path"; + + // Program.indexEditor.Update(); + // output = File.ReadAllText(path); + // need = + // "#SLDL:/other/new/file/path,\"Artist, 1.5\",,\"Title, , 1.5\",-1,0,3,0;path/to/file1,\"Artist, 1\",,\"Title, , 1\",-1,0,3,0;path/to/file2,\"Artist, 2\",,Title2,-1,0,3,0;,\"Artist; ,3\",,Title3 ;a,-1,0,4,0;,\"Artist,,, ;4\",,Title4,-1,0,4,3;" + + // ",,,,-1,0,0,0;new/file/path,ArtistA,Albumm,TitleA,-1,0,1,0;,ArtistB,Albumm,TitleB,-1,0,2,3;" + + // "\n" + + // "\n#FAIL: Artist; ,3 - Title3 ;a [NoSuitableFileFound]" + + // "\n#FAIL: Artist,,, ;4 - Title4 [NoSuitableFileFound]" + + // "\npath/to/file1" + + // "\n/other/new/file/path" + + // "\npath/to/file2" + + // "\nnew/file/path" + + // "\n#FAIL: ArtistB - TitleB [NoSuitableFileFound]" + + // "\n"; + // Assert(output == need); + + // Console.WriteLine(); + // Console.WriteLine(output); + + // Program.indexEditor = new M3uEditor(path, trackLists, M3uOption.All); + + // foreach (var t in trackLists.Flattened(false, false)) + // { + // Program.indexEditor.TryGetPreviousRunResult(t, out var prev); + // Assert(prev != null); + // Assert(prev.ToKey() == t.ToKey()); + // Assert(prev.DownloadPath == t.DownloadPath); + // Assert(prev.State == t.State || prev.State == TrackState.NotFoundLastTime); + // Assert(prev.FailureReason == t.FailureReason); + // } + + // Program.indexEditor.Update(); + // output = File.ReadAllText(path); + // Assert(output == need); + + + // var test = new List + // { + // new() { Artist = "ArtistA", Album = "AlbumA", Type = TrackType.Album }, + // new() { Artist = "ArtistB", Album = "AlbumB", Type = TrackType.Album }, + // new() { Artist = "ArtistC", Album = "AlbumC", Type = TrackType.Album }, + // }; + + // trackLists = new TrackLists(); + // foreach (var t in test) + // trackLists.AddEntry(new TrackListEntry(t)); + + // File.WriteAllText(path, ""); + // Program.indexEditor = new M3uEditor(path, trackLists, M3uOption.Index); + // Program.indexEditor.Update(); + + // Assert(File.ReadAllText(path) == ""); + + // test[0].State = TrackState.Downloaded; + // test[0].DownloadPath = "download/path"; + // test[1].State = TrackState.Failed; + // test[1].FailureReason = FailureReason.NoSuitableFileFound; + // test[2].State = TrackState.AlreadyExists; + + // Program.indexEditor.Update(); + + // Program.indexEditor = new M3uEditor(path, trackLists, M3uOption.Index); + + // foreach (var t in test) + // { + // Program.indexEditor.TryGetPreviousRunResult(t, out var tt); + // Assert(tt != null); + // Assert(tt.ToKey() == t.ToKey()); + // t.DownloadPath = "this should not change tt.DownloadPath"; + // Assert(t.DownloadPath != tt.DownloadPath); + // } + + // File.Delete(path); + + // Passed(); + //} } static class Helpers diff --git a/slsk-batchdl/Utilities/Printing.cs b/slsk-batchdl/Utilities/Printing.cs index 6694c01..28c72ff 100644 --- a/slsk-batchdl/Utilities/Printing.cs +++ b/slsk-batchdl/Utilities/Printing.cs @@ -141,39 +141,39 @@ public static class Printing } - public static async Task PrintResults(TrackListEntry tle, List existing, List notFound) + public static async Task PrintResults(TrackListEntry tle, List existing, List notFound, Config config) { - await Program.InitClientAndUpdateIfNeeded(); + await Program.InitClientAndUpdateIfNeeded(config); if (tle.source.Type == TrackType.Normal) { - await Search.SearchAndPrintResults(tle.list[0]); + await Search.SearchAndPrintResults(tle.list[0], config); } else if (tle.source.Type == TrackType.Aggregate) { Console.WriteLine(new string('-', 60)); Console.WriteLine($"Results for aggregate {tle.source.ToString(true)}:"); - PrintTracksTbd(tle.list[0].Where(t => t.State == TrackState.Initial).ToList(), existing, notFound, tle.source.Type); + PrintTracksTbd(tle.list[0].Where(t => t.State == TrackState.Initial).ToList(), existing, notFound, tle.source.Type, config); } else if (tle.source.Type == TrackType.Album) { Console.WriteLine(new string('-', 60)); - if (!Config.I.printOption.HasFlag(PrintOption.Full)) + if (!config.printOption.HasFlag(PrintOption.Full)) Console.WriteLine($"Result 1 of {tle.list.Count} for album {tle.source.ToString(true)}:"); else Console.WriteLine($"Results ({tle.list.Count}) for album {tle.source.ToString(true)}:"); if (tle.list.Count > 0 && tle.list[0].Count > 0) { - if (!Config.I.noBrowseFolder) + if (!config.noBrowseFolder) Console.WriteLine("[Skipping full folder retrieval]"); foreach (var ls in tle.list) { PrintAlbum(ls); - if (!Config.I.printOption.HasFlag(PrintOption.Full)) + if (!config.printOption.HasFlag(PrintOption.Full)) break; } } @@ -201,16 +201,16 @@ public static class Printing } - public static void PrintTracksTbd(List toBeDownloaded, List existing, List notFound, TrackType type, bool summary = true) + public static void PrintTracksTbd(List toBeDownloaded, List existing, List notFound, TrackType type, Config config, bool summary = true) { - if (type == TrackType.Normal && !Config.I.PrintTracks && toBeDownloaded.Count == 1 && existing.Count + notFound.Count == 0) + if (type == TrackType.Normal && !config.PrintTracks && toBeDownloaded.Count == 1 && existing.Count + notFound.Count == 0) return; string notFoundLastTime = notFound.Count > 0 ? $"{notFound.Count} not found" : ""; string alreadyExist = existing.Count > 0 ? $"{existing.Count} already exist" : ""; notFoundLastTime = alreadyExist.Length > 0 && notFoundLastTime.Length > 0 ? ", " + notFoundLastTime : notFoundLastTime; string skippedTracks = alreadyExist.Length + notFoundLastTime.Length > 0 ? $" ({alreadyExist}{notFoundLastTime})" : ""; - bool full = Config.I.printOption.HasFlag(PrintOption.Full); + bool full = config.printOption.HasFlag(PrintOption.Full); bool allSkipped = existing.Count + notFound.Count > toBeDownloaded.Count; if (summary && (type == TrackType.Normal || skippedTracks.Length > 0)) @@ -218,24 +218,24 @@ public static class Printing if (toBeDownloaded.Count > 0) { - bool showAll = type != TrackType.Normal || Config.I.PrintTracks || Config.I.PrintResults; - PrintTracks(toBeDownloaded, showAll ? int.MaxValue : 10, full, infoFirst: Config.I.PrintTracks); + bool showAll = type != TrackType.Normal || config.PrintTracks || config.PrintResults; + PrintTracks(toBeDownloaded, showAll ? int.MaxValue : 10, full, infoFirst: config.PrintTracks); if (full && (existing.Count > 0 || notFound.Count > 0)) Console.WriteLine("\n-----------------------------------------------\n"); } - if (Config.I.PrintTracks || Config.I.PrintResults) + if (config.PrintTracks || config.PrintResults) { if (existing.Count > 0) { Console.WriteLine($"\nThe following tracks already exist:"); - PrintTracks(existing, fullInfo: full, infoFirst: Config.I.PrintTracks); + PrintTracks(existing, fullInfo: full, infoFirst: config.PrintTracks); } if (notFound.Count > 0) { Console.WriteLine($"\nThe following tracks were not found during a prior run:"); - PrintTracks(notFound, fullInfo: full, infoFirst: Config.I.PrintTracks); + PrintTracks(notFound, fullInfo: full, infoFirst: config.PrintTracks); } } } @@ -307,17 +307,15 @@ public static class Printing try { progress.Refresh(current, item); } catch { } } - else if ((Config.I.noProgress || Console.IsOutputRedirected) && print) + else if ((progress == null || Console.IsOutputRedirected) && print) { Console.WriteLine(item); } } - public static void WriteLine(string value, ConsoleColor color = ConsoleColor.Gray, bool safe = false, bool debugOnly = false) + public static void WriteLine(string value, ConsoleColor color = ConsoleColor.Gray, bool safe = false) { - if (debugOnly && !Config.I.debugInfo) - return; if (!safe) { Console.ForegroundColor = color; @@ -339,11 +337,18 @@ public static class Printing } - public static ProgressBar? GetProgressBar() + public static void WriteLineIf(string value, bool condition, ConsoleColor color = ConsoleColor.Gray) + { + if (condition) + Printing.WriteLine(value, color); + } + + + public static ProgressBar? GetProgressBar(Config config) { lock (consoleLock) { - if (!Config.I.noProgress) + if (!config.noProgress) { return new ProgressBar(PbStyle.SingleLine, 100, Console.WindowWidth - 10, character: ' '); } diff --git a/slsk-batchdl/slsk-batchdl.csproj b/slsk-batchdl/slsk-batchdl.csproj index d9b6ca4..a5946e6 100644 --- a/slsk-batchdl/slsk-batchdl.csproj +++ b/slsk-batchdl/slsk-batchdl.csproj @@ -6,7 +6,7 @@ enable enable sldl - 2.3.1 + 2.4.0 @@ -27,7 +27,7 @@ - +