Moved manager.rs file contents to parent modules

This commit is contained in:
Antoine Gersant 2022-11-09 00:14:52 -08:00
parent df0de19567
commit 388901cf65
17 changed files with 931 additions and 967 deletions

View file

@ -5,12 +5,10 @@ use std::path;
use crate::app::{ddns, settings, user, vfs};
mod error;
mod manager;
#[cfg(test)]
mod test;
pub use error::*;
pub use manager::*;
#[derive(Default, Deserialize)]
pub struct Config {
@ -29,3 +27,84 @@ impl Config {
Ok(config)
}
}
#[derive(Clone)]
pub struct Manager {
settings_manager: settings::Manager,
user_manager: user::Manager,
vfs_manager: vfs::Manager,
ddns_manager: ddns::Manager,
}
impl Manager {
pub fn new(
settings_manager: settings::Manager,
user_manager: user::Manager,
vfs_manager: vfs::Manager,
ddns_manager: ddns::Manager,
) -> Self {
Self {
settings_manager,
user_manager,
vfs_manager,
ddns_manager,
}
}
pub fn apply(&self, config: &Config) -> Result<(), Error> {
if let Some(new_settings) = &config.settings {
self.settings_manager
.amend(new_settings)
.map_err(|_| Error::Unspecified)?;
}
if let Some(mount_dirs) = &config.mount_dirs {
self.vfs_manager
.set_mount_dirs(mount_dirs)
.map_err(|_| Error::Unspecified)?;
}
if let Some(ddns_config) = &config.ydns {
self.ddns_manager
.set_config(ddns_config)
.map_err(|_| Error::Unspecified)?;
}
if let Some(ref users) = config.users {
let old_users: Vec<user::User> =
self.user_manager.list().map_err(|_| Error::Unspecified)?;
// Delete users that are not in new list
for old_user in old_users
.iter()
.filter(|old_user| !users.iter().any(|u| u.name == old_user.name))
{
self.user_manager
.delete(&old_user.name)
.map_err(|_| Error::Unspecified)?;
}
// Insert new users
for new_user in users
.iter()
.filter(|u| !old_users.iter().any(|old_user| old_user.name == u.name))
{
self.user_manager
.create(new_user)
.map_err(|_| Error::Unspecified)?;
}
// Update users
for user in users {
self.user_manager
.set_password(&user.name, &user.password)
.map_err(|_| Error::Unspecified)?;
self.user_manager
.set_is_admin(&user.name, user.admin)
.map_err(|_| Error::Unspecified)?;
}
}
Ok(())
}
}

View file

@ -1,83 +0,0 @@
use super::*;
use crate::app::{ddns, settings, user, vfs};
#[derive(Clone)]
pub struct Manager {
settings_manager: settings::Manager,
user_manager: user::Manager,
vfs_manager: vfs::Manager,
ddns_manager: ddns::Manager,
}
impl Manager {
pub fn new(
settings_manager: settings::Manager,
user_manager: user::Manager,
vfs_manager: vfs::Manager,
ddns_manager: ddns::Manager,
) -> Self {
Self {
settings_manager,
user_manager,
vfs_manager,
ddns_manager,
}
}
pub fn apply(&self, config: &Config) -> Result<(), Error> {
if let Some(new_settings) = &config.settings {
self.settings_manager
.amend(new_settings)
.map_err(|_| Error::Unspecified)?;
}
if let Some(mount_dirs) = &config.mount_dirs {
self.vfs_manager
.set_mount_dirs(mount_dirs)
.map_err(|_| Error::Unspecified)?;
}
if let Some(ddns_config) = &config.ydns {
self.ddns_manager
.set_config(ddns_config)
.map_err(|_| Error::Unspecified)?;
}
if let Some(ref users) = config.users {
let old_users: Vec<user::User> =
self.user_manager.list().map_err(|_| Error::Unspecified)?;
// Delete users that are not in new list
for old_user in old_users
.iter()
.filter(|old_user| !users.iter().any(|u| u.name == old_user.name))
{
self.user_manager
.delete(&old_user.name)
.map_err(|_| Error::Unspecified)?;
}
// Insert new users
for new_user in users
.iter()
.filter(|u| !old_users.iter().any(|old_user| old_user.name == u.name))
{
self.user_manager
.create(new_user)
.map_err(|_| Error::Unspecified)?;
}
// Update users
for user in users {
self.user_manager
.set_password(&user.name, &user.password)
.map_err(|_| Error::Unspecified)?;
self.user_manager
.set_is_admin(&user.name, user.admin)
.map_err(|_| Error::Unspecified)?;
}
}
Ok(())
}
}

View file

