diff --git a/docs/meli.1 b/docs/meli.1 index ccccd12d2..b77c240b4 100644 --- a/docs/meli.1 +++ b/docs/meli.1 @@ -418,6 +418,8 @@ Copy or move to other mailbox. Copy or move to another account's mailbox. .It Cm delete Delete selected threads. +.It Cm export-mbox Ar FILEPATH +Export selected threads to mboxcl2 file. .It Cm create-mailbox Ar ACCOUNT Ar MAILBOX_PATH create mailbox with given path. Be careful with backends and separator sensitivity (eg IMAP) diff --git a/src/command.rs b/src/command.rs index 2ad88fd35..589c8839c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -419,6 +419,19 @@ define_commands!([ } ) }, + { tags: ["export-mbox "], + desc: "export-mbox PATH", + tokens: &[One(Literal("export-mbox")), One(Filepath)], + parser:( + fn export_mbox(input: &[u8]) -> IResult<&[u8], Action> { + let (input, _) = tag("export-mbox")(input.trim())?; + let (input, _) = is_a(" ")(input)?; + let (input, path) = quoted_argument(input.trim())?; + let (input, _) = eof(input)?; + Ok((input, Listing(ExportMbox(Some(melib::backends::mbox::MboxFormat::MboxCl2), path.to_string().into())))) + } + ) + }, { tags: ["list-archive", "list-post", "list-unsubscribe", "list-"], desc: "list-[unsubscribe/post/archive]", tokens: &[One(Alternatives(&[to_stream!(One(Literal("list-archive"))), to_stream!(One(Literal("list-post"))), to_stream!(One(Literal("list-unsubscribe")))]))], @@ -852,6 +865,7 @@ fn listing_action(input: &[u8]) -> IResult<&[u8], Action> { select, toggle_thread_snooze, open_in_new_tab, + export_mbox, _tag, ))(input) } diff --git a/src/command/actions.rs b/src/command/actions.rs index 85895644d..616fb00a0 100644 --- a/src/command/actions.rs +++ b/src/command/actions.rs @@ -51,6 +51,7 @@ pub enum ListingAction { MoveTo(MailboxPath), MoveToOtherAccount(AccountName, MailboxPath), Import(PathBuf, MailboxPath), + ExportMbox(Option, PathBuf), Delete, OpenInNewTab, Tag(TagAction), diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs index b31ef62ce..2a49b2184 100644 --- a/src/components/mail/listing.rs +++ b/src/components/mail/listing.rs @@ -342,6 +342,87 @@ pub trait MailListingTrait: ListingTrait { } } } + ListingAction::ExportMbox(format, ref path) => { + use futures::future::try_join_all; + use std::future::Future; + use std::io::Write; + use std::pin::Pin; + + let futures: Result> = envs_to_set + .iter() + .map(|&env_hash| account.operation(env_hash).and_then(|mut op| op.as_bytes())) + .collect::>>(); + let path_ = path.to_path_buf(); + let format = format.clone().unwrap_or_default(); + let collection = account.collection.clone(); + let (sender, mut receiver) = crate::jobs::oneshot::channel(); + let fut: Pin> + Send + 'static>> = + Box::pin(async move { + let cl = async move { + let bytes: Vec> = try_join_all(futures?).await?; + let envs: Vec<_> = envs_to_set + .iter() + .map(|&env_hash| collection.get_env(env_hash)) + .collect(); + let mut file = std::io::BufWriter::new(std::fs::File::create(&path_)?); + let mut iter = envs.iter().zip(bytes.into_iter()); + if let Some((env, ref bytes)) = iter.next() { + format.append( + &mut file, + bytes.as_slice(), + env.from().get(0), + Some(env.date()), + true, + false, + )?; + } + for (env, bytes) in iter { + format.append( + &mut file, + bytes.as_slice(), + env.from().get(0), + Some(env.date()), + false, + false, + )?; + } + file.flush()?; + Ok(()) + }; + let r: Result<()> = cl.await; + let _ = sender.send(r); + Ok(()) + }); + let handle = account.job_executor.spawn_blocking(fut); + let path = path.to_path_buf(); + account.insert_job( + handle.job_id, + JobRequest::Generic { + name: "exporting mbox".into(), + handle, + on_finish: Some(CallbackFn(Box::new(move |context: &mut Context| { + context.replies.push_back(match receiver.try_recv() { + Err(_) | Ok(None) => UIEvent::Notification( + Some("Could not export mbox".to_string()), + "Job was canceled.".to_string(), + Some(NotificationType::Info), + ), + Ok(Some(Err(err))) => UIEvent::Notification( + Some("Could not export mbox".to_string()), + err.to_string(), + Some(NotificationType::Error(err.kind)), + ), + Ok(Some(Ok(()))) => UIEvent::Notification( + Some("Succesfully exported mbox".to_string()), + format!("Wrote to file {}", path.display()), + Some(NotificationType::Info), + ), + }); + }))), + logging_level: melib::LoggingLevel::INFO, + }, + ); + } ListingAction::MoveToOtherAccount(ref _account_name, ref _mailbox_path) => { context .replies @@ -967,6 +1048,7 @@ impl Component for Listing { | Action::Listing(a @ ListingAction::MoveTo(_)) | Action::Listing(a @ ListingAction::CopyToOtherAccount(_, _)) | Action::Listing(a @ ListingAction::MoveToOtherAccount(_, _)) + | Action::Listing(a @ ListingAction::ExportMbox(_, _)) | Action::Listing(a @ ListingAction::Tag(_)) => { let focused = self.component.get_focused_items(context); self.component.perform_action(context, focused, a);