Skip to content

Commit 3959305

Browse files
Hocurilink2xt
authored andcommitted
feat: Deduplicate in more places (#6464)
Deduplicate: - In the REPL - In `store_from_base64()`, which writes avatars received in headers - In a few tests - The saved messages, broadcast, device, archive icons - The autocrypt setup message 1-2 more PRs, and we can get rid of `BlobObject::create`, `sanitise_name()`, and some others
1 parent 744cab1 commit 3959305

File tree

10 files changed

+43
-55
lines changed

10 files changed

+43
-55
lines changed

deltachat-ffi/deltachat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4752,6 +4752,7 @@ void dc_msg_set_override_sender_name(dc_msg_t* msg, const char* name)
47524752
* @param file If the message object is used in dc_send_msg() later,
47534753
* this must be the full path of the image file to send.
47544754
* @param filemime The MIME type of the file. NULL if you don't know or don't care.
4755+
* @deprecated 2025-01-21 Use dc_msg_set_file_and_deduplicate instead
47554756
*/
47564757
void dc_msg_set_file (dc_msg_t* msg, const char* file, const char* filemime);
47574758

deltachat-repl/src/cmdline.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
939939
} else {
940940
Viewtype::File
941941
});
942-
msg.set_file(arg1, None);
942+
msg.set_file_and_deduplicate(&context, Path::new(arg1), None, None)?;
943943
msg.set_text(arg2.to_string());
944944
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
945945
}

src/blob.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -439,25 +439,21 @@ impl<'a> BlobObject<'a> {
439439

440440
/// Returns path to the stored Base64-decoded blob.
441441
///
442-
/// If `data` represents an image of known format, this adds the corresponding extension to
443-
/// `suggested_file_stem`.
444-
pub(crate) async fn store_from_base64(
445-
context: &Context,
446-
data: &str,
447-
suggested_file_stem: &str,
448-
) -> Result<String> {
442+
/// If `data` represents an image of known format, this adds the corresponding extension.
443+
///
444+
/// Even though this function is not async, it's OK to call it from an async context.
445+
pub(crate) fn store_from_base64(context: &Context, data: &str) -> Result<String> {
449446
let buf = base64::engine::general_purpose::STANDARD.decode(data)?;
450-
let ext = if let Ok(format) = image::guess_format(&buf) {
447+
let name = if let Ok(format) = image::guess_format(&buf) {
451448
if let Some(ext) = format.extensions_str().first() {
452-
format!(".{ext}")
449+
format!("file.{ext}")
453450
} else {
454451
String::new()
455452
}
456453
} else {
457454
String::new()
458455
};
459-
let blob =
460-
BlobObject::create(context, &format!("{suggested_file_stem}{ext}"), &buf).await?;
456+
let blob = BlobObject::create_and_deduplicate_from_bytes(context, &buf, &name)?;
461457
Ok(blob.as_name().to_string())
462458
}
463459

@@ -930,15 +926,18 @@ mod tests {
930926
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
931927
async fn test_as_file_name() {
932928
let t = TestContext::new().await;
933-
let blob = BlobObject::create(&t, "foo.txt", b"hello").await.unwrap();
934-
assert_eq!(blob.as_file_name(), "foo.txt");
929+
let blob = BlobObject::create_and_deduplicate_from_bytes(&t, b"hello", "foo.txt").unwrap();
930+
assert_eq!(blob.as_file_name(), "ea8f163db38682925e4491c5e58d4bb.txt");
935931
}
936932

937933
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
938934
async fn test_as_rel_path() {
939935
let t = TestContext::new().await;
940-
let blob = BlobObject::create(&t, "foo.txt", b"hello").await.unwrap();
941-
assert_eq!(blob.as_rel_path(), Path::new("foo.txt"));
936+
let blob = BlobObject::create_and_deduplicate_from_bytes(&t, b"hello", "foo.txt").unwrap();
937+
assert_eq!(
938+
blob.as_rel_path(),
939+
Path::new("ea8f163db38682925e4491c5e58d4bb.txt")
940+
);
942941
}
943942

944943
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -1620,7 +1619,7 @@ mod tests {
16201619
.await
16211620
.context("failed to write file")?;
16221621
let mut msg = Message::new(Viewtype::Sticker);
1623-
msg.set_file(file.to_str().unwrap(), None);
1622+
msg.set_file_and_deduplicate(alice, &file, None, None)?;
16241623
let chat = alice.get_self_chat().await;
16251624
let sent = alice.send_msg(chat.id, &mut msg).await;
16261625
let msg = Message::load_from_db(alice, sent.sender_msg_id).await?;

src/chat.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2461,7 +2461,8 @@ pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<()>
24612461
ChatIdBlocked::lookup_by_contact(context, ContactId::SELF).await?
24622462
{
24632463
let icon = include_bytes!("../assets/icon-saved-messages.png");
2464-
let blob = BlobObject::create(context, "icon-saved-messages.png", icon).await?;
2464+
let blob =
2465+
BlobObject::create_and_deduplicate_from_bytes(context, icon, "saved-messages.png")?;
24652466
let icon = blob.as_name().to_string();
24662467

24672468
let mut chat = Chat::load_from_db(context, chat_id).await?;
@@ -2476,7 +2477,7 @@ pub(crate) async fn update_device_icon(context: &Context) -> Result<()> {
24762477
ChatIdBlocked::lookup_by_contact(context, ContactId::DEVICE).await?
24772478
{
24782479
let icon = include_bytes!("../assets/icon-device.png");
2479-
let blob = BlobObject::create(context, "icon-device.png", icon).await?;
2480+
let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "device.png")?;
24802481
let icon = blob.as_name().to_string();
24812482

24822483
let mut chat = Chat::load_from_db(context, chat_id).await?;
@@ -2496,7 +2497,7 @@ pub(crate) async fn get_broadcast_icon(context: &Context) -> Result<String> {
24962497
}
24972498

24982499
let icon = include_bytes!("../assets/icon-broadcast.png");
2499-
let blob = BlobObject::create(context, "icon-broadcast.png", icon).await?;
2500+
let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "broadcast.png")?;
25002501
let icon = blob.as_name().to_string();
25012502
context
25022503
.sql
@@ -2511,7 +2512,7 @@ pub(crate) async fn get_archive_icon(context: &Context) -> Result<String> {
25112512
}
25122513

25132514
let icon = include_bytes!("../assets/icon-archive.png");
2514-
let blob = BlobObject::create(context, "icon-archive.png", icon).await?;
2515+
let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "archive.png")?;
25152516
let icon = blob.as_name().to_string();
25162517
context
25172518
.sql

src/config.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ impl Context {
686686
let value = match key {
687687
Config::Selfavatar if value.is_empty() => None,
688688
Config::Selfavatar => {
689-
config_value = BlobObject::store_from_base64(self, value, "avatar").await?;
689+
config_value = BlobObject::store_from_base64(self, value)?;
690690
Some(config_value.as_str())
691691
}
692692
_ => Some(value),
@@ -1143,6 +1143,8 @@ mod tests {
11431143
Ok(())
11441144
}
11451145

1146+
const SAVED_MESSAGES_DEDUPLICATED_FILE: &str = "969142cb84015bc135767bc2370934a.png";
1147+
11461148
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11471149
async fn test_sync() -> Result<()> {
11481150
let alice0 = TestContext::new_alice().await;
@@ -1217,7 +1219,7 @@ mod tests {
12171219
let self_chat_avatar_path = self_chat.get_profile_image(&alice0).await?.unwrap();
12181220
assert_eq!(
12191221
self_chat_avatar_path,
1220-
alice0.get_blobdir().join("icon-saved-messages.png")
1222+
alice0.get_blobdir().join(SAVED_MESSAGES_DEDUPLICATED_FILE)
12211223
);
12221224
assert!(alice1
12231225
.get_config(Config::Selfavatar)

src/contact.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Resu
345345
return Ok(id);
346346
}
347347
let path = match &contact.profile_image {
348-
Some(image) => match BlobObject::store_from_base64(context, image, "avatar").await {
348+
Some(image) => match BlobObject::store_from_base64(context, image) {
349349
Err(e) => {
350350
warn!(
351351
context,

src/imex/key_transfer.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,20 @@ pub async fn initiate_key_transfer(context: &Context) -> Result<String> {
2626
/* this may require a keypair to be created. this may take a second ... */
2727
let setup_file_content = render_setup_file(context, &setup_code).await?;
2828
/* encrypting may also take a while ... */
29-
let setup_file_blob = BlobObject::create(
29+
let setup_file_blob = BlobObject::create_and_deduplicate_from_bytes(
3030
context,
31-
"autocrypt-setup-message.html",
3231
setup_file_content.as_bytes(),
33-
)
34-
.await?;
32+
"autocrypt-setup-message.html",
33+
)?;
3534

3635
let chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
3736
let mut msg = Message {
3837
viewtype: Viewtype::File,
3938
..Default::default()
4039
};
4140
msg.param.set(Param::File, setup_file_blob.as_name());
41+
msg.param
42+
.set(Param::Filename, "autocrypt-setup-message.html");
4243
msg.subject = stock_str::ac_setup_msg_subject(context).await;
4344
msg.param
4445
.set(Param::MimeType, "application/autocrypt-setup");

src/mimeparser.rs

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use deltachat_derive::{FromSql, ToSql};
1212
use format_flowed::unformat_flowed;
1313
use lettre_email::mime::Mime;
1414
use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo};
15-
use rand::distributions::{Alphanumeric, DistString};
1615

1716
use crate::aheader::{Aheader, EncryptPreference};
1817
use crate::authres::handle_authres;
@@ -652,17 +651,13 @@ impl MimeMessage {
652651
}
653652

654653
/// Parses avatar action headers.
655-
async fn parse_avatar_headers(&mut self, context: &Context) {
654+
fn parse_avatar_headers(&mut self, context: &Context) {
656655
if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar) {
657-
self.group_avatar = self
658-
.avatar_action_from_header(context, header_value.to_string())
659-
.await;
656+
self.group_avatar = self.avatar_action_from_header(context, header_value.to_string());
660657
}
661658

662659
if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar) {
663-
self.user_avatar = self
664-
.avatar_action_from_header(context, header_value.to_string())
665-
.await;
660+
self.user_avatar = self.avatar_action_from_header(context, header_value.to_string());
666661
}
667662
}
668663

@@ -762,7 +757,7 @@ impl MimeMessage {
762757

763758
async fn parse_headers(&mut self, context: &Context) -> Result<()> {
764759
self.parse_system_message_headers(context);
765-
self.parse_avatar_headers(context).await;
760+
self.parse_avatar_headers(context);
766761
self.parse_videochat_headers();
767762
if self.delivery_report.is_none() {
768763
self.squash_attachment_parts();
@@ -856,7 +851,7 @@ impl MimeMessage {
856851
Ok(())
857852
}
858853

859-
async fn avatar_action_from_header(
854+
fn avatar_action_from_header(
860855
&mut self,
861856
context: &Context,
862857
header_value: String,
@@ -868,15 +863,7 @@ impl MimeMessage {
868863
.collect::<String>()
869864
.strip_prefix("base64:")
870865
{
871-
// Add random suffix to the filename
872-
// to prevent the UI from accidentally using
873-
// cached "avatar.jpg".
874-
let suffix = Alphanumeric
875-
.sample_string(&mut rand::thread_rng(), 7)
876-
.to_lowercase();
877-
878-
match BlobObject::store_from_base64(context, base64, &format!("avatar-{suffix}")).await
879-
{
866+
match BlobObject::store_from_base64(context, base64) {
880867
Ok(path) => Some(AvatarAction::Change(path)),
881868
Err(err) => {
882869
warn!(

src/net/http.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::context::Context;
1313
use crate::net::proxy::ProxyConfig;
1414
use crate::net::session::SessionStream;
1515
use crate::net::tls::wrap_rustls;
16-
use crate::tools::{create_id, time};
16+
use crate::tools::time;
1717

1818
/// HTTP(S) GET response.
1919
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -119,12 +119,8 @@ fn http_url_cache_timestamps(url: &str, mimetype: Option<&str>) -> (i64, i64) {
119119

120120
/// Places the binary into HTTP cache.
121121
async fn http_cache_put(context: &Context, url: &str, response: &Response) -> Result<()> {
122-
let blob = BlobObject::create(
123-
context,
124-
&format!("http_cache_{}", create_id()),
125-
response.blob.as_slice(),
126-
)
127-
.await?;
122+
let blob =
123+
BlobObject::create_and_deduplicate_from_bytes(context, response.blob.as_slice(), "")?;
128124

129125
let (expires, stale) = http_url_cache_timestamps(url, response.mimetype.as_deref());
130126
context

src/stock_str.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1415,9 +1415,10 @@ impl Context {
14151415
// add welcome-messages. by the label, this is done only once,
14161416
// if the user has deleted the message or the chat, it is not added again.
14171417
let image = include_bytes!("../assets/welcome-image.jpg");
1418-
let blob = BlobObject::create(self, "welcome-image.jpg", image).await?;
1418+
let blob = BlobObject::create_and_deduplicate_from_bytes(self, image, "welcome.jpg")?;
14191419
let mut msg = Message::new(Viewtype::Image);
14201420
msg.param.set(Param::File, blob.as_name());
1421+
msg.param.set(Param::Filename, "welcome-image.jpg");
14211422
chat::add_device_msg(self, Some("core-welcome-image"), Some(&mut msg)).await?;
14221423

14231424
let mut msg = Message::new_text(welcome_message(self).await);

0 commit comments

Comments
 (0)