Rustfmt
This commit is contained in:
parent
42140d3137
commit
0297b351bf
17 changed files with 592 additions and 530 deletions
188
src/api.rs
188
src/api.rs
|
@ -1,39 +1,38 @@
|
|||
use diesel::prelude::*;
|
||||
use iron::prelude::*;
|
||||
use iron::headers::{Authorization, Basic, Range};
|
||||
use iron::{AroundMiddleware, Handler, status};
|
||||
use iron::prelude::*;
|
||||
use iron::{status, AroundMiddleware, Handler};
|
||||
use mount::Mount;
|
||||
use router::Router;
|
||||
use params;
|
||||
use secure_session::middleware::{SessionMiddleware, SessionConfig};
|
||||
use secure_session::session::{SessionManager, ChaCha20Poly1305SessionManager};
|
||||
use router::Router;
|
||||
use secure_session::middleware::{SessionConfig, SessionMiddleware};
|
||||
use secure_session::session::{ChaCha20Poly1305SessionManager, SessionManager};
|
||||
use serde_json;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::*;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::path::*;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use typemap;
|
||||
use url::percent_encoding::percent_decode;
|
||||
|
||||
use config;
|
||||
use db::{ConnectionSource, DB};
|
||||
use db::misc_settings;
|
||||
use db::{ConnectionSource, DB};
|
||||
use errors::*;
|
||||
use index;
|
||||
use lastfm;
|
||||
use playlist;
|
||||
use user;
|
||||
use serve;
|
||||
use thumbnails::*;
|
||||
use user;
|
||||
use utils::*;
|
||||
use vfs::VFSSource;
|
||||
|
||||
const CURRENT_MAJOR_VERSION: i32 = 2;
|
||||
const CURRENT_MINOR_VERSION: i32 = 2;
|
||||
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Session {
|
||||
username: String,
|
||||
|
@ -46,7 +45,8 @@ impl typemap::Key for SessionKey {
|
|||
}
|
||||
|
||||
fn get_auth_secret<T>(db: &T) -> Result<String>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
use self::misc_settings::dsl::*;
|
||||
let connection = db.get_connection();
|
||||
|
@ -62,11 +62,11 @@ pub fn get_handler(db: Arc<DB>, index: Arc<Mutex<Sender<index::Command>>>) -> Re
|
|||
let session_manager =
|
||||
ChaCha20Poly1305SessionManager::<Session>::from_password(auth_secret.as_bytes());
|
||||
let session_config = SessionConfig::default();
|
||||
let session_middleware =
|
||||
SessionMiddleware::<Session,
|
||||
SessionKey,
|
||||
ChaCha20Poly1305SessionManager<Session>>::new(session_manager,
|
||||
session_config);
|
||||
let session_middleware = SessionMiddleware::<
|
||||
Session,
|
||||
SessionKey,
|
||||
ChaCha20Poly1305SessionManager<Session>,
|
||||
>::new(session_manager, session_config);
|
||||
api_chain.link_around(session_middleware);
|
||||
|
||||
Ok(api_chain)
|
||||
|
@ -79,8 +79,9 @@ fn get_endpoints(db: Arc<DB>, index_channel: Arc<Mutex<Sender<index::Command>>>)
|
|||
api_handler.mount("/version/", self::version);
|
||||
{
|
||||
let db = db.clone();
|
||||
api_handler.mount("/auth/",
|
||||
move |request: &mut Request| self::auth(request, db.deref()));
|
||||
api_handler.mount("/auth/", move |request: &mut Request| {
|
||||
self::auth(request, db.deref())
|
||||
});
|
||||
}
|
||||
{
|
||||
let db = db.clone();
|
||||
|
@ -94,64 +95,70 @@ fn get_endpoints(db: Arc<DB>, index_channel: Arc<Mutex<Sender<index::Command>>>)
|
|||
let mut auth_api_mount = Mount::new();
|
||||
{
|
||||
let db = db.clone();
|
||||
auth_api_mount.mount("/browse/",
|
||||
move |request: &mut Request| self::browse(request, db.deref()));
|
||||
auth_api_mount.mount("/browse/", move |request: &mut Request| {
|
||||
self::browse(request, db.deref())
|
||||
});
|
||||
}
|
||||
{
|
||||
let db = db.clone();
|
||||
auth_api_mount.mount("/flatten/",
|
||||
move |request: &mut Request| self::flatten(request, db.deref()));
|
||||
auth_api_mount.mount("/flatten/", move |request: &mut Request| {
|
||||
self::flatten(request, db.deref())
|
||||
});
|
||||
}
|
||||
{
|
||||
let db = db.clone();
|
||||
auth_api_mount.mount("/random/",
|
||||
move |request: &mut Request| self::random(request, db.deref()));
|
||||
auth_api_mount.mount("/random/", move |request: &mut Request| {
|
||||
self::random(request, db.deref())
|
||||
});
|
||||
}
|
||||
{
|
||||
let db = db.clone();
|
||||
auth_api_mount.mount("/recent/",
|
||||
move |request: &mut Request| self::recent(request, db.deref()));
|
||||
auth_api_mount.mount("/recent/", move |request: &mut Request| {
|
||||
self::recent(request, db.deref())
|
||||
});
|
||||
}
|
||||
{
|
||||
let db = db.clone();
|
||||
auth_api_mount.mount("/search/",
|
||||
move |request: &mut Request| self::search(request, db.deref()));
|
||||
auth_api_mount.mount("/search/", move |request: &mut Request| {
|
||||
self::search(request, db.deref())
|
||||
});
|
||||
}
|
||||
{
|
||||
let db = db.clone();
|
||||
auth_api_mount.mount("/serve/",
|
||||
move |request: &mut Request| self::serve(request, db.deref()));
|
||||
auth_api_mount.mount("/serve/", move |request: &mut Request| {
|
||||
self::serve(request, db.deref())
|
||||
});
|
||||
}
|
||||
{
|
||||
let mut preferences_router = Router::new();
|
||||
let get_db = db.clone();
|
||||
let put_db = db.clone();
|
||||
preferences_router.get("/",
|
||||
move |request: &mut Request| {
|
||||
self::get_preferences(request, get_db.deref())
|
||||
},
|
||||
"get_preferences");
|
||||
preferences_router.put("/",
|
||||
move |request: &mut Request| {
|
||||
self::put_preferences(request, put_db.deref())
|
||||
},
|
||||
"put_preferences");
|
||||
preferences_router.get(
|
||||
"/",
|
||||
move |request: &mut Request| self::get_preferences(request, get_db.deref()),
|
||||
"get_preferences",
|
||||
);
|
||||
preferences_router.put(
|
||||
"/",
|
||||
move |request: &mut Request| self::put_preferences(request, put_db.deref()),
|
||||
"put_preferences",
|
||||
);
|
||||
auth_api_mount.mount("/preferences/", preferences_router);
|
||||
}
|
||||
{
|
||||
let mut settings_router = Router::new();
|
||||
let get_db = db.clone();
|
||||
let put_db = db.clone();
|
||||
settings_router.get("/",
|
||||
move |request: &mut Request| {
|
||||
self::get_config(request, get_db.deref())
|
||||
},
|
||||
"get_config");
|
||||
settings_router.put("/",
|
||||
move |request: &mut Request| {
|
||||
self::put_config(request, put_db.deref())
|
||||
},
|
||||
"put_config");
|
||||
settings_router.get(
|
||||
"/",
|
||||
move |request: &mut Request| self::get_config(request, get_db.deref()),
|
||||
"get_config",
|
||||
);
|
||||
settings_router.put(
|
||||
"/",
|
||||
move |request: &mut Request| self::put_config(request, put_db.deref()),
|
||||
"put_config",
|
||||
);
|
||||
|
||||
let mut settings_api_chain = Chain::new(settings_router);
|
||||
let admin_req = AdminRequirement { db: db.clone() };
|
||||
|
@ -162,9 +169,11 @@ fn get_endpoints(db: Arc<DB>, index_channel: Arc<Mutex<Sender<index::Command>>>)
|
|||
{
|
||||
let index_channel = index_channel.clone();
|
||||
let mut reindex_router = Router::new();
|
||||
reindex_router.post("/",
|
||||
move |_: &mut Request| self::trigger_index(index_channel.deref()),
|
||||
"trigger_index");
|
||||
reindex_router.post(
|
||||
"/",
|
||||
move |_: &mut Request| self::trigger_index(index_channel.deref()),
|
||||
"trigger_index",
|
||||
);
|
||||
|
||||
let mut reindex_api_chain = Chain::new(reindex_router);
|
||||
let admin_req = AdminRequirement { db: db.clone() };
|
||||
|
@ -178,29 +187,29 @@ fn get_endpoints(db: Arc<DB>, index_channel: Arc<Mutex<Sender<index::Command>>>)
|
|||
let list_db = db.clone();
|
||||
let read_db = db.clone();
|
||||
let delete_db = db.clone();
|
||||
playlist_router.put("/",
|
||||
move |request: &mut Request| {
|
||||
self::save_playlist(request, put_db.deref())
|
||||
},
|
||||
"save_playlist");
|
||||
playlist_router.put(
|
||||
"/",
|
||||
move |request: &mut Request| self::save_playlist(request, put_db.deref()),
|
||||
"save_playlist",
|
||||
);
|
||||
|
||||
playlist_router.get("/list",
|
||||
move |request: &mut Request| {
|
||||
self::list_playlists(request, list_db.deref())
|
||||
},
|
||||
"list_playlists");
|
||||
playlist_router.get(
|
||||
"/list",
|
||||
move |request: &mut Request| self::list_playlists(request, list_db.deref()),
|
||||
"list_playlists",
|
||||
);
|
||||
|
||||
playlist_router.get("/read/:playlist_name",
|
||||
move |request: &mut Request| {
|
||||
self::read_playlist(request, read_db.deref())
|
||||
},
|
||||
"read_playlist");
|
||||
playlist_router.get(
|
||||
"/read/:playlist_name",
|
||||
move |request: &mut Request| self::read_playlist(request, read_db.deref()),
|
||||
"read_playlist",
|
||||
);
|
||||
|
||||
playlist_router.delete("/:playlist_name",
|
||||
move |request: &mut Request| {
|
||||
self::delete_playlist(request, delete_db.deref())
|
||||
},
|
||||
"delete_playlist");
|
||||
playlist_router.delete(
|
||||
"/:playlist_name",
|
||||
move |request: &mut Request| self::delete_playlist(request, delete_db.deref()),
|
||||
"delete_playlist",
|
||||
);
|
||||
|
||||
auth_api_mount.mount("/playlist/", playlist_router);
|
||||
}
|
||||
|
@ -249,9 +258,9 @@ struct AuthRequirement {
|
|||
impl AroundMiddleware for AuthRequirement {
|
||||
fn around(self, handler: Box<Handler>) -> Box<Handler> {
|
||||
Box::new(AuthHandler {
|
||||
db: self.db,
|
||||
handler: handler,
|
||||
}) as Box<Handler>
|
||||
db: self.db,
|
||||
handler: handler,
|
||||
}) as Box<Handler>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,8 +286,9 @@ impl Handler for AuthHandler {
|
|||
auth_success =
|
||||
user::auth(self.db.deref(), auth.username.as_str(), password.as_str())?;
|
||||
if auth_success {
|
||||
req.extensions
|
||||
.insert::<SessionKey>(Session { username: auth.username.clone() });
|
||||
req.extensions.insert::<SessionKey>(Session {
|
||||
username: auth.username.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -299,7 +309,6 @@ impl Handler for AuthHandler {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
struct AdminRequirement {
|
||||
db: Arc<DB>,
|
||||
}
|
||||
|
@ -307,9 +316,9 @@ struct AdminRequirement {
|
|||
impl AroundMiddleware for AdminRequirement {
|
||||
fn around(self, handler: Box<Handler>) -> Box<Handler> {
|
||||
Box::new(AdminHandler {
|
||||
db: self.db,
|
||||
handler: handler,
|
||||
}) as Box<Handler>
|
||||
db: self.db,
|
||||
handler: handler,
|
||||
}) as Box<Handler>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,7 +377,9 @@ fn initial_setup(_: &mut Request, db: &DB) -> IronResult<Response> {
|
|||
has_any_users: bool,
|
||||
};
|
||||
|
||||
let initial_setup = InitialSetup { has_any_users: user::count(db)? > 0 };
|
||||
let initial_setup = InitialSetup {
|
||||
has_any_users: user::count(db)? > 0,
|
||||
};
|
||||
|
||||
match serde_json::to_string(&initial_setup) {
|
||||
Ok(result_json) => Ok(Response::with((status::Ok, result_json))),
|
||||
|
@ -395,16 +406,18 @@ fn auth(request: &mut Request, db: &DB) -> IronResult<Response> {
|
|||
return Err(Error::from(ErrorKind::IncorrectCredentials).into());
|
||||
}
|
||||
|
||||
request
|
||||
.extensions
|
||||
.insert::<SessionKey>(Session { username: username.clone() });
|
||||
request.extensions.insert::<SessionKey>(Session {
|
||||
username: username.clone(),
|
||||
});
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AuthOutput {
|
||||
admin: bool,
|
||||
}
|
||||
|
||||
let auth_output = AuthOutput { admin: user::is_admin(db.deref(), &username)? };
|
||||
let auth_output = AuthOutput {
|
||||
admin: user::is_admin(db.deref(), &username)?,
|
||||
};
|
||||
let result_json = serde_json::to_string(&auth_output);
|
||||
let result_json = match result_json {
|
||||
Ok(j) => j,
|
||||
|
@ -602,7 +615,6 @@ fn trigger_index(channel: &Mutex<Sender<index::Command>>) -> IronResult<Response
|
|||
}
|
||||
|
||||
fn save_playlist(request: &mut Request, db: &DB) -> IronResult<Response> {
|
||||
|
||||
let username = match request.extensions.get::<SessionKey>() {
|
||||
Some(s) => s.username.clone(),
|
||||
None => return Err(Error::from(ErrorKind::AuthenticationRequired).into()),
|
||||
|
|
182
src/config.rs
182
src/config.rs
|
@ -8,8 +8,8 @@ use std::io::Read;
|
|||
use std::path;
|
||||
use toml;
|
||||
|
||||
use db::DB;
|
||||
use db::ConnectionSource;
|
||||
use db::DB;
|
||||
use db::{ddns_config, misc_settings, mount_points, users};
|
||||
use ddns::DDNSConfig;
|
||||
use errors::*;
|
||||
|
@ -76,10 +76,11 @@ pub fn parse_toml_file(path: &path::Path) -> Result<Config> {
|
|||
}
|
||||
|
||||
pub fn read<T>(db: &T) -> Result<Config>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
use self::misc_settings::dsl::*;
|
||||
use self::ddns_config::dsl::*;
|
||||
use self::misc_settings::dsl::*;
|
||||
|
||||
let connection = db.get_connection();
|
||||
|
||||
|
@ -93,8 +94,11 @@ pub fn read<T>(db: &T) -> Result<Config>
|
|||
};
|
||||
|
||||
let (art_pattern, sleep_duration, url) = misc_settings
|
||||
.select((index_album_art_pattern, index_sleep_duration_seconds, prefix_url))
|
||||
.get_result(connection.deref())?;
|
||||
.select((
|
||||
index_album_art_pattern,
|
||||
index_sleep_duration_seconds,
|
||||
prefix_url,
|
||||
)).get_result(connection.deref())?;
|
||||
config.album_art_pattern = Some(art_pattern);
|
||||
config.reindex_every_n_seconds = Some(sleep_duration);
|
||||
config.prefix_url = if url != "" { Some(url) } else { None };
|
||||
|
@ -111,16 +115,15 @@ pub fn read<T>(db: &T) -> Result<Config>
|
|||
let found_users: Vec<(String, i32)> = users::table
|
||||
.select((users::columns::name, users::columns::admin))
|
||||
.get_results(connection.deref())?;
|
||||
config.users = Some(found_users
|
||||
.into_iter()
|
||||
.map(|(name, admin)| {
|
||||
ConfigUser {
|
||||
name: name,
|
||||
password: "".to_owned(),
|
||||
admin: admin != 0,
|
||||
}
|
||||
})
|
||||
.collect::<_>());
|
||||
config.users = Some(
|
||||
found_users
|
||||
.into_iter()
|
||||
.map(|(name, admin)| ConfigUser {
|
||||
name: name,
|
||||
password: "".to_owned(),
|
||||
admin: admin != 0,
|
||||
}).collect::<_>(),
|
||||
);
|
||||
|
||||
let ydns = ddns_config
|
||||
.select((host, username, password))
|
||||
|
@ -131,13 +134,13 @@ pub fn read<T>(db: &T) -> Result<Config>
|
|||
}
|
||||
|
||||
fn reset<T>(db: &T) -> Result<()>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
use self::ddns_config::dsl::*;
|
||||
let connection = db.get_connection();
|
||||
|
||||
diesel::delete(mount_points::table)
|
||||
.execute(connection.deref())?;
|
||||
diesel::delete(mount_points::table).execute(connection.deref())?;
|
||||
diesel::delete(users::table).execute(connection.deref())?;
|
||||
diesel::update(ddns_config)
|
||||
.set((host.eq(""), username.eq(""), password.eq("")))
|
||||
|
@ -147,20 +150,21 @@ fn reset<T>(db: &T) -> Result<()>
|
|||
}
|
||||
|
||||
pub fn overwrite<T>(db: &T, new_config: &Config) -> Result<()>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
reset(db)?;
|
||||
amend(db, new_config)
|
||||
}
|
||||
|
||||
pub fn amend<T>(db: &T, new_config: &Config) -> Result<()>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
let connection = db.get_connection();
|
||||
|
||||
if let Some(ref mount_dirs) = new_config.mount_dirs {
|
||||
diesel::delete(mount_points::table)
|
||||
.execute(connection.deref())?;
|
||||
diesel::delete(mount_points::table).execute(connection.deref())?;
|
||||
diesel::insert_into(mount_points::table)
|
||||
.values(mount_dirs)
|
||||
.execute(connection.deref())?;
|
||||
|
@ -175,12 +179,7 @@ pub fn amend<T>(db: &T, new_config: &Config) -> Result<()>
|
|||
let delete_usernames: Vec<String> = old_usernames
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|old_name| {
|
||||
config_users
|
||||
.iter()
|
||||
.find(|u| &u.name == old_name)
|
||||
.is_none()
|
||||
})
|
||||
.filter(|old_name| config_users.iter().find(|u| &u.name == old_name).is_none())
|
||||
.collect::<_>();
|
||||
diesel::delete(users::table.filter(users::name.eq_any(&delete_usernames)))
|
||||
.execute(connection.deref())?;
|
||||
|
@ -189,12 +188,11 @@ pub fn amend<T>(db: &T, new_config: &Config) -> Result<()>
|
|||
let insert_users: Vec<&ConfigUser> = config_users
|
||||
.iter()
|
||||
.filter(|u| {
|
||||
old_usernames
|
||||
.iter()
|
||||
.find(|old_name| *old_name == &u.name)
|
||||
.is_none()
|
||||
})
|
||||
.collect::<_>();
|
||||
old_usernames
|
||||
.iter()
|
||||
.find(|old_name| *old_name == &u.name)
|
||||
.is_none()
|
||||
}).collect::<_>();
|
||||
for ref config_user in insert_users {
|
||||
let new_user = User::new(&config_user.name, &config_user.password);
|
||||
diesel::insert_into(users::table)
|
||||
|
@ -238,10 +236,11 @@ pub fn amend<T>(db: &T, new_config: &Config) -> Result<()>
|
|||
if let Some(ref ydns) = new_config.ydns {
|
||||
use self::ddns_config::dsl::*;
|
||||
diesel::update(ddns_config)
|
||||
.set((host.eq(ydns.host.clone()),
|
||||
username.eq(ydns.username.clone()),
|
||||
password.eq(ydns.password.clone())))
|
||||
.execute(connection.deref())?;
|
||||
.set((
|
||||
host.eq(ydns.host.clone()),
|
||||
username.eq(ydns.username.clone()),
|
||||
password.eq(ydns.password.clone()),
|
||||
)).execute(connection.deref())?;
|
||||
}
|
||||
|
||||
if let Some(ref prefix_url) = new_config.prefix_url {
|
||||
|
@ -254,13 +253,15 @@ pub fn amend<T>(db: &T, new_config: &Config) -> Result<()>
|
|||
}
|
||||
|
||||
pub fn read_preferences<T>(_: &T, _: &str) -> Result<Preferences>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
Ok(Preferences {})
|
||||
}
|
||||
|
||||
pub fn write_preferences<T>(_: &T, _: &str, _: &Preferences) -> Result<()>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
|
@ -294,14 +295,14 @@ fn test_amend() {
|
|||
reindex_every_n_seconds: Some(123),
|
||||
prefix_url: None,
|
||||
mount_dirs: Some(vec![MountPoint {
|
||||
source: "C:\\Music".into(),
|
||||
name: "root".into(),
|
||||
}]),
|
||||
source: "C:\\Music".into(),
|
||||
name: "root".into(),
|
||||
}]),
|
||||
users: Some(vec![ConfigUser {
|
||||
name: "Teddy🐻".into(),
|
||||
password: "Tasty🍖".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
name: "Teddy🐻".into(),
|
||||
password: "Tasty🍖".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
ydns: None,
|
||||
};
|
||||
|
||||
|
@ -310,19 +311,19 @@ fn test_amend() {
|
|||
reindex_every_n_seconds: None,
|
||||
prefix_url: Some("polaris".into()),
|
||||
mount_dirs: Some(vec![MountPoint {
|
||||
source: "/home/music".into(),
|
||||
name: "🎵📁".into(),
|
||||
}]),
|
||||
source: "/home/music".into(),
|
||||
name: "🎵📁".into(),
|
||||
}]),
|
||||
users: Some(vec![ConfigUser {
|
||||
name: "Kermit🐸".into(),
|
||||
password: "🐞🐞".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
name: "Kermit🐸".into(),
|
||||
password: "🐞🐞".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
ydns: Some(DDNSConfig {
|
||||
host: "🐸🐸🐸.ydns.eu".into(),
|
||||
username: "kfr🐸g".into(),
|
||||
password: "tasty🐞".into(),
|
||||
}),
|
||||
host: "🐸🐸🐸.ydns.eu".into(),
|
||||
username: "kfr🐸g".into(),
|
||||
password: "tasty🐞".into(),
|
||||
}),
|
||||
};
|
||||
|
||||
let mut expected_config = new_config.clone();
|
||||
|
@ -351,10 +352,10 @@ fn test_amend_preserve_password_hashes() {
|
|||
prefix_url: None,
|
||||
mount_dirs: None,
|
||||
users: Some(vec![ConfigUser {
|
||||
name: "Teddy🐻".into(),
|
||||
password: "Tasty🍖".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
name: "Teddy🐻".into(),
|
||||
password: "Tasty🍖".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
ydns: None,
|
||||
};
|
||||
amend(&db, &initial_config).unwrap();
|
||||
|
@ -373,16 +374,18 @@ fn test_amend_preserve_password_hashes() {
|
|||
reindex_every_n_seconds: None,
|
||||
prefix_url: None,
|
||||
mount_dirs: None,
|
||||
users: Some(vec![ConfigUser {
|
||||
name: "Kermit🐸".into(),
|
||||
password: "tasty🐞".into(),
|
||||
admin: false,
|
||||
},
|
||||
ConfigUser {
|
||||
name: "Teddy🐻".into(),
|
||||
password: "".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
users: Some(vec![
|
||||
ConfigUser {
|
||||
name: "Kermit🐸".into(),
|
||||
password: "tasty🐞".into(),
|
||||
admin: false,
|
||||
},
|
||||
ConfigUser {
|
||||
name: "Teddy🐻".into(),
|
||||
password: "".into(),
|
||||
admin: false,
|
||||
},
|
||||
]),
|
||||
ydns: None,
|
||||
};
|
||||
amend(&db, &new_config).unwrap();
|
||||
|
@ -411,20 +414,17 @@ fn test_toggle_admin() {
|
|||
prefix_url: None,
|
||||
mount_dirs: None,
|
||||
users: Some(vec![ConfigUser {
|
||||
name: "Teddy🐻".into(),
|
||||
password: "Tasty🍖".into(),
|
||||
admin: true,
|
||||
}]),
|
||||
name: "Teddy🐻".into(),
|
||||
password: "Tasty🍖".into(),
|
||||
admin: true,
|
||||
}]),
|
||||
ydns: None,
|
||||
};
|
||||
amend(&db, &initial_config).unwrap();
|
||||
|
||||
{
|
||||
let connection = db.get_connection();
|
||||
let is_admin: i32 = users
|
||||
.select(admin)
|
||||
.get_result(connection.deref())
|
||||
.unwrap();
|
||||
let is_admin: i32 = users.select(admin).get_result(connection.deref()).unwrap();
|
||||
assert_eq!(is_admin, 1);
|
||||
}
|
||||
|
||||
|
@ -434,27 +434,23 @@ fn test_toggle_admin() {
|
|||
prefix_url: None,
|
||||
mount_dirs: None,
|
||||
users: Some(vec![ConfigUser {
|
||||
name: "Teddy🐻".into(),
|
||||
password: "".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
name: "Teddy🐻".into(),
|
||||
password: "".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
ydns: None,
|
||||
};
|
||||
amend(&db, &new_config).unwrap();
|
||||
|
||||
{
|
||||
let connection = db.get_connection();
|
||||
let is_admin: i32 = users
|
||||
.select(admin)
|
||||
.get_result(connection.deref())
|
||||
.unwrap();
|
||||
let is_admin: i32 = users.select(admin).get_result(connection.deref()).unwrap();
|
||||
assert_eq!(is_admin, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preferences_read_write() {
|
||||
|
||||
let db = _get_test_db("preferences_read_write.sqlite");
|
||||
|
||||
let initial_config = Config {
|
||||
|
@ -463,10 +459,10 @@ fn test_preferences_read_write() {
|
|||
prefix_url: None,
|
||||
mount_dirs: None,
|
||||
users: Some(vec![ConfigUser {
|
||||
name: "Teddy🐻".into(),
|
||||
password: "Tasty🍖".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
name: "Teddy🐻".into(),
|
||||
password: "Tasty🍖".into(),
|
||||
admin: false,
|
||||
}]),
|
||||
ydns: None,
|
||||
};
|
||||
amend(&db, &initial_config).unwrap();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use core::ops::Deref;
|
||||
use diesel_migrations;
|
||||
use diesel::prelude::*;
|
||||
use diesel::sqlite::SqliteConnection;
|
||||
use diesel_migrations;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
@ -28,9 +28,12 @@ pub struct DB {
|
|||
impl DB {
|
||||
pub fn new(path: &Path) -> Result<DB> {
|
||||
info!("Database file path: {}", path.to_string_lossy());
|
||||
let connection =
|
||||
Arc::new(Mutex::new(SqliteConnection::establish(&path.to_string_lossy())?));
|
||||
let db = DB { connection: connection.clone() };
|
||||
let connection = Arc::new(Mutex::new(SqliteConnection::establish(
|
||||
&path.to_string_lossy(),
|
||||
)?));
|
||||
let db = DB {
|
||||
connection: connection.clone(),
|
||||
};
|
||||
db.init()?;
|
||||
Ok(db)
|
||||
}
|
||||
|
@ -49,9 +52,14 @@ impl DB {
|
|||
let connection = self.connection.lock().unwrap();
|
||||
let connection = connection.deref();
|
||||
loop {
|
||||
match diesel_migrations::revert_latest_migration_in_directory(connection, Path::new(DB_MIGRATIONS_PATH)) {
|
||||
match diesel_migrations::revert_latest_migration_in_directory(
|
||||
connection,
|
||||
Path::new(DB_MIGRATIONS_PATH),
|
||||
) {
|
||||
Ok(_) => (),
|
||||
Err(diesel_migrations::RunMigrationsError::MigrationError(diesel_migrations::MigrationError::NoMigrationRun)) => break,
|
||||
Err(diesel_migrations::RunMigrationsError::MigrationError(
|
||||
diesel_migrations::MigrationError::NoMigrationRun,
|
||||
)) => break,
|
||||
Err(e) => bail!(e),
|
||||
}
|
||||
}
|
||||
|
|
26
src/ddns.rs
26
src/ddns.rs
|
@ -6,12 +6,12 @@ use std::io;
|
|||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
use db::{ConnectionSource, DB};
|
||||
use db::ddns_config;
|
||||
use db::{ConnectionSource, DB};
|
||||
use errors;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Insertable, PartialEq, Queryable, Serialize)]
|
||||
#[table_name="ddns_config"]
|
||||
#[table_name = "ddns_config"]
|
||||
pub struct DDNSConfig {
|
||||
pub host: String,
|
||||
pub username: String,
|
||||
|
@ -27,8 +27,8 @@ impl DDNSConfigSource for DB {
|
|||
use self::ddns_config::dsl::*;
|
||||
let connection = self.get_connection();
|
||||
Ok(ddns_config
|
||||
.select((host, username, password))
|
||||
.get_result(connection.deref())?)
|
||||
.select((host, username, password))
|
||||
.get_result(connection.deref())?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,9 +60,9 @@ impl From<reqwest::Error> for DDNSError {
|
|||
|
||||
const DDNS_UPDATE_URL: &'static str = "https://ydns.io/api/v1/update/";
|
||||
|
||||
|
||||
fn update_my_ip<T>(config_source: &T) -> Result<(), DDNSError>
|
||||
where T: DDNSConfigSource
|
||||
where
|
||||
T: DDNSConfigSource,
|
||||
{
|
||||
let config = config_source.get_ddns_config()?;
|
||||
if config.host.len() == 0 || config.username.len() == 0 {
|
||||
|
@ -72,14 +72,11 @@ fn update_my_ip<T>(config_source: &T) -> Result<(), DDNSError>
|
|||
|
||||
let full_url = format!("{}?host={}", DDNS_UPDATE_URL, &config.host);
|
||||
let auth_header = Authorization(Basic {
|
||||
username: config.username.clone(),
|
||||
password: Some(config.password.to_owned()),
|
||||
});
|
||||
username: config.username.clone(),
|
||||
password: Some(config.password.to_owned()),
|
||||
});
|
||||
let client = reqwest::Client::new()?;
|
||||
let res = client
|
||||
.get(full_url.as_str())
|
||||
.header(auth_header)
|
||||
.send()?;
|
||||
let res = client.get(full_url.as_str()).header(auth_header).send()?;
|
||||
if !res.status().is_success() {
|
||||
return Err(DDNSError::UpdateError(*res.status()));
|
||||
}
|
||||
|
@ -87,7 +84,8 @@ fn update_my_ip<T>(config_source: &T) -> Result<(), DDNSError>
|
|||
}
|
||||
|
||||
pub fn run<T>(config_source: &T)
|
||||
where T: DDNSConfigSource
|
||||
where
|
||||
T: DDNSConfigSource,
|
||||
{
|
||||
loop {
|
||||
if let Err(e) = update_my_ip(config_source) {
|
||||
|
|
|
@ -2,12 +2,12 @@ use ape;
|
|||
use core;
|
||||
use diesel;
|
||||
use diesel_migrations;
|
||||
use id3;
|
||||
use getopts;
|
||||
use image;
|
||||
use hyper;
|
||||
use iron::IronError;
|
||||
use id3;
|
||||
use image;
|
||||
use iron::status::Status;
|
||||
use iron::IronError;
|
||||
use lewton;
|
||||
use metaflac;
|
||||
use regex;
|
||||
|
@ -17,46 +17,46 @@ use std;
|
|||
use toml;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Ape(ape::Error);
|
||||
Diesel(diesel::result::Error);
|
||||
DieselConnection(diesel::ConnectionError);
|
||||
DieselMigration(diesel_migrations::RunMigrationsError);
|
||||
Encoding(core::str::Utf8Error);
|
||||
Flac(metaflac::Error);
|
||||
GetOpts(getopts::Fail);
|
||||
Hyper(hyper::Error);
|
||||
Id3(id3::Error);
|
||||
Image(image::ImageError);
|
||||
Io(std::io::Error);
|
||||
Json(serde_json::Error);
|
||||
Time(std::time::SystemTimeError);
|
||||
Toml(toml::de::Error);
|
||||
Regex(regex::Error);
|
||||
Scrobbler(rustfm_scrobble::ScrobblerError);
|
||||
Vorbis(lewton::VorbisError);
|
||||
}
|
||||
foreign_links {
|
||||
Ape(ape::Error);
|
||||
Diesel(diesel::result::Error);
|
||||
DieselConnection(diesel::ConnectionError);
|
||||
DieselMigration(diesel_migrations::RunMigrationsError);
|
||||
Encoding(core::str::Utf8Error);
|
||||
Flac(metaflac::Error);
|
||||
GetOpts(getopts::Fail);
|
||||
Hyper(hyper::Error);
|
||||
Id3(id3::Error);
|
||||
Image(image::ImageError);
|
||||
Io(std::io::Error);
|
||||
Json(serde_json::Error);
|
||||
Time(std::time::SystemTimeError);
|
||||
Toml(toml::de::Error);
|
||||
Regex(regex::Error);
|
||||
Scrobbler(rustfm_scrobble::ScrobblerError);
|
||||
Vorbis(lewton::VorbisError);
|
||||
}
|
||||
|
||||
errors {
|
||||
DaemonError {}
|
||||
AuthenticationRequired {}
|
||||
AdminPrivilegeRequired {}
|
||||
MissingConfig {}
|
||||
MissingPreferences {}
|
||||
MissingUsername {}
|
||||
MissingPassword {}
|
||||
MissingPlaylist {}
|
||||
IncorrectCredentials {}
|
||||
CannotServeDirectory {}
|
||||
UnsupportedFileType {}
|
||||
FileNotFound {}
|
||||
MissingIndexVersion {}
|
||||
MissingPlaylistName {}
|
||||
EncodingError {}
|
||||
MissingLastFMCredentials {}
|
||||
LastFMAuthError {}
|
||||
LastFMDeserializationError {}
|
||||
}
|
||||
errors {
|
||||
DaemonError {}
|
||||
AuthenticationRequired {}
|
||||
AdminPrivilegeRequired {}
|
||||
MissingConfig {}
|
||||
MissingPreferences {}
|
||||
MissingUsername {}
|
||||
MissingPassword {}
|
||||
MissingPlaylist {}
|
||||
IncorrectCredentials {}
|
||||
CannotServeDirectory {}
|
||||
UnsupportedFileType {}
|
||||
FileNotFound {}
|
||||
MissingIndexVersion {}
|
||||
MissingPlaylistName {}
|
||||
EncodingError {}
|
||||
MissingLastFMCredentials {}
|
||||
LastFMAuthError {}
|
||||
LastFMDeserializationError {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for IronError {
|
||||
|
|
171
src/index.rs
171
src/index.rs
|
@ -9,8 +9,8 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
#[cfg(test)]
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
|
@ -19,16 +19,18 @@ use config::MiscSettings;
|
|||
use db;
|
||||
use db::ConnectionSource;
|
||||
use db::{directories, misc_settings, songs};
|
||||
use vfs::{VFS, VFSSource};
|
||||
use errors;
|
||||
use metadata;
|
||||
use vfs::{VFSSource, VFS};
|
||||
|
||||
const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 1000; // Insertions in each transaction
|
||||
const INDEX_BUILDING_CLEAN_BUFFER_SIZE: usize = 500; // Insertions in each transaction
|
||||
|
||||
no_arg_sql_function!(random,
|
||||
types::Integer,
|
||||
"Represents the SQL RANDOM() function");
|
||||
no_arg_sql_function!(
|
||||
random,
|
||||
types::Integer,
|
||||
"Represents the SQL RANDOM() function"
|
||||
);
|
||||
|
||||
pub enum Command {
|
||||
REINDEX,
|
||||
|
@ -74,7 +76,7 @@ pub enum CollectionFile {
|
|||
}
|
||||
|
||||
#[derive(Debug, Insertable)]
|
||||
#[table_name="songs"]
|
||||
#[table_name = "songs"]
|
||||
struct NewSong {
|
||||
path: String,
|
||||
parent: String,
|
||||
|
@ -90,7 +92,7 @@ struct NewSong {
|
|||
}
|
||||
|
||||
#[derive(Debug, Insertable)]
|
||||
#[table_name="directories"]
|
||||
#[table_name = "directories"]
|
||||
struct NewDirectory {
|
||||
path: String,
|
||||
parent: Option<String>,
|
||||
|
@ -109,31 +111,31 @@ struct IndexBuilder<'conn> {
|
|||
}
|
||||
|
||||
impl<'conn> IndexBuilder<'conn> {
|
||||
fn new(connection: &Mutex<SqliteConnection>,
|
||||
album_art_pattern: Regex)
|
||||
-> Result<IndexBuilder, errors::Error> {
|
||||
fn new(
|
||||
connection: &Mutex<SqliteConnection>,
|
||||
album_art_pattern: Regex,
|
||||
) -> Result<IndexBuilder, errors::Error> {
|
||||
let mut new_songs = Vec::new();
|
||||
let mut new_directories = Vec::new();
|
||||
new_songs.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
|
||||
new_directories.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
|
||||
Ok(IndexBuilder {
|
||||
new_songs: new_songs,
|
||||
new_directories: new_directories,
|
||||
connection: connection,
|
||||
album_art_pattern: album_art_pattern,
|
||||
})
|
||||
new_songs: new_songs,
|
||||
new_directories: new_directories,
|
||||
connection: connection,
|
||||
album_art_pattern: album_art_pattern,
|
||||
})
|
||||
}
|
||||
|
||||
fn flush_songs(&mut self) -> Result<(), errors::Error> {
|
||||
let connection = self.connection.lock().unwrap();
|
||||
let connection = connection.deref();
|
||||
connection
|
||||
.transaction::<_, errors::Error, _>(|| {
|
||||
diesel::insert_into(songs::table)
|
||||
.values(&self.new_songs)
|
||||
.execute(connection)?;
|
||||
Ok(())
|
||||
})?;
|
||||
connection.transaction::<_, errors::Error, _>(|| {
|
||||
diesel::insert_into(songs::table)
|
||||
.values(&self.new_songs)
|
||||
.execute(connection)?;
|
||||
Ok(())
|
||||
})?;
|
||||
self.new_songs.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -141,13 +143,12 @@ impl<'conn> IndexBuilder<'conn> {
|
|||
fn flush_directories(&mut self) -> Result<(), errors::Error> {
|
||||
let connection = self.connection.lock().unwrap();
|
||||
let connection = connection.deref();
|
||||
connection
|
||||
.transaction::<_, errors::Error, _>(|| {
|
||||
diesel::insert_into(directories::table)
|
||||
.values(&self.new_directories)
|
||||
.execute(connection)?;
|
||||
Ok(())
|
||||
})?;
|
||||
connection.transaction::<_, errors::Error, _>(|| {
|
||||
diesel::insert_into(directories::table)
|
||||
.values(&self.new_directories)
|
||||
.execute(connection)?;
|
||||
Ok(())
|
||||
})?;
|
||||
self.new_directories.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -180,11 +181,11 @@ impl<'conn> IndexBuilder<'conn> {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
fn populate_directory(&mut self,
|
||||
parent: Option<&Path>,
|
||||
path: &Path)
|
||||
-> Result<(), errors::Error> {
|
||||
|
||||
fn populate_directory(
|
||||
&mut self,
|
||||
parent: Option<&Path>,
|
||||
path: &Path,
|
||||
) -> Result<(), errors::Error> {
|
||||
// Find artwork
|
||||
let artwork = self.get_artwork(path).unwrap_or(None);
|
||||
|
||||
|
@ -228,24 +229,24 @@ impl<'conn> IndexBuilder<'conn> {
|
|||
if let Some(file_path_string) = file_path.to_str() {
|
||||
if let Ok(tags) = metadata::read(file_path.as_path()) {
|
||||
if tags.year.is_some() {
|
||||
inconsistent_directory_year |= directory_year.is_some() &&
|
||||
directory_year != tags.year;
|
||||
inconsistent_directory_year |=
|
||||
directory_year.is_some() && directory_year != tags.year;
|
||||
directory_year = tags.year;
|
||||
}
|
||||
|
||||
if tags.album.is_some() {
|
||||
inconsistent_directory_album |= directory_album.is_some() &&
|
||||
directory_album != tags.album;
|
||||
inconsistent_directory_album |=
|
||||
directory_album.is_some() && directory_album != tags.album;
|
||||
directory_album = tags.album.as_ref().map(|a| a.clone());
|
||||
}
|
||||
|
||||
if tags.album_artist.is_some() {
|
||||
inconsistent_directory_artist |= directory_artist.is_some() &&
|
||||
directory_artist != tags.album_artist;
|
||||
inconsistent_directory_artist |=
|
||||
directory_artist.is_some() && directory_artist != tags.album_artist;
|
||||
directory_artist = tags.album_artist.as_ref().map(|a| a.clone());
|
||||
} else if tags.artist.is_some() {
|
||||
inconsistent_directory_artist |= directory_artist.is_some() &&
|
||||
directory_artist != tags.artist;
|
||||
inconsistent_directory_artist |=
|
||||
directory_artist.is_some() && directory_artist != tags.artist;
|
||||
directory_artist = tags.artist.as_ref().map(|a| a.clone());
|
||||
}
|
||||
|
||||
|
@ -300,7 +301,8 @@ impl<'conn> IndexBuilder<'conn> {
|
|||
}
|
||||
|
||||
fn clean<T>(db: &T) -> Result<(), errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let vfs = db.get_vfs()?;
|
||||
|
||||
|
@ -308,18 +310,15 @@ fn clean<T>(db: &T) -> Result<(), errors::Error>
|
|||
let all_songs: Vec<String>;
|
||||
{
|
||||
let connection = db.get_connection();
|
||||
all_songs = songs::table
|
||||
.select(songs::path)
|
||||
.load(connection.deref())?;
|
||||
all_songs = songs::table.select(songs::path).load(connection.deref())?;
|
||||
}
|
||||
|
||||
let missing_songs = all_songs
|
||||
.into_iter()
|
||||
.filter(|ref song_path| {
|
||||
let path = Path::new(&song_path);
|
||||
!path.exists() || vfs.real_to_virtual(path).is_err()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let path = Path::new(&song_path);
|
||||
!path.exists() || vfs.real_to_virtual(path).is_err()
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
{
|
||||
let connection = db.get_connection();
|
||||
|
@ -327,7 +326,6 @@ fn clean<T>(db: &T) -> Result<(), errors::Error>
|
|||
diesel::delete(songs::table.filter(songs::path.eq_any(chunk)))
|
||||
.execute(connection.deref())?;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,10 +341,9 @@ fn clean<T>(db: &T) -> Result<(), errors::Error>
|
|||
let missing_directories = all_directories
|
||||
.into_iter()
|
||||
.filter(|ref directory_path| {
|
||||
let path = Path::new(&directory_path);
|
||||
!path.exists() || vfs.real_to_virtual(path).is_err()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let path = Path::new(&directory_path);
|
||||
!path.exists() || vfs.real_to_virtual(path).is_err()
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
{
|
||||
let connection = db.get_connection();
|
||||
|
@ -361,7 +358,8 @@ fn clean<T>(db: &T) -> Result<(), errors::Error>
|
|||
}
|
||||
|
||||
fn populate<T>(db: &T) -> Result<(), errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let vfs = db.get_vfs()?;
|
||||
let mount_points = vfs.get_mount_points();
|
||||
|
@ -384,19 +382,23 @@ fn populate<T>(db: &T) -> Result<(), errors::Error>
|
|||
}
|
||||
|
||||
pub fn update<T>(db: &T) -> Result<(), errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let start = time::Instant::now();
|
||||
info!("Beginning library index update");
|
||||
clean(db)?;
|
||||
populate(db)?;
|
||||
info!("Library index update took {} seconds",
|
||||
start.elapsed().as_secs());
|
||||
info!(
|
||||
"Library index update took {} seconds",
|
||||
start.elapsed().as_secs()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_loop<T>(db: &T, command_buffer: Receiver<Command>)
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
loop {
|
||||
// Wait for a command
|
||||
|
@ -425,7 +427,8 @@ pub fn update_loop<T>(db: &T, command_buffer: Receiver<Command>)
|
|||
}
|
||||
|
||||
pub fn self_trigger<T>(db: &T, command_buffer: Arc<Mutex<Sender<Command>>>)
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
loop {
|
||||
{
|
||||
|
@ -482,7 +485,8 @@ fn virtualize_directory(vfs: &VFS, mut directory: Directory) -> Option<Directory
|
|||
}
|
||||
|
||||
pub fn browse<T>(db: &T, virtual_path: &Path) -> Result<Vec<CollectionFile>, errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
let vfs = db.get_vfs()?;
|
||||
|
@ -496,10 +500,11 @@ pub fn browse<T>(db: &T, virtual_path: &Path) -> Result<Vec<CollectionFile>, err
|
|||
let virtual_directories = real_directories
|
||||
.into_iter()
|
||||
.filter_map(|s| virtualize_directory(&vfs, s));
|
||||
output.extend(virtual_directories
|
||||
.into_iter()
|
||||
.map(|d| CollectionFile::Directory(d)));
|
||||
|
||||
output.extend(
|
||||
virtual_directories
|
||||
.into_iter()
|
||||
.map(|d| CollectionFile::Directory(d)),
|
||||
);
|
||||
} else {
|
||||
// Browse sub-directory
|
||||
let real_path = vfs.virtual_to_real(virtual_path)?;
|
||||
|
@ -528,7 +533,8 @@ pub fn browse<T>(db: &T, virtual_path: &Path) -> Result<Vec<CollectionFile>, err
|
|||
}
|
||||
|
||||
pub fn flatten<T>(db: &T, virtual_path: &Path) -> Result<Vec<Song>, errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
use self::songs::dsl::*;
|
||||
let vfs = db.get_vfs()?;
|
||||
|
@ -552,7 +558,8 @@ pub fn flatten<T>(db: &T, virtual_path: &Path) -> Result<Vec<Song>, errors::Erro
|
|||
}
|
||||
|
||||
pub fn get_random_albums<T>(db: &T, count: i64) -> Result<Vec<Directory>, errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
use self::directories::dsl::*;
|
||||
let vfs = db.get_vfs()?;
|
||||
|
@ -569,7 +576,8 @@ pub fn get_random_albums<T>(db: &T, count: i64) -> Result<Vec<Directory>, errors
|
|||
}
|
||||
|
||||
pub fn get_recent_albums<T>(db: &T, count: i64) -> Result<Vec<Directory>, errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
use self::directories::dsl::*;
|
||||
let vfs = db.get_vfs()?;
|
||||
|
@ -586,7 +594,8 @@ pub fn get_recent_albums<T>(db: &T, count: i64) -> Result<Vec<Directory>, errors
|
|||
}
|
||||
|
||||
pub fn search<T>(db: &T, query: &str) -> Result<Vec<CollectionFile>, errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let vfs = db.get_vfs()?;
|
||||
let connection = db.get_connection();
|
||||
|
@ -612,12 +621,13 @@ pub fn search<T>(db: &T, query: &str) -> Result<Vec<CollectionFile>, errors::Err
|
|||
{
|
||||
use self::songs::dsl::*;
|
||||
let real_songs: Vec<Song> = songs
|
||||
.filter(path.like(&like_test)
|
||||
.or(title.like(&like_test))
|
||||
.or(album.like(&like_test))
|
||||
.or(artist.like(&like_test))
|
||||
.or(album_artist.like(&like_test)))
|
||||
.filter(parent.not_like(&like_test))
|
||||
.filter(
|
||||
path.like(&like_test)
|
||||
.or(title.like(&like_test))
|
||||
.or(album.like(&like_test))
|
||||
.or(artist.like(&like_test))
|
||||
.or(album_artist.like(&like_test)),
|
||||
).filter(parent.not_like(&like_test))
|
||||
.load(connection.deref())?;
|
||||
|
||||
let virtual_songs = real_songs
|
||||
|
@ -631,7 +641,8 @@ pub fn search<T>(db: &T, query: &str) -> Result<Vec<CollectionFile>, errors::Err
|
|||
}
|
||||
|
||||
pub fn get_song<T>(db: &T, virtual_path: &Path) -> Result<Song, errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let vfs = db.get_vfs()?;
|
||||
let connection = db.get_connection();
|
||||
|
@ -695,8 +706,10 @@ fn test_metadata() {
|
|||
assert_eq!(song.album_artist, None);
|
||||
assert_eq!(song.album, Some("Picnic".to_owned()));
|
||||
assert_eq!(song.year, Some(2016));
|
||||
assert_eq!(song.artwork,
|
||||
Some(artwork_path.to_string_lossy().into_owned()));
|
||||
assert_eq!(
|
||||
song.artwork,
|
||||
Some(artwork_path.to_string_lossy().into_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use md5;
|
||||
use reqwest;
|
||||
use rustfm_scrobble::{Scrobbler, Scrobble};
|
||||
use rustfm_scrobble::{Scrobble, Scrobbler};
|
||||
use serde_xml_rs::deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
|
@ -48,16 +48,20 @@ struct AuthResponse {
|
|||
}
|
||||
|
||||
fn scrobble_from_path<T>(db: &T, track: &Path) -> Result<Scrobble, errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let song = index::get_song(db, track)?;
|
||||
Ok(Scrobble::new(song.artist.unwrap_or("".into()),
|
||||
song.title.unwrap_or("".into()),
|
||||
song.album.unwrap_or("".into())))
|
||||
Ok(Scrobble::new(
|
||||
song.artist.unwrap_or("".into()),
|
||||
song.title.unwrap_or("".into()),
|
||||
song.album.unwrap_or("".into()),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn auth<T>(db: &T, username: &str, token: &str) -> Result<(), errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let mut params = HashMap::new();
|
||||
params.insert("token".to_string(), token.to_string());
|
||||
|
@ -75,14 +79,15 @@ pub fn auth<T>(db: &T, username: &str, token: &str) -> Result<(), errors::Error>
|
|||
|
||||
let auth_response: AuthResponse = match deserialize(body.as_bytes()) {
|
||||
Ok(d) => d,
|
||||
Err(_) => bail!(errors::ErrorKind::LastFMDeserializationError)
|
||||
Err(_) => bail!(errors::ErrorKind::LastFMDeserializationError),
|
||||
};
|
||||
|
||||
user::set_lastfm_session_key(db, username, &auth_response.session.key.body)
|
||||
}
|
||||
|
||||
pub fn scrobble<T>(db: &T, username: &str, track: &Path) -> Result<(), errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY.into(), LASTFM_API_SECRET.into());
|
||||
let scrobble = scrobble_from_path(db, track)?;
|
||||
|
@ -93,7 +98,8 @@ pub fn scrobble<T>(db: &T, username: &str, track: &Path) -> Result<(), errors::E
|
|||
}
|
||||
|
||||
pub fn now_playing<T>(db: &T, username: &str, track: &Path) -> Result<(), errors::Error>
|
||||
where T: ConnectionSource + VFSSource
|
||||
where
|
||||
T: ConnectionSource + VFSSource,
|
||||
{
|
||||
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY.into(), LASTFM_API_SECRET.into());
|
||||
let scrobble = scrobble_from_path(db, track)?;
|
||||
|
@ -103,14 +109,15 @@ pub fn now_playing<T>(db: &T, username: &str, track: &Path) -> Result<(), errors
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn api_request(method: &str, params: &HashMap<String, String>) -> Result<reqwest::Response, reqwest::Error>
|
||||
{
|
||||
fn api_request(
|
||||
method: &str,
|
||||
params: &HashMap<String, String>,
|
||||
) -> Result<reqwest::Response, reqwest::Error> {
|
||||
let mut url = LASTFM_API_ROOT.to_string();
|
||||
url.push_str("?");
|
||||
|
||||
url.push_str(&format!("method={}&", method));
|
||||
for (k, v) in params.iter()
|
||||
{
|
||||
for (k, v) in params.iter() {
|
||||
url.push_str(&format!("{}={}&", k, v));
|
||||
}
|
||||
let api_signature = get_signature(method, params);
|
||||
|
@ -121,21 +128,18 @@ fn api_request(method: &str, params: &HashMap<String, String>) -> Result<reqwest
|
|||
request.send()
|
||||
}
|
||||
|
||||
fn get_signature(method: &str, params: &HashMap<String, String>) -> String
|
||||
{
|
||||
fn get_signature(method: &str, params: &HashMap<String, String>) -> String {
|
||||
let mut signature_data = params.clone();
|
||||
signature_data.insert("method".to_string(), method.to_string());
|
||||
|
||||
let mut param_names = Vec::new();
|
||||
for param_name in signature_data.keys()
|
||||
{
|
||||
for param_name in signature_data.keys() {
|
||||
param_names.push(param_name);
|
||||
}
|
||||
param_names.sort();
|
||||
|
||||
let mut signature = String::new();
|
||||
for param_name in param_names
|
||||
{
|
||||
for param_name in param_names {
|
||||
signature.push_str((param_name.to_string() + signature_data[param_name].as_str()).as_str())
|
||||
}
|
||||
|
||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -23,8 +23,8 @@ extern crate mount;
|
|||
extern crate mp3_duration;
|
||||
extern crate params;
|
||||
extern crate rand;
|
||||
extern crate reqwest;
|
||||
extern crate regex;
|
||||
extern crate reqwest;
|
||||
extern crate ring;
|
||||
extern crate router;
|
||||
extern crate rustfm_scrobble;
|
||||
|
@ -50,25 +50,25 @@ extern crate winapi;
|
|||
#[cfg(unix)]
|
||||
extern crate unix_daemonize;
|
||||
|
||||
#[cfg(unix)]
|
||||
use unix_daemonize::{daemonize_redirect, ChdirMode};
|
||||
#[cfg(unix)]
|
||||
use std::fs::File;
|
||||
#[cfg(unix)]
|
||||
use std::io::prelude::*;
|
||||
#[cfg(unix)]
|
||||
use unix_daemonize::{daemonize_redirect, ChdirMode};
|
||||
|
||||
use core::ops::Deref;
|
||||
use errors::*;
|
||||
use getopts::Options;
|
||||
use iron::prelude::*;
|
||||
use mount::Mount;
|
||||
use staticfile::Static;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::channel;
|
||||
use simplelog::{TermLogger, LogLevelFilter};
|
||||
#[cfg(unix)]
|
||||
use simplelog::SimpleLogger;
|
||||
use simplelog::{LogLevelFilter, TermLogger};
|
||||
use staticfile::Static;
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
mod api;
|
||||
mod config;
|
||||
|
@ -79,11 +79,11 @@ mod index;
|
|||
mod lastfm;
|
||||
mod metadata;
|
||||
mod playlist;
|
||||
mod serve;
|
||||
mod thumbnails;
|
||||
mod ui;
|
||||
mod user;
|
||||
mod utils;
|
||||
mod serve;
|
||||
mod thumbnails;
|
||||
mod vfs;
|
||||
|
||||
static LOG_CONFIG: simplelog::Config = simplelog::Config {
|
||||
|
@ -148,7 +148,6 @@ fn init_log(log_level: LogLevelFilter, _: &getopts::Matches) -> Result<()> {
|
|||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
|
||||
// Parse CLI options
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let mut options = Options::new();
|
||||
|
@ -156,15 +155,19 @@ fn run() -> Result<()> {
|
|||
options.optopt("p", "port", "set polaris to run on a custom port", "PORT");
|
||||
options.optopt("d", "database", "set the path to index database", "FILE");
|
||||
options.optopt("w", "web", "set the path to web client files", "DIRECTORY");
|
||||
options.optopt("l",
|
||||
"log",
|
||||
"set the log level to a value between 0 (off) and 3 (debug)",
|
||||
"LEVEL");
|
||||
options.optopt(
|
||||
"l",
|
||||
"log",
|
||||
"set the log level to a value between 0 (off) and 3 (debug)",
|
||||
"LEVEL",
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
options.optflag("f",
|
||||
"foreground",
|
||||
"run polaris in the foreground instead of daemonizing");
|
||||
options.optflag(
|
||||
"f",
|
||||
"foreground",
|
||||
"run polaris in the foreground instead of daemonizing",
|
||||
);
|
||||
|
||||
options.optflag("h", "help", "print this help menu");
|
||||
|
||||
|
@ -214,14 +217,16 @@ fn run() -> Result<()> {
|
|||
let index_sender = Arc::new(Mutex::new(index_sender));
|
||||
let db_ref = db.clone();
|
||||
std::thread::spawn(move || {
|
||||
let db = db_ref.deref();
|
||||
index::update_loop(db, index_receiver);
|
||||
});
|
||||
let db = db_ref.deref();
|
||||
index::update_loop(db, index_receiver);
|
||||
});
|
||||
|
||||
// Trigger auto-indexing
|
||||
let db_ref = db.clone();
|
||||
let sender_ref = index_sender.clone();
|
||||
std::thread::spawn(move || { index::self_trigger(db_ref.deref(), sender_ref); });
|
||||
std::thread::spawn(move || {
|
||||
index::self_trigger(db_ref.deref(), sender_ref);
|
||||
});
|
||||
|
||||
// Mount API
|
||||
let prefix_url = config.prefix_url.unwrap_or("".to_string());
|
||||
|
@ -258,7 +263,9 @@ fn run() -> Result<()> {
|
|||
|
||||
// Start DDNS updates
|
||||
let db_ref = db.clone();
|
||||
std::thread::spawn(move || { ddns::run(db_ref.deref()); });
|
||||
std::thread::spawn(move || {
|
||||
ddns::run(db_ref.deref());
|
||||
});
|
||||
|
||||
// Run UI
|
||||
ui::run();
|
||||
|
|
|
@ -45,21 +45,22 @@ fn read_id3(path: &Path) -> Result<SongTags> {
|
|||
let title = tag.title().map(|s| s.to_string());
|
||||
let disc_number = tag.disc();
|
||||
let track_number = tag.track();
|
||||
let year = tag.year()
|
||||
let year = tag
|
||||
.year()
|
||||
.map(|y| y as i32)
|
||||
.or(tag.date_released().and_then(|d| Some(d.year)))
|
||||
.or(tag.date_recorded().and_then(|d| Some(d.year)));
|
||||
|
||||
Ok(SongTags {
|
||||
artist: artist,
|
||||
album_artist: album_artist,
|
||||
album: album,
|
||||
title: title,
|
||||
duration: duration,
|
||||
disc_number: disc_number,
|
||||
track_number: track_number,
|
||||
year: year,
|
||||
})
|
||||
artist: artist,
|
||||
album_artist: album_artist,
|
||||
album: album,
|
||||
title: title,
|
||||
duration: duration,
|
||||
disc_number: disc_number,
|
||||
track_number: track_number,
|
||||
year: year,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_ape_string(item: &ape::Item) -> Option<String> {
|
||||
|
@ -100,19 +101,18 @@ fn read_ape(path: &Path) -> Result<SongTags> {
|
|||
let disc_number = tag.item("Disc").and_then(read_ape_x_of_y);
|
||||
let track_number = tag.item("Track").and_then(read_ape_x_of_y);
|
||||
Ok(SongTags {
|
||||
artist: artist,
|
||||
album_artist: album_artist,
|
||||
album: album,
|
||||
title: title,
|
||||
duration: None,
|
||||
disc_number: disc_number,
|
||||
track_number: track_number,
|
||||
year: year,
|
||||
})
|
||||
artist: artist,
|
||||
album_artist: album_artist,
|
||||
album: album,
|
||||
title: title,
|
||||
duration: None,
|
||||
disc_number: disc_number,
|
||||
track_number: track_number,
|
||||
year: year,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_vorbis(path: &Path) -> Result<SongTags> {
|
||||
|
||||
let file = fs::File::open(path)?;
|
||||
let source = OggStreamReader::new(file)?;
|
||||
|
||||
|
@ -159,15 +159,15 @@ fn read_flac(path: &Path) -> Result<SongTags> {
|
|||
};
|
||||
|
||||
Ok(SongTags {
|
||||
artist: vorbis.artist().map(|v| v[0].clone()),
|
||||
album_artist: vorbis.album_artist().map(|v| v[0].clone()),
|
||||
album: vorbis.album().map(|v| v[0].clone()),
|
||||
title: vorbis.title().map(|v| v[0].clone()),
|
||||
duration: duration,
|
||||
disc_number: disc_number,
|
||||
track_number: vorbis.track(),
|
||||
year: year,
|
||||
})
|
||||
artist: vorbis.artist().map(|v| v[0].clone()),
|
||||
album_artist: vorbis.album_artist().map(|v| v[0].clone()),
|
||||
album: vorbis.album().map(|v| v[0].clone()),
|
||||
title: vorbis.title().map(|v| v[0].clone()),
|
||||
duration: duration,
|
||||
disc_number: disc_number,
|
||||
track_number: vorbis.track(),
|
||||
year: year,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -192,6 +192,8 @@ fn test_read_metadata() {
|
|||
};
|
||||
assert_eq!(read(Path::new("test/sample.mp3")).unwrap(), mp3_sample_tag);
|
||||
assert_eq!(read(Path::new("test/sample.ogg")).unwrap(), sample_tags);
|
||||
assert_eq!(read(Path::new("test/sample.flac")).unwrap(),
|
||||
flac_sample_tag);
|
||||
assert_eq!(
|
||||
read(Path::new("test/sample.flac")).unwrap(),
|
||||
flac_sample_tag
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use diesel::prelude::*;
|
|||
use diesel::BelongingToDsl;
|
||||
use diesel::types::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(test)]
|
||||
use db;
|
||||
use db::ConnectionSource;
|
||||
|
|
59
src/serve.rs
59
src/serve.rs
|
@ -1,19 +1,19 @@
|
|||
use std::cmp;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
use iron::headers::{AcceptRanges, ByteRangeSpec, ContentLength, ContentRange, ContentRangeSpec,
|
||||
Range, RangeUnit};
|
||||
use iron::headers::{
|
||||
AcceptRanges, ByteRangeSpec, ContentLength, ContentRange, ContentRangeSpec, Range, RangeUnit,
|
||||
};
|
||||
use iron::modifier::Modifier;
|
||||
use iron::modifiers::Header;
|
||||
use iron::prelude::*;
|
||||
use iron::response::WriteBody;
|
||||
use iron::status::{self, Status};
|
||||
use std::cmp;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use errors::{Error, ErrorKind};
|
||||
|
||||
pub fn deliver(path: &Path, range_header: Option<&Range>) -> IronResult<Response> {
|
||||
|
||||
match fs::metadata(path) {
|
||||
Ok(meta) => meta,
|
||||
Err(e) => {
|
||||
|
@ -31,18 +31,20 @@ pub fn deliver(path: &Path, range_header: Option<&Range>) -> IronResult<Response
|
|||
|
||||
match range_header {
|
||||
None => Ok(Response::with((status::Ok, path, accept_range_header))),
|
||||
Some(range) => {
|
||||
match range {
|
||||
Range::Bytes(vec_range) => {
|
||||
if let Ok(partial_file) = PartialFile::from_path(path, vec_range) {
|
||||
Ok(Response::with((status::Ok, partial_file, accept_range_header)))
|
||||
} else {
|
||||
Err(Error::from(ErrorKind::FileNotFound).into())
|
||||
}
|
||||
Some(range) => match range {
|
||||
Range::Bytes(vec_range) => {
|
||||
if let Ok(partial_file) = PartialFile::from_path(path, vec_range) {
|
||||
Ok(Response::with((
|
||||
status::Ok,
|
||||
partial_file,
|
||||
accept_range_header,
|
||||
)))
|
||||
} else {
|
||||
Err(Error::from(ErrorKind::FileNotFound).into())
|
||||
}
|
||||
_ => Ok(Response::with(status::RangeNotSatisfiable)),
|
||||
}
|
||||
}
|
||||
_ => Ok(Response::with(status::RangeNotSatisfiable)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +80,8 @@ impl From<Vec<ByteRangeSpec>> for PartialFileRange {
|
|||
|
||||
impl PartialFile {
|
||||
pub fn new<Range>(file: File, range: Range) -> PartialFile
|
||||
where Range: Into<PartialFileRange>
|
||||
where
|
||||
Range: Into<PartialFileRange>,
|
||||
{
|
||||
let range = range.into();
|
||||
PartialFile {
|
||||
|
@ -88,14 +91,14 @@ impl PartialFile {
|
|||
}
|
||||
|
||||
pub fn from_path<P: AsRef<Path>, Range>(path: P, range: Range) -> Result<PartialFile, io::Error>
|
||||
where Range: Into<PartialFileRange>
|
||||
where
|
||||
Range: Into<PartialFileRange>,
|
||||
{
|
||||
let file = File::open(path.as_ref())?;
|
||||
Ok(Self::new(file, range))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Modifier<Response> for PartialFile {
|
||||
fn modify(self, res: &mut Response) {
|
||||
use self::PartialFileRange::*;
|
||||
|
@ -124,13 +127,12 @@ impl Modifier<Response> for PartialFile {
|
|||
}
|
||||
}
|
||||
(_, None) => None,
|
||||
|
||||
};
|
||||
if let Some(range) = range {
|
||||
let content_range = ContentRange(ContentRangeSpec::Bytes {
|
||||
range: Some(range),
|
||||
instance_length: file_length,
|
||||
});
|
||||
range: Some(range),
|
||||
instance_length: file_length,
|
||||
});
|
||||
let content_len = range.1 - range.0 + 1;
|
||||
res.headers.set(ContentLength(content_len));
|
||||
res.headers.set(content_range);
|
||||
|
@ -143,11 +145,10 @@ impl Modifier<Response> for PartialFile {
|
|||
res.body = Some(Box::new(partial_content));
|
||||
} else {
|
||||
if let Some(file_length) = file_length {
|
||||
res.headers
|
||||
.set(ContentRange(ContentRangeSpec::Bytes {
|
||||
range: None,
|
||||
instance_length: Some(file_length),
|
||||
}));
|
||||
res.headers.set(ContentRange(ContentRangeSpec::Bytes {
|
||||
range: None,
|
||||
instance_length: Some(file_length),
|
||||
}));
|
||||
};
|
||||
res.status = Some(Status::RangeNotSatisfiable);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use image;
|
||||
use image::FilterType;
|
||||
use image::GenericImage;
|
||||
use image::ImageBuffer;
|
||||
use image::ImageFormat;
|
||||
use image::FilterType;
|
||||
use std::cmp;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs::{DirBuilder, File};
|
||||
|
@ -23,7 +23,6 @@ fn hash(path: &Path, dimension: u32) -> u64 {
|
|||
}
|
||||
|
||||
pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf> {
|
||||
|
||||
let mut out_path = utils::get_data_root()?;
|
||||
out_path.push(THUMBNAILS_PATH);
|
||||
|
||||
|
@ -46,9 +45,11 @@ pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf> {
|
|||
source_image.resize(out_dimension, out_dimension, FilterType::Lanczos3);
|
||||
let (scaled_width, scaled_height) = scaled_image.dimensions();
|
||||
let mut final_image = ImageBuffer::new(out_dimension, out_dimension);
|
||||
final_image.copy_from(&scaled_image,
|
||||
(out_dimension - scaled_width) / 2,
|
||||
(out_dimension - scaled_height) / 2);
|
||||
final_image.copy_from(
|
||||
&scaled_image,
|
||||
(out_dimension - scaled_width) / 2,
|
||||
(out_dimension - scaled_height) / 2,
|
||||
);
|
||||
final_image.save(&out_path)?;
|
||||
} else {
|
||||
let mut out_file = File::create(&out_path)?;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::time;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
pub fn run() {
|
||||
info!("Starting up UI (headless)");
|
||||
|
|
|
@ -3,9 +3,9 @@ use std::ffi::OsStr;
|
|||
use std::os::windows::ffi::OsStrExt;
|
||||
use uuid;
|
||||
use winapi;
|
||||
use winapi::um::{shellapi, winuser};
|
||||
use winapi::shared::minwindef::{DWORD, LOWORD, UINT, WPARAM, LPARAM, LRESULT};
|
||||
use winapi::shared::minwindef::{DWORD, LOWORD, LPARAM, LRESULT, UINT, WPARAM};
|
||||
use winapi::shared::windef::HWND;
|
||||
use winapi::um::{shellapi, winuser};
|
||||
|
||||
const IDI_POLARIS_TRAY: isize = 0x102;
|
||||
const UID_NOTIFICATION_ICON: u32 = 0;
|
||||
|
@ -33,12 +33,15 @@ impl ToWin for uuid::Uuid {
|
|||
|
||||
fn to_win(&self) -> Self::Out {
|
||||
let bytes = self.as_bytes();
|
||||
let end = [bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
|
||||
bytes[15]];
|
||||
let end = [
|
||||
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
|
||||
];
|
||||
|
||||
winapi::shared::guiddef::GUID {
|
||||
Data1: ((bytes[0] as u32) << 24 | (bytes[1] as u32) << 16 | (bytes[2] as u32) << 8 |
|
||||
(bytes[3] as u32)),
|
||||
Data1: ((bytes[0] as u32) << 24
|
||||
| (bytes[1] as u32) << 16
|
||||
| (bytes[2] as u32) << 8
|
||||
| (bytes[3] as u32)),
|
||||
Data2: ((bytes[4] as u16) << 8 | (bytes[5] as u16)),
|
||||
Data3: ((bytes[6] as u16) << 8 | (bytes[7] as u16)),
|
||||
Data4: end,
|
||||
|
@ -55,7 +58,6 @@ impl Constructible for shellapi::NOTIFYICONDATAW {
|
|||
type Out = shellapi::NOTIFYICONDATAW;
|
||||
|
||||
fn new() -> Self::Out {
|
||||
|
||||
let mut version_union: shellapi::NOTIFYICONDATAW_u = unsafe { std::mem::zeroed() };
|
||||
unsafe {
|
||||
let version = version_union.uVersion_mut();
|
||||
|
@ -83,7 +85,6 @@ impl Constructible for shellapi::NOTIFYICONDATAW {
|
|||
}
|
||||
|
||||
fn create_window() -> Option<HWND> {
|
||||
|
||||
let class_name = "Polaris-class".to_win();
|
||||
let window_name = "Polaris-window".to_win();
|
||||
|
||||
|
@ -107,18 +108,20 @@ fn create_window() -> Option<HWND> {
|
|||
return None;
|
||||
}
|
||||
|
||||
let window_handle = winuser::CreateWindowExW(0,
|
||||
atom as winapi::shared::ntdef::LPCWSTR,
|
||||
window_name.as_ptr(),
|
||||
winuser::WS_DISABLED,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
winuser::GetDesktopWindow(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut());
|
||||
let window_handle = winuser::CreateWindowExW(
|
||||
0,
|
||||
atom as winapi::shared::ntdef::LPCWSTR,
|
||||
window_name.as_ptr(),
|
||||
winuser::WS_DISABLED,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
winuser::GetDesktopWindow(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
|
||||
if window_handle.is_null() {
|
||||
return None;
|
||||
|
@ -129,7 +132,6 @@ fn create_window() -> Option<HWND> {
|
|||
}
|
||||
|
||||
fn add_notification_icon(window: HWND) {
|
||||
|
||||
let mut tooltip = [0 as winapi::um::winnt::WCHAR; 128];
|
||||
for (&x, p) in "Polaris".to_win().iter().zip(tooltip.iter_mut()) {
|
||||
*p = x;
|
||||
|
@ -173,24 +175,28 @@ fn open_notification_context_menu(window: HWND) {
|
|||
if context_menu.is_null() {
|
||||
return;
|
||||
}
|
||||
winuser::InsertMenuW(context_menu,
|
||||
0,
|
||||
winuser::MF_STRING,
|
||||
MESSAGE_NOTIFICATION_ICON_QUIT as usize,
|
||||
quit_string.as_ptr());
|
||||
winuser::InsertMenuW(
|
||||
context_menu,
|
||||
0,
|
||||
winuser::MF_STRING,
|
||||
MESSAGE_NOTIFICATION_ICON_QUIT as usize,
|
||||
quit_string.as_ptr(),
|
||||
);
|
||||
|
||||
let mut cursor_position = winapi::shared::windef::POINT { x: 0, y: 0 };
|
||||
winuser::GetCursorPos(&mut cursor_position);
|
||||
|
||||
winuser::SetForegroundWindow(window);
|
||||
let flags = winuser::TPM_RIGHTALIGN | winuser::TPM_BOTTOMALIGN | winuser::TPM_RIGHTBUTTON;
|
||||
winuser::TrackPopupMenu(context_menu,
|
||||
flags,
|
||||
cursor_position.x,
|
||||
cursor_position.y,
|
||||
0,
|
||||
window,
|
||||
std::ptr::null_mut());
|
||||
winuser::TrackPopupMenu(
|
||||
context_menu,
|
||||
flags,
|
||||
cursor_position.x,
|
||||
cursor_position.y,
|
||||
0,
|
||||
window,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
winuser::PostMessageW(window, 0, 0, 0);
|
||||
|
||||
info!("Closing notification context menu");
|
||||
|
@ -224,8 +230,10 @@ pub fn run() {
|
|||
unsafe {
|
||||
status = winuser::GetMessageW(&mut message, std::ptr::null_mut(), 0, 0);
|
||||
if status == -1 {
|
||||
panic!("GetMessageW error: {}",
|
||||
winapi::um::errhandlingapi::GetLastError());
|
||||
panic!(
|
||||
"GetMessageW error: {}",
|
||||
winapi::um::errhandlingapi::GetLastError()
|
||||
);
|
||||
}
|
||||
if status == 0 {
|
||||
break;
|
||||
|
@ -236,34 +244,30 @@ pub fn run() {
|
|||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn window_proc(window: HWND,
|
||||
msg: UINT,
|
||||
w_param: WPARAM,
|
||||
l_param: LPARAM)
|
||||
-> LRESULT {
|
||||
pub unsafe extern "system" fn window_proc(
|
||||
window: HWND,
|
||||
msg: UINT,
|
||||
w_param: WPARAM,
|
||||
l_param: LPARAM,
|
||||
) -> LRESULT {
|
||||
match msg {
|
||||
|
||||
winuser::WM_CREATE => {
|
||||
add_notification_icon(window);
|
||||
}
|
||||
|
||||
MESSAGE_NOTIFICATION_ICON => {
|
||||
match LOWORD(l_param as DWORD) as u32 {
|
||||
winuser::WM_RBUTTONUP => {
|
||||
open_notification_context_menu(window);
|
||||
}
|
||||
_ => (),
|
||||
MESSAGE_NOTIFICATION_ICON => match LOWORD(l_param as DWORD) as u32 {
|
||||
winuser::WM_RBUTTONUP => {
|
||||
open_notification_context_menu(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
|
||||
winuser::WM_COMMAND => {
|
||||
match LOWORD(w_param as DWORD) as u32 {
|
||||
MESSAGE_NOTIFICATION_ICON_QUIT => {
|
||||
quit(window);
|
||||
}
|
||||
_ => (),
|
||||
winuser::WM_COMMAND => match LOWORD(w_param as DWORD) as u32 {
|
||||
MESSAGE_NOTIFICATION_ICON_QUIT => {
|
||||
quit(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
|
||||
winuser::WM_DESTROY => {
|
||||
remove_notification_icon(window);
|
||||
|
|
60
src/user.rs
60
src/user.rs
|
@ -4,12 +4,12 @@ use diesel::prelude::*;
|
|||
use rand;
|
||||
use ring::{digest, pbkdf2};
|
||||
|
||||
use db::ConnectionSource;
|
||||
use db::users;
|
||||
use db::ConnectionSource;
|
||||
use errors::*;
|
||||
|
||||
#[derive(Debug, Insertable, Queryable)]
|
||||
#[table_name="users"]
|
||||
#[table_name = "users"]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
pub password_salt: Vec<u8>,
|
||||
|
@ -37,35 +37,41 @@ impl User {
|
|||
|
||||
pub fn hash_password(salt: &Vec<u8>, password: &str) -> Vec<u8> {
|
||||
let mut hash: PasswordHash = [0; CREDENTIAL_LEN];
|
||||
pbkdf2::derive(DIGEST_ALG,
|
||||
HASH_ITERATIONS,
|
||||
salt,
|
||||
password.as_bytes(),
|
||||
&mut hash);
|
||||
pbkdf2::derive(
|
||||
DIGEST_ALG,
|
||||
HASH_ITERATIONS,
|
||||
salt,
|
||||
password.as_bytes(),
|
||||
&mut hash,
|
||||
);
|
||||
hash.to_vec()
|
||||
}
|
||||
|
||||
fn verify_password(password_hash: &Vec<u8>,
|
||||
password_salt: &Vec<u8>,
|
||||
attempted_password: &str)
|
||||
-> bool {
|
||||
pbkdf2::verify(DIGEST_ALG,
|
||||
HASH_ITERATIONS,
|
||||
password_salt,
|
||||
attempted_password.as_bytes(),
|
||||
password_hash)
|
||||
.is_ok()
|
||||
fn verify_password(
|
||||
password_hash: &Vec<u8>,
|
||||
password_salt: &Vec<u8>,
|
||||
attempted_password: &str,
|
||||
) -> bool {
|
||||
pbkdf2::verify(
|
||||
DIGEST_ALG,
|
||||
HASH_ITERATIONS,
|
||||
password_salt,
|
||||
attempted_password.as_bytes(),
|
||||
password_hash,
|
||||
).is_ok()
|
||||
}
|
||||
|
||||
pub fn auth<T>(db: &T, username: &str, password: &str) -> Result<bool>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
use db::users::dsl::*;
|
||||
let connection = db.get_connection();
|
||||
match users
|
||||
.select((password_hash, password_salt))
|
||||
.filter(name.eq(username))
|
||||
.get_result(connection.deref()) {
|
||||
.select((password_hash, password_salt))
|
||||
.filter(name.eq(username))
|
||||
.get_result(connection.deref())
|
||||
{
|
||||
Err(diesel::result::Error::NotFound) => Ok(false),
|
||||
Ok((hash, salt)) => Ok(verify_password(&hash, &salt, password)),
|
||||
Err(e) => Err(e.into()),
|
||||
|
@ -73,7 +79,8 @@ pub fn auth<T>(db: &T, username: &str, password: &str) -> Result<bool>
|
|||
}
|
||||
|
||||
pub fn count<T>(db: &T) -> Result<i64>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
use db::users::dsl::*;
|
||||
let connection = db.get_connection();
|
||||
|
@ -82,7 +89,8 @@ pub fn count<T>(db: &T) -> Result<i64>
|
|||
}
|
||||
|
||||
pub fn is_admin<T>(db: &T, username: &str) -> Result<bool>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
use db::users::dsl::*;
|
||||
let connection = db.get_connection();
|
||||
|
@ -94,7 +102,8 @@ pub fn is_admin<T>(db: &T, username: &str) -> Result<bool>
|
|||
}
|
||||
|
||||
pub fn set_lastfm_session_key<T>(db: &T, username: &str, token: &str) -> Result<()>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
use db::users::dsl::*;
|
||||
let connection = db.get_connection();
|
||||
|
@ -105,7 +114,8 @@ pub fn set_lastfm_session_key<T>(db: &T, username: &str, token: &str) -> Result<
|
|||
}
|
||||
|
||||
pub fn get_lastfm_session_key<T>(db: &T, username: &str) -> Result<String>
|
||||
where T: ConnectionSource
|
||||
where
|
||||
T: ConnectionSource,
|
||||
{
|
||||
use db::users::dsl::*;
|
||||
let connection = db.get_connection();
|
||||
|
|
19
src/utils.rs
19
src/utils.rs
|
@ -1,6 +1,6 @@
|
|||
use app_dirs::{AppDataType, AppInfo, app_root};
|
||||
use std::path::{Path, PathBuf};
|
||||
use app_dirs::{app_root, AppDataType, AppInfo};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use errors::*;
|
||||
|
||||
|
@ -18,8 +18,7 @@ const APP_INFO: AppInfo = AppInfo {
|
|||
|
||||
pub fn get_data_root() -> Result<PathBuf> {
|
||||
if let Ok(root) = app_root(AppDataType::UserData, &APP_INFO) {
|
||||
fs::create_dir_all(&root)
|
||||
.chain_err(|| format!("opening user data: {}", root.display()))?;
|
||||
fs::create_dir_all(&root).chain_err(|| format!("opening user data: {}", root.display()))?;
|
||||
return Ok(root);
|
||||
}
|
||||
bail!("Could not retrieve data directory root");
|
||||
|
@ -55,10 +54,14 @@ pub fn get_audio_format(path: &Path) -> Option<AudioFormat> {
|
|||
|
||||
#[test]
|
||||
fn test_get_audio_format() {
|
||||
assert_eq!(get_audio_format(Path::new("animals/🐷/my🐖file.jpg")),
|
||||
None);
|
||||
assert_eq!(get_audio_format(Path::new("animals/🐷/my🐖file.flac")),
|
||||
Some(AudioFormat::FLAC));
|
||||
assert_eq!(
|
||||
get_audio_format(Path::new("animals/🐷/my🐖file.jpg")),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
get_audio_format(Path::new("animals/🐷/my🐖file.flac")),
|
||||
Some(AudioFormat::FLAC)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn is_song(path: &Path) -> bool {
|
||||
|
|
26
src/vfs.rs
26
src/vfs.rs
|
@ -1,11 +1,11 @@
|
|||
use core::ops::Deref;
|
||||
use diesel::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use db::{ConnectionSource, DB};
|
||||
use db::mount_points;
|
||||
use db::{ConnectionSource, DB};
|
||||
use errors::*;
|
||||
|
||||
pub trait VFSSource {
|
||||
|
@ -28,7 +28,7 @@ impl VFSSource for DB {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Insertable, PartialEq, Queryable, Serialize)]
|
||||
#[table_name="mount_points"]
|
||||
#[table_name = "mount_points"]
|
||||
pub struct MountPoint {
|
||||
pub source: String,
|
||||
pub name: String,
|
||||
|
@ -40,7 +40,9 @@ pub struct VFS {
|
|||
|
||||
impl VFS {
|
||||
pub fn new() -> VFS {
|
||||
VFS { mount_points: HashMap::new() }
|
||||
VFS {
|
||||
mount_points: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mount(&mut self, real_path: &Path, name: &str) -> Result<()> {
|
||||
|
@ -55,10 +57,10 @@ impl VFS {
|
|||
Ok(p) => {
|
||||
let mount_path = Path::new(&name);
|
||||
return if p.components().count() == 0 {
|
||||
Ok(mount_path.to_path_buf())
|
||||
} else {
|
||||
Ok(mount_path.join(p))
|
||||
};
|
||||
Ok(mount_path.to_path_buf())
|
||||
} else {
|
||||
Ok(mount_path.join(p))
|
||||
};
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
|
@ -72,10 +74,10 @@ impl VFS {
|
|||
match virtual_path.strip_prefix(mount_path) {
|
||||
Ok(p) => {
|
||||
return if p.components().count() == 0 {
|
||||
Ok(target.clone())
|
||||
} else {
|
||||
Ok(target.join(p))
|
||||
};
|
||||
Ok(target.clone())
|
||||
} else {
|
||||
Ok(target.join(p))
|
||||
};
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue