Skip to content

Commit 0230ea8

Browse files
author
Meyn
committed
Implement timeout handling for Slskd, fix blocklist population issues, and resolve Spotify URL encoding
- Added timeout handling for Slskd downloads to ensure proper failure marking when the timeout is exceeded. - Fixed blocklist population issues to ensure blocked items are correctly identified and managed. - Resolved Spotify URL encoding problems to prevent double-encoding
1 parent e67fc2a commit 0230ea8

File tree

9 files changed

+40
-12
lines changed

9 files changed

+40
-12
lines changed

Tubifarry/Blocklisting/BaseBlocklist.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public abstract class BaseBlocklist<TProtocol> : IBlocklistForProtocol where TPr
1111

1212
public BaseBlocklist(IBlocklistRepository blocklistRepository) => _blocklistRepository = blocklistRepository;
1313

14-
public string Protocol => nameof(TProtocol);
14+
public string Protocol => typeof(TProtocol).Name;
1515

1616
public bool IsBlocklisted(int artistId, ReleaseInfo release) => _blocklistRepository.BlocklistedByTorrentInfoHash(artistId, release.Guid).Any(b => BaseBlocklist<TProtocol>.SameRelease(b, release));
1717

Tubifarry/Blocklisting/Blocklists.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace NzbDrone.Core.Blocklisting
55
public class YoutubeBlocklist : BaseBlocklist<YoutubeDownloadProtocol>
66
{
77
public YoutubeBlocklist(IBlocklistRepository blocklistRepository) : base(blocklistRepository) { }
8+
89
}
910

1011
public class SoulseekBlocklist : BaseBlocklist<SoulseekDownloadProtocol>

