Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
310 changes: 249 additions & 61 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ libpulse-binding = { version = "2.27.1", optional = true }
libpulse-simple-binding = { version = "2.27.1", optional = true }
urlencoding = "2.1"
ureq = "2.9"
uuid = { version = "1.23.0", default-features = false, features = ["v4"] }

[features]
default = []
Expand Down
2 changes: 1 addition & 1 deletion docs/DATABASE_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Currently, a music file's uniqueness is determined solely by its absolute file p
## 5. Destructive Schema Migrations
- **The Issue**: The initialization logic compares `current_version` with `SCHEMA_VERSION`. If they don't match, it executes `drop_tables_if_exist()`.
- **Impact**: Any future update that increments the schema version will permanently and silently delete all user playlists, play records, and library data during application startup.
- **Solution**: Implement a proper migration system (using `ALTER TABLE` statements or via a migration crate like `refinery` or `rusqlite_migration`) to evolve the database schema safely without data loss.
- **Status (Pre-MVP)**: While a non-destructive migration system (e.g. `ALTER TABLE` or migration crates) is normally required, we are intentionally bypassing forward compatibility during our rapid iteration phase prior to reaching an MVP. Destructive migrations are currently acceptable.
2 changes: 1 addition & 1 deletion src/app/components/player_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ impl AppComponent for PlayerComponent {
ctx.playlists.get_mut(playlist_idx)
{
if let Some(track_position) =
playlist.get_pos_by_key(removed_key)
playlist.get_pos_by_key(&removed_key)
{
playlist.remove(track_position);
}
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/playlist_table/actions.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#[derive(Default)]
pub(crate) struct PendingActions {
pub clear_lyrics: Vec<usize>,
pub clear_lyrics: Vec<String>,
pub toggle_selection: Option<usize>,
pub metadata_updates: Vec<(usize, String, String)>,
pub play_track: Option<usize>,
pub remove_track: Option<usize>,
}

impl PendingActions {
pub(crate) fn clear_lyrics(&mut self, key: usize) {
pub(crate) fn clear_lyrics(&mut self, key: String) {
self.clear_lyrics.push(key);
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/components/playlist_table/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ pub(crate) fn render_lyrics_column(
row_id: egui::Id,
column_width: f32,
has_lyrics: bool,
track_key: usize,
track_key: String,
track_path: &Path,
localization: &PlaylistLocalization,
actions: &mut PendingActions,
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/playlist_table/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl<'a> PlaylistTableService<'a> {
}
}

pub(crate) fn clear_lyrics(&mut self, track_key: usize) {
pub(crate) fn clear_lyrics(&mut self, track_key: String) {
self.ctx.update_track_lyrics(track_key, None);
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ impl App {
let db_conn = self.db().connection();
PersistenceService::save_state(
&self.app_settings,
&self.library,
&mut self.library,
&mut self.playlists,
&db_conn,
);
Expand Down Expand Up @@ -270,7 +270,7 @@ impl App {
LibraryImportService::import_library_path(lib_path, lib_cmd_tx, album_art_dir);
}

pub fn update_track_lyrics(&mut self, track_key: usize, lyrics: Option<&str>) {
pub fn update_track_lyrics(&mut self, track_key: String, lyrics: Option<&str>) {
let db_conn = self.db().connection();
let player = self.runtime.as_mut().map(|rt| &mut rt.player);

Expand Down
9 changes: 8 additions & 1 deletion src/app/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct Database {

impl Database {
// The current schema version - increment this when making schema changes
const SCHEMA_VERSION: i32 = 2;
const SCHEMA_VERSION: i32 = 3;

Comment thread
RetricSu marked this conversation as resolved.
pub fn new() -> Result<Self> {
// Get the app's configuration directory
Expand Down Expand Up @@ -89,6 +89,7 @@ impl Database {
key TEXT PRIMARY KEY,
library_path_id INTEGER NOT NULL,
path TEXT NOT NULL,
file_hash TEXT NOT NULL DEFAULT '',
title TEXT,
artist TEXT,
album TEXT,
Expand All @@ -115,6 +116,12 @@ impl Database {
[],
)?;

// Create index on pictures table to prevent N+1 full table scans
connection.execute(
"CREATE INDEX IF NOT EXISTS idx_pictures_lib_id ON pictures(library_item_id)",
[],
)?;

// Create the playlists table
connection.execute(
"CREATE TABLE IF NOT EXISTS playlists (
Expand Down
2 changes: 1 addition & 1 deletion src/app/services/db_persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub struct DBPersistence;
impl DBPersistence {
/// Save library to database
pub fn save_library(
library: &crate::app::library::Library,
library: &mut crate::app::library::Library,
db_conn: &std::sync::Arc<std::sync::Mutex<rusqlite::Connection>>,
) -> Result<(), Box<dyn std::error::Error>> {
library.save_to_db(db_conn)?;
Expand Down
2 changes: 1 addition & 1 deletion src/app/services/library_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl LibraryService {
/// Process a library command and update the library accordingly
pub fn process_library_command(library: &mut Library, lib_cmd: LibraryCommand) {
match lib_cmd {
LibraryCommand::AddItem(lib_item) => library.add_item(lib_item),
LibraryCommand::AddItem(lib_item) => library.add_item(*lib_item),
LibraryCommand::AddView(lib_view) => library.add_view(lib_view),
LibraryCommand::AddPathId(path_id) => library.set_path_to_imported(path_id),
}
Expand Down
6 changes: 3 additions & 3 deletions src/app/services/lyrics_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ impl LyricsService {
/// Update lyrics for a track and persist to database
pub fn update_track_lyrics(
&mut self,
track_key: usize,
track_key: String,
lyrics: Option<&str>,
library: &mut Library,
playlists: &mut [crate::app::Playlist],
player: Option<&mut Player>,
db_conn: &Arc<Mutex<rusqlite::Connection>>,
) {
let lyrics_owned = library.update_item_lyrics(track_key, lyrics);
let lyrics_owned = library.update_item_lyrics(track_key.clone(), lyrics);

// Update lyrics in all playlists
for playlist in playlists.iter_mut() {
Expand Down Expand Up @@ -83,7 +83,7 @@ impl LyricsService {
match db_conn.lock() {
Ok(conn_guard) => conn_guard.execute(
"UPDATE library_items SET lyrics = ?1 WHERE key = ?2",
rusqlite::params![lyrics_param, track_key.to_string()],
rusqlite::params![lyrics_param, track_key],
),
Err(e) => {
tracing::error!(
Expand Down
2 changes: 1 addition & 1 deletion src/app/services/persistence_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ impl PersistenceService {
/// Save all application state
pub fn save_state(
config: &AppSettings,
library: &Library,
library: &mut Library,
playlists: &mut [Playlist],
db_conn: &Arc<Mutex<rusqlite::Connection>>,
) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/services/player_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl PlayerService {

/// Remove the currently selected track from the playlist and handle playback continuation
/// Returns the track key that was removed, or None if no track was removed
pub fn remove_current_track(player: &mut Player) -> Option<usize> {
pub fn remove_current_track(player: &mut Player) -> Option<String> {
if let Some(track) = &player.selected_track {
let track_key = track.key();
// Clear the selected track - the playlist removal will be handled by the caller
Expand Down
Loading
Loading