diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs
index c6e8dbd..d4abc15 100644
--- a/allstarr/Controllers/JellyfinController.cs
+++ b/allstarr/Controllers/JellyfinController.cs
@@ -3096,6 +3096,54 @@ public class JellyfinController : ControllerBase
return Ok(results);
}
+ ///
+ /// Manually trigger track matching for all Spotify playlists.
+ /// GET /spotify/match?api_key=YOUR_KEY
+ ///
+ [HttpGet("spotify/match", Order = 1)]
+ [ServiceFilter(typeof(ApiKeyAuthFilter))]
+ public async Task TriggerSpotifyMatch([FromServices] IEnumerable hostedServices)
+ {
+ if (!_spotifySettings.Enabled)
+ {
+ return BadRequest(new { error = "Spotify Import is not enabled" });
+ }
+
+ _logger.LogInformation("Manual Spotify track matching triggered");
+
+ // Find the SpotifyTrackMatchingService
+ var matchingService = hostedServices
+ .OfType()
+ .FirstOrDefault();
+
+ if (matchingService == null)
+ {
+ return StatusCode(500, new { error = "SpotifyTrackMatchingService not found" });
+ }
+
+ // Trigger matching asynchronously
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await matchingService.TriggerMatchingAsync();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error during manual track matching");
+ }
+ });
+
+ return Ok(new
+ {
+ status = "started",
+ message = "Track matching started in background. Check logs for progress.",
+ playlists = _spotifySettings.PlaylistNames.Count > 0
+ ? _spotifySettings.PlaylistNames
+ : _spotifySettings.PlaylistIds
+ });
+ }
+
private List ParseMissingTracksJson(string json)
{
var tracks = new List();
diff --git a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs
index 85a8235..97ff395 100644
--- a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs
+++ b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs
@@ -72,28 +72,19 @@ public class SpotifyMissingTracksFetcher : BackgroundService
}
_logger.LogInformation("========================================");
- // Run once on startup if we haven't run in the last 24 hours
+ // Always run once on startup to ensure we have missing tracks
if (!_hasRunOnce)
{
- var shouldRunOnStartup = await ShouldRunOnStartupAsync();
- if (shouldRunOnStartup)
+ _logger.LogInformation("Running initial fetch on startup");
+ try
{
- _logger.LogInformation("Running initial fetch on startup (bypassing sync window check)");
- try
- {
- await FetchMissingTracksAsync(stoppingToken, bypassSyncWindowCheck: true);
- _hasRunOnce = true;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error during startup fetch");
- }
- }
- else
- {
- _logger.LogInformation("Skipping startup fetch - already have recent cache");
+ await FetchMissingTracksAsync(stoppingToken, bypassSyncWindowCheck: true);
_hasRunOnce = true;
}
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error during startup fetch");
+ }
}
while (!stoppingToken.IsCancellationRequested)
diff --git a/allstarr/Services/Spotify/SpotifyTrackMatchingService.cs b/allstarr/Services/Spotify/SpotifyTrackMatchingService.cs
index f00f2c5..edc2ee4 100644
--- a/allstarr/Services/Spotify/SpotifyTrackMatchingService.cs
+++ b/allstarr/Services/Spotify/SpotifyTrackMatchingService.cs
@@ -44,6 +44,17 @@ public class SpotifyTrackMatchingService : BackgroundService
// Wait a bit for the fetcher to run first
await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken);
+ // Run once on startup to match any existing missing tracks
+ try
+ {
+ _logger.LogInformation("Running initial track matching on startup");
+ await MatchAllPlaylistsAsync(stoppingToken);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error during startup track matching");
+ }
+
while (!stoppingToken.IsCancellationRequested)
{
try
@@ -60,6 +71,15 @@ public class SpotifyTrackMatchingService : BackgroundService
}
}
+ ///
+ /// Public method to trigger matching manually (called from controller).
+ ///
+ public async Task TriggerMatchingAsync()
+ {
+ _logger.LogInformation("Manual track matching triggered");
+ await MatchAllPlaylistsAsync(CancellationToken.None);
+ }
+
private async Task MatchAllPlaylistsAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("=== STARTING TRACK MATCHING ===");
@@ -103,7 +123,7 @@ public class SpotifyTrackMatchingService : BackgroundService
var existingMatched = await _cache.GetAsync>(matchedTracksKey);
if (existingMatched != null && existingMatched.Count > 0)
{
- _logger.LogDebug("Playlist {Playlist} already has {Count} matched tracks cached, skipping",
+ _logger.LogInformation("Playlist {Playlist} already has {Count} matched tracks cached, skipping",
playlistName, existingMatched.Count);
return;
}
@@ -112,7 +132,7 @@ public class SpotifyTrackMatchingService : BackgroundService
var missingTracks = await _cache.GetAsync>(missingTracksKey);
if (missingTracks == null || missingTracks.Count == 0)
{
- _logger.LogDebug("No missing tracks found for {Playlist}, skipping matching", playlistName);
+ _logger.LogInformation("No missing tracks found for {Playlist}, skipping matching", playlistName);
return;
}
@@ -158,7 +178,7 @@ public class SpotifyTrackMatchingService : BackgroundService
if (matchCount % 10 == 0)
{
- _logger.LogDebug("Matched {Count}/{Total} tracks for {Playlist}",
+ _logger.LogInformation("Matched {Count}/{Total} tracks for {Playlist}",
matchCount, missingTracks.Count, playlistName);
}
}