@ -1,5 +1,88 @@
mod config;
mod manager;
use anyhow::bail;
use diesel::prelude::*;
use log::{error, info};
use serde::{Deserialize, Serialize};
use std::thread;
use std::time;
pub use config::Config;
pub use manager::Manager;
use crate::db::{ddns_config, DB};
const DDNS_UPDATE_URL: &str = "https://ydns.io/api/v1/update/";
#[derive(Clone, Debug, Deserialize, Insertable, PartialEq, Eq, Queryable, Serialize)]
#[diesel(table_name = ddns_config)]
pub struct Config {
pub host: String,
pub username: String,
pub password: String,
}
#[derive(Clone)]
pub struct Manager {
db: DB,
}
impl Manager {
pub fn new(db: DB) -> Self {
Self { db }
}
fn update_my_ip(&self) -> anyhow::Result<()> {
let config = self.config()?;
if config.host.is_empty() || config.username.is_empty() {
info!("Skipping DDNS update because credentials are missing");
return Ok(());
}
let full_url = format!("{}?host={}", DDNS_UPDATE_URL, &config.host);
let response = ureq::get(full_url.as_str())
.auth(&config.username, &config.password)
.call();
if !response.ok() {
bail!(
"DDNS update query failed with status code: {}",
response.status()
);
}
Ok(())
}
pub fn config(&self) -> anyhow::Result<Config> {
use crate::db::ddns_config::dsl::*;
let mut connection = self.db.connect()?;
Ok(ddns_config
.select((host, username, password))
.get_result(&mut connection)?)
}
pub fn set_config(&self, new_config: &Config) -> anyhow::Result<()> {
use crate::db::ddns_config::dsl::*;
let mut connection = self.db.connect()?;
diesel::update(ddns_config)
.set((
host.eq(&new_config.host),
username.eq(&new_config.username),
password.eq(&new_config.password),
))
.execute(&mut connection)?;
Ok(())
}
pub fn begin_periodic_updates(&self) {
let cloned = self.clone();
std::thread::spawn(move || {
cloned.run();
});
}
fn run(&self) {
loop {
if let Err(e) = self.update_my_ip() {
error!("Dynamic DNS update error: {:?}", e);
}
thread::sleep(time::Duration::from_secs(60 * 30));
}
}
}

View file

@ -1,11 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::db::ddns_config;
#[derive(Clone, Debug, Deserialize, Insertable, PartialEq, Eq, Queryable, Serialize)]
#[diesel(table_name = ddns_config)]
pub struct Config {
pub host: String,
pub username: String,
pub password: String,
}

View file

@ -1,80 +0,0 @@
use anyhow::*;
use diesel::prelude::*;
use log::{error, info};
use std::thread;
use std::time;
use super::*;
use crate::db::DB;
const DDNS_UPDATE_URL: &str = "https://ydns.io/api/v1/update/";
#[derive(Clone)]
pub struct Manager {
db: DB,
}
impl Manager {
pub fn new(db: DB) -> Self {
Self { db }
}
fn update_my_ip(&self) -> Result<()> {
let config = self.config()?;
if config.host.is_empty() || config.username.is_empty() {
info!("Skipping DDNS update because credentials are missing");
return Ok(());
}
let full_url = format!("{}?host={}", DDNS_UPDATE_URL, &config.host);
let response = ureq::get(full_url.as_str())
.auth(&config.username, &config.password)
.call();
if !response.ok() {
bail!(
"DDNS update query failed with status code: {}",
response.status()
);
}
Ok(())
}
pub fn config(&self) -> Result<Config> {
use crate::db::ddns_config::dsl::*;
let mut connection = self.db.connect()?;
Ok(ddns_config
.select((host, username, password))
.get_result(&mut connection)?)
}
pub fn set_config(&self, new_config: &Config) -> Result<()> {
use crate::db::ddns_config::dsl::*;
let mut connection = self.db.connect()?;
diesel::update(ddns_config)
.set((
host.eq(&new_config.host),
username.eq(&new_config.username),
password.eq(&new_config.password),
))
.execute(&mut connection)?;
Ok(())
}
pub fn begin_periodic_updates(&self) {
let cloned = self.clone();
std::thread::spawn(move || {
cloned.run();
});
}
fn run(&self) {
loop {
if let Err(e) = self.update_my_ip() {
error!("Dynamic DNS update error: {:?}", e);
}
thread::sleep(time::Duration::from_secs(60 * 30));
}
}
}

View file

@ -1,3 +1,70 @@
mod manager;
use anyhow::*;
use rustfm_scrobble::{Scrobble, Scrobbler};
use std::path::Path;
use user::AuthToken;
pub use manager::*;
use crate::app::{index::Index, user};
const LASTFM_API_KEY: &str = "02b96c939a2b451c31dfd67add1f696e";
const LASTFM_API_SECRET: &str = "0f25a80ceef4b470b5cb97d99d4b3420";
#[derive(Clone)]
pub struct Manager {
index: Index,
user_manager: user::Manager,
}
impl Manager {
pub fn new(index: Index, user_manager: user::Manager) -> Self {
Self {
index,
user_manager,
}
}
pub fn generate_link_token(&self, username: &str) -> Result<AuthToken> {
self.user_manager
.generate_lastfm_link_token(username)
.map_err(|e| e.into())
}
pub fn link(&self, username: &str, lastfm_token: &str) -> Result<()> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let auth_response = scrobbler.authenticate_with_token(lastfm_token)?;
self.user_manager
.lastfm_link(username, &auth_response.name, &auth_response.key)
.map_err(|e| e.into())
}
pub fn unlink(&self, username: &str) -> Result<()> {
self.user_manager.lastfm_unlink(username)
}
pub fn scrobble(&self, username: &str, track: &Path) -> Result<()> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let scrobble = self.scrobble_from_path(track)?;
let auth_token = self.user_manager.get_lastfm_session_key(username)?;
scrobbler.authenticate_with_session_key(&auth_token);
scrobbler.scrobble(&scrobble)?;
Ok(())
}
pub fn now_playing(&self, username: &str, track: &Path) -> Result<()> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let scrobble = self.scrobble_from_path(track)?;
let auth_token = self.user_manager.get_lastfm_session_key(username)?;
scrobbler.authenticate_with_session_key(&auth_token);
scrobbler.now_playing(&scrobble)?;
Ok(())
}
fn scrobble_from_path(&self, track: &Path) -> Result<Scrobble> {
let song = self.index.get_song(track)?;
Ok(Scrobble::new(
song.artist.as_deref().unwrap_or(""),
song.title.as_deref().unwrap_or(""),
song.album.as_deref().unwrap_or(""),
))
}
}

