aboutsummaryrefslogtreecommitdiff
path: root/import
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-12-10 17:52:41 +0100
committermetamuffin <metamuffin@disroot.org>2025-12-10 17:52:41 +0100
commite4f865e9da9d6660399e22a6fbeb5b84a749b07a (patch)
tree4af69589e8850d8a2b0c88a10e43efe8c79cb8dc /import
parenta0cfd77b4d19c43a28c4d82072e6ff136e336af3 (diff)
downloadjellything-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.rs113
-rw-r--r--import/src/plugins/acoustid.rs9
-rw-r--r--import/src/plugins/infojson.rs10
-rw-r--r--import/src/plugins/media_info.rs9
-rw-r--r--import/src/plugins/misc.rs60
-rw-r--r--import/src/plugins/mod.rs53
-rw-r--r--import/src/plugins/musicbrainz.rs14
-rw-r--r--import/src/plugins/tags.rs9
-rw-r--r--import/src/plugins/tmdb.rs22
-rw-r--r--import/src/plugins/trakt.rs11
-rw-r--r--import/src/plugins/vgmdb.rs20
-rw-r--r--import/src/plugins/wikidata.rs14
-rw-r--r--import/src/plugins/wikimedia_commons.rs18
-rw-r--r--import/src/reporting.rs46
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
+ }
+ }
+}