Tubifarry/Download/Clients/Soulseek/SlskdClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public override IEnumerable<DownloadClientItem> GetItems()
7878
{
7979
UpdateDownloadItemsAsync().Wait();
8080
DownloadClientItemClientInfo clientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false);
81-
foreach (DownloadClientItem? clientItem in GetDownloadItems().Select(x => x.GetDownloadClientItem(Settings.DownloadPath)))
81+
foreach (DownloadClientItem? clientItem in GetDownloadItems().Select(x => x.GetDownloadClientItem(Settings.DownloadPath, Settings.GetTimeout())))
8282
{
8383
clientItem.DownloadClientInfo = clientInfo;
8484
yield return clientItem;

Tubifarry/Download/Clients/Soulseek/SlskdModels.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using NzbDrone.Common.Disk;
1+
using NLog;
2+
using NzbDrone.Common.Disk;
3+
using NzbDrone.Common.Instrumentation;
24
using NzbDrone.Core.Indexers.Soulseek;
35
using NzbDrone.Core.Parser.Model;
46
using System.Text.Json;
@@ -18,6 +20,8 @@ public class SlskdDownloadItem
1820

1921
public event EventHandler<SlskdDownloadFile>? FileStateChanged;
2022

23+
private Logger _logger;
24+
2125
private SlskdDownloadDirectory? _slskdDownloadDirectory;
2226
private Dictionary<string, string> _previousFileStates = new();
2327

@@ -37,6 +41,7 @@ public SlskdDownloadDirectory? SlskdDownloadDirectory
3741

3842
public SlskdDownloadItem(RemoteAlbum remoteAlbum)
3943
{
44+
_logger = NzbDroneLogger.GetLogger(this);
4045
RemoteAlbum = remoteAlbum;
4146
FileData = JsonSerializer.Deserialize<List<SlskdFileData>>(RemoteAlbum.Release.Source) ?? new();
4247
_lastUpdateTime = DateTime.UtcNow;
@@ -65,7 +70,7 @@ private void CompareFileStates(SlskdDownloadDirectory? previousDirectory, SlskdD
6570
.Split('/')
6671
.LastOrDefault() ?? ""));
6772

68-
public DownloadClientItem GetDownloadClientItem(string downloadPath)
73+
public DownloadClientItem GetDownloadClientItem(string downloadPath, TimeSpan? timeout)
6974
{
7075
_downloadClientItem.OutputPath = GetFullFolderPath(downloadPath);
7176
_downloadClientItem.Title = RemoteAlbum.Release.Title;
@@ -92,8 +97,11 @@ public DownloadClientItem GetDownloadClientItem(string downloadPath)
9297
.Select(file => Path.GetFileName(file.Filename)).ToList();
9398

9499
DownloadItemStatus status = DownloadItemStatus.Queued;
100+
DateTime lastTime = SlskdDownloadDirectory.Files.Max(x => x.EnqueuedAt > x.StartedAt ? x.EnqueuedAt : x.StartedAt + x.ElapsedTime);
95101

96-
if ((double)failedFiles.Count / fileStatuses.Count * 100 > 10)
102+
if (now - lastTime > timeout)
103+
status = DownloadItemStatus.Failed;
104+
else if ((double)failedFiles.Count / fileStatuses.Count * 100 > 10)
97105
{
98106
status = DownloadItemStatus.Failed;
99107
_downloadClientItem.Message = $"Downloading {failedFiles.Count} files failed: {string.Join(", ", failedFiles)}";
@@ -161,7 +169,8 @@ public record SlskdDownloadFile(
161169
long BytesRemaining,
162170
TimeSpan ElapsedTime,
163171
double PercentComplete,
164-
TimeSpan RemainingTime
172+
TimeSpan RemainingTime,
173+
TimeSpan? EndedAt
165174
)
166175
{
167176
public DownloadItemStatus GetStatus() => State switch
@@ -195,13 +204,14 @@ public static IEnumerable<SlskdDownloadFile> GetFiles(JsonElement filesElement)
195204
State: file.TryGetProperty("state", out JsonElement state) ? state.GetString() ?? string.Empty : string.Empty,
196205
RequestedAt: file.TryGetProperty("requestedAt", out JsonElement requestedAt) && DateTime.TryParse(requestedAt.GetString(), out DateTime requestedAtParsed) ? requestedAtParsed : DateTime.MinValue,
197206
EnqueuedAt: file.TryGetProperty("enqueuedAt", out JsonElement enqueuedAt) && DateTime.TryParse(enqueuedAt.GetString(), out DateTime enqueuedAtParsed) ? enqueuedAtParsed : DateTime.MinValue,
198-
StartedAt: file.TryGetProperty("startedAt", out JsonElement startedAt) && DateTime.TryParse(startedAt.GetString(), out DateTime startedAtParsed) ? startedAtParsed : DateTime.MinValue,
207+
StartedAt: file.TryGetProperty("startedAt", out JsonElement startedAt) && DateTime.TryParse(startedAt.GetString(), out DateTime startedAtParsed) ? startedAtParsed.ToUniversalTime() : DateTime.MinValue,
199208
BytesTransferred: file.TryGetProperty("bytesTransferred", out JsonElement bytesTransferred) ? bytesTransferred.GetInt64() : 0L,
200209
AverageSpeed: file.TryGetProperty("averageSpeed", out JsonElement averageSpeed) ? averageSpeed.GetDouble() : 0.0,
201210
BytesRemaining: file.TryGetProperty("bytesRemaining", out JsonElement bytesRemaining) ? bytesRemaining.GetInt64() : 0L,
202211
ElapsedTime: file.TryGetProperty("elapsedTime", out JsonElement elapsedTime) && TimeSpan.TryParse(elapsedTime.GetString(), out TimeSpan elapsedTimeParsed) ? elapsedTimeParsed : TimeSpan.Zero,
203212
PercentComplete: file.TryGetProperty("percentComplete", out JsonElement percentComplete) ? percentComplete.GetDouble() : 0.0,
204-
RemainingTime: file.TryGetProperty("remainingTime", out JsonElement remainingTime) && TimeSpan.TryParse(remainingTime.GetString(), out TimeSpan remainingTimeParsed) ? remainingTimeParsed : TimeSpan.Zero
213+
RemainingTime: file.TryGetProperty("remainingTime", out JsonElement remainingTime) && TimeSpan.TryParse(remainingTime.GetString(), out TimeSpan remainingTimeParsed) ? remainingTimeParsed : TimeSpan.Zero,
214+
EndedAt: file.TryGetProperty("endedAt", out JsonElement endedAt) && TimeSpan.TryParse(endedAt.GetString(), out TimeSpan endedAtParsed) ? endedAtParsed : null
205215
);
206216
}
207217
}

Tubifarry/Download/Clients/Soulseek/SlskdProviderSettings.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ public SlskdProviderSettingsValidator()
2525
.Must(path => string.IsNullOrEmpty(path) || Path.IsPathRooted(path))
2626
.WithMessage("Download path must be a valid directory path.")
2727
.When(x => !string.IsNullOrEmpty(x.DownloadPath));
28+
29+
// Validate LRCLIBInstance URL
30+
RuleFor(x => x.LRCLIBInstance)
31+
.IsValidUrl()
32+
.WithMessage("LRCLIB instance URL must be a valid URL.");
33+
34+
// Timeout validation (only if it has a value)
35+
RuleFor(c => c.Timeout)
36+
.GreaterThanOrEqualTo(0.1)
37+
.WithMessage("Timeout must be at least 0.1 hours.")
38+
.When(c => c.Timeout.HasValue);
2839
}
2940
}
3041

