aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock20
-rw-r--r--Cargo.toml1
-rw-r--r--base/Cargo.toml1
-rw-r--r--client/Cargo.toml14
-rw-r--r--client/src/lib.rs196
-rw-r--r--import/Cargo.toml1
-rw-r--r--import/src/lib.rs20
-rw-r--r--server/src/config.rs2
-rw-r--r--server/src/helper/filter_sort.rs5
-rw-r--r--server/src/helper/mod.rs2
-rw-r--r--server/src/ui/error.rs23
-rw-r--r--server/src/ui/node.rs3
-rw-r--r--stream/Cargo.toml2
-rw-r--r--stream/src/stream_info.rs5
-rw-r--r--tool/Cargo.toml2
-rw-r--r--ui/src/error.rs27
-rw-r--r--ui/src/home.rs4
-rw-r--r--ui/src/lib.rs13
-rw-r--r--ui/src/node_page.rs7
-rw-r--r--web/style/themes.css8
20 files changed, 83 insertions, 273 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 23e258c..ccf4e74 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 133055a..89259bc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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);