Compare commits
4 Commits
2d9ed88f1e
...
c1c41c9126
Author | SHA1 | Date |
---|---|---|
Manos Pitsidianakis | c1c41c9126 | |
Manos Pitsidianakis | a1cbb1988b | |
Manos Pitsidianakis | 470cae6b88 | |
Manos Pitsidianakis | 23507932f9 |
|
@ -244,6 +244,7 @@ pub enum JobRequest {
|
|||
},
|
||||
SetFlags {
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
mailbox_hash: MailboxHash,
|
||||
flags: SmallVec<[FlagOp; 8]>,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
|
@ -329,10 +330,14 @@ impl std::fmt::Debug for JobRequest {
|
|||
JobRequest::IsOnline { .. } => write!(f, "JobRequest::IsOnline"),
|
||||
JobRequest::Refresh { .. } => write!(f, "JobRequest::Refresh"),
|
||||
JobRequest::SetFlags {
|
||||
env_hashes, flags, ..
|
||||
env_hashes,
|
||||
mailbox_hash,
|
||||
flags,
|
||||
..
|
||||
} => f
|
||||
.debug_struct(stringify!(JobRequest::SetFlags))
|
||||
.field("env_hashes", &env_hashes)
|
||||
.field("mailbox_hash", &mailbox_hash)
|
||||
.field("flags", &flags)
|
||||
.finish(),
|
||||
JobRequest::SaveMessage { .. } => write!(f, "JobRequest::SaveMessage"),
|
||||
|
@ -1202,15 +1207,15 @@ impl Account {
|
|||
if let Some(mailbox_hash) = saved_at {
|
||||
Ok(mailbox_hash)
|
||||
} else {
|
||||
let file = crate::types::create_temp_file(bytes, None, None, Some("eml"), false);
|
||||
log::trace!("message saved in {}", file.path.display());
|
||||
let file = crate::types::File::create_temp_file(bytes, None, None, Some("eml"), false)?;
|
||||
log::trace!("message saved in {}", file.path().display());
|
||||
log::info!(
|
||||
"Message was stored in {} so that you can restore it manually.",
|
||||
file.path.display()
|
||||
file.path().display()
|
||||
);
|
||||
Err(Error::new(format!(
|
||||
"Message was stored in {} so that you can restore it manually.",
|
||||
file.path.display()
|
||||
file.path().display()
|
||||
))
|
||||
.set_summary("Could not save in any mailbox"))
|
||||
}
|
||||
|
@ -1882,6 +1887,7 @@ impl Account {
|
|||
JobRequest::SetFlags {
|
||||
ref mut handle,
|
||||
ref env_hashes,
|
||||
ref mailbox_hash,
|
||||
ref flags,
|
||||
} => match handle.chan.try_recv() {
|
||||
Ok(Some(Err(err))) => {
|
||||
|
@ -1936,8 +1942,11 @@ impl Account {
|
|||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::EnvelopeUpdate(env_hash)));
|
||||
}
|
||||
for env_hash in env_hashes.iter() {
|
||||
self.collection.update_flags(env_hash, *mailbox_hash);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
Err(_) | Ok(None) => {}
|
||||
},
|
||||
JobRequest::SaveMessage {
|
||||
ref mut handle,
|
||||
|
@ -1949,22 +1958,33 @@ impl Account {
|
|||
.job_executor
|
||||
.set_job_success(job_id, false);
|
||||
log::error!("Could not save message: {err}");
|
||||
let file =
|
||||
crate::types::create_temp_file(bytes, None, None, Some("eml"), false);
|
||||
log::debug!("message saved in {}", file.path.display());
|
||||
log::info!(
|
||||
"Message was stored in {} so that you can restore it manually.",
|
||||
file.path.display()
|
||||
);
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
Some(format!("{}: could not save message", &self.name)),
|
||||
format!(
|
||||
match crate::types::File::create_temp_file(
|
||||
bytes,
|
||||
None,
|
||||
None,
|
||||
Some("eml"),
|
||||
false,
|
||||
) {
|
||||
Ok(file) => {
|
||||
log::debug!("message saved in {}", file.path().display());
|
||||
log::info!(
|
||||
"Message was stored in {} so that you can restore it manually.",
|
||||
file.path.display()
|
||||
),
|
||||
Some(crate::types::NotificationType::Info),
|
||||
)));
|
||||
file.path().display()
|
||||
);
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!("{}: could not save message", &self.name)),
|
||||
format!(
|
||||
"Message was stored in {} so that you can restore it \
|
||||
manually.",
|
||||
file.path().display()
|
||||
),
|
||||
Some(crate::types::NotificationType::Info),
|
||||
),
|
||||
));
|
||||
}
|
||||
Err(err) => log::error!("Could not save message: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
JobRequest::SendMessage => {}
|
||||
|
|
|
@ -38,12 +38,13 @@ impl Account {
|
|||
let handle = self
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized("set_unseen".into(), fut);
|
||||
.spawn_specialized("set_flags".into(), fut);
|
||||
let job_id = handle.job_id;
|
||||
self.insert_job(
|
||||
job_id,
|
||||
JobRequest::SetFlags {
|
||||
env_hashes,
|
||||
mailbox_hash,
|
||||
flags,
|
||||
handle,
|
||||
},
|
||||
|
|
|
@ -841,8 +841,12 @@ To: {}
|
|||
}
|
||||
|
||||
fn update_from_file(&mut self, file: File, context: &mut Context) -> bool {
|
||||
let result = file.read_to_string();
|
||||
match self.draft.update(result.as_str()) {
|
||||
match file.read_to_string().and_then(|res| {
|
||||
self.draft.update(res.as_str()).map_err(|err| {
|
||||
self.draft.set_body(res);
|
||||
err
|
||||
})
|
||||
}) {
|
||||
Ok(has_changes) => {
|
||||
self.has_changes = has_changes;
|
||||
true
|
||||
|
@ -850,13 +854,9 @@ To: {}
|
|||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
Some("Could not parse draft headers correctly.".to_string()),
|
||||
format!(
|
||||
"{}\nThe invalid text has been set as the body of your draft",
|
||||
&err
|
||||
),
|
||||
format!("{err}\nThe invalid text has been set as the body of your draft",),
|
||||
Some(NotificationType::Error(melib::error::ErrorKind::None)),
|
||||
));
|
||||
self.draft.set_body(result);
|
||||
self.has_changes = true;
|
||||
false
|
||||
}
|
||||
|
@ -1898,13 +1898,24 @@ impl Component for Composer {
|
|||
.clone(),
|
||||
);
|
||||
|
||||
let f = create_temp_file(
|
||||
let f = match File::create_temp_file(
|
||||
self.draft.to_edit_string().as_bytes(),
|
||||
None,
|
||||
None,
|
||||
Some("eml"),
|
||||
true,
|
||||
);
|
||||
) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
None,
|
||||
err.to_string(),
|
||||
Some(NotificationType::Error(err.kind)),
|
||||
));
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
if *account_settings!(context[self.account_hash].composing.embed) {
|
||||
match crate::terminal::embed::create_pty(
|
||||
|
@ -1966,8 +1977,12 @@ impl Component for Composer {
|
|||
}
|
||||
}
|
||||
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
||||
let result = f.read_to_string();
|
||||
match self.draft.update(result.as_str()) {
|
||||
match f.read_to_string().and_then(|res| {
|
||||
self.draft.update(res.as_str()).map_err(|err| {
|
||||
self.draft.set_body(res);
|
||||
err
|
||||
})
|
||||
}) {
|
||||
Ok(has_changes) => {
|
||||
self.has_changes = has_changes;
|
||||
}
|
||||
|
@ -1975,12 +1990,10 @@ impl Component for Composer {
|
|||
context.replies.push_back(UIEvent::Notification(
|
||||
Some("Could not parse draft headers correctly.".to_string()),
|
||||
format!(
|
||||
"{}\nThe invalid text has been set as the body of your draft",
|
||||
&err
|
||||
"{err}\nThe invalid text has been set as the body of your draft",
|
||||
),
|
||||
Some(NotificationType::Error(melib::error::ErrorKind::None)),
|
||||
));
|
||||
self.draft.set_body(result);
|
||||
self.has_changes = true;
|
||||
}
|
||||
}
|
||||
|
@ -1998,15 +2011,21 @@ impl Component for Composer {
|
|||
));
|
||||
return false;
|
||||
}
|
||||
let f = create_temp_file(&[], None, None, None, true);
|
||||
match Command::new("sh")
|
||||
.args(["-c", command])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::from(f.file()))
|
||||
.spawn()
|
||||
.and_then(|child| Ok(child.wait_with_output()?.stderr))
|
||||
match File::create_temp_file(&[], None, None, None, true)
|
||||
.and_then(|f| {
|
||||
let std_file = f.as_std_file()?;
|
||||
Ok((
|
||||
f,
|
||||
Command::new("sh")
|
||||
.args(["-c", command])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::from(std_file))
|
||||
.spawn()?,
|
||||
))
|
||||
})
|
||||
.and_then(|(f, child)| Ok((f, child.wait_with_output()?.stderr)))
|
||||
{
|
||||
Ok(stderr) => {
|
||||
Ok((f, stderr)) => {
|
||||
if !stderr.is_empty() {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
|
@ -2016,7 +2035,7 @@ impl Component for Composer {
|
|||
));
|
||||
}
|
||||
let attachment =
|
||||
match melib::email::compose::attachment_from_file(f.path()) {
|
||||
match melib::email::compose::attachment_from_file(&f.path()) {
|
||||
Ok(a) => a,
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
|
@ -2548,7 +2567,7 @@ pub fn send_draft_async(
|
|||
))))
|
||||
.unwrap();
|
||||
} else if !store_sent_mail && is_ok {
|
||||
let f = create_temp_file(message.as_bytes(), None, None, Some("eml"), false);
|
||||
let f = File::create_temp_file(message.as_bytes(), None, None, Some("eml"), false)?;
|
||||
log::info!(
|
||||
"store_sent_mail is false; stored sent mail to {}",
|
||||
f.path().display()
|
||||
|
|
|
@ -1532,33 +1532,36 @@ impl Component for EnvelopeView {
|
|||
let attachment_type = attachment.mime_type();
|
||||
let filename = attachment.filename();
|
||||
if let Ok(command) = query_default_app(&attachment_type) {
|
||||
let p = create_temp_file(
|
||||
match File::create_temp_file(
|
||||
&attachment.decode(Default::default()),
|
||||
filename.as_deref(),
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
);
|
||||
let exec_cmd = desktop_exec_to_command(
|
||||
&command,
|
||||
p.path.display().to_string(),
|
||||
false,
|
||||
);
|
||||
match Command::new("sh")
|
||||
.args(["-c", &exec_cmd])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => {
|
||||
)
|
||||
.and_then(|p| {
|
||||
let exec_cmd = desktop_exec_to_command(
|
||||
&command,
|
||||
p.path().display().to_string(),
|
||||
false,
|
||||
);
|
||||
Ok((
|
||||
p,
|
||||
Command::new("sh")
|
||||
.args(["-c", &exec_cmd])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?,
|
||||
))
|
||||
}) {
|
||||
Ok((p, child)) => {
|
||||
context.temp_files.push(p);
|
||||
context.children.push(child);
|
||||
}
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Failed to start `{}`: {}",
|
||||
&exec_cmd, err
|
||||
"Failed to execute command: {err}"
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -173,31 +173,36 @@ impl Component for HtmlView {
|
|||
command
|
||||
};
|
||||
if let Some(command) = command {
|
||||
let p = create_temp_file(&self.bytes, None, None, Some("html"), true);
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateSubStatus(
|
||||
command.to_string(),
|
||||
)));
|
||||
let exec_cmd =
|
||||
super::desktop_exec_to_command(&command, p.path.display().to_string(), false);
|
||||
match File::create_temp_file(&self.bytes, None, None, Some("html"), true).and_then(
|
||||
|p| {
|
||||
let exec_cmd = super::desktop_exec_to_command(
|
||||
&command,
|
||||
p.path().display().to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
match Command::new("sh")
|
||||
.args(["-c", &exec_cmd])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => {
|
||||
Ok((
|
||||
p,
|
||||
Command::new("sh")
|
||||
.args(["-c", &exec_cmd])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?,
|
||||
))
|
||||
},
|
||||
) {
|
||||
Ok((p, child)) => {
|
||||
context.temp_files.push(p);
|
||||
context.children.push(child);
|
||||
}
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Failed to start `{}`: {}",
|
||||
&exec_cmd, err
|
||||
)),
|
||||
StatusEvent::DisplayMessage(format!("Failed to start {err}",)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ use melib::{email::Attachment, log, text_processing::GlobMatch, Error, Result};
|
|||
|
||||
use crate::{
|
||||
state::Context,
|
||||
types::{create_temp_file, ForkType, UIEvent},
|
||||
types::{File, ForkType, UIEvent},
|
||||
};
|
||||
|
||||
pub struct MailcapEntry {
|
||||
|
@ -165,31 +165,34 @@ impl MailcapEntry {
|
|||
.map(|arg| match *arg {
|
||||
"%s" => {
|
||||
needs_stdin = false;
|
||||
let _f = create_temp_file(
|
||||
let _f = File::create_temp_file(
|
||||
&a.decode(Default::default()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
);
|
||||
)?;
|
||||
let p = _f.path().display().to_string();
|
||||
f = Some(_f);
|
||||
p
|
||||
Ok(p)
|
||||
}
|
||||
"%t" => a.content_type().to_string(),
|
||||
"%t" => Ok(a.content_type().to_string()),
|
||||
param if param.starts_with("%{") && param.ends_with('}') => {
|
||||
let param = ¶m["%{".len()..param.len() - 1];
|
||||
if let Some(v) = params.iter().find(|(k, _)| *k == param.as_bytes()) {
|
||||
String::from_utf8_lossy(v.1).into()
|
||||
} else if param == "charset" {
|
||||
String::from("utf-8")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
Ok(
|
||||
if let Some(v) = params.iter().find(|(k, _)| *k == param.as_bytes())
|
||||
{
|
||||
String::from_utf8_lossy(v.1).into()
|
||||
} else if param == "charset" {
|
||||
String::from("utf-8")
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
)
|
||||
}
|
||||
a => a.to_string(),
|
||||
a => Ok(a.to_string()),
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
.collect::<Result<Vec<String>>>()?;
|
||||
let cmd_string = format!("{} {}", cmd, args.join(" "));
|
||||
log::trace!("Executing: sh -c \"{}\"", cmd_string.replace('"', "\\\""));
|
||||
if copiousoutput {
|
||||
|
|
|
@ -24,14 +24,17 @@ use std::{
|
|||
fs::OpenOptions,
|
||||
io::{Read, Write},
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use melib::uuid::Uuid;
|
||||
use melib::{error::*, uuid::Uuid};
|
||||
|
||||
/// Temporary file that can optionally cleaned up when it is dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct File {
|
||||
pub path: PathBuf,
|
||||
/// File's path.
|
||||
path: PathBuf,
|
||||
/// Delete file when it is dropped.
|
||||
delete_on_drop: bool,
|
||||
}
|
||||
|
||||
|
@ -44,69 +47,121 @@ impl Drop for File {
|
|||
}
|
||||
|
||||
impl File {
|
||||
pub fn file(&self) -> std::fs::File {
|
||||
/// Open as a standard library file type.
|
||||
pub fn as_std_file(&self) -> Result<std::fs::File> {
|
||||
OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&self.path)
|
||||
.unwrap()
|
||||
.chain_err_summary(|| format!("Could not create/open path {}", self.path.display()))
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
/// The file's path.
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn read_to_string(&self) -> String {
|
||||
let mut buf = Vec::new();
|
||||
let mut f = fs::File::open(&self.path)
|
||||
.unwrap_or_else(|_| panic!("Can't open {}", &self.path.display()));
|
||||
f.read_to_end(&mut buf)
|
||||
.unwrap_or_else(|_| panic!("Can't read {}", &self.path.display()));
|
||||
String::from_utf8(buf).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned `File` will be deleted when dropped if delete_on_drop is set, so
|
||||
/// make sure to add it on `context.temp_files` to reap it later.
|
||||
pub fn create_temp_file(
|
||||
bytes: &[u8],
|
||||
filename: Option<&str>,
|
||||
path: Option<&mut PathBuf>,
|
||||
extension: Option<&str>,
|
||||
delete_on_drop: bool,
|
||||
) -> File {
|
||||
let mut dir = std::env::temp_dir();
|
||||
|
||||
let path = path.unwrap_or_else(|| {
|
||||
dir.push("meli");
|
||||
std::fs::DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(&dir)
|
||||
.unwrap();
|
||||
if let Some(filename) = filename {
|
||||
dir.push(filename)
|
||||
} else {
|
||||
let u = Uuid::new_v4();
|
||||
dir.push(u.as_simple().to_string());
|
||||
/// Convenience method to read `File` to `String`.
|
||||
pub fn read_to_string(&self) -> Result<String> {
|
||||
fn inner(path: &Path) -> Result<String> {
|
||||
let mut buf = Vec::new();
|
||||
let mut f = fs::File::open(path)?;
|
||||
f.read_to_end(&mut buf)?;
|
||||
Ok(String::from_utf8(buf)?)
|
||||
}
|
||||
&mut dir
|
||||
});
|
||||
if let Some(ext) = extension {
|
||||
path.set_extension(ext);
|
||||
inner(&self.path).chain_err_summary(|| format!("Can't read {}", self.path.display()))
|
||||
}
|
||||
|
||||
let mut f = std::fs::File::create(&path).unwrap();
|
||||
let metadata = f.metadata().unwrap();
|
||||
let mut permissions = metadata.permissions();
|
||||
/// Returned `File` will be deleted when dropped if delete_on_drop is set,
|
||||
/// so make sure to add it on `context.temp_files` to reap it later.
|
||||
pub fn create_temp_file(
|
||||
bytes: &[u8],
|
||||
filename: Option<&str>,
|
||||
path: Option<&mut PathBuf>,
|
||||
extension: Option<&str>,
|
||||
delete_on_drop: bool,
|
||||
) -> Result<Self> {
|
||||
let mut dir = std::env::temp_dir();
|
||||
|
||||
permissions.set_mode(0o600); // Read/write for owner only.
|
||||
f.set_permissions(permissions).unwrap();
|
||||
let path = if let Some(p) = path {
|
||||
p
|
||||
} else {
|
||||
dir.push("meli");
|
||||
std::fs::DirBuilder::new().recursive(true).create(&dir)?;
|
||||
if let Some(filename) = filename {
|
||||
dir.push(filename)
|
||||
} else {
|
||||
let u = Uuid::new_v4();
|
||||
dir.push(u.as_simple().to_string());
|
||||
}
|
||||
&mut dir
|
||||
};
|
||||
if let Some(ext) = extension {
|
||||
path.set_extension(ext);
|
||||
}
|
||||
fn inner(path: &Path, bytes: &[u8], delete_on_drop: bool) -> Result<File> {
|
||||
let mut f = std::fs::File::create(path)?;
|
||||
let metadata = f.metadata()?;
|
||||
let mut permissions = metadata.permissions();
|
||||
|
||||
f.write_all(bytes).unwrap();
|
||||
f.flush().unwrap();
|
||||
File {
|
||||
path: path.clone(),
|
||||
delete_on_drop,
|
||||
permissions.set_mode(0o600); // Read/write for owner only.
|
||||
f.set_permissions(permissions)?;
|
||||
|
||||
f.write_all(bytes)?;
|
||||
f.flush()?;
|
||||
Ok(File {
|
||||
path: path.to_path_buf(),
|
||||
delete_on_drop,
|
||||
})
|
||||
}
|
||||
inner(path, bytes, delete_on_drop)
|
||||
.chain_err_summary(|| format!("Could not create file at path {}", path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_file_invalid_path() {
|
||||
let f = File {
|
||||
path: PathBuf::from("//////"),
|
||||
delete_on_drop: true,
|
||||
};
|
||||
f.as_std_file().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_delete_on_drop() {
|
||||
const S: &str = "hello world";
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
|
||||
let delete_on_drop = File::create_temp_file(
|
||||
S.as_bytes(),
|
||||
None,
|
||||
Some(&mut tempdir.path().join("test")),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&delete_on_drop.read_to_string().unwrap(), S);
|
||||
drop(delete_on_drop);
|
||||
assert!(!tempdir.path().join("test").try_exists().unwrap());
|
||||
|
||||
let persist = File::create_temp_file(
|
||||
S.as_bytes(),
|
||||
None,
|
||||
Some(&mut tempdir.path().join("test")),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&persist.read_to_string().unwrap(), S);
|
||||
drop(persist);
|
||||
assert!(tempdir.path().join("test").try_exists().unwrap());
|
||||
|
||||
_ = tempdir.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ nom = { version = "7" }
|
|||
notify = { version = "4.0.15", optional = true }
|
||||
polling = "2.8"
|
||||
regex = { version = "1" }
|
||||
rusqlite = { version = "^0.29", default-features = false, optional = true }
|
||||
rusqlite = { version = "^0.29", default-features = false, features = ["array"], optional = true }
|
||||
serde = { version = "1.0", features = ["rc"] }
|
||||
serde_derive = "1.0"
|
||||
serde_json = { version = "1.0", features = ["raw_value"] }
|
||||
|
|
|
@ -92,6 +92,13 @@ pub trait ImapCache: Send + std::fmt::Debug {
|
|||
identifier: std::result::Result<UID, EnvelopeHash>,
|
||||
mailbox_hash: MailboxHash,
|
||||
) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
fn update_flags(
|
||||
&mut self,
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
mailbox_hash: MailboxHash,
|
||||
flags: SmallVec<[FlagOp; 8]>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait ImapCacheReset: Send + std::fmt::Debug {
|
||||
|
@ -108,7 +115,7 @@ pub mod sqlite3_m {
|
|||
use super::*;
|
||||
use crate::utils::sqlite3::{
|
||||
self,
|
||||
rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput},
|
||||
rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Value},
|
||||
Connection, DatabaseDescription,
|
||||
};
|
||||
|
||||
|
@ -151,6 +158,12 @@ pub mod sqlite3_m {
|
|||
version: 3,
|
||||
};
|
||||
|
||||
impl From<EnvelopeHash> for Value {
|
||||
fn from(env_hash: EnvelopeHash) -> Self {
|
||||
(env_hash.0 as i64).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for ModSequence {
|
||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
|
||||
Ok(ToSqlOutput::from(self.0.get() as i64))
|
||||
|
@ -539,6 +552,67 @@ pub mod sqlite3_m {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn update_flags(
|
||||
&mut self,
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
mailbox_hash: MailboxHash,
|
||||
flags: SmallVec<[FlagOp; 8]>,
|
||||
) -> Result<()> {
|
||||
if self.mailbox_state(mailbox_hash)?.is_none() {
|
||||
return Err(Error::new("Mailbox is not in cache").set_kind(ErrorKind::Bug));
|
||||
}
|
||||
let Self {
|
||||
ref mut connection,
|
||||
ref uid_store,
|
||||
loaded_mailboxes: _,
|
||||
} = self;
|
||||
let tx = connection.transaction()?;
|
||||
let values =
|
||||
std::rc::Rc::new(env_hashes.iter().map(Value::from).collect::<Vec<Value>>());
|
||||
|
||||
let mut stmt =
|
||||
tx.prepare("SELECT uid, envelope FROM envelopes WHERE hash IN rarray(?1);")?;
|
||||
let rows = stmt
|
||||
.query_map([values], |row| Ok((row.get(0)?, row.get(1)?)))?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect::<Vec<(UID, Envelope)>>();
|
||||
drop(stmt);
|
||||
let mut stmt = tx.prepare(
|
||||
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;",
|
||||
)?;
|
||||
for (uid, mut env) in rows {
|
||||
for op in flags.iter() {
|
||||
match op {
|
||||
FlagOp::UnSet(flag) | FlagOp::Set(flag) => {
|
||||
let mut f = env.flags();
|
||||
f.set(*flag, op.as_bool());
|
||||
env.set_flags(f);
|
||||
}
|
||||
FlagOp::UnSetTag(tag) | FlagOp::SetTag(tag) => {
|
||||
let hash = TagHash::from_bytes(tag.as_bytes());
|
||||
if op.as_bool() {
|
||||
env.tags_mut().insert(hash);
|
||||
} else {
|
||||
env.tags_mut().remove(&hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stmt.execute(sqlite3::params![&env, mailbox_hash, uid as Sqlite3UID])?;
|
||||
uid_store
|
||||
.envelopes
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(env.hash())
|
||||
.and_modify(|entry| {
|
||||
entry.inner = env;
|
||||
});
|
||||
}
|
||||
drop(stmt);
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
mailbox_hash: MailboxHash,
|
||||
|
@ -829,5 +903,14 @@ pub mod default_m {
|
|||
) -> Result<Option<Vec<u8>>> {
|
||||
Err(Error::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug))
|
||||
}
|
||||
|
||||
fn update_flags(
|
||||
&mut self,
|
||||
_env_hashes: EnvelopeHashBatch,
|
||||
_mailbox_hash: MailboxHash,
|
||||
_flags: SmallVec<[FlagOp; 8]>,
|
||||
) -> Result<()> {
|
||||
Err(Error::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -857,6 +857,12 @@ impl MailBackend for ImapType {
|
|||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "sqlite3")]
|
||||
if *uid_store.keep_offline_cache.lock().unwrap() {
|
||||
let mut cache_handle = cache::Sqlite3Cache::get(uid_store.clone())?;
|
||||
let res = cache_handle.update_flags(env_hashes, mailbox_hash, flags);
|
||||
log::trace!("update_flags in cache: {:?}", res);
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -102,11 +102,6 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
mailbox_hash,
|
||||
kind: RefreshEventKind::Rescan,
|
||||
});
|
||||
/*
|
||||
uid_store.uid_index.lock().unwrap().clear();
|
||||
uid_store.hash_index.lock().unwrap().clear();
|
||||
uid_store.byte_cache.lock().unwrap().clear();
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
uidvalidities.insert(mailbox_hash, select_response.uidvalidity);
|
||||
|
|
|
@ -45,7 +45,10 @@ pub fn open_db(db_path: PathBuf) -> Result<Connection> {
|
|||
if !db_path.exists() {
|
||||
return Err(Error::new("Database doesn't exist"));
|
||||
}
|
||||
Connection::open(&db_path).map_err(|e| Error::new(e.to_string()))
|
||||
Ok(Connection::open(&db_path).and_then(|db| {
|
||||
rusqlite::vtab::array::load_module(&db)?;
|
||||
Ok(db)
|
||||
})?)
|
||||
}
|
||||
|
||||
pub fn open_or_create_db(
|
||||
|
@ -66,7 +69,8 @@ pub fn open_or_create_db(
|
|||
db_path.display()
|
||||
);
|
||||
}
|
||||
let conn = Connection::open(&db_path).map_err(|e| Error::new(e.to_string()))?;
|
||||
let conn = Connection::open(&db_path)?;
|
||||
rusqlite::vtab::array::load_module(&conn)?;
|
||||
if set_mode {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let file = std::fs::File::open(&db_path)?;
|
||||
|
|
Loading…
Reference in New Issue