diff --git a/src/db/migrations/201707091522_playlists_tables/up.sql b/src/db/migrations/201707091522_playlists_tables/up.sql index 7f886cb..69dea8f 100644 --- a/src/db/migrations/201707091522_playlists_tables/up.sql +++ b/src/db/migrations/201707091522_playlists_tables/up.sql @@ -11,7 +11,6 @@ CREATE TABLE playlist_songs ( playlist INTEGER NOT NULL, path TEXT NOT NULL, ordering INTEGER NOT NULL, - FOREIGN KEY(path) REFERENCES songs(path), FOREIGN KEY(playlist) REFERENCES playlists(id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(playlist, ordering) ON CONFLICT REPLACE ); diff --git a/src/db/schema.sqlite b/src/db/schema.sqlite index 84aa6e4..7273458 100644 Binary files a/src/db/schema.sqlite and b/src/db/schema.sqlite differ diff --git a/src/playlist.rs b/src/playlist.rs index a6aad91..74740f0 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -2,9 +2,11 @@ use core::ops::Deref; use diesel; use diesel::prelude::*; use diesel::BelongingToDsl; +use std::path::Path; use db::{self, ConnectionSource}; -use db::{playlists, users}; +use db::{playlists, playlist_songs, songs, users}; +use index::Song; use vfs::VFSSource; use errors::*; @@ -17,18 +19,33 @@ struct NewPlaylist { #[derive(Identifiable, Queryable)] pub struct User { - id: i32, + id: i32, name: String, } #[derive(Identifiable, Queryable, Associations)] #[belongs_to(User, foreign_key="owner")] -struct Playlist { +pub struct Playlist { id: i32, owner: i32, + name: String, } -struct PlaylistSong { +#[derive(Identifiable, Queryable, Associations)] +#[belongs_to(Playlist, foreign_key="playlist")] +pub struct PlaylistSong { + id: i32, + playlist: i32, + path: String, + ordering: i32, +} + +#[derive(Insertable)] +#[table_name="playlist_songs"] +pub struct NewPlaylistSong { + playlist: i32, + path: String, + ordering: i32, } fn list_playlists(owner: &str, db: &T) -> Result> @@ -38,41 +55,138 @@ fn list_playlists(owner: &str, db: &T) -> Result> let connection = connection.lock().unwrap(); let connection = connection.deref(); - let user : User; + let user: User; { - use self::users::dsl::*; - user = users.filter(name.eq(owner)).select((id, name)).first(connection)?; + use self::users::dsl::*; + user = users + .filter(name.eq(owner)) + .select((id, name)) + .first(connection)?; } - + { use self::playlists::dsl::*; - let found_playlists : Vec = Playlist::belonging_to(&user).select(name).load(connection)?; + let found_playlists: Vec = Playlist::belonging_to(&user) + .select(name) + .load(connection)?; Ok(found_playlists) } } -fn save_playlist(name: &str, owner: &str, content: &Vec, db: &T) -> Result<()> +fn save_playlist(name: &str, owner: &str, content: &Vec, db: &T) -> Result<()> where T: ConnectionSource + VFSSource { - let connection = db.get_connection(); - let connection = connection.lock().unwrap(); - let connection = connection.deref(); + // TODO transaction for content delete+add + let user: User; + let new_playlist: NewPlaylist; + let playlist: Playlist; - let new_playlist = NewPlaylist { - name: name.into(), - owner: users::table - .filter(users::columns::name.eq(owner)) - .select(users::columns::id) - .get_result(connection)?, - }; + { + let connection = db.get_connection(); + let connection = connection.lock().unwrap(); + let connection = connection.deref(); - diesel::insert(&new_playlist) + // Find owner + { + use self::users::dsl::*; + user = users + .filter(name.eq(owner)) + .select((id, name)) + .get_result(connection)?; + } + + // Create playlist + new_playlist = NewPlaylist { + name: name.into(), + owner: user.id, + }; + + diesel::insert(&new_playlist) .into(playlists::table) .execute(connection)?; + { + use self::playlists::dsl::*; + playlist = playlists + .filter(name.eq(name).and(owner.eq(user.id))) + .get_result(connection)?; + } + + // Delete old content (if any) + let old_songs = PlaylistSong::belonging_to(&playlist); + diesel::delete(old_songs).execute(connection)?; + } + + // Insert content + let vfs = db.get_vfs()?; + let mut new_songs: Vec = Vec::new(); + new_songs.reserve(content.len()); + for (i, path) in content.iter().enumerate() { + let virtual_path = Path::new(&path); + if let Some(real_path) = vfs.virtual_to_real(virtual_path) + .ok() + .and_then(|p| p.to_str().map(|s| s.to_owned())) { + new_songs.push(NewPlaylistSong { + playlist: playlist.id, + path: real_path.into(), + ordering: i as i32, + }); + } + } + + { + let connection = db.get_connection(); + let connection = connection.lock().unwrap(); + let connection = connection.deref(); + + diesel::insert(&new_songs) + .into(playlist_songs::table) + .execute(connection)?; + } + Ok(()) } +fn read_playlist(playlist_name: &str, owner: &str, db: &T) -> Result> + where T: ConnectionSource + VFSSource +{ + let user: User; + let playlist: Playlist; + let playlist_songs: Vec; + let vfs = db.get_vfs()?; + + { + let connection = db.get_connection(); + let connection = connection.lock().unwrap(); + let connection = connection.deref(); + + // Find owner + { + use self::users::dsl::*; + user = users + .filter(name.eq(owner)) + .select((id, name)) + .get_result(connection)?; + } + + // Find playlist + { + use self::playlists::dsl::*; + playlist = playlists + .filter(name.eq(playlist_name).and(owner.eq(user.id))) + .get_result(connection)?; + } + + // Find content + playlist_songs = PlaylistSong::belonging_to(&playlist) + .order(playlist_songs::columns::ordering) + .get_results(connection)?; + } + + // TODO + Ok(Vec::new()) +} + fn delete_playlist(playlist_name: &str, owner: &str, db: &T) -> Result<()> where T: ConnectionSource + VFSSource { @@ -80,29 +194,32 @@ fn delete_playlist(playlist_name: &str, owner: &str, db: &T) -> Result<()> let connection = connection.lock().unwrap(); let connection = connection.deref(); - let user : User; + let user: User; { - use self::users::dsl::*; - user = users.filter(name.eq(owner)).select((id, name)).first(connection)?; + use self::users::dsl::*; + user = users + .filter(name.eq(owner)) + .select((id, name)) + .first(connection)?; } - + { use self::playlists::dsl::*; let q = Playlist::belonging_to(&user).filter(name.eq(playlist_name)); diesel::delete(q).execute(connection)?; } + Ok(()) } #[test] fn test_create_playlist() { let db = db::_get_test_db("create_playlist.sqlite"); - let playlist_content = Vec::new(); let found_playlists = list_playlists("test_user", &db).unwrap(); assert!(found_playlists.is_empty()); - save_playlist("chill_and_grill", "test_user", &playlist_content, &db).unwrap(); + save_playlist("chill_and_grill", "test_user", &Vec::new(), &db).unwrap(); let found_playlists = list_playlists("test_user", &db).unwrap(); assert_eq!(found_playlists.len(), 1); assert_eq!(found_playlists[0], "chill_and_grill"); @@ -129,3 +246,29 @@ fn test_delete_playlist() { let delete_result = delete_playlist("mellow_bungalow", "someone_else", &db); assert!(delete_result.is_err()); } + +#[test] +fn test_fill_playlist() { + use index; + + let db = db::_get_test_db("fill_playlist.sqlite"); + index::update(&db).unwrap(); + + let mut playlist_content: Vec = index::flatten(&db, Path::new("root")) + .unwrap() + .into_iter() + .map(|s| s.path) + .collect(); + assert_eq!(playlist_content.len(), 12); + + let first_song = playlist_content[0].clone(); + playlist_content.push(first_song); + assert_eq!(playlist_content.len(), 13); + + save_playlist("all_the_music", "test_user", &playlist_content, &db).unwrap(); + + // let songs = read_playlist("all_the_music", "test_user", &db).unwrap(); + // assert_eq!(songs.len(), 13); + // assert_eq!(songs[0].title, Some("Above The Water".to_owned())); + // assert_eq!(songs[12].title, Some("Above The Water".to_owned())); +}