Skip to content

Commit

Permalink
feat: Optionally load art work from tmdb (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
lostb1t authored Aug 7, 2023
1 parent cf7ffb2 commit d2cbbd2
Show file tree
Hide file tree
Showing 12 changed files with 485 additions and 20 deletions.
45 changes: 45 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ moka = { version = "0.11.2", features = ["future"] }
tonic = {version = "0.8.0", features = ["tls", "tls-roots"]}
async-recursion = "1.0.4"
console-subscriber = "0.1.10"
tmdb-api = "0.5.0"
bincode = "1.3.3"

[dev-dependencies]
async-std = { version = "^1.12", features = ["attributes"] }
Expand Down
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ docker-run:
# REPLEX_CACHE_TTL=0 REPLEX_HOST=https://46-4-30-217.01b0839de64b49138531cab1bf32f7c2.plex.direct:42405 REPLEX_NEWRELIC_API_KEY="eu01xx2d3c6a5e537373a8f8b52003b3FFFFNRAL" RUST_LOG="debug,replex=debug" cargo watch -x run


# run:
# REPLEX_ENABLE_CONSOLE=0 REPLEX_CACHE_TTL=0 REPLEX_HOST=https://46-4-30-217.01b0839de64b49138531cab1bf32f7c2.plex.direct:42405 RUST_LOG="debug" cargo watch -x run

run:
REPLEX_ENABLE_CONSOLE=0 REPLEX_CACHE_TTL=0 REPLEX_HOST=https://46-4-30-217.01b0839de64b49138531cab1bf32f7c2.plex.direct:42405 RUST_LOG="info" cargo run
REPLEX_TMDB_API_KEY=0d73e0cb91f39e670b0efa6913afbd58 REPLEX_ENABLE_CONSOLE=0 REPLEX_CACHE_TTL=0 REPLEX_HOST=https://46-4-30-217.01b0839de64b49138531cab1bf32f7c2.plex.direct:42405 RUST_LOG="info" cargo watch -x run

# run:
# REPLEX_ENABLE_CONSOLE=0 REPLEX_CACHE_TTL=0 REPLEX_HOST=https://46-4-30-217.01b0839de64b49138531cab1bf32f7c2.plex.direct:42405 RUST_LOG="info" cargo run


fix:
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ Settings are set via [environment variables](https://kinsta.com/knowledgebase/wh
| REPLEX_HOST | | Plex target host to proxy |
| REPLEX_INCLUDE_WATCHED | false | If set to false, hide watched items. |
| REPLEX_CACHE_TTL | 300 | Time to live for caches in seconds. Set to 0 to disable |
| REPLEX_TMDB_API_KEY | | Enables tmdb artwork for hero hubs instead of plex background artwork |

## hub style

You can change the hub style to hero elements by setting the label "REPLEXHERO" on an collection.
Plex uses an items background for hero styles rows. Often these dont have any text or are not suitable for hero artwork in general.
You can use tmdb to automaticly load hero artwork by providing the env `REPLEX_TMDB_API_KEY`. This way you can keep your backgrounds and hero artwork seperated.

see https://developer.themoviedb.org/docs/getting-started on how to get an api key.

## usage example

Expand Down
144 changes: 140 additions & 4 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,145 @@
use async_trait::async_trait;
use salvo::{cache::CacheIssuer, Request, Depot};
use moka::{future::Cache, future::ConcurrentCacheExt, Expiry};
use once_cell::sync::Lazy;
use salvo::{cache::CacheIssuer, Depot, Request};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize};
use std::hash::Hash;
use std::{
sync::Arc,
time::{Duration, Instant},
};
use std::error::Error;

use crate::config::Config;

// we close, this is a good example: https://github.com/getsentry/symbolicator/blob/170062d5bc7d4638a3e6af8a564cd881d798f1f0/crates/symbolicator-service/src/caching/memory.rs#L85

pub type CacheKey = String;
pub type CacheValue = (Expiration, Arc<Vec<u8>>);
// pub type CacheValue = Arc<Vec<u8>>;
pub type GlobalCacheType = Cache<CacheKey, CacheValue>;

pub(crate) static GLOBAL_CACHE: Lazy<CacheManager> = Lazy::new(|| {
let expiry = CacheExpiry;

// let store: GlobalCacheType =
CacheManager::new(
Cache::builder()
.max_capacity(10000)
.expire_after(expiry)
.build(),
)
});

/// An enum to represent the expiration of a value.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Expiration {
/// The value never expires.
Never,
/// Global TTL from the config
Global,
}

