Replace BlobError type with anyhow

This commit is contained in:
link2xt
2022-06-11 19:09:44 +00:00
parent 29eb2cc68a
commit 5c0447ee29
3 changed files with 40 additions and 185 deletions

View File

@@ -9,10 +9,9 @@ use async_std::path::{Path, PathBuf};
use async_std::prelude::*; use async_std::prelude::*;
use async_std::{fs, io}; use async_std::{fs, io};
use anyhow::{format_err, Context as _, Error}; use anyhow::{format_err, Context as _, Error, Result};
use image::{DynamicImage, ImageFormat}; use image::{DynamicImage, ImageFormat};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use thiserror::Error;
use crate::config::Config; use crate::config::Config;
use crate::constants::{ use crate::constants::{
@@ -44,31 +43,15 @@ impl<'a> BlobObject<'a> {
/// name, followed by a random number and followed by a possible /// name, followed by a random number and followed by a possible
/// extension. The `data` will be written into the file without /// extension. The `data` will be written into the file without
/// race-conditions. /// race-conditions.
///
/// # Errors
///
/// [BlobError::CreateFailure] is used when the file could not
/// be created. You can expect [BlobError.cause] to contain an
/// underlying error.
///
/// [BlobError::WriteFailure] is used when the file could not
/// be written to. You can expect [BlobError.cause] to contain an
/// underlying error.
pub async fn create( pub async fn create(
context: &'a Context, context: &'a Context,
suggested_name: &str, suggested_name: &str,
data: &[u8], data: &[u8],
) -> std::result::Result<BlobObject<'a>, BlobError> { ) -> Result<BlobObject<'a>> {
let blobdir = context.get_blobdir(); let blobdir = context.get_blobdir();
let (stem, ext) = BlobObject::sanitise_name(suggested_name); let (stem, ext) = BlobObject::sanitise_name(suggested_name);
let (name, mut file) = BlobObject::create_new_file(context, blobdir, &stem, &ext).await?; let (name, mut file) = BlobObject::create_new_file(context, blobdir, &stem, &ext).await?;
file.write_all(data) file.write_all(data).await.context("file write failure")?;
.await
.map_err(|err| BlobError::WriteFailure {
blobdir: blobdir.to_path_buf(),
blobname: name.clone(),
cause: err.into(),
})?;
// workaround a bug in async-std // workaround a bug in async-std
// (the executor does not handle blocking operation in Drop correctly, // (the executor does not handle blocking operation in Drop correctly,
@@ -89,7 +72,7 @@ impl<'a> BlobObject<'a> {
dir: &Path, dir: &Path,
stem: &str, stem: &str,
ext: &str, ext: &str,
) -> Result<(String, fs::File), BlobError> { ) -> Result<(String, fs::File)> {
const MAX_ATTEMPT: u32 = 16; const MAX_ATTEMPT: u32 = 16;
let mut attempt = 0; let mut attempt = 0;
let mut name = format!("{}{}", stem, ext); let mut name = format!("{}{}", stem, ext);
@@ -105,11 +88,7 @@ impl<'a> BlobObject<'a> {
Ok(file) => return Ok((name, file)), Ok(file) => return Ok((name, file)),
Err(err) => { Err(err) => {
if attempt >= MAX_ATTEMPT { if attempt >= MAX_ATTEMPT {
return Err(BlobError::CreateFailure { return Err(err).context("failed to create file");
blobdir: dir.to_path_buf(),
blobname: name,
cause: err,
});
} else if attempt == 1 && !dir.exists().await { } else if attempt == 1 && !dir.exists().await {
fs::create_dir_all(dir).await.ok_or_log(context); fs::create_dir_all(dir).await.ok_or_log(context);
} else { } else {
@@ -126,40 +105,19 @@ impl<'a> BlobObject<'a> {
/// but also copies an existing file into it. This is done in a /// but also copies an existing file into it. This is done in a
/// in way which avoids race-conditions when multiple files are /// in way which avoids race-conditions when multiple files are
/// concurrently created. /// concurrently created.
/// pub async fn create_and_copy(context: &'a Context, src: &Path) -> Result<BlobObject<'a>> {
/// # Errors
///
/// In addition to the errors in [BlobObject::create] the
/// [BlobError::CopyFailure] is used when the data can not be
/// copied.
pub async fn create_and_copy(
context: &'a Context,
src: &Path,
) -> std::result::Result<BlobObject<'a>, BlobError> {
let mut src_file = fs::File::open(src) let mut src_file = fs::File::open(src)
.await .await
.map_err(|err| BlobError::CopyFailure { .with_context(|| format!("failed to open file {}", src.display()))?;
blobdir: context.get_blobdir().to_path_buf(),
blobname: String::from(""),
src: src.to_path_buf(),
cause: err,
})?;
let (stem, ext) = BlobObject::sanitise_name(&src.to_string_lossy()); let (stem, ext) = BlobObject::sanitise_name(&src.to_string_lossy());
let (name, mut dst_file) = let (name, mut dst_file) =
BlobObject::create_new_file(context, context.get_blobdir(), &stem, &ext).await?; BlobObject::create_new_file(context, context.get_blobdir(), &stem, &ext).await?;
let name_for_err = name.clone(); let name_for_err = name.clone();
if let Err(err) = io::copy(&mut src_file, &mut dst_file).await { if let Err(err) = io::copy(&mut src_file, &mut dst_file).await {
{ // Attempt to remove the failed file, swallow errors resulting from that.
// Attempt to remove the failed file, swallow errors resulting from that. let path = context.get_blobdir().join(&name_for_err);
let path = context.get_blobdir().join(&name_for_err); fs::remove_file(path).await.ok();
fs::remove_file(path).await.ok(); return Err(err).context("failed to copy file");
}
return Err(BlobError::CopyFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: name_for_err,
src: src.to_path_buf(),
cause: err,
});
} }
// workaround, see create() for details // workaround, see create() for details
@@ -184,16 +142,7 @@ impl<'a> BlobObject<'a> {
/// ///
/// Paths into the blob directory may be either defined by an absolute path /// Paths into the blob directory may be either defined by an absolute path
/// or by the relative prefix `$BLOBDIR`. /// or by the relative prefix `$BLOBDIR`.
/// pub async fn new_from_path(context: &'a Context, src: &Path) -> Result<BlobObject<'a>> {
/// # Errors
///
/// This merely delegates to the [BlobObject::create_and_copy] and
/// the [BlobObject::from_path] methods. See those for possible
/// errors.
pub async fn new_from_path(
context: &'a Context,
src: &Path,
) -> std::result::Result<BlobObject<'a>, BlobError> {
if src.starts_with(context.get_blobdir()) { if src.starts_with(context.get_blobdir()) {
BlobObject::from_path(context, src) BlobObject::from_path(context, src)
} else if src.starts_with("$BLOBDIR/") { } else if src.starts_with("$BLOBDIR/") {
@@ -209,32 +158,14 @@ impl<'a> BlobObject<'a> {
/// must use a valid blob name. That is after sanitisation the /// must use a valid blob name. That is after sanitisation the
/// name must still be the same, that means it must be valid UTF-8 /// name must still be the same, that means it must be valid UTF-8
/// and not have any special characters in it. /// and not have any special characters in it.
/// pub fn from_path(context: &'a Context, path: &Path) -> Result<BlobObject<'a>> {
/// # Errors let rel_path = path
/// .strip_prefix(context.get_blobdir())
/// [BlobError::WrongBlobdir] is used if the path is not in .context("wrong blobdir")?;
/// the blob directory.
///
/// [BlobError::WrongName] is used if the file name does not
/// remain identical after sanitisation.
pub fn from_path(
context: &'a Context,
path: &Path,
) -> std::result::Result<BlobObject<'a>, BlobError> {
let rel_path =
path.strip_prefix(context.get_blobdir())
.map_err(|_| BlobError::WrongBlobdir {
blobdir: context.get_blobdir().to_path_buf(),
src: path.to_path_buf(),
})?;
if !BlobObject::is_acceptible_blob_name(rel_path) { if !BlobObject::is_acceptible_blob_name(rel_path) {
return Err(BlobError::WrongName { return Err(format_err!("wrong name"));
blobname: path.to_path_buf(),
});
} }
let name = rel_path.to_str().ok_or_else(|| BlobError::WrongName { let name = rel_path.to_str().context("wrong name")?;
blobname: path.to_path_buf(),
})?;
BlobObject::from_name(context, name.to_string()) BlobObject::from_name(context, name.to_string())
} }
@@ -244,24 +175,13 @@ impl<'a> BlobObject<'a> {
/// prefixed, as returned by [BlobObject::as_name]. This is how /// prefixed, as returned by [BlobObject::as_name]. This is how
/// you want to create a [BlobObject] for a filename read from the /// you want to create a [BlobObject] for a filename read from the
/// database. /// database.
/// pub fn from_name(context: &'a Context, name: String) -> Result<BlobObject<'a>> {
/// # Errors
///
/// [BlobError::WrongName] is used if the name is not a valid
/// blobname, i.e. if [BlobObject::sanitise_name] does modify the
/// provided name.
pub fn from_name(
context: &'a Context,
name: String,
) -> std::result::Result<BlobObject<'a>, BlobError> {
let name: String = match name.starts_with("$BLOBDIR/") { let name: String = match name.starts_with("$BLOBDIR/") {
true => name.splitn(2, '/').last().unwrap().to_string(), true => name.splitn(2, '/').last().unwrap().to_string(),
false => name, false => name,
}; };
if !BlobObject::is_acceptible_blob_name(&name) { if !BlobObject::is_acceptible_blob_name(&name) {
return Err(BlobError::WrongName { return Err(format_err!("not an acceptable blob name: {}", &name));
blobname: PathBuf::from(name),
});
} }
Ok(BlobObject { Ok(BlobObject {
blobdir: context.get_blobdir(), blobdir: context.get_blobdir(),
@@ -394,7 +314,7 @@ impl<'a> BlobObject<'a> {
true true
} }
pub async fn recode_to_avatar_size(&mut self, context: &Context) -> Result<(), BlobError> { pub async fn recode_to_avatar_size(&mut self, context: &Context) -> Result<()> {
let blob_abs = self.to_abs_path(); let blob_abs = self.to_abs_path();
let img_wh = let img_wh =
@@ -416,7 +336,7 @@ impl<'a> BlobObject<'a> {
Ok(()) Ok(())
} }
pub async fn recode_to_image_size(&self, context: &Context) -> Result<(), BlobError> { pub async fn recode_to_image_size(&self, context: &Context) -> Result<()> {
let blob_abs = self.to_abs_path(); let blob_abs = self.to_abs_path();
if message::guess_msgtype_from_suffix(Path::new(&blob_abs)) if message::guess_msgtype_from_suffix(Path::new(&blob_abs))
!= Some((Viewtype::Image, "image/jpeg")) != Some((Viewtype::Image, "image/jpeg"))
@@ -439,8 +359,7 @@ impl<'a> BlobObject<'a> {
{ {
return Err(format_err!( return Err(format_err!(
"Internal error: recode_to_size(..., None) shouldn't change the name of the image" "Internal error: recode_to_size(..., None) shouldn't change the name of the image"
) ));
.into());
} }
Ok(()) Ok(())
} }
@@ -451,12 +370,8 @@ impl<'a> BlobObject<'a> {
mut blob_abs: PathBuf, mut blob_abs: PathBuf,
mut img_wh: u32, mut img_wh: u32,
max_bytes: Option<usize>, max_bytes: Option<usize>,
) -> Result<Option<String>, BlobError> { ) -> Result<Option<String>> {
let mut img = image::open(&blob_abs).map_err(|err| BlobError::RecodeFailure { let mut img = image::open(&blob_abs).context("image recode failure")?;
blobdir: context.get_blobdir().to_path_buf(),
blobname: blob_abs.to_str().unwrap_or_default().to_string(),
cause: err,
})?;
let orientation = self.get_exif_orientation(context); let orientation = self.get_exif_orientation(context);
let mut encoded = Vec::new(); let mut encoded = Vec::new();
let mut changed_name = None; let mut changed_name = None;
@@ -520,8 +435,7 @@ impl<'a> BlobObject<'a> {
return Err(format_err!( return Err(format_err!(
"Failed to scale image to below {}B", "Failed to scale image to below {}B",
max_bytes.unwrap_or_default() max_bytes.unwrap_or_default()
) ));
.into());
} }
img_wh = img_wh * 2 / 3; img_wh = img_wh * 2 / 3;
@@ -555,11 +469,7 @@ impl<'a> BlobObject<'a> {
fs::write(&blob_abs, &encoded) fs::write(&blob_abs, &encoded)
.await .await
.map_err(|err| BlobError::WriteFailure { .context("failed to write recoded blob to file")?;
blobdir: context.get_blobdir().to_path_buf(),
blobname: blob_abs.to_str().unwrap_or_default().to_string(),
cause: err.into(),
})?;
} }
Ok(changed_name) Ok(changed_name)
@@ -590,46 +500,6 @@ impl<'a> fmt::Display for BlobObject<'a> {
} }
} }
/// Errors for the [BlobObject].
#[derive(Debug, Error)]
pub enum BlobError {
#[error("Failed to create blob {blobname} in {}", .blobdir.display())]
CreateFailure {
blobdir: PathBuf,
blobname: String,
#[source]
cause: std::io::Error,
},
#[error("Failed to write data to blob {blobname} in {}", .blobdir.display())]
WriteFailure {
blobdir: PathBuf,
blobname: String,
#[source]
cause: anyhow::Error,
},
#[error("Failed to copy data from {} to blob {blobname} in {}", .src.display(), .blobdir.display())]
CopyFailure {
blobdir: PathBuf,
blobname: String,
src: PathBuf,
#[source]
cause: std::io::Error,
},
#[error("Failed to recode to blob {blobname} in {}", .blobdir.display())]
RecodeFailure {
blobdir: PathBuf,
blobname: String,
#[source]
cause: image::ImageError,
},
#[error("File path {} is not in {}", .src.display(), .blobdir.display())]
WrongBlobdir { blobdir: PathBuf, src: PathBuf },
#[error("Blob has a badname {}", .blobname.display())]
WrongName { blobname: PathBuf },
#[error("{0}")]
Other(#[from] anyhow::Error),
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use fs::File; use fs::File;

View File

@@ -11,7 +11,7 @@ use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::aheader::EncryptPreference; use crate::aheader::EncryptPreference;
use crate::blob::{BlobError, BlobObject}; use crate::blob::BlobObject;
use crate::color::str_to_color; use crate::color::str_to_color;
use crate::config::Config; use crate::config::Config;
use crate::constants::{ use crate::constants::{
@@ -3022,15 +3022,8 @@ pub async fn set_chat_profile_image(
msg.param.remove(Param::Arg); msg.param.remove(Param::Arg);
msg.text = Some(stock_str::msg_grp_img_deleted(context, ContactId::SELF).await); msg.text = Some(stock_str::msg_grp_img_deleted(context, ContactId::SELF).await);
} else { } else {
let mut image_blob = match BlobObject::from_path(context, Path::new(new_image.as_ref())) { let mut image_blob =
Ok(blob) => Ok(blob), BlobObject::new_from_path(context, Path::new(new_image.as_ref())).await?;
Err(err) => match err {
BlobError::WrongBlobdir { .. } => {
BlobObject::create_and_copy(context, Path::new(new_image.as_ref())).await
}
_ => Err(err),
},
}?;
image_blob.recode_to_avatar_size(context).await?; image_blob.recode_to_avatar_size(context).await?;
chat.param.set(Param::ProfileImage, image_blob.as_name()); chat.param.set(Param::ProfileImage, image_blob.as_name());
msg.param.set(Param::Arg, image_blob.as_name()); msg.param.set(Param::Arg, image_blob.as_name());

View File

@@ -2,12 +2,12 @@ use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use std::str; use std::str;
use anyhow::{bail, Error}; use anyhow::{bail, Error, Result};
use async_std::path::PathBuf; use async_std::path::PathBuf;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::blob::{BlobError, BlobObject}; use crate::blob::BlobObject;
use crate::context::Context; use crate::context::Context;
use crate::message::MsgId; use crate::message::MsgId;
use crate::mimeparser::SystemMessage; use crate::mimeparser::SystemMessage;
@@ -320,11 +320,7 @@ impl Params {
/// ///
/// See also [Params::get_blob] and [Params::get_path] which may /// See also [Params::get_blob] and [Params::get_path] which may
/// be more convenient. /// be more convenient.
pub fn get_file<'a>( pub fn get_file<'a>(&self, key: Param, context: &'a Context) -> Result<Option<ParamsFile<'a>>> {
&self,
key: Param,
context: &'a Context,
) -> Result<Option<ParamsFile<'a>>, BlobError> {
let val = match self.get(key) { let val = match self.get(key) {
Some(val) => val, Some(val) => val,
None => return Ok(None), None => return Ok(None),
@@ -337,8 +333,8 @@ impl Params {
/// This parses the parameter value as a [ParamsFile] and than /// This parses the parameter value as a [ParamsFile] and than
/// tries to return a [BlobObject] for that file. If the file is /// tries to return a [BlobObject] for that file. If the file is
/// not yet a valid blob, one will be created by copying the file /// not yet a valid blob, one will be created by copying the file
/// only if `create` is set to `true`, otherwise the a [BlobError] /// only if `create` is set to `true`, otherwise an error is
/// will result. /// returned.
/// ///
/// Note that in the [ParamsFile::FsPath] case the blob can be /// Note that in the [ParamsFile::FsPath] case the blob can be
/// created without copying if the path already referes to a valid /// created without copying if the path already referes to a valid
@@ -350,7 +346,7 @@ impl Params {
key: Param, key: Param,
context: &'a Context, context: &'a Context,
create: bool, create: bool,
) -> Result<Option<BlobObject<'a>>, BlobError> { ) -> Result<Option<BlobObject<'a>>> {
let val = match self.get(key) { let val = match self.get(key) {
Some(val) => val, Some(val) => val,
None => return Ok(None), None => return Ok(None),
@@ -370,7 +366,7 @@ impl Params {
/// ///
/// This parses the parameter value as a [ParamsFile] and returns /// This parses the parameter value as a [ParamsFile] and returns
/// a [PathBuf] to the file. /// a [PathBuf] to the file.
pub fn get_path(&self, key: Param, context: &Context) -> Result<Option<PathBuf>, BlobError> { pub fn get_path(&self, key: Param, context: &Context) -> Result<Option<PathBuf>> {
let val = match self.get(key) { let val = match self.get(key) {
Some(val) => val, Some(val) => val,
None => return Ok(None), None => return Ok(None),
@@ -425,7 +421,7 @@ impl<'a> ParamsFile<'a> {
/// ///
/// If the value was stored into the [Params] correctly this /// If the value was stored into the [Params] correctly this
/// should not fail. /// should not fail.
pub fn from_param(context: &'a Context, src: &str) -> Result<ParamsFile<'a>, BlobError> { pub fn from_param(context: &'a Context, src: &str) -> Result<ParamsFile<'a>> {
let param = match src.starts_with("$BLOBDIR/") { let param = match src.starts_with("$BLOBDIR/") {
true => ParamsFile::Blob(BlobObject::from_name(context, src.to_string())?), true => ParamsFile::Blob(BlobObject::from_name(context, src.to_string())?),
false => ParamsFile::FsPath(PathBuf::from(src)), false => ParamsFile::FsPath(PathBuf::from(src)),
@@ -524,12 +520,8 @@ mod tests {
let fname: PathBuf = fname.into(); let fname: PathBuf = fname.into();
assert_eq!(path, fname); assert_eq!(path, fname);
// Blob does not exist yet, expect BlobError. // Blob does not exist yet, expect error.
let err = p.get_blob(Param::File, &t, false).await.unwrap_err(); assert!(p.get_blob(Param::File, &t, false).await.is_err());
match err {
BlobError::WrongBlobdir { .. } => (),
_ => panic!("wrong error type/variant: {:?}", err),
}
fs::write(fname, b"boo").await.unwrap(); fs::write(fname, b"boo").await.unwrap();
let blob = p.get_blob(Param::File, &t, true).await.unwrap().unwrap(); let blob = p.get_blob(Param::File, &t, true).await.unwrap().unwrap();