/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin */ use crate::{database::Database, federation::Federation, CONF}; use anyhow::{anyhow, bail, Context, Ok}; use async_recursion::async_recursion; use jellycommon::{Node, RemoteImportOptions}; use log::{error, info}; use std::{ffi::OsStr, fs::File, os::unix::prelude::OsStrExt, path::PathBuf, sync::LazyLock}; use tokio::sync::Semaphore; static IMPORT_SEM: LazyLock = LazyLock::new(|| Semaphore::new(1)); pub async fn import(db: &Database, fed: &Federation) -> anyhow::Result<()> { info!("clearing node tree"); let permit = IMPORT_SEM.try_acquire()?; db.node.clear()?; info!("importing..."); let (_, errs) = import_path(CONF.library_path.clone(), db, fed, None) .await .context("indexing")?; info!("import completed"); drop(permit); if errs == 0 { Ok(()) } else { Err(anyhow!( "partial import, {errs} errors occured; see server log" )) } } #[async_recursion] pub async fn import_path( path: PathBuf, db: &Database, fed: &Federation, parent: Option, ) -> anyhow::Result<(Vec, usize)> { if path.is_dir() { let mpath = path.join("directory.json"); 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() { Some(e.path()) } else { None } }); let identifier = path.file_name().unwrap().to_str().unwrap().to_string(); let mut children_ids = Vec::new(); let mut errs = 0; for p in children_paths { let k = import_path(p, db, fed, Some(identifier.clone())).await; match k { core::result::Result::Ok((els, errs2)) => { errs += errs2; children_ids.extend(els) } Err(e) => { errs += 1; error!("import failed: {e:?}") } } } if mpath.exists() { let mut data: Node = serde_json::from_reader(File::open(mpath).context("metadata missing")?)?; data.public.children = children_ids; data.public.parent = parent; info!("adding {identifier}"); db.node.insert(&identifier, &data)?; Ok((vec![identifier], errs)) } else { Ok((children_ids, errs)) } } else if path.is_file() { info!("loading {path:?}"); let datafile = File::open(path.clone()).context("cant load metadata")?; let mut data: Node = serde_json::from_reader(datafile).context("invalid metadata")?; let identifier = path .file_name() .unwrap() .to_str() .unwrap() .strip_suffix(".jelly") .unwrap() .to_string(); if let Some(io) = data.private.import.take() { let title = data.public.title; data = import_remote(io, db, fed) .await .context("federated import")?; data.public.title = title; } info!("adding {identifier}"); data.public.parent = parent; db.node.insert(&identifier, &data)?; Ok((vec![identifier], 0)) } else { bail!("did somebody really put a fifo or socket in the library?!") } } async fn import_remote( opts: RemoteImportOptions, db: &Database, fed: &Federation, ) -> anyhow::Result { let sess = fed .get_session(&opts.host) .await .context("creating session")?; let node = sess.node(&opts.id).await.context("fetching remote node")?; if !node.public.children.is_empty() { todo!() } Ok(node) }