impl Expiration {
/// Returns the duration of this expiration.
pub fn as_duration(&self) -> Option<Duration> {
let config: Config = Config::figment().extract().unwrap();
match self {
Expiration::Never => None,
Expiration::Global => Some(Duration::from_secs(config.cache_ttl)),
}
}
}

/// An expiry that implements `moka::Expiry` trait. `Expiry` trait provides the
/// default implementations of three callback methods `expire_after_create`,
/// `expire_after_read`, and `expire_after_update`.
///
/// In this example, we only override the `expire_after_create` method.
pub struct CacheExpiry;

impl Expiry<CacheKey, (Expiration, Arc<Vec<u8>>)> for CacheExpiry {
/// Returns the duration of the expiration of the value that was just
/// created.
fn expire_after_create(
&self,
_key: &CacheKey,
value: &(Expiration, Arc<Vec<u8>>),
_current_time: Instant,
) -> Option<Duration> {
let duration = value.0.as_duration();
duration
}
}

#[derive(Clone)]
pub struct CacheManager {
/// The instance of `moka::future::Cache`
// pub store: Arc<Cache<String, Arc<Vec<u8>>>>,
// pub inner: S,
pub inner: GlobalCacheType,
}

impl CacheManager {
/// Create a new manager from a pre-configured Cache
// pub fn new(store: Cache<String, Arc<Vec<u8>>>) -> Self {
pub fn new(cache: GlobalCacheType) -> Self {
Self {
inner: cache, // store: Arc::new(store),
}
}
/// Clears out the entire cache.
pub async fn clear(&self) -> anyhow::Result<()> {
self.inner.invalidate_all();
self.inner.sync();
Ok(())
}

pub async fn get<T>(&self, cache_key: &str) -> Option<T>
where
T: DeserializeOwned,
{
match self.inner.get(cache_key) {
Some(d) => {
let result: T = bincode::deserialize(&d.1).unwrap();
Some(result)
},
None => None,
}
}

pub async fn insert<V>(
&self,
cache_key: String,
v: V,
expires: Expiration,
) -> anyhow::Result<()>
where
V: Serialize,
{

let value = (expires, Arc::new(bincode::serialize(&v)?));
// let bytes = bincode::serialize(&value)?;
self.inner.insert(cache_key, value).await;
self.inner.sync();
Ok(())
}

pub async fn delete(&self, cache_key: &str) -> anyhow::Result<()> {
self.inner.invalidate(cache_key).await;
self.inner.sync();
Ok(())
}
}

pub struct RequestIssuer {
use_scheme: bool,
use_authority: bool,
use_path: bool,
use_query: bool,
use_method: bool,
use_token: bool
use_token: bool,
}
impl Default for RequestIssuer {
fn default() -> Self {
Expand Down Expand Up @@ -60,7 +192,11 @@ impl RequestIssuer {
#[async_trait]
impl CacheIssuer for RequestIssuer {
type Key = String;
async fn issue(&self, req: &mut Request, _depot: &Depot) -> Option<Self::Key> {
async fn issue(
&self,
req: &mut Request,
_depot: &Depot,
) -> Option<Self::Key> {
let mut key = String::new();
if self.use_scheme {
if let Some(scheme) = req.uri().scheme_str() {
Expand Down Expand Up @@ -97,4 +233,4 @@ impl CacheIssuer for RequestIssuer {
}
Some(key)
}
}
}
3 changes: 2 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ pub struct Config {
default = "default_as_false",
deserialize_with = "figment::util::bool_from_str_or_int"
)]
pub enable_console: bool
pub enable_console: bool,
pub tmdb_api_key: Option<String>,
}

fn default_cache_ttl() -> u64 {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod transform;
pub mod logging;
pub mod cache;
pub mod routes;
pub mod tmdb;

#[cfg(test)]
mod test_helpers;
Loading

0 comments on commit d2cbbd2

Please sign in to comment.