polaris-mirror/src/app/playlist/manager.rs
pmphfm f104355076
Add few more fields to song information ()
* [meta] Add ignore paths to vscode settings

* [feature] Add few more fields to song information

Fields include lyricist, composer, genre, category
and label.
2021-05-20 22:08:43 -07:00

247 lines
5.5 KiB
Rust

use anyhow::Result;
use core::clone::Clone;
use diesel;
use diesel::prelude::*;
use diesel::sql_types;
use diesel::BelongingToDsl;
use std::path::Path;
use super::*;
use crate::app::index::Song;
use crate::app::vfs;
use crate::db::{playlist_songs, playlists, users, DB};
#[derive(Clone)]
pub struct Manager {
db: DB,
vfs_manager: vfs::Manager,
}
impl Manager {
pub fn new(db: DB, vfs_manager: vfs::Manager) -> Self {
Self { db, vfs_manager }
}
pub fn list_playlists(&self, owner: &str) -> Result<Vec<String>, Error> {
let connection = self.db.connect()?;
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&connection)
.optional()
.map_err(anyhow::Error::new)?
.ok_or(Error::UserNotFound)?
};
{
use self::playlists::dsl::*;
let found_playlists: Vec<String> = Playlist::belonging_to(&user)
.select(name)
.load(&connection)
.map_err(anyhow::Error::new)?;
Ok(found_playlists)
}
}
pub fn save_playlist(
&self,
playlist_name: &str,
owner: &str,
content: &[String],
) -> Result<(), Error> {
let new_playlist: NewPlaylist;
let playlist: Playlist;
let vfs = self.vfs_manager.get_vfs()?;
{
let connection = self.db.connect()?;
// Find owner
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&connection)
.optional()
.map_err(anyhow::Error::new)?
.ok_or(Error::UserNotFound)?
};
// Create playlist
new_playlist = NewPlaylist {
name: playlist_name.into(),
owner: user.id,
};
diesel::insert_into(playlists::table)
.values(&new_playlist)
.execute(&connection)
.map_err(anyhow::Error::new)?;
playlist = {
use self::playlists::dsl::*;
playlists
.select((id, owner))
.filter(name.eq(playlist_name).and(owner.eq(user.id)))
.get_result(&connection)
.map_err(anyhow::Error::new)?
}
}
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,
ordering: i as i32,
});
}
}
{
let connection = self.db.connect()?;
connection
.transaction::<_, diesel::result::Error, _>(|| {
// Delete old content (if any)
let old_songs = PlaylistSong::belonging_to(&playlist);
diesel::delete(old_songs).execute(&connection)?;
// Insert content
diesel::insert_into(playlist_songs::table)
.values(&new_songs)
.execute(&*connection)?; // TODO https://github.com/diesel-rs/diesel/issues/1822
Ok(())
})
.map_err(anyhow::Error::new)?;
}
Ok(())
}
pub fn read_playlist(&self, playlist_name: &str, owner: &str) -> Result<Vec<Song>, Error> {
let vfs = self.vfs_manager.get_vfs()?;
let songs: Vec<Song>;
{
let connection = self.db.connect()?;
// Find owner
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&connection)
.optional()
.map_err(anyhow::Error::new)?
.ok_or(Error::UserNotFound)?
};
// Find playlist
let playlist: Playlist = {
use self::playlists::dsl::*;
playlists
.select((id, owner))
.filter(name.eq(playlist_name).and(owner.eq(user.id)))
.get_result(&connection)
.optional()
.map_err(anyhow::Error::new)?
.ok_or(Error::PlaylistNotFound)?
};
// Select songs. Not using Diesel because we need to LEFT JOIN using a custom column
let query = diesel::sql_query(
r#"
SELECT s.id, s.path, s.parent, s.track_number, s.disc_number, s.title, s.artist, s.album_artist, s.year, s.album, s.artwork, s.duration, s.lyricist, s.composer, s.genre, s.label
FROM playlist_songs ps
LEFT JOIN songs s ON ps.path = s.path
WHERE ps.playlist = ?
ORDER BY ps.ordering
"#,
);
let query = query.clone().bind::<sql_types::Integer, _>(playlist.id);
songs = query.get_results(&connection).map_err(anyhow::Error::new)?;
}
// Map real path to virtual paths
let virtual_songs = songs
.into_iter()
.filter_map(|s| s.virtualize(&vfs))
.collect();
Ok(virtual_songs)
}
pub fn delete_playlist(&self, playlist_name: &str, owner: &str) -> Result<(), Error> {
let connection = self.db.connect()?;
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&connection)
.optional()
.map_err(anyhow::Error::new)?
.ok_or(Error::UserNotFound)?
};
{
use self::playlists::dsl::*;
let q = Playlist::belonging_to(&user).filter(name.eq(playlist_name));
match diesel::delete(q)
.execute(&connection)
.map_err(anyhow::Error::new)?
{
0 => Err(Error::PlaylistNotFound),
_ => Ok(()),
}
}
}
}
#[derive(Identifiable, Queryable, Associations)]
#[belongs_to(User, foreign_key = "owner")]
struct Playlist {
id: i32,
owner: i32,
}
#[derive(Identifiable, Queryable, Associations)]
#[belongs_to(Playlist, foreign_key = "playlist")]
struct PlaylistSong {
id: i32,
playlist: i32,
}
#[derive(Insertable)]
#[table_name = "playlists"]
struct NewPlaylist {
name: String,
owner: i32,
}
#[derive(Insertable)]
#[table_name = "playlist_songs"]
struct NewPlaylistSong {
playlist: i32,
path: String,
ordering: i32,
}
#[derive(Identifiable, Queryable)]
struct User {
id: i32,
}