From 7140898db99e153adffed899dac9bbefe8afa30d Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 14 Mar 2020 16:26:15 +0100 Subject: [PATCH] async file io --- examples/simple.rs | 2 +- src/blob.rs | 137 +++++++++++++++++++++++--------------- src/chat.rs | 32 +++++---- src/config.rs | 6 +- src/contact.rs | 3 +- src/context.rs | 32 +++++---- src/dc_tools.rs | 160 +++++++++++++++++++++++++-------------------- src/events.rs | 2 +- src/imex.rs | 108 ++++++++++++++++-------------- src/job.rs | 6 +- src/key.rs | 6 +- src/message.rs | 23 ++++--- src/mimefactory.rs | 15 +++-- src/mimeparser.rs | 11 ++-- src/param.rs | 37 +++++++---- src/sql.rs | 4 +- src/stock.rs | 2 +- src/test_utils.rs | 4 +- tests/stress.rs | 2 +- 19 files changed, 340 insertions(+), 252 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 2b2c6e2a4..c4a5aa827 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -36,7 +36,7 @@ async fn main() { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); println!("creating database {:?}", dbfile); - let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile) + let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile.into()) .await .expect("Failed to create context"); let running = Arc::new(RwLock::new(true)); diff --git a/src/blob.rs b/src/blob.rs index c43cff569..438fea6dd 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -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, data: &[u8], ) -> std::result::Result, 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, ) -> std::result::Result, 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, - ) -> std::result::Result { + ) -> std::result::Result, 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" diff --git a/src/chat.rs b/src/chat.rs index 4b4868b7c..97dbf83e3 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -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 { 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( diff --git a/src/config.rs b/src/config.rs index 187d26735..66197c970 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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())); diff --git a/src/contact.rs b/src/contact.rs index 2453c1f91..af8a5855e 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1,7 +1,6 @@ //! Contacts module -use std::path::PathBuf; - +use async_std::path::PathBuf; use deltachat_derive::*; use itertools::Itertools; use rusqlite; diff --git a/src/context.rs b/src/context.rs index 7a7408f63..cba9d15f4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -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 { 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()); } diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 1144a2b57..04eeed66c 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -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>( - context: &Context, - path: P, -) -> std::path::PathBuf { - let p: &std::path::Path = path.as_ref(); +pub(crate) fn dc_get_abs_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>( } } -pub(crate) fn dc_get_filebytes(context: &Context, path: impl AsRef) -> u64 { +pub(crate) async fn dc_get_filebytes(context: &Context, path: impl AsRef) -> 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) -> bool { +pub(crate) async fn dc_delete_file(context: &Context, path: impl AsRef) -> 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 { context.call_cb(Event::DeletedBlobFile(dpath)); true @@ -287,13 +285,13 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef, - dest_path: impl AsRef, + src_path: impl AsRef, + dest_path: impl AsRef, ) -> 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, -) -> Result<(), std::io::Error> { + path: impl AsRef, +) -> 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, 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>( - context: &Context, - path: P, -) -> Result, Error> { +pub async fn dc_read_file>(context: &Context, path: P) -> Result, 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>( } } -pub fn dc_open_file>( +pub async fn dc_open_file>(context: &Context, path: P) -> Result { + 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>( context: &Context, path: P, ) -> Result { - 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>( } } -pub(crate) fn dc_get_next_backup_path( +pub(crate) async fn dc_get_next_backup_path( folder: impl AsRef, backup_time: i64, ) -> Result { @@ -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] diff --git a/src/events.rs b/src/events.rs index 37a0a4bc4..4d7e17ab9 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,6 +1,6 @@ //! # Events specification -use std::path::PathBuf; +use async_std::path::PathBuf; use strum::EnumProperty; diff --git a/src/imex.rs b/src/imex.rs index 3ca1a2a89..c5f82b97e 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -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) -> Result { 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 = None; - for dirent in dir_iter { + let mut newest_backup_path: Option = 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 { 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, ¶m)?; + dc_create_folder(context, ¶m).await?; } } let path = Path::new(param); @@ -424,14 +426,14 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> "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) -> } 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> Result<() async fn export_self_keys(context: &Context, dir: impl AsRef) -> 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) -> 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::, _>>() + .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) -> Result<() /******************************************************************************* * Classic key export ******************************************************************************/ -fn export_key_to_asc_file( +async fn export_key_to_asc_file( context: &Context, dir: impl AsRef, id: Option, @@ -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()); } diff --git a/src/job.rs b/src/job.rs index 1274ce946..edc9c2342 100644 --- a/src/job.rs +++ b/src/job.rs @@ -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()); diff --git a/src/key.rs b/src/key.rs index 0ab6bcc23..7e1800566 100644 --- a/src/key.rs +++ b/src/key.rs @@ -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, 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()); } diff --git a/src/message.rs b/src/message.rs index e1e874667..9f9ad4631 100644 --- a/src/message.rs +++ b/src/message.rs @@ -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 { + pub async fn get_setupcodebegin(&self, context: &Context) -> Option { 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); } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index b831f7c7b..a541b5997 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -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, diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 0836ff6bf..707d7afeb 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -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!( diff --git a/src/param.rs b/src/param.rs index ccd02549f..2b64cd347 100644 --- a/src/param.rs +++ b/src/param.rs @@ -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()); } } diff --git a/src/sql.rs b/src/sql.rs index b32d6dfbb..d21f18886 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -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>(&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) => { diff --git a/src/stock.rs b/src/stock.rs index 0e30fc794..6cb6bf3e1 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -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?; diff --git a/src/test_utils.rs b/src/test_utils.rs index f384df70f..86c61adf2 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -32,7 +32,9 @@ pub(crate) async fn test_context(callback: Option>) -> 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 } } diff --git a/tests/stress.rs b/tests/stress.rs index e4ec5e873..f44aa1e19 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -104,7 +104,7 @@ struct TestContext { async fn create_test_context() -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile) + let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile.into()) .await .unwrap(); TestContext { ctx, dir }