aboutsummaryrefslogtreecommitdiff
path: root/server/src/compat/jellyfin/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/compat/jellyfin/mod.rs')
-rw-r--r--server/src/compat/jellyfin/mod.rs296
1 files changed, 146 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