diff options
| author | metamuffin <metamuffin@disroot.org> | 2025-12-10 17:52:41 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2025-12-10 17:52:41 +0100 |
| commit | e4f865e9da9d6660399e22a6fbeb5b84a749b07a (patch) | |
| tree | 4af69589e8850d8a2b0c88a10e43efe8c79cb8dc /import | |
| parent | a0cfd77b4d19c43a28c4d82072e6ff136e336af3 (diff) | |
| download | jellything-e4f865e9da9d6660399e22a6fbeb5b84a749b07a.tar jellything-e4f865e9da9d6660399e22a6fbeb5b84a749b07a.tar.bz2 jellything-e4f865e9da9d6660399e22a6fbeb5b84a749b07a.tar.zst | |
refactor import plugins part 2
Diffstat (limited to 'import')
| -rw-r--r-- | import/src/lib.rs | 113 | ||||
| -rw-r--r-- | import/src/plugins/acoustid.rs | 9 | ||||
| -rw-r--r-- | import/src/plugins/infojson.rs | 10 | ||||
| -rw-r--r-- | import/src/plugins/media_info.rs | 9 | ||||
| -rw-r--r-- | import/src/plugins/misc.rs | 60 | ||||
| -rw-r--r-- | import/src/plugins/mod.rs | 53 | ||||
| -rw-r--r-- | import/src/plugins/musicbrainz.rs | 14 | ||||
| -rw-r--r-- | import/src/plugins/tags.rs | 9 | ||||
| -rw-r--r-- | import/src/plugins/tmdb.rs | 22 | ||||
| -rw-r--r-- | import/src/plugins/trakt.rs | 11 | ||||
| -rw-r--r-- | import/src/plugins/vgmdb.rs | 20 | ||||
| -rw-r--r-- | import/src/plugins/wikidata.rs | 14 | ||||
| -rw-r--r-- | import/src/plugins/wikimedia_commons.rs | 18 | ||||
| -rw-r--r-- | import/src/reporting.rs | 46 |
14 files changed, 294 insertions, 114 deletions
diff --git a/import/src/lib.rs b/import/src/lib.rs index 36c65d3..8ad6790 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -6,6 +6,7 @@ #![feature(duration_constants)] pub mod plugins; +pub mod reporting; use crate::plugins::{ acoustid::AcoustID, @@ -21,8 +22,8 @@ use crate::plugins::{ use anyhow::{Context, Result, anyhow}; use jellycache::{HashKey, cache_memory, cache_store}; use jellycommon::{ - Appearance, Asset, CreditCategory, IdentifierType, Node, NodeID, NodeKind, PictureSlot, - RatingType, Visibility, + Appearance, Asset, CreditCategory, IdentifierType, NodeID, NodeKind, PictureSlot, RatingType, + Visibility, }; use jellydb::Database; use jellyimport_fallback_generator::generate_fallback; @@ -41,11 +42,7 @@ use std::{ sync::{Arc, LazyLock, Mutex}, time::UNIX_EPOCH, }; -use tokio::{ - runtime::Handle, - sync::{RwLock, Semaphore}, - task::spawn_blocking, -}; +use tokio::{runtime::Handle, sync::Semaphore, task::spawn_blocking}; #[rustfmt::skip] #[derive(Debug, Deserialize, Serialize, Default)] @@ -81,8 +78,6 @@ pub const USER_AGENT: &str = concat!( ); static IMPORT_SEM: LazyLock<Semaphore> = LazyLock::new(|| Semaphore::new(1)); -pub static IMPORT_ERRORS: RwLock<Vec<String>> = RwLock::const_new(Vec::new()); -pub static IMPORT_PROGRESS: RwLock<Option<(usize, usize, String)>> = RwLock::const_new(None); static RE_EPISODE_FILENAME: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"([sS](?<season>\d+))?([eE](?<episode>\d+))( (.+))?"#).unwrap()); @@ -114,10 +109,9 @@ pub async fn import_wrap(db: Database, incremental: bool) -> Result<()> { let _sem = IMPORT_SEM.try_acquire().context("already importing")?; let jh = spawn_blocking(move || { - *IMPORT_ERRORS.blocking_write() = Vec::new(); - if let Err(e) = import(&db, incremental) { - IMPORT_ERRORS.blocking_write().push(format!("{e:#}")); - } + reporting::start_import(); + reporting::catch(import(&db, incremental)); + reporting::end_import(); }); let _ = jh.await; @@ -139,6 +133,7 @@ fn import(db: &Database, incremental: bool) -> Result<()> { let rthandle = Handle::current(); let mut files = Vec::new(); + import_traverse( &CONF.media_path, db, @@ -157,8 +152,8 @@ fn import(db: &Database, incremental: bool) -> Result<()> { }); // let meta = path.metadata()?; - // let mtime = meta.modified()?.duration_since(UNIX_EPOCH)?.as_secs(); - // db.set_import_file_mtime(path, mtime)?; + // let mtime = meta.modified()?.duration_since(UNIX_EPOCH)?.as_secs(); + // db.set_import_file_mtime(path, mtime)?; Ok(()) } @@ -179,6 +174,8 @@ fn import_traverse( out: &mut Vec<(PathBuf, NodeID, InheritedFlags)>, ) -> Result<()> { if path.is_dir() { + reporting::set_task(format!("indexing {path:?}")); + let slug_fragment = if path == CONF.media_path { "library".to_string() } else { @@ -214,12 +211,10 @@ fn import_traverse( for e in path.read_dir()? { let path = e?.path(); - if let Err(e) = import_traverse(&path, db, incremental, id, &slug_fragment, iflags, out) - { - IMPORT_ERRORS - .blocking_write() - .push(format!("{path:?} import failed: {e:#}")); - } + reporting::catch( + import_traverse(&path, db, incremental, id, &slug_fragment, iflags, out) + .context(anyhow!("index {slug_fragment:?}")), + ); } return Ok(()); } @@ -248,54 +243,19 @@ fn import_file( path: &Path, parent: NodeID, iflags: InheritedFlags, -) -> Result<()> { +) { let filename = path.file_name().unwrap().to_string_lossy(); - match filename.as_ref() { - "node.yaml" => { - info!("import node info at {path:?}"); - let data = serde_yaml::from_str::<Node>(&read_to_string(path)?)?; - db.update_node_init(parent, |node| { - fn merge_option<T>(a: &mut Option<T>, b: Option<T>) { - if b.is_some() { - *a = b; - } - } - if data.kind != NodeKind::Unknown { - node.kind = data.kind; - } - merge_option(&mut node.title, data.title); - merge_option(&mut node.tagline, data.tagline); - merge_option(&mut node.description, data.description); - merge_option(&mut node.index, data.index); - merge_option(&mut node.release_date, data.release_date); - - Ok(()) - })?; - } - "flags" => { - let content = read_to_string(path)?; - for flag in content.lines() { - apply_node_flag(db, rthandle, apis, parent, flag.trim())?; - } - } - "children" => { - info!("import children at {path:?}"); - for line in read_to_string(path)?.lines() { - let line = line.trim(); - if line.starts_with("#") || line.is_empty() { - continue; - } - db.update_node_init(NodeID::from_slug(line), |n| { - n.slug = line.to_owned(); - n.parents.insert(parent); - Ok(()) - })?; - } - } - _ => import_media_file(db, apis, rthandle, path, parent, iflags).context("media file")?, + if filename == "flags" { + let Some(content) = + reporting::catch(read_to_string(path).context(anyhow!("read flags at {path:?}"))) + else { + return; + }; + for flag in content.lines() {} + } + if filename.ends_with("mkv") || filename.ends_with("mka") || filename.ends_with("mks") { + import_media_file(db, apis, rthandle, path, parent, iflags).context("media file"); } - - Ok(()) } pub fn read_media_metadata(path: &Path) -> Result<Arc<matroska::Segment>> { @@ -447,26 +407,13 @@ fn import_media_file( } } - for tok in filename_toks { - apply_node_flag(db, rthandle, apis, node, tok)?; - } + // for tok in filename_toks { + // apply_node_flag(db, rthandle, apis, node, tok)?; + // } Ok(()) } -fn apply_node_flag( - db: &Database, - rthandle: &Handle, - apis: &Apis, - node: NodeID, - flag: &str, -) -> Result<()> { - if let Some(mbid) = flag.strip_prefix("mbrec-").or(flag.strip_prefix("mbrec=")) { - apply_musicbrainz_recording(db, rthandle, apis, node, mbid.to_string())? - } - Ok(()) -} - fn apply_musicbrainz_recording( db: &Database, rthandle: &Handle, diff --git a/import/src/plugins/acoustid.rs b/import/src/plugins/acoustid.rs index 154b0a2..bf07f90 100644 --- a/import/src/plugins/acoustid.rs +++ b/import/src/plugins/acoustid.rs @@ -5,7 +5,7 @@ */ use crate::{ USER_AGENT, - plugins::{ImportContext, ImportPlugin}, + plugins::{ImportContext, ImportPlugin, PluginInfo}, }; use anyhow::{Context, Result}; use jellycache::{HashKey, cache_memory}; @@ -159,6 +159,13 @@ pub(crate) fn acoustid_fingerprint(path: &Path) -> Result<Arc<Fingerprint>> { } impl ImportPlugin for AcoustID { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "acoustid", + handle_media: true, + ..Default::default() + } + } fn media(&self, ct: &ImportContext, node: NodeID, path: &Path, _seg: &Segment) -> Result<()> { let fp = acoustid_fingerprint(path)?; if let Some((atid, mbid)) = self.get_atid_mbid(&fp, &ct.rt)? { diff --git a/import/src/plugins/infojson.rs b/import/src/plugins/infojson.rs index 4dceeb8..5c3645c 100644 --- a/import/src/plugins/infojson.rs +++ b/import/src/plugins/infojson.rs @@ -14,7 +14,7 @@ use log::info; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, path::Path}; -use crate::plugins::{ImportContext, ImportPlugin}; +use crate::plugins::{ImportContext, ImportPlugin, PluginInfo}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct YVideo { @@ -160,6 +160,14 @@ pub fn is_info_json(a: &&AttachedFile) -> bool { } pub struct Infojson; impl ImportPlugin for Infojson { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "infojson", + handle_file: true, + handle_media: true, + ..Default::default() + } + } fn file(&self, ct: &ImportContext, parent: NodeID, path: &Path) -> Result<()> { let filename = path.file_name().unwrap().to_string_lossy(); if filename != "channel.info.json" { diff --git a/import/src/plugins/media_info.rs b/import/src/plugins/media_info.rs index 1d4d627..43d76e8 100644 --- a/import/src/plugins/media_info.rs +++ b/import/src/plugins/media_info.rs @@ -4,7 +4,7 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::plugins::{ImportContext, ImportPlugin}; +use crate::plugins::{ImportContext, ImportPlugin, PluginInfo}; use anyhow::{Result, anyhow}; use jellycommon::{Chapter, NodeID, SourceTrack, SourceTrackKind, TrackSource}; use jellyremuxer::matroska::Segment; @@ -12,6 +12,13 @@ use std::path::Path; pub struct MediaInfo; impl ImportPlugin for MediaInfo { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "media-info", + handle_media: true, + ..Default::default() + } + } fn media(&self, ct: &ImportContext, node: NodeID, path: &Path, seg: &Segment) -> Result<()> { let tracks = seg .tracks diff --git a/import/src/plugins/misc.rs b/import/src/plugins/misc.rs index 4717753..6f2c18e 100644 --- a/import/src/plugins/misc.rs +++ b/import/src/plugins/misc.rs @@ -3,16 +3,27 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::plugins::{ImportContext, ImportPlugin}; +use crate::plugins::{ImportContext, ImportPlugin, PluginInfo}; use anyhow::{Result, bail}; use jellycache::{HashKey, cache_store}; use jellycommon::{Asset, NodeID, NodeKind, PictureSlot, Visibility}; use jellyremuxer::matroska::{AttachedFile, Segment}; use log::info; -use std::{fs::File, io::Read, path::Path}; +use std::{ + fs::{File, read_to_string}, + io::Read, + path::Path, +}; pub struct ImageFiles; impl ImportPlugin for ImageFiles { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "image-files", + handle_file: true, + ..Default::default() + } + } fn file(&self, ct: &ImportContext, parent: NodeID, path: &Path) -> Result<()> { let filename = path.file_name().unwrap().to_string_lossy(); let slot = match filename.as_ref() { @@ -42,6 +53,13 @@ pub fn is_cover(a: &&AttachedFile) -> bool { } pub struct ImageAttachments; impl ImportPlugin for ImageAttachments { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "image-attachments", + handle_media: true, + ..Default::default() + } + } fn media(&self, ct: &ImportContext, node: NodeID, _path: &Path, seg: &Segment) -> Result<()> { let Some(cover) = seg .attachments @@ -63,7 +81,14 @@ impl ImportPlugin for ImageAttachments { pub struct General; impl ImportPlugin for General { - fn import_instruction(&self, ct: &ImportContext, node: NodeID, line: &str) -> Result<()> { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "general", + handle_instruction: true, + ..Default::default() + } + } + fn instruction(&self, ct: &ImportContext, node: NodeID, line: &str) -> Result<()> { if line == "hidden" { ct.db.update_node_init(node, |node| { node.visibility = node.visibility.min(Visibility::Hidden); @@ -98,3 +123,32 @@ impl ImportPlugin for General { Ok(()) } } + +pub struct Children; +impl ImportPlugin for Children { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "children", + handle_file: true, + ..Default::default() + } + } + fn file(&self, ct: &ImportContext, parent: NodeID, path: &Path) -> Result<()> { + let filename = path.file_name().unwrap().to_string_lossy(); + if filename.as_ref() == "children" { + info!("import children at {path:?}"); + for line in read_to_string(path)?.lines() { + let line = line.trim(); + if line.starts_with("#") || line.is_empty() { + continue; + } + ct.db.update_node_init(NodeID::from_slug(line), |n| { + n.slug = line.to_owned(); + n.parents.insert(parent); + Ok(()) + })?; + } + } + Ok(()) + } +} diff --git a/import/src/plugins/mod.rs b/import/src/plugins/mod.rs index 47fcfbf..a5cc3dc 100644 --- a/import/src/plugins/mod.rs +++ b/import/src/plugins/mod.rs @@ -5,6 +5,8 @@ */ pub mod acoustid; pub mod infojson; +pub mod media_info; +pub mod misc; pub mod musicbrainz; pub mod tags; pub mod tmdb; @@ -12,23 +14,32 @@ pub mod trakt; pub mod vgmdb; pub mod wikidata; pub mod wikimedia_commons; -pub mod media_info; -pub mod misc; - -use std::path::Path; use anyhow::Result; use jellycommon::NodeID; use jellydb::Database; use jellyremuxer::matroska::Segment; +use std::path::Path; use tokio::runtime::Handle; +use crate::ApiSecrets; + pub struct ImportContext { pub db: Database, pub rt: Handle, } -pub trait ImportPlugin { +#[derive(Default, Clone, Copy)] +pub struct PluginInfo { + name: &'static str, + handle_file: bool, + handle_media: bool, + handle_instruction: bool, + handle_process: bool, +} + +pub trait ImportPlugin: Send + Sync { + fn info(&self) -> PluginInfo; fn file(&self, ct: &ImportContext, parent: NodeID, path: &Path) -> Result<()> { let _ = (ct, parent, path); Ok(()) @@ -37,12 +48,40 @@ pub trait ImportPlugin { let _ = (ct, node, path, seg); Ok(()) } - fn import_instruction(&self, ct: &ImportContext, node: NodeID, line: &str) -> Result<()> { + fn instruction(&self, ct: &ImportContext, node: NodeID, line: &str) -> Result<()> { let _ = (ct, node, line); Ok(()) } - fn process_node(&self, ct: &ImportContext, node: NodeID) -> Result<()> { + fn process(&self, ct: &ImportContext, node: NodeID) -> Result<()> { let _ = (ct, node); Ok(()) } } + +pub fn init_plugins(secrets: &ApiSecrets) -> Vec<Box<dyn ImportPlugin>> { + let mut plugins = Vec::<Box<dyn ImportPlugin>>::new(); + + plugins.push(Box::new(misc::General)); + plugins.push(Box::new(misc::Children)); + plugins.push(Box::new(misc::ImageAttachments)); + plugins.push(Box::new(misc::ImageFiles)); + plugins.push(Box::new(tags::Tags)); + plugins.push(Box::new(media_info::MediaInfo)); + + if let Some(s) = &secrets.trakt { + plugins.push(Box::new(trakt::Trakt::new(&s))); + } + if let Some(s) = &secrets.tmdb { + plugins.push(Box::new(tmdb::Tmdb::new(&s))); // deps: trakt + } + + if let Some(s) = &secrets.acoustid { + plugins.push(Box::new(acoustid::AcoustID::new(&s))); + } + plugins.push(Box::new(musicbrainz::MusicBrainz::new())); // deps: acoustid + plugins.push(Box::new(wikidata::Wikidata::new())); // deps: musicbrainz + plugins.push(Box::new(wikimedia_commons::WikimediaCommons::new())); // deps: wikidata + plugins.push(Box::new(vgmdb::Vgmdb::new())); // deps: wikidata + + plugins +} diff --git a/import/src/plugins/musicbrainz.rs b/import/src/plugins/musicbrainz.rs index 44b2a06..305b03f 100644 --- a/import/src/plugins/musicbrainz.rs +++ b/import/src/plugins/musicbrainz.rs @@ -4,7 +4,10 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{USER_AGENT, plugins::ImportPlugin}; +use crate::{ + USER_AGENT, + plugins::{ImportPlugin, PluginInfo}, +}; use anyhow::{Context, Result}; use jellycache::cache_memory; use log::info; @@ -317,4 +320,11 @@ impl MusicBrainz { } } -impl ImportPlugin for MusicBrainz {} +impl ImportPlugin for MusicBrainz { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "musicbrainz", + ..Default::default() + } + } +} diff --git a/import/src/plugins/tags.rs b/import/src/plugins/tags.rs index 8452aad..7e30504 100644 --- a/import/src/plugins/tags.rs +++ b/import/src/plugins/tags.rs @@ -4,7 +4,7 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::plugins::{ImportContext, ImportPlugin}; +use crate::plugins::{ImportContext, ImportPlugin, PluginInfo}; use anyhow::Result; use jellycommon::{IdentifierType, NodeID, NodeKind}; use jellyremuxer::matroska::Segment; @@ -12,6 +12,13 @@ use std::{collections::HashMap, path::Path}; pub struct Tags; impl ImportPlugin for Tags { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "tags", + handle_media: true, + ..Default::default() + } + } fn media(&self, ct: &ImportContext, node: NodeID, _path: &Path, seg: &Segment) -> Result<()> { let tags = seg .tags diff --git a/import/src/plugins/tmdb.rs b/import/src/plugins/tmdb.rs index 3d6e832..5980d53 100644 --- a/import/src/plugins/tmdb.rs +++ b/import/src/plugins/tmdb.rs @@ -3,17 +3,20 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::USER_AGENT; -use anyhow::{anyhow, bail, Context, Result}; -use jellycache::{cache_memory, cache_store, EscapeKey, HashKey}; +use crate::{ + USER_AGENT, + plugins::{ImportPlugin, PluginInfo}, +}; +use anyhow::{Context, Result, anyhow, bail}; +use jellycache::{EscapeKey, HashKey, cache_memory, cache_store}; use jellycommon::{ - chrono::{format::Parsed, Utc}, Asset, + chrono::{Utc, format::Parsed}, }; use log::info; use reqwest::{ - header::{HeaderMap, HeaderName, HeaderValue}, Client, ClientBuilder, + header::{HeaderMap, HeaderName, HeaderValue}, }; use serde::{Deserialize, Serialize}; use std::{fmt::Display, sync::Arc}; @@ -153,6 +156,15 @@ impl Tmdb { } } +impl ImportPlugin for Tmdb { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "tmdb", + ..Default::default() + } + } +} + pub fn parse_release_date(d: &str) -> Result<Option<i64>> { if d.is_empty() { return Ok(None); diff --git a/import/src/plugins/trakt.rs b/import/src/plugins/trakt.rs index 5a1aa8e..6d5b007 100644 --- a/import/src/plugins/trakt.rs +++ b/import/src/plugins/trakt.rs @@ -5,7 +5,7 @@ */ use crate::{ USER_AGENT, - plugins::{ImportContext, ImportPlugin}, + plugins::{ImportContext, ImportPlugin, PluginInfo}, }; use anyhow::{Context, Result, bail}; use jellycache::{HashKey, cache_memory}; @@ -383,7 +383,14 @@ impl Display for TraktKind { } impl ImportPlugin for Trakt { - fn import_instruction(&self, ct: &ImportContext, node: NodeID, line: &str) -> Result<()> { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "takt", + handle_instruction: true, + ..Default::default() + } + } + fn instruction(&self, ct: &ImportContext, node: NodeID, line: &str) -> Result<()> { if let Some(value) = line.strip_prefix("trakt-").or(line.strip_prefix("trakt=")) { let (ty, id) = value.split_once(":").unwrap_or(("movie", value)); let ty = match ty { diff --git a/import/src/plugins/vgmdb.rs b/import/src/plugins/vgmdb.rs index 402fd90..a3928bd 100644 --- a/import/src/plugins/vgmdb.rs +++ b/import/src/plugins/vgmdb.rs @@ -4,15 +4,18 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::USER_AGENT; +use crate::{ + USER_AGENT, + plugins::{ImportPlugin, PluginInfo}, +}; use anyhow::{Context, Result}; -use jellycache::{cache, cache_store, HashKey}; +use jellycache::{HashKey, cache, cache_store}; use jellycommon::Asset; use log::info; use regex::Regex; use reqwest::{ - header::{HeaderMap, HeaderName, HeaderValue}, Client, ClientBuilder, + header::{HeaderMap, HeaderName, HeaderValue}, }; use std::{ sync::{Arc, LazyLock}, @@ -21,7 +24,7 @@ use std::{ use tokio::{ runtime::Handle, sync::Semaphore, - time::{sleep_until, Instant}, + time::{Instant, sleep_until}, }; pub struct Vgmdb { @@ -125,3 +128,12 @@ impl Vgmdb { .context("vgmdb artist page scrape") } } + +impl ImportPlugin for Vgmdb { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "vgmdb", + ..Default::default() + } + } +} diff --git a/import/src/plugins/wikidata.rs b/import/src/plugins/wikidata.rs index 358996e..095e4bd 100644 --- a/import/src/plugins/wikidata.rs +++ b/import/src/plugins/wikidata.rs @@ -4,7 +4,10 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::USER_AGENT; +use crate::{ + USER_AGENT, + plugins::{ImportPlugin, PluginInfo}, +}; use anyhow::{Context, Result, bail}; use jellycache::{EscapeKey, cache_memory}; use log::info; @@ -127,3 +130,12 @@ impl Wikidata { .context("wikidata entity") } } + +impl ImportPlugin for Wikidata { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "wikidata", + ..Default::default() + } + } +} diff --git a/import/src/plugins/wikimedia_commons.rs b/import/src/plugins/wikimedia_commons.rs index 86d934c..3d11d74 100644 --- a/import/src/plugins/wikimedia_commons.rs +++ b/import/src/plugins/wikimedia_commons.rs @@ -4,14 +4,17 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::USER_AGENT; +use crate::{ + USER_AGENT, + plugins::{ImportPlugin, PluginInfo}, +}; use anyhow::{Context, Result}; -use jellycache::{cache_store, EscapeKey}; +use jellycache::{EscapeKey, cache_store}; use jellycommon::Asset; use reqwest::{ + Client, ClientBuilder, header::{HeaderMap, HeaderName, HeaderValue}, redirect::Policy, - Client, ClientBuilder, }; use tokio::runtime::Handle; @@ -61,3 +64,12 @@ impl WikimediaCommons { .map(Asset) } } + +impl ImportPlugin for WikimediaCommons { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "wikimedia-commons", + ..Default::default() + } + } +} diff --git a/import/src/reporting.rs b/import/src/reporting.rs new file mode 100644 index 0000000..3105b59 --- /dev/null +++ b/import/src/reporting.rs @@ -0,0 +1,46 @@ +/* + 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) 2025 metamuffin <metamuffin.org> +*/ + +use anyhow::Result; +use rayon::{current_num_threads, current_thread_index}; +use tokio::sync::RwLock; + +pub static IMPORT_ERRORS: RwLock<Vec<String>> = RwLock::const_new(Vec::new()); +pub static IMPORT_PROGRESS: RwLock<Option<ImportProgress>> = RwLock::const_new(None); + +pub struct ImportProgress { + pub total_items: usize, + pub finished_items: usize, + pub tasks: Vec<String>, +} + +pub(crate) fn start_import() { + *IMPORT_ERRORS.blocking_write() = Vec::new(); + *IMPORT_PROGRESS.blocking_write() = Some(ImportProgress { + total_items: 0, + finished_items: 0, + tasks: vec!["idle".to_string(); current_num_threads()], + }); +} +pub(crate) fn end_import() { + *IMPORT_PROGRESS.blocking_write() = None; +} + +pub(crate) fn set_task(task: String) { + if let Some(p) = IMPORT_PROGRESS.blocking_write().as_mut() { + p.tasks[current_thread_index().unwrap_or(0)] = task + } +} + +pub(crate) fn catch<T>(res: Result<T>) -> Option<T> { + match res { + Ok(res) => Some(res), + Err(e) => { + IMPORT_ERRORS.blocking_write().push(format!("{e:#}")); + None + } + } +} |