async file io

This commit is contained in:
dignifiedquire
2020-03-14 16:26:15 +01:00
parent 6db253e1cc
commit 7140898db9
19 changed files with 340 additions and 252 deletions

View File

@@ -2,9 +2,10 @@
use std::ffi::OsStr;
use std::fmt;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use async_std::path::{Path, PathBuf};
use async_std::prelude::*;
use async_std::{fs, io};
use self::image::GenericImageView;
use crate::constants::AVATAR_SIZE;
@@ -43,15 +44,16 @@ impl<'a> BlobObject<'a> {
/// [BlobError::WriteFailure] is used when the file could not
/// be written to. You can expect [BlobError.cause] to contain an
/// underlying error.
pub fn create(
pub async fn create(
context: &'a Context,
suggested_name: impl AsRef<str>,
data: &[u8],
) -> std::result::Result<BlobObject<'a>, BlobError> {
let blobdir = context.get_blobdir();
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref());
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?;
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext).await?;
file.write_all(data)
.await
.map_err(|err| BlobError::WriteFailure {
blobdir: blobdir.to_path_buf(),
blobname: name.clone(),
@@ -67,7 +69,11 @@ impl<'a> BlobObject<'a> {
}
// Creates a new file, returning a tuple of the name and the handle.
fn create_new_file(dir: &Path, stem: &str, ext: &str) -> Result<(String, fs::File), BlobError> {
async fn create_new_file(
dir: &Path,
stem: &str,
ext: &str,
) -> Result<(String, fs::File), BlobError> {
let max_attempt = 15;
let mut name = format!("{}{}", stem, ext);
for attempt in 0..max_attempt {
@@ -76,6 +82,7 @@ impl<'a> BlobObject<'a> {
.create_new(true)
.write(true)
.open(&path)
.await
{
Ok(file) => return Ok((name, file)),
Err(err) => {
@@ -113,34 +120,38 @@ impl<'a> BlobObject<'a> {
/// In addition to the errors in [BlobObject::create] the
/// [BlobError::CopyFailure] is used when the data can not be
/// copied.
pub fn create_and_copy(
pub async fn create_and_copy(
context: &'a Context,
src: impl AsRef<Path>,
) -> std::result::Result<BlobObject<'a>, BlobError> {
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| BlobError::CopyFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: String::from(""),
src: src.as_ref().to_path_buf(),
cause: err,
backtrace: failure::Backtrace::new(),
})?;
let mut src_file =
fs::File::open(src.as_ref())
.await
.map_err(|err| BlobError::CopyFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: String::from(""),
src: src.as_ref().to_path_buf(),
cause: err,
backtrace: failure::Backtrace::new(),
})?;
let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy());
let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?;
let (name, mut dst_file) =
BlobObject::create_new_file(context.get_blobdir(), &stem, &ext).await?;
let name_for_err = name.clone();
std::io::copy(&mut src_file, &mut dst_file).map_err(|err| {
if let Err(err) = io::copy(&mut src_file, &mut dst_file).await {
{
// Attempt to remove the failed file, swallow errors resulting from that.
let path = context.get_blobdir().join(&name_for_err);
fs::remove_file(path).ok();
fs::remove_file(path).await.ok();
}
BlobError::CopyFailure {
return Err(BlobError::CopyFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: name_for_err,
src: src.as_ref().to_path_buf(),
cause: err,
backtrace: failure::Backtrace::new(),
}
})?;
});
}
let blob = BlobObject {
blobdir: context.get_blobdir(),
name: format!("$BLOBDIR/{}", name),
@@ -163,14 +174,14 @@ impl<'a> BlobObject<'a> {
/// This merely delegates to the [BlobObject::create_and_copy] and
/// the [BlobObject::from_path] methods. See those for possible
/// errors.
pub fn new_from_path(
pub async fn new_from_path(
context: &Context,
src: impl AsRef<Path>,
) -> std::result::Result<BlobObject, BlobError> {
) -> std::result::Result<BlobObject<'_>, BlobError> {
if src.as_ref().starts_with(context.get_blobdir()) {
BlobObject::from_path(context, src)
} else {
BlobObject::create_and_copy(context, src)
BlobObject::create_and_copy(context, src).await
}
}
@@ -489,9 +500,9 @@ mod tests {
#[async_std::test]
async fn test_create() {
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo", b"hello").unwrap();
let blob = BlobObject::create(&t.ctx, "foo", b"hello").await.unwrap();
let fname = t.ctx.get_blobdir().join("foo");
let data = fs::read(fname).unwrap();
let data = fs::read(fname).await.unwrap();
assert_eq!(data, b"hello");
assert_eq!(blob.as_name(), "$BLOBDIR/foo");
assert_eq!(blob.to_abs_path(), t.ctx.get_blobdir().join("foo"));
@@ -500,44 +511,57 @@ mod tests {
#[async_std::test]
async fn test_lowercase_ext() {
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello").unwrap();
let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello")
.await
.unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/foo.txt");
}
#[async_std::test]
async fn test_as_file_name() {
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello")
.await
.unwrap();
assert_eq!(blob.as_file_name(), "foo.txt");
}
#[async_std::test]
async fn test_as_rel_path() {
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello")
.await
.unwrap();
assert_eq!(blob.as_rel_path(), Path::new("foo.txt"));
}
#[async_std::test]
async fn test_suffix() {
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello")
.await
.unwrap();
assert_eq!(blob.suffix(), Some("txt"));
let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
let blob = BlobObject::create(&t.ctx, "bar", b"world").await.unwrap();
assert_eq!(blob.suffix(), None);
}
#[async_std::test]
async fn test_create_dup() {
let t = dummy_context().await;
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
BlobObject::create(&t.ctx, "foo.txt", b"hello")
.await
.unwrap();
let foo_path = t.ctx.get_blobdir().join("foo.txt");
assert!(foo_path.exists());
BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
assert!(foo_path.exists().await);
BlobObject::create(&t.ctx, "foo.txt", b"world")
.await
.unwrap();
let mut dir = fs::read_dir(t.ctx.get_blobdir()).await.unwrap();
while let Some(dirent) = dir.next().await {
let fname = dirent.unwrap().file_name();
if fname == foo_path.file_name().unwrap() {
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
assert_eq!(fs::read(&foo_path).await.unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
assert!(name.starts_with("foo"));
@@ -549,14 +573,19 @@ mod tests {
#[async_std::test]
async fn test_double_ext_preserved() {
let t = dummy_context().await;
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello")
.await
.unwrap();
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
assert!(foo_path.exists());
BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
assert!(foo_path.exists().await);
BlobObject::create(&t.ctx, "foo.tar.gz", b"world")
.await
.unwrap();
let mut dir = fs::read_dir(t.ctx.get_blobdir()).await.unwrap();
while let Some(dirent) = dir.next().await {
let fname = dirent.unwrap().file_name();
if fname == foo_path.file_name().unwrap() {
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
assert_eq!(fs::read(&foo_path).await.unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
println!("{}", name);
@@ -570,7 +599,7 @@ mod tests {
async fn test_create_long_names() {
let t = dummy_context().await;
let s = "1".repeat(150);
let blob = BlobObject::create(&t.ctx, &s, b"data").unwrap();
let blob = BlobObject::create(&t.ctx, &s, b"data").await.unwrap();
let blobname = blob.as_name().split('/').last().unwrap();
assert!(blobname.len() < 128);
}
@@ -579,16 +608,16 @@ mod tests {
async fn test_create_and_copy() {
let t = dummy_context().await;
let src = t.dir.path().join("src");
fs::write(&src, b"boo").unwrap();
let blob = BlobObject::create_and_copy(&t.ctx, &src).unwrap();
fs::write(&src, b"boo").await.unwrap();
let blob = BlobObject::create_and_copy(&t.ctx, &src).await.unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/src");
let data = fs::read(blob.to_abs_path()).unwrap();
let data = fs::read(blob.to_abs_path()).await.unwrap();
assert_eq!(data, b"boo");
let whoops = t.dir.path().join("whoops");
assert!(BlobObject::create_and_copy(&t.ctx, &whoops).is_err());
assert!(BlobObject::create_and_copy(&t.ctx, &whoops).await.is_err());
let whoops = t.ctx.get_blobdir().join("whoops");
assert!(!whoops.exists());
assert!(!whoops.exists().await);
}
#[async_std::test]
@@ -596,25 +625,25 @@ mod tests {
let t = dummy_context().await;
let src_ext = t.dir.path().join("external");
fs::write(&src_ext, b"boo").unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
fs::write(&src_ext, b"boo").await.unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).await.unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/external");
let data = fs::read(blob.to_abs_path()).unwrap();
let data = fs::read(blob.to_abs_path()).await.unwrap();
assert_eq!(data, b"boo");
let src_int = t.ctx.get_blobdir().join("internal");
fs::write(&src_int, b"boo").unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_int).unwrap();
fs::write(&src_int, b"boo").await.unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_int).await.unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/internal");
let data = fs::read(blob.to_abs_path()).unwrap();
let data = fs::read(blob.to_abs_path()).await.unwrap();
assert_eq!(data, b"boo");
}
#[async_std::test]
async fn test_create_from_name_long() {
let t = dummy_context().await;
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
fs::write(&src_ext, b"boo").unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
fs::write(&src_ext, b"boo").await.unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).await.unwrap();
assert_eq!(
blob.as_name(),
"$BLOBDIR/autocrypt-setup-message-4137848473.html"

View File

@@ -1,9 +1,9 @@
//! # Chat module
use std::convert::TryFrom;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use async_std::path::{Path, PathBuf};
use itertools::Itertools;
use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize};
@@ -326,7 +326,8 @@ impl ChatId {
_ => {
let blob = msg
.param
.get_blob(Param::File, context, !msg.is_increation())?
.get_blob(Param::File, context, !msg.is_increation())
.await?
.ok_or_else(|| format_err!("No file stored in params"))?;
msg.param.set(Param::File, blob.as_name());
}
@@ -697,7 +698,8 @@ impl Chat {
profile_image: self
.get_profile_image(context)
.await
.unwrap_or_else(PathBuf::new),
.map(Into::into)
.unwrap_or_else(std::path::PathBuf::new),
subtitle: self.get_subtitle(context).await,
draft,
is_muted: self.is_muted(),
@@ -1046,7 +1048,7 @@ pub struct ChatInfo {
///
/// If there is no profile image set this will be an empty string
/// currently.
pub profile_image: PathBuf,
pub profile_image: std::path::PathBuf,
/// Subtitle for the chat.
pub subtitle: String,
@@ -1158,7 +1160,7 @@ pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<(),
// if there is no saved-messages chat, there is nothing to update. this is no error.
if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_SELF).await {
let icon = include_bytes!("../assets/icon-saved-messages.png");
let blob = BlobObject::create(context, "icon-saved-messages.png".to_string(), icon)?;
let blob = BlobObject::create(context, "icon-saved-messages.png".to_string(), icon).await?;
let icon = blob.as_name().to_string();
let mut chat = Chat::load_from_db(context, chat_id).await?;
@@ -1172,7 +1174,7 @@ pub(crate) async fn update_device_icon(context: &Context) -> Result<(), Error> {
// if there is no device-chat, there is nothing to update. this is no error.
if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE).await {
let icon = include_bytes!("../assets/icon-device.png");
let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?;
let blob = BlobObject::create(context, "icon-device.png".to_string(), icon).await?;
let icon = blob.as_name().to_string();
let mut chat = Chat::load_from_db(context, chat_id).await?;
@@ -1335,13 +1337,14 @@ pub(crate) fn msgtype_has_file(msgtype: Viewtype) -> bool {
}
}
fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
if msg.viewtype == Viewtype::Text {
// the caller should check if the message text is empty
} else if msgtype_has_file(msg.viewtype) {
let blob = msg
.param
.get_blob(Param::File, context, !msg.is_increation())?
.get_blob(Param::File, context, !msg.is_increation())
.await?
.ok_or_else(|| {
format_err!("Attachment missing for message of type #{}", msg.viewtype)
})?;
@@ -1383,7 +1386,7 @@ async fn prepare_msg_common(
msg: &mut Message,
) -> Result<MsgId, Error> {
msg.id = MsgId::new_unset();
prepare_msg_blob(context, msg)?;
prepare_msg_blob(context, msg).await?;
chat_id.unarchive(context).await?;
let mut chat = Chat::load_from_db(context, chat_id).await?;
@@ -2364,14 +2367,15 @@ pub async fn set_chat_profile_image(
.await,
);
} else {
let image_blob = BlobObject::from_path(context, Path::new(new_image.as_ref())).or_else(
|err| match err {
let image_blob = match BlobObject::from_path(context, Path::new(new_image.as_ref())) {
Ok(blob) => Ok(blob),
Err(err) => match err {
BlobError::WrongBlobdir { .. } => {
BlobObject::create_and_copy(context, Path::new(new_image.as_ref()))
BlobObject::create_and_copy(context, Path::new(new_image.as_ref())).await
}
_ => Err(err),
},
)?;
}?;
image_blob.recode_to_avatar_size(context)?;
chat.param.set(Param::ProfileImage, image_blob.as_name());
msg.param.set(Param::Arg, image_blob.as_name());
@@ -2560,7 +2564,7 @@ pub async fn add_device_msg(
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
msg.try_calc_and_set_dimensions(context).await.ok();
prepare_msg_blob(context, msg)?;
prepare_msg_blob(context, msg).await?;
chat_id.unarchive(context).await?;
context.sql.execute(

View File

@@ -141,7 +141,7 @@ impl Context {
.await?;
match value {
Some(value) => {
let blob = BlobObject::new_from_path(&self, value)?;
let blob = BlobObject::new_from_path(&self, value).await?;
blob.recode_to_avatar_size(self)?;
self.sql
.set_raw_config(self, key, Some(blob.as_name()))
@@ -231,12 +231,12 @@ mod tests {
.write_all(avatar_bytes)
.unwrap();
let avatar_blob = t.ctx.get_blobdir().join("avatar.jpg");
assert!(!avatar_blob.exists());
assert!(!avatar_blob.exists().await);
t.ctx
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))
.await
.unwrap();
assert!(avatar_blob.exists());
assert!(avatar_blob.exists().await);
assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64);
let avatar_cfg = t.ctx.get_config(Config::Selfavatar).await;
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));

View File

@@ -1,7 +1,6 @@
//! Contacts module
use std::path::PathBuf;
use async_std::path::PathBuf;
use deltachat_derive::*;
use itertools::Itertools;
use rusqlite;

View File

@@ -2,9 +2,9 @@
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use async_std::path::{Path, PathBuf};
use async_std::sync::{Arc, Mutex, RwLock};
use crate::chat::*;
@@ -94,7 +94,7 @@ impl Context {
blob_fname.push(dbfile.file_name().unwrap_or_default());
blob_fname.push("-blobs");
let blobdir = dbfile.with_file_name(blob_fname);
if !blobdir.exists() {
if !blobdir.exists().await {
std::fs::create_dir_all(&blobdir)?;
}
Context::with_blobdir(cb, os_name, dbfile, blobdir).await
@@ -107,7 +107,7 @@ impl Context {
blobdir: PathBuf,
) -> Result<Context> {
ensure!(
blobdir.is_dir(),
blobdir.is_dir().await,
"Blobdir does not exist: {}",
blobdir.display()
);
@@ -518,7 +518,7 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
std::fs::write(&dbfile, b"123").unwrap();
let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile).await;
let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile.into()).await;
assert!(res.is_err());
}
@@ -533,7 +533,7 @@ mod tests {
async fn test_blobdir_exists() {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile)
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into())
.await
.unwrap();
let blobdir = tmp.path().join("db.sqlite-blobs");
@@ -546,7 +546,7 @@ mod tests {
let dbfile = tmp.path().join("db.sqlite");
let blobdir = tmp.path().join("db.sqlite-blobs");
std::fs::write(&blobdir, b"123").unwrap();
let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).await;
let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into()).await;
assert!(res.is_err());
}
@@ -556,7 +556,7 @@ mod tests {
let subdir = tmp.path().join("subdir");
let dbfile = subdir.join("db.sqlite");
let dbfile2 = dbfile.clone();
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile)
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into())
.await
.unwrap();
assert!(subdir.is_dir());
@@ -568,8 +568,13 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
let blobdir = PathBuf::new();
let res =
Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir).await;
let res = Context::with_blobdir(
Box::new(|_, _| ()),
"FakeOS".into(),
dbfile.into(),
blobdir.into(),
)
.await;
assert!(res.is_err());
}
@@ -578,8 +583,13 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
let blobdir = tmp.path().join("blobs");
let res =
Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir).await;
let res = Context::with_blobdir(
Box::new(|_, _| ()),
"FakeOS".into(),
dbfile.into(),
blobdir.into(),
)
.await;
assert!(res.is_err());
}

View File

@@ -3,11 +3,12 @@
use core::cmp::{max, min};
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::fmt;
use std::str::FromStr;
use std::time::SystemTime;
use std::{fmt, fs};
use async_std::path::{Path, PathBuf};
use async_std::{fs, io};
use chrono::{Local, TimeZone};
use rand::{thread_rng, Rng};
@@ -240,11 +241,8 @@ pub fn dc_get_filemeta(buf: &[u8]) -> Result<(u32, u32), Error> {
///
/// If `path` starts with "$BLOBDIR", replaces it with the blobdir path.
/// Otherwise, returns path as is.
pub(crate) fn dc_get_abs_path<P: AsRef<std::path::Path>>(
context: &Context,
path: P,
) -> std::path::PathBuf {
let p: &std::path::Path = path.as_ref();
pub(crate) fn dc_get_abs_path<P: AsRef<Path>>(context: &Context, path: P) -> PathBuf {
let p: &Path = path.as_ref();
if let Ok(p) = p.strip_prefix("$BLOBDIR") {
context.get_blobdir().join(p)
} else {
@@ -252,20 +250,20 @@ pub(crate) fn dc_get_abs_path<P: AsRef<std::path::Path>>(
}
}
pub(crate) fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> u64 {
pub(crate) async fn dc_get_filebytes(context: &Context, path: impl AsRef<Path>) -> u64 {
let path_abs = dc_get_abs_path(context, &path);
match fs::metadata(&path_abs) {
match fs::metadata(&path_abs).await {
Ok(meta) => meta.len() as u64,
Err(_err) => 0,
}
}
pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
pub(crate) async fn dc_delete_file(context: &Context, path: impl AsRef<Path>) -> bool {
let path_abs = dc_get_abs_path(context, &path);
if !path_abs.exists() {
if !path_abs.exists().await {
return false;
}
if !path_abs.is_file() {
if !path_abs.is_file().await {
warn!(
context,
"refusing to delete non-file \"{}\".",
@@ -275,7 +273,7 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
}
let dpath = format!("{}", path.as_ref().to_string_lossy());
match fs::remove_file(path_abs) {
match fs::remove_file(path_abs).await {
Ok(_) => {
context.call_cb(Event::DeletedBlobFile(dpath));
true
@@ -287,13 +285,13 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
}
}
pub(crate) fn dc_copy_file(
pub(crate) async fn dc_copy_file(
context: &Context,
src_path: impl AsRef<std::path::Path>,
dest_path: impl AsRef<std::path::Path>,
src_path: impl AsRef<Path>,
dest_path: impl AsRef<Path>,
) -> bool {
let src_abs = dc_get_abs_path(context, &src_path);
let mut src_file = match fs::File::open(&src_abs) {
let mut src_file = match fs::File::open(&src_abs).await {
Ok(file) => file,
Err(err) => {
warn!(
@@ -311,6 +309,7 @@ pub(crate) fn dc_copy_file(
.create_new(true)
.write(true)
.open(&dest_abs)
.await
{
Ok(file) => file,
Err(err) => {
@@ -324,7 +323,7 @@ pub(crate) fn dc_copy_file(
}
};
match std::io::copy(&mut src_file, &mut dest_file) {
match io::copy(&mut src_file, &mut dest_file).await {
Ok(_) => true,
Err(err) => {
error!(
@@ -336,20 +335,20 @@ pub(crate) fn dc_copy_file(
);
{
// Attempt to remove the failed file, swallow errors resulting from that.
fs::remove_file(dest_abs).ok();
fs::remove_file(dest_abs).await.ok();
}
false
}
}
}
pub(crate) fn dc_create_folder(
pub(crate) async fn dc_create_folder(
context: &Context,
path: impl AsRef<std::path::Path>,
) -> Result<(), std::io::Error> {
path: impl AsRef<Path>,
) -> Result<(), io::Error> {
let path_abs = dc_get_abs_path(context, &path);
if !path_abs.exists() {
match fs::create_dir_all(path_abs) {
if !path_abs.exists().await {
match fs::create_dir_all(path_abs).await {
Ok(_) => Ok(()),
Err(err) => {
warn!(
@@ -367,13 +366,13 @@ pub(crate) fn dc_create_folder(
}
/// Write a the given content to provied file path.
pub(crate) fn dc_write_file(
pub(crate) async fn dc_write_file(
context: &Context,
path: impl AsRef<Path>,
buf: &[u8],
) -> Result<(), std::io::Error> {
) -> Result<(), io::Error> {
let path_abs = dc_get_abs_path(context, &path);
fs::write(&path_abs, buf).map_err(|err| {
fs::write(&path_abs, buf).await.map_err(|err| {
warn!(
context,
"Cannot write {} bytes to \"{}\": {}",
@@ -385,13 +384,10 @@ pub(crate) fn dc_write_file(
})
}
pub fn dc_read_file<P: AsRef<std::path::Path>>(
context: &Context,
path: P,
) -> Result<Vec<u8>, Error> {
pub async fn dc_read_file<P: AsRef<Path>>(context: &Context, path: P) -> Result<Vec<u8>, Error> {
let path_abs = dc_get_abs_path(context, &path);
match fs::read(&path_abs) {
match fs::read(&path_abs).await {
Ok(bytes) => Ok(bytes),
Err(err) => {
warn!(
@@ -405,13 +401,31 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
}
}
pub fn dc_open_file<P: AsRef<std::path::Path>>(
pub async fn dc_open_file<P: AsRef<Path>>(context: &Context, path: P) -> Result<fs::File, Error> {
let path_abs = dc_get_abs_path(context, &path);
match fs::File::open(&path_abs).await {
Ok(bytes) => Ok(bytes),
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty: {}",
path.as_ref().display(),
err
);
Err(err.into())
}
}
}
pub fn dc_open_file_std<P: AsRef<std::path::Path>>(
context: &Context,
path: P,
) -> Result<std::fs::File, Error> {
let path_abs = dc_get_abs_path(context, &path);
let p: PathBuf = path.as_ref().into();
let path_abs = dc_get_abs_path(context, p);
match fs::File::open(&path_abs) {
match std::fs::File::open(&path_abs) {
Ok(bytes) => Ok(bytes),
Err(err) => {
warn!(
@@ -425,7 +439,7 @@ pub fn dc_open_file<P: AsRef<std::path::Path>>(
}
}
pub(crate) fn dc_get_next_backup_path(
pub(crate) async fn dc_get_next_backup_path(
folder: impl AsRef<Path>,
backup_time: i64,
) -> Result<PathBuf, Error> {
@@ -438,7 +452,7 @@ pub(crate) fn dc_get_next_backup_path(
for i in 0..64 {
let mut path = folder.clone();
path.push(format!("{}-{}.bak", stem, i));
if !path.exists() {
if !path.exists().await {
return Ok(path);
}
}
@@ -715,27 +729,31 @@ mod tests {
async fn test_file_handling() {
let t = dummy_context().await;
let context = &t.ctx;
let dc_file_exist = |ctx: &Context, fname: &str| {
ctx.get_blobdir()
.join(Path::new(fname).file_name().unwrap())
.exists()
};
assert!(!dc_delete_file(context, "$BLOBDIR/lkqwjelqkwlje"));
if dc_file_exist(context, "$BLOBDIR/foobar")
|| dc_file_exist(context, "$BLOBDIR/dada")
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
{
dc_delete_file(context, "$BLOBDIR/foobar");
dc_delete_file(context, "$BLOBDIR/dada");
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
dc_delete_file(context, "$BLOBDIR/foobar-folder");
macro_rules! dc_file_exist {
($ctx:expr, $fname:expr) => {
$ctx.get_blobdir()
.join(Path::new($fname).file_name().unwrap())
.exists()
};
}
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content").is_ok());
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar"), 7);
assert!(!dc_delete_file(context, "$BLOBDIR/lkqwjelqkwlje").await);
if dc_file_exist!(context, "$BLOBDIR/foobar").await
|| dc_file_exist!(context, "$BLOBDIR/dada").await
|| dc_file_exist!(context, "$BLOBDIR/foobar.dadada").await
|| dc_file_exist!(context, "$BLOBDIR/foobar-folder").await
{
dc_delete_file(context, "$BLOBDIR/foobar").await;
dc_delete_file(context, "$BLOBDIR/dada").await;
dc_delete_file(context, "$BLOBDIR/foobar.dadada").await;
dc_delete_file(context, "$BLOBDIR/foobar-folder").await;
}
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content")
.await
.is_ok());
assert!(dc_file_exist!(context, "$BLOBDIR/foobar").await);
assert!(!dc_file_exist!(context, "$BLOBDIR/foobarx").await);
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar").await, 7);
let abs_path = context
.get_blobdir()
@@ -743,31 +761,33 @@ mod tests {
.to_string_lossy()
.to_string();
assert!(dc_file_exist(context, &abs_path));
assert!(dc_file_exist!(context, &abs_path).await);
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada").await);
// attempting to copy a second time should fail
assert!(!dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert!(!dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada").await);
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada").await, 7);
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();
let buf = dc_read_file(context, "$BLOBDIR/dada").await.unwrap();
assert_eq!(buf.len(), 7);
assert_eq!(&buf, b"content");
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder").is_ok());
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
assert!(dc_delete_file(context, "$BLOBDIR/foobar").await);
assert!(dc_delete_file(context, "$BLOBDIR/dada").await);
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder")
.await
.is_ok());
assert!(dc_file_exist!(context, "$BLOBDIR/foobar-folder").await);
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder").await);
let fn0 = "$BLOBDIR/data.data";
assert!(dc_write_file(context, &fn0, b"content").is_ok());
assert!(dc_write_file(context, &fn0, b"content").await.is_ok());
assert!(dc_delete_file(context, &fn0));
assert!(!dc_file_exist(context, &fn0));
assert!(dc_delete_file(context, &fn0).await);
assert!(!dc_file_exist!(context, &fn0).await);
}
#[test]

View File

@@ -1,6 +1,6 @@
//! # Events specification
use std::path::PathBuf;
use async_std::path::PathBuf;
use strum::EnumProperty;

View File

@@ -1,8 +1,9 @@
//! # Import/export module
use core::cmp::{max, min};
use std::path::Path;
use std::cmp::{max, min};
use async_std::path::{Path, PathBuf};
use async_std::prelude::*;
use num_traits::FromPrimitive;
use rand::{thread_rng, Rng};
@@ -83,10 +84,10 @@ pub async fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<P
/// Returns the filename of the backup found (otherwise an error)
pub async fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
let dir_name = dir_name.as_ref();
let dir_iter = std::fs::read_dir(dir_name)?;
let mut dir_iter = async_std::fs::read_dir(dir_name).await?;
let mut newest_backup_time = 0;
let mut newest_backup_path: Option<std::path::PathBuf> = None;
for dirent in dir_iter {
let mut newest_backup_path: Option<PathBuf> = None;
while let Some(dirent) = dir_iter.next().await {
if let Ok(dirent) = dirent {
let path = dirent.path();
let name = dirent.file_name();
@@ -133,7 +134,8 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
context,
"autocrypt-setup-message.html",
setup_file_content.as_bytes(),
)?;
)
.await?;
let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF).await?;
msg = Message::default();
@@ -270,7 +272,7 @@ pub async fn continue_key_transfer(
);
if let Some(filename) = msg.get_file(context) {
let file = dc_open_file(context, filename)?;
let file = dc_open_file_std(context, filename)?;
let sc = normalize_setup_code(setup_code);
let armored_key = decrypt_setup_file(context, &sc, file)?;
set_self_key(context, &armored_key, true, true).await?;
@@ -383,7 +385,7 @@ pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> {
context.free_ongoing().await;
bail!("Cannot create private key or private key not available.");
} else {
dc_create_folder(context, &param)?;
dc_create_folder(context, &param).await?;
}
}
let path = Path::new(param);
@@ -424,14 +426,14 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
"Cannot import backups to accounts in use."
);
context.sql.close(&context).await;
dc_delete_file(context, context.get_dbfile());
dc_delete_file(context, context.get_dbfile()).await;
ensure!(
!context.get_dbfile().exists(),
!context.get_dbfile().exists().await,
"Cannot delete old database."
);
ensure!(
dc_copy_file(context, backup_to_import.as_ref(), context.get_dbfile()),
dc_copy_file(context, backup_to_import.as_ref(), context.get_dbfile()).await,
"could not copy file"
);
/* error already logged */
@@ -494,7 +496,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
}
let path_filename = context.get_blobdir().join(file_name);
dc_write_file(context, &path_filename, &file_blob)?;
dc_write_file(context, &path_filename, &file_blob).await?;
}
if all_files_extracted {
@@ -520,7 +522,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete.
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
let now = time();
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
let dest_path_filename = dc_get_next_backup_path(dir, now).await?;
let dest_path_string = dest_path_filename.to_string_lossy().to_string();
sql::housekeeping(context).await;
@@ -535,7 +537,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
context.get_dbfile().display(),
dest_path_filename.display(),
);
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename).await;
context
.sql
.open(&context, &context.get_dbfile(), false)
@@ -556,7 +558,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
);
let res = match add_files_to_export(context, &dest_sql).await {
Err(err) => {
dc_delete_file(context, &dest_path_filename);
dc_delete_file(context, &dest_path_filename).await;
error!(context, "backup failed: {}", err);
Err(err)
}
@@ -586,17 +588,17 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
// copy all files from BLOBDIR into backup-db
let mut total_files_cnt = 0;
let dir = context.get_blobdir();
let dir_handle = std::fs::read_dir(&dir)?;
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
let dir_handle = async_std::fs::read_dir(&dir).await?;
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count().await;
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
sql.with_conn_async(|conn| async move {
// scan directory, pass 2: copy files
let dir_handle = std::fs::read_dir(&dir)?;
let mut dir_handle = async_std::fs::read_dir(&dir).await?;
let mut processed_files_cnt = 0;
for entry in dir_handle {
while let Some(entry) = dir_handle.next().await {
let entry = entry?;
if context.shall_stop_ongoing().await {
return Ok(());
@@ -612,7 +614,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
}
info!(context, "EXPORT: copying filename={}", name);
let curr_path_filename = context.get_blobdir().join(entry.file_name());
if let Ok(buf) = dc_read_file(context, &curr_path_filename) {
if let Ok(buf) = dc_read_file(context, &curr_path_filename).await {
if buf.is_empty() {
continue;
}
@@ -644,8 +646,8 @@ async fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
let mut imported_cnt = 0;
let dir_name = dir.as_ref().to_string_lossy();
let dir_handle = std::fs::read_dir(&dir)?;
for entry in dir_handle {
let mut dir_handle = async_std::fs::read_dir(&dir).await?;
while let Some(entry) = dir_handle.next().await {
let entry_fn = entry?.file_name();
let name_f = entry_fn.to_string_lossy();
let path_plus_name = dir.as_ref().join(&entry_fn);
@@ -665,7 +667,7 @@ async fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
continue;
}
}
match dc_read_file(context, &path_plus_name) {
match dc_read_file(context, &path_plus_name).await {
Ok(buf) => {
let armored = std::string::String::from_utf8_lossy(&buf);
if let Err(err) = set_self_key(context, &armored, set_default, false).await {
@@ -688,7 +690,7 @@ async fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let mut export_errors = 0;
context
let keys = context
.sql
.query_map(
"SELECT id, public_key, private_key, is_default FROM keypairs;",
@@ -704,30 +706,36 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
Ok((id, public_key, private_key, is_default))
},
|keys| {
for key_pair in keys {
let (id, public_key, private_key, is_default) = key_pair?;
let id = Some(id).filter(|_| is_default != 0);
if let Some(key) = public_key {
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
export_errors += 1;
}
} else {
export_errors += 1;
}
if let Some(key) = private_key {
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
export_errors += 1;
}
} else {
export_errors += 1;
}
}
Ok(())
keys.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await?;
for (id, public_key, private_key, is_default) in keys {
let id = Some(id).filter(|_| is_default != 0);
if let Some(key) = public_key {
if export_key_to_asc_file(context, &dir, id, &key)
.await
.is_err()
{
export_errors += 1;
}
} else {
export_errors += 1;
}
if let Some(key) = private_key {
if export_key_to_asc_file(context, &dir, id, &key)
.await
.is_err()
{
export_errors += 1;
}
} else {
export_errors += 1;
}
}
ensure!(export_errors == 0, "errors while exporting keys");
Ok(())
}
@@ -735,7 +743,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
/*******************************************************************************
* Classic key export
******************************************************************************/
fn export_key_to_asc_file(
async fn export_key_to_asc_file(
context: &Context,
dir: impl AsRef<Path>,
id: Option<i64>,
@@ -748,9 +756,9 @@ fn export_key_to_asc_file(
dir.as_ref().join(format!("{}-key-{}.asc", kind, &id))
};
info!(context, "Exporting key {}", file_name.display());
dc_delete_file(context, &file_name);
dc_delete_file(context, &file_name).await;
let res = key.write_asc_to_file(&file_name, context);
let res = key.write_asc_to_file(&file_name, context).await;
if res.is_err() {
error!(context, "Cannot write key to {}", file_name.display());
} else {
@@ -819,10 +827,12 @@ mod tests {
let context = dummy_context().await;
let key = Key::from(alice_keypair().public);
let blobdir = "$BLOBDIR";
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok());
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key)
.await
.is_ok());
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
let filename = format!("{}/public-key-default.asc", blobdir);
let bytes = std::fs::read(&filename).unwrap();
let bytes = async_std::fs::read(&filename).await.unwrap();
assert_eq!(bytes, key.to_asc(None).into_bytes());
}

View File

@@ -261,7 +261,7 @@ impl Job {
.get_path(Param::File, context)
.map_err(|_| format_err!("Can't get filename")))
.ok_or_else(|| format_err!("Can't get filename")));
let body = job_try!(dc_read_file(context, &filename));
let body = job_try!(dc_read_file(context, &filename).await);
let recipients = job_try!(self.param.get(Param::Recipients).ok_or_else(|| {
warn!(context, "Missing recipients for job {}", self.job_id);
format_err!("Missing recipients")
@@ -298,7 +298,7 @@ impl Job {
set_delivered(context, MsgId::new(foreign_id)).await;
}
// now also delete the generated file
dc_delete_file(context, filename);
dc_delete_file(context, filename).await;
Ok(())
}
})
@@ -1086,7 +1086,7 @@ async fn add_smtp_job(
ensure!(!recipients.is_empty(), "no recipients for smtp job set");
let mut param = Params::new();
let bytes = &rendered_msg.message;
let blob = BlobObject::create(context, &rendered_msg.rfc724_mid, bytes)?;
let blob = BlobObject::create(context, &rendered_msg.rfc724_mid, bytes).await?;
let recipients = recipients.join("\x1e");
param.set(Param::File, blob.as_name());

View File

@@ -2,8 +2,8 @@
use std::collections::BTreeMap;
use std::io::Cursor;
use std::path::Path;
use async_std::path::Path;
use pgp::composed::Deserializable;
use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait};
@@ -268,14 +268,14 @@ impl Key {
.expect("failed to serialize key")
}
pub fn write_asc_to_file(
pub async fn write_asc_to_file(
&self,
file: impl AsRef<Path>,
context: &Context,
) -> std::io::Result<()> {
let file_content = self.to_asc(None).into_bytes();
let res = dc_write_file(context, &file, &file_content);
let res = dc_write_file(context, &file, &file_content).await;
if res.is_err() {
error!(context, "Cannot write key to {}", file.as_ref().display());
}

View File

@@ -1,7 +1,6 @@
//! # Messages and their identifiers
use std::path::{Path, PathBuf};
use async_std::path::{Path, PathBuf};
use deltachat_derive::{FromSql, ToSql};
use failure::Fail;
use lazy_static::lazy_static;
@@ -349,7 +348,7 @@ impl Message {
self.param.set_int(Param::Width, 0);
self.param.set_int(Param::Height, 0);
if let Ok(buf) = dc_read_file(context, path_and_filename) {
if let Ok(buf) = dc_read_file(context, path_and_filename).await {
if let Ok((width, height)) = dc_get_filemeta(&buf) {
self.param.set_int(Param::Width, width as i32);
self.param.set_int(Param::Height, height as i32);
@@ -450,12 +449,12 @@ impl Message {
.map(|name| name.to_string_lossy().to_string())
}
pub fn get_filebytes(&self, context: &Context) -> u64 {
self.param
.get_path(Param::File, context)
.unwrap_or(None)
.map(|path| dc_get_filebytes(context, &path))
.unwrap_or_default()
pub async fn get_filebytes(&self, context: &Context) -> u64 {
match self.param.get_path(Param::File, context) {
Ok(Some(path)) => dc_get_filebytes(context, &path).await,
Ok(None) => 0,
Err(_) => 0,
}
}
pub fn get_width(&self) -> i32 {
@@ -558,13 +557,13 @@ impl Message {
self.param.get_cmd() == SystemMessage::AutocryptSetupMessage
}
pub fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
pub async fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
if !self.is_setupmessage() {
return None;
}
if let Some(filename) = self.get_file(context) {
if let Ok(ref buf) = dc_read_file(context, filename) {
if let Ok(ref buf) = dc_read_file(context, filename).await {
if let Ok((typ, headers, _)) = split_armored_data(buf) {
if typ == pgp::armor::BlockType::Message {
return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
@@ -909,7 +908,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
}
if let Some(path) = msg.get_file(context) {
let bytes = dc_get_filebytes(context, &path);
let bytes = dc_get_filebytes(context, &path).await;
ret += &format!("\nFile: {}, {}, bytes\n", path.display(), bytes);
}

View File

@@ -808,7 +808,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
meta.viewtype = Viewtype::Image;
meta.param.set(Param::File, grpimage);
let (mail, filename_as_sent) = build_body_file(context, &meta, "group-image")?;
let (mail, filename_as_sent) = build_body_file(context, &meta, "group-image").await?;
meta_part = Some(mail);
protected_headers.push(Header::new("Chat-Group-Avatar".into(), filename_as_sent));
}
@@ -879,13 +879,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
// add attachment part
if chat::msgtype_has_file(self.msg.viewtype) {
if !is_file_size_okay(context, &self.msg) {
if !is_file_size_okay(context, &self.msg).await {
bail!(
"Message exceeds the recommended {} MB.",
RECOMMENDED_FILE_SIZE / 1_000_000,
);
} else {
let (file_part, _) = build_body_file(context, &self.msg, "")?;
let (file_part, _) = build_body_file(context, &self.msg, "").await?;
parts.push(file_part);
}
}
@@ -1035,14 +1035,15 @@ fn wrapped_base64_encode(buf: &[u8]) -> String {
.join("\r\n")
}
fn build_body_file(
async fn build_body_file(
context: &Context,
msg: &Message,
base_name: &str,
) -> Result<(PartBuilder, String), Error> {
let blob = msg
.param
.get_blob(Param::File, context, true)?
.get_blob(Param::File, context, true)
.await?
.ok_or_else(|| format_err!("msg has no filename"))?;
let suffix = blob.suffix().unwrap_or("dat");
@@ -1138,10 +1139,10 @@ fn recipients_contain_addr(recipients: &[(String, String)], addr: &str) -> bool
.any(|(_, cur)| cur.to_lowercase() == addr_lc)
}
fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
async fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
match msg.param.get_path(Param::File, context).unwrap_or(None) {
Some(path) => {
let bytes = dc_get_filebytes(context, &path);
let bytes = dc_get_filebytes(context, &path).await;
bytes <= UPPER_LIMIT_FILE_SIZE
}
None => false,

View File

@@ -463,7 +463,7 @@ impl MimeMessage {
self.parse_mime_recursive(context, &mail).await
}
MimeS::Single => self.add_single_part_if_known(context, mail),
MimeS::Single => self.add_single_part_if_known(context, mail).await,
}
}
.boxed()
@@ -578,7 +578,7 @@ impl MimeMessage {
Ok(any_part_added)
}
fn add_single_part_if_known(
async fn add_single_part_if_known(
&mut self,
context: &Context,
mail: &mailparse::ParsedMail<'_>,
@@ -600,7 +600,8 @@ impl MimeMessage {
&raw_mime,
&mail.get_body_raw()?,
&filename,
);
)
.await;
}
None => {
match mime_type.type_() {
@@ -652,7 +653,7 @@ impl MimeMessage {
Ok(self.parts.len() > old_part_count)
}
fn do_add_single_file_part(
async fn do_add_single_file_part(
&mut self,
context: &Context,
msg_type: Viewtype,
@@ -685,7 +686,7 @@ impl MimeMessage {
/* we have a regular file attachment,
write decoded data to new blob object */
let blob = match BlobObject::create(context, filename, decoded_data) {
let blob = match BlobObject::create(context, filename, decoded_data).await {
Ok(blob) => blob,
Err(err) => {
error!(

View File

@@ -1,8 +1,8 @@
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
use std::str;
use async_std::path::PathBuf;
use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize};
@@ -281,7 +281,7 @@ impl Params {
/// created without copying if the path already referes to a valid
/// blob. If so a [BlobObject] will be returned regardless of the
/// `create` argument.
pub fn get_blob<'a>(
pub async fn get_blob<'a>(
&self,
key: Param,
context: &'a Context,
@@ -294,7 +294,7 @@ impl Params {
let file = ParamsFile::from_param(context, val)?;
let blob = match file {
ParamsFile::FsPath(path) => match create {
true => BlobObject::new_from_path(context, path)?,
true => BlobObject::new_from_path(context, path).await?,
false => BlobObject::from_path(context, path)?,
},
ParamsFile::Blob(blob) => blob,
@@ -368,8 +368,8 @@ impl<'a> ParamsFile<'a> {
mod tests {
use super::*;
use std::fs;
use std::path::Path;
use async_std::fs;
use async_std::path::Path;
use crate::test_utils::*;
@@ -446,20 +446,25 @@ mod tests {
p.set(Param::File, fname.to_str().unwrap());
let file = p.get_file(Param::File, &t.ctx).unwrap().unwrap();
assert_eq!(file, ParamsFile::FsPath(fname.clone()));
assert_eq!(file, ParamsFile::FsPath(fname.clone().into()));
let path = p.get_path(Param::File, &t.ctx).unwrap().unwrap();
let path: PathBuf = p.get_path(Param::File, &t.ctx).unwrap().unwrap();
let fname: PathBuf = fname.into();
assert_eq!(path, fname);
// Blob does not exist yet, expect BlobError.
let err = p.get_blob(Param::File, &t.ctx, false).unwrap_err();
let err = p.get_blob(Param::File, &t.ctx, false).await.unwrap_err();
match err {
BlobError::WrongBlobdir { .. } => (),
_ => panic!("wrong error type/variant: {:?}", err),
}
fs::write(fname, b"boo").unwrap();
let blob = p.get_blob(Param::File, &t.ctx, true).unwrap().unwrap();
fs::write(fname, b"boo").await.unwrap();
let blob = p
.get_blob(Param::File, &t.ctx, true)
.await
.unwrap()
.unwrap();
assert_eq!(
blob,
BlobObject::from_name(&t.ctx, "foo".to_string()).unwrap()
@@ -468,7 +473,11 @@ mod tests {
// Blob in blobdir, expect blob.
let bar = t.ctx.get_blobdir().join("bar");
p.set(Param::File, bar.to_str().unwrap());
let blob = p.get_blob(Param::File, &t.ctx, false).unwrap().unwrap();
let blob = p
.get_blob(Param::File, &t.ctx, false)
.await
.unwrap()
.unwrap();
assert_eq!(
blob,
BlobObject::from_name(&t.ctx, "bar".to_string()).unwrap()
@@ -477,6 +486,10 @@ mod tests {
p.remove(Param::File);
assert!(p.get_file(Param::File, &t.ctx).unwrap().is_none());
assert!(p.get_path(Param::File, &t.ctx).unwrap().is_none());
assert!(p.get_blob(Param::File, &t.ctx, false).unwrap().is_none());
assert!(p
.get_blob(Param::File, &t.ctx, false)
.await
.unwrap()
.is_none());
}
}

View File

@@ -96,7 +96,7 @@ impl Sql {
}
// return true on success, false on failure
pub async fn open(&self, context: &Context, dbfile: &Path, readonly: bool) -> bool {
pub async fn open<T: AsRef<Path>>(&self, context: &Context, dbfile: T, readonly: bool) -> bool {
match open(context, self, dbfile, readonly).await {
Ok(_) => true,
Err(crate::error::Error::SqlError(Error::SqlAlreadyOpen)) => false,
@@ -607,7 +607,7 @@ pub async fn housekeeping(context: &Context) {
entry.file_name()
);
let path = entry.path();
dc_delete_file(context, path);
dc_delete_file(context, path).await;
}
}
Err(err) => {

View File

@@ -376,7 +376,7 @@ impl Context {
chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg)).await?;
let image = include_bytes!("../assets/welcome-image.jpg");
let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image)?;
let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image).await?;
let mut msg = Message::new(Viewtype::Image);
msg.param.set(Param::File, blob.as_name());
chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg)).await?;

View File

@@ -32,7 +32,9 @@ pub(crate) async fn test_context(callback: Option<Box<ContextCallback>>) -> Test
Some(cb) => cb,
None => Box::new(|_, _| ()),
};
let ctx = Context::new(cb, "FakeOs".into(), dbfile).await.unwrap();
let ctx = Context::new(cb, "FakeOs".into(), dbfile.into())
.await
.unwrap();
TestContext { ctx, dir }
}