aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-07-31 19:53:01 +0200
committermetamuffin <metamuffin@disroot.org>2023-07-31 19:53:01 +0200
commitaeafba7847e189313df3025e6d6f291999b57350 (patch)
treebf7affdca28208695648bc9b18856cbb7049d1e8 /server/src/routes/ui
parent0c651f11920350a4aa96aa24f8fe15b28390aed2 (diff)
downloadjellything-aeafba7847e189313df3025e6d6f291999b57350.tar
jellything-aeafba7847e189313df3025e6d6f291999b57350.tar.bz2
jellything-aeafba7847e189313df3025e6d6f291999b57350.tar.zst
update server to new schema
Diffstat (limited to 'server/src/routes/ui')
-rw-r--r--server/src/routes/ui/assets.rs44
-rw-r--r--server/src/routes/ui/browser.rs31
-rw-r--r--server/src/routes/ui/home.rs8
-rw-r--r--server/src/routes/ui/mod.rs1
-rw-r--r--server/src/routes/ui/node.rs172
-rw-r--r--server/src/routes/ui/player.rs49
6 files changed, 161 insertions, 144 deletions
diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs
new file mode 100644
index 0000000..2c0b85a
--- /dev/null
+++ b/server/src/routes/ui/assets.rs
@@ -0,0 +1,44 @@
+/*
+ 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) 2023 metamuffin <metamuffin.org>
+*/
+use crate::{
+ database::Database,
+ routes::ui::{account::session::Session, error::MyError, CacheControlFile},
+ CONF,
+};
+use anyhow::anyhow;
+use log::info;
+use rocket::{get, http::ContentType, FromFormField, State, UriDisplayQuery};
+use std::{path::PathBuf, str::FromStr};
+use tokio::fs::File;
+
+#[derive(FromFormField, UriDisplayQuery)]
+pub enum AssetRole {
+ Poster,
+ Backdrop,
+}
+
+#[get("/item_assets/<id>?<role>")]
+pub async fn r_item_assets(
+ _sess: Session,
+ id: String,
+ role: AssetRole,
+ db: &State<Database>,
+) -> Result<(ContentType, CacheControlFile), MyError> {
+ let node = db.node.get(&id)?.ok_or(anyhow!("node does not exist"))?;
+ let path = CONF.asset_path.join(
+ match role {
+ AssetRole::Backdrop => node.private.backdrop,
+ AssetRole::Poster => node.private.poster,
+ }
+ .unwrap_or_else(|| PathBuf::from_str("fallback.jpeg").unwrap()),
+ );
+ info!("loading asset from {path:?}");
+ let ext = path.extension().unwrap().to_str().unwrap();
+ Ok((
+ ContentType::from_extension(ext).unwrap(),
+ CacheControlFile::new(File::open(path).await?).await,
+ ))
+}
diff --git a/server/src/routes/ui/browser.rs b/server/src/routes/ui/browser.rs
index 30eb3f2..767e411 100644
--- a/server/src/routes/ui/browser.rs
+++ b/server/src/routes/ui/browser.rs
@@ -3,35 +3,24 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
-use super::{account::session::Session, error::MyError, layout::DynLayoutPage, node::PosterCard};
-use crate::library::{Library, Node};
+use super::{account::session::Session, error::MyError, layout::DynLayoutPage};
+use crate::database::Database;
use rocket::{get, State};
-use std::collections::VecDeque;
#[get("/items")]
-pub fn r_all_items(_sess: Session, library: &State<Library>) -> Result<DynLayoutPage<'_>, MyError> {
- let mut dirs = VecDeque::from_iter(Some(library.root.get_directory().unwrap()));
- let mut items = Vec::new();
- while let Some(d) = dirs.pop_front() {
- for e in &d.children {
- match e.as_ref() {
- Node::Directory(d) => dirs.push_back(d.clone()),
- Node::Item(i) => items.push(i.clone()),
- }
- }
- }
+pub fn r_all_items(_sess: Session, db: &State<Database>) -> Result<DynLayoutPage<'_>, MyError> {
Ok(super::layout::LayoutPage {
title: "All Items".to_owned(),
content: markup::new! {
.page.dir {
h1 { "All Items" }
- ul.directorylisting { @for item in &items {
- li {@PosterCard {
- wide: false, dir: false,
- path: item.lib_path.clone(),
- title: &item.info.title
- }}
- }}
+ // ul.directorylisting { @for item in &items {
+ // li {@PosterCard {
+ // wide: false, dir: false,
+ // path: item.lib_path.clone(),
+ // title: &item.info.title
+ // }}
+ // }}
}
},
..Default::default()
diff --git a/server/src/routes/ui/home.rs b/server/src/routes/ui/home.rs
index cdde478..0b85e89 100644
--- a/server/src/routes/ui/home.rs
+++ b/server/src/routes/ui/home.rs
@@ -5,20 +5,20 @@
*/
use super::{account::session::Session, layout::LayoutPage};
use crate::{
- library::Library,
- routes::ui::{error::MyResult, layout::DynLayoutPage, node::NodePage},
+ database::Database,
+ routes::ui::{error::MyResult, layout::DynLayoutPage},
CONF,
};
use rocket::{get, State};
use tokio::fs::read_to_string;
#[get("/")]
-pub fn r_home(_sess: Session, library: &State<Library>) -> DynLayoutPage {
+pub fn r_home(_sess: Session, _db: &State<Database>) -> DynLayoutPage {
LayoutPage {
title: "Home".to_string(),
content: markup::new! {
p { "Welcome to " @CONF.brand }
- @NodePage { node: &library.root }
+ // @NodePage { node: &db }
},
..Default::default()
}
diff --git a/server/src/routes/ui/mod.rs b/server/src/routes/ui/mod.rs
index f566f6d..5fad2b7 100644
--- a/server/src/routes/ui/mod.rs
+++ b/server/src/routes/ui/mod.rs
@@ -29,6 +29,7 @@ pub mod layout;
pub mod node;
pub mod player;
pub mod style;
+pub mod assets;
pub struct HtmlTemplate<'a>(pub markup::DynRender<'a>);
diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs
index 4d599dc..0ae0d9e 100644
--- a/server/src/routes/ui/node.rs
+++ b/server/src/routes/ui/node.rs
@@ -1,163 +1,141 @@
/*
-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) 2023 metamuffin <metamuffin.org>
+ 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) 2023 metamuffin <metamuffin.org>
*/
-use super::error::MyError;
-use super::player::player_uri;
-use super::CacheControlFile;
-use crate::uri;
+use super::{assets::rocket_uri_macro_r_item_assets, error::MyError, player::player_uri};
use crate::{
- library::{Directory, Item, Library, Node},
+ database::Database,
routes::ui::{
account::session::Session,
+ assets::AssetRole,
layout::{DynLayoutPage, LayoutPage},
},
+ uri,
};
-use anyhow::Context;
-use jellycommon::DirectoryKind;
-use log::info;
-use rocket::{get, http::ContentType, State};
-use rocket::{FromFormField, UriDisplayQuery};
-use std::{path::PathBuf, sync::Arc};
-use tokio::fs::File;
+use anyhow::{anyhow, Context};
+use jellycommon::{Node, NodeKind};
+use rocket::{get, State};
-#[get("/library/<path..>")]
+#[get("/library/<id>")]
pub async fn r_library_node(
_sess: Session,
- path: PathBuf,
- library: &State<Library>,
+ id: String,
+ db: &State<Database>,
) -> Result<DynLayoutPage<'_>, MyError> {
- let node = library
- .nested_path(&path)
- .context("retrieving library node")?;
+ let node = db
+ .node
+ .get(&id)
+ .context("retrieving library node")?
+ .ok_or(anyhow!("node does not exist"))?;
+
+ let children = node
+ .public
+ .children
+ .iter()
+ .map(|c| {
+ Ok((
+ c.to_owned(),
+ db.node.get(c)?.ok_or(anyhow!("child does not exist"))?,
+ ))
+ })
+ .collect::<anyhow::Result<Vec<_>>>()?
+ .into_iter()
+ .collect();
+
Ok(LayoutPage {
- title: node.common().title.to_string(),
- show_back: node.get_item().is_ok(),
+ title: node.public.title.to_string(),
+ show_back: matches!(node.public.kind, NodeKind::Movie | NodeKind::Episode),
content: markup::new! {
- @NodePage { node: &node }
+ @NodePage { node: &node, id: &id, children: &children }
},
..Default::default()
})
}
markup::define! {
- NodePage<'a>(node: &'a Arc<Node>) {
- @match node.as_ref() {
- Node::Directory(dir) => { @match dir.info.kind {
- DirectoryKind::Series => { @SeriesPage { dir } }
- _ => { @DirectoryPage { dir } }
- } }
- Node::Item(item) => { @ItemPage { item } }
+ NodePage<'a>(id: &'a str, node: &'a Node, children: &'a Vec<(String,Node)>) {
+ @match node.public.kind {
+ NodeKind::Collection | NodeKind::Show | NodeKind::Season => { @DirectoryPage { node, id, children } }
+ NodeKind::Series => { @SeriesPage { node, children, id } }
+ NodeKind::Movie | NodeKind::Episode => { @ItemPage { node, id } }
}
}
- NodeCard<'a>(node: &'a Arc<Node>) {
- @match node.as_ref() {
- Node::Directory(dir) => {@PosterCard {
- wide: !matches!(dir.info.kind, DirectoryKind::Series | DirectoryKind::Season),
- dir: true,
- path: dir.lib_path.clone(),
- title: &dir.info.title
- }}
- Node::Item(item) => {@PosterCard {
- wide: false, dir: false,
- path: item.lib_path.clone(),
- title: &item.info.title
- }}
+ NodeCard<'a>(id: &'a str, node: &'a Node) {
+ @PosterCard {
+ wide: matches!(node.public.kind, NodeKind::Collection),
+ dir: !matches!(node.public.kind, NodeKind::Movie | NodeKind::Episode),
+ id,
+ title: &node.public.title
}
}
- DirectoryPage<'a>(dir: &'a Arc<Directory>) {
+ DirectoryPage<'a>(id: &'a str, node: &'a Node, children: &'a Vec<(String,Node)>) {
div.page.dir {
- h1 { @dir.info.title }
- @if let Some(parent) = dir.lib_path.parent() {
- a.dirup[href=uri!(r_library_node(&parent))] { "Go up" }
- }
+ h1 { @node.public.title }
+ // @if let Some(parent) = node.lib_path.parent() {
+ // a.dirup[href=uri!(r_library_node(&parent))] { "Go up" }
+ // }
ul.directorylisting {
- @for node in &dir.children {
- li { @NodeCard { node } }
+ @for (id, node) in children.iter() {
+ li { @NodeCard { id, node } }
}
}
}
}
- PosterCard<'a>(path: PathBuf, title: &'a str, wide: bool, dir: bool) {
+ PosterCard<'a>(id: &'a str, title: &'a str, wide: bool, dir: bool) {
div[class=if *wide {"card wide poster"} else {"card poster"}] {
div.banner {
- a[href=uri!(r_library_node(path))] {
- img[src=uri!(r_item_assets(path, AssetRole::Poster))];
+ a[href=uri!(r_library_node(id))] {
+ img[src=uri!(r_item_assets(id, AssetRole::Poster))];
}
@if *dir {
- div.hoverdir { a[href=&uri!(r_library_node(path))] { "Open" } }
+ div.hoverdir { a[href=&uri!(r_library_node(id))] { "Open" } }
} else {
- div.hoveritem { a[href=&player_uri(path)] { "▶" } }
+ div.hoveritem { a[href=&player_uri(id)] { "▶" } }
}
}
p.title {
- a[href=uri!(r_library_node(path))] {
+ a[href=uri!(r_library_node(id))] {
@title
}
}
}
}
- ItemPage<'a>(item: &'a Arc<Item>) {
+ ItemPage<'a>(id: &'a str, node: &'a Node) {
// TODO different image here
- img.backdrop[src=uri!(r_item_assets(&item.lib_path, AssetRole::Backdrop))];
+ img.backdrop[src=uri!(r_item_assets(id, AssetRole::Backdrop))];
div.page.item {
div.banner {
- img[src=uri!(r_item_assets(&item.lib_path, AssetRole::Poster))];
+ img[src=uri!(r_item_assets(id, AssetRole::Poster))];
}
div.title {
- h1 { @item.info.title }
+ h1 { @node.public.title }
// TODO release date, duration, ratings
- a.play[href=&player_uri(&item.lib_path)] { "Watch now" }
+ a.play[href=&player_uri(id)] { "Watch now" }
}
div.details {
- h3 { @item.info.tagline }
- p { @item.info.description }
+ h3 { @node.public.tagline }
+ p { @node.public.description }
}
}
}
- SeriesPage<'a>(dir: &'a Arc<Directory>) {
+ SeriesPage<'a>(id: &'a str, node: &'a Node, children: &'a Vec<(String,Node)>) {
// TODO different image here
- img.backdrop[src=uri!(r_item_assets(&dir.lib_path, AssetRole::Backdrop))];
+ img.backdrop[src=uri!(r_item_assets(id, AssetRole::Backdrop))];
div.page.item {
div.banner {
- img[src=uri!(r_item_assets(&dir.lib_path, AssetRole::Poster))];
+ img[src=uri!(r_item_assets(id, AssetRole::Poster))];
}
div.title {
- h1 { @dir.info.title }
+ h1 { @node.public.title }
}
div.details {
- h3 { @dir.info.tagline }
- p { @dir.info.description }
+ h3 { @node.public.tagline }
+ p { @node.public.description }
}
- ol { @for ep in &dir.children {
- li { a[href=uri!(r_library_node(ep.lib_path()))] { @ep.common().title } }
+ ol { @for (id, c) in children.iter() {
+ li { a[href=uri!(r_library_node(id))] { @c.public.title } }
} }
}
}
}
-
-#[derive(FromFormField, UriDisplayQuery)]
-pub enum AssetRole {
- Poster,
- Backdrop,
-}
-
-#[get("/item_assets/<path..>?<role>")]
-pub async fn r_item_assets(
- _sess: Session,
- path: PathBuf,
- role: AssetRole,
- library: &State<Library>,
-) -> Result<(ContentType, CacheControlFile), MyError> {
- let node = library
- .nested_path(&path)
- .context("retrieving library node")?;
- let path = node.get_asset(library, role);
- info!("loading asset from {path:?}");
- let ext = path.extension().unwrap().to_str().unwrap();
- Ok((
- ContentType::from_extension(ext).unwrap(),
- CacheControlFile::new(File::open(path).await?).await,
- ))
-}
diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs
index d5cb685..0e87749 100644
--- a/server/src/routes/ui/player.rs
+++ b/server/src/routes/ui/player.rs
@@ -5,27 +5,24 @@
*/
use super::{account::session::Session, layout::LayoutPage};
use crate::{
- library::{Item, Library},
+ database::Database,
routes::{
stream::stream_uri,
ui::{
+ assets::{rocket_uri_macro_r_item_assets, AssetRole},
error::MyResult,
layout::DynLayoutPage,
- node::{rocket_uri_macro_r_item_assets, AssetRole},
},
},
uri,
};
-use jellycommon::SourceTrackKind;
+use anyhow::anyhow;
+use jellycommon::{Node, SourceTrackKind};
use markup::DynRender;
use rocket::{get, FromForm, State};
-use std::{
- path::{Path, PathBuf},
- sync::Arc,
-};
-pub fn player_uri(path: &Path) -> String {
- format!("/player/{}", path.to_str().unwrap())
+pub fn player_uri(id: &str) -> String {
+ format!("/player/{}", id)
}
#[derive(FromForm, Default, Clone, Debug)]
@@ -36,14 +33,14 @@ pub struct PlayerConfig {
pub webm: bool,
}
-#[get("/player/<path..>?<conf..>", rank = 4)]
+#[get("/player/<id>?<conf..>", rank = 4)]
pub fn r_player(
_sess: Session,
- library: &State<Library>,
- path: PathBuf,
+ db: &State<Database>,
+ id: String,
conf: PlayerConfig,
) -> MyResult<DynLayoutPage<'_>> {
- let item = library.nested_path(&path)?.get_item()?;
+ let item = db.node.get(&id)?.ok_or(anyhow!("node does not exist"))?;
let tracks = None
.into_iter()
.chain(conf.v.into_iter())
@@ -51,27 +48,35 @@ pub fn r_player(
.chain(conf.s.into_iter())
.collect::<Vec<_>>();
+ let conf = player_conf(item.clone(), !tracks.is_empty())?;
Ok(LayoutPage {
- title: item.info.title.to_owned(),
+ title: item.public.title.to_owned(),
class: Some("player"),
content: markup::new! {
@if tracks.is_empty() {
- img.backdrop[src=uri!(r_item_assets(&item.lib_path, AssetRole::Backdrop)).to_string()];
+ img.backdrop[src=uri!(r_item_assets(&id, AssetRole::Backdrop)).to_string()];
} else {
- video[src=stream_uri(&item.lib_path, &tracks, true), controls]{}
+ video[src=stream_uri(&id, &tracks, true), controls]{}
}
- @player_conf(item.clone(), !tracks.is_empty())
+ @conf
},
show_back: true,
..Default::default()
})
}
-pub fn player_conf<'a>(item: Arc<Item>, playing: bool) -> DynRender<'a> {
+pub fn player_conf<'a>(item: Node, playing: bool) -> anyhow::Result<DynRender<'a>> {
let mut audio_tracks = vec![];
let mut video_tracks = vec![];
let mut sub_tracks = vec![];
- for (tid, track) in item.info.tracks.clone() {
+ let tracks = item
+ .public
+ .media
+ .clone()
+ .ok_or(anyhow!("node does not have media"))?
+ .tracks
+ .clone();
+ for (tid, track) in tracks.into_iter().enumerate() {
match &track.kind {
SourceTrackKind::Audio { .. } => audio_tracks.push((tid, track)),
SourceTrackKind::Video { .. } => video_tracks.push((tid, track)),
@@ -79,9 +84,9 @@ pub fn player_conf<'a>(item: Arc<Item>, playing: bool) -> DynRender<'a> {
}
}
- markup::new! {
+ Ok(markup::new! {
form.playerconf[method = "GET", action = ""] {
- h2 { "Select tracks for " @item.info.title }
+ h2 { "Select tracks for " @item.public.title }
fieldset.video {
legend { "Video" }
@@ -115,5 +120,5 @@ pub fn player_conf<'a>(item: Arc<Item>, playing: bool) -> DynRender<'a> {
input[type="submit", value=if playing { "Change tracks" } else { "Start playback" }];
}
- }
+ })
}