diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/Cargo.toml | 1 | ||||
-rw-r--r-- | server/src/import.rs | 43 | ||||
-rw-r--r-- | server/src/main.rs | 17 | ||||
-rw-r--r-- | server/src/routes/mod.rs | 5 | ||||
-rw-r--r-- | server/src/routes/ui/account/admin.rs | 58 | ||||
-rw-r--r-- | server/src/routes/ui/error.rs | 2 | ||||
-rw-r--r-- | server/src/routes/ui/layout.rs | 9 |
7 files changed, 84 insertions, 51 deletions
diff --git a/server/Cargo.toml b/server/Cargo.toml index eccdd74..6118240 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -25,6 +25,7 @@ argon2 = "0.5.1" aes-gcm-siv = "0.11.1" async-std = "1.12.0" +async-recursion = "1.0.4" tokio = { version = "1.29.1", features = ["io-util"] } tokio-util = { version = "0.7.8", features = ["io", "io-util"] } diff --git a/server/src/import.rs b/server/src/import.rs index c8f4f10..b306332 100644 --- a/server/src/import.rs +++ b/server/src/import.rs @@ -5,27 +5,38 @@ */ use crate::{database::Database, CONF}; use anyhow::{bail, Context, Ok}; +use async_recursion::async_recursion; use jellycommon::Node; use log::info; -use std::{ffi::OsStr, fs::File, os::unix::prelude::OsStrExt, path::PathBuf}; +use std::{ffi::OsStr, fs::File, os::unix::prelude::OsStrExt, path::PathBuf, sync::LazyLock}; +use tokio::sync::Semaphore; -pub fn import(db: &Database) -> anyhow::Result<()> { +static IMPORT_SEM: LazyLock<Semaphore> = LazyLock::new(|| Semaphore::new(1)); + +pub async fn import(db: &Database) -> anyhow::Result<()> { info!("clearing node tree"); - db.node.clear()?; - info!("importing..."); - import_path(CONF.library_path.clone(), db, None).context("indexing")?; + let permit = IMPORT_SEM.try_acquire()?; + { + db.node.clear()?; + info!("importing..."); + import_path(CONF.library_path.clone(), db, None) + .await + .context("indexing")?; + info!("import completed"); + } + drop(permit); Ok(()) } -pub fn import_path( +#[async_recursion] +pub async fn import_path( path: PathBuf, db: &Database, parent: Option<String>, ) -> anyhow::Result<Vec<String>> { if path.is_dir() { let mpath = path.join("directory.json"); - let children = path.read_dir()?.filter_map(|e| { - let e = e.unwrap(); + let children_paths = path.read_dir()?.map(Result::unwrap).filter_map(|e| { if e.path().extension() == Some(&OsStr::from_bytes(b"jelly")) || e.metadata().unwrap().is_dir() { @@ -35,23 +46,21 @@ pub fn import_path( } }); let identifier = path.file_name().unwrap().to_str().unwrap().to_string(); - let children = children - .map(|e| import_path(e, db, Some(identifier.clone()))) - .collect::<anyhow::Result<Vec<_>>>()? - .into_iter() - .flatten() - .collect(); + let mut children_ids = Vec::new(); + for p in children_paths { + children_ids.extend(import_path(p, db, Some(identifier.clone())).await?) + } if mpath.exists() { let mut data: Node = serde_json::from_reader(File::open(mpath).context("metadata missing")?)?; - data.public.children = children; + data.public.children = children_ids; data.public.parent = parent; info!("insert {identifier}"); db.node.insert(&identifier, &data)?; Ok(vec![identifier]) } else { - Ok(children) + Ok(children_ids) } } else if path.is_file() { info!("loading item {path:?}"); @@ -65,7 +74,7 @@ pub fn import_path( .strip_suffix(".jelly") .unwrap() .to_string(); - + info!("insert {identifier}"); data.public.parent = parent; db.node.insert(&identifier, &data)?; diff --git a/server/src/main.rs b/server/src/main.rs index 341f72b..5b2d070 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -5,13 +5,12 @@ */ #![feature(lazy_cell)] -use std::fs::File; use database::Database; use jellycommon::config::GlobalConfig; use jellyremuxer::RemuxerContext; use once_cell::sync::Lazy; -use rocket::launch; use routes::build_rocket; +use std::fs::File; pub mod database; pub mod import; @@ -20,8 +19,7 @@ pub mod routes; pub static CONF: Lazy<GlobalConfig> = Lazy::new(|| serde_json::from_reader(File::open("data/config.json").unwrap()).unwrap()); -#[launch] -fn rocket() -> _ { +fn main() { env_logger::builder() .filter_level(log::LevelFilter::Info) .parse_env("LOG") @@ -30,9 +28,16 @@ fn rocket() -> _ { #[cfg(feature = "bypass-auth")] log::warn!("authentification bypass enabled"); + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async_main()) +} +async fn async_main() { let remuxer = RemuxerContext::new(); let database = Database::open(&CONF.database_path).unwrap(); - import::import(&database).unwrap(); + import::import(&database).await.unwrap(); database.create_admin(); - build_rocket(remuxer, database) + build_rocket(remuxer, database).launch().await.unwrap(); } diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 7f4789d..be4d2cb 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -18,8 +18,8 @@ use stream::r_stream; use ui::{ account::{ admin::{ - r_account_admin_dashboard, r_account_admin_invite, r_account_admin_remove_invite, - r_account_admin_remove_user, + r_account_admin_dashboard, r_account_admin_import, r_account_admin_invite, + r_account_admin_remove_invite, r_account_admin_remove_user, }, r_account_login, r_account_login_post, r_account_logout, r_account_logout_post, r_account_register, r_account_register_post, @@ -92,6 +92,7 @@ pub fn build_rocket(remuxer: RemuxerContext, database: Database) -> Rocket<Build r_account_admin_invite, r_account_admin_remove_user, r_account_admin_remove_invite, + r_account_admin_import, r_account_settings, r_account_settings_post, r_api_version, diff --git a/server/src/routes/ui/account/admin.rs b/server/src/routes/ui/account/admin.rs index 37457b0..7124f4a 100644 --- a/server/src/routes/ui/account/admin.rs +++ b/server/src/routes/ui/account/admin.rs @@ -5,10 +5,11 @@ */ use crate::{ database::Database, + import::import, routes::ui::{ account::session::Session, error::MyResult, - layout::{DynLayoutPage, LayoutPage}, + layout::{DynLayoutPage, FlashDisplay, LayoutPage}, }, uri, }; @@ -24,16 +25,28 @@ pub fn r_account_admin_dashboard( if !session.user.admin { Err(anyhow!("you not admin"))? } + admin_dashboard(database, None) +} +pub fn admin_dashboard<'a>( + database: &Database, + flash: Option<MyResult<String>>, +) -> MyResult<DynLayoutPage<'a>> { // TODO this doesnt scale, pagination! let users = database.user.iter().collect::<Result<Vec<_>, _>>()?; let invites = database.invite.iter().collect::<Result<Vec<_>, _>>()?; + let flash = flash.map(|f| f.map_err(|e| format!("{e:?}"))); Ok(LayoutPage { title: "Admin Dashboard".to_string(), content: markup::new! { h1 { "Admin Panel" } - h2 { "Invitations"} + @FlashDisplay { flash: flash.clone() } + h2 { "Library" } + form[method="POST", action=uri!(r_account_admin_import())] { + input[type="submit", value="(Re-)Import Library"]; + } + h2 { "Invitations" } form[method="POST", action=uri!(r_account_admin_invite())] { input[type="submit", value="Generate new invite code"]; } @@ -71,13 +84,7 @@ pub fn r_account_admin_invite( let i = format!("{}", rand::thread_rng().gen::<u128>()); database.invite.insert(&i, &())?; - Ok(LayoutPage { - title: "Admin Dashboard".to_string(), - content: markup::new! { - pre { code { @i } } - }, - ..Default::default() - }) + admin_dashboard(database, Some(Ok(format!("Invite: {}", i)))) } #[derive(FromForm)] @@ -99,14 +106,7 @@ pub fn r_account_admin_remove_user( .remove(&form.name)? .ok_or(anyhow!("user did not exist"))?; - Ok(LayoutPage { - title: "User removed".to_string(), - content: markup::new! { - p { "User removed" } - a[href=uri!(r_account_admin_dashboard())] {"Back"} - }, - ..Default::default() - }) + admin_dashboard(database, Some(Ok("User removed".into()))) } #[derive(FromForm)] @@ -128,12 +128,20 @@ pub fn r_account_admin_remove_invite( .remove(&form.invite)? .ok_or(anyhow!("invite did not exist"))?; - Ok(LayoutPage { - title: "Invite invalidated".to_string(), - content: markup::new! { - p { "Invite invalidated" } - a[href=uri!(r_account_admin_dashboard())] {"Back"} - }, - ..Default::default() - }) + admin_dashboard(database, Some(Ok("Invite invalidated".into()))) +} + +#[post("/account/admin/import")] +pub async fn r_account_admin_import( + session: Session, + database: &State<Database>, +) -> MyResult<DynLayoutPage<'static>> { + if !session.user.admin { + Err(anyhow!("you not admin"))? + } + let r = import(&database).await; + admin_dashboard( + &database, + Some(r.map_err(|e| e.into()).map(|_| "Import successful".into())), + ) } diff --git a/server/src/routes/ui/error.rs b/server/src/routes/ui/error.rs index 01aebd3..190650f 100644 --- a/server/src/routes/ui/error.rs +++ b/server/src/routes/ui/error.rs @@ -90,4 +90,4 @@ impl From<serde_json::Error> for MyError { fn from(err: serde_json::Error) -> Self { MyError(anyhow::anyhow!("{err}")) } -}
\ No newline at end of file +} diff --git a/server/src/routes/ui/layout.rs b/server/src/routes/ui/layout.rs index 7844e22..7e51e5c 100644 --- a/server/src/routes/ui/layout.rs +++ b/server/src/routes/ui/layout.rs @@ -62,6 +62,15 @@ markup::define! { } } } + + FlashDisplay(flash: Option<Result<String, String>>) { + @if let Some(flash) = &flash { + @match flash { + Ok(mesg) => { section.message { p.success { @mesg } } } + Err(err) => { section.message { p.error { @format!("{err}") } } } + } + } + } } pub type DynLayoutPage<'a> = LayoutPage<markup::DynRender<'a>>; |