diff options
Diffstat (limited to 'server/src/routes')
-rw-r--r-- | server/src/routes/mod.rs | 3 | ||||
-rw-r--r-- | server/src/routes/ui/assets.rs | 115 | ||||
-rw-r--r-- | server/src/routes/ui/error.rs | 7 |
3 files changed, 80 insertions, 45 deletions
diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index ca8c009..dec0858 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -33,7 +33,7 @@ use ui::{ r_admin_remove_invite, user::{r_admin_remove_user, r_admin_user, r_admin_user_permission, r_admin_users}, }, - assets::{r_item_assets, r_node_thumbnail, r_person_asset}, + assets::{r_asset, r_item_assets, r_node_thumbnail, r_person_asset}, browser::r_all_items_filter, error::{r_api_catch, r_catch}, home::{r_home, r_home_unpriv}, @@ -98,6 +98,7 @@ pub fn build_rocket(database: DataAcid, federation: Federation) -> Rocket<Build> r_home_unpriv, r_streamsync, r_favicon, + r_asset, r_item_assets, r_person_asset, r_search, diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs index 7973b0c..97396f3 100644 --- a/server/src/routes/ui/assets.rs +++ b/server/src/routes/ui/assets.rs @@ -3,22 +3,62 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin <metamuffin.org> */ -use crate::{ - database::DataAcid, - routes::ui::{account::session::Session, error::MyResult, CacheControlFile}, -}; +use crate::routes::ui::{account::session::Session, error::MyResult, CacheControlFile}; use anyhow::{anyhow, Context}; +use base64::Engine; use jellybase::{ + assetfed::AssetInner, cache::async_cache_file, - database::{TableExt, T_NODE, T_NODE_EXTENDED}, + database::{DataAcid, TableExt, T_NODE, T_NODE_EXTENDED}, federation::Federation, permission::NodePermissionExt, - AssetLocationExt, + CONF, }; pub use jellycommon::AssetRole; -use jellycommon::{AssetLocation, LocalTrack, PeopleGroup, SourceTrackKind, TrackSource}; +use jellycommon::{LocalTrack, PeopleGroup, SourceTrackKind, TrackSource}; use log::info; -use rocket::{get, http::ContentType, State}; +use rocket::{get, http::ContentType, response::Redirect, State}; +use std::path::PathBuf; + +#[get("/asset/<token>?<width>")] +pub async fn r_asset( + _session: Session, + fed: &State<Federation>, + token: &str, + width: Option<usize>, +) -> MyResult<(ContentType, CacheControlFile)> { + let asset = AssetInner::deser(token)?; + + let source = resolve_asset(asset, fed).await.context("resolving asset")?; + + // fit the resolution into a finite set so the maximum cache is finite too. + let width = 2usize.pow(width.unwrap_or(2048).clamp(128, 2048).ilog2()); + let path = jellytranscoder::image::transcode(source, 50., 5, width) + .await + .context("transcoding asset")?; + info!("loading asset from {path:?}"); + Ok(( + ContentType::AVIF, + CacheControlFile::new_cachekey(&path.abs()).await?, + )) +} + +pub async fn resolve_asset(asset: AssetInner, fed: &State<Federation>) -> anyhow::Result<PathBuf> { + match asset { + AssetInner::Federated { host, asset } => { + let session = fed.get_session(&host).await?; + + let asset = base64::engine::general_purpose::URL_SAFE.encode(asset); + Ok(async_cache_file(&["fed-asset", &asset], |out| async { + session.asset(out, &asset, 2048).await + }) + .await? + .abs()) + } + AssetInner::Cache(c) => Ok(c.abs()), + AssetInner::Assets(c) => Ok(CONF.asset_path.join(c)), + } +} #[get("/n/<id>/asset?<role>&<width>")] pub async fn r_item_assets( @@ -27,15 +67,15 @@ pub async fn r_item_assets( id: &str, role: AssetRole, width: Option<usize>, -) -> MyResult<(ContentType, CacheControlFile)> { +) -> MyResult<Redirect> { let node = T_NODE .get(&db, id)? .only_if_permitted(&session.user.permissions) .ok_or(anyhow!("node does not exist"))?; let mut asset = match role { - AssetRole::Backdrop => node.private.backdrop, - AssetRole::Poster => node.private.poster, + AssetRole::Backdrop => node.public.backdrop, + AssetRole::Poster => node.public.poster, }; if let None = asset { if let Some(parent) = &node.public.path.last() { @@ -43,15 +83,18 @@ pub async fn r_item_assets( .get(&db, parent.as_str())? .ok_or(anyhow!("node does not exist"))?; asset = match role { - AssetRole::Backdrop => parent.private.backdrop, - AssetRole::Poster => parent.private.poster, + AssetRole::Backdrop => parent.public.backdrop, + AssetRole::Poster => parent.public.poster, }; } }; - let asset = asset.unwrap_or(AssetLocation::Assets( - format!("fallback-{:?}.avif", node.public.kind.unwrap_or_default()).into(), - )); - Ok(asset_with_res(asset, width).await?) + let asset = asset.unwrap_or_else(|| { + AssetInner::Assets( + format!("fallback-{:?}.avif", node.public.kind.unwrap_or_default()).into(), + ) + .ser() + }); + Ok(Redirect::temporary(rocket::uri!(r_asset(asset.0, width)))) } #[get("/n/<id>/person/<index>/asset?<group>&<width>")] @@ -62,7 +105,7 @@ pub async fn r_person_asset( index: usize, group: PeopleGroup, width: Option<usize>, -) -> MyResult<(ContentType, CacheControlFile)> { +) -> MyResult<Redirect> { T_NODE .get(&db, id)? .only_if_permitted(&session.user.permissions) @@ -76,13 +119,17 @@ pub async fn r_person_asset( .get(index) .ok_or(anyhow!("person does not exist"))?; - let asset = app.person.asset.to_owned().unwrap_or(AssetLocation::Assets( - format!("fallback-Person.avif").into(), - )); - Ok(asset_with_res(asset, width).await?) + let asset = app + .person + .headshot + .to_owned() + .unwrap_or(AssetInner::Assets(format!("fallback-Person.avif").into()).ser()); + Ok(Redirect::temporary(rocket::uri!(r_asset(asset.0, width)))) } // TODO this can create "federation recursion" because track selection cannot be relied on. +//? TODO is this still relevant? + #[get("/n/<id>/thumbnail?<t>&<width>")] pub async fn r_node_thumbnail( session: Session, @@ -91,7 +138,7 @@ pub async fn r_node_thumbnail( id: &str, t: f64, width: Option<usize>, -) -> MyResult<(ContentType, CacheControlFile)> { +) -> MyResult<Redirect> { let node = T_NODE .get(&db, id)? .only_if_permitted(&session.user.permissions) @@ -120,6 +167,7 @@ pub async fn r_node_thumbnail( jellytranscoder::thumbnail::create_thumbnail(path, t).await? } TrackSource::Remote(_) => { + // TODO in the new system this is preferrably a property of node ext for regular fed let session = fed .get_session( thumb_track @@ -136,21 +184,8 @@ pub async fn r_node_thumbnail( } }; - Ok(asset_with_res(asset, width).await?) -} - -async fn asset_with_res( - asset: AssetLocation, - width: Option<usize>, -) -> MyResult<(ContentType, CacheControlFile)> { - // fit the resolution into a finite set so the maximum cache is finite too. - let width = 2usize.pow(width.unwrap_or(2048).clamp(128, 2048).ilog2()); - let path = jellytranscoder::image::transcode(asset, 50., 5, width) - .await - .context("transcoding asset")?; - info!("loading asset from {path:?}"); - Ok(( - ContentType::AVIF, - CacheControlFile::new_cachekey(&path.path()).await?, - )) + Ok(Redirect::temporary(rocket::uri!(r_asset( + AssetInner::Cache(asset).ser().0, + width + )))) } diff --git a/server/src/routes/ui/error.rs b/server/src/routes/ui/error.rs index d111041..8154209 100644 --- a/server/src/routes/ui/error.rs +++ b/server/src/routes/ui/error.rs @@ -5,8 +5,7 @@ */ use super::layout::{DynLayoutPage, LayoutPage}; use crate::{routes::ui::account::rocket_uri_macro_r_account_login, uri}; -use jellybase::AssetLocationExt; -use jellycommon::AssetLocation; +use jellybase::CONF; use log::info; use rocket::{ catch, @@ -15,11 +14,11 @@ use rocket::{ Request, }; use serde_json::{json, Value}; -use std::{fmt::Display, fs::File, io::Read, path::PathBuf, str::FromStr, sync::LazyLock}; +use std::{fmt::Display, fs::File, io::Read, sync::LazyLock}; static ERROR_IMAGE: LazyLock<Vec<u8>> = LazyLock::new(|| { info!("loading error image"); - let mut f = File::open(AssetLocation::Assets(PathBuf::from_str("error.avif").unwrap()).path()) + let mut f = File::open(CONF.asset_path.join("error.avif")) .expect("please create error.avif in the asset dir"); let mut o = Vec::new(); f.read_to_end(&mut o).unwrap(); |