mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Fix playlist linking to save all playlists and add restart banner
- Read playlists from .env file instead of stale in-memory config - This fixes issue where linking multiple playlists only saved the last one - Add prominent restart banner at top when config changes - Update UI immediately after linking/unlinking playlists - Banner shows 'Restart Now' button and dismissible option - All config changes now show the restart banner instead of toast messages - GetJellyfinPlaylists now reads from .env for accurate linked status
This commit is contained in:
@@ -450,7 +450,8 @@ public class AdminController : ControllerBase
|
|||||||
var decodedName = Uri.UnescapeDataString(name);
|
var decodedName = Uri.UnescapeDataString(name);
|
||||||
_logger.LogInformation("Removing playlist: {Name}", decodedName);
|
_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);
|
var playlist = currentPlaylists.FirstOrDefault(p => p.Name == decodedName);
|
||||||
|
|
||||||
if (playlist == null)
|
if (playlist == null)
|
||||||
@@ -767,6 +768,9 @@ public class AdminController : ControllerBase
|
|||||||
|
|
||||||
var playlists = new List<object>();
|
var playlists = new List<object>();
|
||||||
|
|
||||||
|
// Read current playlists from .env file for accurate linked status
|
||||||
|
var configuredPlaylists = await ReadPlaylistsFromEnvFile();
|
||||||
|
|
||||||
if (doc.RootElement.TryGetProperty("Items", out var items))
|
if (doc.RootElement.TryGetProperty("Items", out var items))
|
||||||
{
|
{
|
||||||
foreach (var item in items.EnumerateArray())
|
foreach (var item in items.EnumerateArray())
|
||||||
@@ -784,7 +788,7 @@ public class AdminController : ControllerBase
|
|||||||
childCount = ric.GetInt32();
|
childCount = ric.GetInt32();
|
||||||
|
|
||||||
// Check if this playlist is configured in allstarr and get linked Spotify ID
|
// 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));
|
.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||||
var isConfigured = configuredPlaylist != null;
|
var isConfigured = configuredPlaylist != null;
|
||||||
var linkedSpotifyId = configuredPlaylist?.Id;
|
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}",
|
_logger.LogInformation("Linking Jellyfin playlist {JellyfinId} to Spotify playlist {SpotifyId} with name {Name}",
|
||||||
jellyfinPlaylistId, request.SpotifyPlaylistId, request.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
|
// Check if already configured
|
||||||
var existingPlaylist = _spotifyImportSettings.Playlists
|
var existingPlaylist = currentPlaylists
|
||||||
.FirstOrDefault(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
|
.FirstOrDefault(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (existingPlaylist != null)
|
if (existingPlaylist != null)
|
||||||
@@ -919,7 +926,6 @@ public class AdminController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the playlist to configuration
|
// Add the playlist to configuration
|
||||||
var currentPlaylists = _spotifyImportSettings.Playlists.ToList();
|
|
||||||
currentPlaylists.Add(new SpotifyPlaylistConfig
|
currentPlaylists.Add(new SpotifyPlaylistConfig
|
||||||
{
|
{
|
||||||
Name = request.Name,
|
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}\"";
|
return $"MediaBrowser Client=\"Allstarr\", Device=\"Server\", DeviceId=\"allstarr-admin\", Version=\"1.0.0\", Token=\"{_jellyfinSettings.ApiKey}\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read current playlists from .env file (not stale in-memory config)
|
||||||
|
/// </summary>
|
||||||
|
private async Task<List<SpotifyPlaylistConfig>> ReadPlaylistsFromEnvFile()
|
||||||
|
{
|
||||||
|
var playlists = new List<SpotifyPlaylistConfig>();
|
||||||
|
|
||||||
|
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<string[][]>(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)
|
private static string MaskValue(string? value, int showLast = 0)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value)) return "(not set)";
|
if (string.IsNullOrEmpty(value)) return "(not set)";
|
||||||
|
|||||||
@@ -329,6 +329,40 @@
|
|||||||
color: var(--text-secondary);
|
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 {
|
.modal {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -478,6 +512,13 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Restart Required Banner -->
|
||||||
|
<div class="restart-banner" id="restart-banner">
|
||||||
|
⚠️ Configuration changed. Restart required to apply changes.
|
||||||
|
<button onclick="restartContainer()">Restart Now</button>
|
||||||
|
<button onclick="dismissRestartBanner()" style="background: transparent; border: 1px solid var(--bg-primary);">Dismiss</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<h1>
|
||||||
@@ -851,6 +892,18 @@
|
|||||||
// Track if we've already initialized the cookie date to prevent infinite loop
|
// Track if we've already initialized the cookie date to prevent infinite loop
|
||||||
let cookieDateInitialized = false;
|
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
|
// Tab switching
|
||||||
document.querySelectorAll('.tab').forEach(tab => {
|
document.querySelectorAll('.tab').forEach(tab => {
|
||||||
tab.addEventListener('click', () => {
|
tab.addEventListener('click', () => {
|
||||||
@@ -1190,8 +1243,10 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast('Playlist linked! Restart container to apply.', 'success');
|
showToast('Playlist linked!', 'success');
|
||||||
|
showRestartBanner();
|
||||||
closeModal('link-playlist-modal');
|
closeModal('link-playlist-modal');
|
||||||
|
// Refresh both tabs to show updated status
|
||||||
fetchJellyfinPlaylists();
|
fetchJellyfinPlaylists();
|
||||||
fetchPlaylists();
|
fetchPlaylists();
|
||||||
} else {
|
} else {
|
||||||
@@ -1213,7 +1268,8 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast('Playlist unlinked. Restart container to apply.', 'success');
|
showToast('Playlist unlinked.', 'success');
|
||||||
|
showRestartBanner();
|
||||||
fetchJellyfinPlaylists();
|
fetchJellyfinPlaylists();
|
||||||
fetchPlaylists();
|
fetchPlaylists();
|
||||||
} else {
|
} else {
|
||||||
@@ -1288,6 +1344,7 @@
|
|||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
document.getElementById('restart-status').textContent = 'Server is back! Reloading...';
|
document.getElementById('restart-status').textContent = 'Server is back! Reloading...';
|
||||||
|
dismissRestartBanner();
|
||||||
setTimeout(() => window.location.reload(), 500);
|
setTimeout(() => window.location.reload(), 500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1334,7 +1391,8 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast('Playlist added. Restart container to apply.', 'success');
|
showToast('Playlist added.', 'success');
|
||||||
|
showRestartBanner();
|
||||||
closeModal('add-playlist-modal');
|
closeModal('add-playlist-modal');
|
||||||
} else {
|
} else {
|
||||||
showToast(data.error || 'Failed to add playlist', 'error');
|
showToast(data.error || 'Failed to add playlist', 'error');
|
||||||
@@ -1355,7 +1413,8 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast('Playlist removed. Restart container to apply.', 'success');
|
showToast('Playlist removed.', 'success');
|
||||||
|
showRestartBanner();
|
||||||
fetchPlaylists();
|
fetchPlaylists();
|
||||||
} else {
|
} else {
|
||||||
showToast(data.error || 'Failed to remove playlist', 'error');
|
showToast(data.error || 'Failed to remove playlist', 'error');
|
||||||
@@ -1458,7 +1517,8 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast('Setting updated. Restart container to apply.', 'success');
|
showToast('Setting updated.', 'success');
|
||||||
|
showRestartBanner();
|
||||||
closeModal('edit-setting-modal');
|
closeModal('edit-setting-modal');
|
||||||
fetchConfig();
|
fetchConfig();
|
||||||
fetchStatus();
|
fetchStatus();
|
||||||
|
|||||||
Reference in New Issue
Block a user