use crate::{
ProcessingError, ProcessingOutput, QueuedUpload,
office::{
convert_lambda::{
OfficeConvertLambdaConfig, OfficeConvertLambdaConfigError, OfficeConvertLambdaError,
OfficeConverterLambda,
},
convert_server::{OfficeConvertServerConfig, OfficeConvertServerError},
libreoffice::is_known_libreoffice_pdf_convertable,
},
pdf::{is_pdf_file, process_pdf},
};
use aws_config::SdkConfig;
use bytes::Bytes;
use convert_server::OfficeConverterServer;
use docbox_database::models::generated_file::GeneratedFileType;
use docbox_storage::StorageLayerFactory;
use mime::Mime;
use office_convert_client::RequestError;
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub mod convert_lambda;
pub mod convert_server;
pub mod libreoffice;
const DISALLOW_MALFORMED_OFFICE: bool = true;
#[derive(Debug, Error)]
pub enum PdfConvertError {
#[error(transparent)]
ConversionFailed(#[from] RequestError),
#[error(transparent)]
ConversionFailedLambda(#[from] OfficeConvertLambdaError),
#[error("office document is malformed")]
MalformedDocument,
#[error("office document is password protected")]
EncryptedDocument,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OfficeConverterConfig {
ConverterServer(OfficeConvertServerConfig),
ConverterLambda(OfficeConvertLambdaConfig),
}
#[derive(Debug, Error)]
pub enum OfficeConverterConfigError {
#[error(transparent)]
ConverterLambda(#[from] OfficeConvertLambdaConfigError),
}
impl OfficeConverterConfig {
pub fn from_env() -> Result<OfficeConverterConfig, OfficeConverterConfigError> {
let variant =
std::env::var("DOCBOX_OFFICE_CONVERTER").unwrap_or_else(|_| "server".to_string());
match variant.as_str() {
"lambda" => {
let config = OfficeConvertLambdaConfig::from_env()?;
Ok(OfficeConverterConfig::ConverterLambda(config))
}
_ => {
let config = OfficeConvertServerConfig::from_env();
Ok(OfficeConverterConfig::ConverterServer(config))
}
}
}
}
#[derive(Clone)]
pub enum OfficeConverter {
ConverterServer(OfficeConverterServer),
ConverterLambda(OfficeConverterLambda),
}
#[derive(Debug, Error)]
pub enum OfficeConverterError {
#[error(transparent)]
ConverterServer(#[from] OfficeConvertServerError),
#[error(transparent)]
ConverterLambda(#[from] OfficeConvertLambdaError),
}
#[derive(Clone)]
pub struct OfficeProcessingLayer {
pub converter: OfficeConverter,
}
impl OfficeConverter {
pub fn from_config(
aws_config: &SdkConfig,
storage: &StorageLayerFactory,
config: OfficeConverterConfig,
) -> Result<OfficeConverter, OfficeConverterError> {
match config {
OfficeConverterConfig::ConverterServer(config) => {
let converter_server = OfficeConverterServer::from_config(config)?;
Ok(OfficeConverter::ConverterServer(converter_server))
}
OfficeConverterConfig::ConverterLambda(config) => {
let converter_server =
OfficeConverterLambda::from_config(aws_config, storage, config)?;
Ok(OfficeConverter::ConverterLambda(converter_server))
}
}
}
pub async fn convert_to_pdf(&self, bytes: Bytes) -> Result<Bytes, PdfConvertError> {
match self {
OfficeConverter::ConverterServer(inner) => inner.convert_to_pdf(bytes).await,
OfficeConverter::ConverterLambda(inner) => inner.convert_to_pdf(bytes).await,
}
}
pub fn is_convertable(&self, mime: &Mime) -> bool {
match self {
OfficeConverter::ConverterServer(inner) => inner.is_convertable(mime),
OfficeConverter::ConverterLambda(inner) => inner.is_convertable(mime),
}
}
}
pub(crate) trait ConvertToPdf {
async fn convert_to_pdf(&self, bytes: Bytes) -> Result<Bytes, PdfConvertError>;
fn is_convertable(&self, mime: &Mime) -> bool;
}
pub fn is_pdf_compatible(mime: &Mime) -> bool {
is_pdf_file(mime) || is_known_libreoffice_pdf_convertable(mime)
}
pub async fn process_office(
layer: &OfficeProcessingLayer,
file_bytes: Bytes,
) -> Result<ProcessingOutput, ProcessingError> {
let file_bytes = match layer.converter.convert_to_pdf(file_bytes).await {
Ok(value) => value,
Err(PdfConvertError::EncryptedDocument) => {
return Ok(ProcessingOutput {
encrypted: true,
..Default::default()
});
}
Err(PdfConvertError::MalformedDocument) => {
if DISALLOW_MALFORMED_OFFICE {
return Err(ProcessingError::MalformedFile(
"office file appears to be malformed failed conversion".to_string(),
));
}
return Ok(ProcessingOutput::default());
}
Err(error) => {
tracing::error!(?error, "failed to convert document to pdf");
return Err(ProcessingError::ConvertFile(error));
}
};
let mut output = process_pdf(&file_bytes).await?;
output.upload_queue.push(QueuedUpload::new(
mime::APPLICATION_PDF,
GeneratedFileType::Pdf,
file_bytes,
));
Ok(output)
}