View file

@ -1,70 +0,0 @@
use anyhow::*;
use rustfm_scrobble::{Scrobble, Scrobbler};
use std::path::Path;
use user::AuthToken;
use crate::app::{index::Index, user};
const LASTFM_API_KEY: &str = "02b96c939a2b451c31dfd67add1f696e";
const LASTFM_API_SECRET: &str = "0f25a80ceef4b470b5cb97d99d4b3420";
#[derive(Clone)]
pub struct Manager {
index: Index,
user_manager: user::Manager,
}
impl Manager {
pub fn new(index: Index, user_manager: user::Manager) -> Self {
Self {
index,
user_manager,
}
}
pub fn generate_link_token(&self, username: &str) -> Result<AuthToken> {
self.user_manager
.generate_lastfm_link_token(username)
.map_err(|e| e.into())
}
pub fn link(&self, username: &str, lastfm_token: &str) -> Result<()> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let auth_response = scrobbler.authenticate_with_token(lastfm_token)?;
self.user_manager
.lastfm_link(username, &auth_response.name, &auth_response.key)
.map_err(|e| e.into())
}
pub fn unlink(&self, username: &str) -> Result<()> {
self.user_manager.lastfm_unlink(username)
}
pub fn scrobble(&self, username: &str, track: &Path) -> Result<()> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let scrobble = self.scrobble_from_path(track)?;
let auth_token = self.user_manager.get_lastfm_session_key(username)?;
scrobbler.authenticate_with_session_key(&auth_token);
scrobbler.scrobble(&scrobble)?;
Ok(())
}
pub fn now_playing(&self, username: &str, track: &Path) -> Result<()> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let scrobble = self.scrobble_from_path(track)?;
let auth_token = self.user_manager.get_lastfm_session_key(username)?;
scrobbler.authenticate_with_session_key(&auth_token);
scrobbler.now_playing(&scrobble)?;
Ok(())
}
fn scrobble_from_path(&self, track: &Path) -> Result<Scrobble> {
let song = self.index.get_song(track)?;
Ok(Scrobble::new(
song.artist.as_deref().unwrap_or(""),
song.title.as_deref().unwrap_or(""),
song.album.as_deref().unwrap_or(""),
))
}
}

View file

