diff --git a/allstarr/Controllers/AdminController.cs b/allstarr/Controllers/AdminController.cs index 2082452..16a2147 100644 --- a/allstarr/Controllers/AdminController.cs +++ b/allstarr/Controllers/AdminController.cs @@ -450,7 +450,8 @@ public class AdminController : ControllerBase var decodedName = Uri.UnescapeDataString(name); _logger.LogInformation("Removing playlist: {Name}", decodedName); - var currentPlaylists = _spotifyImportSettings.Playlists.ToList(); + // Read current playlists from .env file (not stale in-memory config) + var currentPlaylists = await ReadPlaylistsFromEnvFile(); var playlist = currentPlaylists.FirstOrDefault(p => p.Name == decodedName); if (playlist == null) @@ -767,6 +768,9 @@ public class AdminController : ControllerBase var playlists = new List(); + // Read current playlists from .env file for accurate linked status + var configuredPlaylists = await ReadPlaylistsFromEnvFile(); + if (doc.RootElement.TryGetProperty("Items", out var items)) { foreach (var item in items.EnumerateArray()) @@ -784,7 +788,7 @@ public class AdminController : ControllerBase childCount = ric.GetInt32(); // Check if this playlist is configured in allstarr and get linked Spotify ID - var configuredPlaylist = _spotifyImportSettings.Playlists + var configuredPlaylist = configuredPlaylists .FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); var isConfigured = configuredPlaylist != null; var linkedSpotifyId = configuredPlaylist?.Id; @@ -909,8 +913,11 @@ public class AdminController : ControllerBase _logger.LogInformation("Linking Jellyfin playlist {JellyfinId} to Spotify playlist {SpotifyId} with name {Name}", jellyfinPlaylistId, request.SpotifyPlaylistId, request.Name); + // Read current playlists from .env file (not in-memory config which is stale) + var currentPlaylists = await ReadPlaylistsFromEnvFile(); + // Check if already configured - var existingPlaylist = _spotifyImportSettings.Playlists + var existingPlaylist = currentPlaylists .FirstOrDefault(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)); if (existingPlaylist != null) @@ -919,7 +926,6 @@ public class AdminController : ControllerBase } // Add the playlist to configuration - var currentPlaylists = _spotifyImportSettings.Playlists.ToList(); currentPlaylists.Add(new SpotifyPlaylistConfig { Name = request.Name, @@ -959,6 +965,64 @@ public class AdminController : ControllerBase return $"MediaBrowser Client=\"Allstarr\", Device=\"Server\", DeviceId=\"allstarr-admin\", Version=\"1.0.0\", Token=\"{_jellyfinSettings.ApiKey}\""; } + /// + /// Read current playlists from .env file (not stale in-memory config) + /// + private async Task> ReadPlaylistsFromEnvFile() + { + var playlists = new List(); + + if (!System.IO.File.Exists(_envFilePath)) + { + return playlists; + } + + try + { + var lines = await System.IO.File.ReadAllLinesAsync(_envFilePath); + foreach (var line in lines) + { + if (line.TrimStart().StartsWith("SPOTIFY_IMPORT_PLAYLISTS=")) + { + var value = line.Substring(line.IndexOf('=') + 1).Trim(); + + if (string.IsNullOrWhiteSpace(value) || value == "[]") + { + return playlists; + } + + // Parse JSON array format: [["Name","Id","first|last"],...] + var playlistArrays = JsonSerializer.Deserialize(value); + if (playlistArrays != null) + { + foreach (var arr in playlistArrays) + { + if (arr.Length >= 2) + { + playlists.Add(new SpotifyPlaylistConfig + { + Name = arr[0].Trim(), + Id = arr[1].Trim(), + LocalTracksPosition = arr.Length >= 3 && + arr[2].Trim().Equals("last", StringComparison.OrdinalIgnoreCase) + ? LocalTracksPosition.Last + : LocalTracksPosition.First + }); + } + } + } + break; + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to read playlists from .env file"); + } + + return playlists; + } + private static string MaskValue(string? value, int showLast = 0) { if (string.IsNullOrEmpty(value)) return "(not set)"; diff --git a/allstarr/wwwroot/index.html b/allstarr/wwwroot/index.html index d1632ba..05f6874 100644 --- a/allstarr/wwwroot/index.html +++ b/allstarr/wwwroot/index.html @@ -329,6 +329,40 @@ color: var(--text-secondary); } + .restart-banner { + position: fixed; + top: 0; + left: 0; + right: 0; + background: var(--warning); + color: var(--bg-primary); + padding: 12px 20px; + text-align: center; + font-weight: 500; + z-index: 9998; + display: none; + box-shadow: 0 2px 8px rgba(0,0,0,0.3); + } + + .restart-banner.active { + display: block; + } + + .restart-banner button { + margin-left: 16px; + background: var(--bg-primary); + color: var(--text-primary); + border: none; + padding: 6px 16px; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + } + + .restart-banner button:hover { + background: var(--bg-secondary); + } + .modal { display: none; position: fixed; @@ -478,6 +512,13 @@ + +
+ ⚠️ Configuration changed. Restart required to apply changes. + + +
+

@@ -851,6 +892,18 @@ // Track if we've already initialized the cookie date to prevent infinite loop let cookieDateInitialized = false; + // Track if restart is required + let restartRequired = false; + + function showRestartBanner() { + restartRequired = true; + document.getElementById('restart-banner').classList.add('active'); + } + + function dismissRestartBanner() { + document.getElementById('restart-banner').classList.remove('active'); + } + // Tab switching document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', () => { @@ -1190,8 +1243,10 @@ const data = await res.json(); if (res.ok) { - showToast('Playlist linked! Restart container to apply.', 'success'); + showToast('Playlist linked!', 'success'); + showRestartBanner(); closeModal('link-playlist-modal'); + // Refresh both tabs to show updated status fetchJellyfinPlaylists(); fetchPlaylists(); } else { @@ -1213,7 +1268,8 @@ const data = await res.json(); if (res.ok) { - showToast('Playlist unlinked. Restart container to apply.', 'success'); + showToast('Playlist unlinked.', 'success'); + showRestartBanner(); fetchJellyfinPlaylists(); fetchPlaylists(); } else { @@ -1288,6 +1344,7 @@ }); if (res.ok) { document.getElementById('restart-status').textContent = 'Server is back! Reloading...'; + dismissRestartBanner(); setTimeout(() => window.location.reload(), 500); return; } @@ -1334,7 +1391,8 @@ const data = await res.json(); if (res.ok) { - showToast('Playlist added. Restart container to apply.', 'success'); + showToast('Playlist added.', 'success'); + showRestartBanner(); closeModal('add-playlist-modal'); } else { showToast(data.error || 'Failed to add playlist', 'error'); @@ -1355,7 +1413,8 @@ const data = await res.json(); if (res.ok) { - showToast('Playlist removed. Restart container to apply.', 'success'); + showToast('Playlist removed.', 'success'); + showRestartBanner(); fetchPlaylists(); } else { showToast(data.error || 'Failed to remove playlist', 'error'); @@ -1458,7 +1517,8 @@ const data = await res.json(); if (res.ok) { - showToast('Setting updated. Restart container to apply.', 'success'); + showToast('Setting updated.', 'success'); + showRestartBanner(); closeModal('edit-setting-modal'); fetchConfig(); fetchStatus();