aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/Cargo.toml1
-rw-r--r--server/src/import.rs43
-rw-r--r--server/src/main.rs17
-rw-r--r--server/src/routes/mod.rs5
-rw-r--r--server/src/routes/ui/account/admin.rs58
-rw-r--r--server/src/routes/ui/error.rs2
-rw-r--r--server/src/routes/ui/layout.rs9
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>>;