This commit is contained in:
Antoine Gersant 2018-10-06 15:46:30 -07:00
parent 42140d3137
commit 0297b351bf
17 changed files with 592 additions and 530 deletions

View file

@ -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()),

View file

@ -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();

View file

@ -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),
}
}

View file

@ -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) {

View file

@ -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 {

View file

@ -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]

View file

@ -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())
}

View file

@ -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();

View file

@ -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
);
}

View file

@ -5,6 +5,7 @@ use diesel::prelude::*;
use diesel::BelongingToDsl;
use diesel::types::*;
use std::path::Path;
#[cfg(test)]
use db;
use db::ConnectionSource;

View file

@ -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);
}

View file

@ -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)?;

View file

@ -1,5 +1,5 @@
use std::time;
use std::thread;
use std::time;
pub fn run() {
info!("Starting up UI (headless)");

View file

@ -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);

View file

@ -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();

View file

@ -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 {

View file

@ -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(_) => (),
}