diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-02-20 21:02:33 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-02-20 21:02:33 +0100 |
| commit | 8b2cf84bc7a80f9a45aa350a2c98949bfed4b7c1 (patch) | |
| tree | 1611e9f7e221427cd0ff5c18da2dec87c19720de /import/src | |
| parent | 5caf1f1db721d6dee2ddb5d0613e8c9914ccf879 (diff) | |
| download | jellything-8b2cf84bc7a80f9a45aa350a2c98949bfed4b7c1.tar jellything-8b2cf84bc7a80f9a45aa350a2c98949bfed4b7c1.tar.bz2 jellything-8b2cf84bc7a80f9a45aa350a2c98949bfed4b7c1.tar.zst | |
source ranks
Diffstat (limited to 'import/src')
| -rw-r--r-- | import/src/lib.rs | 29 | ||||
| -rw-r--r-- | import/src/plugins/acoustid.rs | 33 | ||||
| -rw-r--r-- | import/src/plugins/infojson.rs | 32 | ||||
| -rw-r--r-- | import/src/plugins/media_info.rs | 16 | ||||
| -rw-r--r-- | import/src/plugins/misc.rs | 42 | ||||
| -rw-r--r-- | import/src/plugins/mod.rs | 5 | ||||
| -rw-r--r-- | import/src/plugins/musicbrainz.rs | 11 | ||||
| -rw-r--r-- | import/src/plugins/omdb.rs | 10 | ||||
| -rw-r--r-- | import/src/plugins/tags.rs | 13 | ||||
| -rw-r--r-- | import/src/plugins/tmdb.rs | 40 | ||||
| -rw-r--r-- | import/src/plugins/trakt.rs | 26 | ||||
| -rw-r--r-- | import/src/plugins/vgmdb.rs | 4 | ||||
| -rw-r--r-- | import/src/plugins/wikidata.rs | 4 | ||||
| -rw-r--r-- | import/src/source_rank.rs | 85 |
14 files changed, 251 insertions, 99 deletions
diff --git a/import/src/lib.rs b/import/src/lib.rs index b31d356..7e402be 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -8,15 +8,17 @@ pub mod helpers; pub mod plugins; pub mod reporting; +pub mod source_rank; -use crate::plugins::{ - ImportPlugin, PluginContext, infojson::is_info_json, init_plugins, misc::is_cover, +use crate::{ + plugins::{ImportPlugin, PluginContext, infojson::is_info_json, init_plugins, misc::is_cover}, + source_rank::{ImportSource, SourceRanks}, }; use anyhow::{Context, Result, anyhow}; use jellycache::{Cache, HashKey}; use jellycommon::{ internal::{IM_MTIME, IM_PATH}, - jellyobject::{self, ObjectBuffer, Path as TagPath}, + jellyobject::{self, ObjectBuffer, Path as TagPath, Tag}, *, }; use jellydb::{Database, Filter, Query, RowNum, Sort}; @@ -153,6 +155,7 @@ pub async fn import_wrap(ic: ImportConfig, incremental: bool) -> Result<()> { fn import(ic: ImportConfig, rt: &Handle, incremental: bool) -> Result<()> { reporting::set_stage(format!("Initializing Plugins"), 0); let plugins = init_plugins(&ic.config.api); + let ranks = SourceRanks::new(); reporting::set_stage(format!("Indexing files"), 0); let files = Mutex::new(Vec::new()); @@ -171,7 +174,7 @@ fn import(ic: ImportConfig, rt: &Handle, incremental: bool) -> Result<()> { reporting::set_stage(format!("Importing files"), files.len()); files.into_par_iter().for_each(|(path, parent, iflags)| { reporting::set_task(format!("unknown: {path:?}")); - import_file(&ic, &rt, &nodes, &plugins, &path, parent, iflags); + import_file(&ic, &rt, &ranks, &nodes, &plugins, &path, parent, iflags); reporting::inc_finished(); reporting::set_task("idle".to_owned()); }); @@ -187,7 +190,7 @@ fn import(ic: ImportConfig, rt: &Handle, incremental: bool) -> Result<()> { swap(nodes.get_mut().unwrap(), &mut cur_nodes); cur_nodes.into_par_iter().for_each(|node| { reporting::set_task(format!("unknown: {node}")); - process_node(&ic, &rt, &plugins, &nodes, node); + process_node(&ic, &rt, &ranks, &plugins, &nodes, node); reporting::inc_finished(); reporting::set_task("idle".to_owned()); }); @@ -274,6 +277,7 @@ fn import_traverse( fn import_file( ic: &ImportConfig, rt: &Handle, + ranks: &SourceRanks, pending_nodes: &Mutex<HashSet<RowNum>>, plugins: &[Box<dyn ImportPlugin>], path: &Path, @@ -281,9 +285,13 @@ fn import_file( iflags: InheritedFlags, ) { let mut all_ok = true; - let ct = PluginContext { + let mut ct = PluginContext { ic, rt, + is: ImportSource { + tag: Tag::new(b"xxxx"), + ranks, + }, iflags, pending_nodes, }; @@ -299,6 +307,7 @@ fn import_file( for p in plugins { let inf = p.info(); if inf.handle_instruction { + ct.is.tag = inf.tag; reporting::set_task(format!("{}(inst): {path:?}", inf.name)); all_ok &= reporting::catch( p.instruction(&ct, parent, line) @@ -344,6 +353,7 @@ fn import_file( for p in plugins { let inf = p.info(); if inf.handle_instruction { + ct.is.tag = inf.tag; reporting::set_task(format!("{}(inst): {path:?}", inf.name)); all_ok &= reporting::catch( p.instruction(&ct, row, line) @@ -363,6 +373,7 @@ fn import_file( for p in plugins { let inf = p.info(); if inf.handle_media { + ct.is.tag = inf.tag; reporting::set_task(format!("{}(media): {path:?}", inf.name)); all_ok &= reporting::catch( p.media(&ct, row, path, &seg) @@ -375,6 +386,7 @@ fn import_file( for p in plugins { let inf = p.info(); if inf.handle_file { + ct.is.tag = inf.tag; reporting::set_task(format!("{}(file): {path:?}", inf.name)); all_ok &= reporting::catch( p.file(&ct, parent, path) @@ -393,6 +405,7 @@ fn import_file( fn process_node( dba: &ImportConfig, rt: &Handle, + ranks: &SourceRanks, plugins: &[Box<dyn ImportPlugin>], pending_nodes: &Mutex<HashSet<RowNum>>, node: RowNum, @@ -414,6 +427,10 @@ fn process_node( p.process( &PluginContext { ic: dba, + is: ImportSource { + tag: inf.tag, + ranks, + }, rt, iflags: InheritedFlags::default(), pending_nodes, diff --git a/import/src/plugins/acoustid.rs b/import/src/plugins/acoustid.rs index 8e502b0..c829aac 100644 --- a/import/src/plugins/acoustid.rs +++ b/import/src/plugins/acoustid.rs @@ -6,12 +6,11 @@ use crate::{ USER_AGENT, plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, }; use anyhow::{Context, Result}; use jellycache::{Cache, HashKey}; -use jellycommon::{ - IDENT_ACOUST_ID_TRACK, IDENT_MUSICBRAINZ_RECORDING, NO_IDENTIFIERS, jellyobject::Object, -}; +use jellycommon::*; use jellydb::RowNum; use jellyremuxer::matroska::Segment; use log::info; @@ -176,6 +175,7 @@ impl ImportPlugin for AcoustID { fn info(&self) -> PluginInfo { PluginInfo { name: "acoustid", + tag: MSOURCE_ACOUSTID, handle_media: true, ..Default::default() } @@ -193,23 +193,18 @@ impl ImportPlugin for AcoustID { let fp = acoustid_fingerprint(&ct.ic.cache, path)?; - if let Some((atid, mbid)) = self.get_atid_mbid(&ct.ic.cache, &fp, &ct.rt)? { - ct.ic.db.transaction(&mut |txn| { - let ob = txn.get(node)?.unwrap(); - let ob = ob.as_object(); - let ob = ob.insert( - NO_IDENTIFIERS, - ob.get(NO_IDENTIFIERS) - .unwrap_or(Object::EMPTY) - .insert(IDENT_ACOUST_ID_TRACK, &atid) - .as_object() - .insert(IDENT_MUSICBRAINZ_RECORDING, &mbid) - .as_object(), - ); - txn.update(node, ob)?; - Ok(()) - })?; + let Some((atid, mbid)) = self.get_atid_mbid(&ct.ic.cache, &fp, &ct.rt)? else { + return Ok(()); }; + + ct.ic.update_node(node, |node| { + node.as_object().update(NO_IDENTIFIERS, |ids| { + ids.insert(IDENT_ACOUST_ID_TRACK, &atid) + .as_object() + .insert_s(ct.is, IDENT_MUSICBRAINZ_RECORDING, &mbid) + }) + })?; + Ok(()) } } diff --git a/import/src/plugins/infojson.rs b/import/src/plugins/infojson.rs index 3928e7a..6905e57 100644 --- a/import/src/plugins/infojson.rs +++ b/import/src/plugins/infojson.rs @@ -3,7 +3,10 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::plugins::{ImportPlugin, PluginContext, PluginInfo}; +use crate::{ + plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, +}; use anyhow::{Context, Result, anyhow}; use chrono::{Utc, format::Parsed}; use jellycommon::*; @@ -160,6 +163,7 @@ impl ImportPlugin for Infojson { fn info(&self) -> PluginInfo { PluginInfo { name: "infojson", + tag: MSOURCE_INFOJSON, handle_file: true, handle_media: true, ..Default::default() @@ -177,20 +181,20 @@ impl ImportPlugin for Infojson { ct.ic.db.transaction(&mut |txn| { let mut node = txn.get(parent)?.unwrap(); - node = node.as_object().insert(NO_KIND, KIND_CHANNEL); - node = node.as_object().insert(NO_TITLE, title); + node = node.as_object().insert_s(ct.is, NO_KIND, KIND_CHANNEL); + node = node.as_object().insert_s(ct.is, NO_TITLE, title); if let Some(cid) = &data.channel_id { node = node.as_object().update(NO_IDENTIFIERS, |ids| { - ids.insert(IDENT_YOUTUBE_CHANNEL, &cid) + ids.insert_s(ct.is, IDENT_YOUTUBE_CHANNEL, &cid) }); } if let Some(uid) = &data.uploader_id { node = node.as_object().update(NO_IDENTIFIERS, |ids| { - ids.insert(IDENT_YOUTUBE_CHANNEL_HANDLE, &uid) + ids.insert_s(ct.is, IDENT_YOUTUBE_CHANNEL_HANDLE, &uid) }) } if let Some(desc) = &data.description { - node = node.as_object().insert(NO_DESCRIPTION, &desc); + node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &desc); } if let Some(followers) = data.channel_follower_count { node = node.as_object().update(NO_RATINGS, |rat| { @@ -238,23 +242,23 @@ impl ImportPlugin for Infojson { ct.ic.db.transaction(&mut |txn| { let mut node = txn.get(row)?.unwrap(); - node = node.as_object().insert(NO_KIND, kind); - node = node.as_object().insert(NO_TITLE, &infojson.title); + node = node.as_object().insert_s(ct.is, NO_KIND, kind); + node = node.as_object().insert_s(ct.is, NO_TITLE, &infojson.title); if let Some(title) = &infojson.alt_title && title != &infojson.title && !node.as_object().has(NO_SUBTITLE.0) { - node = node.as_object().insert(NO_SUBTITLE, &title); + node = node.as_object().insert_s(ct.is, NO_SUBTITLE, &title); } if let Some(up) = &infojson.uploader && !node.as_object().has(NO_SUBTITLE.0) { node = node .as_object() - .insert(NO_SUBTITLE, &clean_uploader_name(&up)); + .insert_s(ct.is, NO_SUBTITLE, &clean_uploader_name(&up)); } if let Some(desc) = &infojson.description { - node = node.as_object().insert(NO_DESCRIPTION, &desc); + node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &desc); } if let Some(tag) = infojson.tags.clone() { node = node @@ -262,12 +266,12 @@ impl ImportPlugin for Infojson { .extend(NO_TAG, tag.iter().map(String::as_str)); } if let Some(rd) = release_date { - node = node.as_object().insert(NO_RELEASEDATE, rd); + node = node.as_object().insert_s(ct.is, NO_RELEASEDATE, rd); } match infojson.extractor.as_str() { "youtube" => { node = node.as_object().update(NO_IDENTIFIERS, |rat| { - rat.insert(IDENT_YOUTUBE_VIDEO, &infojson.id) + rat.insert_s(ct.is, IDENT_YOUTUBE_VIDEO, &infojson.id) }); node = node.as_object().update(NO_RATINGS, |rat| { rat.insert( @@ -284,7 +288,7 @@ impl ImportPlugin for Infojson { } "Bandcamp" => { node = node.as_object().update(NO_IDENTIFIERS, |rat| { - rat.insert(IDENT_BANDCAMP, &infojson.id) + rat.insert_s(ct.is, IDENT_BANDCAMP, &infojson.id) }); } _ => (), diff --git a/import/src/plugins/media_info.rs b/import/src/plugins/media_info.rs index 11da365..f21386e 100644 --- a/import/src/plugins/media_info.rs +++ b/import/src/plugins/media_info.rs @@ -4,7 +4,10 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::plugins::{ImportPlugin, PluginContext, PluginInfo}; +use crate::{ + plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, +}; use anyhow::Result; use jellycommon::{ jellyobject::{Object, ObjectBuffer}, @@ -19,6 +22,7 @@ impl ImportPlugin for MediaInfo { fn info(&self) -> PluginInfo { PluginInfo { name: "media-info", + tag: MSOURCE_MEDIA, handle_media: true, ..Default::default() } @@ -101,13 +105,11 @@ impl ImportPlugin for MediaInfo { ); } - node = node.as_object().insert( - NO_DURATION, - fix_invalid_runtime( - seg.info.duration.unwrap_or_default() * seg.info.timestamp_scale as f64 * 1e-9, - ), + let runtime = fix_invalid_runtime( + seg.info.duration.unwrap_or_default() * seg.info.timestamp_scale as f64 * 1e-9, ); - node = node.as_object().insert(NO_STORAGE_SIZE, size); + node = node.as_object().insert_s(ct.is, NO_DURATION, runtime); + node = node.as_object().insert_s(ct.is, NO_STORAGE_SIZE, size); txn.update(row, node)?; diff --git a/import/src/plugins/misc.rs b/import/src/plugins/misc.rs index 1699f6d..2c56dae 100644 --- a/import/src/plugins/misc.rs +++ b/import/src/plugins/misc.rs @@ -3,7 +3,10 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::plugins::{ImportPlugin, PluginContext, PluginInfo}; +use crate::{ + plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, +}; use anyhow::{Context, Result, bail}; use jellycache::HashKey; use jellycommon::*; @@ -23,6 +26,7 @@ impl ImportPlugin for ImageFiles { fn info(&self) -> PluginInfo { PluginInfo { name: "image-files", + tag: MSOURCE_EXPLICIT, handle_file: true, ..Default::default() } @@ -47,7 +51,7 @@ impl ImportPlugin for ImageFiles { let mut node = txn.get(row)?.unwrap(); node = node .as_object() - .update(NO_PICTURES, |picts| picts.insert(slot, &asset)); + .update(NO_PICTURES, |picts| picts.insert_s(ct.is, slot, &asset)); txn.update(row, node)?; Ok(()) })?; @@ -63,6 +67,7 @@ impl ImportPlugin for ImageAttachments { fn info(&self) -> PluginInfo { PluginInfo { name: "image-attachments", + tag: MSOURCE_IMAGE_ATT, handle_media: true, ..Default::default() } @@ -79,8 +84,9 @@ impl ImportPlugin for ImageAttachments { }; ct.ic.update_node(row, |node| { - node.as_object() - .update(NO_PICTURES, |picts| picts.insert(PICT_COVER, &cover)) + node.as_object().update(NO_PICTURES, |picts| { + picts.insert_s(ct.is, PICT_COVER, &cover) + }) })?; Ok(()) } @@ -91,6 +97,7 @@ impl ImportPlugin for General { fn info(&self) -> PluginInfo { PluginInfo { name: "general", + tag: MSOURCE_EXPLICIT, handle_instruction: true, ..Default::default() } @@ -98,12 +105,13 @@ impl ImportPlugin for General { fn instruction(&self, ct: &PluginContext, node: RowNum, line: &str) -> Result<()> { if line == "hidden" { ct.ic.update_node(node, |node| { - node.as_object().insert(NO_VISIBILITY, VISI_HIDDEN) + node.as_object().insert_s(ct.is, NO_VISIBILITY, VISI_HIDDEN) })?; } if line == "reduced" { ct.ic.update_node(node, |node| { - node.as_object().insert(NO_VISIBILITY, VISI_REDUCED) + node.as_object() + .insert_s(ct.is, NO_VISIBILITY, VISI_REDUCED) })?; } if let Some(kind) = line.strip_prefix("kind-").or(line.strip_prefix("kind=")) { @@ -121,16 +129,18 @@ impl ImportPlugin for General { _ => bail!("unknown node kind"), }; ct.ic - .update_node(node, |node| node.as_object().insert(NO_KIND, kind))?; + .update_node(node, |node| node.as_object().insert_s(ct.is, NO_KIND, kind))?; } if let Some(title) = line.strip_prefix("title=") { - ct.ic - .update_node(node, |node| node.as_object().insert(NO_TITLE, title))?; + ct.ic.update_node(node, |node| { + node.as_object().insert_s(ct.is, NO_TITLE, title) + })?; } if let Some(index) = line.strip_prefix("index=") { let index = index.parse().context("parse index")?; - ct.ic - .update_node(node, |node| node.as_object().insert(NO_INDEX, index))?; + ct.ic.update_node(node, |node| { + node.as_object().insert_s(ct.is, NO_INDEX, index) + })?; } Ok(()) } @@ -141,6 +151,7 @@ impl ImportPlugin for Children { fn info(&self) -> PluginInfo { PluginInfo { name: "children", + tag: MSOURCE_EXPLICIT, handle_file: true, ..Default::default() } @@ -155,7 +166,7 @@ impl ImportPlugin for Children { continue; } ct.ic - .update_node_slug(line, |n| n.as_object().insert(NO_PARENT, parent))?; + .update_node_slug(line, |n| n.as_object().extend(NO_PARENT, [parent]))?; } } Ok(()) @@ -170,6 +181,7 @@ impl ImportPlugin for EpisodeIndex { fn info(&self) -> PluginInfo { PluginInfo { name: "episode-info", + tag: MSOURCE_IMAGE_ATT, handle_media: true, ..Default::default() } @@ -186,9 +198,9 @@ impl ImportPlugin for EpisodeIndex { .context("parse season num")?; ct.ic.update_node(node, |mut node| { - node = node.as_object().insert(NO_SEASON_INDEX, season); - node = node.as_object().insert(NO_INDEX, episode); - node = node.as_object().insert(NO_KIND, KIND_EPISODE); + node = node.as_object().insert_s(ct.is, NO_SEASON_INDEX, season); + node = node.as_object().insert_s(ct.is, NO_INDEX, episode); + node = node.as_object().insert_s(ct.is, NO_KIND, KIND_EPISODE); node })?; } diff --git a/import/src/plugins/mod.rs b/import/src/plugins/mod.rs index 60bf09b..8fd1e67 100644 --- a/import/src/plugins/mod.rs +++ b/import/src/plugins/mod.rs @@ -15,8 +15,9 @@ pub mod trakt; pub mod vgmdb; pub mod wikidata; -use crate::{ApiSecrets, ImportConfig, InheritedFlags}; +use crate::{ApiSecrets, ImportConfig, InheritedFlags, source_rank::ImportSource}; use anyhow::Result; +use jellycommon::jellyobject::Tag; use jellydb::RowNum; use jellyremuxer::matroska::Segment; use std::{collections::HashSet, path::Path, sync::Mutex}; @@ -25,6 +26,7 @@ use tokio::runtime::Handle; pub struct PluginContext<'a> { pub ic: &'a ImportConfig, pub rt: &'a Handle, + pub is: ImportSource<'a>, pub iflags: InheritedFlags, pub pending_nodes: &'a Mutex<HashSet<RowNum>>, } @@ -32,6 +34,7 @@ pub struct PluginContext<'a> { #[derive(Default, Clone, Copy)] pub struct PluginInfo { pub name: &'static str, + pub tag: Tag, pub handle_file: bool, pub handle_media: bool, pub handle_instruction: bool, diff --git a/import/src/plugins/musicbrainz.rs b/import/src/plugins/musicbrainz.rs index 4dfd974..454c562 100644 --- a/import/src/plugins/musicbrainz.rs +++ b/import/src/plugins/musicbrainz.rs @@ -11,6 +11,7 @@ use crate::{ ImportPlugin, PluginContext, PluginInfo, musicbrainz::reltypes::{VGMDB, WIKIDATA}, }, + source_rank::ObjectImportSourceExt, }; use anyhow::{Context, Result}; use jellycache::Cache; @@ -339,6 +340,7 @@ impl ImportPlugin for MusicBrainz { fn info(&self) -> PluginInfo { PluginInfo { name: "musicbrainz", + tag: MSOURCE_MUSICBRAINZ, handle_process: true, ..Default::default() } @@ -367,9 +369,11 @@ impl MusicBrainz { ct.ic.db.transaction(&mut |txn| { let mut node = txn.get(node_row)?.unwrap(); - node = node.as_object().insert(NO_TITLE, &rec.title); + node = node.as_object().insert_s(ct.is, NO_TITLE, &rec.title); if let Some(a) = rec.artist_credit.first() { - node = node.as_object().insert(NO_SUBTITLE, &a.artist.name); + node = node + .as_object() + .insert_s(ct.is, NO_SUBTITLE, &a.artist.name); } node = node.as_object().update(NO_IDENTIFIERS, |ids| { ids.insert_multi( @@ -436,8 +440,7 @@ impl MusicBrainz { ct.ic.db.transaction(&mut |txn| { let mut node = txn.get(node_row)?.unwrap(); - - node = node.as_object().insert(NO_TITLE, &artist.name); + node = node.as_object().insert_s(ct.is, NO_TITLE, &artist.name); for rel in &artist.relations { let url = rel.url.as_ref().map(|u| u.resource.clone()); diff --git a/import/src/plugins/omdb.rs b/import/src/plugins/omdb.rs index bb58c6b..20fb933 100644 --- a/import/src/plugins/omdb.rs +++ b/import/src/plugins/omdb.rs @@ -9,7 +9,8 @@ use std::sync::Arc; use anyhow::{Context, Result, anyhow}; use jellycache::Cache; use jellycommon::{ - IDENT_IMDB, NO_IDENTIFIERS, NO_RATINGS, RTYP_IMDB, RTYP_METACRITIC, RTYP_ROTTEN_TOMATOES, + IDENT_IMDB, MSOURCE_OMDB, NO_DESCRIPTION, NO_IDENTIFIERS, NO_RATINGS, NO_TITLE, RTYP_IMDB, + RTYP_METACRITIC, RTYP_ROTTEN_TOMATOES, }; use jellydb::RowNum; use log::info; @@ -23,6 +24,7 @@ use tokio::runtime::Handle; use crate::{ USER_AGENT, plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, }; pub struct Omdb { @@ -115,6 +117,7 @@ impl ImportPlugin for Omdb { fn info(&self) -> PluginInfo { PluginInfo { name: "omdb", + tag: MSOURCE_OMDB, handle_process: true, ..Default::default() } @@ -151,6 +154,11 @@ impl ImportPlugin for Omdb { .transpose()?; ct.ic.update_node(node, |mut node| { + node = node.as_object().insert_s(ct.is, NO_TITLE, &entry.title); + node = node + .as_object() + .insert_s(ct.is, NO_DESCRIPTION, &entry.plot); + for (typ, val) in [ (RTYP_METACRITIC, metascore), (RTYP_IMDB, imdb), diff --git a/import/src/plugins/tags.rs b/import/src/plugins/tags.rs index bd7d0bc..b60ef37 100644 --- a/import/src/plugins/tags.rs +++ b/import/src/plugins/tags.rs @@ -4,7 +4,10 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::plugins::{PluginContext, ImportPlugin, PluginInfo}; +use crate::{ + plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, +}; use anyhow::Result; use jellycommon::*; use jellydb::RowNum; @@ -16,6 +19,7 @@ impl ImportPlugin for Tags { fn info(&self) -> PluginInfo { PluginInfo { name: "tags", + tag: MSOURCE_TAGS, handle_media: true, ..Default::default() } @@ -35,16 +39,17 @@ impl ImportPlugin for Tags { ct.ic.update_node(node, |mut node| { if let Some(title) = &seg.info.title { - node = node.as_object().insert(NO_TITLE, title); + node = node.as_object().insert_s(ct.is, NO_TITLE, title); } for (key, value) in &tags { match key.as_str() { "DESCRIPTION" | "SYNOPSIS" => { - node = node.as_object().insert(NO_DESCRIPTION, &value) + node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &value) } "COMMENT" => node = node.as_object().insert(NO_TAGLINE, &value), "CONTENT_TYPE" => { - node = node.as_object().insert( + node = node.as_object().insert_s( + ct.is, NO_KIND, match value.to_lowercase().trim() { "movie" | "documentary" | "film" => KIND_MOVIE, diff --git a/import/src/plugins/tmdb.rs b/import/src/plugins/tmdb.rs index ab0a679..db336fe 100644 --- a/import/src/plugins/tmdb.rs +++ b/import/src/plugins/tmdb.rs @@ -6,6 +6,7 @@ use crate::{ USER_AGENT, plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, }; use anyhow::{Context, Result, anyhow, bail}; use chrono::{Utc, format::Parsed}; @@ -180,6 +181,7 @@ impl ImportPlugin for Tmdb { fn info(&self) -> PluginInfo { PluginInfo { name: "tmdb", + tag: MSOURCE_TMDB, handle_process: true, ..Default::default() } @@ -235,27 +237,31 @@ impl Tmdb { ct.ic.update_node(node, |mut node| { if let Some(title) = &details.title { - node = node.as_object().insert(NO_TITLE, &title); + node = node.as_object().insert_s(ct.is, NO_TITLE, &title); } if let Some(tagline) = &details.tagline { - node = node.as_object().insert(NO_TAGLINE, &tagline); + node = node.as_object().insert_s(ct.is, NO_TAGLINE, &tagline); } - node = node.as_object().insert(NO_DESCRIPTION, &details.overview); + node = node + .as_object() + .insert_s(ct.is, NO_DESCRIPTION, &details.overview); node = node.as_object().update(NO_RATINGS, |rat| { rat.insert(RTYP_TMDB, details.vote_average) }); if let Some(poster) = &poster { node = node .as_object() - .update(NO_PICTURES, |rat| rat.insert(PICT_COVER, &poster)); + .update(NO_PICTURES, |rat| rat.insert_s(ct.is, PICT_COVER, &poster)); } if let Some(backdrop) = &backdrop { - node = node - .as_object() - .update(NO_PICTURES, |rat| rat.insert(PICT_BACKDROP, &backdrop)); + node = node.as_object().update(NO_PICTURES, |rat| { + rat.insert_s(ct.is, PICT_BACKDROP, &backdrop) + }); } if let Some(releasedate) = release_date { - node = node.as_object().insert(NO_RELEASEDATE, releasedate); + node = node + .as_object() + .insert_s(ct.is, NO_RELEASEDATE, releasedate); } node })?; @@ -296,18 +302,22 @@ impl Tmdb { .context("still image download")?; let release_date = parse_release_date(&details.air_date)?; ct.ic.update_node(node, |mut node| { - node = node.as_object().insert(NO_TITLE, &details.name); - node = node.as_object().insert(NO_DESCRIPTION, &details.overview); + node = node.as_object().insert_s(ct.is, NO_TITLE, &details.name); + node = node + .as_object() + .insert_s(ct.is, NO_DESCRIPTION, &details.overview); if let Some(release_date) = release_date { - node = node.as_object().insert(NO_RELEASEDATE, release_date) + node = node + .as_object() + .insert_s(ct.is, NO_RELEASEDATE, release_date) } node = node.as_object().update(NO_RATINGS, |rat| { rat.insert(RTYP_TMDB, details.vote_average) }); if let Some(cover) = &cover { - node = node - .as_object() - .update(NO_PICTURES, |picts| picts.insert(PICT_COVER, &cover)); + node = node.as_object().update(NO_PICTURES, |picts| { + picts.insert_s(ct.is, PICT_COVER, &cover) + }); } node }) @@ -335,7 +345,7 @@ impl Tmdb { ct.ic.update_node(node, |node| { node.as_object() - .update(NO_PICTURES, |pict| pict.insert(PICT_COVER, &image)) + .update(NO_PICTURES, |pict| pict.insert_s(ct.is, PICT_COVER, &image)) })?; Ok(()) diff --git a/import/src/plugins/trakt.rs b/import/src/plugins/trakt.rs index 1d01436..cc3b119 100644 --- a/import/src/plugins/trakt.rs +++ b/import/src/plugins/trakt.rs @@ -7,6 +7,7 @@ use crate::{ USER_AGENT, helpers::get_or_insert_slug, plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, }; use anyhow::{Context, Result, anyhow, bail}; use jellycache::{Cache, HashKey}; @@ -404,6 +405,7 @@ impl ImportPlugin for Trakt { fn info(&self) -> PluginInfo { PluginInfo { name: "trakt", + tag: MSOURCE_TRAKT, handle_instruction: true, handle_process: true, ..Default::default() @@ -470,13 +472,15 @@ impl Trakt { ct.ic.db.transaction(&mut |txn| { let mut node = txn.get(node_row)?.unwrap(); - node = node.as_object().insert(NO_KIND, trakt_kind.as_node_kind()); - node = node.as_object().insert(NO_TITLE, &details.title); + node = node + .as_object() + .insert_s(ct.is, NO_KIND, trakt_kind.as_node_kind()); + node = node.as_object().insert_s(ct.is, NO_TITLE, &details.title); if let Some(overview) = &details.overview { - node = node.as_object().insert(NO_DESCRIPTION, &overview); + node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &overview); } if let Some(tagline) = &details.tagline { - node = node.as_object().insert(NO_TAGLINE, &tagline); + node = node.as_object().insert_s(ct.is, NO_TAGLINE, &tagline); } if let Some(x) = details.ids.imdb.clone() { node = node @@ -520,9 +524,9 @@ impl Trakt { let row = get_or_insert_slug(txn, &slug)?; let mut c = txn.get(row)?.unwrap(); - c = c.as_object().insert(NO_KIND, KIND_PERSON); - c = c.as_object().insert(NO_VISIBILITY, VISI_VISIBLE); - c = c.as_object().insert(NO_TITLE, &ap.person.name); + c = c.as_object().insert_s(ct.is, NO_KIND, KIND_PERSON); + c = c.as_object().insert_s(ct.is, NO_VISIBILITY, VISI_VISIBLE); + c = c.as_object().insert_s(ct.is, NO_TITLE, &ap.person.name); c = c.as_object().update(NO_IDENTIFIERS, |ids| { let mut ids = ids.insert(IDENT_TRAKT_PERSON, &traktid.to_string()); if let Some(tmdbid) = ap.person.ids.tmdb { @@ -591,11 +595,11 @@ impl Trakt { let episodes = self.show_season_episodes(&ct.ic.cache, show_id, season, ct.rt)?; if let Some(episode) = episodes.get(episode.saturating_sub(1) as usize) { ct.ic.update_node(node, |mut node| { - node = node.as_object().insert(NO_KIND, KIND_EPISODE); - node = node.as_object().insert(NO_INDEX, episode.number); - node = node.as_object().insert(NO_TITLE, &episode.title); + node = node.as_object().insert_s(ct.is, NO_KIND, KIND_EPISODE); + node = node.as_object().insert_s(ct.is, NO_INDEX, episode.number); + node = node.as_object().insert_s(ct.is, NO_TITLE, &episode.title); if let Some(overview) = &episode.overview { - node = node.as_object().insert(NO_DESCRIPTION, &overview); + node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &overview); } if let Some(r) = episode.rating { node = node diff --git a/import/src/plugins/vgmdb.rs b/import/src/plugins/vgmdb.rs index b7630d2..4e1b273 100644 --- a/import/src/plugins/vgmdb.rs +++ b/import/src/plugins/vgmdb.rs @@ -7,6 +7,7 @@ use crate::{ USER_AGENT, plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, }; use anyhow::{Context, Result}; use jellycache::{Cache, HashKey}; @@ -140,6 +141,7 @@ impl ImportPlugin for Vgmdb { fn info(&self) -> PluginInfo { PluginInfo { name: "vgmdb", + tag: MSOURCE_VGMDB, handle_process: true, ..Default::default() } @@ -164,7 +166,7 @@ impl ImportPlugin for Vgmdb { ct.ic.update_node(node, |node| { node.as_object() - .update(NO_PICTURES, |pics| pics.insert(PICT_COVER, &image)) + .update(NO_PICTURES, |pics| pics.insert_s(ct.is, PICT_COVER, &image)) })?; Ok(()) diff --git a/import/src/plugins/wikidata.rs b/import/src/plugins/wikidata.rs index 2286f8d..b92fdbb 100644 --- a/import/src/plugins/wikidata.rs +++ b/import/src/plugins/wikidata.rs @@ -7,6 +7,7 @@ use crate::{ USER_AGENT, plugins::{ImportPlugin, PluginContext, PluginInfo}, + source_rank::ObjectImportSourceExt, }; use anyhow::{Context, Result, bail}; use jellycache::{Cache, EscapeKey}; @@ -195,6 +196,7 @@ impl ImportPlugin for Wikidata { fn info(&self) -> PluginInfo { PluginInfo { name: "wikidata", + tag: MSOURCE_WIKIDATA, handle_process: true, ..Default::default() } @@ -221,7 +223,7 @@ impl ImportPlugin for Wikidata { ct.ic.update_node(node, |node| { node.as_object() - .update(NO_PICTURES, |pics| pics.insert(PICT_COVER, &image)) + .update(NO_PICTURES, |pics| pics.insert_s(ct.is, PICT_COVER, &image)) })?; Ok(()) diff --git a/import/src/source_rank.rs b/import/src/source_rank.rs new file mode 100644 index 0000000..28ab4f7 --- /dev/null +++ b/import/src/source_rank.rs @@ -0,0 +1,85 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +use jellycommon::{ + jellyobject::{Object, ObjectBuffer, Tag, TypedTag, ValueStore}, + *, +}; +use std::marker::PhantomData; + +pub struct SourceRanks { + list: Vec<Tag>, +} + +#[derive(Clone, Copy)] +pub struct ImportSource<'a> { + pub tag: Tag, + pub ranks: &'a SourceRanks, +} + +pub trait ObjectImportSourceExt { + fn insert_s<T: ValueStore>(&self, is: ImportSource, key: TypedTag<T>, value: T) + -> ObjectBuffer; +} +impl<'a> ObjectImportSourceExt for Object<'a> { + fn insert_s<T: ValueStore>( + &self, + is: ImportSource, + key: TypedTag<T>, + value: T, + ) -> ObjectBuffer { + let ms = self.get(NO_METASOURCE).unwrap_or_default(); + let ms_key = TypedTag::<Tag>(key.0, PhantomData); + + if let Some(current_source) = ms.get(ms_key) { + if !is.ranks.compare(key.0, current_source, is.tag) { + return self.dump(); + } + } + + self.insert(key, value) + .as_object() + .update(NO_METASOURCE, |ms| ms.insert(ms_key, is.tag)) + } +} + +impl SourceRanks { + pub fn new() -> Self { + Self { + list: [ + MSOURCE_EXPLICIT, + MSOURCE_TRAKT, + MSOURCE_MUSICBRAINZ, + MSOURCE_MEDIA, + MSOURCE_TAGS, + MSOURCE_IMAGE_ATT, + MSOURCE_TMDB, + MSOURCE_WIKIDATA, + MSOURCE_VGMDB, + MSOURCE_ACOUSTID, + MSOURCE_INFOJSON, + MSOURCE_OMDB, + ] + .to_vec(), + } + } + pub fn compare(&self, key: Tag, old: Tag, new: Tag) -> bool { + let _ = key; + + let old_index = self + .list + .iter() + .position(|e| *e == old) + .unwrap_or(usize::MAX); + let new_index = self + .list + .iter() + .position(|e| *e == new) + .unwrap_or(usize::MAX); + + new_index <= old_index + } +} |