Extract cache into own crate

This commit is contained in:
Valentin Brandl 2019-08-07 21:44:20 +02:00
parent 2ef81fb75b
commit 4985c497df
No known key found for this signature in database
GPG Key ID: 30D341DD34118D7D
10 changed files with 132 additions and 106 deletions

5
backend/Cargo.lock generated
View File

@ -659,6 +659,7 @@ dependencies = [
"serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
"time-cache 0.1.0",
] ]
[[package]] [[package]]
@ -1602,6 +1603,10 @@ dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "time-cache"
version = "0.1.0"
[[package]] [[package]]
name = "tokio-codec" name = "tokio-codec"
version = "0.1.1" version = "0.1.1"

View File

@ -19,3 +19,4 @@ serde = { version = "1.0.97", features = ["rc"] }
serde_derive = "1.0.97" serde_derive = "1.0.97"
serde_json = "1.0.40" serde_json = "1.0.40"
structopt = "0.2.18" structopt = "0.2.18"
time-cache = { path = "../time-cache" }

View File

@ -1,90 +0,0 @@
use crate::statics::REDIRECT_AGE;
use std::{
collections::HashMap,
hash::Hash,
sync::Arc,
time::{Duration, Instant},
};
pub(crate) struct Cache<K, V> {
cache: HashMap<K, CacheEntry<V>>,
duration: Duration,
}
impl<K, V> Cache<K, V>
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<V> {
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<V>) -> bool {
entry.0 >= when
}
}
pub(crate) enum CacheResult<T> {
Cached(T),
Invalid,
Empty,
}
struct CacheEntry<T>(Instant, T);
impl<T> CacheEntry<T> {
fn new(value: T, duration: Duration) -> Self {
CacheEntry(Instant::now() + duration, value)
}
}
#[derive(Eq, PartialEq, Hash, Debug)]
pub(crate) struct Key(Service, Arc<String>, Arc<String>, Arc<String>);
#[derive(Eq, PartialEq, Hash, Debug)]
pub(crate) enum Service {
GitHub,
GitLab,
Bitbucket,
}
impl Key {
pub(crate) fn new(
service: Service,
user: Arc<String>,
repo: Arc<String>,
branch: Arc<String>,
) -> Self {
Key(service, user, repo, branch)
}
}

View File

@ -1,8 +1,6 @@
use crate::{ use crate::service;
cache::{Cache, Key},
service::Service,
};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use time_cache::Cache;
pub(crate) type State = Arc<RwLock<Cache<Key, String>>>; pub(crate) type State = Arc<RwLock<Cache<Key, String>>>;
@ -19,7 +17,7 @@ impl FilePath {
format!("{}/{}/{}/{}", self.user, self.repo, self.commit, self.file) format!("{}/{}/{}/{}", self.user, self.repo, self.commit, self.file)
} }
pub(crate) fn to_key<T: Service>(&self) -> Key { pub(crate) fn to_key<T: service::Service>(&self) -> Key {
Key::new( Key::new(
T::cache_service(), T::cache_service(),
self.user.clone(), self.user.clone(),
@ -28,3 +26,24 @@ impl FilePath {
) )
} }
} }
#[derive(Eq, PartialEq, Hash, Debug)]
pub(crate) struct Key(Service, Arc<String>, Arc<String>, Arc<String>);
#[derive(Eq, PartialEq, Hash, Debug)]
pub(crate) enum Service {
GitHub,
GitLab,
Bitbucket,
}
impl Key {
pub(crate) fn new(
service: Service,
user: Arc<String>,
repo: Arc<String>,
branch: Arc<String>,
) -> Self {
Key(service, user, repo, branch)
}
}

View File

@ -16,7 +16,6 @@ mod service;
mod statics; mod statics;
use crate::{ use crate::{
cache::{Cache, CacheResult},
cdn::Cloudflare, cdn::Cloudflare,
data::{FilePath, State}, data::{FilePath, State},
error::Result, error::Result,
@ -31,6 +30,7 @@ use actix_web::{
use awc::{http::StatusCode, Client}; use awc::{http::StatusCode, Client};
use futures::Future; use futures::Future;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use time_cache::{Cache, CacheResult};
fn proxy_file<T: Service>( fn proxy_file<T: Service>(
client: web::Data<Client>, client: web::Data<Client>,
@ -210,7 +210,7 @@ fn main() -> Result<()> {
pretty_env_logger::init(); pretty_env_logger::init();
openssl_probe::init_ssl_cert_env_vars(); 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 || { Ok(HttpServer::new(move || {
App::new() App::new()
.data(Client::new()) .data(Client::new())

View File

@ -1,6 +1,5 @@
use crate::{ use crate::{
cache, data::{self, FilePath, State},
data::{FilePath, State},
statics::{load_env_var, GITHUB_AUTH_QUERY, OPT, REDIRECT_AGE}, statics::{load_env_var, GITHUB_AUTH_QUERY, OPT, REDIRECT_AGE},
}; };
use actix_web::{ 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 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; fn api_url(path: &FilePath) -> String;
@ -141,8 +140,8 @@ impl Github {
impl Service for Github { impl Service for Github {
type Response = GitHubApiResponse; type Response = GitHubApiResponse;
fn cache_service() -> cache::Service { fn cache_service() -> data::Service {
cache::Service::GitHub data::Service::GitHub
} }
fn path() -> &'static str { fn path() -> &'static str {
@ -176,8 +175,8 @@ pub(crate) struct Bitbucket;
impl Service for Bitbucket { impl Service for Bitbucket {
type Response = BitbucketApiResponse; type Response = BitbucketApiResponse;
fn cache_service() -> cache::Service { fn cache_service() -> data::Service {
cache::Service::Bitbucket data::Service::Bitbucket
} }
fn path() -> &'static str { fn path() -> &'static str {
@ -208,8 +207,8 @@ pub(crate) struct GitLab;
impl Service for GitLab { impl Service for GitLab {
type Response = GitLabApiResponse; type Response = GitLabApiResponse;
fn cache_service() -> cache::Service { fn cache_service() -> data::Service {
cache::Service::GitLab data::Service::GitLab
} }
fn path() -> &'static str { fn path() -> &'static str {

3
time-cache/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

6
time-cache/Cargo.lock generated Normal file
View File

@ -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"

9
time-cache/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "time-cache"
version = "0.1.0"
authors = ["Valentin Brandl <vbrandl@riseup.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

74
time-cache/src/lib.rs Normal file
View File

@ -0,0 +1,74 @@
use std::{
collections::HashMap,
hash::Hash,
time::{Duration, Instant},
};
pub struct Cache<K, V> {
cache: HashMap<K, CacheEntry<V>>,
duration: Duration,
}
impl<K, V> Cache<K, V>
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<V> {
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<V>) -> bool {
entry.0 >= when
}
}
pub enum CacheResult<T> {
Cached(T),
Invalid,
Empty,
}
struct CacheEntry<T>(Instant, T);
impl<T> CacheEntry<T> {
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);
}
}