Skip to content

Commit ceca5bf

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 7443c91 commit ceca5bf

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
@@ -4006,6 +4006,19 @@ char* dc_msg_get_subject (const dc_msg_t* msg);
40064006
char* dc_msg_get_file (const dc_msg_t* msg);
40074007

40084008

4009+
/**
4010+
* Save file copy at the user-provided path.
4011+
*
4012+
* Fails if file already exists at the provided path.
4013+
*
4014+
* @memberof dc_msg_t
4015+
* @param msg The message object.
4016+
* @param path Destination file path with filename and extension.
4017+
* @return 0 on failure, 1 on success.
4018+
*/
4019+
int dc_msg_save_file (const dc_msg_t* msg, const char* path);
4020+
4021+
40094022
/**
40104023
* Get an original attachment filename, with extension but without the path. To get the full path,
40114024
* use dc_msg_get_file().

deltachat-ffi/src/lib.rs

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

3320+
#[no_mangle]
3321+
pub unsafe extern "C" fn dc_msg_save_file(
3322+
msg: *mut dc_msg_t,
3323+
path: *const libc::c_char,
3324+
) -> libc::c_int {
3325+
if msg.is_null() || path.is_null() {
3326+
eprintln!("ignoring careless call to dc_msg_save_file()");
3327+
return 0;
3328+
}
3329+
let ffi_msg = &*msg;
3330+
let ctx = &*ffi_msg.context;
3331+
let path = to_string_lossy(path);
3332+
let r = block_on(
3333+
ffi_msg
3334+
.message
3335+
.save_file(ctx, &std::path::PathBuf::from(path)),
3336+
);
3337+
match r {
3338+
Ok(()) => 1,
3339+
Err(_) => {
3340+
r.context("Failed to save file from message")
3341+
.log_err(ctx)
3342+
.unwrap_or_default();
3343+
0
3344+
}
3345+
}
3346+
}
3347+
33203348
#[no_mangle]
33213349
pub unsafe extern "C" fn dc_msg_get_filename(msg: *mut dc_msg_t) -> *mut libc::c_char {
33223350
if msg.is_null() {

deltachat-jsonrpc/src/api/mod.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

@@ -1891,19 +1892,21 @@ impl CommandApi {
18911892
);
18921893
let destination_path = account_folder.join("stickers").join(collection);
18931894
fs::create_dir_all(&destination_path).await?;
1894-
let file = message.get_file(&ctx).context("no file")?;
1895-
fs::copy(
1896-
&file,
1897-
destination_path.join(format!(
1898-
"{}.{}",
1899-
msg_id,
1900-
file.extension()
1901-
.unwrap_or_default()
1902-
.to_str()
1903-
.unwrap_or_default()
1904-
)),
1905-
)
1906-
.await?;
1895+
let file = message.get_filename().context("no file?")?;
1896+
message
1897+
.save_file(
1898+
&ctx,
1899+
&destination_path.join(format!(
1900+
"{}.{}",
1901+
msg_id,
1902+
Path::new(&file)
1903+
.extension()
1904+
.unwrap_or_default()
1905+
.to_str()
1906+
.unwrap_or_default()
1907+
)),
1908+
)
1909+
.await?;
19071910
Ok(())
19081911
}
19091912

src/blob.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -1097,22 +1097,25 @@ mod tests {
10971097
let alice_msg = alice.get_last_msg().await;
10981098
assert_eq!(alice_msg.get_width() as u32, compressed_width);
10991099
assert_eq!(alice_msg.get_height() as u32, compressed_height);
1100-
check_image_size(
1101-
alice_msg.get_file(&alice).unwrap(),
1102-
compressed_width,
1103-
compressed_height,
1104-
);
1100+
let file_saved = alice
1101+
.get_blobdir()
1102+
.join("saved-".to_string() + &alice_msg.get_filename().unwrap());
1103+
alice_msg.save_file(&alice, &file_saved).await?;
1104+
check_image_size(file_saved, compressed_width, compressed_height);
11051105

11061106
let bob_msg = bob.recv_msg(&sent).await;
11071107
assert_eq!(bob_msg.get_width() as u32, compressed_width);
11081108
assert_eq!(bob_msg.get_height() as u32, compressed_height);
1109-
let file = bob_msg.get_file(&bob).unwrap();
1109+
let file_saved = bob
1110+
.get_blobdir()
1111+
.join("saved-".to_string() + &bob_msg.get_filename().unwrap());
1112+
bob_msg.save_file(&bob, &file_saved).await?;
11101113

1111-
let blob = BlobObject::new_from_path(&bob, &file).await?;
1114+
let blob = BlobObject::new_from_path(&bob, &file_saved).await?;
11121115
let (_, exif) = blob.metadata()?;
11131116
assert!(exif.is_none());
11141117

1115-
let img = check_image_size(file, compressed_width, compressed_height);
1118+
let img = check_image_size(file_saved, compressed_width, compressed_height);
11161119
Ok(img)
11171120
}
11181121

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::chat::{Chat, ChatId};
1112
use crate::config::Config;
@@ -574,6 +575,19 @@ impl Message {
574575
self.param.get_path(Param::File, context).unwrap_or(None)
575576
}
576577

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

src/receive_imf/tests.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -2830,11 +2830,15 @@ async fn test_long_and_duplicated_filenames() -> Result<()> {
28302830
let resulting_filename = msg.get_filename().unwrap();
28312831
assert_eq!(resulting_filename, filename);
28322832
let path = msg.get_file(t).unwrap();
2833+
let path2 = path.with_file_name("saved.txt");
2834+
msg.save_file(t, &path2).await.unwrap();
28332835
assert!(
28342836
path.to_str().unwrap().ends_with(".tar.gz"),
28352837
"path {path:?} doesn't end with .tar.gz"
28362838
);
2837-
assert_eq!(fs::read_to_string(path).await.unwrap(), content);
2839+
assert_eq!(fs::read_to_string(&path).await.unwrap(), content);
2840+
assert_eq!(fs::read_to_string(&path2).await.unwrap(), content);
2841+
fs::remove_file(path2).await.unwrap();
28382842
}
28392843
check_message(&msg_alice, &alice, filename_sent, &content).await;
28402844
check_message(&msg_bob, &bob, filename_sent, &content).await;

0 commit comments

Comments
 (0)