Skip to content

Commit e635108

Browse files
committed
api: Add dc_msg_save_file() which saves file copy at the provided path (#4309)
... and fails if file already exists. The UI should open the file saving dialog, defaulting to Downloads and original filename, when asked to save the file. After confirmation it should call dc_msg_save_file().
1 parent 61995e0 commit e635108

File tree

7 files changed

+93
-22
lines changed

7 files changed

+93
-22
lines changed

deltachat-ffi/deltachat.h

+13
Original file line numberDiff line numberDiff line change
@@ -4092,6 +4092,19 @@ char* dc_msg_get_subject (const dc_msg_t* msg);
40924092
char* dc_msg_get_file (const dc_msg_t* msg);
40934093

40944094

4095+
/**
4096+
* Save file copy at the user-provided path.
4097+
*
4098+
* Fails if file already exists at the provided path.
4099+
*
4100+
* @memberof dc_msg_t
4101+
* @param msg The message object.
4102+
* @param path Destination file path with filename and extension.
4103+
* @return 0 on failure, 1 on success.
4104+
*/
4105+
int dc_msg_save_file (const dc_msg_t* msg, const char* path);
4106+
4107+
40954108
/**
40964109
* Get an original attachment filename, with extension but without the path. To get the full path,
40974110
* use dc_msg_get_file().

deltachat-ffi/src/lib.rs

+28
Original file line numberDiff line numberDiff line change
@@ -3373,6 +3373,34 @@ pub unsafe extern "C" fn dc_msg_get_file(msg: *mut dc_msg_t) -> *mut libc::c_cha
33733373
.unwrap_or_else(|| "".strdup())
33743374
}
33753375

3376+
#[no_mangle]
3377+
pub unsafe extern "C" fn dc_msg_save_file(
3378+
msg: *mut dc_msg_t,
3379+
path: *const libc::c_char,
3380+
) -> libc::c_int {
3381+
if msg.is_null() || path.is_null() {
3382+
eprintln!("ignoring careless call to dc_msg_save_file()");
3383+
return 0;
3384+
}
3385+
let ffi_msg = &*msg;
3386+
let ctx = &*ffi_msg.context;
3387+
let path = to_string_lossy(path);
3388+
let r = block_on(
3389+
ffi_msg
3390+
.message
3391+
.save_file(ctx, &std::path::PathBuf::from(path)),
3392+
);
3393+
match r {
3394+
Ok(()) => 1,
3395+
Err(_) => {
3396+
r.context("Failed to save file from message")
3397+
.log_err(ctx)
3398+
.unwrap_or_default();
3399+
0
3400+
}
3401+
}
3402+
}
3403+
33763404
#[no_mangle]
33773405
pub unsafe extern "C" fn dc_msg_get_filename(msg: *mut dc_msg_t) -> *mut libc::c_char {
33783406
if msg.is_null() {

deltachat-jsonrpc/src/api.rs

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::collections::BTreeMap;
2+
use std::path::Path;
23
use std::sync::Arc;
34
use std::{collections::HashMap, str::FromStr};
45

@@ -1931,19 +1932,21 @@ impl CommandApi {
19311932
);
19321933
let destination_path = account_folder.join("stickers").join(collection);
19331934
fs::create_dir_all(&destination_path).await?;
1934-
let file = message.get_file(&ctx).context("no file")?;
1935-
fs::copy(
1936-
&file,
1937-
destination_path.join(format!(
1938-
"{}.{}",
1939-
msg_id,
1940-
file.extension()
1941-
.unwrap_or_default()
1942-
.to_str()
1943-
.unwrap_or_default()
1944-
)),
1945-
)
1946-
.await?;
1935+
let file = message.get_filename().context("no file?")?;
1936+
message
1937+
.save_file(
1938+
&ctx,
1939+
&destination_path.join(format!(
1940+
"{}.{}",
1941+
msg_id,
1942+
Path::new(&file)
1943+
.extension()
1944+
.unwrap_or_default()
1945+
.to_str()
1946+
.unwrap_or_default()
1947+
)),
1948+
)
1949+
.await?;
19471950
Ok(())
19481951
}
19491952

src/blob.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -1268,23 +1268,26 @@ mod tests {
12681268
let alice_msg = alice.get_last_msg().await;
12691269
assert_eq!(alice_msg.get_width() as u32, compressed_width);
12701270
assert_eq!(alice_msg.get_height() as u32, compressed_height);
1271-
check_image_size(
1272-
alice_msg.get_file(&alice).unwrap(),
1273-
compressed_width,
1274-
compressed_height,
1275-
);
1271+
let file_saved = alice
1272+
.get_blobdir()
1273+
.join("saved-".to_string() + &alice_msg.get_filename().unwrap());
1274+
alice_msg.save_file(&alice, &file_saved).await?;
1275+
check_image_size(file_saved, compressed_width, compressed_height);
12761276

12771277
let bob_msg = bob.recv_msg(&sent).await;
12781278
assert_eq!(bob_msg.get_viewtype(), Viewtype::Image);
12791279
assert_eq!(bob_msg.get_width() as u32, compressed_width);
12801280
assert_eq!(bob_msg.get_height() as u32, compressed_height);
1281-
let file = bob_msg.get_file(&bob).unwrap();
1281+
let file_saved = bob
1282+
.get_blobdir()
1283+
.join("saved-".to_string() + &bob_msg.get_filename().unwrap());
1284+
bob_msg.save_file(&bob, &file_saved).await?;
12821285

1283-
let blob = BlobObject::new_from_path(&bob, &file).await?;
1286+
let blob = BlobObject::new_from_path(&bob, &file_saved).await?;
12841287
let (_, exif) = blob.metadata()?;
12851288
assert!(exif.is_none());
12861289

1287-
let img = check_image_size(file, compressed_width, compressed_height);
1290+
let img = check_image_size(file_saved, compressed_width, compressed_height);
12881291
Ok(img)
12891292
}
12901293

src/imex/transfer.rs

+6
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,12 @@ mod tests {
656656
let text = fs::read_to_string(&path).await.unwrap();
657657
assert_eq!(text, "i am attachment");
658658

659+
let path = path.with_file_name("saved.txt");
660+
msg.save_file(&ctx1, &path).await.unwrap();
661+
let text = fs::read_to_string(&path).await.unwrap();
662+
assert_eq!(text, "i am attachment");
663+
assert!(msg.save_file(&ctx1, &path).await.is_err());
664+
659665
// Check that both received the ImexProgress events.
660666
ctx0.evtracker
661667
.get_matching(|ev| matches!(ev, EventType::ImexProgress(1000)))

src/message.rs

+14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
66
use anyhow::{ensure, format_err, Context as _, Result};
77
use deltachat_derive::{FromSql, ToSql};
88
use serde::{Deserialize, Serialize};
9+
use tokio::{fs, io};
910

1011
use crate::blob::BlobObject;
1112
use crate::chat::{Chat, ChatId};
@@ -579,6 +580,19 @@ impl Message {
579580
self.param.get_path(Param::File, context).unwrap_or(None)
580581
}
581582

583+
/// Save file copy at the user-provided path.
584+
pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
585+
let path_src = self.get_file(context).context("No file")?;
586+
let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
587+
let mut dst = fs::OpenOptions::new()
588+
.write(true)
589+
.create_new(true)
590+
.open(path)
591+
.await?;
592+
io::copy(&mut src, &mut dst).await?;
593+
Ok(())
594+
}
595+
582596
/// If message is an image or gif, set Param::Width and Param::Height
583597
pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
584598
if self.viewtype.has_file() {

src/receive_imf/tests.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -2965,11 +2965,15 @@ async fn test_long_and_duplicated_filenames() -> Result<()> {
29652965
let resulting_filename = msg.get_filename().unwrap();
29662966
assert_eq!(resulting_filename, filename);
29672967
let path = msg.get_file(t).unwrap();
2968+
let path2 = path.with_file_name("saved.txt");
2969+
msg.save_file(t, &path2).await.unwrap();
29682970
assert!(
29692971
path.to_str().unwrap().ends_with(".tar.gz"),
29702972
"path {path:?} doesn't end with .tar.gz"
29712973
);
2972-
assert_eq!(fs::read_to_string(path).await.unwrap(), content);
2974+
assert_eq!(fs::read_to_string(&path).await.unwrap(), content);
2975+
assert_eq!(fs::read_to_string(&path2).await.unwrap(), content);
2976+
fs::remove_file(path2).await.unwrap();
29732977
}
29742978
check_message(&msg_alice, &alice, filename_sent, &content).await;
29752979
check_message(&msg_bob, &bob, filename_sent, &content).await;

0 commit comments

Comments
 (0)