mirror of
https://github.com/agersant/polaris
synced 2024-11-10 02:04:13 +00:00
Moved manager.rs file contents to parent modules
This commit is contained in:
parent
df0de19567
commit
388901cf65
17 changed files with 931 additions and 967 deletions
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(""),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(""),
|
||||
))
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
257
src/app/user.rs
257
src/app/user.rs
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue