mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 19:31:35 +00:00
Remove cache functionality
This commit is contained in:
parent
123488f617
commit
281ac43dff
9 changed files with 16 additions and 344 deletions
|
@ -91,8 +91,6 @@ Options:
|
||||||
Which media filters should be enabled on the `process` endpoint
|
Which media filters should be enabled on the `process` endpoint
|
||||||
--media-format <MEDIA_FORMAT>
|
--media-format <MEDIA_FORMAT>
|
||||||
Enforce uploaded media is transcoded to the provided format [possible values: jpeg, webp, png]
|
Enforce uploaded media is transcoded to the provided format [possible values: jpeg, webp, png]
|
||||||
--media-cache-duration <MEDIA_CACHE_DURATION>
|
|
||||||
How long, in hours, to keep media ingested through the "cached" endpoint
|
|
||||||
-h, --help
|
-h, --help
|
||||||
Print help information (use `--help` for more detail)
|
Print help information (use `--help` for more detail)
|
||||||
```
|
```
|
||||||
|
@ -230,15 +228,11 @@ pict-rs offers the following endpoints:
|
||||||
"msg": "ok"
|
"msg": "ok"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- `GET /image/download?url={url}&backgrounded=(true|false)&ephemeral=(true|false)` Download an image
|
- `GET /image/download?url={url}&backgrounded=(true|false)` Download an image
|
||||||
from a remote server, returning the same JSON payload as the `POST /image` endpoint by default.
|
from a remote server, returning the same JSON payload as the `POST /image` endpoint by default.
|
||||||
|
|
||||||
if `backgrounded` is set to `true`, then the ingest processing will be queued for later and the
|
if `backgrounded` is set to `true`, then the ingest processing will be queued for later and the
|
||||||
response json will be the same as the `POST /image/backgrounded` endpoint.
|
response json will be the same as the `POST /image/backgrounded` endpoint.
|
||||||
|
|
||||||
if `ephemeral` is set to true, the downloaded image will be marked as a "cached" image, and
|
|
||||||
automatically removed from pict-rs N hours after its last access. The duration is configurable
|
|
||||||
with the `--media-cache-duration` run flag, or the `[media] cache_duration` toml option.
|
|
||||||
- `GET /image/backgrounded/claim?upload_id={uuid}` Wait for a backgrounded upload to complete, claiming it's result
|
- `GET /image/backgrounded/claim?upload_id={uuid}` Wait for a backgrounded upload to complete, claiming it's result
|
||||||
Possible results:
|
Possible results:
|
||||||
- 200 Ok (validation and ingest complete):
|
- 200 Ok (validation and ingest complete):
|
||||||
|
|
|
@ -48,7 +48,6 @@ pub(crate) async fn ingest<R, S>(
|
||||||
stream: impl Stream<Item = Result<Bytes, Error>> + Unpin + 'static,
|
stream: impl Stream<Item = Result<Bytes, Error>> + Unpin + 'static,
|
||||||
declared_alias: Option<Alias>,
|
declared_alias: Option<Alias>,
|
||||||
should_validate: bool,
|
should_validate: bool,
|
||||||
is_cached: bool,
|
|
||||||
) -> Result<Session<R, S>, Error>
|
) -> Result<Session<R, S>, Error>
|
||||||
where
|
where
|
||||||
R: FullRepo + 'static,
|
R: FullRepo + 'static,
|
||||||
|
@ -98,9 +97,9 @@ where
|
||||||
save_upload(repo, store, &hash, &identifier).await?;
|
save_upload(repo, store, &hash, &identifier).await?;
|
||||||
|
|
||||||
if let Some(alias) = declared_alias {
|
if let Some(alias) = declared_alias {
|
||||||
session.add_existing_alias(&hash, alias, is_cached).await?
|
session.add_existing_alias(&hash, alias).await?
|
||||||
} else {
|
} else {
|
||||||
session.create_alias(&hash, input_type, is_cached).await?;
|
session.create_alias(&hash, input_type).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(session)
|
Ok(session)
|
||||||
|
@ -168,7 +167,6 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
hash: &[u8],
|
hash: &[u8],
|
||||||
alias: Alias,
|
alias: Alias,
|
||||||
is_cached: bool,
|
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
AliasRepo::create(&self.repo, &alias)
|
AliasRepo::create(&self.repo, &alias)
|
||||||
.await?
|
.await?
|
||||||
|
@ -179,10 +177,6 @@ where
|
||||||
self.repo.relate_hash(&alias, hash.to_vec().into()).await?;
|
self.repo.relate_hash(&alias, hash.to_vec().into()).await?;
|
||||||
self.repo.relate_alias(hash.to_vec().into(), &alias).await?;
|
self.repo.relate_alias(hash.to_vec().into(), &alias).await?;
|
||||||
|
|
||||||
if is_cached {
|
|
||||||
self.repo.mark_cached(&alias).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +185,6 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
hash: &[u8],
|
hash: &[u8],
|
||||||
input_type: ValidInputType,
|
input_type: ValidInputType,
|
||||||
is_cached: bool,
|
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
loop {
|
loop {
|
||||||
let alias = Alias::generate(input_type.as_ext().to_string());
|
let alias = Alias::generate(input_type.as_ext().to_string());
|
||||||
|
@ -202,10 +195,6 @@ where
|
||||||
self.repo.relate_hash(&alias, hash.to_vec().into()).await?;
|
self.repo.relate_hash(&alias, hash.to_vec().into()).await?;
|
||||||
self.repo.relate_alias(hash.to_vec().into(), &alias).await?;
|
self.repo.relate_alias(hash.to_vec().into(), &alias).await?;
|
||||||
|
|
||||||
if is_cached {
|
|
||||||
self.repo.mark_cached(&alias).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
30
src/lib.rs
30
src/lib.rs
|
@ -156,10 +156,8 @@ impl<R: FullRepo, S: Store + 'static> FormData for Upload<R, S> {
|
||||||
let stream = stream.map_err(Error::from);
|
let stream = stream.map_err(Error::from);
|
||||||
|
|
||||||
Box::pin(
|
Box::pin(
|
||||||
async move {
|
async move { ingest::ingest(&**repo, &**store, stream, None, true).await }
|
||||||
ingest::ingest(&**repo, &**store, stream, None, true, false).await
|
.instrument(span),
|
||||||
}
|
|
||||||
.instrument(span),
|
|
||||||
)
|
)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
@ -211,7 +209,6 @@ impl<R: FullRepo, S: Store + 'static> FormData for Import<R, S> {
|
||||||
stream,
|
stream,
|
||||||
Some(Alias::from_existing(&filename)),
|
Some(Alias::from_existing(&filename)),
|
||||||
!CONFIG.media.skip_validate_imports,
|
!CONFIG.media.skip_validate_imports,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -367,7 +364,7 @@ async fn upload_backgrounded<R: FullRepo, S: Store>(
|
||||||
.expect("Identifier exists")
|
.expect("Identifier exists")
|
||||||
.to_bytes()?;
|
.to_bytes()?;
|
||||||
|
|
||||||
queue::queue_ingest(&repo, identifier, upload_id, None, true, false).await?;
|
queue::queue_ingest(&repo, identifier, upload_id, None, true).await?;
|
||||||
|
|
||||||
files.push(serde_json::json!({
|
files.push(serde_json::json!({
|
||||||
"upload_id": upload_id.to_string(),
|
"upload_id": upload_id.to_string(),
|
||||||
|
@ -433,9 +430,6 @@ struct UrlQuery {
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
backgrounded: bool,
|
backgrounded: bool,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
ephemeral: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// download an image from a URL
|
/// download an image from a URL
|
||||||
|
@ -457,9 +451,9 @@ async fn download<R: FullRepo + 'static, S: Store + 'static>(
|
||||||
.limit((CONFIG.media.max_file_size * MEGABYTES) as u64);
|
.limit((CONFIG.media.max_file_size * MEGABYTES) as u64);
|
||||||
|
|
||||||
if query.backgrounded {
|
if query.backgrounded {
|
||||||
do_download_backgrounded(stream, repo, store, query.ephemeral).await
|
do_download_backgrounded(stream, repo, store).await
|
||||||
} else {
|
} else {
|
||||||
do_download_inline(stream, repo, store, query.ephemeral).await
|
do_download_inline(stream, repo, store).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,9 +462,8 @@ async fn do_download_inline<R: FullRepo + 'static, S: Store + 'static>(
|
||||||
stream: impl Stream<Item = Result<web::Bytes, Error>> + Unpin + 'static,
|
stream: impl Stream<Item = Result<web::Bytes, Error>> + Unpin + 'static,
|
||||||
repo: web::Data<R>,
|
repo: web::Data<R>,
|
||||||
store: web::Data<S>,
|
store: web::Data<S>,
|
||||||
is_cached: bool,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let mut session = ingest::ingest(&repo, &store, stream, None, true, is_cached).await?;
|
let mut session = ingest::ingest(&repo, &store, stream, None, true).await?;
|
||||||
|
|
||||||
let alias = session.alias().expect("alias should exist").to_owned();
|
let alias = session.alias().expect("alias should exist").to_owned();
|
||||||
let delete_token = session.delete_token().await?;
|
let delete_token = session.delete_token().await?;
|
||||||
|
@ -494,7 +487,6 @@ async fn do_download_backgrounded<R: FullRepo + 'static, S: Store + 'static>(
|
||||||
stream: impl Stream<Item = Result<web::Bytes, Error>> + Unpin + 'static,
|
stream: impl Stream<Item = Result<web::Bytes, Error>> + Unpin + 'static,
|
||||||
repo: web::Data<R>,
|
repo: web::Data<R>,
|
||||||
store: web::Data<S>,
|
store: web::Data<S>,
|
||||||
is_cached: bool,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let backgrounded = Backgrounded::proxy((**repo).clone(), (**store).clone(), stream).await?;
|
let backgrounded = Backgrounded::proxy((**repo).clone(), (**store).clone(), stream).await?;
|
||||||
|
|
||||||
|
@ -504,7 +496,7 @@ async fn do_download_backgrounded<R: FullRepo + 'static, S: Store + 'static>(
|
||||||
.expect("Identifier exists")
|
.expect("Identifier exists")
|
||||||
.to_bytes()?;
|
.to_bytes()?;
|
||||||
|
|
||||||
queue::queue_ingest(&repo, identifier, upload_id, None, true, is_cached).await?;
|
queue::queue_ingest(&repo, identifier, upload_id, None, true).await?;
|
||||||
|
|
||||||
backgrounded.disarm();
|
backgrounded.disarm();
|
||||||
|
|
||||||
|
@ -605,8 +597,6 @@ async fn process<R: FullRepo, S: Store + 'static>(
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let (format, alias, thumbnail_path, thumbnail_args) = prepare_process(query, ext.as_str())?;
|
let (format, alias, thumbnail_path, thumbnail_args) = prepare_process(query, ext.as_str())?;
|
||||||
|
|
||||||
repo.check_cached(&alias).await?;
|
|
||||||
|
|
||||||
let path_string = thumbnail_path.to_string_lossy().to_string();
|
let path_string = thumbnail_path.to_string_lossy().to_string();
|
||||||
let hash = repo.hash(&alias).await?;
|
let hash = repo.hash(&alias).await?;
|
||||||
|
|
||||||
|
@ -694,8 +684,6 @@ async fn process_head<R: FullRepo, S: Store + 'static>(
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let (format, alias, thumbnail_path, _) = prepare_process(query, ext.as_str())?;
|
let (format, alias, thumbnail_path, _) = prepare_process(query, ext.as_str())?;
|
||||||
|
|
||||||
repo.check_cached(&alias).await?;
|
|
||||||
|
|
||||||
let path_string = thumbnail_path.to_string_lossy().to_string();
|
let path_string = thumbnail_path.to_string_lossy().to_string();
|
||||||
let hash = repo.hash(&alias).await?;
|
let hash = repo.hash(&alias).await?;
|
||||||
let identifier_opt = repo
|
let identifier_opt = repo
|
||||||
|
@ -776,8 +764,6 @@ async fn serve<R: FullRepo, S: Store + 'static>(
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let alias = alias.into_inner();
|
let alias = alias.into_inner();
|
||||||
|
|
||||||
repo.check_cached(&alias).await?;
|
|
||||||
|
|
||||||
let identifier = repo.identifier_from_alias::<S::Identifier>(&alias).await?;
|
let identifier = repo.identifier_from_alias::<S::Identifier>(&alias).await?;
|
||||||
|
|
||||||
let details = ensure_details(&repo, &store, &alias).await?;
|
let details = ensure_details(&repo, &store, &alias).await?;
|
||||||
|
@ -794,8 +780,6 @@ async fn serve_head<R: FullRepo, S: Store + 'static>(
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let alias = alias.into_inner();
|
let alias = alias.into_inner();
|
||||||
|
|
||||||
repo.check_cached(&alias).await?;
|
|
||||||
|
|
||||||
let identifier = repo.identifier_from_alias::<S::Identifier>(&alias).await?;
|
let identifier = repo.identifier_from_alias::<S::Identifier>(&alias).await?;
|
||||||
|
|
||||||
let details = ensure_details(&repo, &store, &alias).await?;
|
let details = ensure_details(&repo, &store, &alias).await?;
|
||||||
|
|
|
@ -68,7 +68,6 @@ enum Process {
|
||||||
upload_id: Serde<UploadId>,
|
upload_id: Serde<UploadId>,
|
||||||
declared_alias: Option<Serde<Alias>>,
|
declared_alias: Option<Serde<Alias>>,
|
||||||
should_validate: bool,
|
should_validate: bool,
|
||||||
is_cached: bool,
|
|
||||||
},
|
},
|
||||||
Generate {
|
Generate {
|
||||||
target_format: ImageFormat,
|
target_format: ImageFormat,
|
||||||
|
@ -130,14 +129,12 @@ pub(crate) async fn queue_ingest<R: QueueRepo>(
|
||||||
upload_id: UploadId,
|
upload_id: UploadId,
|
||||||
declared_alias: Option<Alias>,
|
declared_alias: Option<Alias>,
|
||||||
should_validate: bool,
|
should_validate: bool,
|
||||||
is_cached: bool,
|
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let job = serde_json::to_vec(&Process::Ingest {
|
let job = serde_json::to_vec(&Process::Ingest {
|
||||||
identifier: Base64Bytes(identifier),
|
identifier: Base64Bytes(identifier),
|
||||||
declared_alias: declared_alias.map(Serde::new),
|
declared_alias: declared_alias.map(Serde::new),
|
||||||
upload_id: Serde::new(upload_id),
|
upload_id: Serde::new(upload_id),
|
||||||
should_validate,
|
should_validate,
|
||||||
is_cached,
|
|
||||||
})?;
|
})?;
|
||||||
repo.push(PROCESS_QUEUE, job.into()).await?;
|
repo.push(PROCESS_QUEUE, job.into()).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -27,7 +27,6 @@ where
|
||||||
upload_id,
|
upload_id,
|
||||||
declared_alias,
|
declared_alias,
|
||||||
should_validate,
|
should_validate,
|
||||||
is_cached,
|
|
||||||
} => {
|
} => {
|
||||||
process_ingest(
|
process_ingest(
|
||||||
repo,
|
repo,
|
||||||
|
@ -36,7 +35,6 @@ where
|
||||||
Serde::into_inner(upload_id),
|
Serde::into_inner(upload_id),
|
||||||
declared_alias.map(Serde::into_inner),
|
declared_alias.map(Serde::into_inner),
|
||||||
should_validate,
|
should_validate,
|
||||||
is_cached,
|
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
@ -74,7 +72,6 @@ async fn process_ingest<R, S>(
|
||||||
upload_id: UploadId,
|
upload_id: UploadId,
|
||||||
declared_alias: Option<Alias>,
|
declared_alias: Option<Alias>,
|
||||||
should_validate: bool,
|
should_validate: bool,
|
||||||
is_cached: bool,
|
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
R: FullRepo + 'static,
|
R: FullRepo + 'static,
|
||||||
|
@ -88,15 +85,8 @@ where
|
||||||
.await?
|
.await?
|
||||||
.map_err(Error::from);
|
.map_err(Error::from);
|
||||||
|
|
||||||
let session = crate::ingest::ingest(
|
let session =
|
||||||
repo,
|
crate::ingest::ingest(repo, store, stream, declared_alias, should_validate).await?;
|
||||||
store,
|
|
||||||
stream,
|
|
||||||
declared_alias,
|
|
||||||
should_validate,
|
|
||||||
is_cached,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let token = session.delete_token().await?;
|
let token = session.delete_token().await?;
|
||||||
|
|
||||||
|
|
36
src/repo.rs
36
src/repo.rs
|
@ -49,8 +49,7 @@ pub(crate) enum UploadResult {
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
pub(crate) trait FullRepo:
|
pub(crate) trait FullRepo:
|
||||||
CachedRepo
|
UploadRepo
|
||||||
+ UploadRepo
|
|
||||||
+ SettingsRepo
|
+ SettingsRepo
|
||||||
+ IdentifierRepo
|
+ IdentifierRepo
|
||||||
+ AliasRepo
|
+ AliasRepo
|
||||||
|
@ -92,18 +91,6 @@ pub(crate) trait FullRepo:
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
async fn check_cached(&self, alias: &Alias) -> Result<(), Error> {
|
|
||||||
let aliases = CachedRepo::update(self, alias).await?;
|
|
||||||
|
|
||||||
for alias in aliases {
|
|
||||||
let token = self.delete_token(&alias).await?;
|
|
||||||
crate::queue::cleanup_alias(self, alias, token).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
|
@ -127,27 +114,6 @@ where
|
||||||
type Bytes = T::Bytes;
|
type Bytes = T::Bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
pub(crate) trait CachedRepo: BaseRepo {
|
|
||||||
async fn mark_cached(&self, alias: &Alias) -> Result<(), Error>;
|
|
||||||
|
|
||||||
async fn update(&self, alias: &Alias) -> Result<Vec<Alias>, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl<T> CachedRepo for actix_web::web::Data<T>
|
|
||||||
where
|
|
||||||
T: CachedRepo,
|
|
||||||
{
|
|
||||||
async fn mark_cached(&self, alias: &Alias) -> Result<(), Error> {
|
|
||||||
T::mark_cached(self, alias).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(&self, alias: &Alias) -> Result<Vec<Alias>, Error> {
|
|
||||||
T::update(self, alias).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
pub(crate) trait UploadRepo: BaseRepo {
|
pub(crate) trait UploadRepo: BaseRepo {
|
||||||
async fn create(&self, upload_id: UploadId) -> Result<(), Error>;
|
async fn create(&self, upload_id: UploadId) -> Result<(), Error>;
|
||||||
|
|
160
src/repo/sled.rs
160
src/repo/sled.rs
|
@ -1,15 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, UploadError},
|
error::{Error, UploadError},
|
||||||
repo::{
|
repo::{
|
||||||
Alias, AliasRepo, AlreadyExists, BaseRepo, CachedRepo, DeleteToken, Details, FullRepo,
|
Alias, AliasRepo, AlreadyExists, BaseRepo, DeleteToken, Details, FullRepo, HashRepo,
|
||||||
HashRepo, Identifier, IdentifierRepo, QueueRepo, SettingsRepo, UploadId, UploadRepo,
|
Identifier, IdentifierRepo, QueueRepo, SettingsRepo, UploadId, UploadRepo, UploadResult,
|
||||||
UploadResult,
|
|
||||||
},
|
},
|
||||||
serde_str::Serde,
|
serde_str::Serde,
|
||||||
stream::from_iterator,
|
stream::from_iterator,
|
||||||
};
|
};
|
||||||
use futures_util::Stream;
|
use futures_util::Stream;
|
||||||
use sled::{CompareAndSwapError, Db, IVec, Tree};
|
use sled::{Db, IVec, Tree};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
|
@ -20,12 +19,6 @@ use std::{
|
||||||
};
|
};
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
mod bucket;
|
|
||||||
mod datetime;
|
|
||||||
|
|
||||||
use bucket::Bucket;
|
|
||||||
use datetime::DateTime;
|
|
||||||
|
|
||||||
macro_rules! b {
|
macro_rules! b {
|
||||||
($self:ident.$ident:ident, $expr:expr) => {{
|
($self:ident.$ident:ident, $expr:expr) => {{
|
||||||
let $ident = $self.$ident.clone();
|
let $ident = $self.$ident.clone();
|
||||||
|
@ -71,8 +64,6 @@ pub(crate) struct SledRepo {
|
||||||
in_progress_queue: Tree,
|
in_progress_queue: Tree,
|
||||||
queue_notifier: Arc<RwLock<HashMap<&'static str, Arc<Notify>>>>,
|
queue_notifier: Arc<RwLock<HashMap<&'static str, Arc<Notify>>>>,
|
||||||
uploads: Tree,
|
uploads: Tree,
|
||||||
cache: Tree,
|
|
||||||
cache_inverse: Tree,
|
|
||||||
db: Db,
|
db: Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,8 +86,6 @@ impl SledRepo {
|
||||||
in_progress_queue: db.open_tree("pict-rs-in-progress-queue-tree")?,
|
in_progress_queue: db.open_tree("pict-rs-in-progress-queue-tree")?,
|
||||||
queue_notifier: Arc::new(RwLock::new(HashMap::new())),
|
queue_notifier: Arc::new(RwLock::new(HashMap::new())),
|
||||||
uploads: db.open_tree("pict-rs-uploads-tree")?,
|
uploads: db.open_tree("pict-rs-uploads-tree")?,
|
||||||
cache: db.open_tree("pict-rs-cache-tree")?,
|
|
||||||
cache_inverse: db.open_tree("pict-rs-cache-inverse-tree")?,
|
|
||||||
db,
|
db,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -154,149 +143,6 @@ impl From<InnerUploadResult> for UploadResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_cache_inverse(
|
|
||||||
cache_inverse: &Tree,
|
|
||||||
now_bytes: &[u8],
|
|
||||||
alias_bytes: &[u8],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut old = cache_inverse.get(now_bytes)?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// unsure of whether to bail on deserialize error or fail with empty bucket
|
|
||||||
let mut bucket = old
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|old| serde_cbor::from_slice::<Bucket>(old).ok())
|
|
||||||
.unwrap_or_else(Bucket::empty);
|
|
||||||
|
|
||||||
bucket.insert(alias_bytes.to_vec());
|
|
||||||
|
|
||||||
tracing::trace!("Inserting new {:?}", bucket);
|
|
||||||
let bucket_bytes = serde_cbor::to_vec(&bucket)?;
|
|
||||||
let new = Some(bucket_bytes);
|
|
||||||
|
|
||||||
let res = cache_inverse.compare_and_swap(now_bytes, old, new)?;
|
|
||||||
|
|
||||||
if let Err(CompareAndSwapError { current, .. }) = res {
|
|
||||||
old = current;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl CachedRepo for SledRepo {
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
async fn mark_cached(&self, alias: &Alias) -> Result<(), Error> {
|
|
||||||
let now = DateTime::now();
|
|
||||||
let now_bytes = serde_json::to_vec(&now)?;
|
|
||||||
|
|
||||||
let alias_bytes = alias.to_bytes();
|
|
||||||
|
|
||||||
let cache_inverse = self.cache_inverse.clone();
|
|
||||||
b!(self.cache, {
|
|
||||||
cache.insert(&alias_bytes, now_bytes.clone())?;
|
|
||||||
|
|
||||||
insert_cache_inverse(&cache_inverse, &now_bytes, &alias_bytes)
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
async fn update(&self, alias: &Alias) -> Result<Vec<Alias>, Error> {
|
|
||||||
let now = DateTime::now();
|
|
||||||
let now_bytes = serde_json::to_vec(&now)?;
|
|
||||||
|
|
||||||
let to_clean = now.min_cache_date();
|
|
||||||
let to_clean_bytes = serde_json::to_vec(&to_clean)?;
|
|
||||||
|
|
||||||
let alias_bytes = alias.to_bytes();
|
|
||||||
|
|
||||||
let cache_inverse = self.cache_inverse.clone();
|
|
||||||
let aliases = b!(self.cache, {
|
|
||||||
let previous_datetime_opt = cache
|
|
||||||
.fetch_and_update(&alias_bytes, |previous_datetime_opt| {
|
|
||||||
previous_datetime_opt.map(|_| now_bytes.clone())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(previous_datetime_bytes) = previous_datetime_opt {
|
|
||||||
// Insert cached media into new date bucket
|
|
||||||
insert_cache_inverse(&cache_inverse, &now_bytes, &alias_bytes)?;
|
|
||||||
|
|
||||||
// Remove cached media from old date bucket
|
|
||||||
let mut old = cache_inverse.get(&previous_datetime_bytes)?;
|
|
||||||
loop {
|
|
||||||
let new = old
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|bucket_bytes| {
|
|
||||||
let mut bucket = serde_cbor::from_slice::<Bucket>(bucket_bytes).ok()?;
|
|
||||||
|
|
||||||
bucket.remove(&alias_bytes);
|
|
||||||
|
|
||||||
if bucket.is_empty() {
|
|
||||||
tracing::trace!("Removed old {:?}", bucket);
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
tracing::trace!("Inserting old {:?}", bucket);
|
|
||||||
Some(serde_cbor::to_vec(&bucket))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
if let Err(CompareAndSwapError { current, .. }) =
|
|
||||||
cache_inverse.compare_and_swap(&previous_datetime_bytes, old, new)?
|
|
||||||
{
|
|
||||||
old = current;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut aliases: Vec<Alias> = Vec::new();
|
|
||||||
|
|
||||||
for (date_bytes, bucket_bytes) in
|
|
||||||
cache_inverse.range(..to_clean_bytes).filter_map(Result::ok)
|
|
||||||
{
|
|
||||||
if let Ok(datetime) = serde_json::from_slice::<DateTime>(&date_bytes) {
|
|
||||||
tracing::trace!("Checking {}", datetime);
|
|
||||||
} else {
|
|
||||||
tracing::warn!("Invalid date bytes");
|
|
||||||
}
|
|
||||||
if let Ok(bucket) = serde_cbor::from_slice::<Bucket>(&bucket_bytes) {
|
|
||||||
tracing::trace!("Read for deletion: {:?}", bucket);
|
|
||||||
for alias_bytes in bucket {
|
|
||||||
// Best effort cleanup
|
|
||||||
let _ = cache.remove(&alias_bytes);
|
|
||||||
if let Some(alias) = Alias::from_slice(&alias_bytes) {
|
|
||||||
aliases.push(alias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::warn!("Invalid bucket");
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_inverse.remove(date_bytes)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(debug)]
|
|
||||||
for date_bytes in cache_inverse.range(to_clean_bytes..).filter_map(Result::ok) {
|
|
||||||
if let Ok(datetime) = serde_json::from_slice::<DateTime>(&date_bytes) {
|
|
||||||
tracing::trace!("Not cleaning for {}", datetime);
|
|
||||||
} else {
|
|
||||||
tracing::warn!("Invalid date bytes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(aliases) as Result<_, Error>
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(aliases)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl UploadRepo for SledRepo {
|
impl UploadRepo for SledRepo {
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
#[tracing::instrument(level = "trace", skip(self))]
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub(super) struct Bucket {
|
|
||||||
// each Vec<u8> represents a unique image hash
|
|
||||||
inner: HashSet<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bucket {
|
|
||||||
pub(super) fn empty() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: HashSet::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn insert(&mut self, alias_bytes: Vec<u8>) {
|
|
||||||
self.inner.insert(alias_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn remove(&mut self, alias_bytes: &[u8]) {
|
|
||||||
self.inner.remove(alias_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn is_empty(&self) -> bool {
|
|
||||||
self.inner.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoIterator for Bucket {
|
|
||||||
type Item = <HashSet<Vec<u8>> as IntoIterator>::Item;
|
|
||||||
type IntoIter = <HashSet<Vec<u8>> as IntoIterator>::IntoIter;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
self.inner.into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
use std::ops::Deref;
|
|
||||||
use time::{Duration, OffsetDateTime};
|
|
||||||
|
|
||||||
use crate::CONFIG;
|
|
||||||
|
|
||||||
const SECONDS: i64 = 1;
|
|
||||||
const MINUTES: i64 = 60 * SECONDS;
|
|
||||||
const HOURS: i64 = 60 * MINUTES;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub(super) struct DateTime {
|
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
|
||||||
inner_date: OffsetDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DateTime {
|
|
||||||
pub(super) fn now() -> Self {
|
|
||||||
DateTime {
|
|
||||||
inner_date: OffsetDateTime::now_utc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn min_cache_date(&self) -> Self {
|
|
||||||
let cache_duration = Duration::new(CONFIG.media.cache_duration * HOURS, 0);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
inner_date: self
|
|
||||||
.checked_sub(cache_duration)
|
|
||||||
.expect("Should never be older than Jan 7, 1970"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for DateTime {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.inner_date.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<OffsetDateTime> for DateTime {
|
|
||||||
fn as_ref(&self) -> &OffsetDateTime {
|
|
||||||
&self.inner_date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for DateTime {
|
|
||||||
type Target = OffsetDateTime;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.inner_date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<OffsetDateTime> for DateTime {
|
|
||||||
fn from(inner_date: OffsetDateTime) -> Self {
|
|
||||||
Self { inner_date }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue