mirror of
https://github.com/chatmail/core.git
synced 2026-04-26 09:56:35 +03:00
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().
This commit is contained in:
@@ -4050,6 +4050,19 @@ char* dc_msg_get_subject (const dc_msg_t* msg);
|
||||
char* dc_msg_get_file (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Save file copy at the user-provided path.
|
||||
*
|
||||
* Fails if file already exists at the provided path.
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @param path Destination file path with filename and extension.
|
||||
* @return 0 on failure, 1 on success.
|
||||
*/
|
||||
int dc_msg_save_file (const dc_msg_t* msg, const char* path);
|
||||
|
||||
|
||||
/**
|
||||
* Get an original attachment filename, with extension but without the path. To get the full path,
|
||||
* use dc_msg_get_file().
|
||||
|
||||
@@ -3362,6 +3362,34 @@ pub unsafe extern "C" fn dc_msg_get_file(msg: *mut dc_msg_t) -> *mut libc::c_cha
|
||||
.unwrap_or_else(|| "".strdup())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_msg_save_file(
|
||||
msg: *mut dc_msg_t,
|
||||
path: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
if msg.is_null() || path.is_null() {
|
||||
eprintln!("ignoring careless call to dc_msg_save_file()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_msg = &*msg;
|
||||
let ctx = &*ffi_msg.context;
|
||||
let path = to_string_lossy(path);
|
||||
let r = block_on(
|
||||
ffi_msg
|
||||
.message
|
||||
.save_file(ctx, &std::path::PathBuf::from(path)),
|
||||
);
|
||||
match r {
|
||||
Ok(()) => 1,
|
||||
Err(_) => {
|
||||
r.context("Failed to save file from message")
|
||||
.log_err(ctx)
|
||||
.unwrap_or_default();
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_msg_get_filename(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||
if msg.is_null() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
@@ -1927,19 +1928,21 @@ impl CommandApi {
|
||||
);
|
||||
let destination_path = account_folder.join("stickers").join(collection);
|
||||
fs::create_dir_all(&destination_path).await?;
|
||||
let file = message.get_file(&ctx).context("no file")?;
|
||||
fs::copy(
|
||||
&file,
|
||||
destination_path.join(format!(
|
||||
"{}.{}",
|
||||
msg_id,
|
||||
file.extension()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
let file = message.get_filename().context("no file?")?;
|
||||
message
|
||||
.save_file(
|
||||
&ctx,
|
||||
&destination_path.join(format!(
|
||||
"{}.{}",
|
||||
msg_id,
|
||||
Path::new(&file)
|
||||
.extension()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
19
src/blob.rs
19
src/blob.rs
@@ -1182,23 +1182,26 @@ mod tests {
|
||||
let alice_msg = alice.get_last_msg().await;
|
||||
assert_eq!(alice_msg.get_width() as u32, compressed_width);
|
||||
assert_eq!(alice_msg.get_height() as u32, compressed_height);
|
||||
check_image_size(
|
||||
alice_msg.get_file(&alice).unwrap(),
|
||||
compressed_width,
|
||||
compressed_height,
|
||||
);
|
||||
let file_saved = alice
|
||||
.get_blobdir()
|
||||
.join("saved-".to_string() + &alice_msg.get_filename().unwrap());
|
||||
alice_msg.save_file(&alice, &file_saved).await?;
|
||||
check_image_size(file_saved, compressed_width, compressed_height);
|
||||
|
||||
let bob_msg = bob.recv_msg(&sent).await;
|
||||
assert_eq!(bob_msg.get_viewtype(), Viewtype::Image);
|
||||
assert_eq!(bob_msg.get_width() as u32, compressed_width);
|
||||
assert_eq!(bob_msg.get_height() as u32, compressed_height);
|
||||
let file = bob_msg.get_file(&bob).unwrap();
|
||||
let file_saved = bob
|
||||
.get_blobdir()
|
||||
.join("saved-".to_string() + &bob_msg.get_filename().unwrap());
|
||||
bob_msg.save_file(&bob, &file_saved).await?;
|
||||
|
||||
let blob = BlobObject::new_from_path(&bob, &file).await?;
|
||||
let blob = BlobObject::new_from_path(&bob, &file_saved).await?;
|
||||
let (_, exif) = blob.metadata()?;
|
||||
assert!(exif.is_none());
|
||||
|
||||
let img = check_image_size(file, compressed_width, compressed_height);
|
||||
let img = check_image_size(file_saved, compressed_width, compressed_height);
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
|
||||
@@ -656,6 +656,12 @@ mod tests {
|
||||
let text = fs::read_to_string(&path).await.unwrap();
|
||||
assert_eq!(text, "i am attachment");
|
||||
|
||||
let path = path.with_file_name("saved.txt");
|
||||
msg.save_file(&ctx1, &path).await.unwrap();
|
||||
let text = fs::read_to_string(&path).await.unwrap();
|
||||
assert_eq!(text, "i am attachment");
|
||||
assert!(msg.save_file(&ctx1, &path).await.is_err());
|
||||
|
||||
// Check that both received the ImexProgress events.
|
||||
ctx0.evtracker
|
||||
.get_matching(|ev| matches!(ev, EventType::ImexProgress(1000)))
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::{ensure, format_err, Context as _, Result};
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{fs, io};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat::{Chat, ChatId};
|
||||
@@ -579,6 +580,19 @@ impl Message {
|
||||
self.param.get_path(Param::File, context).unwrap_or(None)
|
||||
}
|
||||
|
||||
/// Save file copy at the user-provided path.
|
||||
pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
|
||||
let path_src = self.get_file(context).context("No file")?;
|
||||
let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
|
||||
let mut dst = fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(path)
|
||||
.await?;
|
||||
io::copy(&mut src, &mut dst).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If message is an image or gif, set Param::Width and Param::Height
|
||||
pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
|
||||
if self.viewtype.has_file() {
|
||||
|
||||
@@ -2921,11 +2921,15 @@ async fn test_long_and_duplicated_filenames() -> Result<()> {
|
||||
let resulting_filename = msg.get_filename().unwrap();
|
||||
assert_eq!(resulting_filename, filename);
|
||||
let path = msg.get_file(t).unwrap();
|
||||
let path2 = path.with_file_name("saved.txt");
|
||||
msg.save_file(t, &path2).await.unwrap();
|
||||
assert!(
|
||||
path.to_str().unwrap().ends_with(".tar.gz"),
|
||||
"path {path:?} doesn't end with .tar.gz"
|
||||
);
|
||||
assert_eq!(fs::read_to_string(path).await.unwrap(), content);
|
||||
assert_eq!(fs::read_to_string(&path).await.unwrap(), content);
|
||||
assert_eq!(fs::read_to_string(&path2).await.unwrap(), content);
|
||||
fs::remove_file(path2).await.unwrap();
|
||||
}
|
||||
check_message(&msg_alice, &alice, filename_sent, &content).await;
|
||||
check_message(&msg_bob, &bob, filename_sent, &content).await;
|
||||
|
||||
Reference in New Issue
Block a user