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); } }