mirror of
https://github.com/agersant/polaris
synced 2024-12-03 18:19:09 +00:00
Implemented writing songs to playlist
This commit is contained in:
parent
09b7eca2b5
commit
729bf7a653
3 changed files with 170 additions and 28 deletions
|
@ -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
|
||||
);
|
||||
|
|
Binary file not shown.
197
src/playlist.rs
197
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<T>(owner: &str, db: &T) -> Result<Vec<String>>
|
||||
|
@ -38,41 +55,138 @@ fn list_playlists<T>(owner: &str, db: &T) -> Result<Vec<String>>
|
|||
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<String> = Playlist::belonging_to(&user).select(name).load(connection)?;
|
||||
let found_playlists: Vec<String> = Playlist::belonging_to(&user)
|
||||
.select(name)
|
||||
.load(connection)?;
|
||||
Ok(found_playlists)
|
||||
}
|
||||
}
|
||||
|
||||
fn save_playlist<T>(name: &str, owner: &str, content: &Vec<PlaylistSong>, db: &T) -> Result<()>
|
||||
fn save_playlist<T>(name: &str, owner: &str, content: &Vec<String>, 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<NewPlaylistSong> = 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<T>(playlist_name: &str, owner: &str, db: &T) -> Result<Vec<Song>>
|
||||
where T: ConnectionSource + VFSSource
|
||||
{
|
||||
let user: User;
|
||||
let playlist: Playlist;
|
||||
let playlist_songs: Vec<PlaylistSong>;
|
||||
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<T>(playlist_name: &str, owner: &str, db: &T) -> Result<()>
|
||||
where T: ConnectionSource + VFSSource
|
||||
{
|
||||
|
@ -80,29 +194,32 @@ fn delete_playlist<T>(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<String> = 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()));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue