2023-09-02 23:30:45 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
2021-09-14 01:22:42 +00:00
|
|
|
use crate::{
|
2023-09-02 01:50:10 +00:00
|
|
|
error_code::ErrorCode,
|
2023-07-13 03:12:21 +00:00
|
|
|
formats::ProcessableFormat,
|
2023-07-10 20:29:41 +00:00
|
|
|
process::{Process, ProcessError},
|
2023-09-24 16:54:16 +00:00
|
|
|
read::BoxRead,
|
2023-07-10 20:29:41 +00:00
|
|
|
store::Store,
|
2021-09-14 01:22:42 +00:00
|
|
|
};
|
2023-09-24 16:54:16 +00:00
|
|
|
|
2023-07-13 18:48:59 +00:00
|
|
|
use tokio::io::AsyncRead;
|
2023-07-10 20:29:41 +00:00
|
|
|
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub(crate) enum MagickError {
|
|
|
|
#[error("Error in imagemagick process")]
|
|
|
|
Process(#[source] ProcessError),
|
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
#[error("Error in store")]
|
|
|
|
Store(#[source] crate::store::StoreError),
|
|
|
|
|
2023-07-10 20:29:41 +00:00
|
|
|
#[error("Invalid output format")]
|
|
|
|
Json(#[source] serde_json::Error),
|
|
|
|
|
|
|
|
#[error("Error reading bytes")]
|
|
|
|
Read(#[source] std::io::Error),
|
|
|
|
|
|
|
|
#[error("Error writing bytes")]
|
|
|
|
Write(#[source] std::io::Error),
|
|
|
|
|
|
|
|
#[error("Error creating file")]
|
|
|
|
CreateFile(#[source] std::io::Error),
|
|
|
|
|
|
|
|
#[error("Error creating directory")]
|
|
|
|
CreateDir(#[source] crate::store::file_store::FileError),
|
|
|
|
|
|
|
|
#[error("Error closing file")]
|
|
|
|
CloseFile(#[source] std::io::Error),
|
|
|
|
|
|
|
|
#[error("Error removing file")]
|
|
|
|
RemoveFile(#[source] std::io::Error),
|
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
#[error("Error in metadata discovery")]
|
|
|
|
Discover(#[source] crate::discover::DiscoverError),
|
|
|
|
|
2023-07-17 18:30:08 +00:00
|
|
|
#[error("Invalid media file provided")]
|
|
|
|
CommandFailed(ProcessError),
|
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
#[error("Command output is empty")]
|
|
|
|
Empty,
|
|
|
|
|
2023-07-10 20:29:41 +00:00
|
|
|
#[error("Invalid file path")]
|
|
|
|
Path,
|
|
|
|
}
|
|
|
|
|
2023-07-17 18:30:08 +00:00
|
|
|
impl From<ProcessError> for MagickError {
|
|
|
|
fn from(value: ProcessError) -> Self {
|
|
|
|
match value {
|
|
|
|
e @ ProcessError::Status(_, _) => Self::CommandFailed(e),
|
|
|
|
otherwise => Self::Process(otherwise),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-10 20:29:41 +00:00
|
|
|
impl MagickError {
|
2023-09-02 01:50:10 +00:00
|
|
|
pub(crate) const fn error_code(&self) -> ErrorCode {
|
|
|
|
match self {
|
|
|
|
Self::CommandFailed(_) => ErrorCode::COMMAND_FAILURE,
|
|
|
|
Self::Store(e) => e.error_code(),
|
|
|
|
Self::Process(e) => e.error_code(),
|
|
|
|
Self::Json(_)
|
|
|
|
| Self::Read(_)
|
|
|
|
| Self::Write(_)
|
|
|
|
| Self::CreateFile(_)
|
|
|
|
| Self::CreateDir(_)
|
|
|
|
| Self::CloseFile(_)
|
|
|
|
| Self::RemoveFile(_)
|
|
|
|
| Self::Discover(_)
|
|
|
|
| Self::Empty
|
|
|
|
| Self::Path => ErrorCode::COMMAND_ERROR,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-10 20:29:41 +00:00
|
|
|
pub(crate) fn is_client_error(&self) -> bool {
|
|
|
|
// Failing validation or imagemagick bailing probably means bad input
|
2023-08-05 21:18:06 +00:00
|
|
|
matches!(
|
|
|
|
self,
|
|
|
|
Self::CommandFailed(_) | Self::Process(ProcessError::Timeout(_))
|
|
|
|
)
|
2023-07-10 20:29:41 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-28 22:15:14 +00:00
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
async fn process_image<F, Fut>(
|
2023-07-13 03:12:21 +00:00
|
|
|
process_args: Vec<String>,
|
2023-07-14 00:21:57 +00:00
|
|
|
input_format: ProcessableFormat,
|
2023-07-13 03:12:21 +00:00
|
|
|
format: ProcessableFormat,
|
2023-07-18 21:18:01 +00:00
|
|
|
quality: Option<u8>,
|
2023-08-05 17:41:06 +00:00
|
|
|
timeout: u64,
|
2023-07-14 00:21:57 +00:00
|
|
|
write_file: F,
|
2023-09-24 16:54:16 +00:00
|
|
|
) -> Result<BoxRead<'static>, MagickError>
|
2023-07-14 00:21:57 +00:00
|
|
|
where
|
|
|
|
F: FnOnce(crate::file::File) -> Fut,
|
|
|
|
Fut: std::future::Future<Output = Result<crate::file::File, MagickError>>,
|
|
|
|
{
|
|
|
|
let input_file = crate::tmp_file::tmp_file(None);
|
|
|
|
let input_file_str = input_file.to_str().ok_or(MagickError::Path)?;
|
|
|
|
crate::store::file_store::safe_create_parent(&input_file)
|
|
|
|
.await
|
|
|
|
.map_err(MagickError::CreateDir)?;
|
|
|
|
|
|
|
|
let tmp_one = crate::file::File::create(&input_file)
|
|
|
|
.await
|
|
|
|
.map_err(MagickError::CreateFile)?;
|
|
|
|
let tmp_one = (write_file)(tmp_one).await?;
|
|
|
|
tmp_one.close().await.map_err(MagickError::CloseFile)?;
|
|
|
|
|
|
|
|
let input_arg = format!("{}:{input_file_str}", input_format.magick_format());
|
|
|
|
let output_arg = format!("{}:-", format.magick_format());
|
2023-07-18 21:18:01 +00:00
|
|
|
let quality = quality.map(|q| q.to_string());
|
2021-09-14 01:22:42 +00:00
|
|
|
|
2023-09-24 16:54:16 +00:00
|
|
|
let len = format.coalesce().then(|| 4).unwrap_or(3)
|
|
|
|
+ quality.is_some().then(|| 1).unwrap_or(0)
|
|
|
|
+ process_args.len();
|
2023-07-13 03:12:21 +00:00
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
let mut args: Vec<&str> = Vec::with_capacity(len);
|
|
|
|
args.push("convert");
|
|
|
|
args.push(&input_arg);
|
2023-07-13 03:12:21 +00:00
|
|
|
if format.coalesce() {
|
|
|
|
args.push("-coalesce");
|
|
|
|
}
|
2023-07-18 21:18:01 +00:00
|
|
|
args.extend(process_args.iter().map(|s| s.as_str()));
|
|
|
|
if let Some(quality) = &quality {
|
|
|
|
args.extend(["-quality", quality]);
|
|
|
|
}
|
2023-07-14 00:21:57 +00:00
|
|
|
args.push(&output_arg);
|
|
|
|
|
2023-08-05 17:41:06 +00:00
|
|
|
let reader = Process::run("magick", &args, timeout)?.read();
|
2023-07-14 00:21:57 +00:00
|
|
|
|
|
|
|
let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, input_file);
|
2023-07-10 20:29:41 +00:00
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
Ok(Box::pin(clean_reader))
|
2022-09-25 20:17:33 +00:00
|
|
|
}
|
2021-08-31 02:19:47 +00:00
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
pub(crate) async fn process_image_store_read<S: Store + 'static>(
|
|
|
|
store: &S,
|
2023-09-02 23:30:45 +00:00
|
|
|
identifier: &Arc<str>,
|
2022-09-25 20:17:33 +00:00
|
|
|
args: Vec<String>,
|
2023-07-14 00:21:57 +00:00
|
|
|
input_format: ProcessableFormat,
|
2023-07-13 03:12:21 +00:00
|
|
|
format: ProcessableFormat,
|
2023-07-18 21:18:01 +00:00
|
|
|
quality: Option<u8>,
|
2023-08-05 17:41:06 +00:00
|
|
|
timeout: u64,
|
2023-09-24 16:54:16 +00:00
|
|
|
) -> Result<BoxRead<'static>, MagickError> {
|
2023-07-14 00:21:57 +00:00
|
|
|
let stream = store
|
|
|
|
.to_stream(identifier, None, None)
|
|
|
|
.await
|
|
|
|
.map_err(MagickError::Store)?;
|
|
|
|
|
2023-07-18 21:18:01 +00:00
|
|
|
process_image(
|
|
|
|
args,
|
|
|
|
input_format,
|
|
|
|
format,
|
|
|
|
quality,
|
2023-08-05 17:41:06 +00:00
|
|
|
timeout,
|
2023-07-18 21:18:01 +00:00
|
|
|
|mut tmp_file| async move {
|
|
|
|
tmp_file
|
|
|
|
.write_from_stream(stream)
|
|
|
|
.await
|
|
|
|
.map_err(MagickError::Write)?;
|
|
|
|
Ok(tmp_file)
|
|
|
|
},
|
|
|
|
)
|
2023-07-14 00:21:57 +00:00
|
|
|
.await
|
2022-09-25 20:17:33 +00:00
|
|
|
}
|
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
pub(crate) async fn process_image_async_read<A: AsyncRead + Unpin + 'static>(
|
2022-09-25 20:17:33 +00:00
|
|
|
async_read: A,
|
|
|
|
args: Vec<String>,
|
2023-07-14 00:21:57 +00:00
|
|
|
input_format: ProcessableFormat,
|
2023-07-13 03:12:21 +00:00
|
|
|
format: ProcessableFormat,
|
2023-07-18 21:18:01 +00:00
|
|
|
quality: Option<u8>,
|
2023-08-05 17:41:06 +00:00
|
|
|
timeout: u64,
|
2023-09-24 16:54:16 +00:00
|
|
|
) -> Result<BoxRead<'static>, MagickError> {
|
2023-07-18 21:18:01 +00:00
|
|
|
process_image(
|
|
|
|
args,
|
|
|
|
input_format,
|
|
|
|
format,
|
|
|
|
quality,
|
2023-08-05 17:41:06 +00:00
|
|
|
timeout,
|
2023-07-18 21:18:01 +00:00
|
|
|
|mut tmp_file| async move {
|
|
|
|
tmp_file
|
|
|
|
.write_from_async_read(async_read)
|
|
|
|
.await
|
|
|
|
.map_err(MagickError::Write)?;
|
|
|
|
Ok(tmp_file)
|
|
|
|
},
|
|
|
|
)
|
2023-07-14 00:21:57 +00:00
|
|
|
.await
|
2023-07-10 20:29:41 +00:00
|
|
|
}
|