Skip to content

Commit 014c04b

Browse files
author
Meyn
committed
Improved Slskd path, priority and timeout handling
squash squash
1 parent 9b56dff commit 014c04b

File tree

5 files changed

+97
-21
lines changed

5 files changed

+97
-21
lines changed

Tubifarry/Download/Clients/Soulseek/SlskdClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private async Task CleanStaleDirectoriesAsync(string directoryPath)
126126
{
127127
try
128128
{
129-
string localPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(directoryPath)).FullPath;
129+
string localPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(Path.Combine(Settings.DownloadPath, directoryPath))).FullPath;
130130
await Task.Delay(1000);
131131
HttpRequest request = BuildHttpRequest("/api/v0/transfers/downloads/");
132132
HttpResponse response = await ExecuteAsync(request);
@@ -226,7 +226,7 @@ private static ReleaseInfo CreateReleaseInfoFromDownloadDirectory(string usernam
226226

227227
IGrouping<string, SlskdFileData> directory = dir.ToSlskdFileDataList().GroupBy(_ => dir.Directory).First();
228228

229-
AlbumData albumData = SlskdItemsParser.CreateAlbumData(null!, directory, searchData, folderData);
229+
AlbumData albumData = SlskdItemsParser.CreateAlbumData(null!, directory, searchData, folderData, null, 0);
230230
ReleaseInfo release = albumData.ToReleaseInfo();
231231
release.DownloadProtocol = null;
232232
return release;

Tubifarry/Download/Clients/Soulseek/SlskdModels.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,25 @@ public DownloadClientItem GetDownloadClientItem(OsPath downloadPath, TimeSpan? t
118118
.Select(file => Path.GetFileName(file.File.Filename)).ToList();
119119

120120
DownloadItemStatus status = DownloadItemStatus.Queued;
121-
DateTime lastTime = SlskdDownloadDirectory.Files.Max(x => x.EnqueuedAt > x.StartedAt ? x.EnqueuedAt : x.StartedAt + x.ElapsedTime);
122121

123-
if (now - lastTime > timeout)
122+
bool anyActiveDownload = SlskdDownloadDirectory.Files.Any(f =>
123+
f.State == "InProgress" || f.State == "Queued, Locally" || f.State == "Initializing");
124+
125+
List<SlskdDownloadFile> incompleteFiles = SlskdDownloadDirectory.Files.Where(f => !f.State.StartsWith("Completed")).ToList();
126+
127+
bool allStuckInRemoteQueue = incompleteFiles.Count != 0 && incompleteFiles.All(f =>
128+
f.State == "Queued, Remotely" && (now - f.EnqueuedAt) > timeout);
129+
130+
if (allStuckInRemoteQueue && !anyActiveDownload)
124131
{
125132
status = DownloadItemStatus.Failed;
126133
}
134+
else if (!anyActiveDownload && incompleteFiles.Count != 0)
135+
{
136+
DateTime lastActivity = incompleteFiles.SelectMany(f => new[] { f.EnqueuedAt, f.StartedAt, f.StartedAt + f.ElapsedTime }).Max();
137+
if ((now - lastActivity) > (timeout * 2))
138+
status = DownloadItemStatus.Failed;
139+
}
127140
else if ((double)failedFiles.Count / fileStatuses.Count * 100 > 20)
128141
{
129142
status = DownloadItemStatus.Failed;

Tubifarry/Indexers/Soulseek/SlsdkRecords.cs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,29 +65,64 @@ public record SlskdFolderData(
6565
[property: JsonPropertyName("fileCount")] int FileCount,
6666
[property: JsonPropertyName("files")] List<SlskdFileData> Files)
6767
{
68-
public int CalculatePriority()
68+
public int CalculatePriority(int expectedTrackCount = 0)
6969
{
70+
// Early exit: completely locked folders
7071
if (LockedFileCount >= FileCount && FileCount > 0)
7172
return 0;
7273

74+
// Early exit: more than 50% locked = useless source
75+
double availabilityRatio = FileCount > 0 ? (FileCount - LockedFileCount) / (double)FileCount : 1.0;
76+
if (availabilityRatio <= 0.5)
77+
return 0;
78+
7379
int score = 0;
74-
if (FileCount > 0)
80+
81+
// Get actual track count
82+
int actualTrackCount = 0;
83+
if (expectedTrackCount > 0)
7584
{
76-
double availabilityRatio = (FileCount - LockedFileCount) / (double)FileCount;
77-
score += (int)(Math.Pow(availabilityRatio, 0.6) * 4000);
85+
actualTrackCount = Files.Count(f =>
86+
AudioFormatHelper.GetAudioCodecFromExtension(
87+
f.Extension ?? System.IO.Path.GetExtension(f.Filename) ?? "") != AudioFormat.Unknown);
7888
}
7989

90+
// Early exit: Missing 50%+ of expected tracks
91+
if (expectedTrackCount > 0 && actualTrackCount > 0 && actualTrackCount <= expectedTrackCount * 0.5)
92+
return 0;
93+
94+
// ===== TRACK COUNT MATCHING (0 to +2500) =====
95+
if (expectedTrackCount > 0 && actualTrackCount > 0)
96+
{
97+
int trackDiff = actualTrackCount - expectedTrackCount;
98+
99+
if (trackDiff < 0) // -1: ~500 pts, -2: ~60 pts, -3+: near 0
100+
score += (int)(2500 * Math.Exp(-Math.Pow(Math.Abs(trackDiff), 2) * 5));
101+
else if (trackDiff == 0) // PERFECT MATCH
102+
score += 2500;
103+
else // EXTRA TRACKS: Less critical, just penalized: +1: ~1600 pts, +2: ~600 pts, +3: ~100 pts, +15: near 0
104+
score += (int)(2500 * Math.Exp(-Math.Pow(trackDiff, 2) * 1.5));
105+
}
106+
107+
// ===== AVAILABILITY RATIO (0 to +2000) =====
108+
score += (int)(Math.Pow(availabilityRatio, 2.0) * 2000);
109+
110+
// ===== UPLOAD SPEED (0 to +1800) =====
80111
if (UploadSpeed > 0)
81112
{
82113
double speedMbps = UploadSpeed / (1024.0 * 1024.0 / 8.0);
83-
score += (int)(Math.Log10(Math.Max(0.1, speedMbps) + 1) * 1200);
114+
score += Math.Min(1800, (int)(Math.Log10(Math.Max(0.1, speedMbps) + 1) * 1100));
84115
}
85-
double queuePenalty = Math.Pow(0.9, Math.Min(QueueLength, 30));
86-
score += (int)(queuePenalty * 2000);
87-
if (HasFreeUploadSlot)
88-
score += 1000;
89-
if (FileCount >= 15)
90-
score += 200;
116+
117+
// ===== QUEUE LENGTH (50 to +1500) =====
118+
double queueFactor = Math.Pow(0.94, Math.Min(QueueLength, 40));
119+
score += (int)(queueFactor * 1500);
120+
121+
// ===== FREE UPLOAD SLOT (0 or +800) =====
122+
score += HasFreeUploadSlot ? 800 : 0;
123+
124+
// ===== COLLECTION SIZE (0 to +300) =====
125+
score += Math.Min(300, (int)(Math.Log10(Math.Max(1, FileCount) + 1) * 150));
91126

92127
return Math.Clamp(score, 0, 10000);
93128
}
@@ -100,7 +135,7 @@ public record SlskdSearchData(
100135
[property: JsonPropertyName("expandDirectory")] bool ExpandDirectory,
101136
[property: JsonPropertyName("mimimumFiles")] int MinimumFiles)
102137
{
103-
private readonly static JsonSerializerOptions _jsonOptions = new() { PropertyNameCaseInsensitive = true };
138+
private static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNameCaseInsensitive = true };
104139
public static SlskdSearchData FromJson(string jsonString) => JsonSerializer.Deserialize<SlskdSearchData>(jsonString, _jsonOptions)!;
105140
}
106141

Tubifarry/Indexers/Soulseek/SlskdIndexerParser.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
5252

5353
foreach (SlskdFolderData response in searchResponse.Responses)
5454
{
55-
if (response.FileCount < searchTextData.MinimumFiles || ignoredUsers?.Contains(response.Username) == true)
55+
if (ignoredUsers?.Contains(response.Username) == true)
5656
continue;
5757

5858
IEnumerable<SlskdFileData> filteredFiles = SlskdFileData.GetFilteredFiles(response.Files, Settings.OnlyAudioFiles, Settings.IncludeFileExtensions);
@@ -62,6 +62,13 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
6262
if (string.IsNullOrEmpty(directoryGroup.Key))
6363
continue;
6464

65+
int actualTrackCount = directoryGroup.Count();
66+
if (Settings.FilterLessFilesThanAlbum && searchTextData.MinimumFiles > 0 && actualTrackCount < searchTextData.MinimumFiles)
67+
{
68+
_logger.Trace($"Filtered: {directoryGroup.Key} ({actualTrackCount}/{searchTextData.MinimumFiles} tracks)");
69+
continue;
70+
}
71+
6572
SlskdFolderData folderData = SlskdItemsParser.ParseFolderName(directoryGroup.Key) with
6673
{
6774
Username = response.Username,
@@ -77,7 +84,7 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
7784
if (searchTextData.ExpandDirectory && ShouldExpandDirectory(albumDatas, searchResponse, searchTextData, directoryGroup, folderData))
7885
continue;
7986

80-
AlbumData originalAlbumData = SlskdItemsParser.CreateAlbumData(searchResponse.Id, directoryGroup, searchTextData, folderData, Settings);
87+
AlbumData originalAlbumData = SlskdItemsParser.CreateAlbumData(searchResponse.Id, directoryGroup, searchTextData, folderData, Settings, searchTextData.MinimumFiles);
8188
albumDatas.Add(originalAlbumData);
8289
}
8390
}
@@ -116,7 +123,7 @@ private bool ShouldExpandDirectory(List<AlbumData> albumDatas, SlskdSearchRespon
116123
if (expandedGroup != null)
117124
{
118125
_logger.Debug($"Successfully expanded directory to {expandedGroup.Count()} files");
119-
AlbumData albumData = SlskdItemsParser.CreateAlbumData(searchResponse.Id, expandedGroup, searchTextData, folderData, Settings);
126+
AlbumData albumData = SlskdItemsParser.CreateAlbumData(searchResponse.Id, expandedGroup, searchTextData, folderData, Settings, searchTextData.MinimumFiles);
120127
albumDatas.Add(albumData);
121128
return true;
122129
}

Tubifarry/Indexers/Soulseek/SlskdItemsParser.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
using NLog;
44
using NzbDrone.Common.Instrumentation;
55
using NzbDrone.Core.Indexers;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
69
using System.Text.RegularExpressions;
710
using Tubifarry.Core.Model;
811
using Tubifarry.Core.Utilities;
@@ -60,7 +63,7 @@ public static SlskdFolderData ParseFolderName(string folderPath)
6063
Files: []);
6164
}
6265

63-
public static AlbumData CreateAlbumData(string searchId, IGrouping<string, SlskdFileData> directory, SlskdSearchData searchData, SlskdFolderData folderData, SlskdSettings? settings = null)
66+
public static AlbumData CreateAlbumData(string searchId, IGrouping<string, SlskdFileData> directory, SlskdSearchData searchData, SlskdFolderData folderData, SlskdSettings? settings = null, int expectedTrackCount = 0)
6467
{
6568
string dirNameNorm = NormalizeString(directory.Key);
6669
string searchArtistNorm = NormalizeString(searchData.Artist ?? "");
@@ -111,7 +114,8 @@ public static AlbumData CreateAlbumData(string searchId, IGrouping<string, Slskd
111114
: BitRate) ?? 0,
112115
Size = TotalSize,
113116
InfoUrl = infoUrl,
114-
Priotity = folderData.CalculatePriority(),
117+
ExplicitContent = ExtractExplicitTag(folderData.Path),
118+
Priotity = folderData.CalculatePriority(expectedTrackCount),
115119
CustomString = JsonConvert.SerializeObject(filesToDownload),
116120
ExtraInfo = [$"👤 {folderData.Username} ", $"{(folderData.HasFreeUploadSlot ? "⚡" : "❌")} {folderData.UploadSpeed / 1024.0 / 1024.0:F2}MB/s ", folderData.QueueLength == 0 ? "" : $"📋 {folderData.QueueLength}"],
117121
Duration = TotalDuration
@@ -283,6 +287,23 @@ private static string CleanComponent(string component)
283287
return ReduceWhitespaceRegex().Replace(component.Trim(), " ");
284288
}
285289

290+
private static bool ExtractExplicitTag(string path)
291+
{
292+
Match match = ExplicitTagRegex().Match(path);
293+
if (match.Success)
294+
{
295+
if (match.Groups["negation"].Success && !string.IsNullOrWhiteSpace(match.Groups["negation"].Value))
296+
{
297+
Logger.Trace($"Found negated explicit tag in path, skipping: {match.Value}");
298+
return false;
299+
}
300+
301+
Logger.Trace($"Extracted explicit tag from path: {path}");
302+
return true;
303+
}
304+
return false;
305+
}
306+
286307
private static string? ExtractYearFromPath(string path)
287308
{
288309
Match yearMatch = YearExtractionRegex().Match(path);

0 commit comments

Comments
 (0)