Skip to content

Commit

Permalink
Basic eml/mbox import with ImportMailService instead of DraftService
Browse files Browse the repository at this point in the history
Co-authored-by: jomapp <17314077+jomapp@users.noreply.github.com>
  • Loading branch information
tuta-sudipg and jomapp committed Oct 28, 2024
1 parent 322ad01 commit b8ebada
Show file tree
Hide file tree
Showing 42 changed files with 915 additions and 2,215 deletions.
12 changes: 4 additions & 8 deletions packages/node-mimimi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ rand = { version = "0.8.5" }
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", rev = "54ca7df" }
thiserror = { version = "1.0.64" }
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.16.12", default-features = false, features = ["napi9", "async", "tokio_rt"], optional = true }
napi = { version = "2.16.12", default-features = false, features = ["napi9", "async", "tokio_rt"] }
napi-derive = { version = "2.16.12", optional = true }

# used for tuta-imap
Expand Down Expand Up @@ -48,12 +48,8 @@ lazy_static = { version = "0.2.11" }
[features]
default = ["javascript"]
# needed to turn off the autogenerated ffi when using the examples
javascript = ["dep:napi", "dep:napi-derive"]
javascript = ["dep:napi-derive"]

[[example]]
name = "http_request"
path = "examples/http_request.rs"

[[example]]
name = "imap_mail_as_draft"
path = "examples/imap_mail_as_draft.rs"
name = "import_imap_mail"
path = "examples/import_imap_mail.rs"
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// How to: Import Imap mail from greenmail to tutanota
/// How to: Import IMAP mail from Greenmail to TutaMail
///
///
/// 1. Start GreenMail server
Expand Down Expand Up @@ -33,8 +33,8 @@ async fn main() {
import_client::ImapImport, ImapCredentials, ImapImportConfig, LoginMechanism,
};
use tutao_node_mimimi::importer::{ImportSource, ImportState, Importer};
use tutasdk::folder_system::MailSetKind;
use tutasdk::net::native_rest_client::NativeRestClient;
use tutasdk::IdTuple;
use tutasdk::Sdk;

