#[macro_use] extern crate diesel; #[macro_use] extern crate diesel_migrations; #[macro_use] extern crate lazy_static; use std::env; use std::net::SocketAddr; use std::path::PathBuf; use actix_web::{middleware, App, HttpServer}; use anyhow::Result; use clap::{App as ClapApp, Arg}; use log::{info, warn}; use std::sync::{Arc, RwLock}; use crate::{common::PKG_VERSION, database::UpEndDatabase}; mod addressing; mod common; mod database; mod filesystem; mod previews; mod routes; mod util; fn main() -> Result<()> { let env = env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"); env_logger::init_from_env(env); let app = ClapApp::new("upend") .version(PKG_VERSION) .author("Tomáš Mládek ") .arg(Arg::with_name("DIRECTORY").required(true).index(1)) .arg( Arg::with_name("BIND") .long("bind") .default_value("127.0.0.1:8093") .help("address and port to bind the Web interface on") .required(true), ) .arg( Arg::with_name("DB_PATH") .long("db-path") .takes_value(true) .help("path to sqlite db file (\"$VAULT_PATH/.upend\" by default)"), ) .arg( Arg::with_name("NO_BROWSER") .long("no-browser") .help("Do not open web browser with the UI."), ) .arg( Arg::with_name("NO_DESKTOP") .long("no-desktop") .help("Disable desktop features (webbrowser, native file opening)"), ) .arg( Arg::with_name("NO_UI") .long("no-ui") .help("Do not serve the web UI."), ) .arg( Arg::with_name("NO_INITIAL_UPDATE") .long("no-initial-update") .help("Don't run a database update on start."), ) .arg( Arg::with_name("REINITIALIZE") .long("reinitialize") .help("Delete and initialize database, if it exists already."), ) .arg( Arg::with_name("VAULT_NAME") .takes_value(true) .long("name") .help("Name of the vault."), ); let matches = app.get_matches(); info!("Starting UpEnd {}...", PKG_VERSION); let sys = actix::System::new("upend"); let job_container = Arc::new(RwLock::new(util::jobs::JobContainer::default())); let vault_path = PathBuf::from(matches.value_of("DIRECTORY").unwrap()); let open_result = UpEndDatabase::open( &vault_path, matches.value_of("DB_PATH").map(PathBuf::from), matches.is_present("REINITIALIZE"), ) .expect("failed to open database!"); let upend = Arc::new(open_result.db); let ui_path = env::current_exe().unwrap().parent().unwrap().join("webui"); if !ui_path.exists() { warn!("No Web UI directory present ({:?}), disabling...", ui_path); } let desktop_enabled = !matches.is_present("NO_DESKTOP"); let ui_enabled = ui_path.exists() && !matches.is_present("NO_UI"); let browser_enabled = desktop_enabled && !matches.is_present("NO_BROWSER"); #[cfg(feature = "previews")] let preview_store = Some(Arc::new(crate::previews::PreviewStore::new( upend.db_path.join("previews"), upend.clone(), ))); #[cfg(not(feature = "previews"))] let preview_store = None; let mut bind: SocketAddr = matches .value_of("BIND") .unwrap() .parse() .expect("Incorrect bind format."); let state = routes::State { upend: upend.clone(), vault_name: Some( matches .value_of("VAULT_NAME") .map(|s| s.to_string()) .unwrap_or_else(|| { vault_path .iter() .last() .unwrap() .to_string_lossy() .into_owned() }), ), job_container: job_container.clone(), preview_store, desktop_enabled, }; // Start HTTP server let mut cnt = 0; let server = loop { let state = state.clone(); let ui_path = ui_path.clone(); let server = HttpServer::new(move || { let app = App::new() .app_data(actix_web::web::PayloadConfig::new(4_294_967_296)) .data(state.clone()) .wrap(middleware::Logger::default().exclude("/api/jobs")) .service(routes::get_raw) .service(routes::get_thumbnail) .service(routes::get_query) .service(routes::get_object) .service(routes::put_object) .service(routes::put_object_attribute) .service(routes::delete_object) .service(routes::get_all_attributes) .service(routes::api_refresh) .service(routes::list_hier) .service(routes::list_hier_roots) .service(routes::latest_files) .service(routes::get_file) .service(routes::get_jobs) .service(routes::get_info); if ui_enabled { app.service(actix_files::Files::new("/", &ui_path).index_file("index.html")) } else { app } }); let bind_result = server.bind(&bind); if let Ok(server) = bind_result { break server; } else { warn!("Failed to bind at {:?}, trying next port number...", bind); bind.set_port(bind.port() + 1); } if cnt > 10 { panic!("Couldn't start server.") } else { cnt += 1; } }; info!("Starting server at: {}", &bind); server.run(); if !matches.is_present("NO_INITIAL_UPDATE") { info!("Running initial update..."); actix::spawn(filesystem::rescan_vault(upend, job_container, false, true)); } #[cfg(feature = "desktop")] { if browser_enabled && ui_enabled { let ui_result = webbrowser::open(&format!("http://localhost:{}", bind.port())); if ui_result.is_err() { warn!("Could not open UI in browser!"); } } } Ok(sys.run()?) }