diff options
author | metamuffin <metamuffin@disroot.org> | 2025-05-26 18:24:16 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-05-26 18:24:16 +0200 |
commit | 3b15caade07e8fbe351fed9aceb3f435bf58368e (patch) | |
tree | cce91c229b78061ad36f29d76a76d67c3c737c59 /server/src/compat/jellyfin | |
parent | 1eeff5c03e8985d16d4f2b6283741dd82b369bd3 (diff) | |
download | jellything-3b15caade07e8fbe351fed9aceb3f435bf58368e.tar jellything-3b15caade07e8fbe351fed9aceb3f435bf58368e.tar.bz2 jellything-3b15caade07e8fbe351fed9aceb3f435bf58368e.tar.zst |
move all direct database access to logic crate
Diffstat (limited to 'server/src/compat/jellyfin')
-rw-r--r-- | server/src/compat/jellyfin/mod.rs | 296 | ||||
-rw-r--r-- | server/src/compat/jellyfin/models.rs | 8 |
2 files changed, 154 insertions, 150 deletions
diff --git a/server/src/compat/jellyfin/mod.rs b/server/src/compat/jellyfin/mod.rs index e8a74d7..0a901b2 100644 --- a/server/src/compat/jellyfin/mod.rs +++ b/server/src/compat/jellyfin/mod.rs @@ -6,19 +6,22 @@ pub mod models; use crate::{helper::A, ui::error::MyResult}; -use anyhow::{anyhow, Context}; +use anyhow::anyhow; use jellycommon::{ - api::{FilterProperty, NodeFilterSort, SortOrder, SortProperty}, + api::{NodeFilterSort, SortOrder, SortProperty}, routes::{u_asset, u_node_slug_backdrop, u_node_slug_poster}, stream::{StreamContainer, StreamSpec}, user::{NodeUserData, WatchedState}, MediaInfo, Node, NodeID, NodeKind, SourceTrack, SourceTrackKind, Visibility, }; use jellylogic::{ - filter_sort::filter_and_sort_nodes, login::login_logic, node::DatabaseNodeUserDataExt, - session::Session, Database, + login::login_logic, + node::{get_node, update_node_userdata_watched_progress}, + search::search, + session::Session, }; use jellyui::{get_brand, get_slogan, node_page::aspect_class}; +use log::warn; use models::*; use rocket::{ get, @@ -26,16 +29,18 @@ use rocket::{ post, response::Redirect, serde::json::Json, - FromForm, State, + FromForm, }; use serde::Deserialize; use serde_json::{json, Value}; use std::{collections::BTreeMap, net::IpAddr}; +// these are both random values. idk what they are for const SERVER_ID: &str = "1694a95daf70708147f16103ce7b7566"; const USER_ID: &str = "33f772aae6c2495ca89fe00340dbd17c"; + const VERSION: &str = "10.10.0"; -const LOCAL_ADDRESS: &str = "http://127.0.0.1:8000"; +const LOCAL_ADDRESS: &str = "http://127.0.0.1:8000"; // TODO #[get("/System/Info/Public")] pub fn r_jellyfin_system_info_public_case() -> Json<Value> { @@ -182,24 +187,26 @@ pub fn r_jellyfin_items_images_backdrop( #[get("/Items/<id>")] #[allow(private_interfaces)] -pub fn r_jellyfin_items_item( - session: A<Session>, - database: &State<Database>, - id: &str, -) -> MyResult<Json<JellyfinItem>> { - let (n, ud) = database.get_node_with_userdata(NodeID::from_slug(id), &session.0)?; - Ok(Json(item_object(&n, &ud))) +pub fn r_jellyfin_items_item(session: A<Session>, id: &str) -> MyResult<Json<JellyfinItem>> { + let r = get_node( + &session.0, + NodeID::from_slug(id), + false, + false, + NodeFilterSort::default(), + )?; + Ok(Json(item_object(&r.node, &r.userdata))) } + #[get("/Users/<uid>/Items/<id>")] #[allow(private_interfaces)] pub fn r_jellyfin_users_items_item( session: A<Session>, - database: &State<Database>, uid: &str, id: &str, ) -> MyResult<Json<JellyfinItem>> { let _ = uid; - r_jellyfin_items_item(session, database, id) + r_jellyfin_items_item(session, id) } #[derive(Debug, FromForm)] @@ -223,112 +230,118 @@ struct JellyfinItemQuery { #[allow(private_interfaces)] pub fn r_jellyfin_users_items( session: A<Session>, - database: &State<Database>, uid: &str, query: JellyfinItemQuery, -) -> MyResult<Json<Value>> { +) -> MyResult<Json<JellyfinItemsResponse>> { let _ = uid; - r_jellyfin_items(session, database, query) + r_jellyfin_items(session, query) } #[get("/Artists?<query..>")] #[allow(private_interfaces)] pub fn r_jellyfin_artists( session: A<Session>, - database: &State<Database>, mut query: JellyfinItemQuery, -) -> MyResult<Json<Value>> { +) -> MyResult<Json<JellyfinItemsResponse>> { query.internal_artists = true; - r_jellyfin_items(session, database, query)?; // TODO - Ok(Json(json!({ - "Items": [], - "TotalRecordCount": 0, - "StartIndex": 0 - }))) + r_jellyfin_items(session, query)?; // TODO + Ok(Json(JellyfinItemsResponse::default())) } #[get("/Persons?<query..>")] #[allow(private_interfaces)] pub fn r_jellyfin_persons( session: A<Session>, - database: &State<Database>, mut query: JellyfinItemQuery, -) -> MyResult<Json<Value>> { +) -> MyResult<Json<JellyfinItemsResponse>> { query.internal_persons = true; - r_jellyfin_items(session, database, query)?; // TODO - Ok(Json(json!({ - "Items": [], - "TotalRecordCount": 0, - "StartIndex": 0 - }))) + r_jellyfin_items(session, query)?; // TODO + Ok(Json(JellyfinItemsResponse::default())) } #[get("/Items?<query..>")] #[allow(private_interfaces)] pub fn r_jellyfin_items( session: A<Session>, - database: &State<Database>, query: JellyfinItemQuery, -) -> MyResult<Json<Value>> { - let (nodes, parent_kind) = if let Some(q) = query.search_term { - ( - database - .search(&q, query.limit, query.start_index.unwrap_or_default())? - .1, - None, - ) +) -> MyResult<Json<JellyfinItemsResponse>> { + // let (nodes, parent_kind) = if let Some(q) = query.search_term { + // ( + // database + // .search(&q, query.limit, query.start_index.unwrap_or_default())? + // .1, + // None, + // ) + // } else if let Some(parent) = query.parent_id { + // let parent = NodeID::from_slug(&parent); + // ( + // database + // .get_node_children(parent)? + // .into_iter() + // .skip(query.start_index.unwrap_or_default()) + // .take(query.limit) + // .collect(), + // database.get_node(parent)?.map(|n| n.kind), + // ) + // } else { + // (vec![], None) + // }; + + // let filter_kind = query + // .include_item_types + // .map(|n| match n.as_str() { + // "Movie" => vec![FilterProperty::KindMovie], + // "Audio" => vec![FilterProperty::KindMusic], + // "Video" => vec![FilterProperty::KindVideo], + // "TvChannel" => vec![FilterProperty::KindChannel], + // _ => vec![], + // }) + // .or(if query.internal_artists { + // Some(vec![]) + // } else { + // None + // }) + // .or(if query.internal_persons { + // Some(vec![]) + // } else { + // None + // }); + + // let mut nodes = nodes + // .into_iter() + // .map(|nid| database.get_node_with_userdata(nid, &session.0)) + // .collect::<Result<Vec<_>, anyhow::Error>>()?; + + // filter_and_sort_nodes( + // &NodeFilterSort { + // sort_by: None, + // filter_kind, + // sort_order: None, + // }, + // match parent_kind { + // Some(NodeKind::Channel) => (SortProperty::ReleaseDate, SortOrder::Descending), + // _ => (SortProperty::Title, SortOrder::Ascending), + // }, + // &mut nodes, + // ); + + let nodes = if let Some(q) = query.search_term { + search(&session.0, &q, query.start_index.map(|x| x / 50))?.results // TODO } else if let Some(parent) = query.parent_id { - let parent = NodeID::from_slug(&parent); - ( - database - .get_node_children(parent)? - .into_iter() - .skip(query.start_index.unwrap_or_default()) - .take(query.limit) - .collect(), - database.get_node(parent)?.map(|n| n.kind), - ) + get_node( + &session.0, + NodeID::from_slug(&parent), + true, + false, + NodeFilterSort::default(), + )? + .children } else { - (vec![], None) + warn!("unknown items request"); + vec![] }; - let filter_kind = query - .include_item_types - .map(|n| match n.as_str() { - "Movie" => vec![FilterProperty::KindMovie], - "Audio" => vec![FilterProperty::KindMusic], - "Video" => vec![FilterProperty::KindVideo], - "TvChannel" => vec![FilterProperty::KindChannel], - _ => vec![], - }) - .or(if query.internal_artists { - Some(vec![]) - } else { - None - }) - .or(if query.internal_persons { - Some(vec![]) - } else { - None - }); - - let mut nodes = nodes - .into_iter() - .map(|nid| database.get_node_with_userdata(nid, &session.0)) - .collect::<Result<Vec<_>, anyhow::Error>>()?; - - filter_and_sort_nodes( - &NodeFilterSort { - sort_by: None, - filter_kind, - sort_order: None, - }, - match parent_kind { - Some(NodeKind::Channel) => (SortProperty::ReleaseDate, SortOrder::Descending), - _ => (SortProperty::Title, SortOrder::Ascending), - }, - &mut nodes, - ); + // TODO reimplemnt filter behaviour let items = nodes .into_iter() @@ -336,37 +349,33 @@ pub fn r_jellyfin_items( .map(|(n, ud)| item_object(&n, &ud)) .collect::<Vec<_>>(); - Ok(Json(json!({ - "Items": items, - "TotalRecordCount": items.len(), - "StartIndex": query.start_index.unwrap_or_default() - }))) + Ok(Json(JellyfinItemsResponse { + total_record_count: items.len(), + start_index: query.start_index.unwrap_or_default(), + items, + })) } #[get("/UserViews?<userId>")] #[allow(non_snake_case)] -pub fn r_jellyfin_users_views( - session: A<Session>, - database: &State<Database>, - userId: &str, -) -> MyResult<Json<Value>> { +pub fn r_jellyfin_users_views(session: A<Session>, userId: &str) -> MyResult<Json<Value>> { let _ = userId; - let mut toplevel = database - .get_node_children(NodeID::from_slug("library")) - .context("root node missing")? - .into_iter() - .map(|nid| database.get_node_with_userdata(nid, &session.0)) - .collect::<Result<Vec<_>, anyhow::Error>>()?; - - toplevel.sort_by_key(|(n, _)| n.index.unwrap_or(usize::MAX)); - - let mut items = Vec::new(); - for (n, ud) in toplevel { - if n.visibility >= Visibility::Reduced { - items.push(item_object(&n, &ud)) - } - } + let items = get_node( + &session.0, + NodeID::from_slug("library"), + false, + true, + NodeFilterSort { + sort_by: Some(SortProperty::Index), + sort_order: Some(SortOrder::Ascending), + filter_kind: None, + }, + )? + .children + .into_iter() + .map(|(node, udata)| item_object(&node, &udata)) + .collect::<Vec<_>>(); Ok(Json(json!({ "Items": items, @@ -414,14 +423,15 @@ pub fn r_jellyfin_shows_nextup(_session: A<Session>) -> Json<Value> { } #[post("/Items/<id>/PlaybackInfo")] -pub fn r_jellyfin_items_playbackinfo( - _session: A<Session>, - database: &State<Database>, - id: &str, -) -> MyResult<Json<Value>> { - let node = database - .get_node_slug(id)? - .ok_or(anyhow!("node does not exist"))?; +pub fn r_jellyfin_items_playbackinfo(session: A<Session>, id: &str) -> MyResult<Json<Value>> { + let node = get_node( + &session.0, + NodeID::from_slug(id), + false, + false, + NodeFilterSort::default(), + )? + .node; let media = node.media.as_ref().ok_or(anyhow!("node has no media"))?; let ms = media_source_object(&node, media); Ok(Json(json!({ @@ -431,14 +441,15 @@ pub fn r_jellyfin_items_playbackinfo( } #[get("/Videos/<id>/stream.webm")] -pub fn r_jellyfin_video_stream( - _session: A<Session>, - database: &State<Database>, - id: &str, -) -> MyResult<Redirect> { - let node = database - .get_node_slug(id)? - .ok_or(anyhow!("node does not exist"))?; +pub fn r_jellyfin_video_stream(session: A<Session>, id: &str) -> MyResult<Redirect> { + let node = get_node( + &session.0, + NodeID::from_slug(id), + false, + false, + NodeFilterSort::default(), + )? + .node; let media = node.media.as_ref().ok_or(anyhow!("node has no media"))?; let params = StreamSpec::Remux { tracks: (0..media.tracks.len()).collect(), @@ -458,23 +469,10 @@ struct JellyfinProgressData { #[allow(private_interfaces)] pub fn r_jellyfin_sessions_playing_progress( session: A<Session>, - database: &State<Database>, data: Json<JellyfinProgressData>, ) -> MyResult<()> { let position = data.position_ticks / 10_000_000.; - database.update_node_udata( - NodeID::from_slug(&data.item_id), - &session.0.user.name, - |udata| { - udata.watched = match udata.watched { - WatchedState::None | WatchedState::Pending | WatchedState::Progress(_) => { - WatchedState::Progress(position) - } - WatchedState::Watched => WatchedState::Watched, - }; - Ok(()) - }, - )?; + update_node_userdata_watched_progress(&session.0, NodeID::from_slug(&data.item_id), position)?; Ok(()) } @@ -501,22 +499,20 @@ struct AuthData { #[allow(private_interfaces)] pub fn r_jellyfin_users_authenticatebyname_case( client_addr: IpAddr, - database: &State<Database>, data: Json<AuthData>, jar: &CookieJar, ) -> MyResult<Json<Value>> { - r_jellyfin_users_authenticatebyname(client_addr, database, data, jar) + r_jellyfin_users_authenticatebyname(client_addr, data, jar) } #[post("/Users/authenticatebyname", data = "<data>")] #[allow(private_interfaces)] pub fn r_jellyfin_users_authenticatebyname( client_addr: IpAddr, - database: &State<Database>, data: Json<AuthData>, jar: &CookieJar, ) -> MyResult<Json<Value>> { - let token = login_logic(database, &data.username, &data.pw, None, None)?; + let token = login_logic(&data.username, &data.pw, None, None)?; // setting the session cookie too because image requests carry no auth headers for some reason. // TODO find alternative, non-web clients might not understand cookies diff --git a/server/src/compat/jellyfin/models.rs b/server/src/compat/jellyfin/models.rs index 6a68455..9dbad9c 100644 --- a/server/src/compat/jellyfin/models.rs +++ b/server/src/compat/jellyfin/models.rs @@ -7,6 +7,14 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap; +#[derive(Debug, Serialize, Default)] +#[serde(rename_all = "PascalCase")] +pub(super) struct JellyfinItemsResponse { + pub items: Vec<JellyfinItem>, + pub total_record_count: usize, + pub start_index: usize, +} + #[derive(Debug, Serialize, Deserialize)] pub(super) enum JellyfinItemType { AudioBook, |