diff options
-rw-r--r-- | Cargo.lock | 20 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | base/Cargo.toml | 1 | ||||
-rw-r--r-- | client/Cargo.toml | 14 | ||||
-rw-r--r-- | client/src/lib.rs | 196 | ||||
-rw-r--r-- | import/Cargo.toml | 1 | ||||
-rw-r--r-- | import/src/lib.rs | 20 | ||||
-rw-r--r-- | server/src/config.rs | 2 | ||||
-rw-r--r-- | server/src/helper/filter_sort.rs | 5 | ||||
-rw-r--r-- | server/src/helper/mod.rs | 2 | ||||
-rw-r--r-- | server/src/ui/error.rs | 23 | ||||
-rw-r--r-- | server/src/ui/node.rs | 3 | ||||
-rw-r--r-- | stream/Cargo.toml | 2 | ||||
-rw-r--r-- | stream/src/stream_info.rs | 5 | ||||
-rw-r--r-- | tool/Cargo.toml | 2 | ||||
-rw-r--r-- | ui/src/error.rs | 27 | ||||
-rw-r--r-- | ui/src/home.rs | 4 | ||||
-rw-r--r-- | ui/src/lib.rs | 13 | ||||
-rw-r--r-- | ui/src/node_page.rs | 7 | ||||
-rw-r--r-- | web/style/themes.css | 8 |
20 files changed, 83 insertions, 273 deletions
@@ -1751,7 +1751,6 @@ dependencies = [ "bincode", "humansize", "jellycache", - "jellyclient", "jellycommon", "log", "rand 0.9.1", @@ -1780,20 +1779,6 @@ dependencies = [ ] [[package]] -name = "jellyclient" -version = "0.1.0" -dependencies = [ - "anyhow", - "jellycommon", - "log", - "percent-encoding", - "reqwest", - "serde", - "serde_json", - "tokio", -] - -[[package]] name = "jellycommon" version = "0.1.0" dependencies = [ @@ -1816,7 +1801,6 @@ dependencies = [ "futures", "jellybase", "jellycache", - "jellyclient", "jellyimport-fallback-generator", "jellyremuxer", "log", @@ -1887,10 +1871,8 @@ name = "jellystream" version = "0.1.0" dependencies = [ "anyhow", - "ebml-struct", "jellycache", "jellycommon", - "jellymatroska", "jellyremuxer", "jellytranscoder", "log", @@ -1944,8 +1926,6 @@ dependencies = [ "dialoguer", "env_logger", "indicatif", - "jellybase", - "jellyclient", "jellycommon", "jellyimport", "log", @@ -6,7 +6,6 @@ members = [ "tool", "matroska", "ebml_derive", - "client", "transcoder", "base", "import", diff --git a/base/Cargo.toml b/base/Cargo.toml index e19caff..ca4f454 100644 --- a/base/Cargo.toml +++ b/base/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] jellycommon = { path = "../common" } -jellyclient = { path = "../client" } jellycache = { path = "../cache" } serde = { version = "1.0.217", features = ["derive"] } serde_yaml = "0.9.34" diff --git a/client/Cargo.toml b/client/Cargo.toml deleted file mode 100644 index 772de57..0000000 --- a/client/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "jellyclient" -version = "0.1.0" -edition = "2021" - -[dependencies] -jellycommon = { path = "../common" } -log = { workspace = true } -reqwest = { workspace = true } -anyhow = "1.0.95" -serde_json = "1.0.138" -serde = { version = "1.0.217", features = ["derive"] } -tokio = { workspace = true } -percent-encoding = "2.3.1" diff --git a/client/src/lib.rs b/client/src/lib.rs deleted file mode 100644 index d3172fd..0000000 --- a/client/src/lib.rs +++ /dev/null @@ -1,196 +0,0 @@ -/* - 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) 2025 metamuffin <metamuffin.org> -*/ -use anyhow::Result; -use jellycommon::{ - api::{ApiHomeResponse, ApiNodeResponse, ApiSearchResponse}, - user::CreateSessionParams, -}; -use log::{debug, info}; -use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; -use reqwest::{ - header::{HeaderMap, HeaderValue}, - Client, -}; -use stream::StreamSpec; -use tokio::io::AsyncWriteExt; - -pub use jellycommon::*; - -#[derive(Debug, Clone)] -pub struct Instance { - pub host: String, - pub tls: bool, -} - -impl Instance { - pub fn new(host: String, tls: bool) -> Self { - Self { host, tls } - } - pub fn base(&self) -> String { - format!( - "{}://{}", - if self.tls { "https" } else { "http" }, - self.host - ) - } - pub async fn login(self, data: CreateSessionParams) -> anyhow::Result<Session> { - let session_token = Client::builder() - .build()? - .post(format!("{}/api/create_session", self.base())) - .json(&data) - .send() - .await? - .json() - .await?; - - let mut headers = HeaderMap::new(); - headers.insert( - "Cookie", - HeaderValue::from_str(&format!("session={session_token}")).unwrap(), - ); - headers.insert("Accept", HeaderValue::from_static("application/json")); - - Ok(Session { - instance: self, - session_token, - client: Client::builder().default_headers(headers).build().unwrap(), - }) - } -} - -pub struct Session { - client: Client, - instance: Instance, - session_token: String, -} - -pub trait UnpinWrite: tokio::io::AsyncWrite + std::marker::Unpin {} -impl<T: tokio::io::AsyncWrite + std::marker::Unpin> UnpinWrite for T {} - -impl Session { - fn session_param(&self) -> String { - format!("session={}", self.session_token) - } - - pub async fn node( - &self, - id: NodeIDOrSlug, - children: bool, - parents: bool, - ) -> Result<ApiNodeResponse> { - debug!("downloading node {id}"); - let params = match (children, parents) { - (true, true) => "?children&parents", - (true, false) => "?children", - (false, true) => "?parents", - (false, false) => "", - }; - Ok(self - .client - .get(format!("{}/n/{id}{params}", self.instance.base(),)) - .send() - .await? - .error_for_status()? - .json() - .await?) - } - - pub async fn search(&self, query: &str, page: usize) -> Result<ApiSearchResponse> { - debug!("searching for {query:?}"); - Ok(self - .client - .get(format!( - "{}/search?query={}&page={page}", - utf8_percent_encode(query, NON_ALPHANUMERIC), - self.instance.base(), - )) - .send() - .await? - .error_for_status()? - .json() - .await?) - } - - pub async fn home(&self) -> Result<ApiHomeResponse> { - debug!("home page"); - Ok(self - .client - .get(format!("{}/home", self.instance.base(),)) - .send() - .await? - .error_for_status()? - .json() - .await?) - } - - pub async fn node_thumbnail( - &self, - writer: impl UnpinWrite, - id: NodeIDOrSlug, - width: usize, - time: f64, - ) -> Result<()> { - debug!("downloading thumbnail for {id} at {time}s"); - self.download_url( - writer, - format!( - "{}/n/{id}/thumbnail?t={time}&width={width}", - self.instance.base(), - ), - ) - .await - } - - pub async fn asset(&self, writer: impl UnpinWrite, token: &str, width: usize) -> Result<()> { - debug!("downloading asset {token:?} (w={width})"); - self.download_url( - writer, - format!("{}/asset/{token}?width={width}", self.instance.base()), - ) - .await - } - - pub async fn stream( - &self, - writer: impl UnpinWrite, - id: NodeIDOrSlug, - stream_spec: &StreamSpec, - ) -> Result<()> { - self.download_url(writer, self.stream_url(id, stream_spec)) - .await - } - - pub fn stream_url(&self, id: NodeIDOrSlug, stream_spec: &StreamSpec) -> String { - format!( - "{}/n/{}/stream{}&{}", - self.instance.base(), - id, - stream_spec.to_query(), - self.session_param() - ) - } - - pub async fn download_url(&self, mut writer: impl UnpinWrite, url: String) -> Result<()> { - let mut r = self.client.get(url).send().await?.error_for_status()?; - while let Some(chunk) = r.chunk().await? { - writer.write_all(&chunk).await?; - } - Ok(()) - } - - pub async fn reimport(&self, incremental: bool) -> Result<()> { - info!("reimport"); - self.client - .post(format!( - "{}/admin/import?incremental={incremental}", - self.instance.base() - )) - .send() - .await?; - info!("done"); - Ok(()) - } -} diff --git a/import/Cargo.toml b/import/Cargo.toml index 506ed24..112df40 100644 --- a/import/Cargo.toml +++ b/import/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] jellybase = { path = "../base" } -jellyclient = { path = "../client" } jellyremuxer = { path = "../remuxer" } jellycache = { path = "../cache" } jellyimport-fallback-generator = { path = "fallback_generator" } diff --git a/import/src/lib.rs b/import/src/lib.rs index da339d8..784b717 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -19,14 +19,13 @@ use anyhow::{anyhow, bail, Context, Result}; use infojson::YVideo; use jellybase::{ assetfed::AssetInner, - common::{Chapter, MediaInfo, Node, NodeID, NodeKind, Rating, SourceTrack, SourceTrackKind}, + common::{ + Appearance, Chapter, LocalTrack, MediaInfo, Node, NodeID, NodeKind, ObjectIds, PeopleGroup, + Person, Rating, SourceTrack, SourceTrackKind, TmdbKind, TrackSource, TraktKind, Visibility, + }, database::Database, }; use jellycache::cache_file; -use jellyclient::{ - Appearance, LocalTrack, ObjectIds, PeopleGroup, Person, TmdbKind, TrackSource, TraktKind, - Visibility, -}; use jellyimport_fallback_generator::generate_fallback; use jellyremuxer::metadata::checked_matroska_metadata; use log::info; @@ -39,13 +38,13 @@ use std::{ fs::{read_to_string, File}, io::BufReader, path::{Path, PathBuf}, - sync::LazyLock, + sync::{LazyLock, Mutex}, time::UNIX_EPOCH, }; use tmdb::Tmdb; use tokio::{ runtime::Handle, - sync::{Mutex, RwLock, Semaphore}, + sync::{RwLock, Semaphore}, task::spawn_blocking, }; use trakt::Trakt; @@ -71,12 +70,13 @@ pub struct ApiSecrets { pub trakt: Option<String>, } -pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::const_new(None); +pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None); static CONF: LazyLock<Config> = LazyLock::new(|| { CONF_PRELOAD - .blocking_lock() + .lock() + .unwrap() .take() - .expect("cache config not preloaded. logic error") + .expect("import config not preloaded. logic error") }); pub const USER_AGENT: &'static str = concat!( diff --git a/server/src/config.rs b/server/src/config.rs index 27074b4..68148dd 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -18,6 +18,7 @@ struct Config { server: crate::Config, base: jellybase::Config, logic: jellylogic::Config, + import: jellyimport::Config, } pub async fn load_config() -> Result<()> { @@ -36,6 +37,7 @@ pub async fn load_config() -> Result<()> { *jellycache::CONF_PRELOAD.lock().unwrap() = Some(config.cache); *jellylogic::CONF_PRELOAD.lock().unwrap() = Some(config.logic); *jellybase::CONF_PRELOAD.lock().unwrap() = Some(config.base); + *jellyimport::CONF_PRELOAD.lock().unwrap() = Some(config.import); *crate::CONF_PRELOAD.lock().unwrap() = Some(config.server); *jellyui::CONF_PRELOAD.lock().unwrap() = Some(config.ui); diff --git a/server/src/helper/filter_sort.rs b/server/src/helper/filter_sort.rs index 186aa86..b30ff18 100644 --- a/server/src/helper/filter_sort.rs +++ b/server/src/helper/filter_sort.rs @@ -17,10 +17,11 @@ use rocket::{ #[async_trait] impl<'v> FromFormField<'v> for A<NodeFilterSort> { fn from_value(field: ValueField<'v>) -> Result<'v, Self> { - Err(field.unexpected())? + // TODO + Ok(A(NodeFilterSort::default())) } async fn from_data(field: DataField<'v, '_>) -> Result<'v, Self> { - Err(field.unexpected())? + Ok(A(NodeFilterSort::default())) } } diff --git a/server/src/helper/mod.rs b/server/src/helper/mod.rs index 7164175..f068cbc 100644 --- a/server/src/helper/mod.rs +++ b/server/src/helper/mod.rs @@ -9,5 +9,5 @@ pub mod filter_sort; pub mod node_id; pub mod session; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Default)] pub struct A<T>(pub T); diff --git a/server/src/ui/error.rs b/server/src/ui/error.rs index 05249af..d9716f5 100644 --- a/server/src/ui/error.rs +++ b/server/src/ui/error.rs @@ -4,6 +4,7 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use crate::CONF; +use jellyui::{error::ErrorPage, locale::Language, render_page, scaffold::RenderInfo}; use log::info; use rocket::{ catch, @@ -25,18 +26,16 @@ static ERROR_IMAGE: LazyLock<Vec<u8>> = LazyLock::new(|| { #[catch(default)] pub fn r_catch<'a>(status: Status, _request: &Request) -> RawHtml<String> { - // LayoutPage { - // title: "Not found".to_string(), - // content: markup::new! { - // h2 { "Error" } - // p { @format!("{status}") } - // @if status == Status::NotFound { - // p { "You might need to " a[href=uri!(r_account_login())] { "log in" } ", to see this page" } - // } - // }, - // ..Default::default() - // } - RawHtml("as".to_string()) + RawHtml(render_page( + &ErrorPage { + status: format!("{status}"), + }, + RenderInfo { + importing: false, + session: None, + }, + Language::English, + )) } #[catch(default)] diff --git a/server/src/ui/node.rs b/server/src/ui/node.rs index 6241f4f..1441cfc 100644 --- a/server/src/ui/node.rs +++ b/server/src/ui/node.rs @@ -24,12 +24,13 @@ pub async fn r_node<'a>( id: A<NodeID>, db: &'a State<Database>, aj: AcceptJson, - filter: A<NodeFilterSort>, + filter: Option<A<NodeFilterSort>>, lang: AcceptLanguage, parents: bool, children: bool, ) -> MyResult<Either<RawHtml<String>, Json<ApiNodeResponse>>> { let AcceptLanguage(lang) = lang; + let filter = filter.unwrap_or_default(); let r = get_node( &db, diff --git a/stream/Cargo.toml b/stream/Cargo.toml index ad6f098..33741d7 100644 --- a/stream/Cargo.toml +++ b/stream/Cargo.toml @@ -8,8 +8,6 @@ jellycommon = { path = "../common" } jellycache = { path = "../cache" } jellytranscoder = { path = "../transcoder" } jellyremuxer = { path = "../remuxer" } -jellymatroska = { path = "../matroska" } -ebml-struct = { git = "https://codeberg.org/metamuffin/ebml-struct" } log = { workspace = true } anyhow = { workspace = true } diff --git a/stream/src/stream_info.rs b/stream/src/stream_info.rs index 6f7824e..920ce69 100644 --- a/stream/src/stream_info.rs +++ b/stream/src/stream_info.rs @@ -5,12 +5,11 @@ */ use crate::{SMediaInfo, CONF}; use anyhow::Result; -use ebml_struct::matroska::TrackEntry; use jellycommon::stream::{ StreamContainer, StreamFormatInfo, StreamInfo, StreamSegmentInfo, StreamTrackInfo, TrackKind, }; use jellyremuxer::{ - metadata::{matroska_metadata, MatroskaMetadata}, + metadata::{matroska_metadata, MatroskaMetadata, MatroskaTrackEntry}, seek_index::get_track_sizes, }; use std::{collections::BTreeMap, path::PathBuf, sync::Arc}; @@ -83,7 +82,7 @@ pub(crate) async fn stream_info(info: Arc<SMediaInfo>) -> Result<(InternalStream )) } -fn stream_formats(t: &TrackEntry, remux_bitrate: f64) -> Vec<StreamFormatInfo> { +fn stream_formats(t: &MatroskaTrackEntry, remux_bitrate: f64) -> Vec<StreamFormatInfo> { let mut formats = Vec::new(); formats.push(StreamFormatInfo { codec: t.codec_id.to_string(), diff --git a/tool/Cargo.toml b/tool/Cargo.toml index 1383ee6..888381f 100644 --- a/tool/Cargo.toml +++ b/tool/Cargo.toml @@ -5,9 +5,7 @@ edition = "2021" [dependencies] jellycommon = { path = "../common" } -jellybase = { path = "../base" } jellyimport = { path = "../import" } -jellyclient = { path = "../client" } log = { workspace = true } env_logger = "0.11.6" diff --git a/ui/src/error.rs b/ui/src/error.rs new file mode 100644 index 0000000..ddf05bb --- /dev/null +++ b/ui/src/error.rs @@ -0,0 +1,27 @@ +/* + 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) 2025 metamuffin <metamuffin.org> +*/ + +use crate::Page; +use jellycommon::routes::u_account_login; + +impl Page for ErrorPage { + fn title(&self) -> String { + "Error".to_string() + } + fn to_render(&self) -> markup::DynRender { + markup::new!(@self) + } +} + +markup::define! { + ErrorPage(status: String) { + h2 { "Error" } + p { @status } + // @if status == Status::NotFound { + p { "You might need to " a[href=u_account_login()] { "log in" } ", to see this page" } + // } + } +} diff --git a/ui/src/home.rs b/ui/src/home.rs index 53055e8..21ce740 100644 --- a/ui/src/home.rs +++ b/ui/src/home.rs @@ -4,7 +4,7 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use crate::{ - Page, + CONF, Page, locale::{Language, tr, trs}, node_card::NodeCard, }; @@ -13,7 +13,7 @@ use markup::DynRender; markup::define! { HomePage<'a>(lang: &'a Language, r: ApiHomeResponse) { - h2 { @trs(lang, "home.bin.root") } //.replace("{title}", &CONF.brand) } + h2 { @tr(**lang, &"home.bin.root").replace("{title}", &CONF.brand) } ul.children.hlist {@for (node, udata) in &r.toplevel { li { @NodeCard { node, udata, lang: &lang } } }} diff --git a/ui/src/lib.rs b/ui/src/lib.rs index cbfc298..0e7547e 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -16,7 +16,9 @@ pub mod props; pub mod scaffold; pub mod search; pub mod stats; +pub mod error; +use jellycommon::user::Theme; use locale::Language; use markup::DynRender; use scaffold::{RenderInfo, Scaffold}; @@ -64,8 +66,17 @@ pub trait Page { pub fn render_page(page: &dyn Page, renderinfo: RenderInfo, lang: Language) -> String { Scaffold { lang, + class: &format!( + "{} theme-{}", + page.class().unwrap_or("custom-page"), + renderinfo + .session + .as_ref() + .map(|s| s.user.theme) + .unwrap_or(Theme::Dark) + .to_str() + ), renderinfo, - class: page.class().unwrap_or("aaaa"), title: page.title(), main: page.to_render(), } diff --git a/ui/src/node_page.rs b/ui/src/node_page.rs index 7fb299f..45cf18c 100644 --- a/ui/src/node_page.rs +++ b/ui/src/node_page.rs @@ -28,6 +28,13 @@ impl Page for NodePage<'_> { fn title(&self) -> String { self.node.title.clone().unwrap_or_default() } + fn class(&self) -> Option<&'static str> { + if self.player { + Some("player") + } else { + Some("node-page") + } + } fn to_render(&self) -> markup::DynRender { markup::new!(@self) } diff --git a/web/style/themes.css b/web/style/themes.css index 2c6bdea..ee6bc1a 100644 --- a/web/style/themes.css +++ b/web/style/themes.css @@ -7,7 +7,7 @@ body { --video-brackground: black; --c-danger: rgb(177, 36, 36); } -body.theme-Dark { +body.theme-dark { --accent-light: rgb(255, 163, 87); --accent-dark: rgb(199, 90, 0); --c-error: rgb(255, 117, 117); @@ -29,7 +29,7 @@ body.theme-Dark { --font-highlight: white; --image-loading-placeholder: rgb(50, 50, 50); } -body.theme-Light { +body.theme-light { --accent-light: #e46600; --accent-dark: #ff9036; --c-error: rgb(255, 117, 117); @@ -50,7 +50,7 @@ body.theme-Light { --font-highlight: black; --image-loading-placeholder: rgb(200, 200, 200); } -body.theme-Purple { +body.theme-purple { --accent-light: rgb(191, 87, 255); --accent-dark: rgb(143, 43, 205); --c-error: rgb(255, 117, 117); @@ -71,7 +71,7 @@ body.theme-Purple { --font-highlight: white; --image-loading-placeholder: rgb(50, 50, 50); } -body.theme-Black { +body.theme-black { --accent-light: hsl(250, 100%, 67%); --accent-dark: hsl(250, 60%, 42%); --c-error: rgb(255, 117, 117); |