mirror of
https://github.com/chatmail/core.git
synced 2026-04-26 09:56:35 +03:00
async file io
This commit is contained in:
137
src/blob.rs
137
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<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"
|
||||
|
||||
32
src/chat.rs
32
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<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(
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Contacts module
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use async_std::path::PathBuf;
|
||||
use deltachat_derive::*;
|
||||
use itertools::Itertools;
|
||||
use rusqlite;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
160
src/dc_tools.rs
160
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<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]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! # Events specification
|
||||
|
||||
use std::path::PathBuf;
|
||||
use async_std::path::PathBuf;
|
||||
|
||||
use strum::EnumProperty;
|
||||
|
||||
|
||||
108
src/imex.rs
108
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<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, ¶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<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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!(
|
||||
|
||||
37
src/param.rs
37
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user