mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 11:21:24 +00:00
Add content type, width, height to Details, add details endpoints
This commit is contained in:
parent
c43737c894
commit
c0a20588d3
5 changed files with 229 additions and 56 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1449,7 +1449,7 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pict-rs"
|
name = "pict-rs"
|
||||||
version = "0.3.0-alpha.0"
|
version = "0.3.0-alpha.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-form-data",
|
"actix-form-data",
|
||||||
"actix-fs",
|
"actix-fs",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pict-rs"
|
name = "pict-rs"
|
||||||
description = "A simple image hosting service"
|
description = "A simple image hosting service"
|
||||||
version = "0.3.0-alpha.0"
|
version = "0.3.0-alpha.1"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
19
README.md
19
README.md
|
@ -4,7 +4,7 @@ _a simple image hosting service_
|
||||||
## Usage
|
## Usage
|
||||||
### Running
|
### Running
|
||||||
```
|
```
|
||||||
pict-rs 0.3.0-alpha.0
|
pict-rs 0.3.0-alpha.1
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
pict-rs [FLAGS] [OPTIONS] --path <path>
|
pict-rs [FLAGS] [OPTIONS] --path <path>
|
||||||
|
@ -105,6 +105,21 @@ pict-rs offers the following endpoints:
|
||||||
payload as the `POST` endpoint
|
payload as the `POST` endpoint
|
||||||
- `GET /image/original/{file}` for getting a full-resolution image. `file` here is the `file` key from the
|
- `GET /image/original/{file}` for getting a full-resolution image. `file` here is the `file` key from the
|
||||||
`/image` endpoint's JSON
|
`/image` endpoint's JSON
|
||||||
|
- `GET /image/details/original/{file}` for getting the details of a full-resolution image.
|
||||||
|
The returned JSON is structured like so:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"width": 800,
|
||||||
|
"height": 537,
|
||||||
|
"content_type": "image/webp",
|
||||||
|
"created_at": [
|
||||||
|
2020,
|
||||||
|
345,
|
||||||
|
67376,
|
||||||
|
394363487
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
- `GET /image/process.{ext}?src={file}&...` get a file with transformations applied.
|
- `GET /image/process.{ext}?src={file}&...` get a file with transformations applied.
|
||||||
existing transformations include
|
existing transformations include
|
||||||
- `identity=true`: apply no changes
|
- `identity=true`: apply no changes
|
||||||
|
@ -121,6 +136,8 @@ pict-rs offers the following endpoints:
|
||||||
GET /image/process.jpg?src=asdf.png&thumbnail=256&blur=3.0
|
GET /image/process.jpg?src=asdf.png&thumbnail=256&blur=3.0
|
||||||
```
|
```
|
||||||
which would create a 256x256px JPEG thumbnail and blur it
|
which would create a 256x256px JPEG thumbnail and blur it
|
||||||
|
- `GET /image/details/process.{ext}?src={file}&...` for getting the details of a processed image.
|
||||||
|
The returned JSON is the same format as listed for the full-resolution details endpoint.
|
||||||
- `DELETE /image/delete/{delete_token}/{file}` or `GET /image/delete/{delete_token}/{file}` to
|
- `DELETE /image/delete/{delete_token}/{file}` or `GET /image/delete/{delete_token}/{file}` to
|
||||||
delete a file, where `delete_token` and `file` are from the `/image` endpoint's JSON
|
delete a file, where `delete_token` and `file` are from the `/image` endpoint's JSON
|
||||||
|
|
||||||
|
|
154
src/main.rs
154
src/main.rs
|
@ -136,30 +136,6 @@ fn to_ext(mime: mime::Mime) -> Result<&'static str, UploadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_name(name: &str) -> Result<mime::Mime, UploadError> {
|
|
||||||
match name
|
|
||||||
.rsplit('.')
|
|
||||||
.next()
|
|
||||||
.ok_or(UploadError::UnsupportedFormat)?
|
|
||||||
{
|
|
||||||
"jpg" => Ok(mime::IMAGE_JPEG),
|
|
||||||
"webp" => Ok(image_webp()),
|
|
||||||
"png" => Ok(mime::IMAGE_PNG),
|
|
||||||
"mp4" => Ok(video_mp4()),
|
|
||||||
"gif" => Ok(mime::IMAGE_GIF),
|
|
||||||
_ => Err(UploadError::UnsupportedFormat),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_ext(ext: &str) -> Result<mime::Mime, UploadError> {
|
|
||||||
match ext {
|
|
||||||
"jpg" => Ok(mime::IMAGE_JPEG),
|
|
||||||
"png" => Ok(mime::IMAGE_PNG),
|
|
||||||
"webp" => Ok(image_webp()),
|
|
||||||
_ => Err(UploadError::UnsupportedFormat),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle responding to succesful uploads
|
/// Handle responding to succesful uploads
|
||||||
#[instrument(skip(value, manager))]
|
#[instrument(skip(value, manager))]
|
||||||
async fn upload(
|
async fn upload(
|
||||||
|
@ -244,14 +220,12 @@ async fn delete(
|
||||||
|
|
||||||
type ProcessQuery = Vec<(String, String)>;
|
type ProcessQuery = Vec<(String, String)>;
|
||||||
|
|
||||||
/// Process files
|
async fn prepare_process(
|
||||||
#[instrument(skip(manager, whitelist))]
|
|
||||||
async fn process(
|
|
||||||
query: web::Query<ProcessQuery>,
|
query: web::Query<ProcessQuery>,
|
||||||
ext: web::Path<String>,
|
ext: &str,
|
||||||
manager: web::Data<UploadManager>,
|
manager: &UploadManager,
|
||||||
whitelist: web::Data<Option<HashSet<String>>>,
|
whitelist: &Option<HashSet<String>>,
|
||||||
) -> Result<HttpResponse, UploadError> {
|
) -> Result<(processor::ProcessChain, Format, String, PathBuf), UploadError> {
|
||||||
let (alias, operations) =
|
let (alias, operations) =
|
||||||
query
|
query
|
||||||
.into_inner()
|
.into_inner()
|
||||||
|
@ -282,8 +256,6 @@ async fn process(
|
||||||
|
|
||||||
let chain = self::processor::build_chain(&operations);
|
let chain = self::processor::build_chain(&operations);
|
||||||
|
|
||||||
let ext = ext.into_inner();
|
|
||||||
let content_type = from_ext(&ext)?;
|
|
||||||
let format = ext
|
let format = ext
|
||||||
.parse::<Format>()
|
.parse::<Format>()
|
||||||
.map_err(|_| UploadError::UnsupportedFormat)?;
|
.map_err(|_| UploadError::UnsupportedFormat)?;
|
||||||
|
@ -291,6 +263,36 @@ async fn process(
|
||||||
let base = manager.image_dir();
|
let base = manager.image_dir();
|
||||||
let thumbnail_path = self::processor::build_path(base, &chain, processed_name);
|
let thumbnail_path = self::processor::build_path(base, &chain, processed_name);
|
||||||
|
|
||||||
|
Ok((chain, format, name, thumbnail_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_details(
|
||||||
|
query: web::Query<ProcessQuery>,
|
||||||
|
ext: web::Path<String>,
|
||||||
|
manager: web::Data<UploadManager>,
|
||||||
|
whitelist: web::Data<Option<HashSet<String>>>,
|
||||||
|
) -> Result<HttpResponse, UploadError> {
|
||||||
|
let (_, _, name, thumbnail_path) =
|
||||||
|
prepare_process(query, ext.as_str(), &manager, &whitelist).await?;
|
||||||
|
|
||||||
|
let details = manager.variant_details(thumbnail_path, name).await?;
|
||||||
|
|
||||||
|
let details = details.ok_or(UploadError::NoFiles)?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(details))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process files
|
||||||
|
#[instrument(skip(manager, whitelist))]
|
||||||
|
async fn process(
|
||||||
|
query: web::Query<ProcessQuery>,
|
||||||
|
ext: web::Path<String>,
|
||||||
|
manager: web::Data<UploadManager>,
|
||||||
|
whitelist: web::Data<Option<HashSet<String>>>,
|
||||||
|
) -> Result<HttpResponse, UploadError> {
|
||||||
|
let (chain, format, name, thumbnail_path) =
|
||||||
|
prepare_process(query, ext.as_str(), &manager, &whitelist).await?;
|
||||||
|
|
||||||
// If the thumbnail doesn't exist, we need to create it
|
// If the thumbnail doesn't exist, we need to create it
|
||||||
let thumbnail_exists = if let Err(e) = actix_fs::metadata(thumbnail_path.clone()).await {
|
let thumbnail_exists = if let Err(e) = actix_fs::metadata(thumbnail_path.clone()).await {
|
||||||
if e.kind() != Some(std::io::ErrorKind::NotFound) {
|
if e.kind() != Some(std::io::ErrorKind::NotFound) {
|
||||||
|
@ -339,16 +341,27 @@ async fn process(
|
||||||
let path2 = thumbnail_path.clone();
|
let path2 = thumbnail_path.clone();
|
||||||
let img_bytes2 = img_bytes.clone();
|
let img_bytes2 = img_bytes.clone();
|
||||||
|
|
||||||
|
let store_details = details.is_none();
|
||||||
|
let details = if let Some(details) = details {
|
||||||
|
details
|
||||||
|
} else {
|
||||||
|
let details = Details::from_bytes(&img_bytes)?;
|
||||||
|
manager
|
||||||
|
.store_variant_details(path2.clone(), name.clone(), &details)
|
||||||
|
.await?;
|
||||||
|
details
|
||||||
|
};
|
||||||
|
|
||||||
// Save the file in another task, we want to return the thumbnail now
|
// Save the file in another task, we want to return the thumbnail now
|
||||||
debug!("Spawning storage task");
|
debug!("Spawning storage task");
|
||||||
let span = Span::current();
|
let span = Span::current();
|
||||||
let store_details = details.is_none();
|
let details2 = details.clone();
|
||||||
actix_rt::spawn(async move {
|
actix_rt::spawn(async move {
|
||||||
let entered = span.enter();
|
let entered = span.enter();
|
||||||
if store_details {
|
if store_details {
|
||||||
debug!("Storing details");
|
debug!("Storing details");
|
||||||
if let Err(e) = manager
|
if let Err(e) = manager
|
||||||
.store_variant_details(path2.clone(), name.clone())
|
.store_variant_details(path2.clone(), name.clone(), &details2)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
error!("Error storing details, {}", e);
|
error!("Error storing details, {}", e);
|
||||||
|
@ -366,30 +379,60 @@ async fn process(
|
||||||
drop(entered);
|
drop(entered);
|
||||||
});
|
});
|
||||||
|
|
||||||
let details = details.unwrap_or(Details::now());
|
|
||||||
|
|
||||||
return Ok(srv_response(
|
return Ok(srv_response(
|
||||||
Box::pin(futures::stream::once(async {
|
Box::pin(futures::stream::once(async {
|
||||||
Ok(img_bytes) as Result<_, UploadError>
|
Ok(img_bytes) as Result<_, UploadError>
|
||||||
})),
|
})),
|
||||||
content_type,
|
details.content_type(),
|
||||||
7 * DAYS,
|
7 * DAYS,
|
||||||
details.system_time(),
|
details.system_time(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let stream = actix_fs::read_to_stream(thumbnail_path).await?;
|
let details = if let Some(details) = details {
|
||||||
|
details
|
||||||
|
} else {
|
||||||
|
let details = Details::from_path(thumbnail_path.clone()).await?;
|
||||||
|
manager
|
||||||
|
.store_variant_details(thumbnail_path.clone(), name, &details)
|
||||||
|
.await?;
|
||||||
|
details
|
||||||
|
};
|
||||||
|
|
||||||
let details = details.unwrap_or(Details::now());
|
let stream = actix_fs::read_to_stream(thumbnail_path).await?;
|
||||||
|
|
||||||
Ok(srv_response(
|
Ok(srv_response(
|
||||||
stream,
|
stream,
|
||||||
content_type,
|
details.content_type(),
|
||||||
7 * DAYS,
|
7 * DAYS,
|
||||||
details.system_time(),
|
details.system_time(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch file details
|
||||||
|
async fn details(
|
||||||
|
alias: web::Path<String>,
|
||||||
|
manager: web::Data<UploadManager>,
|
||||||
|
) -> Result<HttpResponse, UploadError> {
|
||||||
|
let name = manager.from_alias(alias.into_inner()).await?;
|
||||||
|
let mut path = manager.image_dir();
|
||||||
|
path.push(name.clone());
|
||||||
|
|
||||||
|
let details = manager.variant_details(path.clone(), name.clone()).await?;
|
||||||
|
|
||||||
|
let details = if let Some(details) = details {
|
||||||
|
details
|
||||||
|
} else {
|
||||||
|
let new_details = Details::from_path(path.clone()).await?;
|
||||||
|
manager
|
||||||
|
.store_variant_details(path.clone(), name, &new_details)
|
||||||
|
.await?;
|
||||||
|
new_details
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(details))
|
||||||
|
}
|
||||||
|
|
||||||
/// Serve files
|
/// Serve files
|
||||||
#[instrument(skip(manager))]
|
#[instrument(skip(manager))]
|
||||||
async fn serve(
|
async fn serve(
|
||||||
|
@ -397,23 +440,26 @@ async fn serve(
|
||||||
manager: web::Data<UploadManager>,
|
manager: web::Data<UploadManager>,
|
||||||
) -> Result<HttpResponse, UploadError> {
|
) -> Result<HttpResponse, UploadError> {
|
||||||
let name = manager.from_alias(alias.into_inner()).await?;
|
let name = manager.from_alias(alias.into_inner()).await?;
|
||||||
let content_type = from_name(&name)?;
|
|
||||||
let mut path = manager.image_dir();
|
let mut path = manager.image_dir();
|
||||||
path.push(name.clone());
|
path.push(name.clone());
|
||||||
|
|
||||||
let details = manager.variant_details(path.clone(), name.clone()).await?;
|
let details = manager.variant_details(path.clone(), name.clone()).await?;
|
||||||
|
|
||||||
if details.is_none() {
|
let details = if let Some(details) = details {
|
||||||
manager.store_variant_details(path.clone(), name).await?;
|
details
|
||||||
}
|
} else {
|
||||||
|
let details = Details::from_path(path.clone()).await?;
|
||||||
let details = details.unwrap_or(Details::now());
|
manager
|
||||||
|
.store_variant_details(path.clone(), name, &details)
|
||||||
|
.await?;
|
||||||
|
details
|
||||||
|
};
|
||||||
|
|
||||||
let stream = actix_fs::read_to_stream(path).await?;
|
let stream = actix_fs::read_to_stream(path).await?;
|
||||||
|
|
||||||
Ok(srv_response(
|
Ok(srv_response(
|
||||||
stream,
|
stream,
|
||||||
content_type,
|
details.content_type(),
|
||||||
7 * DAYS,
|
7 * DAYS,
|
||||||
details.system_time(),
|
details.system_time(),
|
||||||
))
|
))
|
||||||
|
@ -604,7 +650,17 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
.route(web::get().to(delete)),
|
.route(web::get().to(delete)),
|
||||||
)
|
)
|
||||||
.service(web::resource("/original/{filename}").route(web::get().to(serve)))
|
.service(web::resource("/original/{filename}").route(web::get().to(serve)))
|
||||||
.service(web::resource("/process.{ext}").route(web::get().to(process))),
|
.service(web::resource("/process.{ext}").route(web::get().to(process)))
|
||||||
|
.service(
|
||||||
|
web::scope("/details")
|
||||||
|
.service(
|
||||||
|
web::resource("/original/{filename}").route(web::get().to(details)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/process.{ext}")
|
||||||
|
.route(web::get().to(process_details)),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::scope("/internal")
|
web::scope("/internal")
|
||||||
|
|
|
@ -46,18 +46,114 @@ impl std::fmt::Debug for UploadManager {
|
||||||
|
|
||||||
type UploadStream<E> = Pin<Box<dyn Stream<Item = Result<bytes::Bytes, E>>>>;
|
type UploadStream<E> = Pin<Box<dyn Stream<Item = Result<bytes::Bytes, E>>>>;
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Serde<T> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Serde<T> {
|
||||||
|
pub(crate) fn new(inner: T) -> Self {
|
||||||
|
Serde { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod my_serde {
|
||||||
|
impl<T> serde::Serialize for super::Serde<T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Display,
|
||||||
|
{
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let s = self.inner.to_string();
|
||||||
|
serde::Serialize::serialize(s.as_str(), serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T> serde::Deserialize<'de> for super::Serde<T>
|
||||||
|
where
|
||||||
|
T: std::str::FromStr,
|
||||||
|
<T as std::str::FromStr>::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s: String = serde::Deserialize::deserialize(deserializer)?;
|
||||||
|
let inner = s
|
||||||
|
.parse::<T>()
|
||||||
|
.map_err(|e| serde::de::Error::custom(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(super::Serde { inner })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||||
pub(crate) struct Details {
|
pub(crate) struct Details {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
content_type: Serde<mime::Mime>,
|
||||||
created_at: time::OffsetDateTime,
|
created_at: time::OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mime_from_media_type(media_type: rexiv2::MediaType) -> mime::Mime {
|
||||||
|
match media_type {
|
||||||
|
rexiv2::MediaType::Jpeg => mime::IMAGE_JPEG,
|
||||||
|
rexiv2::MediaType::Png => mime::IMAGE_PNG,
|
||||||
|
rexiv2::MediaType::Gif => mime::IMAGE_GIF,
|
||||||
|
rexiv2::MediaType::Other(s) if s == "image/webp" => s.parse::<mime::Mime>().unwrap(),
|
||||||
|
rexiv2::MediaType::Other(s) if s == "video/mp4" || s == "video/quicktime" => {
|
||||||
|
"video/mp4".parse::<mime::Mime>().unwrap()
|
||||||
|
}
|
||||||
|
_ => mime::APPLICATION_OCTET_STREAM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Details {
|
impl Details {
|
||||||
pub(crate) fn now() -> Self {
|
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, UploadError> {
|
||||||
|
let metadata = rexiv2::Metadata::new_from_buffer(bytes)?;
|
||||||
|
let mime_type = mime_from_media_type(metadata.get_media_type()?);
|
||||||
|
let width = metadata.get_pixel_width();
|
||||||
|
let height = metadata.get_pixel_height();
|
||||||
|
let details = Details::now(width as usize, height as usize, mime_type);
|
||||||
|
Ok(details)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn from_path(path: PathBuf) -> Result<Self, UploadError> {
|
||||||
|
let (mime_type, width, height) = web::block(move || {
|
||||||
|
rexiv2::Metadata::new_from_path(&path).and_then(|metadata| {
|
||||||
|
metadata
|
||||||
|
.get_media_type()
|
||||||
|
.map(mime_from_media_type)
|
||||||
|
.map(|mime_type| {
|
||||||
|
(
|
||||||
|
mime_type,
|
||||||
|
metadata.get_pixel_width(),
|
||||||
|
metadata.get_pixel_height(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Details::now(width as usize, height as usize, mime_type))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn now(width: usize, height: usize, content_type: mime::Mime) -> Self {
|
||||||
Details {
|
Details {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
content_type: Serde::new(content_type),
|
||||||
created_at: time::OffsetDateTime::now_utc(),
|
created_at: time::OffsetDateTime::now_utc(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn content_type(&self) -> mime::Mime {
|
||||||
|
self.content_type.inner.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn system_time(&self) -> std::time::SystemTime {
|
pub(crate) fn system_time(&self) -> std::time::SystemTime {
|
||||||
self.created_at.into()
|
self.created_at.into()
|
||||||
}
|
}
|
||||||
|
@ -184,7 +280,10 @@ impl UploadManager {
|
||||||
let main_tree = self.inner.main_tree.clone();
|
let main_tree = self.inner.main_tree.clone();
|
||||||
debug!("Getting details");
|
debug!("Getting details");
|
||||||
let opt = match web::block(move || main_tree.get(key)).await? {
|
let opt = match web::block(move || main_tree.get(key)).await? {
|
||||||
Some(ivec) => Some(serde_json::from_slice(&ivec)?),
|
Some(ivec) => match serde_json::from_slice(&ivec) {
|
||||||
|
Ok(details) => Some(details),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
debug!("Got details");
|
debug!("Got details");
|
||||||
|
@ -196,6 +295,7 @@ impl UploadManager {
|
||||||
&self,
|
&self,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
filename: String,
|
filename: String,
|
||||||
|
details: &Details,
|
||||||
) -> Result<(), UploadError> {
|
) -> Result<(), UploadError> {
|
||||||
let path_string = path.to_str().ok_or(UploadError::Path)?.to_string();
|
let path_string = path.to_str().ok_or(UploadError::Path)?.to_string();
|
||||||
|
|
||||||
|
@ -207,7 +307,7 @@ impl UploadManager {
|
||||||
|
|
||||||
let key = variant_details_key(&hash, &path_string);
|
let key = variant_details_key(&hash, &path_string);
|
||||||
let main_tree = self.inner.main_tree.clone();
|
let main_tree = self.inner.main_tree.clone();
|
||||||
let details_value = serde_json::to_string(&Details::now())?;
|
let details_value = serde_json::to_string(details)?;
|
||||||
debug!("Storing details");
|
debug!("Storing details");
|
||||||
web::block(move || main_tree.insert(key, details_value.as_bytes())).await?;
|
web::block(move || main_tree.insert(key, details_value.as_bytes())).await?;
|
||||||
debug!("Stored details");
|
debug!("Stored details");
|
||||||
|
|
Loading…
Reference in a new issue