@@ -47,12 +58,20 @@ public class SlskdProviderSettings : IProviderConfig
4758
[FieldDefinition(6, Label = "LRC Lib Instance", Type = FieldType.Url, HelpText = "The URL of a LRC Lib instance to connect to. Default is 'https://lrclib.net'.", Advanced = true)]
4859
public string LRCLIBInstance { get; set; } = "https://lrclib.net";
4960

61+
[FieldDefinition(7, Label = "Timeout", Type = FieldType.Textbox, HelpText = "Specify the maximum time to wait for a response from the Slskd instance before timing out. Fractional values are allowed (e.g., 1.5 for 1 hour and 30 minutes). Set leave blank for no timeout.", Unit = "hours", Advanced = true, Placeholder = "Enter timeout in hours")]
62+
public double? Timeout { get; set; }
63+
5064
[FieldDefinition(98, Label = "Is Fetched remote", Type = FieldType.Checkbox, Hidden = HiddenType.Hidden)]
5165
public bool IsRemotePath { get; set; }
5266

5367
[FieldDefinition(99, Label = "Is Localhost", Type = FieldType.Checkbox, Hidden = HiddenType.Hidden)]
5468
public bool IsLocalhost { get; set; }
5569

70+
71+
public TimeSpan? GetTimeout() => Timeout == null ? null : TimeSpan.FromHours(Timeout.Value);
72+
73+
74+
5675
public NzbDroneValidationResult Validate() => new(Validator.Validate(this));
5776
}
5877
}

Tubifarry/ImportLists/ArrStack/ArrSoundtrackImportSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public class ArrSoundtrackImportSettings : IImportListSettings
7272
[FieldDefinition(6, Label = "Cache Retention Time", Type = FieldType.Number, HelpText = "The number of days to retain cached data.", Advanced = true, Placeholder = "7")]
7373
public int CacheRetentionDays { get; set; } = 7;
7474

75-
[FieldDefinition(7, Label = "Refresh Interval", Type = FieldType.Textbox, HelpText = "The interval in hours to refresh the import list. Fractional values are allowed (e.g., 1.5 for 1 hour and 30 minutes).", Advanced = true, Placeholder = "12")]
75+
[FieldDefinition(7, Label = "Refresh Interval", Type = FieldType.Textbox, HelpText = "The interval to refresh the import list. Fractional values are allowed (e.g., 1.5 for 1 hour and 30 minutes).", Unit = "hours", Advanced = true, Placeholder = "12")]
7676
public double RefreshInterval { get; set; } = 12.0;
7777

7878
public NzbDroneValidationResult Validate() => new(Validator.Validate(this));

Tubifarry/ImportLists/Spotify/SpotifyUserPlaylistImportSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class SpotifyUserPlaylistImportSettings : SpotifySettingsBase<SpotifyUser
2727

2828
public override string Scope => "playlist-read-private";
2929

30-
[FieldDefinition(1, Label = "Refresh Interval", Type = FieldType.Textbox, HelpText = "The interval in hours to refresh the import list. Fractional values are allowed (e.g., 1.5 for 1 hour and 30 minutes).", Advanced = true, Placeholder = "12")]
30+
[FieldDefinition(1, Label = "Refresh Interval", Type = FieldType.Textbox, HelpText = "The interval to refresh the import list. Fractional values are allowed (e.g., 1.5 for 1 hour and 30 minutes).", Unit = "hours", Advanced = true, Placeholder = "12")]
3131
public double RefreshInterval { get; set; } = 12.0;
3232

3333
[FieldDefinition(2, Label = "Cache Directory", Type = FieldType.Path, HelpText = "The directory where cached data will be stored. If left empty, no cache will be used.", Placeholder = "/config/spotify-cache")]

Tubifarry/Indexers/Spotify/SpotifyRequestGenerator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ private IEnumerable<IndexerRequest> GetRequests(string searchQuery, string searc
7575
HandleToken();
7676

7777
string formattedQuery = Uri.EscapeDataString(searchQuery)
78-
.Replace("%20", "%2520") // Encode spaces as %2520 for better compatibility
7978
.Replace(":", "%3A"); // Encode colons as %3A for filters
8079

8180
for (int page = 0; page < MaxPages; page++)

Tubifarry/Indexers/Spotify/SpotifyToYoutubeParser.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
6666
_logger.Error("Spotify response does not contain 'items' property under 'albums'.");
6767
return releases;
6868
}
69-
7069
ProcessAlbums(items, releases);
7170
return releases.OrderByDescending(o => o.PublishDate).ToArray();
7271
}

0 commit comments

Comments
 (0)