let sdk = Sdk::new(
Expand All @@ -61,15 +61,21 @@ async fn main() {
imap_import_client: ImapImport::new(imap_import_config),
};

let mail_facade = logged_in_sdk.mail_facade();
let mailbox = mail_facade.load_user_mailbox().await.unwrap();
let folders = mail_facade
.load_folders_for_mailbox(&mailbox)
.await
.unwrap();
let inbox_folder = folders
.system_folder_by_type(MailSetKind::Inbox)
.expect("inbox should exist");

let mut importer = Importer::new(
logged_in_sdk,
import_source,
"map-free@tutanota.de".to_string(),
// todo!("How to get target mail folder id")
IdTuple {
list_id: Default::default(),
element_id: Default::default(),
},
inbox_folder._id.clone(),
);

let import_status = importer
Expand Down
71 changes: 39 additions & 32 deletions packages/node-mimimi/src/importer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::importer::file_reader::import_client::{FileImport, FileIterationError
use crate::importer::imap_reader::import_client::{ImapImport, ImapIterationError};
use crate::importer::imap_reader::ImapImportConfig;
use crate::importer::importable_mail::ImportableMail;
use std::future::Future;
use std::sync::Arc;
use tutasdk::crypto::aes::Iv;
use tutasdk::crypto::key::{GenericAesKey, VersionedAesKey};
Expand All @@ -13,13 +12,6 @@ use tutasdk::services::ExtraServiceParams;
use tutasdk::{ApiCallError, IdTuple, LoggedInSdk};

pub type NapiTokioMutex<T> = napi::tokio::sync::Mutex<T>;
pub type NapiResult<T> = napi::Result<T>;

/// A handle, once imap/file reader get the importable mail,
/// this handle is responsible to do the import to server,
/// Returns a boolean, indicating if this import was successful or not.
// todo: return more verbose status
pub type ImporterHandle = Box<dyn Fn(ImportableMail) -> Box<dyn Future<Output = bool>>>;

pub mod file_reader;
pub mod imap_reader;
Expand Down Expand Up @@ -196,7 +188,7 @@ impl ImporterInner {
.get_service_executor()
.post::<ImportMailService>(import_mail_post_in, service_params)
.await
.expect("Cannot execute DraftService");
.expect("Cannot execute ImportMailService");

Ok(import_mail_post_out)
}
Expand Down Expand Up @@ -283,15 +275,36 @@ mod tests {
use crate::importer::imap_reader::{ImapCredentials, LoginMechanism};
use crate::tuta_imap::testing::GreenMailTestServer;
use mail_builder::MessageBuilder;
use tutasdk::entities::tutanota::MailFolder;
use tutasdk::folder_system::MailSetKind;
use tutasdk::net::native_rest_client::NativeRestClient;
use tutasdk::Sdk;

// todo: implement a way to get any folder id ( or create a new one for every test? )
async fn get_test_import_folder_id() -> IdTuple {
IdTuple {
list_id: Default::default(),
element_id: Default::default(),
}
fn sample_email(subject: String) -> String {
let email = MessageBuilder::new()
.from(("Matthias", "map@example.org"))
.to(("Johannes", "jmp@example.org"))
.subject(subject)
.text_body("Hello tutao! this is the first step to have email import.Want to see html 😀?<p style='color:red'>red</p>")
.write_to_string()
.unwrap();
email
}

async fn get_test_import_folder_id(
logged_in_sdk: &Arc<LoggedInSdk>,
kind: MailSetKind,
) -> MailFolder {
let mail_facade = logged_in_sdk.mail_facade();
let mailbox = mail_facade.load_user_mailbox().await.unwrap();
let folders = mail_facade
.load_folders_for_mailbox(&mailbox)
.await
.unwrap();
folders
.system_folder_by_type(kind)
.expect("inbox should exist")
.clone()
}

async fn init_imap_importer() -> (ImporterInner, GreenMailTestServer) {
Expand Down Expand Up @@ -320,11 +333,13 @@ mod tests {
imap_import_client: ImapImport::new(imap_import_config),
};
let randomizer_facade = RandomizerFacade::from_core(rand::rngs::OsRng);
let target_mail_folder_id = get_test_import_folder_id(&logged_in_sdk, MailSetKind::Archive)
.await
._id;

let importer = ImporterInner {
importer_mail_address,
// todo: find a way to get inbox folder id?
target_mail_folder_id: get_test_import_folder_id().await,
target_mail_folder_id,
logged_in_sdk,
import_source,
randomizer_facade,
Expand All @@ -347,15 +362,18 @@ mod tests {
fs_email_client: FileImport::new(file_path, is_mbox),
};
let randomizer_facade = RandomizerFacade::from_core(rand::rngs::OsRng);
let import_inner = ImporterInner {
let target_mail_folder_id = get_test_import_folder_id(&logged_in_sdk, MailSetKind::Archive)
.await
._id;

ImporterInner {
status: ImportStatus::default(),
importer_mail_address: "map-free@tutanota.de".to_string(),
target_mail_folder_id: get_test_import_folder_id().await,
target_mail_folder_id,
logged_in_sdk,
import_source,
randomizer_facade,
};
import_inner
}
}

#[tokio::test]
Expand All @@ -377,17 +395,6 @@ mod tests {
);
}

fn sample_email(subject: String) -> String {
let email = MessageBuilder::new()
.from(("Matthias", "map@example.org"))
.to(("Johannes", "jmp@example.org"))
.subject(subject)
.text_body("Hello tutao! this is the first step to have email import.Want to see html 😀?<p style='color:red'>red</p>")
.write_to_string()
.unwrap();
email
}

#[tokio::test]
pub async fn import_single_from_imap_default_folder() {
let (mut importer, greenmail) = init_imap_importer().await;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,8 @@ impl Iterator for ImportableMailProvider {

let parsed_message = MessageParser::default()
.parse(message_contents.as_slice())
.expect(&format!(
"Cannot parse: \n{}",
String::from_utf8(message_contents.to_vec()).unwrap()
));
.unwrap_or_else(|| panic!("Cannot parse: \n{}",
String::from_utf8(message_contents.to_vec()).unwrap()));
// {
// Some(parsed_message) => parsed_message,
// None => return Some(Err(FileIterationError::MessageParseError)),
Expand Down
17 changes: 8 additions & 9 deletions packages/node-mimimi/src/importer/imap_reader/import_client.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::importer::imap_reader::{ImapImportConfig, LoginMechanism};
use crate::importer::importable_mail::ImportableMail;
use crate::importer::importable_mail::{ImportableMail, MailParseError};
use crate::tuta_imap::client::TutaImapClient;
use imap_codec::imap_types::mailbox::Mailbox;
use imap_codec::imap_types::response::StatusKind;
use std::borrow::Borrow;
use std::num::NonZeroU32;

pub struct ImapImport {
Expand Down Expand Up @@ -48,7 +47,7 @@ pub enum ImapIterationError {
NonOkCommandStatus,

/// Can not convert ImapMail to ConvertableMail
NonImportableMail,
MailParseError(MailParseError),

/// Can not login to imap server
NoLogin,
Expand All @@ -60,7 +59,9 @@ impl ImapImport {
import_config.credentials.host.as_str(),
import_config.credentials.port,
);
let imap_import = Self {


Self {
imap_client,
import_config,

Expand All @@ -70,9 +71,7 @@ impl ImapImport {
current_mailbox: None,
fetched_from_current_mailbox: vec![],
},
};

imap_import
}
}

/// High level abstraction to read next mail from imap,
Expand Down Expand Up @@ -114,9 +113,9 @@ impl ImapImport {

// search results of *search_all_uid* is not empty,
// meaning we completed this folder
if self.imap_client.latest_search_results.is_empty() {}
self.imap_client.latest_search_results.is_empty();

ImportableMail::try_from(next_mail_imap).map_err(|()| ImapIterationError::NonImportableMail)
ImportableMail::try_from(next_mail_imap).map_err(ImapIterationError::MailParseError)
}

fn ensure_mailbox_selected(&mut self) -> Result<(), ImapIterationError> {
Expand Down
Loading

0 comments on commit b8ebada

Please sign in to comment.