diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f80bfeb..9b59fdd 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -659,6 +659,7 @@ dependencies = [ "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "time-cache 0.1.0", ] [[package]] @@ -1602,6 +1603,10 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "time-cache" +version = "0.1.0" + [[package]] name = "tokio-codec" version = "0.1.1" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b294c0c..f4dd078 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -19,3 +19,4 @@ serde = { version = "1.0.97", features = ["rc"] } serde_derive = "1.0.97" serde_json = "1.0.40" structopt = "0.2.18" +time-cache = { path = "../time-cache" } diff --git a/backend/src/cache.rs b/backend/src/cache.rs index a190484..e69de29 100644 --- a/backend/src/cache.rs +++ b/backend/src/cache.rs @@ -1,90 +0,0 @@ -use crate::statics::REDIRECT_AGE; -use std::{ - collections::HashMap, - hash::Hash, - sync::Arc, - time::{Duration, Instant}, -}; - -pub(crate) struct Cache { - cache: HashMap>, - duration: Duration, -} - -impl Cache -where - K: Eq + Hash, -{ - pub(crate) fn new() -> Self { - Self { - cache: HashMap::new(), - duration: REDIRECT_AGE, - } - } - - pub(crate) fn get(&self, key: &K) -> CacheResult<&V> { - if let Some(entry) = self.cache.get(key) { - if Self::is_valid(Instant::now(), entry) { - CacheResult::Cached(&entry.1) - } else { - CacheResult::Invalid - } - } else { - CacheResult::Empty - } - } - - pub(crate) fn invalidate(&mut self, key: &K) -> bool { - self.cache.remove(key).is_some() - } - - pub(crate) fn store(&mut self, key: K, value: V) -> Option { - self.cache - .insert(key, CacheEntry::new(value, self.duration)) - .map(|old| old.1) - } - - pub(crate) fn clear(&mut self) { - let now = Instant::now(); - self.cache.retain(|_, v| !Self::is_valid(now, v)); - } - - fn is_valid(when: Instant, entry: &CacheEntry) -> bool { - entry.0 >= when - } -} - -pub(crate) enum CacheResult { - Cached(T), - Invalid, - Empty, -} - -struct CacheEntry(Instant, T); - -impl CacheEntry { - fn new(value: T, duration: Duration) -> Self { - CacheEntry(Instant::now() + duration, value) - } -} - -#[derive(Eq, PartialEq, Hash, Debug)] -pub(crate) struct Key(Service, Arc, Arc, Arc); - -#[derive(Eq, PartialEq, Hash, Debug)] -pub(crate) enum Service { - GitHub, - GitLab, - Bitbucket, -} - -impl Key { - pub(crate) fn new( - service: Service, - user: Arc, - repo: Arc, - branch: Arc, - ) -> Self { - Key(service, user, repo, branch) - } -} diff --git a/backend/src/data.rs b/backend/src/data.rs index 7f36ab0..4c8395d 100644 --- a/backend/src/data.rs +++ b/backend/src/data.rs @@ -1,8 +1,6 @@ -use crate::{ - cache::{Cache, Key}, - service::Service, -}; +use crate::service; use std::sync::{Arc, RwLock}; +use time_cache::Cache; pub(crate) type State = Arc>>; @@ -19,7 +17,7 @@ impl FilePath { format!("{}/{}/{}/{}", self.user, self.repo, self.commit, self.file) } - pub(crate) fn to_key(&self) -> Key { + pub(crate) fn to_key(&self) -> Key { Key::new( T::cache_service(), self.user.clone(), @@ -28,3 +26,24 @@ impl FilePath { ) } } + +#[derive(Eq, PartialEq, Hash, Debug)] +pub(crate) struct Key(Service, Arc, Arc, Arc); + +#[derive(Eq, PartialEq, Hash, Debug)] +pub(crate) enum Service { + GitHub, + GitLab, + Bitbucket, +} + +impl Key { + pub(crate) fn new( + service: Service, + user: Arc, + repo: Arc, + branch: Arc, + ) -> Self { + Key(service, user, repo, branch) + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 9aceba8..85a7c15 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -16,7 +16,6 @@ mod service; mod statics; use crate::{ - cache::{Cache, CacheResult}, cdn::Cloudflare, data::{FilePath, State}, error::Result, @@ -31,6 +30,7 @@ use actix_web::{ use awc::{http::StatusCode, Client}; use futures::Future; use std::sync::{Arc, RwLock}; +use time_cache::{Cache, CacheResult}; fn proxy_file( client: web::Data, @@ -210,7 +210,7 @@ fn main() -> Result<()> { pretty_env_logger::init(); openssl_probe::init_ssl_cert_env_vars(); - let state: State = Arc::new(RwLock::new(Cache::new())); + let state: State = Arc::new(RwLock::new(Cache::new(REDIRECT_AGE))); Ok(HttpServer::new(move || { App::new() .data(Client::new()) diff --git a/backend/src/service.rs b/backend/src/service.rs index 9edf522..e8490d6 100644 --- a/backend/src/service.rs +++ b/backend/src/service.rs @@ -1,6 +1,5 @@ use crate::{ - cache, - data::{FilePath, State}, + data::{self, FilePath, State}, statics::{load_env_var, GITHUB_AUTH_QUERY, OPT, REDIRECT_AGE}, }; use actix_web::{ @@ -71,7 +70,7 @@ pub(crate) trait Service: Sized { fn raw_url(user: &str, repo: &str, commit: &str, file: &str) -> String; - fn cache_service() -> cache::Service; + fn cache_service() -> data::Service; fn api_url(path: &FilePath) -> String; @@ -141,8 +140,8 @@ impl Github { impl Service for Github { type Response = GitHubApiResponse; - fn cache_service() -> cache::Service { - cache::Service::GitHub + fn cache_service() -> data::Service { + data::Service::GitHub } fn path() -> &'static str { @@ -176,8 +175,8 @@ pub(crate) struct Bitbucket; impl Service for Bitbucket { type Response = BitbucketApiResponse; - fn cache_service() -> cache::Service { - cache::Service::Bitbucket + fn cache_service() -> data::Service { + data::Service::Bitbucket } fn path() -> &'static str { @@ -208,8 +207,8 @@ pub(crate) struct GitLab; impl Service for GitLab { type Response = GitLabApiResponse; - fn cache_service() -> cache::Service { - cache::Service::GitLab + fn cache_service() -> data::Service { + data::Service::GitLab } fn path() -> &'static str { diff --git a/time-cache/.gitignore b/time-cache/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/time-cache/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/time-cache/Cargo.lock b/time-cache/Cargo.lock new file mode 100644 index 0000000..5d28d78 --- /dev/null +++ b/time-cache/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "time-cache" +version = "0.1.0" + diff --git a/time-cache/Cargo.toml b/time-cache/Cargo.toml new file mode 100644 index 0000000..f453678 --- /dev/null +++ b/time-cache/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "time-cache" +version = "0.1.0" +authors = ["Valentin Brandl "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/time-cache/src/lib.rs b/time-cache/src/lib.rs new file mode 100644 index 0000000..52bce78 --- /dev/null +++ b/time-cache/src/lib.rs @@ -0,0 +1,74 @@ +use std::{ + collections::HashMap, + hash::Hash, + time::{Duration, Instant}, +}; + +pub struct Cache { + cache: HashMap>, + duration: Duration, +} + +impl Cache +where + K: Eq + Hash, +{ + pub fn new(duration: Duration) -> Self { + Self { + cache: HashMap::new(), + duration, + } + } + + pub fn get(&self, key: &K) -> CacheResult<&V> { + if let Some(entry) = self.cache.get(key) { + if Self::is_valid(Instant::now(), entry) { + CacheResult::Cached(&entry.1) + } else { + CacheResult::Invalid + } + } else { + CacheResult::Empty + } + } + + pub fn invalidate(&mut self, key: &K) -> bool { + self.cache.remove(key).is_some() + } + + pub fn store(&mut self, key: K, value: V) -> Option { + self.cache + .insert(key, CacheEntry::new(value, self.duration)) + .map(|old| old.1) + } + + pub fn clear(&mut self) { + let now = Instant::now(); + self.cache.retain(|_, v| !Self::is_valid(now, v)); + } + + fn is_valid(when: Instant, entry: &CacheEntry) -> bool { + entry.0 >= when + } +} + +pub enum CacheResult { + Cached(T), + Invalid, + Empty, +} + +struct CacheEntry(Instant, T); + +impl CacheEntry { + fn new(value: T, duration: Duration) -> Self { + CacheEntry(Instant::now() + duration, value) + } +} +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}