mirror of
https://github.com/agersant/polaris
synced 2024-11-10 10:14:12 +00:00
Cleaned up startup code (#104)
This commit is contained in:
parent
847c26ddfe
commit
e1934a8e92
23 changed files with 474 additions and 443 deletions
|
@ -2,7 +2,6 @@ use anyhow::*;
|
|||
use core::ops::Deref;
|
||||
use diesel;
|
||||
use diesel::prelude::*;
|
||||
use log::info;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
|
@ -61,7 +60,6 @@ impl Config {
|
|||
}
|
||||
|
||||
pub fn parse_toml_file(path: &path::Path) -> Result<Config> {
|
||||
info!("Config file path: {}", path.to_string_lossy());
|
||||
let mut config_file = fs::File::open(path)?;
|
||||
let mut config_file_content = String::new();
|
||||
config_file.read_to_string(&mut config_file_content)?;
|
||||
|
|
|
@ -3,7 +3,7 @@ use diesel::r2d2::{self, ConnectionManager, PooledConnection};
|
|||
use diesel::sqlite::SqliteConnection;
|
||||
use diesel::RunQueryDsl;
|
||||
use diesel_migrations;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
mod schema;
|
||||
|
||||
|
@ -16,6 +16,7 @@ embed_migrations!("migrations");
|
|||
#[derive(Clone)]
|
||||
pub struct DB {
|
||||
pool: r2d2::Pool<ConnectionManager<SqliteConnection>>,
|
||||
location: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -45,11 +46,18 @@ impl DB {
|
|||
let pool = diesel::r2d2::Pool::builder()
|
||||
.connection_customizer(Box::new(ConnectionCustomizer {}))
|
||||
.build(manager)?;
|
||||
let db = DB { pool: pool };
|
||||
let db = DB {
|
||||
pool: pool,
|
||||
location: path.to_owned(),
|
||||
};
|
||||
db.migrate_up()?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
pub fn location(&self) -> &Path {
|
||||
&self.location
|
||||
}
|
||||
|
||||
pub fn connect(&self) -> Result<PooledConnection<ConnectionManager<SqliteConnection>>> {
|
||||
self.pool.get().map_err(Error::new)
|
||||
}
|
||||
|
|
|
@ -22,46 +22,6 @@ pub use self::query::*;
|
|||
pub use self::types::*;
|
||||
pub use self::update::*;
|
||||
|
||||
pub fn builder(db: DB) -> IndexBuilder {
|
||||
IndexBuilder {
|
||||
db: db,
|
||||
periodic_updates: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IndexBuilder {
|
||||
db: DB,
|
||||
periodic_updates: bool,
|
||||
}
|
||||
|
||||
impl IndexBuilder {
|
||||
pub fn periodic_updates(mut self, enabled: bool) -> IndexBuilder {
|
||||
self.periodic_updates = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Index {
|
||||
let index = Index {
|
||||
pending_reindex: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
db: self.db.clone(),
|
||||
};
|
||||
|
||||
let commands_index = index.clone();
|
||||
std::thread::spawn(move || {
|
||||
commands_index.process_commands();
|
||||
});
|
||||
|
||||
if self.periodic_updates {
|
||||
let auto_index = index.clone();
|
||||
std::thread::spawn(move || {
|
||||
auto_index.automatic_reindex();
|
||||
});
|
||||
}
|
||||
|
||||
index
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Index {
|
||||
db: DB,
|
||||
|
@ -69,6 +29,20 @@ pub struct Index {
|
|||
}
|
||||
|
||||
impl Index {
|
||||
pub fn new(db: DB) -> Self {
|
||||
let index = Self {
|
||||
pending_reindex: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
db,
|
||||
};
|
||||
|
||||
let commands_index = index.clone();
|
||||
std::thread::spawn(move || {
|
||||
commands_index.process_commands();
|
||||
});
|
||||
|
||||
index
|
||||
}
|
||||
|
||||
pub fn trigger_reindex(&self) {
|
||||
let (lock, cvar) = &*self.pending_reindex;
|
||||
let mut pending_reindex = lock.lock().unwrap();
|
||||
|
@ -76,6 +50,13 @@ impl Index {
|
|||
cvar.notify_one();
|
||||
}
|
||||
|
||||
pub fn begin_periodic_updates(&self) {
|
||||
let auto_index = self.clone();
|
||||
std::thread::spawn(move || {
|
||||
auto_index.automatic_reindex();
|
||||
});
|
||||
}
|
||||
|
||||
fn process_commands(&self) {
|
||||
loop {
|
||||
{
|
||||
|
|
274
src/main.rs
274
src/main.rs
|
@ -9,57 +9,45 @@ extern crate diesel_migrations;
|
|||
#[macro_use]
|
||||
extern crate flamer;
|
||||
|
||||
#[cfg(unix)]
|
||||
use log::error;
|
||||
#[cfg(unix)]
|
||||
use sd_notify::{self, NotifyState};
|
||||
#[cfg(unix)]
|
||||
use std::io::prelude::*;
|
||||
#[cfg(unix)]
|
||||
use unix_daemonize::{daemonize_redirect, ChdirMode};
|
||||
|
||||
use anyhow::*;
|
||||
use getopts::Options;
|
||||
use log::info;
|
||||
use log::{error, info};
|
||||
use simplelog::{LevelFilter, SimpleLogger, TermLogger, TerminalMode};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod artwork;
|
||||
mod config;
|
||||
mod db;
|
||||
mod ddns;
|
||||
mod index;
|
||||
mod lastfm;
|
||||
mod options;
|
||||
mod playlist;
|
||||
mod service;
|
||||
|
||||
mod artwork;
|
||||
mod thumbnails;
|
||||
mod ui;
|
||||
mod user;
|
||||
mod utils;
|
||||
mod vfs;
|
||||
|
||||
fn log_config() -> simplelog::Config {
|
||||
simplelog::ConfigBuilder::new()
|
||||
.set_location_level(LevelFilter::Error)
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn daemonize(options: &getopts::Matches) -> Result<()> {
|
||||
if options.opt_present("f") {
|
||||
fn daemonize(
|
||||
foreground: bool,
|
||||
pid_file_path: &Option<std::path::PathBuf>,
|
||||
log_file_path: &Option<std::path::PathBuf>,
|
||||
) -> Result<()> {
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use unix_daemonize::{daemonize_redirect, ChdirMode};
|
||||
|
||||
if foreground {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let log_path = options
|
||||
.opt_str("log")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
let mut path = PathBuf::from(option_env!("POLARIS_LOG_DIR").unwrap_or("."));
|
||||
path.push("polaris.log");
|
||||
path
|
||||
});
|
||||
let log_path = log_file_path.clone().unwrap_or_else(|| {
|
||||
let mut path = PathBuf::from(option_env!("POLARIS_LOG_DIR").unwrap_or("."));
|
||||
path.push("polaris.log");
|
||||
path
|
||||
});
|
||||
fs::create_dir_all(&log_path.parent().unwrap())?;
|
||||
|
||||
let pid = match daemonize_redirect(Some(&log_path), Some(&log_path), ChdirMode::NoChdir) {
|
||||
|
@ -67,14 +55,11 @@ fn daemonize(options: &getopts::Matches) -> Result<()> {
|
|||
Err(e) => bail!("Daemonize error: {:#?}", e),
|
||||
};
|
||||
|
||||
let pid_path = options
|
||||
.opt_str("pid")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
let mut path = PathBuf::from(option_env!("POLARIS_PID_DIR").unwrap_or("."));
|
||||
path.push("polaris.pid");
|
||||
path
|
||||
});
|
||||
let pid_path = pid_file_path.clone().unwrap_or_else(|| {
|
||||
let mut path = PathBuf::from(option_env!("POLARIS_PID_DIR").unwrap_or("."));
|
||||
path.push("polaris.pid");
|
||||
path
|
||||
});
|
||||
fs::create_dir_all(&pid_path.parent().unwrap())?;
|
||||
|
||||
let mut file = fs::File::create(pid_path)?;
|
||||
|
@ -82,36 +67,10 @@ fn daemonize(options: &getopts::Matches) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn init_log(log_level: LevelFilter, options: &getopts::Matches) -> Result<()> {
|
||||
if options.opt_present("f") {
|
||||
if let Err(e) = TermLogger::init(log_level, log_config(), TerminalMode::Stdout) {
|
||||
println!("Error starting terminal logger: {}", e);
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = SimpleLogger::init(log_level, log_config()) {
|
||||
bail!("Error starting simple logger: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn init_log(log_level: LevelFilter, _: &getopts::Matches) -> Result<()> {
|
||||
if TermLogger::init(log_level, log_config(), TerminalMode::Stdout).is_err() {
|
||||
if let Err(e) = SimpleLogger::init(log_level, log_config()) {
|
||||
bail!("Error starting simple logger: {}", e);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn notify_ready() {
|
||||
if let Ok(true) = sd_notify::booted() {
|
||||
if let Err(e) = sd_notify::notify(true, &[NotifyState::Ready]) {
|
||||
if let Err(e) = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]) {
|
||||
error!("Unable to send ready notification: {}", e);
|
||||
}
|
||||
}
|
||||
|
@ -120,151 +79,94 @@ fn notify_ready() {
|
|||
#[cfg(not(unix))]
|
||||
fn notify_ready() {}
|
||||
|
||||
fn init_logging(cli_options: &options::CLIOptions) -> Result<()> {
|
||||
let log_level = cli_options.log_level.unwrap_or(LevelFilter::Info);
|
||||
let log_config = simplelog::ConfigBuilder::new()
|
||||
.set_location_level(LevelFilter::Error)
|
||||
.build();
|
||||
|
||||
#[cfg(unix)]
|
||||
let prefer_term_logger = cli_options.foreground;
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let prefer_term_logger = true;
|
||||
|
||||
if prefer_term_logger {
|
||||
match TermLogger::init(log_level, log_config.clone(), TerminalMode::Stdout) {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => error!("Error starting terminal logger: {}", e),
|
||||
}
|
||||
}
|
||||
SimpleLogger::init(log_level, log_config)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Parse CLI options
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let mut options = Options::new();
|
||||
options.optopt("c", "config", "set the configuration file", "FILE");
|
||||
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("s", "swagger", "set the path to swagger files", "DIRECTORY");
|
||||
options.optopt(
|
||||
"",
|
||||
"cache",
|
||||
"set the directory to use as cache",
|
||||
"DIRECTORY",
|
||||
);
|
||||
options.optopt("", "log", "set the path to the log file", "FILE");
|
||||
options.optopt("", "pid", "set the path to the pid file", "FILE");
|
||||
options.optopt(
|
||||
"",
|
||||
"log-level",
|
||||
"set the log level to a value between 0 (off) and 3 (debug)",
|
||||
"LEVEL",
|
||||
);
|
||||
let options_manager = options::OptionsManager::new();
|
||||
let cli_options = options_manager.parse(&args[1..])?;
|
||||
|
||||
#[cfg(unix)]
|
||||
options.optflag(
|
||||
"f",
|
||||
"foreground",
|
||||
"run polaris in the foreground instead of daemonizing",
|
||||
);
|
||||
|
||||
options.optflag("h", "help", "print this help menu");
|
||||
|
||||
let matches = options.parse(&args[1..])?;
|
||||
|
||||
if matches.opt_present("h") {
|
||||
if cli_options.show_help {
|
||||
let program = args[0].clone();
|
||||
let brief = format!("Usage: {} [options]", program);
|
||||
print!("{}", options.usage(&brief));
|
||||
print!("{}", options_manager.usage(&brief));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let log_level = match matches.opt_str("log-level").as_ref().map(String::as_ref) {
|
||||
Some("0") => LevelFilter::Off,
|
||||
Some("1") => LevelFilter::Error,
|
||||
Some("2") => LevelFilter::Info,
|
||||
Some("3") => LevelFilter::Debug,
|
||||
_ => LevelFilter::Info,
|
||||
};
|
||||
|
||||
init_log(log_level, &matches)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
daemonize(&matches)?;
|
||||
daemonize(
|
||||
cli_options.foreground,
|
||||
&cli_options.pid_file_path,
|
||||
&cli_options.log_file_path,
|
||||
)?;
|
||||
|
||||
// Init DB
|
||||
let db_path = matches.opt_str("d").map(PathBuf::from).unwrap_or_else(|| {
|
||||
let mut path = PathBuf::from(option_env!("POLARIS_DB_DIR").unwrap_or("."));
|
||||
path.push("db.sqlite");
|
||||
path
|
||||
});
|
||||
fs::create_dir_all(&db_path.parent().unwrap())?;
|
||||
info!("Database file path is {}", db_path.display());
|
||||
let db = db::DB::new(&db_path)?;
|
||||
init_logging(&cli_options)?;
|
||||
|
||||
// Parse config
|
||||
if let Some(config_path) = matches.opt_str("c").map(PathBuf::from) {
|
||||
let config = config::parse_toml_file(&config_path)?;
|
||||
info!("Applying configuration from {}", config_path.display());
|
||||
config::amend(&db, &config)?;
|
||||
// Create service context
|
||||
let mut context_builder = service::ContextBuilder::new();
|
||||
if let Some(port) = cli_options.port {
|
||||
context_builder = context_builder.port(port);
|
||||
}
|
||||
if let Some(path) = cli_options.config_file_path {
|
||||
info!("Config file location is {:#?}", path);
|
||||
context_builder = context_builder.config_file_path(path);
|
||||
}
|
||||
if let Some(path) = cli_options.database_file_path {
|
||||
context_builder = context_builder.database_file_path(path);
|
||||
}
|
||||
if let Some(path) = cli_options.web_dir_path {
|
||||
context_builder = context_builder.web_dir_path(path);
|
||||
}
|
||||
if let Some(path) = cli_options.swagger_dir_path {
|
||||
context_builder = context_builder.swagger_dir_path(path);
|
||||
}
|
||||
if let Some(path) = cli_options.cache_dir_path {
|
||||
context_builder = context_builder.cache_dir_path(path);
|
||||
}
|
||||
let auth_secret = config::get_auth_secret(&db)?;
|
||||
|
||||
// Locate web client files
|
||||
let web_dir_path = match matches
|
||||
.opt_str("w")
|
||||
.or(option_env!("POLARIS_WEB_DIR").map(String::from))
|
||||
{
|
||||
Some(s) => PathBuf::from(s),
|
||||
None => [".", "web"].iter().collect(),
|
||||
};
|
||||
fs::create_dir_all(&web_dir_path)?;
|
||||
info!("Static files location is {}", web_dir_path.display());
|
||||
|
||||
// Locate swagger files
|
||||
let swagger_dir_path = match matches
|
||||
.opt_str("s")
|
||||
.or(option_env!("POLARIS_SWAGGER_DIR").map(String::from))
|
||||
{
|
||||
Some(s) => PathBuf::from(s),
|
||||
None => [".", "docs", "swagger"].iter().collect(),
|
||||
};
|
||||
fs::create_dir_all(&swagger_dir_path)?;
|
||||
info!("Swagger files location is {}", swagger_dir_path.display());
|
||||
|
||||
// Initialize thumbnails manager
|
||||
let mut thumbnails_path = PathBuf::from(
|
||||
matches
|
||||
.opt_str("cache")
|
||||
.or(option_env!("POLARIS_CACHE_DIR").map(String::from))
|
||||
.unwrap_or(".".to_owned()),
|
||||
let context = context_builder.build()?;
|
||||
info!("Database file location is {:#?}", context.db.location());
|
||||
info!("Web client files location is {:#?}", context.web_dir_path);
|
||||
info!("Swagger files location is {:#?}", context.swagger_dir_path);
|
||||
info!(
|
||||
"Thumbnails files location is {:#?}",
|
||||
context.thumbnails_manager.get_directory()
|
||||
);
|
||||
thumbnails_path.push("thumbnails");
|
||||
fs::create_dir_all(&thumbnails_path)?;
|
||||
info!("Thumbnails location is {}", thumbnails_path.display());
|
||||
let thumbnails_manager = thumbnails::ThumbnailsManager::new(&thumbnails_path);
|
||||
|
||||
// Endpoints
|
||||
let api_url = "/api";
|
||||
let swagger_url = "/swagger";
|
||||
let web_url = "/";
|
||||
info!("Mounting API on {}", api_url);
|
||||
info!("Mounting web client files on {}", web_url);
|
||||
info!("Mounting swagger files on {}", swagger_url);
|
||||
|
||||
// Init index
|
||||
let index = index::builder(db.clone()).periodic_updates(true).build();
|
||||
// Begin collection scans
|
||||
context.index.begin_periodic_updates();
|
||||
|
||||
// Start DDNS updates
|
||||
let db_ddns = db.clone();
|
||||
let db_ddns = context.db.clone();
|
||||
std::thread::spawn(move || {
|
||||
ddns::run(&db_ddns);
|
||||
});
|
||||
|
||||
// Start server
|
||||
info!("Starting up server");
|
||||
let port: u16 = matches
|
||||
.opt_str("p")
|
||||
.unwrap_or_else(|| "5050".to_owned())
|
||||
.parse()
|
||||
.with_context(|| "Invalid port number")?;
|
||||
let db_server = db.clone();
|
||||
std::thread::spawn(move || {
|
||||
let _ = service::server::run(
|
||||
port,
|
||||
&auth_secret,
|
||||
api_url,
|
||||
web_url,
|
||||
&web_dir_path,
|
||||
swagger_url,
|
||||
&swagger_dir_path,
|
||||
db_server,
|
||||
index,
|
||||
thumbnails_manager,
|
||||
);
|
||||
let _ = service::run(context);
|
||||
});
|
||||
|
||||
// Send readiness notification
|
||||
|
|
86
src/options.rs
Normal file
86
src/options.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use anyhow::Result;
|
||||
use simplelog::LevelFilter;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct CLIOptions {
|
||||
pub show_help: bool,
|
||||
#[cfg(unix)]
|
||||
pub foreground: bool,
|
||||
pub log_file_path: Option<PathBuf>,
|
||||
pub pid_file_path: Option<PathBuf>,
|
||||
pub config_file_path: Option<PathBuf>,
|
||||
pub database_file_path: Option<PathBuf>,
|
||||
pub cache_dir_path: Option<PathBuf>,
|
||||
pub web_dir_path: Option<PathBuf>,
|
||||
pub swagger_dir_path: Option<PathBuf>,
|
||||
pub port: Option<u16>,
|
||||
pub log_level: Option<LevelFilter>,
|
||||
}
|
||||
|
||||
pub struct OptionsManager {
|
||||
protocol: getopts::Options,
|
||||
}
|
||||
|
||||
impl OptionsManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
protocol: get_options(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&self, input: &[String]) -> Result<CLIOptions> {
|
||||
let matches = self.protocol.parse(input)?;
|
||||
|
||||
Ok(CLIOptions {
|
||||
show_help: matches.opt_present("h"),
|
||||
#[cfg(unix)]
|
||||
foreground: matches.opt_present("f"),
|
||||
log_file_path: matches.opt_str("log").map(PathBuf::from),
|
||||
pid_file_path: matches.opt_str("pid").map(PathBuf::from),
|
||||
config_file_path: matches.opt_str("c").map(PathBuf::from),
|
||||
database_file_path: matches.opt_str("d").map(PathBuf::from),
|
||||
cache_dir_path: matches.opt_str("cache").map(PathBuf::from),
|
||||
web_dir_path: matches.opt_str("w").map(PathBuf::from),
|
||||
swagger_dir_path: matches.opt_str("s").map(PathBuf::from),
|
||||
port: matches.opt_str("p").and_then(|p| p.parse().ok()),
|
||||
log_level: matches.opt_str("log-level").and_then(|l| l.parse().ok()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn usage(&self, brief: &str) -> String {
|
||||
self.protocol.usage(brief)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_options() -> getopts::Options {
|
||||
let mut options = getopts::Options::new();
|
||||
options.optopt("c", "config", "set the configuration file", "FILE");
|
||||
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("s", "swagger", "set the path to swagger files", "DIRECTORY");
|
||||
options.optopt(
|
||||
"",
|
||||
"cache",
|
||||
"set the directory to use as cache",
|
||||
"DIRECTORY",
|
||||
);
|
||||
options.optopt("", "log", "set the path to the log file", "FILE");
|
||||
options.optopt("", "pid", "set the path to the pid file", "FILE");
|
||||
options.optopt(
|
||||
"",
|
||||
"log-level",
|
||||
"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("h", "help", "print this help menu");
|
||||
options
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
pub const API_MAJOR_VERSION: i32 = 5;
|
||||
pub const API_MINOR_VERSION: i32 = 0;
|
||||
pub const COOKIE_SESSION: &str = "session";
|
||||
pub const COOKIE_USERNAME: &str = "username";
|
||||
pub const COOKIE_ADMIN: &str = "admin";
|
|
@ -1,5 +1,11 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const API_MAJOR_VERSION: i32 = 5;
|
||||
pub const API_MINOR_VERSION: i32 = 0;
|
||||
pub const COOKIE_SESSION: &str = "session";
|
||||
pub const COOKIE_USERNAME: &str = "username";
|
||||
pub const COOKIE_ADMIN: &str = "admin";
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Version {
|
||||
pub major: i32,
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
mod constants;
|
||||
use crate::db::DB;
|
||||
use crate::index::Index;
|
||||
use crate::thumbnails::ThumbnailsManager;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::config;
|
||||
|
||||
mod dto;
|
||||
mod error;
|
||||
|
||||
|
@ -9,3 +16,115 @@ mod test;
|
|||
mod rocket;
|
||||
#[cfg(feature = "service-rocket")]
|
||||
pub use self::rocket::*;
|
||||
|
||||
pub struct Context {
|
||||
pub port: u16,
|
||||
pub auth_secret: Vec<u8>,
|
||||
pub web_dir_path: PathBuf,
|
||||
pub swagger_dir_path: PathBuf,
|
||||
pub web_url: String,
|
||||
pub swagger_url: String,
|
||||
pub api_url: String,
|
||||
pub db: DB,
|
||||
pub index: Index,
|
||||
pub thumbnails_manager: ThumbnailsManager,
|
||||
}
|
||||
|
||||
pub struct ContextBuilder {
|
||||
port: Option<u16>,
|
||||
config_file_path: Option<PathBuf>,
|
||||
database_file_path: Option<PathBuf>,
|
||||
web_dir_path: Option<PathBuf>,
|
||||
swagger_dir_path: Option<PathBuf>,
|
||||
cache_dir_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl ContextBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
port: None,
|
||||
config_file_path: None,
|
||||
database_file_path: None,
|
||||
web_dir_path: None,
|
||||
swagger_dir_path: None,
|
||||
cache_dir_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self) -> anyhow::Result<Context> {
|
||||
let db_path = self.database_file_path.unwrap_or_else(|| {
|
||||
let mut path = PathBuf::from(option_env!("POLARIS_DB_DIR").unwrap_or("."));
|
||||
path.push("db.sqlite");
|
||||
path
|
||||
});
|
||||
fs::create_dir_all(&db_path.parent().unwrap())?;
|
||||
let db = DB::new(&db_path)?;
|
||||
|
||||
if let Some(config_path) = self.config_file_path {
|
||||
let config = config::parse_toml_file(&config_path)?;
|
||||
config::amend(&db, &config)?;
|
||||
}
|
||||
let auth_secret = config::get_auth_secret(&db)?;
|
||||
|
||||
let web_dir_path = self
|
||||
.web_dir_path
|
||||
.or(option_env!("POLARIS_WEB_DIR").map(PathBuf::from))
|
||||
.unwrap_or([".", "web"].iter().collect());
|
||||
fs::create_dir_all(&web_dir_path)?;
|
||||
|
||||
let swagger_dir_path = self
|
||||
.swagger_dir_path
|
||||
.or(option_env!("POLARIS_SWAGGER_DIR").map(PathBuf::from))
|
||||
.unwrap_or([".", "docs", "swagger"].iter().collect());
|
||||
fs::create_dir_all(&swagger_dir_path)?;
|
||||
|
||||
let mut thumbnails_dir_path = self
|
||||
.cache_dir_path
|
||||
.or(option_env!("POLARIS_CACHE_DIR").map(PathBuf::from))
|
||||
.unwrap_or(PathBuf::from(".").to_owned());
|
||||
thumbnails_dir_path.push("thumbnails");
|
||||
|
||||
Ok(Context {
|
||||
port: self.port.unwrap_or(5050),
|
||||
auth_secret,
|
||||
api_url: "/api".to_owned(),
|
||||
swagger_url: "/swagger".to_owned(),
|
||||
web_url: "/".to_owned(),
|
||||
web_dir_path,
|
||||
swagger_dir_path,
|
||||
thumbnails_manager: ThumbnailsManager::new(thumbnails_dir_path),
|
||||
index: Index::new(db.clone()),
|
||||
db,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn port(mut self, port: u16) -> Self {
|
||||
self.port = Some(port);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn config_file_path(mut self, path: PathBuf) -> Self {
|
||||
self.config_file_path = Some(path);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn database_file_path(mut self, path: PathBuf) -> Self {
|
||||
self.database_file_path = Some(path);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn web_dir_path(mut self, path: PathBuf) -> Self {
|
||||
self.web_dir_path = Some(path);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn swagger_dir_path(mut self, path: PathBuf) -> Self {
|
||||
self.swagger_dir_path = Some(path);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cache_dir_path(mut self, path: PathBuf) -> Self {
|
||||
self.cache_dir_path = Some(path);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ use crate::db::DB;
|
|||
use crate::index::{self, Index, QueryError};
|
||||
use crate::lastfm;
|
||||
use crate::playlist::{self, PlaylistError};
|
||||
use crate::service::constants::*;
|
||||
use crate::service::dto;
|
||||
use crate::service::error::APIError;
|
||||
use crate::thumbnails::{ThumbnailOptions, ThumbnailsManager};
|
||||
|
@ -96,20 +95,20 @@ struct Auth {
|
|||
fn add_session_cookies(cookies: &mut Cookies, username: &str, is_admin: bool) -> () {
|
||||
let duration = Duration::days(1);
|
||||
|
||||
let session_cookie = Cookie::build(COOKIE_SESSION, username.to_owned())
|
||||
let session_cookie = Cookie::build(dto::COOKIE_SESSION, username.to_owned())
|
||||
.same_site(rocket::http::SameSite::Lax)
|
||||
.http_only(true)
|
||||
.max_age(duration)
|
||||
.finish();
|
||||
|
||||
let username_cookie = Cookie::build(COOKIE_USERNAME, username.to_owned())
|
||||
let username_cookie = Cookie::build(dto::COOKIE_USERNAME, username.to_owned())
|
||||
.same_site(rocket::http::SameSite::Lax)
|
||||
.http_only(false)
|
||||
.max_age(duration)
|
||||
.path("/")
|
||||
.finish();
|
||||
|
||||
let is_admin_cookie = Cookie::build(COOKIE_ADMIN, format!("{}", is_admin))
|
||||
let is_admin_cookie = Cookie::build(dto::COOKIE_ADMIN, format!("{}", is_admin))
|
||||
.same_site(rocket::http::SameSite::Lax)
|
||||
.http_only(false)
|
||||
.max_age(duration)
|
||||
|
@ -131,7 +130,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Auth {
|
|||
_ => return Outcome::Failure((Status::InternalServerError, ())),
|
||||
};
|
||||
|
||||
if let Some(u) = cookies.get_private(COOKIE_SESSION) {
|
||||
if let Some(u) = cookies.get_private(dto::COOKIE_SESSION) {
|
||||
let exists = match user::exists(db.deref().deref(), u.value()) {
|
||||
Ok(e) => e,
|
||||
Err(_) => return Outcome::Failure((Status::InternalServerError, ())),
|
||||
|
@ -217,8 +216,8 @@ impl From<VFSPathBuf> for PathBuf {
|
|||
#[get("/version")]
|
||||
fn version() -> Json<dto::Version> {
|
||||
let current_version = dto::Version {
|
||||
major: API_MAJOR_VERSION,
|
||||
minor: API_MINOR_VERSION,
|
||||
major: dto::API_MAJOR_VERSION,
|
||||
minor: dto::API_MINOR_VERSION,
|
||||
};
|
||||
Json(current_version)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,48 @@
|
|||
use anyhow::*;
|
||||
use rocket;
|
||||
use rocket::config::{Environment, LoggingLevel};
|
||||
use rocket_contrib::serve::{Options, StaticFiles};
|
||||
|
||||
use crate::service;
|
||||
|
||||
mod api;
|
||||
mod serve;
|
||||
|
||||
pub mod server;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test;
|
||||
|
||||
pub fn get_server(context: service::Context) -> Result<rocket::Rocket> {
|
||||
let mut config = rocket::Config::build(Environment::Production)
|
||||
.log_level(LoggingLevel::Normal)
|
||||
.port(context.port)
|
||||
.keep_alive(0)
|
||||
.finalize()?;
|
||||
|
||||
let encoded = base64::encode(&context.auth_secret);
|
||||
config.set_secret_key(encoded)?;
|
||||
|
||||
let swagger_routes_rank = 0;
|
||||
let web_routes_rank = swagger_routes_rank + 1;
|
||||
let static_file_options = Options::Index | Options::NormalizeDirs;
|
||||
|
||||
Ok(rocket::custom(config)
|
||||
.manage(context.db)
|
||||
.manage(context.index)
|
||||
.manage(context.thumbnails_manager)
|
||||
.mount(&context.api_url, api::get_routes())
|
||||
.mount(
|
||||
&context.swagger_url,
|
||||
StaticFiles::new(context.swagger_dir_path, static_file_options)
|
||||
.rank(swagger_routes_rank),
|
||||
)
|
||||
.mount(
|
||||
&context.web_url,
|
||||
StaticFiles::new(context.web_dir_path, static_file_options).rank(web_routes_rank),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn run(context: service::Context) -> Result<()> {
|
||||
let server = get_server(context)?;
|
||||
server.launch();
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
use anyhow::*;
|
||||
use rocket;
|
||||
use rocket::config::{Environment, LoggingLevel};
|
||||
use rocket_contrib::serve::{Options, StaticFiles};
|
||||
use std::path::Path;
|
||||
|
||||
use super::api;
|
||||
use crate::db::DB;
|
||||
use crate::index::Index;
|
||||
use crate::thumbnails::ThumbnailsManager;
|
||||
|
||||
pub fn get_server(
|
||||
port: u16,
|
||||
auth_secret: &[u8],
|
||||
api_url: &str,
|
||||
web_url: &str,
|
||||
web_dir_path: &Path,
|
||||
swagger_url: &str,
|
||||
swagger_dir_path: &Path,
|
||||
db: DB,
|
||||
command_sender: Index,
|
||||
thumbnails_manager: ThumbnailsManager,
|
||||
) -> Result<rocket::Rocket> {
|
||||
let mut config = rocket::Config::build(Environment::Production)
|
||||
.log_level(LoggingLevel::Normal)
|
||||
.port(port)
|
||||
.keep_alive(0)
|
||||
.finalize()?;
|
||||
|
||||
let encoded = base64::encode(auth_secret);
|
||||
config.set_secret_key(encoded)?;
|
||||
|
||||
let swagger_routes_rank = 0;
|
||||
let web_routes_rank = swagger_routes_rank + 1;
|
||||
let static_file_options = Options::Index | Options::NormalizeDirs;
|
||||
|
||||
Ok(rocket::custom(config)
|
||||
.manage(db)
|
||||
.manage(command_sender)
|
||||
.manage(thumbnails_manager)
|
||||
.mount(&api_url, api::get_routes())
|
||||
.mount(
|
||||
&swagger_url,
|
||||
StaticFiles::new(swagger_dir_path, static_file_options).rank(swagger_routes_rank),
|
||||
)
|
||||
.mount(
|
||||
&web_url,
|
||||
StaticFiles::new(web_dir_path, static_file_options).rank(web_routes_rank),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
port: u16,
|
||||
auth_secret: &[u8],
|
||||
api_url: &str,
|
||||
web_url: &str,
|
||||
web_dir_path: &Path,
|
||||
swagger_url: &str,
|
||||
swagger_dir_path: &Path,
|
||||
db: DB,
|
||||
command_sender: Index,
|
||||
thumbnails_manager: ThumbnailsManager,
|
||||
) -> Result<()> {
|
||||
let server = get_server(
|
||||
port,
|
||||
auth_secret,
|
||||
api_url,
|
||||
web_url,
|
||||
web_dir_path,
|
||||
swagger_url,
|
||||
swagger_dir_path,
|
||||
db,
|
||||
command_sender,
|
||||
thumbnails_manager,
|
||||
)?;
|
||||
server.launch();
|
||||
Ok(())
|
||||
}
|
|
@ -4,13 +4,10 @@ use rocket::local::{Client, LocalResponse};
|
|||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::server;
|
||||
use crate::db::DB;
|
||||
use crate::index;
|
||||
use crate::service;
|
||||
use crate::service::test::{protocol, TestService};
|
||||
use crate::thumbnails::ThumbnailsManager;
|
||||
|
||||
pub struct RocketTestService {
|
||||
client: Client,
|
||||
|
@ -62,44 +59,24 @@ impl RocketTestService {
|
|||
}
|
||||
|
||||
impl TestService for RocketTestService {
|
||||
fn new(unique_db_name: &str) -> Self {
|
||||
let mut db_path = PathBuf::new();
|
||||
db_path.push("test-output");
|
||||
fn new(test_name: &str) -> Self {
|
||||
let mut db_path: PathBuf = ["test-output", test_name].iter().collect();
|
||||
fs::create_dir_all(&db_path).unwrap();
|
||||
db_path.push("db.sqlite");
|
||||
|
||||
db_path.push(format!("{}.sqlite", unique_db_name));
|
||||
if db_path.exists() {
|
||||
fs::remove_file(&db_path).unwrap();
|
||||
}
|
||||
|
||||
let db = DB::new(&db_path).unwrap();
|
||||
let context = service::ContextBuilder::new()
|
||||
.database_file_path(db_path)
|
||||
.web_dir_path(Path::new("web").into())
|
||||
.swagger_dir_path(["docs", "swagger"].iter().collect())
|
||||
.cache_dir_path(["test-output", test_name].iter().collect())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let web_dir_path = PathBuf::from("web");
|
||||
let mut swagger_dir_path = PathBuf::from("docs");
|
||||
swagger_dir_path.push("swagger");
|
||||
let index = index::builder(db.clone()).periodic_updates(false).build();
|
||||
|
||||
let mut thumbnails_path = PathBuf::new();
|
||||
thumbnails_path.push("test-output");
|
||||
thumbnails_path.push("thumbnails");
|
||||
thumbnails_path.push(unique_db_name);
|
||||
let thumbnails_manager = ThumbnailsManager::new(thumbnails_path.as_path());
|
||||
|
||||
let auth_secret: [u8; 32] = [0; 32];
|
||||
|
||||
let server = server::get_server(
|
||||
5050,
|
||||
&auth_secret,
|
||||
"/api",
|
||||
"/",
|
||||
&web_dir_path,
|
||||
"/swagger",
|
||||
&swagger_dir_path,
|
||||
db,
|
||||
index,
|
||||
thumbnails_manager,
|
||||
)
|
||||
.unwrap();
|
||||
let server = service::get_server(context).unwrap();
|
||||
let client = Client::new(server).unwrap();
|
||||
let request_builder = protocol::RequestBuilder::new();
|
||||
RocketTestService {
|
||||
|
|
|
@ -3,11 +3,11 @@ use http::StatusCode;
|
|||
use crate::index;
|
||||
use crate::service::dto;
|
||||
use crate::service::test::{ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
#[test]
|
||||
fn test_returns_api_version() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().version();
|
||||
let response = service.fetch_json::<_, dto::Version>(&request);
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
@ -15,7 +15,7 @@ fn test_returns_api_version() {
|
|||
|
||||
#[test]
|
||||
fn test_initial_setup_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().initial_setup();
|
||||
{
|
||||
let response = service.fetch_json::<_, dto::InitialSetup>(&request);
|
||||
|
@ -44,7 +44,7 @@ fn test_initial_setup_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_trigger_index_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
|
||||
|
@ -63,7 +63,7 @@ fn test_trigger_index_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_trigger_index_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
let request = service.request_builder().trigger_index();
|
||||
let response = service.fetch(&request);
|
||||
|
@ -72,7 +72,7 @@ fn test_trigger_index_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_trigger_index_requires_admin() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
let request = service.request_builder().trigger_index();
|
||||
|
|
|
@ -2,9 +2,9 @@ use cookie::Cookie;
|
|||
use headers::{self, HeaderMapExt};
|
||||
use http::{Response, StatusCode};
|
||||
|
||||
use crate::service::constants::*;
|
||||
use crate::service::dto;
|
||||
use crate::service::test::{constants::*, ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
fn validate_cookies<T>(response: &Response<T>) {
|
||||
let cookies: Vec<Cookie> = response
|
||||
|
@ -13,14 +13,14 @@ fn validate_cookies<T>(response: &Response<T>) {
|
|||
.iter()
|
||||
.map(|c| Cookie::parse(c.to_str().unwrap()).unwrap())
|
||||
.collect();
|
||||
assert!(cookies.iter().any(|c| c.name() == COOKIE_SESSION));
|
||||
assert!(cookies.iter().any(|c| c.name() == COOKIE_USERNAME));
|
||||
assert!(cookies.iter().any(|c| c.name() == COOKIE_ADMIN));
|
||||
assert!(cookies.iter().any(|c| c.name() == dto::COOKIE_SESSION));
|
||||
assert!(cookies.iter().any(|c| c.name() == dto::COOKIE_USERNAME));
|
||||
assert!(cookies.iter().any(|c| c.name() == dto::COOKIE_ADMIN));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login_rejects_bad_username() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
|
||||
let request = service.request_builder().login("garbage", TEST_PASSWORD);
|
||||
|
@ -30,7 +30,7 @@ fn test_login_rejects_bad_username() {
|
|||
|
||||
#[test]
|
||||
fn test_login_rejects_bad_password() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
|
||||
let request = service.request_builder().login(TEST_USERNAME, "garbage");
|
||||
|
@ -40,7 +40,7 @@ fn test_login_rejects_bad_password() {
|
|||
|
||||
#[test]
|
||||
fn test_login_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
|
||||
let request = service
|
||||
|
@ -54,7 +54,7 @@ fn test_login_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_authentication_via_http_header_rejects_bad_username() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
|
||||
let mut request = service.request_builder().random();
|
||||
|
@ -67,7 +67,7 @@ fn test_authentication_via_http_header_rejects_bad_username() {
|
|||
|
||||
#[test]
|
||||
fn test_authentication_via_http_header_rejects_bad_password() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
|
||||
let mut request = service.request_builder().random();
|
||||
|
@ -80,7 +80,7 @@ fn test_authentication_via_http_header_rejects_bad_password() {
|
|||
|
||||
#[test]
|
||||
fn test_authentication_via_http_header_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
|
||||
let mut request = service.request_builder().random();
|
||||
|
|
|
@ -3,11 +3,11 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use crate::index;
|
||||
use crate::service::test::{constants::*, ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
#[test]
|
||||
fn test_browse_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().browse(&PathBuf::new());
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
@ -15,7 +15,7 @@ fn test_browse_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_browse_root() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -30,7 +30,7 @@ fn test_browse_root() {
|
|||
|
||||
#[test]
|
||||
fn test_browse_directory() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -46,7 +46,7 @@ fn test_browse_directory() {
|
|||
|
||||
#[test]
|
||||
fn test_browse_bad_directory() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -58,7 +58,7 @@ fn test_browse_bad_directory() {
|
|||
|
||||
#[test]
|
||||
fn test_flatten_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().flatten(&PathBuf::new());
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
@ -66,7 +66,7 @@ fn test_flatten_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_flatten_root() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -81,7 +81,7 @@ fn test_flatten_root() {
|
|||
|
||||
#[test]
|
||||
fn test_flatten_directory() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -98,7 +98,7 @@ fn test_flatten_directory() {
|
|||
|
||||
#[test]
|
||||
fn test_flatten_bad_directory() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -110,7 +110,7 @@ fn test_flatten_bad_directory() {
|
|||
|
||||
#[test]
|
||||
fn test_random_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().random();
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
@ -118,7 +118,7 @@ fn test_random_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_random() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -133,7 +133,7 @@ fn test_random() {
|
|||
|
||||
#[test]
|
||||
fn test_recent_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().recent();
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
@ -141,7 +141,7 @@ fn test_recent_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_recent() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -156,7 +156,7 @@ fn test_recent() {
|
|||
|
||||
#[test]
|
||||
fn test_search_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().search("");
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
@ -164,7 +164,7 @@ fn test_search_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_search_without_query() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -175,7 +175,7 @@ fn test_search_without_query() {
|
|||
|
||||
#[test]
|
||||
fn test_search_with_query() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
|
|
@ -2,11 +2,11 @@ use http::{header, HeaderValue, StatusCode};
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::service::test::{constants::*, ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
#[test]
|
||||
fn test_audio_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
|
||||
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted", "02 - Candlelight.mp3"]
|
||||
.iter()
|
||||
|
@ -19,7 +19,7 @@ fn test_audio_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_audio_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -37,7 +37,7 @@ fn test_audio_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_audio_partial_content() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -65,7 +65,7 @@ fn test_audio_partial_content() {
|
|||
|
||||
#[test]
|
||||
fn test_audio_bad_path_returns_not_found() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -78,7 +78,7 @@ fn test_audio_bad_path_returns_not_found() {
|
|||
|
||||
#[test]
|
||||
fn test_thumbnail_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
|
||||
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted", "Folder.jpg"]
|
||||
.iter()
|
||||
|
@ -92,7 +92,7 @@ fn test_thumbnail_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_thumbnail_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
|
@ -110,7 +110,7 @@ fn test_thumbnail_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_thumbnail_bad_path_returns_not_found() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use crate::{config, index, vfs};
|
|||
pub use crate::service::rocket::test::ServiceType;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! unique_db_name {
|
||||
macro_rules! test_name {
|
||||
() => {{
|
||||
let file_name = file!();
|
||||
let file_name = file_name.replace("/", "-");
|
||||
|
@ -34,7 +34,7 @@ macro_rules! unique_db_name {
|
|||
}
|
||||
|
||||
pub trait TestService {
|
||||
fn new(unique_db_name: &str) -> Self;
|
||||
fn new(test_name: &str) -> Self;
|
||||
fn request_builder(&self) -> &protocol::RequestBuilder;
|
||||
fn fetch<T: Serialize>(&mut self, request: &Request<T>) -> Response<()>;
|
||||
fn fetch_bytes<T: Serialize>(&mut self, request: &Request<T>) -> Response<Vec<u8>>;
|
||||
|
|
|
@ -3,11 +3,11 @@ use http::StatusCode;
|
|||
use crate::index;
|
||||
use crate::service::dto;
|
||||
use crate::service::test::{constants::*, ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
#[test]
|
||||
fn test_list_playlists_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().playlists();
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
@ -15,7 +15,7 @@ fn test_list_playlists_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_list_playlists_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
let request = service.request_builder().playlists();
|
||||
|
@ -25,7 +25,7 @@ fn test_list_playlists_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_save_playlist_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let my_playlist = dto::SavePlaylistInput { tracks: Vec::new() };
|
||||
let request = service
|
||||
.request_builder()
|
||||
|
@ -36,7 +36,7 @@ fn test_save_playlist_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_save_playlist_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -50,7 +50,7 @@ fn test_save_playlist_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_get_playlist_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().read_playlist(TEST_PLAYLIST_NAME);
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
@ -58,7 +58,7 @@ fn test_get_playlist_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_get_playlist_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -78,7 +78,7 @@ fn test_get_playlist_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_get_playlist_bad_name_returns_not_found() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -89,7 +89,7 @@ fn test_get_playlist_bad_name_returns_not_found() {
|
|||
|
||||
#[test]
|
||||
fn test_delete_playlist_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service
|
||||
.request_builder()
|
||||
.delete_playlist(TEST_PLAYLIST_NAME);
|
||||
|
@ -99,7 +99,7 @@ fn test_delete_playlist_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_delete_playlist_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -121,7 +121,7 @@ fn test_delete_playlist_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_delete_playlist_bad_name_returns_not_found() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ use http::StatusCode;
|
|||
|
||||
use crate::config;
|
||||
use crate::service::test::{ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
#[test]
|
||||
fn test_get_preferences_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().get_preferences();
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
@ -14,7 +14,7 @@ fn test_get_preferences_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_get_preferences_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -25,7 +25,7 @@ fn test_get_preferences_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_put_preferences_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service
|
||||
.request_builder()
|
||||
.put_preferences(config::Preferences::default());
|
||||
|
@ -35,7 +35,7 @@ fn test_put_preferences_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_put_preferences_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ use http::StatusCode;
|
|||
|
||||
use crate::config;
|
||||
use crate::service::test::{constants::*, ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
#[test]
|
||||
fn test_get_settings_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
|
||||
let request = service.request_builder().get_settings();
|
||||
|
@ -16,7 +16,7 @@ fn test_get_settings_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_get_settings_requires_admin() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
|
||||
|
@ -27,7 +27,7 @@ fn test_get_settings_requires_admin() {
|
|||
|
||||
#[test]
|
||||
fn test_get_settings_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
|
||||
|
@ -38,7 +38,7 @@ fn test_get_settings_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_put_settings_requires_auth() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
let request = service
|
||||
.request_builder()
|
||||
|
@ -49,7 +49,7 @@ fn test_put_settings_requires_auth() {
|
|||
|
||||
#[test]
|
||||
fn test_put_settings_requires_admin() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login();
|
||||
let request = service
|
||||
|
@ -61,7 +61,7 @@ fn test_put_settings_requires_admin() {
|
|||
|
||||
#[test]
|
||||
fn test_put_settings_golden_path() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
|
||||
|
@ -74,7 +74,7 @@ fn test_put_settings_golden_path() {
|
|||
|
||||
#[test]
|
||||
fn test_put_settings_cannot_unadmin_self() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use http::StatusCode;
|
||||
|
||||
use crate::service::test::{ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
#[test]
|
||||
fn test_swagger_can_get_index() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().swagger_index();
|
||||
let response = service.fetch_bytes(&request);
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::service::test::{ServiceType, TestService};
|
||||
use crate::unique_db_name;
|
||||
use crate::test_name;
|
||||
|
||||
#[test]
|
||||
fn test_web_can_get_index() {
|
||||
let mut service = ServiceType::new(&unique_db_name!());
|
||||
let mut service = ServiceType::new(&test_name!());
|
||||
let request = service.request_builder().web_index();
|
||||
let _response = service.fetch_bytes(&request);
|
||||
}
|
||||
|
|
|
@ -3,23 +3,27 @@ use image::imageops::FilterType;
|
|||
use image::{DynamicImage, GenericImage, GenericImageView, ImageBuffer, ImageOutputFormat};
|
||||
use std::cmp;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs::{DirBuilder, File};
|
||||
use std::fs::{self, File};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::*;
|
||||
|
||||
use crate::artwork;
|
||||
|
||||
pub struct ThumbnailsManager {
|
||||
thumbnails_path: PathBuf,
|
||||
thumbnails_dir_path: PathBuf,
|
||||
}
|
||||
|
||||
impl ThumbnailsManager {
|
||||
pub fn new(thumbnails_path: &Path) -> ThumbnailsManager {
|
||||
pub fn new(thumbnails_dir_path: PathBuf) -> ThumbnailsManager {
|
||||
ThumbnailsManager {
|
||||
thumbnails_path: thumbnails_path.to_owned(),
|
||||
thumbnails_dir_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_directory(&self) -> &Path {
|
||||
&self.thumbnails_dir_path
|
||||
}
|
||||
|
||||
pub fn get_thumbnail(
|
||||
&self,
|
||||
image_path: &Path,
|
||||
|
@ -31,20 +35,13 @@ impl ThumbnailsManager {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_thumbnails_directory(&self) -> Result<()> {
|
||||
let mut dir_builder = DirBuilder::new();
|
||||
dir_builder.recursive(true);
|
||||
dir_builder.create(self.thumbnails_path.as_path())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_thumbnail_path(
|
||||
&self,
|
||||
image_path: &Path,
|
||||
thumbnailoptions: &ThumbnailOptions,
|
||||
) -> PathBuf {
|
||||
let hash = hash(image_path, thumbnailoptions);
|
||||
let mut thumbnail_path = self.thumbnails_path.clone();
|
||||
let mut thumbnail_path = self.thumbnails_dir_path.clone();
|
||||
thumbnail_path.push(format!("{}.jpg", hash.to_string()));
|
||||
thumbnail_path
|
||||
}
|
||||
|
@ -70,7 +67,7 @@ impl ThumbnailsManager {
|
|||
let thumbnail = generate_thumbnail(image_path, thumbnailoptions)?;
|
||||
let quality = 80;
|
||||
|
||||
self.create_thumbnails_directory()?;
|
||||
fs::create_dir_all(&self.thumbnails_dir_path)?;
|
||||
let path = self.get_thumbnail_path(image_path, thumbnailoptions);
|
||||
let mut out_file = File::create(&path)?;
|
||||
thumbnail.write_to(&mut out_file, ImageOutputFormat::Jpeg(quality))?;
|
||||
|
|
Loading…
Reference in a new issue