Cleaned up startup code (#104)

This commit is contained in:
Antoine Gersant 2020-11-30 20:27:39 -08:00 committed by GitHub
parent 847c26ddfe
commit e1934a8e92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 474 additions and 443 deletions

View file

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

View file

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

View file

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

View file

@ -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
View 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
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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