Add human-readable identifiers in temp draft files

Draft files that are created in temp directories to be opened and edited
by the user's editor get UUID filenames with the `.eml` extension. Give
them filenames with the draft subject, recipient and date to make it
easier to discern a file's identity on the filesystem.

Closes #466

Resolves: <https://git.meli-email.org/meli/meli/issues/466>
Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/482/head
Manos Pitsidianakis 1 month ago
parent e9b87b2e40
commit 8f0e1d6640
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -41,6 +41,7 @@ use crate::{
accounts::JobRequest,
jobs::{IsAsync, JoinHandle},
terminal::embedded::Terminal,
types::{sanitize_filename, File},
};
#[cfg(feature = "gpgme")]
@ -1867,10 +1868,25 @@ impl Component for Composer {
account_settings!(context[self.account_hash].composing.wrap_header_preamble)
.clone(),
);
let filename = format!(
"{date}_{subject}_{to}_{in_reply_to}",
date = self.draft.headers.get(HeaderName::DATE).unwrap_or_default(),
subject = self
.draft
.headers
.get(HeaderName::SUBJECT)
.unwrap_or_default(),
to = self.draft.headers.get(HeaderName::TO).unwrap_or_default(),
in_reply_to = self
.draft
.headers
.get(HeaderName::IN_REPLY_TO)
.unwrap_or_default()
);
let f = match File::create_temp_file(
self.draft.to_edit_string().as_bytes(),
None,
sanitize_filename(filename).as_deref(),
None,
Some("eml"),
true,

@ -33,12 +33,8 @@
//! [`UIEvent`] is the type passed around
//! [`Component`]'s when something happens.
#[macro_use]
mod helpers;
use std::{borrow::Cow, sync::Arc};
pub use helpers::*;
use indexmap::IndexMap;
use melib::{
backends::{AccountHash, BackendEvent, MailboxHash},
@ -54,6 +50,10 @@ use super::{
};
use crate::components::{Component, ComponentId, ScrollUpdate};
#[macro_use]
mod helpers;
pub use helpers::*;
pub type UIMessage = Box<dyn 'static + std::any::Any + Send + Sync>;
#[derive(Debug)]

@ -91,7 +91,11 @@ impl File {
dir.push("meli");
std::fs::DirBuilder::new().recursive(true).create(&dir)?;
if let Some(filename) = filename {
dir.push(filename)
dir.push(filename);
while dir.try_exists().unwrap_or_default() {
dir.pop();
dir.push(format!("{filename}_{}", Uuid::new_v4().as_simple()));
}
} else {
let u = Uuid::new_v4();
dir.push(u.as_simple().to_string());
@ -136,6 +140,25 @@ pub fn pipe() -> Result<(OwnedFd, OwnedFd)> {
})
}
/// Create a shell-friendly filename by removing control characters and
/// replacing characters that need escaping.
pub fn sanitize_filename(og: String) -> Option<String> {
use regex::Regex;
let regex = Regex::new(r"(?m)[[:space:]]+").ok()?; // _
let mut ret = regex.replace_all(&og, "_").to_string();
let regex = Regex::new(r"(?m)[[:punct:]]").ok()?; // -
ret = regex.replace_all(&ret, "-").to_string();
let regex = Regex::new(r"(?m)[[:cntrl:]]*").ok()?; //
ret = regex.replace_all(&ret, "").to_string();
let regex = Regex::new(r"(?m)[[:blank:]]*").ok()?; //
ret = regex.replace_all(&ret, "").to_string();
let regex = Regex::new(r"^[[:punct:]]*").ok()?; //
ret = regex.replace_all(&ret, "").to_string();
let regex = Regex::new(r"[[:punct:]]*$").ok()?; //
Some(regex.replace_all(&ret, "").to_string())
}
#[cfg(test)]
mod tests {
use super::*;
@ -180,4 +203,12 @@ mod tests {
_ = tempdir.close();
}
#[test]
fn test_file_sanitize_filename() {
assert_eq!(
sanitize_filename("Re: Some long subject - \"User Dot. Name\" <user1@example.com> Sent from my bPad 2024-09-07, on a sunny Saturday".to_string()),
Some("Re--Some-long-subject----User-Dot--Name---user1-example-com--Sent-from-my-bPad-2024-09-07--on-a-sunny-Saturday".to_string())
);
}
}

Loading…
Cancel
Save