@ -1,7 +1,253 @@
use anyhow::Result;
use core::clone::Clone;
use diesel::prelude::*;
use diesel::sql_types;
use diesel::BelongingToDsl;
use std::path::Path;
use crate::app::index::Song;
use crate::app::vfs;
use crate::db::{playlist_songs, playlists, users, DB};
mod error;
mod manager;
#[cfg(test)]
mod test;
pub use error::*;
pub use manager::*;
#[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 mut connection = self.db.connect()?;
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&mut 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(&mut 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 mut connection = self.db.connect()?;
// Find owner
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&mut 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(&mut 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(&mut 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 mut connection = self.db.connect()?;
connection
.transaction::<_, diesel::result::Error, _>(|connection| {
// 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(&mut *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 mut connection = self.db.connect()?;
// Find owner
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&mut 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(&mut 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.bind::<sql_types::Integer, _>(playlist.id);
songs = query
.get_results(&mut 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 mut connection = self.db.connect()?;
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&mut 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(&mut connection)
.map_err(anyhow::Error::new)?
{
0 => Err(Error::PlaylistNotFound),
_ => Ok(()),
}
}
}
}
#[derive(Identifiable, Queryable, Associations)]
#[diesel(belongs_to(User, foreign_key = owner))]
struct Playlist {
id: i32,
owner: i32,
}
#[derive(Identifiable, Queryable, Associations)]
#[diesel(belongs_to(Playlist, foreign_key = playlist))]
struct PlaylistSong {
id: i32,
playlist: i32,
}
#[derive(Insertable)]
#[diesel(table_name = playlists)]
struct NewPlaylist {
name: String,
owner: i32,
}
#[derive(Insertable)]
#[diesel(table_name = playlist_songs)]
struct NewPlaylistSong {
playlist: i32,
path: String,
ordering: i32,
}
#[derive(Identifiable, Queryable)]
struct User {
id: i32,
}

View file

@ -1,248 +0,0 @@
use anyhow::Result;
use core::clone::Clone;
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 mut connection = self.db.connect()?;
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&mut 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(&mut 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 mut connection = self.db.connect()?;
// Find owner
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&mut 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(&mut 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(&mut 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 mut connection = self.db.connect()?;
connection
.transaction::<_, diesel::result::Error, _>(|connection| {
// 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(&mut *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 mut connection = self.db.connect()?;
// Find owner
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&mut 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(&mut 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.bind::<sql_types::Integer, _>(playlist.id);
songs = query
.get_results(&mut 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 mut connection = self.db.connect()?;
let user: User = {
use self::users::dsl::*;
users
.filter(name.eq(owner))
.select((id,))
.first(&mut 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(&mut connection)
.map_err(anyhow::Error::new)?
{
0 => Err(Error::PlaylistNotFound),
_ => Ok(()),
}
}
}
}
#[derive(Identifiable, Queryable, Associations)]
#[diesel(belongs_to(User, foreign_key = owner))]
struct Playlist {
id: i32,
owner: i32,
}
#[derive(Identifiable, Queryable, Associations)]
#[diesel(belongs_to(Playlist, foreign_key = playlist))]
struct PlaylistSong {
id: i32,
playlist: i32,
}
#[derive(Insertable)]
#[diesel(table_name = playlists)]
struct NewPlaylist {
name: String,
owner: i32,
}
#[derive(Insertable)]
#[diesel(table_name = playlist_songs)]
struct NewPlaylistSong {
playlist: i32,
path: String,
ordering: i32,
}
#[derive(Identifiable, Queryable)]
struct User {
id: i32,
}

View file

@ -1,10 +1,14 @@
use diesel::prelude::*;
use regex::Regex;
use serde::Deserialize;
use std::convert::TryInto;
use std::time::Duration;
use crate::db::{misc_settings, DB};
mod error;
mod manager;
pub use error::*;
pub use manager::*;
#[derive(Clone, Default)]
pub struct AuthSecret {
@ -22,3 +26,90 @@ pub struct NewSettings {
pub reindex_every_n_seconds: Option<i32>,
pub album_art_pattern: Option<String>,
}
#[derive(Clone)]
pub struct Manager {
pub db: DB,
}
impl Manager {
pub fn new(db: DB) -> Self {
Self { db }
}
pub fn get_auth_secret(&self) -> Result<AuthSecret, Error> {
use self::misc_settings::dsl::*;
let mut connection = self.db.connect()?;
let secret: Vec<u8> = misc_settings
.select(auth_secret)
.get_result(&mut connection)
.map_err(|e| match e {
diesel::result::Error::NotFound => Error::AuthSecretNotFound,
_ => Error::Unspecified,
})?;
secret
.try_into()
.map_err(|_| Error::InvalidAuthSecret)
.map(|key| AuthSecret { key })
}
pub fn get_index_sleep_duration(&self) -> Result<Duration, Error> {
use self::misc_settings::dsl::*;
let mut connection = self.db.connect()?;
misc_settings
.select(index_sleep_duration_seconds)
.get_result(&mut connection)
.map_err(|e| match e {
diesel::result::Error::NotFound => Error::IndexSleepDurationNotFound,
_ => Error::Unspecified,
})
.map(|s: i32| Duration::from_secs(s as u64))
}
pub fn get_index_album_art_pattern(&self) -> Result<Regex, Error> {
use self::misc_settings::dsl::*;
let mut connection = self.db.connect()?;
misc_settings
.select(index_album_art_pattern)
.get_result(&mut connection)
.map_err(|e| match e {
diesel::result::Error::NotFound => Error::IndexAlbumArtPatternNotFound,
_ => Error::Unspecified,
})
.and_then(|s: String| {
Regex::new(&format!("(?i){}", &s)).map_err(|_| Error::IndexAlbumArtPatternInvalid)
})
}
pub fn read(&self) -> Result<Settings, Error> {
use self::misc_settings::dsl::*;
let mut connection = self.db.connect()?;
let settings: Settings = misc_settings
.select((index_sleep_duration_seconds, index_album_art_pattern))
.get_result(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(settings)
}
pub fn amend(&self, new_settings: &NewSettings) -> Result<(), Error> {
let mut connection = self.db.connect()?;
if let Some(sleep_duration) = new_settings.reindex_every_n_seconds {
diesel::update(misc_settings::table)
.set(misc_settings::index_sleep_duration_seconds.eq(sleep_duration as i32))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
}
if let Some(ref album_art_pattern) = new_settings.album_art_pattern {
diesel::update(misc_settings::table)
.set(misc_settings::index_album_art_pattern.eq(album_art_pattern))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
}
Ok(())
}
}

View file

@ -1,94 +0,0 @@
use diesel::prelude::*;
use regex::Regex;
use std::convert::TryInto;
use std::time::Duration;
use super::*;
use crate::db::{misc_settings, DB};
#[derive(Clone)]
pub struct Manager {
pub db: DB,
}
impl Manager {
pub fn new(db: DB) -> Self {
Self { db }
}
pub fn get_auth_secret(&self) -> Result<AuthSecret, Error> {
use self::misc_settings::dsl::*;
let mut connection = self.db.connect()?;
let secret: Vec<u8> = misc_settings
.select(auth_secret)
.get_result(&mut connection)
.map_err(|e| match e {
diesel::result::Error::NotFound => Error::AuthSecretNotFound,
_ => Error::Unspecified,
})?;
secret
.try_into()
.map_err(|_| Error::InvalidAuthSecret)
.map(|key| AuthSecret { key })
}
pub fn get_index_sleep_duration(&self) -> Result<Duration, Error> {
use self::misc_settings::dsl::*;
let mut connection = self.db.connect()?;
misc_settings
.select(index_sleep_duration_seconds)
.get_result(&mut connection)
.map_err(|e| match e {
diesel::result::Error::NotFound => Error::IndexSleepDurationNotFound,
_ => Error::Unspecified,
})
.map(|s: i32| Duration::from_secs(s as u64))
}
pub fn get_index_album_art_pattern(&self) -> Result<Regex, Error> {
use self::misc_settings::dsl::*;
let mut connection = self.db.connect()?;
misc_settings
.select(index_album_art_pattern)
.get_result(&mut connection)
.map_err(|e| match e {
diesel::result::Error::NotFound => Error::IndexAlbumArtPatternNotFound,
_ => Error::Unspecified,
})
.and_then(|s: String| {
Regex::new(&format!("(?i){}", &s)).map_err(|_| Error::IndexAlbumArtPatternInvalid)
})
}
pub fn read(&self) -> Result<Settings, Error> {
use self::misc_settings::dsl::*;
let mut connection = self.db.connect()?;
let settings: Settings = misc_settings
.select((index_sleep_duration_seconds, index_album_art_pattern))
.get_result(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(settings)
}
pub fn amend(&self, new_settings: &NewSettings) -> Result<(), Error> {
let mut connection = self.db.connect()?;
if let Some(sleep_duration) = new_settings.reindex_every_n_seconds {
diesel::update(misc_settings::table)
.set(misc_settings::index_sleep_duration_seconds.eq(sleep_duration as i32))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
}
if let Some(ref album_art_pattern) = new_settings.album_art_pattern {
diesel::update(misc_settings::table)
.set(misc_settings::index_album_art_pattern.eq(album_art_pattern))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
}
Ok(())
}
}

View file

@ -1,9 +1,68 @@
use anyhow::*;
use image::ImageOutputFormat;
use std::collections::hash_map::DefaultHasher;
use std::fs::{self, File};
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
mod generate;
mod manager;
mod options;
mod read;
pub use generate::*;
pub use manager::*;
pub use options::*;
pub use read::*;
#[derive(Clone)]
pub struct Manager {
thumbnails_dir_path: PathBuf,
}
impl Manager {
pub fn new(thumbnails_dir_path: PathBuf) -> Self {
Self {
thumbnails_dir_path,
}
}
pub fn get_thumbnail(&self, image_path: &Path, thumbnailoptions: &Options) -> Result<PathBuf> {
match self.retrieve_thumbnail(image_path, thumbnailoptions) {
Some(path) => Ok(path),
None => self.create_thumbnail(image_path, thumbnailoptions),
}
}
fn get_thumbnail_path(&self, image_path: &Path, thumbnailoptions: &Options) -> PathBuf {
let hash = Manager::hash(image_path, thumbnailoptions);
let mut thumbnail_path = self.thumbnails_dir_path.clone();
thumbnail_path.push(format!("{}.jpg", hash));
thumbnail_path
}
fn retrieve_thumbnail(&self, image_path: &Path, thumbnailoptions: &Options) -> Option<PathBuf> {
let path = self.get_thumbnail_path(image_path, thumbnailoptions);
if path.exists() {
Some(path)
} else {
None
}
}
fn create_thumbnail(&self, image_path: &Path, thumbnailoptions: &Options) -> Result<PathBuf> {
let thumbnail = generate_thumbnail(image_path, thumbnailoptions)?;
let quality = 80;
fs::create_dir_all(&self.thumbnails_dir_path)?;
let path = self.get_thumbnail_path(image_path, thumbnailoptions);
let mut out_file = File::create(&path)?;
thumbnail.write_to(&mut out_file, ImageOutputFormat::Jpeg(quality))?;
Ok(path)
}
fn hash(path: &Path, thumbnailoptions: &Options) -> u64 {
let mut hasher = DefaultHasher::new();
path.hash(&mut hasher);
thumbnailoptions.hash(&mut hasher);
hasher.finish()
}
}

View file

@ -1,62 +0,0 @@
use anyhow::*;
use image::ImageOutputFormat;
use std::collections::hash_map::DefaultHasher;
use std::fs::{self, File};
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use crate::app::thumbnail::*;
#[derive(Clone)]
pub struct Manager {
thumbnails_dir_path: PathBuf,
}
impl Manager {
pub fn new(thumbnails_dir_path: PathBuf) -> Self {
Self {
thumbnails_dir_path,
}
}
pub fn get_thumbnail(&self, image_path: &Path, thumbnailoptions: &Options) -> Result<PathBuf> {
match self.retrieve_thumbnail(image_path, thumbnailoptions) {
Some(path) => Ok(path),
None => self.create_thumbnail(image_path, thumbnailoptions),
}
}
fn get_thumbnail_path(&self, image_path: &Path, thumbnailoptions: &Options) -> PathBuf {
let hash = Manager::hash(image_path, thumbnailoptions);
let mut thumbnail_path = self.thumbnails_dir_path.clone();
thumbnail_path.push(format!("{}.jpg", hash));
thumbnail_path
}
fn retrieve_thumbnail(&self, image_path: &Path, thumbnailoptions: &Options) -> Option<PathBuf> {
let path = self.get_thumbnail_path(image_path, thumbnailoptions);
if path.exists() {
Some(path)
} else {
None
}
}
fn create_thumbnail(&self, image_path: &Path, thumbnailoptions: &Options) -> Result<PathBuf> {
let thumbnail = generate_thumbnail(image_path, thumbnailoptions)?;
let quality = 80;
fs::create_dir_all(&self.thumbnails_dir_path)?;
let path = self.get_thumbnail_path(image_path, thumbnailoptions);
let mut out_file = File::create(&path)?;
thumbnail.write_to(&mut out_file, ImageOutputFormat::Jpeg(quality))?;
Ok(path)
}
fn hash(path: &Path, thumbnailoptions: &Options) -> u64 {
let mut hasher = DefaultHasher::new();
path.hash(&mut hasher);
thumbnailoptions.hash(&mut hasher);
hasher.finish()
}
}

View file

@ -1,15 +1,20 @@
use anyhow::anyhow;
use diesel::prelude::*;
use pbkdf2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use pbkdf2::Pbkdf2;
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::db::users;
use crate::app::settings::AuthSecret;
use crate::db::{users, DB};
mod error;
mod manager;
mod preferences;
#[cfg(test)]
mod test;
pub use error::*;
pub use manager::*;
pub use preferences::*;
#[derive(Debug, Insertable, Queryable)]
@ -47,3 +52,249 @@ pub struct Authorization {
pub username: String,
pub scope: AuthorizationScope,
}
#[derive(Clone)]
pub struct Manager {
// TODO make this private and move preferences methods in this file
pub db: DB,
auth_secret: AuthSecret,
}
impl Manager {
pub fn new(db: DB, auth_secret: AuthSecret) -> Self {
Self { db, auth_secret }
}
pub fn create(&self, new_user: &NewUser) -> Result<(), Error> {
if new_user.name.is_empty() {
return Err(Error::EmptyUsername);
}
let password_hash = hash_password(&new_user.password)?;
let mut connection = self.db.connect()?;
let new_user = User {
name: new_user.name.to_owned(),
password_hash,
admin: new_user.admin as i32,
};
diesel::insert_into(users::table)
.values(&new_user)
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn delete(&self, username: &str) -> Result<(), Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
diesel::delete(users.filter(name.eq(username)))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn set_password(&self, username: &str, password: &str) -> Result<(), Error> {
let hash = hash_password(password)?;
let mut connection = self.db.connect()?;
use crate::db::users::dsl::*;
diesel::update(users.filter(name.eq(username)))
.set(password_hash.eq(hash))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn set_is_admin(&self, username: &str, is_admin: bool) -> Result<(), Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
diesel::update(users.filter(name.eq(username)))
.set(admin.eq(is_admin as i32))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn login(&self, username: &str, password: &str) -> Result<AuthToken, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
match users
.select(password_hash)
.filter(name.eq(username))
.get_result(&mut connection)
{
Err(diesel::result::Error::NotFound) => Err(Error::IncorrectUsername),
Ok(hash) => {
let hash: String = hash;
if verify_password(&hash, password) {
let authorization = Authorization {
username: username.to_owned(),
scope: AuthorizationScope::PolarisAuth,
};
self.generate_auth_token(&authorization)
} else {
Err(Error::IncorrectPassword)
}
}
Err(_) => Err(Error::Unspecified),
}
}
pub fn authenticate(
&self,
auth_token: &AuthToken,
scope: AuthorizationScope,
) -> Result<Authorization, Error> {
let authorization = self.decode_auth_token(auth_token, scope)?;
if self.exists(&authorization.username)? {
Ok(authorization)
} else {
Err(Error::IncorrectUsername)
}
}
fn decode_auth_token(
&self,
auth_token: &AuthToken,
scope: AuthorizationScope,
) -> Result<Authorization, Error> {
let AuthToken(data) = auth_token;
let ttl = match scope {
AuthorizationScope::PolarisAuth => 0, // permanent
AuthorizationScope::LastFMLink => 10 * 60, // 10 minutes
};
let authorization = branca::decode(data, &self.auth_secret.key, ttl)
.map_err(|_| Error::InvalidAuthToken)?;
let authorization: Authorization =
serde_json::from_slice(&authorization[..]).map_err(|_| Error::InvalidAuthToken)?;
if authorization.scope != scope {
return Err(Error::IncorrectAuthorizationScope);
}
Ok(authorization)
}
fn generate_auth_token(&self, authorization: &Authorization) -> Result<AuthToken, Error> {
let serialized_authorization =
serde_json::to_string(&authorization).map_err(|_| Error::Unspecified)?;
branca::encode(
serialized_authorization.as_bytes(),
&self.auth_secret.key,
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| Error::Unspecified)?
.as_secs() as u32,
)
.map_err(|_| Error::Unspecified)
.map(AuthToken)
}
pub fn count(&self) -> anyhow::Result<i64> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let count = users.count().get_result(&mut connection)?;
Ok(count)
}
pub fn list(&self) -> Result<Vec<User>, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
users
.select((name, password_hash, admin))
.get_results(&mut connection)
.map_err(|_| Error::Unspecified)
}
pub fn exists(&self, username: &str) -> Result<bool, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let results: Vec<String> = users
.select(name)
.filter(name.eq(username))
.get_results(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(!results.is_empty())
}
pub fn is_admin(&self, username: &str) -> Result<bool, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let is_admin: i32 = users
.filter(name.eq(username))
.select(admin)
.get_result(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(is_admin != 0)
}
pub fn lastfm_link(
&self,
username: &str,
lastfm_login: &str,
session_key: &str,
) -> Result<(), Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
diesel::update(users.filter(name.eq(username)))
.set((
lastfm_username.eq(lastfm_login),
lastfm_session_key.eq(session_key),
))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn generate_lastfm_link_token(&self, username: &str) -> Result<AuthToken, Error> {
self.generate_auth_token(&Authorization {
username: username.to_owned(),
scope: AuthorizationScope::LastFMLink,
})
}
pub fn get_lastfm_session_key(&self, username: &str) -> anyhow::Result<String> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let token = users
.filter(name.eq(username))
.select(lastfm_session_key)
.get_result(&mut connection)?;
match token {
Some(t) => Ok(t),
_ => Err(anyhow!("Missing LastFM credentials")),
}
}
pub fn is_lastfm_linked(&self, username: &str) -> bool {
self.get_lastfm_session_key(username).is_ok()
}
pub fn lastfm_unlink(&self, username: &str) -> anyhow::Result<()> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let null: Option<String> = None;
diesel::update(users.filter(name.eq(username)))
.set((lastfm_session_key.eq(&null), lastfm_username.eq(&null)))
.execute(&mut connection)?;
Ok(())
}
}
fn hash_password(password: &str) -> Result<String, Error> {
if password.is_empty() {
return Err(Error::EmptyPassword);
}
let salt = SaltString::generate(&mut OsRng);
match Pbkdf2.hash_password(password.as_bytes(), &salt) {
Ok(h) => Ok(h.to_string()),
Err(_) => Err(Error::Unspecified),
}
}
fn verify_password(password_hash: &str, attempted_password: &str) -> bool {
match PasswordHash::new(password_hash) {
Ok(h) => Pbkdf2
.verify_password(attempted_password.as_bytes(), &h)
.is_ok(),
Err(_) => false,
}
}

View file

@ -1,256 +0,0 @@
use anyhow::anyhow;
use diesel::prelude::*;
use pbkdf2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use pbkdf2::Pbkdf2;
use rand::rngs::OsRng;
use std::time::{SystemTime, UNIX_EPOCH};
use super::*;
use crate::app::settings::AuthSecret;
use crate::db::DB;
#[derive(Clone)]
pub struct Manager {
// TODO make this private and move preferences methods in this file
pub db: DB,
auth_secret: AuthSecret,
}
impl Manager {
pub fn new(db: DB, auth_secret: AuthSecret) -> Self {
Self { db, auth_secret }
}
pub fn create(&self, new_user: &NewUser) -> Result<(), Error> {
if new_user.name.is_empty() {
return Err(Error::EmptyUsername);
}
let password_hash = hash_password(&new_user.password)?;
let mut connection = self.db.connect()?;
let new_user = User {
name: new_user.name.to_owned(),
password_hash,
admin: new_user.admin as i32,
};
diesel::insert_into(users::table)
.values(&new_user)
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn delete(&self, username: &str) -> Result<(), Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
diesel::delete(users.filter(name.eq(username)))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn set_password(&self, username: &str, password: &str) -> Result<(), Error> {
let hash = hash_password(password)?;
let mut connection = self.db.connect()?;
use crate::db::users::dsl::*;
diesel::update(users.filter(name.eq(username)))
.set(password_hash.eq(hash))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn set_is_admin(&self, username: &str, is_admin: bool) -> Result<(), Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
diesel::update(users.filter(name.eq(username)))
.set(admin.eq(is_admin as i32))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn login(&self, username: &str, password: &str) -> Result<AuthToken, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
match users
.select(password_hash)
.filter(name.eq(username))
.get_result(&mut connection)
{
Err(diesel::result::Error::NotFound) => Err(Error::IncorrectUsername),
Ok(hash) => {
let hash: String = hash;
if verify_password(&hash, password) {
let authorization = Authorization {
username: username.to_owned(),
scope: AuthorizationScope::PolarisAuth,
};
self.generate_auth_token(&authorization)
} else {
Err(Error::IncorrectPassword)
}
}
Err(_) => Err(Error::Unspecified),
}
}
pub fn authenticate(
&self,
auth_token: &AuthToken,
scope: AuthorizationScope,
) -> Result<Authorization, Error> {
let authorization = self.decode_auth_token(auth_token, scope)?;
if self.exists(&authorization.username)? {
Ok(authorization)
} else {
Err(Error::IncorrectUsername)
}
}
fn decode_auth_token(
&self,
auth_token: &AuthToken,
scope: AuthorizationScope,
) -> Result<Authorization, Error> {
let AuthToken(data) = auth_token;
let ttl = match scope {
AuthorizationScope::PolarisAuth => 0, // permanent
AuthorizationScope::LastFMLink => 10 * 60, // 10 minutes
};
let authorization = branca::decode(data, &self.auth_secret.key, ttl)
.map_err(|_| Error::InvalidAuthToken)?;
let authorization: Authorization =
serde_json::from_slice(&authorization[..]).map_err(|_| Error::InvalidAuthToken)?;
if authorization.scope != scope {
return Err(Error::IncorrectAuthorizationScope);
}
Ok(authorization)
}
fn generate_auth_token(&self, authorization: &Authorization) -> Result<AuthToken, Error> {
let serialized_authorization =
serde_json::to_string(&authorization).map_err(|_| Error::Unspecified)?;
branca::encode(
serialized_authorization.as_bytes(),
&self.auth_secret.key,
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| Error::Unspecified)?
.as_secs() as u32,
)
.map_err(|_| Error::Unspecified)
.map(AuthToken)
}
pub fn count(&self) -> anyhow::Result<i64> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let count = users.count().get_result(&mut connection)?;
Ok(count)
}
pub fn list(&self) -> Result<Vec<User>, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
users
.select((name, password_hash, admin))
.get_results(&mut connection)
.map_err(|_| Error::Unspecified)
}
pub fn exists(&self, username: &str) -> Result<bool, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let results: Vec<String> = users
.select(name)
.filter(name.eq(username))
.get_results(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(!results.is_empty())
}
pub fn is_admin(&self, username: &str) -> Result<bool, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let is_admin: i32 = users
.filter(name.eq(username))
.select(admin)
.get_result(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(is_admin != 0)
}
pub fn lastfm_link(
&self,
username: &str,
lastfm_login: &str,
session_key: &str,
) -> Result<(), Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
diesel::update(users.filter(name.eq(username)))
.set((
lastfm_username.eq(lastfm_login),
lastfm_session_key.eq(session_key),
))
.execute(&mut connection)
.map_err(|_| Error::Unspecified)?;
Ok(())
}
pub fn generate_lastfm_link_token(&self, username: &str) -> Result<AuthToken, Error> {
self.generate_auth_token(&Authorization {
username: username.to_owned(),
scope: AuthorizationScope::LastFMLink,
})
}
pub fn get_lastfm_session_key(&self, username: &str) -> anyhow::Result<String> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let token = users
.filter(name.eq(username))
.select(lastfm_session_key)
.get_result(&mut connection)?;
match token {
Some(t) => Ok(t),
_ => Err(anyhow!("Missing LastFM credentials")),
}
}
pub fn is_lastfm_linked(&self, username: &str) -> bool {
self.get_lastfm_session_key(username).is_ok()
}
pub fn lastfm_unlink(&self, username: &str) -> anyhow::Result<()> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let null: Option<String> = None;
diesel::update(users.filter(name.eq(username)))
.set((lastfm_session_key.eq(&null), lastfm_username.eq(&null)))
.execute(&mut connection)?;
Ok(())
}
}
fn hash_password(password: &str) -> Result<String, Error> {
if password.is_empty() {
return Err(Error::EmptyPassword);
}
let salt = SaltString::generate(&mut OsRng);
match Pbkdf2.hash_password(password.as_bytes(), &salt) {
Ok(h) => Ok(h.to_string()),
Err(_) => Err(Error::Unspecified),
}
}
fn verify_password(password_hash: &str, attempted_password: &str) -> bool {
match PasswordHash::new(password_hash) {
Ok(h) => Pbkdf2
.verify_password(attempted_password.as_bytes(), &h)
.is_ok(),
Err(_) => false,
}
}

View file

@ -1,17 +1,15 @@
use anyhow::{bail, Result};
use core::ops::Deref;
use diesel::prelude::*;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::path::{self, Path, PathBuf};
use crate::db::mount_points;
use crate::db::{mount_points, DB};
mod manager;
#[cfg(test)]
mod test;
pub use manager::*;
#[derive(Clone, Debug, Deserialize, Insertable, PartialEq, Eq, Queryable, Serialize)]
#[diesel(table_name = mount_points)]
pub struct MountDir {
@ -81,3 +79,39 @@ impl VFS {
&self.mounts
}
}
#[derive(Clone)]
pub struct Manager {
db: DB,
}
impl Manager {
pub fn new(db: DB) -> Self {
Self { db }
}
pub fn get_vfs(&self) -> Result<VFS> {
let mount_dirs = self.mount_dirs()?;
let mounts = mount_dirs.into_iter().map(|p| p.into()).collect();
Ok(VFS::new(mounts))
}
pub fn mount_dirs(&self) -> Result<Vec<MountDir>> {
use self::mount_points::dsl::*;
let mut connection = self.db.connect()?;
let mount_dirs: Vec<MountDir> = mount_points
.select((source, name))
.get_results(&mut connection)?;
Ok(mount_dirs)
}
pub fn set_mount_dirs(&self, mount_dirs: &[MountDir]) -> Result<()> {
use self::mount_points::dsl::*;
let mut connection = self.db.connect()?;
diesel::delete(mount_points).execute(&mut connection)?;
diesel::insert_into(mount_points)
.values(mount_dirs)
.execute(&mut *connection)?; // TODO https://github.com/diesel-rs/diesel/issues/1822
Ok(())
}
}

View file

@ -1,42 +0,0 @@
use anyhow::*;
use diesel::prelude::*;
use super::*;
use crate::db::mount_points;
use crate::db::DB;
#[derive(Clone)]
pub struct Manager {
db: DB,
}
impl Manager {
pub fn new(db: DB) -> Self {
Self { db }
}
pub fn get_vfs(&self) -> Result<VFS> {
let mount_dirs = self.mount_dirs()?;
let mounts = mount_dirs.into_iter().map(|p| p.into()).collect();
Ok(VFS::new(mounts))
}
pub fn mount_dirs(&self) -> Result<Vec<MountDir>> {
use self::mount_points::dsl::*;
let mut connection = self.db.connect()?;
let mount_dirs: Vec<MountDir> = mount_points
.select((source, name))
.get_results(&mut connection)?;
Ok(mount_dirs)
}
pub fn set_mount_dirs(&self, mount_dirs: &[MountDir]) -> Result<()> {
use self::mount_points::dsl::*;
let mut connection = self.db.connect()?;
diesel::delete(mount_points).execute(&mut connection)?;
diesel::insert_into(mount_points)
.values(mount_dirs)
.execute(&mut *connection)?; // TODO https://github.com/diesel-rs/diesel/issues/1822
Ok(())
}
}