mirror of
https://github.com/fiso64/slsk-batchdl.git
synced 2024-12-22 06:22:41 +00:00
refactor config class
This commit is contained in:
parent
38c40ec021
commit
85571115cf
19 changed files with 953 additions and 803 deletions
|
@ -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
|
||||
|
|
|
@ -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<string, (List<string> args, string? cond)> configProfiles = new();
|
||||
readonly HashSet<string> appliedProfiles = new();
|
||||
private Dictionary<string, (List<string> args, string? cond)> configProfiles;
|
||||
private HashSet<string> 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<string, (List<string> 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<string, (List<string> args, string? cond)>();
|
||||
appliedProfiles = new HashSet<string>();
|
||||
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<string>(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<string> 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<string> args)>? toApply)
|
||||
{
|
||||
toApply = null;
|
||||
|
||||
if (DoNotDownload)
|
||||
return false;
|
||||
if (!HasAutoProfiles)
|
||||
return false;
|
||||
|
||||
bool needUpdate = false;
|
||||
toApply = new List<(string name, List<string> 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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Extractors
|
|||
return input.IsInternetUrl() && input.Contains("bandcamp.com");
|
||||
}
|
||||
|
||||
public async Task<TrackLists> GetTracks(string input, int maxTracks, int offset, bool reverse)
|
||||
public async Task<TrackLists> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,15 +15,15 @@ namespace Extractors
|
|||
return !input.IsInternetUrl() && input.EndsWith(".csv");
|
||||
}
|
||||
|
||||
public async Task<TrackLists> GetTracks(string input, int maxTracks, int offset, bool reverse)
|
||||
public async Task<TrackLists> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace Extractors
|
|||
{
|
||||
public interface IExtractor
|
||||
{
|
||||
Task<TrackLists> GetTracks(string input, int maxTracks, int offset, bool reverse);
|
||||
Task<TrackLists> GetTracks(string input, int maxTracks, int offset, bool reverse, Config config);
|
||||
Task RemoveTrackFromSource(Track track) => Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ namespace Extractors
|
|||
return !input.IsInternetUrl();
|
||||
}
|
||||
|
||||
public async Task<TrackLists> GetTracks(string input, int maxTracks, int offset, bool reverse)
|
||||
public async Task<TrackLists> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,22 +18,22 @@ namespace Extractors
|
|||
return input == "spotify-likes" || input.IsInternetUrl() && input.Contains("spotify.com");
|
||||
}
|
||||
|
||||
public async Task<TrackLists> GetTracks(string input, int maxTracks, int offset, bool reverse)
|
||||
public async Task<TrackLists> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,21 @@ namespace Extractors
|
|||
return !input.IsInternetUrl();
|
||||
}
|
||||
|
||||
public async Task<TrackLists> GetTracks(string input, int maxTracks, int offset, bool reverse)
|
||||
public async Task<TrackLists> 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);
|
||||
|
|
|
@ -21,24 +21,24 @@ namespace Extractors
|
|||
return input.IsInternetUrl() && (input.Contains("youtu.be") || input.Contains("youtube.com"));
|
||||
}
|
||||
|
||||
public async Task<TrackLists> GetTracks(string input, int maxTracks, int offset, bool reverse)
|
||||
public async Task<TrackLists> 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<Track>? deleted = null;
|
||||
List<Track> 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)
|
||||
{
|
||||
|
|
|
@ -10,10 +10,12 @@ public class FileManager
|
|||
readonly HashSet<Track> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Models
|
|||
public class TrackLists
|
||||
{
|
||||
public List<TrackListEntry> lists = new();
|
||||
public int Count => lists.Count;
|
||||
|
||||
public TrackLists() { }
|
||||
|
||||
|
|
|
@ -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<Track, SearchInfo> searches = new();
|
||||
public static readonly ConcurrentDictionary<string, DownloadWrapper> downloads = new();
|
||||
public static readonly ConcurrentDictionary<string, int> userSuccessCount = new();
|
||||
public static readonly ConcurrentDictionary<string, int> 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<Config, TrackListEntry?>() { { 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<Track>();
|
||||
var notFound = new List<Track>();
|
||||
|
||||
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<Track>();
|
||||
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<Track> DoSkipExisting(List<Track> tracks)
|
||||
static List<Track> DoSkipExisting(TrackListEntry tle, Config config, List<Track> tracks)
|
||||
{
|
||||
var existing = new List<Track>();
|
||||
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<Track> DoSkipNotFound(List<Track> tracks)
|
||||
static List<Track> DoSkipNotFound(Config config, List<Track> tracks, M3uEditor indexEditor)
|
||||
{
|
||||
var notFound = new List<Track>();
|
||||
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<Track>? tracks = null;
|
||||
var retrievedFolders = new HashSet<string>();
|
||||
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<Track>? 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<Track> tracks, SemaphoreSlim semaphore, CancellationTokenSource cts)
|
||||
static async Task RunAlbumDownloads(Config config, TrackListEntry tle, FileManager organizer, List<Track> 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<Track>? tracks)
|
||||
static async Task OnAlbumSuccess(Config config, TrackListEntry tle, List<Track>? 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<Track>? tracks)
|
||||
static void OnAlbumFail(Config config, List<Track>? 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<List<Track>> DownloadImages(TrackListEntry tle, List<List<Track>> downloads, AlbumArtOption option, List<Track>? chosenAlbum)
|
||||
static async Task<List<Track>> DownloadImages(Config config, TrackListEntry tle, List<List<Track>> downloads, AlbumArtOption option, List<Track>? chosenAlbum)
|
||||
{
|
||||
var downloadedImages = new List<Track>();
|
||||
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<Track> 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<Track> tracks, bool retrieveFolder)> InteractiveModeAlbum(List<List<Track>> list, bool retrieveFolder, HashSet<string>? retrievedFolders)
|
||||
static async Task<(int index, List<Track> tracks, bool retrieveFolder)> InteractiveModeAlbum(Config config, List<List<Track>> list, bool retrieveFolder, HashSet<string>? 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<Track>(), 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<bool> 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<List<List<Track>>> GetAlbumDownloads(Track track, ResponseData responseData)
|
||||
public static async Task<List<List<Track>>> GetAlbumDownloads(Track track, ResponseData responseData, Config config)
|
||||
{
|
||||
var results = new ConcurrentDictionary<string, (SearchResponse, Soulseek.File)>();
|
||||
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<List<Track>> GetAggregateTracks(Track track, ResponseData responseData)
|
||||
public static async Task<List<Track>> 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<List<List<List<Track>>>> GetAggregateAlbums(Track track, ResponseData responseData)
|
||||
public static async Task<List<List<List<Track>>>> 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<Track> 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<KeyValuePair<string, (SlResponse, SlFile)>> results,
|
||||
Track track, bool useInfer = false, bool useLevenshtein = true, bool albumMode = false)
|
||||
public static IOrderedEnumerable<(SlResponse response, SlFile file)> OrderedResults(
|
||||
IEnumerable<KeyValuePair<string, (SlResponse, SlFile)>> 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<int, FileConditions, FileConditions, SearchOptions> getSearchOptions,
|
||||
Action<SearchResponse> responseHandler, CancellationToken? ct = null, Action? onSearch = null)
|
||||
Action<SearchResponse> 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<Task>();
|
||||
|
||||
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<SearchResponse> rHandler, CancellationToken? ct = null, Action? onSearch = null)
|
||||
static async Task DoSearch(string search, SearchOptions opts, Action<SearchResponse> 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<Track> tracks)
|
||||
public static async Task SearchAndPrintResults(List<Track> 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; }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<string>()
|
||||
{
|
||||
"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<Track>()
|
||||
{
|
||||
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<Track>()
|
||||
{
|
||||
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<Track>()
|
||||
{
|
||||
new() { Artist = "Artist; ,3", Title = "Title3 ;a" },
|
||||
new() { Artist = "Artist,,, ;4", Title = "Title4", State = TrackState.Failed, FailureReason = FailureReason.NoSuitableFileFound }
|
||||
};
|
||||
var existingInitial = new List<Track>()
|
||||
{
|
||||
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<Track>()
|
||||
{
|
||||
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<Track>)ProgramInvoke("DoSkipNotFound", new object[] { trackLists[0].list[0] });
|
||||
var existing = (List<Track>)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<Track>
|
||||
{
|
||||
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<string>()
|
||||
// {
|
||||
// "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<Track>()
|
||||
// {
|
||||
// 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<Track>()
|
||||
// {
|
||||
// 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<Track>()
|
||||
// {
|
||||
// new() { Artist = "Artist; ,3", Title = "Title3 ;a" },
|
||||
// new() { Artist = "Artist,,, ;4", Title = "Title4", State = TrackState.Failed, FailureReason = FailureReason.NoSuitableFileFound }
|
||||
// };
|
||||
// var existingInitial = new List<Track>()
|
||||
// {
|
||||
// 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<Track>()
|
||||
// {
|
||||
// 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<Track>)ProgramInvoke("DoSkipNotFound", new object[] { trackLists[0].list[0] });
|
||||
// var existing = (List<Track>)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<Track>
|
||||
// {
|
||||
// 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
|
||||
|
|
|
@ -141,39 +141,39 @@ public static class Printing
|
|||
}
|
||||
|
||||
|
||||
public static async Task PrintResults(TrackListEntry tle, List<Track> existing, List<Track> notFound)
|
||||
public static async Task PrintResults(TrackListEntry tle, List<Track> existing, List<Track> 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<Track> toBeDownloaded, List<Track> existing, List<Track> notFound, TrackType type, bool summary = true)
|
||||
public static void PrintTracksTbd(List<Track> toBeDownloaded, List<Track> existing, List<Track> 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: ' ');
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>sldl</AssemblyName>
|
||||
<VersionPrefix>2.3.1</VersionPrefix>
|
||||
<VersionPrefix>2.4.0</VersionPrefix>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
@ -27,7 +27,7 @@
|
|||
<PackageReference Include="SpotifyAPI.Web" Version="7.1.1" />
|
||||
<PackageReference Include="SpotifyAPI.Web.Auth" Version="7.1.1" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.4.3" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Loading…
Reference in a new issue