commit
4d13c96dcc
20 changed files with 1080 additions and 935 deletions
1740
Cargo.lock
generated
1740
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
20
Cargo.toml
20
Cargo.toml
|
@ -11,28 +11,28 @@ ui = []
|
||||||
ape = "0.2.0"
|
ape = "0.2.0"
|
||||||
app_dirs = "1.1.1"
|
app_dirs = "1.1.1"
|
||||||
base64 = "0.10.0"
|
base64 = "0.10.0"
|
||||||
diesel = { version = "1.3.3", features = ["sqlite"] }
|
diesel = { version = "1.4", features = ["sqlite"] }
|
||||||
diesel_migrations = { version = "1.3.0", features = ["sqlite"] }
|
diesel_migrations = { version = "1.4", features = ["sqlite"] }
|
||||||
error-chain = "0.12.0"
|
error-chain = "0.12.0"
|
||||||
getopts = "0.2.15"
|
getopts = "0.2.15"
|
||||||
id3 = "0.2.3"
|
id3 = "0.3"
|
||||||
image = "0.20.0"
|
image = "0.22"
|
||||||
rustfm-scrobble = { git = "https://github.com/agersant/rustfm-scrobble" }
|
rustfm-scrobble = { git = "https://github.com/agersant/rustfm-scrobble" }
|
||||||
lewton = "0.9.1"
|
lewton = "0.9.1"
|
||||||
log = "0.4.5"
|
log = "0.4.5"
|
||||||
metaflac = "0.1.8"
|
metaflac = "0.1.8"
|
||||||
mp3-duration = "0.1.0"
|
mp3-duration = "0.1.0"
|
||||||
rand = "0.5.5"
|
rand = "0.7"
|
||||||
regex = "1.0.5"
|
regex = "1.2"
|
||||||
ring = "0.13.5"
|
ring = "0.13.5"
|
||||||
reqwest = "0.9.2"
|
reqwest = "0.9.2"
|
||||||
rocket = "0.4.0"
|
rocket = "0.4.2"
|
||||||
rust-crypto = "0.2.36"
|
rust-crypto = "0.2.36"
|
||||||
serde = "1.0"
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
simplelog = "0.5.2"
|
simplelog = "0.6"
|
||||||
toml = "0.4.5"
|
toml = "0.5"
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_contrib]
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
73
src/api.rs
73
src/api.rs
|
@ -1,8 +1,10 @@
|
||||||
|
use error_chain::bail;
|
||||||
use rocket::http::{Cookie, Cookies, RawStr, Status};
|
use rocket::http::{Cookie, Cookies, RawStr, Status};
|
||||||
use rocket::request::{self, FromParam, FromRequest, Request};
|
use rocket::request::{self, FromParam, FromRequest, Request};
|
||||||
use rocket::response::content::Html;
|
use rocket::response::content::Html;
|
||||||
use rocket::{Outcome, State};
|
use rocket::{delete, get, post, put, routes, Outcome, State};
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -71,7 +73,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Auth {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
|
||||||
let mut cookies = request.guard::<Cookies>().unwrap();
|
let mut cookies = request.guard::<Cookies<'_>>().unwrap();
|
||||||
if let Some(u) = cookies.get_private(COOKIE_SESSION) {
|
if let Some(u) = cookies.get_private(COOKIE_SESSION) {
|
||||||
return Outcome::Success(Auth {
|
return Outcome::Success(Auth {
|
||||||
username: u.value().to_string(),
|
username: u.value().to_string(),
|
||||||
|
@ -85,7 +87,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Auth {
|
||||||
password: Some(password),
|
password: Some(password),
|
||||||
}) = Basic::from_str(auth_header_string.trim_start_matches("Basic "))
|
}) = Basic::from_str(auth_header_string.trim_start_matches("Basic "))
|
||||||
{
|
{
|
||||||
let db = match request.guard::<State<Arc<DB>>>() {
|
let db = match request.guard::<State<'_, Arc<DB>>>() {
|
||||||
Outcome::Success(d) => d,
|
Outcome::Success(d) => d,
|
||||||
_ => return Outcome::Failure((Status::InternalServerError, ())),
|
_ => return Outcome::Failure((Status::InternalServerError, ())),
|
||||||
};
|
};
|
||||||
|
@ -107,7 +109,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for AdminRights {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
|
||||||
let db = request.guard::<State<Arc<DB>>>()?;
|
let db = request.guard::<State<'_, Arc<DB>>>()?;
|
||||||
|
|
||||||
match user::count::<DB>(&db) {
|
match user::count::<DB>(&db) {
|
||||||
Err(_) => return Outcome::Failure((Status::InternalServerError, ())),
|
Err(_) => return Outcome::Failure((Status::InternalServerError, ())),
|
||||||
|
@ -166,7 +168,7 @@ pub struct InitialSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/initial_setup")]
|
#[get("/initial_setup")]
|
||||||
fn initial_setup(db: State<Arc<DB>>) -> Result<Json<InitialSetup>, errors::Error> {
|
fn initial_setup(db: State<'_, Arc<DB>>) -> Result<Json<InitialSetup>, errors::Error> {
|
||||||
let initial_setup = InitialSetup {
|
let initial_setup = InitialSetup {
|
||||||
has_any_users: user::count::<DB>(&db)? > 0,
|
has_any_users: user::count::<DB>(&db)? > 0,
|
||||||
};
|
};
|
||||||
|
@ -175,7 +177,7 @@ fn initial_setup(db: State<Arc<DB>>) -> Result<Json<InitialSetup>, errors::Error
|
||||||
|
|
||||||
#[get("/settings")]
|
#[get("/settings")]
|
||||||
fn get_settings(
|
fn get_settings(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
_admin_rights: AdminRights,
|
_admin_rights: AdminRights,
|
||||||
) -> Result<Json<Config>, errors::Error> {
|
) -> Result<Json<Config>, errors::Error> {
|
||||||
let config = config::read::<DB>(&db)?;
|
let config = config::read::<DB>(&db)?;
|
||||||
|
@ -184,7 +186,7 @@ fn get_settings(
|
||||||
|
|
||||||
#[put("/settings", data = "<config>")]
|
#[put("/settings", data = "<config>")]
|
||||||
fn put_settings(
|
fn put_settings(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
_admin_rights: AdminRights,
|
_admin_rights: AdminRights,
|
||||||
config: Json<Config>,
|
config: Json<Config>,
|
||||||
) -> Result<(), errors::Error> {
|
) -> Result<(), errors::Error> {
|
||||||
|
@ -193,14 +195,14 @@ fn put_settings(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/preferences")]
|
#[get("/preferences")]
|
||||||
fn get_preferences(db: State<Arc<DB>>, auth: Auth) -> Result<Json<Preferences>, errors::Error> {
|
fn get_preferences(db: State<'_, Arc<DB>>, auth: Auth) -> Result<Json<Preferences>, errors::Error> {
|
||||||
let preferences = config::read_preferences::<DB>(&db, &auth.username)?;
|
let preferences = config::read_preferences::<DB>(&db, &auth.username)?;
|
||||||
Ok(Json(preferences))
|
Ok(Json(preferences))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/preferences", data = "<preferences>")]
|
#[put("/preferences", data = "<preferences>")]
|
||||||
fn put_preferences(
|
fn put_preferences(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
preferences: Json<Preferences>,
|
preferences: Json<Preferences>,
|
||||||
) -> Result<(), errors::Error> {
|
) -> Result<(), errors::Error> {
|
||||||
|
@ -210,7 +212,7 @@ fn put_preferences(
|
||||||
|
|
||||||
#[post("/trigger_index")]
|
#[post("/trigger_index")]
|
||||||
fn trigger_index(
|
fn trigger_index(
|
||||||
command_sender: State<Arc<index::CommandSender>>,
|
command_sender: State<'_, Arc<index::CommandSender>>,
|
||||||
_admin_rights: AdminRights,
|
_admin_rights: AdminRights,
|
||||||
) -> Result<(), errors::Error> {
|
) -> Result<(), errors::Error> {
|
||||||
command_sender.trigger_reindex()?;
|
command_sender.trigger_reindex()?;
|
||||||
|
@ -230,9 +232,9 @@ struct AuthOutput {
|
||||||
|
|
||||||
#[post("/auth", data = "<credentials>")]
|
#[post("/auth", data = "<credentials>")]
|
||||||
fn auth(
|
fn auth(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
credentials: Json<AuthCredentials>,
|
credentials: Json<AuthCredentials>,
|
||||||
mut cookies: Cookies,
|
mut cookies: Cookies<'_>,
|
||||||
) -> Result<Json<AuthOutput>, errors::Error> {
|
) -> Result<Json<AuthOutput>, errors::Error> {
|
||||||
if !user::auth::<DB>(&db, &credentials.username, &credentials.password)? {
|
if !user::auth::<DB>(&db, &credentials.username, &credentials.password)? {
|
||||||
bail!(errors::ErrorKind::IncorrectCredentials)
|
bail!(errors::ErrorKind::IncorrectCredentials)
|
||||||
|
@ -248,7 +250,7 @@ fn auth(
|
||||||
|
|
||||||
#[get("/browse")]
|
#[get("/browse")]
|
||||||
fn browse_root(
|
fn browse_root(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
_auth: Auth,
|
_auth: Auth,
|
||||||
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
|
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
|
||||||
let result = index::browse(db.deref().deref(), &PathBuf::new())?;
|
let result = index::browse(db.deref().deref(), &PathBuf::new())?;
|
||||||
|
@ -257,7 +259,7 @@ fn browse_root(
|
||||||
|
|
||||||
#[get("/browse/<path>")]
|
#[get("/browse/<path>")]
|
||||||
fn browse(
|
fn browse(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
_auth: Auth,
|
_auth: Auth,
|
||||||
path: VFSPathBuf,
|
path: VFSPathBuf,
|
||||||
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
|
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
|
||||||
|
@ -266,14 +268,17 @@ fn browse(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/flatten")]
|
#[get("/flatten")]
|
||||||
fn flatten_root(db: State<Arc<DB>>, _auth: Auth) -> Result<Json<Vec<index::Song>>, errors::Error> {
|
fn flatten_root(
|
||||||
|
db: State<'_, Arc<DB>>,
|
||||||
|
_auth: Auth,
|
||||||
|
) -> Result<Json<Vec<index::Song>>, errors::Error> {
|
||||||
let result = index::flatten(db.deref().deref(), &PathBuf::new())?;
|
let result = index::flatten(db.deref().deref(), &PathBuf::new())?;
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/flatten/<path>")]
|
#[get("/flatten/<path>")]
|
||||||
fn flatten(
|
fn flatten(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
_auth: Auth,
|
_auth: Auth,
|
||||||
path: VFSPathBuf,
|
path: VFSPathBuf,
|
||||||
) -> Result<Json<Vec<index::Song>>, errors::Error> {
|
) -> Result<Json<Vec<index::Song>>, errors::Error> {
|
||||||
|
@ -282,20 +287,26 @@ fn flatten(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/random")]
|
#[get("/random")]
|
||||||
fn random(db: State<Arc<DB>>, _auth: Auth) -> Result<Json<Vec<index::Directory>>, errors::Error> {
|
fn random(
|
||||||
|
db: State<'_, Arc<DB>>,
|
||||||
|
_auth: Auth,
|
||||||
|
) -> Result<Json<Vec<index::Directory>>, errors::Error> {
|
||||||
let result = index::get_random_albums(db.deref().deref(), 20)?;
|
let result = index::get_random_albums(db.deref().deref(), 20)?;
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/recent")]
|
#[get("/recent")]
|
||||||
fn recent(db: State<Arc<DB>>, _auth: Auth) -> Result<Json<Vec<index::Directory>>, errors::Error> {
|
fn recent(
|
||||||
|
db: State<'_, Arc<DB>>,
|
||||||
|
_auth: Auth,
|
||||||
|
) -> Result<Json<Vec<index::Directory>>, errors::Error> {
|
||||||
let result = index::get_recent_albums(db.deref().deref(), 20)?;
|
let result = index::get_recent_albums(db.deref().deref(), 20)?;
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/search")]
|
#[get("/search")]
|
||||||
fn search_root(
|
fn search_root(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
_auth: Auth,
|
_auth: Auth,
|
||||||
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
|
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
|
||||||
let result = index::search(db.deref().deref(), "")?;
|
let result = index::search(db.deref().deref(), "")?;
|
||||||
|
@ -304,7 +315,7 @@ fn search_root(
|
||||||
|
|
||||||
#[get("/search/<query>")]
|
#[get("/search/<query>")]
|
||||||
fn search(
|
fn search(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
_auth: Auth,
|
_auth: Auth,
|
||||||
query: String,
|
query: String,
|
||||||
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
|
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
|
||||||
|
@ -314,7 +325,7 @@ fn search(
|
||||||
|
|
||||||
#[get("/serve/<path>")]
|
#[get("/serve/<path>")]
|
||||||
fn serve(
|
fn serve(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
_auth: Auth,
|
_auth: Auth,
|
||||||
path: VFSPathBuf,
|
path: VFSPathBuf,
|
||||||
) -> Result<serve::RangeResponder<File>, errors::Error> {
|
) -> Result<serve::RangeResponder<File>, errors::Error> {
|
||||||
|
@ -339,7 +350,7 @@ pub struct ListPlaylistsEntry {
|
||||||
|
|
||||||
#[get("/playlists")]
|
#[get("/playlists")]
|
||||||
fn list_playlists(
|
fn list_playlists(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
) -> Result<Json<Vec<ListPlaylistsEntry>>, errors::Error> {
|
) -> Result<Json<Vec<ListPlaylistsEntry>>, errors::Error> {
|
||||||
let playlist_names = playlist::list_playlists(&auth.username, db.deref().deref())?;
|
let playlist_names = playlist::list_playlists(&auth.username, db.deref().deref())?;
|
||||||
|
@ -358,7 +369,7 @@ pub struct SavePlaylistInput {
|
||||||
|
|
||||||
#[put("/playlist/<name>", data = "<playlist>")]
|
#[put("/playlist/<name>", data = "<playlist>")]
|
||||||
fn save_playlist(
|
fn save_playlist(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
name: String,
|
name: String,
|
||||||
playlist: Json<SavePlaylistInput>,
|
playlist: Json<SavePlaylistInput>,
|
||||||
|
@ -369,7 +380,7 @@ fn save_playlist(
|
||||||
|
|
||||||
#[get("/playlist/<name>")]
|
#[get("/playlist/<name>")]
|
||||||
fn read_playlist(
|
fn read_playlist(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
name: String,
|
name: String,
|
||||||
) -> Result<Json<Vec<index::Song>>, errors::Error> {
|
) -> Result<Json<Vec<index::Song>>, errors::Error> {
|
||||||
|
@ -378,14 +389,14 @@ fn read_playlist(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/playlist/<name>")]
|
#[delete("/playlist/<name>")]
|
||||||
fn delete_playlist(db: State<Arc<DB>>, auth: Auth, name: String) -> Result<(), errors::Error> {
|
fn delete_playlist(db: State<'_, Arc<DB>>, auth: Auth, name: String) -> Result<(), errors::Error> {
|
||||||
playlist::delete_playlist(&name, &auth.username, db.deref().deref())?;
|
playlist::delete_playlist(&name, &auth.username, db.deref().deref())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/lastfm/now_playing/<path>")]
|
#[put("/lastfm/now_playing/<path>")]
|
||||||
fn lastfm_now_playing(
|
fn lastfm_now_playing(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
path: VFSPathBuf,
|
path: VFSPathBuf,
|
||||||
) -> Result<(), errors::Error> {
|
) -> Result<(), errors::Error> {
|
||||||
|
@ -394,14 +405,18 @@ fn lastfm_now_playing(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/lastfm/scrobble/<path>")]
|
#[post("/lastfm/scrobble/<path>")]
|
||||||
fn lastfm_scrobble(db: State<Arc<DB>>, auth: Auth, path: VFSPathBuf) -> Result<(), errors::Error> {
|
fn lastfm_scrobble(
|
||||||
|
db: State<'_, Arc<DB>>,
|
||||||
|
auth: Auth,
|
||||||
|
path: VFSPathBuf,
|
||||||
|
) -> Result<(), errors::Error> {
|
||||||
lastfm::scrobble(db.deref().deref(), &auth.username, &path.into() as &PathBuf)?;
|
lastfm::scrobble(db.deref().deref(), &auth.username, &path.into() as &PathBuf)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/lastfm/link?<token>&<content>")]
|
#[get("/lastfm/link?<token>&<content>")]
|
||||||
fn lastfm_link(
|
fn lastfm_link(
|
||||||
db: State<Arc<DB>>,
|
db: State<'_, Arc<DB>>,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
token: String,
|
token: String,
|
||||||
content: String,
|
content: String,
|
||||||
|
@ -430,7 +445,7 @@ fn lastfm_link(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/lastfm/link")]
|
#[delete("/lastfm/link")]
|
||||||
fn lastfm_unlink(db: State<Arc<DB>>, auth: Auth) -> Result<(), errors::Error> {
|
fn lastfm_unlink(db: State<'_, Arc<DB>>, auth: Auth) -> Result<(), errors::Error> {
|
||||||
lastfm::unlink(db.deref().deref(), &auth.username)?;
|
lastfm::unlink(db.deref().deref(), &auth.username)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use diesel;
|
use diesel;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use error_chain::bail;
|
||||||
|
use log::info;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
|
@ -2,6 +2,8 @@ use core::ops::Deref;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
use diesel_migrations;
|
use diesel_migrations;
|
||||||
|
use error_chain::bail;
|
||||||
|
use log::info;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
@ -17,7 +19,7 @@ const DB_MIGRATIONS_PATH: &str = "migrations";
|
||||||
embed_migrations!("migrations");
|
embed_migrations!("migrations");
|
||||||
|
|
||||||
pub trait ConnectionSource {
|
pub trait ConnectionSource {
|
||||||
fn get_connection(&self) -> MutexGuard<SqliteConnection>;
|
fn get_connection(&self) -> MutexGuard<'_, SqliteConnection>;
|
||||||
fn get_connection_mutex(&self) -> Arc<Mutex<SqliteConnection>>;
|
fn get_connection_mutex(&self) -> Arc<Mutex<SqliteConnection>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +77,7 @@ impl DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectionSource for DB {
|
impl ConnectionSource for DB {
|
||||||
fn get_connection(&self) -> MutexGuard<SqliteConnection> {
|
fn get_connection(&self) -> MutexGuard<'_, SqliteConnection> {
|
||||||
self.connection.lock().unwrap()
|
self.connection.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use log::{error, info};
|
||||||
use reqwest;
|
use reqwest;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time;
|
use std::time;
|
||||||
|
|
|
@ -2,6 +2,7 @@ use ape;
|
||||||
use core;
|
use core;
|
||||||
use diesel;
|
use diesel;
|
||||||
use diesel_migrations;
|
use diesel_migrations;
|
||||||
|
use error_chain::error_chain;
|
||||||
use getopts;
|
use getopts;
|
||||||
use id3;
|
use id3;
|
||||||
use image;
|
use image;
|
||||||
|
@ -44,7 +45,7 @@ error_chain! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> rocket::response::Responder<'r> for Error {
|
impl<'r> rocket::response::Responder<'r> for Error {
|
||||||
fn respond_to(self, _: &rocket::request::Request) -> rocket::response::Result<'r> {
|
fn respond_to(self, _: &rocket::request::Request<'_>) -> rocket::response::Result<'r> {
|
||||||
let mut build = rocket::response::Response::build();
|
let mut build = rocket::response::Response::build();
|
||||||
build
|
build
|
||||||
.status(match self.0 {
|
.status(match self.0 {
|
||||||
|
|
|
@ -4,7 +4,10 @@ use diesel::dsl::sql;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sql_types;
|
use diesel::sql_types;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
|
use error_chain::bail;
|
||||||
|
use log::{error, info};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -169,7 +172,7 @@ impl<'conn> IndexBuilder<'conn> {
|
||||||
fn new(
|
fn new(
|
||||||
connection: &Mutex<SqliteConnection>,
|
connection: &Mutex<SqliteConnection>,
|
||||||
album_art_pattern: Regex,
|
album_art_pattern: Regex,
|
||||||
) -> Result<IndexBuilder, errors::Error> {
|
) -> Result<IndexBuilder<'_>, errors::Error> {
|
||||||
let mut new_songs = Vec::new();
|
let mut new_songs = Vec::new();
|
||||||
let mut new_directories = Vec::new();
|
let mut new_directories = Vec::new();
|
||||||
new_songs.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
|
new_songs.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use rustfm_scrobble::{Scrobble, Scrobbler};
|
use rustfm_scrobble::{Scrobble, Scrobbler};
|
||||||
|
use serde::Deserialize;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::db::ConnectionSource;
|
use crate::db::ConnectionSource;
|
||||||
|
|
69
src/main.rs
69
src/main.rs
|
@ -2,47 +2,10 @@
|
||||||
#![feature(proc_macro_hygiene, decl_macro)]
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
#![allow(proc_macro_derive_resolution_fallback)]
|
#![allow(proc_macro_derive_resolution_fallback)]
|
||||||
|
|
||||||
extern crate ape;
|
|
||||||
extern crate app_dirs;
|
|
||||||
extern crate base64;
|
|
||||||
extern crate core;
|
|
||||||
extern crate crypto;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate diesel_migrations;
|
extern crate diesel_migrations;
|
||||||
#[macro_use]
|
|
||||||
extern crate error_chain;
|
|
||||||
extern crate getopts;
|
|
||||||
extern crate id3;
|
|
||||||
extern crate image;
|
|
||||||
extern crate lewton;
|
|
||||||
extern crate metaflac;
|
|
||||||
extern crate mp3_duration;
|
|
||||||
extern crate rand;
|
|
||||||
extern crate regex;
|
|
||||||
extern crate reqwest;
|
|
||||||
extern crate ring;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate rocket;
|
|
||||||
extern crate rocket_contrib;
|
|
||||||
extern crate rustfm_scrobble;
|
|
||||||
extern crate serde;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate toml;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate simplelog;
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
extern crate uuid;
|
|
||||||
#[cfg(windows)]
|
|
||||||
extern crate winapi;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
extern crate unix_daemonize;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -51,10 +14,12 @@ use std::io::prelude::*;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use unix_daemonize::{daemonize_redirect, ChdirMode};
|
use unix_daemonize::{daemonize_redirect, ChdirMode};
|
||||||
|
|
||||||
use core::ops::Deref;
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use core::ops::Deref;
|
||||||
|
use error_chain::bail;
|
||||||
use getopts::Options;
|
use getopts::Options;
|
||||||
use simplelog::{Level, LevelFilter, SimpleLogger, TermLogger};
|
use log::info;
|
||||||
|
use simplelog::{Level, LevelFilter, SimpleLogger, TermLogger, TerminalMode};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -81,13 +46,15 @@ mod utils;
|
||||||
mod vfs;
|
mod vfs;
|
||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
static LOG_CONFIG: simplelog::Config = simplelog::Config {
|
fn log_config() -> simplelog::Config {
|
||||||
time: Some(Level::Error),
|
simplelog::Config {
|
||||||
level: Some(Level::Error),
|
time: Some(Level::Error),
|
||||||
target: Some(Level::Error),
|
level: Some(Level::Error),
|
||||||
location: Some(Level::Error),
|
target: Some(Level::Error),
|
||||||
time_format: None,
|
location: Some(Level::Error),
|
||||||
};
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Err(ref e) = run() {
|
if let Err(ref e) = run() {
|
||||||
|
@ -123,15 +90,16 @@ fn daemonize(options: &getopts::Matches) -> Result<()> {
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn init_log(log_level: LevelFilter, options: &getopts::Matches) -> Result<()> {
|
fn init_log(log_level: LevelFilter, options: &getopts::Matches) -> Result<()> {
|
||||||
|
let config = log_config();
|
||||||
if options.opt_present("f") {
|
if options.opt_present("f") {
|
||||||
if let Err(e) = TermLogger::init(log_level, LOG_CONFIG) {
|
if let Err(e) = TermLogger::init(log_level, config, TerminalMode::Stdout) {
|
||||||
println!("Error starting terminal logger: {}", e);
|
println!("Error starting terminal logger: {}", e);
|
||||||
} else {
|
} else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = SimpleLogger::init(log_level, LOG_CONFIG) {
|
if let Err(e) = SimpleLogger::init(log_level, config) {
|
||||||
bail!("Error starting simple logger: {}", e);
|
bail!("Error starting simple logger: {}", e);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -139,8 +107,9 @@ fn init_log(log_level: LevelFilter, options: &getopts::Matches) -> Result<()> {
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn init_log(log_level: LevelFilter, _: &getopts::Matches) -> Result<()> {
|
fn init_log(log_level: LevelFilter, _: &getopts::Matches) -> Result<()> {
|
||||||
if TermLogger::init(log_level, LOG_CONFIG).is_err() {
|
let config = log_config();
|
||||||
if let Err(e) = SimpleLogger::init(log_level, LOG_CONFIG) {
|
if TermLogger::init(log_level, config, TerminalMode::Stdout).is_err() {
|
||||||
|
if let Err(e) = SimpleLogger::init(log_level, config) {
|
||||||
bail!("Error starting simple logger: {}", e);
|
bail!("Error starting simple logger: {}", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ape;
|
use ape;
|
||||||
|
use error_chain::bail;
|
||||||
use id3;
|
use id3;
|
||||||
use lewton::inside_ogg::OggStreamReader;
|
use lewton::inside_ogg::OggStreamReader;
|
||||||
use metaflac;
|
use metaflac;
|
||||||
|
|
38
src/serve.rs
38
src/serve.rs
|
@ -1,8 +1,9 @@
|
||||||
|
use log::warn;
|
||||||
use rocket;
|
use rocket;
|
||||||
use rocket::http::hyper::header::*;
|
use rocket::http::hyper::header::*;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::Response;
|
|
||||||
use rocket::response::{self, Responder};
|
use rocket::response::{self, Responder};
|
||||||
|
use rocket::Response;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -44,7 +45,11 @@ impl<'r, R: Responder<'r>> RangeResponder<R> {
|
||||||
RangeResponder { original }
|
RangeResponder { original }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ignore_range(self, request: &rocket::request::Request, file_length: Option<u64>) -> response::Result<'r> {
|
fn ignore_range(
|
||||||
|
self,
|
||||||
|
request: &rocket::request::Request<'_>,
|
||||||
|
file_length: Option<u64>,
|
||||||
|
) -> response::Result<'r> {
|
||||||
let mut response = self.original.respond_to(request)?;
|
let mut response = self.original.respond_to(request)?;
|
||||||
if let Some(content_length) = file_length {
|
if let Some(content_length) = file_length {
|
||||||
response.set_header(ContentLength(content_length));
|
response.set_header(ContentLength(content_length));
|
||||||
|
@ -55,8 +60,8 @@ impl<'r, R: Responder<'r>> RangeResponder<R> {
|
||||||
|
|
||||||
fn reject_range(self, file_length: Option<u64>) -> response::Result<'r> {
|
fn reject_range(self, file_length: Option<u64>) -> response::Result<'r> {
|
||||||
let mut response = Response::build()
|
let mut response = Response::build()
|
||||||
.status(Status::RangeNotSatisfiable)
|
.status(Status::RangeNotSatisfiable)
|
||||||
.finalize();
|
.finalize();
|
||||||
if file_length.is_some() {
|
if file_length.is_some() {
|
||||||
let content_range = ContentRange(ContentRangeSpec::Bytes {
|
let content_range = ContentRange(ContentRangeSpec::Bytes {
|
||||||
range: None,
|
range: None,
|
||||||
|
@ -99,8 +104,7 @@ fn truncate_range(range: &PartialFileRange, file_length: &Option<u64>) -> Option
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> Responder<'r> for RangeResponder<File> {
|
impl<'r> Responder<'r> for RangeResponder<File> {
|
||||||
fn respond_to(mut self, request: &rocket::request::Request) -> response::Result<'r> {
|
fn respond_to(mut self, request: &rocket::request::Request<'_>) -> response::Result<'r> {
|
||||||
|
|
||||||
let metadata: Option<_> = self.original.metadata().ok();
|
let metadata: Option<_> = self.original.metadata().ok();
|
||||||
let file_length: Option<u64> = metadata.map(|m| m.len());
|
let file_length: Option<u64> = metadata.map(|m| m.len());
|
||||||
|
|
||||||
|
@ -113,9 +117,12 @@ impl<'r> Responder<'r> for RangeResponder<File> {
|
||||||
let vec_range = match Range::from_str(range_header) {
|
let vec_range = match Range::from_str(range_header) {
|
||||||
Ok(Range::Bytes(v)) => v,
|
Ok(Range::Bytes(v)) => v,
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Ignoring range header that could not be parse {:?}, file length is {:?}", range_header, file_length);
|
warn!(
|
||||||
|
"Ignoring range header that could not be parsed {:?}, file length is {:?}",
|
||||||
|
range_header, file_length
|
||||||
|
);
|
||||||
return self.ignore_range(request, file_length);
|
return self.ignore_range(request, file_length);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let partial_file_range = match vec_range.into_iter().next() {
|
let partial_file_range = match vec_range.into_iter().next() {
|
||||||
|
@ -138,15 +145,18 @@ impl<'r> Responder<'r> for RangeResponder<File> {
|
||||||
}
|
}
|
||||||
let partial_original = self.original.take(content_len);
|
let partial_original = self.original.take(content_len);
|
||||||
let response = Response::build()
|
let response = Response::build()
|
||||||
.status(Status::PartialContent)
|
.status(Status::PartialContent)
|
||||||
.header(ContentLength(content_len))
|
.header(ContentLength(content_len))
|
||||||
.header(content_range)
|
.header(content_range)
|
||||||
.streamed_body(partial_original)
|
.streamed_body(partial_original)
|
||||||
.finalize();
|
.finalize();
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
} else {
|
} else {
|
||||||
warn!("Rejecting unsatisfiable range header {:?}, file length is {:?}", &partial_file_range, &file_length);
|
warn!(
|
||||||
|
"Rejecting unsatisfiable range header {:?}, file length is {:?}",
|
||||||
|
&partial_file_range, &file_length
|
||||||
|
);
|
||||||
self.reject_range(file_length)
|
self.reject_range(file_length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
use rocket::http::uri::Origin;
|
use rocket::http::uri::Origin;
|
||||||
use rocket::response::NamedFile;
|
use rocket::response::NamedFile;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::State;
|
use rocket::{get, routes, State};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::server::StaticDirs;
|
use crate::server::StaticDirs;
|
||||||
|
|
||||||
pub fn get_routes() -> Vec<rocket::Route> {
|
pub fn get_routes() -> Vec<rocket::Route> {
|
||||||
routes![
|
routes![index, files,]
|
||||||
index,
|
|
||||||
files,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/", rank = 9)]
|
#[get("/", rank = 9)]
|
||||||
fn index(origin: &Origin) -> Redirect {
|
fn index(origin: &Origin<'_>) -> Redirect {
|
||||||
let mut new_path = origin.path().to_owned();
|
let mut new_path = origin.path().to_owned();
|
||||||
if !new_path.ends_with("/") {
|
if !new_path.ends_with("/") {
|
||||||
new_path.push_str("/");
|
new_path.push_str("/");
|
||||||
|
@ -26,27 +23,30 @@ fn index(origin: &Origin) -> Redirect {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<file..>", rank = 9)]
|
#[get("/<file..>", rank = 9)]
|
||||||
fn files(static_dirs: State<Arc<StaticDirs>>, file: PathBuf) -> Option<NamedFile> {
|
fn files(static_dirs: State<'_, Arc<StaticDirs>>, file: PathBuf) -> Option<NamedFile> {
|
||||||
let path = static_dirs.swagger_dir_path.clone().join(file.clone());
|
let path = static_dirs.swagger_dir_path.clone().join(file.clone());
|
||||||
NamedFile::open(path).ok()
|
NamedFile::open(path).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_index_redirect() {
|
fn test_index_redirect() {
|
||||||
use rocket::http::Status;
|
|
||||||
use crate::test::get_test_environment;
|
use crate::test::get_test_environment;
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
let env = get_test_environment("swagger_index_redirect.sqlite");
|
let env = get_test_environment("swagger_index_redirect.sqlite");
|
||||||
let client = &env.client;
|
let client = &env.client;
|
||||||
let response = client.get("/swagger").dispatch();
|
let response = client.get("/swagger").dispatch();
|
||||||
assert_eq!(response.status(), Status::PermanentRedirect);
|
assert_eq!(response.status(), Status::PermanentRedirect);
|
||||||
assert_eq!(response.headers().get_one("Location"), Some("/swagger/index.html"));
|
assert_eq!(
|
||||||
|
response.headers().get_one("Location"),
|
||||||
|
Some("/swagger/index.html")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_index() {
|
fn test_index() {
|
||||||
use rocket::http::Status;
|
|
||||||
use crate::test::get_test_environment;
|
use crate::test::get_test_environment;
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
let env = get_test_environment("swagger_index.sqlite");
|
let env = get_test_environment("swagger_index.sqlite");
|
||||||
let client = &env.client;
|
let client = &env.client;
|
||||||
|
|
|
@ -49,9 +49,7 @@ pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf> {
|
||||||
let scaled_image =
|
let scaled_image =
|
||||||
source_image.resize(out_dimension, out_dimension, FilterType::Lanczos3);
|
source_image.resize(out_dimension, out_dimension, FilterType::Lanczos3);
|
||||||
let (scaled_width, scaled_height) = scaled_image.dimensions();
|
let (scaled_width, scaled_height) = scaled_image.dimensions();
|
||||||
let background = image::Rgb {
|
let background = image::Rgb([255, 255 as u8, 255 as u8]);
|
||||||
data: [255 as u8, 255 as u8, 255 as u8],
|
|
||||||
};
|
|
||||||
final_image = DynamicImage::ImageRgb8(ImageBuffer::from_pixel(
|
final_image = DynamicImage::ImageRgb8(ImageBuffer::from_pixel(
|
||||||
out_dimension,
|
out_dimension,
|
||||||
out_dimension,
|
out_dimension,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use log::info;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use log::info;
|
||||||
use std;
|
use std;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::os::windows::ffi::OsStrExt;
|
use std::os::windows::ffi::OsStrExt;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use diesel;
|
use diesel;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use error_chain::bail;
|
||||||
use rand;
|
use rand;
|
||||||
use ring::{digest, pbkdf2};
|
use ring::{digest, pbkdf2};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use app_dirs::{app_root, AppDataType, AppInfo};
|
use app_dirs::{app_root, AppDataType, AppInfo};
|
||||||
|
use error_chain::bail;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
@ -54,10 +55,7 @@ pub fn get_audio_format(path: &Path) -> Option<AudioFormat> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_audio_format() {
|
fn test_get_audio_format() {
|
||||||
assert_eq!(
|
assert_eq!(get_audio_format(Path::new("animals/🐷/my🐖file.jpg")), None);
|
||||||
get_audio_format(Path::new("animals/🐷/my🐖file.jpg")),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_audio_format(Path::new("animals/🐷/my🐖file.flac")),
|
get_audio_format(Path::new("animals/🐷/my🐖file.flac")),
|
||||||
Some(AudioFormat::FLAC)
|
Some(AudioFormat::FLAC)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use error_chain::bail;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
17
src/web.rs
17
src/web.rs
|
@ -1,5 +1,5 @@
|
||||||
use rocket::response::NamedFile;
|
use rocket::response::NamedFile;
|
||||||
use rocket::State;
|
use rocket::{get, routes, State};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -7,29 +7,26 @@ use std::sync::Arc;
|
||||||
use crate::server::StaticDirs;
|
use crate::server::StaticDirs;
|
||||||
|
|
||||||
pub fn get_routes() -> Vec<rocket::Route> {
|
pub fn get_routes() -> Vec<rocket::Route> {
|
||||||
routes![
|
routes![index, files,]
|
||||||
index,
|
|
||||||
files,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/", rank = 10)]
|
#[get("/", rank = 10)]
|
||||||
fn index(static_dirs: State<Arc<StaticDirs>>) -> io::Result<NamedFile> {
|
fn index(static_dirs: State<'_, Arc<StaticDirs>>) -> io::Result<NamedFile> {
|
||||||
let mut path = static_dirs.web_dir_path.clone();
|
let mut path = static_dirs.web_dir_path.clone();
|
||||||
path.push("index.html");
|
path.push("index.html");
|
||||||
NamedFile::open(path)
|
NamedFile::open(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<file..>", rank = 10)]
|
#[get("/<file..>", rank = 10)]
|
||||||
fn files(static_dirs: State<Arc<StaticDirs>>, file: PathBuf) -> Option<NamedFile> {
|
fn files(static_dirs: State<'_, Arc<StaticDirs>>, file: PathBuf) -> Option<NamedFile> {
|
||||||
let path = static_dirs.web_dir_path.clone().join(file.clone());
|
let path = static_dirs.web_dir_path.clone().join(file.clone());
|
||||||
NamedFile::open(path).ok()
|
NamedFile::open(path).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_index() {
|
fn test_index() {
|
||||||
use rocket::http::Status;
|
|
||||||
use crate::test::get_test_environment;
|
use crate::test::get_test_environment;
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
let env = get_test_environment("web_index.sqlite");
|
let env = get_test_environment("web_index.sqlite");
|
||||||
let client = &env.client;
|
let client = &env.client;
|
||||||
|
|
Loading…
Add table
Reference in a new issue