Fix playlist track ordering to respect insertion order

- Add PlaylistLocalTrack struct with playlist_position field
- Add playlist_get_local_tracks_with_position command
- Update PlaylistDetailView to merge tracks by position instead of
  appending local tracks at the end
- Update PlaylistModal to calculate correct position when adding local
  tracks (total count = qobuz + existing local)
- Local tracks now appear in the order they were added, interleaved
  with Qobuz tracks
This commit is contained in:
vicrodh committed 2026-01-16 07:04:21 -06:00
1 parent 24c9df3bce
commit 16486e4591
6 files changed
+136 -14

No files matched your search

+1
View File
@@ -387,6 +387,7 @@ pub fn run() {
library::commands::playlist_add_local_track,
library::commands::playlist_remove_local_track,
library::commands::playlist_get_local_tracks,
library::commands::playlist_get_local_tracks_with_position,
library::commands::playlist_get_all_local_track_counts,
library::commands::playlist_clear_local_tracks,
// Playlist management commands
+13
View File
@@ -1031,6 +1031,19 @@ pub async fn playlist_get_local_tracks(
.map_err(|e| e.to_string())
}
/// Get all local tracks in a playlist with their positions (for mixed ordering)
#[tauri::command]
pub async fn playlist_get_local_tracks_with_position(
playlist_id: u64,
state: State<'_, LibraryState>,
) -> Result<Vec<crate::library::PlaylistLocalTrack>, String> {
log::info!("Command: playlist_get_local_tracks_with_position {}", playlist_id);
let db = state.db.lock().await;
db.get_playlist_local_tracks_with_position(playlist_id)
.map_err(|e| e.to_string())
}
/// Get local track counts for all playlists
#[tauri::command]
pub async fn playlist_get_all_local_track_counts(
+50
View File
@@ -1550,6 +1550,56 @@ impl LibraryDatabase {
.map_err(|e| LibraryError::Database(format!("Failed to collect playlist local tracks: {}", e)))
}
/// Get all local tracks in a playlist with their positions (for mixed ordering)
pub fn get_playlist_local_tracks_with_position(&self, qobuz_playlist_id: u64) -> Result<Vec<crate::library::PlaylistLocalTrack>, LibraryError> {
let mut stmt = self.conn.prepare(
"SELECT t.id, t.file_path, t.title, t.artist, t.album, t.album_artist,
t.album_group_key, t.album_group_title, t.track_number, t.disc_number,
t.year, t.genre, t.duration_secs, t.format, t.bit_depth, t.sample_rate,
t.channels, t.file_size_bytes, t.cue_file_path, t.cue_start_secs,
t.cue_end_secs, t.artwork_path, t.last_modified, t.indexed_at, plt.position
FROM playlist_local_tracks plt
JOIN local_tracks t ON plt.local_track_id = t.id
WHERE plt.qobuz_playlist_id = ?1
ORDER BY plt.position ASC"
).map_err(|e| LibraryError::Database(format!("Failed to prepare statement: {}", e)))?;
let tracks = stmt.query_map(params![qobuz_playlist_id as i64], |row| {
Ok(crate::library::PlaylistLocalTrack {
track: LocalTrack {
id: row.get(0)?,
file_path: row.get(1)?,
title: row.get(2)?,
artist: row.get(3)?,
album: row.get(4)?,
album_artist: row.get(5)?,
album_group_key: row.get::<_, Option<String>>(6)?.unwrap_or_default(),
album_group_title: row.get::<_, Option<String>>(7)?.unwrap_or_default(),
track_number: row.get(8)?,
disc_number: row.get(9)?,
year: row.get(10)?,
genre: row.get(11)?,
duration_secs: row.get(12)?,
format: Self::parse_format(&row.get::<_, String>(13)?),
bit_depth: row.get(14)?,
sample_rate: row.get(15)?,
channels: row.get(16)?,
file_size_bytes: row.get(17)?,
cue_file_path: row.get(18)?,
cue_start_secs: row.get(19)?,
cue_end_secs: row.get(20)?,
artwork_path: row.get(21)?,
last_modified: row.get(22)?,
indexed_at: row.get(23)?,
},
playlist_position: row.get(24)?,
})
}).map_err(|e| LibraryError::Database(format!("Failed to query playlist local tracks with position: {}", e)))?;
tracks.collect::<Result<Vec<_>, _>>()
.map_err(|e| LibraryError::Database(format!("Failed to collect playlist local tracks with position: {}", e)))
}
/// Get count of local tracks in a playlist
pub fn get_playlist_local_track_count(&self, qobuz_playlist_id: u64) -> Result<u32, LibraryError> {
let count: u32 = self.conn.query_row(
+9
View File
@@ -104,6 +104,15 @@ impl Default for LocalTrack {
}
}
/// A local track within a playlist, including its position
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlaylistLocalTrack {
#[serde(flatten)]
pub track: LocalTrack,
/// Position in the combined playlist (Qobuz + local tracks)
pub playlist_position: i32,
}
/// An album aggregated from local tracks
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalAlbum {