2020-07-11 20:40:40 +00:00
|
|
|
use crate::{
|
|
|
|
config::Format,
|
|
|
|
error::UploadError,
|
|
|
|
migrate::{alias_key_bounds, variant_key_bounds, LatestDb},
|
|
|
|
to_ext,
|
|
|
|
validate::validate_image,
|
|
|
|
};
|
2020-06-06 21:41:17 +00:00
|
|
|
use actix_web::web;
|
2020-06-15 02:41:45 +00:00
|
|
|
use futures::stream::{Stream, StreamExt, TryStreamExt};
|
2020-06-06 21:41:17 +00:00
|
|
|
use sha2::Digest;
|
|
|
|
use std::{path::PathBuf, pin::Pin, sync::Arc};
|
2020-06-14 15:07:31 +00:00
|
|
|
use tracing::{debug, error, info, instrument, warn, Span};
|
2020-06-06 21:41:17 +00:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct UploadManager {
|
|
|
|
inner: Arc<UploadManagerInner>,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct UploadManagerInner {
|
2020-06-07 01:44:26 +00:00
|
|
|
format: Option<Format>,
|
2020-06-06 21:41:17 +00:00
|
|
|
hasher: sha2::Sha256,
|
|
|
|
image_dir: PathBuf,
|
2020-06-06 22:43:33 +00:00
|
|
|
alias_tree: sled::Tree,
|
2020-06-09 23:39:49 +00:00
|
|
|
filename_tree: sled::Tree,
|
2020-06-07 00:29:15 +00:00
|
|
|
db: sled::Db,
|
2020-06-06 21:41:17 +00:00
|
|
|
}
|
|
|
|
|
2020-06-14 02:28:06 +00:00
|
|
|
impl std::fmt::Debug for UploadManager {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
2020-06-14 15:07:31 +00:00
|
|
|
f.debug_struct("UploadManager").finish()
|
2020-06-14 02:28:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
type UploadStream<E> = Pin<Box<dyn Stream<Item = Result<bytes::Bytes, E>>>>;
|
2020-06-14 02:28:06 +00:00
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
struct FilenameIVec {
|
|
|
|
inner: sled::IVec,
|
2020-06-14 02:28:06 +00:00
|
|
|
}
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
impl FilenameIVec {
|
|
|
|
fn new(inner: sled::IVec) -> Self {
|
|
|
|
FilenameIVec { inner }
|
2020-06-14 02:28:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
impl std::fmt::Debug for FilenameIVec {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
write!(f, "{:?}", String::from_utf8(self.inner.to_vec()))
|
2020-06-14 02:28:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
struct Hash {
|
|
|
|
inner: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Hash {
|
|
|
|
fn new(inner: Vec<u8>) -> Self {
|
|
|
|
Hash { inner }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::fmt::Debug for Hash {
|
2020-06-14 02:28:06 +00:00
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
2020-06-14 15:07:31 +00:00
|
|
|
write!(f, "{}", base64::encode(&self.inner))
|
2020-06-14 02:28:06 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-06 21:41:17 +00:00
|
|
|
|
|
|
|
enum Dup {
|
|
|
|
Exists,
|
|
|
|
New,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Dup {
|
|
|
|
fn exists(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Dup::Exists => true,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl UploadManager {
|
|
|
|
/// Get the image directory
|
|
|
|
pub(crate) fn image_dir(&self) -> PathBuf {
|
|
|
|
self.inner.image_dir.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new UploadManager
|
2020-06-07 01:44:26 +00:00
|
|
|
pub(crate) async fn new(
|
|
|
|
mut root_dir: PathBuf,
|
|
|
|
format: Option<Format>,
|
|
|
|
) -> Result<Self, UploadError> {
|
2020-07-11 20:40:40 +00:00
|
|
|
let root_clone = root_dir.clone();
|
2020-06-06 21:41:17 +00:00
|
|
|
// sled automatically creates it's own directories
|
2020-07-11 20:40:40 +00:00
|
|
|
let db = web::block(move || LatestDb::exists(root_clone).migrate()).await?;
|
2020-06-06 21:41:17 +00:00
|
|
|
|
|
|
|
root_dir.push("files");
|
|
|
|
|
|
|
|
// Ensure file dir exists
|
|
|
|
actix_fs::create_dir_all(root_dir.clone()).await?;
|
|
|
|
|
|
|
|
Ok(UploadManager {
|
|
|
|
inner: Arc::new(UploadManagerInner {
|
2020-06-07 01:44:26 +00:00
|
|
|
format,
|
2020-06-06 21:41:17 +00:00
|
|
|
hasher: sha2::Sha256::new(),
|
|
|
|
image_dir: root_dir,
|
2020-06-06 22:43:33 +00:00
|
|
|
alias_tree: db.open_tree("alias")?,
|
2020-06-09 23:39:49 +00:00
|
|
|
filename_tree: db.open_tree("filename")?,
|
2020-06-06 21:41:17 +00:00
|
|
|
db,
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
/// Store the path to a generated image variant so we can easily clean it up later
|
2020-06-14 15:07:31 +00:00
|
|
|
#[instrument(skip(self))]
|
2020-06-24 16:58:46 +00:00
|
|
|
pub(crate) async fn store_variant(
|
|
|
|
&self,
|
|
|
|
path: PathBuf,
|
|
|
|
filename: String,
|
|
|
|
) -> Result<(), UploadError> {
|
2020-06-09 23:39:49 +00:00
|
|
|
let path_string = path.to_str().ok_or(UploadError::Path)?.to_string();
|
|
|
|
|
|
|
|
let fname_tree = self.inner.filename_tree.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Getting hash");
|
2020-06-09 23:39:49 +00:00
|
|
|
let hash: sled::IVec = web::block(move || fname_tree.get(filename.as_bytes()))
|
|
|
|
.await?
|
|
|
|
.ok_or(UploadError::MissingFilename)?;
|
|
|
|
|
|
|
|
let key = variant_key(&hash, &path_string);
|
|
|
|
let db = self.inner.db.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Storing variant");
|
2020-06-09 23:39:49 +00:00
|
|
|
web::block(move || db.insert(key, path_string.as_bytes())).await?;
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Stored variant");
|
2020-06-09 23:39:49 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-07-11 22:22:45 +00:00
|
|
|
/// Get a list of aliases for a given file
|
|
|
|
pub(crate) async fn aliases_by_filename(
|
|
|
|
&self,
|
|
|
|
filename: String,
|
|
|
|
) -> Result<Vec<String>, UploadError> {
|
|
|
|
let fname_tree = self.inner.filename_tree.clone();
|
|
|
|
let hash = web::block(move || fname_tree.get(filename.as_bytes()))
|
|
|
|
.await?
|
|
|
|
.ok_or(UploadError::MissingAlias)?;
|
|
|
|
|
|
|
|
self.aliases_by_hash(&hash).await
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a list of aliases for a given alias
|
|
|
|
pub(crate) async fn aliases_by_alias(&self, alias: String) -> Result<Vec<String>, UploadError> {
|
|
|
|
let alias_tree = self.inner.alias_tree.clone();
|
|
|
|
let hash = web::block(move || alias_tree.get(alias.as_bytes()))
|
|
|
|
.await?
|
|
|
|
.ok_or(UploadError::MissingFilename)?;
|
|
|
|
|
|
|
|
self.aliases_by_hash(&hash).await
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn aliases_by_hash(&self, hash: &sled::IVec) -> Result<Vec<String>, UploadError> {
|
|
|
|
let (start, end) = alias_key_bounds(hash);
|
|
|
|
let db = self.inner.db.clone();
|
|
|
|
let aliases =
|
|
|
|
web::block(move || db.range(start..end).values().collect::<Result<Vec<_>, _>>())
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
debug!("Got {} aliases for hash", aliases.len());
|
|
|
|
let aliases = aliases
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|s| String::from_utf8(s.to_vec()).ok())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
for alias in aliases.iter() {
|
|
|
|
debug!("{}", alias);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(aliases)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Delete an alias without a delete token
|
|
|
|
pub(crate) async fn delete_without_token(&self, alias: String) -> Result<(), UploadError> {
|
|
|
|
let token_key = delete_key(&alias);
|
|
|
|
let alias_tree = self.inner.alias_tree.clone();
|
|
|
|
let token = web::block(move || alias_tree.get(token_key.as_bytes()))
|
|
|
|
.await?
|
|
|
|
.ok_or(UploadError::MissingAlias)?;
|
|
|
|
|
|
|
|
self.delete(alias, String::from_utf8(token.to_vec())?).await
|
|
|
|
}
|
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
/// Delete the alias, and the file & variants if no more aliases exist
|
2020-06-14 18:56:42 +00:00
|
|
|
#[instrument(skip(self, alias, token))]
|
2020-06-07 00:29:15 +00:00
|
|
|
pub(crate) async fn delete(&self, alias: String, token: String) -> Result<(), UploadError> {
|
|
|
|
use sled::Transactional;
|
|
|
|
let db = self.inner.db.clone();
|
|
|
|
let alias_tree = self.inner.alias_tree.clone();
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
let span = Span::current();
|
2020-06-07 00:29:15 +00:00
|
|
|
let alias2 = alias.clone();
|
|
|
|
let hash = web::block(move || {
|
|
|
|
[&*db, &alias_tree].transaction(|v| {
|
2020-06-14 15:07:31 +00:00
|
|
|
let entered = span.enter();
|
2020-06-07 00:29:15 +00:00
|
|
|
let db = &v[0];
|
|
|
|
let alias_tree = &v[1];
|
|
|
|
|
|
|
|
// -- GET TOKEN --
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Deleting alias -> delete-token mapping");
|
2020-06-07 00:29:15 +00:00
|
|
|
let existing_token = alias_tree
|
|
|
|
.remove(delete_key(&alias2).as_bytes())?
|
|
|
|
.ok_or(trans_err(UploadError::MissingAlias))?;
|
|
|
|
|
|
|
|
// Bail if invalid token
|
|
|
|
if existing_token != token {
|
|
|
|
warn!("Invalid delete token");
|
|
|
|
return Err(trans_err(UploadError::InvalidToken));
|
|
|
|
}
|
|
|
|
|
|
|
|
// -- GET ID FOR HASH TREE CLEANUP --
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Deleting alias -> id mapping");
|
2020-06-07 00:29:15 +00:00
|
|
|
let id = alias_tree
|
|
|
|
.remove(alias_id_key(&alias2).as_bytes())?
|
|
|
|
.ok_or(trans_err(UploadError::MissingAlias))?;
|
|
|
|
let id = String::from_utf8(id.to_vec()).map_err(|e| trans_err(e.into()))?;
|
|
|
|
|
|
|
|
// -- GET HASH FOR HASH TREE CLEANUP --
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Deleting alias -> hash mapping");
|
2020-06-07 00:29:15 +00:00
|
|
|
let hash = alias_tree
|
|
|
|
.remove(alias2.as_bytes())?
|
|
|
|
.ok_or(trans_err(UploadError::MissingAlias))?;
|
|
|
|
|
|
|
|
// -- REMOVE HASH TREE ELEMENT --
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Deleting hash -> alias mapping");
|
2020-06-07 00:29:15 +00:00
|
|
|
db.remove(alias_key(&hash, &id))?;
|
2020-06-14 15:07:31 +00:00
|
|
|
drop(entered);
|
2020-06-07 00:29:15 +00:00
|
|
|
Ok(hash)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
|
2020-06-07 03:21:42 +00:00
|
|
|
// -- CHECK IF ANY OTHER ALIASES EXIST --
|
2020-06-07 00:29:15 +00:00
|
|
|
let db = self.inner.db.clone();
|
|
|
|
let (start, end) = alias_key_bounds(&hash);
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Checking for additional aliases referencing hash");
|
2020-06-07 00:29:15 +00:00
|
|
|
let any_aliases = web::block(move || {
|
|
|
|
Ok(db.range(start..end).next().is_some()) as Result<bool, UploadError>
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
// Bail if there are existing aliases
|
|
|
|
if any_aliases {
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Other aliases reference file, not removing from disk");
|
2020-06-07 00:29:15 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// -- DELETE HASH ENTRY --
|
|
|
|
let db = self.inner.db.clone();
|
2020-06-09 23:39:49 +00:00
|
|
|
let hash2 = hash.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Deleting hash -> filename mapping");
|
2020-06-09 23:39:49 +00:00
|
|
|
let filename = web::block(move || db.remove(&hash2))
|
|
|
|
.await?
|
|
|
|
.ok_or(UploadError::MissingFile)?;
|
2020-06-07 00:29:15 +00:00
|
|
|
|
2020-06-07 00:33:29 +00:00
|
|
|
// -- DELETE FILES --
|
2020-06-09 23:39:49 +00:00
|
|
|
let this = self.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Spawning cleanup task");
|
|
|
|
let span = Span::current();
|
2020-06-07 00:33:29 +00:00
|
|
|
actix_rt::spawn(async move {
|
2020-06-14 15:07:31 +00:00
|
|
|
let entered = span.enter();
|
|
|
|
if let Err(e) = this
|
|
|
|
.cleanup_files(FilenameIVec::new(filename.clone()))
|
|
|
|
.await
|
|
|
|
{
|
2020-06-07 00:33:29 +00:00
|
|
|
error!("Error removing files from fs, {}", e);
|
|
|
|
}
|
2020-06-14 15:07:31 +00:00
|
|
|
info!(
|
|
|
|
"Files deleted for {:?}",
|
|
|
|
String::from_utf8(filename.to_vec())
|
|
|
|
);
|
|
|
|
drop(entered);
|
2020-06-07 00:33:29 +00:00
|
|
|
});
|
2020-06-07 00:29:15 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate a delete token for an alias
|
2020-06-14 15:07:31 +00:00
|
|
|
#[instrument(skip(self))]
|
2020-06-07 00:29:15 +00:00
|
|
|
pub(crate) async fn delete_token(&self, alias: String) -> Result<String, UploadError> {
|
2020-06-14 02:28:06 +00:00
|
|
|
debug!("Generating delete token");
|
2020-06-07 00:29:15 +00:00
|
|
|
use rand::distributions::{Alphanumeric, Distribution};
|
|
|
|
let rng = rand::thread_rng();
|
|
|
|
let s: String = Alphanumeric.sample_iter(rng).take(10).collect();
|
|
|
|
let delete_token = s.clone();
|
|
|
|
|
2020-06-14 02:28:06 +00:00
|
|
|
debug!("Saving delete token");
|
2020-06-07 00:29:15 +00:00
|
|
|
let alias_tree = self.inner.alias_tree.clone();
|
|
|
|
let key = delete_key(&alias);
|
|
|
|
let res = web::block(move || {
|
|
|
|
alias_tree.compare_and_swap(
|
|
|
|
key.as_bytes(),
|
|
|
|
None as Option<sled::IVec>,
|
|
|
|
Some(s.as_bytes()),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
if let Err(sled::CompareAndSwapError {
|
|
|
|
current: Some(ivec),
|
|
|
|
..
|
|
|
|
}) = res
|
|
|
|
{
|
|
|
|
let s = String::from_utf8(ivec.to_vec())?;
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Returning existing delete token, {}", s);
|
2020-06-07 00:29:15 +00:00
|
|
|
return Ok(s);
|
|
|
|
}
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Returning new delete token, {}", delete_token);
|
2020-06-07 00:29:15 +00:00
|
|
|
Ok(delete_token)
|
|
|
|
}
|
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
/// Upload the file while preserving the filename, optionally validating the uploaded image
|
2020-06-14 15:07:31 +00:00
|
|
|
#[instrument(skip(self, stream))]
|
2020-06-11 00:26:54 +00:00
|
|
|
pub(crate) async fn import<E>(
|
|
|
|
&self,
|
|
|
|
alias: String,
|
|
|
|
content_type: mime::Mime,
|
|
|
|
validate: bool,
|
|
|
|
stream: UploadStream<E>,
|
|
|
|
) -> Result<String, UploadError>
|
2020-06-07 15:59:58 +00:00
|
|
|
where
|
|
|
|
UploadError: From<E>,
|
2020-06-15 02:41:45 +00:00
|
|
|
E: Unpin,
|
2020-06-07 15:59:58 +00:00
|
|
|
{
|
2020-06-15 02:41:45 +00:00
|
|
|
// -- READ IN BYTES FROM CLIENT --
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Reading stream");
|
2020-06-24 16:58:46 +00:00
|
|
|
let tmpfile = crate::tmp_file();
|
2020-06-15 02:41:45 +00:00
|
|
|
safe_save_stream(tmpfile.clone(), stream).await?;
|
2020-06-06 21:41:17 +00:00
|
|
|
|
2020-06-15 02:41:45 +00:00
|
|
|
let content_type = if validate {
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Validating image");
|
2020-06-11 16:46:00 +00:00
|
|
|
let format = self.inner.format.clone();
|
2020-06-15 02:41:45 +00:00
|
|
|
validate_image(tmpfile.clone(), format).await?
|
2020-06-11 00:26:54 +00:00
|
|
|
} else {
|
2020-06-15 02:41:45 +00:00
|
|
|
content_type
|
2020-06-11 00:26:54 +00:00
|
|
|
};
|
2020-06-06 21:41:17 +00:00
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
// -- DUPLICATE CHECKS --
|
2020-06-07 01:44:26 +00:00
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
// Cloning bytes is fine because it's actually a pointer
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Hashing bytes");
|
2020-06-15 02:41:45 +00:00
|
|
|
let hash = self.hash(tmpfile.clone()).await?;
|
2020-06-07 01:44:26 +00:00
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Storing alias");
|
2020-06-11 00:26:54 +00:00
|
|
|
self.add_existing_alias(&hash, &alias).await?;
|
2020-06-07 01:44:26 +00:00
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Saving file");
|
2020-06-15 02:41:45 +00:00
|
|
|
self.save_upload(tmpfile, hash, content_type).await?;
|
2020-06-07 15:59:58 +00:00
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
// Return alias to file
|
|
|
|
Ok(alias)
|
|
|
|
}
|
2020-06-07 01:44:26 +00:00
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
/// Upload the file, discarding bytes if it's already present, or saving if it's new
|
2020-06-14 15:07:31 +00:00
|
|
|
#[instrument(skip(self, stream))]
|
2020-06-11 00:26:54 +00:00
|
|
|
pub(crate) async fn upload<E>(&self, stream: UploadStream<E>) -> Result<String, UploadError>
|
|
|
|
where
|
|
|
|
UploadError: From<E>,
|
2020-06-15 02:41:45 +00:00
|
|
|
E: Unpin,
|
2020-06-11 00:26:54 +00:00
|
|
|
{
|
|
|
|
// -- READ IN BYTES FROM CLIENT --
|
2020-06-14 02:28:06 +00:00
|
|
|
debug!("Reading stream");
|
2020-06-24 16:58:46 +00:00
|
|
|
let tmpfile = crate::tmp_file();
|
2020-06-15 02:41:45 +00:00
|
|
|
safe_save_stream(tmpfile.clone(), stream).await?;
|
2020-06-11 00:26:54 +00:00
|
|
|
|
|
|
|
// -- VALIDATE IMAGE --
|
2020-06-14 02:28:06 +00:00
|
|
|
debug!("Validating image");
|
2020-06-11 16:46:00 +00:00
|
|
|
let format = self.inner.format.clone();
|
2020-06-15 02:41:45 +00:00
|
|
|
let content_type = validate_image(tmpfile.clone(), format).await?;
|
2020-06-06 21:41:17 +00:00
|
|
|
|
|
|
|
// -- DUPLICATE CHECKS --
|
|
|
|
|
|
|
|
// Cloning bytes is fine because it's actually a pointer
|
2020-06-14 02:28:06 +00:00
|
|
|
debug!("Hashing bytes");
|
2020-06-15 02:41:45 +00:00
|
|
|
let hash = self.hash(tmpfile.clone()).await?;
|
2020-06-06 21:41:17 +00:00
|
|
|
|
2020-06-14 02:28:06 +00:00
|
|
|
debug!("Adding alias");
|
2020-06-06 22:43:33 +00:00
|
|
|
let alias = self.add_alias(&hash, content_type.clone()).await?;
|
2020-06-06 21:41:17 +00:00
|
|
|
|
2020-06-14 02:28:06 +00:00
|
|
|
debug!("Saving file");
|
2020-06-15 02:41:45 +00:00
|
|
|
self.save_upload(tmpfile, hash, content_type).await?;
|
2020-06-06 22:43:33 +00:00
|
|
|
|
|
|
|
// Return alias to file
|
2020-06-07 15:59:58 +00:00
|
|
|
Ok(alias)
|
2020-06-06 21:41:17 +00:00
|
|
|
}
|
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
/// Fetch the real on-disk filename given an alias
|
2020-06-14 15:07:31 +00:00
|
|
|
#[instrument(skip(self))]
|
2020-06-06 22:43:33 +00:00
|
|
|
pub(crate) async fn from_alias(&self, alias: String) -> Result<String, UploadError> {
|
|
|
|
let tree = self.inner.alias_tree.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Getting hash from alias");
|
2020-06-06 22:43:33 +00:00
|
|
|
let hash = web::block(move || tree.get(alias.as_bytes()))
|
|
|
|
.await?
|
|
|
|
.ok_or(UploadError::MissingAlias)?;
|
|
|
|
|
|
|
|
let db = self.inner.db.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Getting filename from hash");
|
2020-06-06 22:43:33 +00:00
|
|
|
let filename = web::block(move || db.get(hash))
|
|
|
|
.await?
|
|
|
|
.ok_or(UploadError::MissingFile)?;
|
|
|
|
|
|
|
|
let filename = String::from_utf8(filename.to_vec())?;
|
|
|
|
|
|
|
|
Ok(filename)
|
|
|
|
}
|
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
// Find image variants and remove them from the DB and the disk
|
2020-06-14 15:07:31 +00:00
|
|
|
#[instrument(skip(self))]
|
|
|
|
async fn cleanup_files(&self, filename: FilenameIVec) -> Result<(), UploadError> {
|
|
|
|
let filename = filename.inner;
|
2020-06-11 00:26:54 +00:00
|
|
|
let mut path = self.image_dir();
|
|
|
|
let fname = String::from_utf8(filename.to_vec())?;
|
|
|
|
path.push(fname);
|
|
|
|
|
|
|
|
let mut errors = Vec::new();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Deleting {:?}", path);
|
2020-06-11 00:26:54 +00:00
|
|
|
if let Err(e) = actix_fs::remove_file(path).await {
|
|
|
|
errors.push(e.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let fname_tree = self.inner.filename_tree.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Deleting filename -> hash mapping");
|
2020-06-11 00:26:54 +00:00
|
|
|
let hash = web::block(move || fname_tree.remove(filename))
|
|
|
|
.await?
|
|
|
|
.ok_or(UploadError::MissingFile)?;
|
|
|
|
|
|
|
|
let (start, end) = variant_key_bounds(&hash);
|
|
|
|
let db = self.inner.db.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Fetching file variants");
|
2020-06-11 00:26:54 +00:00
|
|
|
let keys = web::block(move || {
|
|
|
|
let mut keys = Vec::new();
|
|
|
|
for key in db.range(start..end).keys() {
|
|
|
|
keys.push(key?.to_owned());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(keys) as Result<Vec<sled::IVec>, UploadError>
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("{} files prepared for deletion", keys.len());
|
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
for key in keys {
|
|
|
|
let db = self.inner.db.clone();
|
|
|
|
if let Some(path) = web::block(move || db.remove(key)).await? {
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Deleting {:?}", String::from_utf8(path.to_vec()));
|
2020-06-11 00:26:54 +00:00
|
|
|
if let Err(e) = remove_path(path).await {
|
|
|
|
errors.push(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for error in errors {
|
|
|
|
error!("Error deleting files, {}", error);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// check duplicates & store image if new
|
|
|
|
async fn save_upload(
|
|
|
|
&self,
|
2020-06-15 02:41:45 +00:00
|
|
|
tmpfile: PathBuf,
|
2020-06-14 15:07:31 +00:00
|
|
|
hash: Hash,
|
2020-06-11 00:26:54 +00:00
|
|
|
content_type: mime::Mime,
|
|
|
|
) -> Result<(), UploadError> {
|
|
|
|
let (dup, name) = self.check_duplicate(hash, content_type).await?;
|
|
|
|
|
|
|
|
// bail early with alias to existing file if this is a duplicate
|
|
|
|
if dup.exists() {
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Duplicate exists, not saving file");
|
2020-06-11 00:26:54 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// -- WRITE NEW FILE --
|
|
|
|
let mut real_path = self.image_dir();
|
|
|
|
real_path.push(name);
|
|
|
|
|
2020-06-24 16:58:46 +00:00
|
|
|
crate::safe_move_file(tmpfile, real_path).await?;
|
2020-06-11 00:26:54 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-06 21:41:17 +00:00
|
|
|
// produce a sh256sum of the uploaded file
|
2020-06-15 02:41:45 +00:00
|
|
|
async fn hash(&self, tmpfile: PathBuf) -> Result<Hash, UploadError> {
|
2020-06-06 21:41:17 +00:00
|
|
|
let mut hasher = self.inner.hasher.clone();
|
2020-06-15 02:41:45 +00:00
|
|
|
|
|
|
|
let mut stream = actix_fs::read_to_stream(tmpfile).await?;
|
|
|
|
|
|
|
|
while let Some(res) = stream.next().await {
|
|
|
|
let bytes = res?;
|
|
|
|
hasher = web::block(move || {
|
|
|
|
hasher.update(&bytes);
|
|
|
|
Ok(hasher) as Result<_, UploadError>
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let hash =
|
|
|
|
web::block(move || Ok(hasher.finalize_reset().to_vec()) as Result<_, UploadError>)
|
|
|
|
.await?;
|
2020-06-06 21:41:17 +00:00
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
Ok(Hash::new(hash))
|
2020-06-06 21:41:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// check for an already-uploaded image with this hash, returning the path to the target file
|
2020-06-14 18:56:42 +00:00
|
|
|
#[instrument(skip(self, hash, content_type))]
|
2020-06-06 21:41:17 +00:00
|
|
|
async fn check_duplicate(
|
|
|
|
&self,
|
2020-06-14 15:07:31 +00:00
|
|
|
hash: Hash,
|
2020-06-06 21:41:17 +00:00
|
|
|
content_type: mime::Mime,
|
2020-06-06 22:43:33 +00:00
|
|
|
) -> Result<(Dup, String), UploadError> {
|
2020-06-06 21:41:17 +00:00
|
|
|
let db = self.inner.db.clone();
|
|
|
|
|
|
|
|
let filename = self.next_file(content_type).await?;
|
|
|
|
let filename2 = filename.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
let hash2 = hash.inner.clone();
|
|
|
|
debug!("Inserting filename for hash");
|
2020-06-06 21:41:17 +00:00
|
|
|
let res = web::block(move || {
|
2020-06-09 23:39:49 +00:00
|
|
|
db.compare_and_swap(
|
|
|
|
hash2,
|
|
|
|
None as Option<sled::IVec>,
|
|
|
|
Some(filename2.as_bytes()),
|
|
|
|
)
|
2020-06-06 21:41:17 +00:00
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
if let Err(sled::CompareAndSwapError {
|
|
|
|
current: Some(ivec),
|
|
|
|
..
|
|
|
|
}) = res
|
|
|
|
{
|
|
|
|
let name = String::from_utf8(ivec.to_vec())?;
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Filename exists for hash, {}", name);
|
2020-06-06 22:43:33 +00:00
|
|
|
return Ok((Dup::Exists, name));
|
2020-06-06 21:41:17 +00:00
|
|
|
}
|
|
|
|
|
2020-06-09 23:39:49 +00:00
|
|
|
let fname_tree = self.inner.filename_tree.clone();
|
|
|
|
let filename2 = filename.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Saving filename -> hash relation");
|
|
|
|
web::block(move || fname_tree.insert(filename2, hash.inner)).await?;
|
2020-06-09 23:39:49 +00:00
|
|
|
|
2020-06-06 22:43:33 +00:00
|
|
|
Ok((Dup::New, filename))
|
2020-06-06 21:41:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// generate a short filename that isn't already in-use
|
2020-06-14 18:56:42 +00:00
|
|
|
#[instrument(skip(self, content_type))]
|
2020-06-06 21:41:17 +00:00
|
|
|
async fn next_file(&self, content_type: mime::Mime) -> Result<String, UploadError> {
|
2020-06-06 22:43:33 +00:00
|
|
|
let image_dir = self.image_dir();
|
2020-06-06 21:41:17 +00:00
|
|
|
use rand::distributions::{Alphanumeric, Distribution};
|
|
|
|
let mut limit: usize = 10;
|
|
|
|
let rng = rand::thread_rng();
|
|
|
|
loop {
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Filename generation loop");
|
2020-06-06 21:41:17 +00:00
|
|
|
let mut path = image_dir.clone();
|
|
|
|
let s: String = Alphanumeric.sample_iter(rng).take(limit).collect();
|
|
|
|
|
2020-06-24 16:58:46 +00:00
|
|
|
let filename = file_name(s, content_type.clone())?;
|
2020-06-06 21:41:17 +00:00
|
|
|
|
|
|
|
path.push(filename.clone());
|
|
|
|
|
|
|
|
if let Err(e) = actix_fs::metadata(path).await {
|
|
|
|
if e.kind() == Some(std::io::ErrorKind::NotFound) {
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Generated unused filename {}", filename);
|
2020-06-06 21:41:17 +00:00
|
|
|
return Ok(filename);
|
|
|
|
}
|
|
|
|
return Err(e.into());
|
|
|
|
}
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Filename exists, trying again");
|
|
|
|
|
2020-06-06 21:41:17 +00:00
|
|
|
limit += 1;
|
|
|
|
}
|
|
|
|
}
|
2020-06-06 22:43:33 +00:00
|
|
|
|
2020-06-14 18:56:42 +00:00
|
|
|
#[instrument(skip(self, hash, alias))]
|
2020-06-14 15:07:31 +00:00
|
|
|
async fn add_existing_alias(&self, hash: &Hash, alias: &str) -> Result<(), UploadError> {
|
2020-06-11 00:26:54 +00:00
|
|
|
self.save_alias(hash, alias).await??;
|
|
|
|
|
|
|
|
self.store_alias(hash, alias).await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-06 22:43:33 +00:00
|
|
|
// Add an alias to an existing file
|
|
|
|
//
|
|
|
|
// This will help if multiple 'users' upload the same file, and one of them wants to delete it
|
2020-06-14 18:56:42 +00:00
|
|
|
#[instrument(skip(self, hash, content_type))]
|
2020-06-06 22:43:33 +00:00
|
|
|
async fn add_alias(
|
|
|
|
&self,
|
2020-06-14 15:07:31 +00:00
|
|
|
hash: &Hash,
|
2020-06-06 22:43:33 +00:00
|
|
|
content_type: mime::Mime,
|
|
|
|
) -> Result<String, UploadError> {
|
|
|
|
let alias = self.next_alias(hash, content_type).await?;
|
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
self.store_alias(hash, &alias).await?;
|
|
|
|
|
|
|
|
Ok(alias)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a pre-defined alias to an existin file
|
|
|
|
//
|
|
|
|
// DANGER: this can cause BAD BAD BAD conflicts if the same alias is used for multiple files
|
2020-06-14 18:56:42 +00:00
|
|
|
#[instrument(skip(self, hash))]
|
2020-06-14 15:07:31 +00:00
|
|
|
async fn store_alias(&self, hash: &Hash, alias: &str) -> Result<(), UploadError> {
|
2020-06-11 00:26:54 +00:00
|
|
|
let alias = alias.to_string();
|
2020-06-06 22:43:33 +00:00
|
|
|
loop {
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("hash -> alias save loop");
|
2020-06-06 22:43:33 +00:00
|
|
|
let db = self.inner.db.clone();
|
|
|
|
let id = web::block(move || db.generate_id()).await?.to_string();
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
let key = alias_key(&hash.inner, &id);
|
2020-06-06 22:43:33 +00:00
|
|
|
let db = self.inner.db.clone();
|
|
|
|
let alias2 = alias.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Saving hash/id -> alias mapping");
|
2020-06-06 22:43:33 +00:00
|
|
|
let res = web::block(move || {
|
|
|
|
db.compare_and_swap(key, None as Option<sled::IVec>, Some(alias2.as_bytes()))
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
if res.is_ok() {
|
2020-06-07 00:29:15 +00:00
|
|
|
let alias_tree = self.inner.alias_tree.clone();
|
|
|
|
let key = alias_id_key(&alias);
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Saving alias -> id mapping");
|
2020-06-07 00:29:15 +00:00
|
|
|
web::block(move || alias_tree.insert(key.as_bytes(), id.as_bytes())).await?;
|
|
|
|
|
2020-06-06 22:43:33 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-06-14 15:07:31 +00:00
|
|
|
|
|
|
|
debug!("Id exists, trying again");
|
2020-06-06 22:43:33 +00:00
|
|
|
}
|
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
Ok(())
|
2020-06-06 22:43:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate an alias to the file
|
2020-06-14 18:56:42 +00:00
|
|
|
#[instrument(skip(self, hash, content_type))]
|
2020-06-06 22:43:33 +00:00
|
|
|
async fn next_alias(
|
|
|
|
&self,
|
2020-06-14 15:07:31 +00:00
|
|
|
hash: &Hash,
|
2020-06-06 22:43:33 +00:00
|
|
|
content_type: mime::Mime,
|
|
|
|
) -> Result<String, UploadError> {
|
|
|
|
use rand::distributions::{Alphanumeric, Distribution};
|
|
|
|
let mut limit: usize = 10;
|
|
|
|
let rng = rand::thread_rng();
|
|
|
|
loop {
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Alias gen loop");
|
2020-06-06 22:43:33 +00:00
|
|
|
let s: String = Alphanumeric.sample_iter(rng).take(limit).collect();
|
2020-06-24 16:58:46 +00:00
|
|
|
let alias = file_name(s, content_type.clone())?;
|
2020-06-06 22:43:33 +00:00
|
|
|
|
2020-06-11 00:26:54 +00:00
|
|
|
let res = self.save_alias(hash, &alias).await?;
|
2020-06-06 22:43:33 +00:00
|
|
|
|
|
|
|
if res.is_ok() {
|
2020-06-11 00:26:54 +00:00
|
|
|
return Ok(alias);
|
2020-06-06 22:43:33 +00:00
|
|
|
}
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Alias exists, regenning");
|
2020-06-06 22:43:33 +00:00
|
|
|
|
|
|
|
limit += 1;
|
|
|
|
}
|
|
|
|
}
|
2020-06-11 00:26:54 +00:00
|
|
|
|
|
|
|
// Save an alias to the database
|
2020-06-14 18:56:42 +00:00
|
|
|
#[instrument(skip(self, hash))]
|
2020-06-11 00:26:54 +00:00
|
|
|
async fn save_alias(
|
|
|
|
&self,
|
2020-06-14 15:07:31 +00:00
|
|
|
hash: &Hash,
|
2020-06-11 00:26:54 +00:00
|
|
|
alias: &str,
|
|
|
|
) -> Result<Result<(), UploadError>, UploadError> {
|
|
|
|
let tree = self.inner.alias_tree.clone();
|
2020-06-14 15:07:31 +00:00
|
|
|
let vec = hash.inner.clone();
|
2020-06-11 00:26:54 +00:00
|
|
|
let alias = alias.to_string();
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
debug!("Saving alias");
|
2020-06-11 00:26:54 +00:00
|
|
|
let res = web::block(move || {
|
|
|
|
tree.compare_and_swap(alias.as_bytes(), None as Option<sled::IVec>, Some(vec))
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
if res.is_err() {
|
2020-06-14 15:07:31 +00:00
|
|
|
warn!("Duplicate alias");
|
2020-06-11 00:26:54 +00:00
|
|
|
return Ok(Err(UploadError::DuplicateAlias));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(Ok(()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-14 15:07:31 +00:00
|
|
|
#[instrument(skip(stream))]
|
2020-06-15 02:41:45 +00:00
|
|
|
async fn safe_save_stream<E>(to: PathBuf, stream: UploadStream<E>) -> Result<(), UploadError>
|
2020-06-11 00:26:54 +00:00
|
|
|
where
|
|
|
|
UploadError: From<E>,
|
2020-06-15 02:41:45 +00:00
|
|
|
E: Unpin,
|
2020-06-11 00:26:54 +00:00
|
|
|
{
|
2020-06-15 02:41:45 +00:00
|
|
|
if let Some(path) = to.parent() {
|
|
|
|
debug!("Creating directory {:?}", path);
|
|
|
|
actix_fs::create_dir_all(path.to_owned()).await?;
|
|
|
|
}
|
2020-06-11 00:26:54 +00:00
|
|
|
|
2020-06-15 02:41:45 +00:00
|
|
|
debug!("Checking if {:?} alreayd exists", to);
|
|
|
|
if let Err(e) = actix_fs::metadata(to.clone()).await {
|
|
|
|
if e.kind() != Some(std::io::ErrorKind::NotFound) {
|
|
|
|
return Err(e.into());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return Err(UploadError::FileExists);
|
2020-06-11 00:26:54 +00:00
|
|
|
}
|
|
|
|
|
2020-06-15 02:41:45 +00:00
|
|
|
debug!("Writing stream to {:?}", to);
|
|
|
|
let stream = stream.err_into::<UploadError>();
|
|
|
|
actix_fs::write_stream(to, stream).await?;
|
|
|
|
|
|
|
|
Ok(())
|
2020-06-06 22:43:33 +00:00
|
|
|
}
|
|
|
|
|
2020-06-09 23:39:49 +00:00
|
|
|
async fn remove_path(path: sled::IVec) -> Result<(), UploadError> {
|
|
|
|
let path_string = String::from_utf8(path.to_vec())?;
|
|
|
|
actix_fs::remove_file(path_string).await?;
|
2020-06-07 00:29:15 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn trans_err(e: UploadError) -> sled::transaction::ConflictableTransactionError<UploadError> {
|
|
|
|
sled::transaction::ConflictableTransactionError::Abort(e)
|
|
|
|
}
|
|
|
|
|
2020-06-24 16:58:46 +00:00
|
|
|
fn file_name(name: String, content_type: mime::Mime) -> Result<String, UploadError> {
|
|
|
|
Ok(format!("{}{}", name, to_ext(content_type)?))
|
2020-06-06 21:41:17 +00:00
|
|
|
}
|
2020-06-07 00:29:15 +00:00
|
|
|
|
|
|
|
fn alias_key(hash: &[u8], id: &str) -> Vec<u8> {
|
|
|
|
let mut key = hash.to_vec();
|
|
|
|
// add a separator to the key between the hash and the ID
|
|
|
|
key.extend(&[0]);
|
|
|
|
key.extend(id.as_bytes());
|
|
|
|
|
|
|
|
key
|
|
|
|
}
|
|
|
|
|
|
|
|
fn alias_id_key(alias: &str) -> String {
|
|
|
|
format!("{}/id", alias)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn delete_key(alias: &str) -> String {
|
|
|
|
format!("{}/delete", alias)
|
|
|
|
}
|
2020-06-07 15:59:58 +00:00
|
|
|
|
2020-06-09 23:39:49 +00:00
|
|
|
fn variant_key(hash: &[u8], path: &str) -> Vec<u8> {
|
|
|
|
let mut key = hash.to_vec();
|
|
|
|
key.extend(&[2]);
|
|
|
|
key.extend(path.as_bytes());
|
|
|
|
key
|
|
|
|
}
|