diff options
author | metamuffin <metamuffin@disroot.org> | 2023-08-03 15:36:34 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-08-03 15:36:34 +0200 |
commit | 9898998344f9897d59b629ce643a0e243f5e7b09 (patch) | |
tree | 8571f09510b9e940c0cf86b714e24dce226557ef | |
parent | c0828abd8a0f02240520f3c36419d3a83086fd2b (diff) | |
download | jellything-9898998344f9897d59b629ce643a0e243f5e7b09.tar jellything-9898998344f9897d59b629ce643a0e243f5e7b09.tar.bz2 jellything-9898998344f9897d59b629ce643a0e243f5e7b09.tar.zst |
federated assets and refactored asset paths
-rw-r--r-- | Cargo.lock | 5 | ||||
-rw-r--r-- | client/src/lib.rs | 23 | ||||
-rw-r--r-- | common/src/lib.rs | 12 | ||||
-rw-r--r-- | server/Cargo.toml | 1 | ||||
-rw-r--r-- | server/src/import.rs | 48 | ||||
-rw-r--r-- | server/src/routes/ui/assets.rs | 23 | ||||
-rw-r--r-- | tools/src/bin/import.rs | 6 |
7 files changed, 95 insertions, 23 deletions
@@ -1276,6 +1276,7 @@ dependencies = [ "rocket", "serde", "serde_json", + "sha2", "sled", "tokio", "tokio-util", @@ -2198,9 +2199,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", diff --git a/client/src/lib.rs b/client/src/lib.rs index 39bd1fd..b208525 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -5,7 +5,7 @@ use reqwest::{ Client, }; use serde_json::json; -use std::time::Duration; +use std::{io::Write, time::Duration}; #[derive(Debug, Clone)] pub struct Instance { @@ -79,15 +79,18 @@ impl Session { .await?) } - // pub async fn node_asset(&self, id: &str, role: AssetRole) -> Result<Node> { - // Ok(self - // .client - // .get(format!("/n/{id}")) - // .send() - // .await? - // .bytes() - // .await?) - // } + // TODO use AssetRole instead of str + pub async fn node_asset(&self, id: &str, role: &str, mut writer: impl Write) -> Result<()> { + let mut r = self + .client + .get(format!("{}/n/{id}/asset?role={role}", self.instance.base())) + .send() + .await?; + while let Some(chunk) = r.chunk().await? { + writer.write_all(&chunk)?; + } + Ok(()) + } pub fn stream(&self, id: &str, tracks: &[usize], webm: bool) -> String { format!( diff --git a/common/src/lib.rs b/common/src/lib.rs index 6f6caf5..be25157 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -20,8 +20,8 @@ pub struct Node { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct NodePrivate { #[serde(default)] pub import: Option<RemoteImportOptions>, - #[serde(default)] pub poster: Option<PathBuf>, - #[serde(default)] pub backdrop: Option<PathBuf>, + #[serde(default)] pub poster: Option<AssetLocation>, + #[serde(default)] pub backdrop: Option<AssetLocation>, #[serde(default)] pub source: Option<MediaSource>, } @@ -48,6 +48,14 @@ pub struct RemoteImportOptions { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] +pub enum AssetLocation { + Cache(PathBuf), + Library(PathBuf), + Assets(PathBuf), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] pub enum NodeKind { Movie, Collection, diff --git a/server/Cargo.toml b/server/Cargo.toml index 6118240..6648b80 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -22,6 +22,7 @@ base64 = "0.21.2" chrono = { version = "0.4.26", features = ["serde"] } argon2 = "0.5.1" +sha2 = "0.10.7" aes-gcm-siv = "0.11.1" async-std = "1.12.0" diff --git a/server/src/import.rs b/server/src/import.rs index 2cd1998..a059694 100644 --- a/server/src/import.rs +++ b/server/src/import.rs @@ -6,10 +6,13 @@ use crate::{database::Database, federation::Federation, CONF}; use anyhow::{anyhow, bail, Context, Ok}; use async_recursion::async_recursion; +use base64::Engine; use jellyclient::Session; -use jellycommon::{MediaSource, Node, NodePrivate, RemoteImportOptions}; +use jellycommon::{AssetLocation, MediaSource, Node, NodePrivate, RemoteImportOptions}; use log::{error, info}; -use std::{ffi::OsStr, fs::File, os::unix::prelude::OsStrExt, path::PathBuf, sync::LazyLock}; +use std::{ + ffi::OsStr, fs::File, os::unix::prelude::OsStrExt, path::PathBuf, str::FromStr, sync::LazyLock, +}; use tokio::sync::Semaphore; static IMPORT_SEM: LazyLock<Semaphore> = LazyLock::new(|| Semaphore::new(1)); @@ -125,11 +128,15 @@ async fn import_remote( .node(&opts.id) .await .context("fetching remote node")?; + + let poster = cache_federation_asset(session, &opts.id, "poster").await?; + let backdrop = cache_federation_asset(session, &opts.id, "backdrop").await?; + let mut node = Node { public: node, private: NodePrivate { - backdrop: None, - poster: None, + backdrop: Some(AssetLocation::Cache(backdrop)), + poster: Some(AssetLocation::Cache(poster)), import: None, source: Some(MediaSource::Remote { host: opts.host.clone(), @@ -160,3 +167,36 @@ async fn import_remote( Ok(()) } + +async fn cache_federation_asset( + session: &Session, + identifier: &String, + role: &str, +) -> anyhow::Result<PathBuf> { + let (poster, download) = cache_file(&["federation-asset", role, identifier]); + if let Some(d) = download { + session.node_asset(&identifier, role, d).await?; + } + Ok(poster) +} + +fn cache_file(seed: &[&str]) -> (PathBuf, Option<File>) { + use sha2::Digest; + let mut d = sha2::Sha512::new(); + for s in seed { + d.update(s.as_bytes()); + d.update(b"\0"); + } + let d = d.finalize(); + let fname = base64::engine::general_purpose::URL_SAFE.encode(d); + let fname = &fname[..22]; // about 128 bits + let fullpath = CONF.cache_path.join(fname); + let cachepath = PathBuf::from_str(fname).unwrap(); + + let f = if !fullpath.exists() { + Some(File::create(&fullpath).unwrap()) + } else { + None + }; + (cachepath, f) +} diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs index 7fa083c..4b5c64a 100644 --- a/server/src/routes/ui/assets.rs +++ b/server/src/routes/ui/assets.rs @@ -9,6 +9,7 @@ use crate::{ CONF, }; use anyhow::anyhow; +use jellycommon::AssetLocation; use log::info; use rocket::{get, http::ContentType, FromFormField, State, UriDisplayQuery}; use std::{path::PathBuf, str::FromStr}; @@ -16,7 +17,9 @@ use tokio::fs::File; #[derive(FromFormField, UriDisplayQuery)] pub enum AssetRole { + #[field(value = "poster")] Poster, + #[field(value = "backdrop")] Backdrop, } @@ -32,15 +35,31 @@ pub async fn r_item_assets( AssetRole::Backdrop => node.private.backdrop, AssetRole::Poster => node.private.poster, } - .map(|e| CONF.library_path.join(e)) + .map(|e| e.path()) .unwrap_or_else(|| { CONF.asset_path .join(PathBuf::from_str("fallback.jpeg").unwrap()) }); info!("loading asset from {path:?}"); - let ext = path.extension().unwrap().to_str().unwrap(); + let ext = path + .extension() + .map(|e| e.to_str().unwrap()) + .unwrap_or("jpeg"); Ok(( ContentType::from_extension(ext).unwrap(), CacheControlFile::new(File::open(path).await?).await, )) } + +pub trait AssetLocationExt { + fn path(&self) -> PathBuf; +} +impl AssetLocationExt for AssetLocation { + fn path(&self) -> PathBuf { + match self { + AssetLocation::Assets(p) => CONF.asset_path.join(p), + AssetLocation::Cache(p) => CONF.cache_path.join(p), + AssetLocation::Library(p) => CONF.library_path.join(p), + } + } +} diff --git a/tools/src/bin/import.rs b/tools/src/bin/import.rs index 9e3dc4e..78c8646 100644 --- a/tools/src/bin/import.rs +++ b/tools/src/bin/import.rs @@ -5,7 +5,7 @@ */ use anyhow::Context; use clap::{Parser, Subcommand}; -use jellycommon::{MediaInfo, Node, NodeKind, NodePrivate, NodePublic}; +use jellycommon::{AssetLocation, MediaInfo, Node, NodeKind, NodePrivate, NodePublic}; use jellymatroska::read::EbmlReader; use jellyremuxer::import::import_read; use jellytools::tmdb::{tmdb_details, tmdb_image, tmdb_search}; @@ -167,8 +167,8 @@ fn main() -> anyhow::Result<()> { let node = Node { private: NodePrivate { import: None, - backdrop: backdrop.clone(), - poster: poster.clone(), + backdrop: backdrop.clone().map(AssetLocation::Library), + poster: poster.clone().map(AssetLocation::Library), source, }, public: NodePublic { |