diff options
Diffstat (limited to 'server/src/routes/ui')
| -rw-r--r-- | server/src/routes/ui/assets.rs | 44 | ||||
| -rw-r--r-- | server/src/routes/ui/browser.rs | 31 | ||||
| -rw-r--r-- | server/src/routes/ui/home.rs | 8 | ||||
| -rw-r--r-- | server/src/routes/ui/mod.rs | 1 | ||||
| -rw-r--r-- | server/src/routes/ui/node.rs | 172 | ||||
| -rw-r--r-- | server/src/routes/ui/player.rs | 49 | 
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" }];          } -    } +    })  }  |