From 4089bbb311a81426eaa172bdcee9b4a7d290d1bc Mon Sep 17 00:00:00 2001 From: abp Date: Thu, 30 Jan 2025 14:09:56 +0100 Subject: [PATCH 01/23] fix selected target folder defaulting to INBOX after changing view Co-authored-by: jhm <17314077+jomapp@users.noreply.github.com> --- src/mail-app/mail/import/MailImporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mail-app/mail/import/MailImporter.ts b/src/mail-app/mail/import/MailImporter.ts index a773b76b7b51..83b3ef18ee33 100644 --- a/src/mail-app/mail/import/MailImporter.ts +++ b/src/mail-app/mail/import/MailImporter.ts @@ -74,7 +74,6 @@ export class MailImporter { const importFacade = assertNotNull(this.nativeMailImportFacade) const mailbox = await this.getMailbox() this.foldersForMailbox = this.getFoldersForMailGroup(assertNotNull(mailbox._ownerGroup)) - this.selectedTargetFolder = this.foldersForMailbox.getSystemFolderByType(MailSetKind.INBOX) let activeImportId: IdTuple | null = null if (this.activeImport === null) { @@ -82,6 +81,7 @@ export class MailImporter { const userId = this.loginController.getUserController().userId const unencryptedCredentials = assertNotNull(await this.credentialsProvider?.getDecryptedCredentialsByUserId(userId)) const apiUrl = getApiBaseUrl(this.domainConfigProvider.getCurrentDomainConfig()) + this.selectedTargetFolder = this.foldersForMailbox.getSystemFolderByType(MailSetKind.INBOX) try { activeImportId = await importFacade.getResumableImport(mailbox._id, mailOwnerGroupId, unencryptedCredentials, apiUrl) From 6c2827eb15f356624e0500ce2e708fb96e087cff Mon Sep 17 00:00:00 2001 From: sug Date: Tue, 28 Jan 2025 15:17:21 +0100 Subject: [PATCH 02/23] [mimimi] rename make_import_directory to indicate it's creating a path --- packages/node-mimimi/src/importer.rs | 3 ++- packages/node-mimimi/src/importer/file_reader.rs | 5 +++-- packages/node-mimimi/src/importer_api.rs | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/node-mimimi/src/importer.rs b/packages/node-mimimi/src/importer.rs index 40c7f5efeb6b..44d04d9d66cd 100644 --- a/packages/node-mimimi/src/importer.rs +++ b/packages/node-mimimi/src/importer.rs @@ -487,7 +487,8 @@ impl Importer { tuta_credentials: TutaCredentials, import_state_id: IdTupleGenerated, ) -> Result { - let import_directory = FileImport::make_import_directory(&config_directory, mailbox_id); + let import_directory = + FileImport::make_import_directory_path(&config_directory, mailbox_id); let eml_files_to_import = Self::eml_files_in_directory(import_directory.as_path()) .map_err(|_| PreparationError::FailedToReadEmls)?; diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index 211976083b95..67dc8af8b3a0 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -65,7 +65,8 @@ impl FileImport { mailbox_id: &str, source_paths: impl Iterator, ) -> Result { - let import_directory_path = FileImport::make_import_directory(config_directory, mailbox_id); + let import_directory_path = + FileImport::make_import_directory_path(config_directory, mailbox_id); let failed_sub_directory_path = import_directory_path.join(FAILED_MAILS_SUB_DIR); let mut filename_producer = FileNameProducer::new(import_directory_path.as_path()); @@ -167,7 +168,7 @@ impl FileImport { fs::remove_file(import_dir.join(STATE_ID_FILE_NAME)) } - pub fn make_import_directory(config_directory: &str, mailbox_id: &str) -> PathBuf { + pub fn make_import_directory_path(config_directory: &str, mailbox_id: &str) -> PathBuf { [ config_directory.to_string(), "current_imports".into(), diff --git a/packages/node-mimimi/src/importer_api.rs b/packages/node-mimimi/src/importer_api.rs index a9f9cc73da4b..c34d1be965ad 100644 --- a/packages/node-mimimi/src/importer_api.rs +++ b/packages/node-mimimi/src/importer_api.rs @@ -45,7 +45,8 @@ impl ImporterApi { tuta_credentials: TutaCredentials, ) -> napi::Result> { let target_owner_group = GeneratedId(target_owner_group); - let import_directory = FileImport::make_import_directory(&config_directory, &mailbox_id); + let import_directory = + FileImport::make_import_directory_path(&config_directory, &mailbox_id); let existing_import = Importer::get_existing_import_id(&import_directory) .map_err(|_| PreparationError::CannotReadOldStateId)?; From 66dad7d5f22e8ba00517df799351196d292cfd48 Mon Sep 17 00:00:00 2001 From: sug Date: Tue, 28 Jan 2025 15:18:08 +0100 Subject: [PATCH 03/23] [mimimi] improve error state for import UI the progress estimation for failed imports was not stopped. this gave the impression that the import was still ongoing. we now reset the state if an error happens during import preparation and will pause the running import if an async error happens. this will allow the user to try to resume it and see the snackbar or notification if the error happens again. also adds some local state cleanup if the import fails before being set up on the server --- .../node-mimimi/src/importer/file_reader.rs | 2 +- .../src/importer/filename_producer.rs | 5 +- packages/node-mimimi/src/importer_api.rs | 55 ++++++++++++++----- .../mailimport/DesktopMailImportFacade.ts | 34 ++++++++---- src/mail-app/mail/import/MailImporter.ts | 11 +++- 5 files changed, 75 insertions(+), 32 deletions(-) diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index 67dc8af8b3a0..83a0e1257f15 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -154,7 +154,7 @@ impl FileImport { } /// recursively deletes the given directory and its contents - pub fn delete_dir_if_exists(target_dir: &PathBuf) -> std::io::Result<()> { + pub fn delete_dir_if_exists(target_dir: &Path) -> std::io::Result<()> { target_dir .exists() .then(|| fs::remove_dir_all(target_dir)) diff --git a/packages/node-mimimi/src/importer/filename_producer.rs b/packages/node-mimimi/src/importer/filename_producer.rs index 25f8e008676f..4019b827d046 100644 --- a/packages/node-mimimi/src/importer/filename_producer.rs +++ b/packages/node-mimimi/src/importer/filename_producer.rs @@ -22,7 +22,7 @@ impl<'a> FileNameProducer<'a> { } pub fn new_plain_eml(&mut self, eml_path: &Path) -> PathBuf { - let original_filename = Self::format_new_file_name(&eml_path, self.eml_file_count, ".eml"); + let original_filename = Self::format_new_file_name(eml_path, self.eml_file_count, ".eml"); self.eml_file_count += 1; @@ -30,7 +30,8 @@ impl<'a> FileNameProducer<'a> { } pub fn new_mbox(&mut self, mbox_path: &Path) { - let original_filename = Self::format_new_file_name(&mbox_path, self.mbox_file_count, ".mbox-item-"); + let original_filename = + Self::format_new_file_name(mbox_path, self.mbox_file_count, ".mbox-item-"); self.mbox_file_count += 1; diff --git a/packages/node-mimimi/src/importer_api.rs b/packages/node-mimimi/src/importer_api.rs index c34d1be965ad..9b85a0882e6b 100644 --- a/packages/node-mimimi/src/importer_api.rs +++ b/packages/node-mimimi/src/importer_api.rs @@ -98,23 +98,28 @@ impl ImporterApi { GeneratedId(target_mailset_eid), ); - let import_directory = - FileImport::prepare_file_import(&config_directory, &mailbox_id, source_paths)?; - - let logged_in_sdk = Importer::create_sdk(tuta_credentials).await?; - let importer = Importer::create_new_file_importer( - logged_in_sdk, + let preparation_result = Self::prepare_new_import_inner( + &mailbox_id, + tuta_credentials, target_owner_group, target_mailset, - import_directory, + source_paths, + &config_directory, ) - .await?; - - Ok(ImporterApi { - importer: Arc::new(importer), - importer_loop_handle: None, - message_handler: None, - }) + .await; + match preparation_result { + Ok(importer) => Ok(ImporterApi { + importer: Arc::new(importer), + importer_loop_handle: None, + message_handler: None, + }), + Err(prep_err) => { + let import_directory_path = + FileImport::make_import_directory_path(&config_directory, &mailbox_id); + FileImport::delete_dir_if_exists(&import_directory_path).ok(); + Err(prep_err.into()) + }, + } } /// set a new state for the next import loop. the current upload will be finished before @@ -221,6 +226,28 @@ impl ImporterApi { } impl ImporterApi { + /// extracts the fallible operations out so we can do some common cleanup if one of them fails + async fn prepare_new_import_inner( + mailbox_id: &str, + tuta_credentials: TutaCredentials, + target_owner_group: GeneratedId, + target_mailset: IdTupleGenerated, + source_paths: impl Iterator, + config_directory: &str, + ) -> Result { + let import_directory = + FileImport::prepare_file_import(config_directory, mailbox_id, source_paths)?; + + let logged_in_sdk = Importer::create_sdk(tuta_credentials).await?; + Importer::create_new_file_importer( + logged_in_sdk, + target_owner_group, + target_mailset, + import_directory.clone(), + ) + .await + } + fn spawn_importer_task(&mut self) -> napi::tokio::task::JoinHandle { let importer = Arc::clone(&self.importer); let error_handler = self.message_handler.clone(); diff --git a/src/common/desktop/mailimport/DesktopMailImportFacade.ts b/src/common/desktop/mailimport/DesktopMailImportFacade.ts index 8cc48117aea4..753c4b23cc29 100644 --- a/src/common/desktop/mailimport/DesktopMailImportFacade.ts +++ b/src/common/desktop/mailimport/DesktopMailImportFacade.ts @@ -147,13 +147,14 @@ export class DesktopMailImportFacade implements NativeMailImportFacade { filePaths.slice(), this.configDirectory, ) + // we want an unconditional error handler, but also don't want to change the type of the promise. + importerApiPromise.catch((_) => this.importerApis.delete(mailboxId)) this.importerApis.set(mailboxId, importerApiPromise) - let importerApi: ImporterApi | null = null try { importerApi = await importerApiPromise } catch (e) { - this.importerApis.delete(mailboxId) + this.showImportFailNotification(null) throw new MailImportError(mimimiErrorToImportErrorData(e)) } importerApi.setMessageHook((message: MailImportMessage) => this.processMimimiMessage(mailboxId, message)) @@ -229,26 +230,35 @@ export class DesktopMailImportFacade implements NativeMailImportFacade { if (errorData.category === ImportErrorCategories.ImportIncomplete) { this.importerApis.delete(mailboxId) } + this.showImportFailNotification(mailboxId) + let listeners = this.currentListeners.get(mailboxId) + if (listeners != null) { + for (const listener of listeners) { + const mailImportError = new MailImportError(errorData) + listener(mailImportError) + } + clear(listeners) + } + } + + /** + * show a system notification (even if there are currently no windows) + * + * @param mailboxId this is the name of the import subdirectory we show on click. if null, the notification does nothing, + * for example if the directory hasn't been created yet. + */ + private showImportFailNotification(mailboxId: string | null) { this.notifier .showOneShot({ title: this.lang.get("importIncomplete_title"), body: this.lang.get("importIncomplete_msg"), }) .then((res) => { - if (res === NotificationResult.Click) { + if (res === NotificationResult.Click && mailboxId != null) { this.electron.shell.showItemInFolder(path.join(this.configDirectory, "current_imports", mailboxId, "dummy.eml")) } }) - - let listeners = this.currentListeners.get(mailboxId) - if (listeners != null) { - for (const listener of listeners) { - const mailImportError = new MailImportError(errorData) - listener(mailImportError) - } - clear(listeners) - } } private createTutaCredentials(unencTutaCredentials: UnencryptedCredentials, apiUrl: string) { diff --git a/src/mail-app/mail/import/MailImporter.ts b/src/mail-app/mail/import/MailImporter.ts index 83b3ef18ee33..4f0df14e83b5 100644 --- a/src/mail-app/mail/import/MailImporter.ts +++ b/src/mail-app/mail/import/MailImporter.ts @@ -202,10 +202,11 @@ export class MailImporter { } private async handleError(err: MailImportError) { + if (this.activeImport) { + this.activeImport.uiStatus = UiImportStatus.Paused + this.activeImport.progressMonitor.pauseEstimation() + } if (err.data.category == ImportErrorCategories.ImportFeatureDisabled) { - if (this.activeImport) { - this.activeImport.uiStatus = UiImportStatus.Paused - } await Dialog.message("mailImportErrorServiceUnavailable_msg") } else if (err.data.category == ImportErrorCategories.ConcurrentImport) { console.log("Tried to start concurrent import") @@ -263,6 +264,9 @@ export class MailImporter { apiUrl, ) } catch (e) { + this.resetStatus() + m.redraw() + if (e instanceof MailImportError) { this.handleError(e).catch() } else { @@ -413,6 +417,7 @@ export class MailImporter { } private resetStatus() { + this.activeImport?.progressMonitor?.pauseEstimation() this.activeImport = null } From 3e5e33a8a7f666914dd7b956261ffaf800a1f4aa Mon Sep 17 00:00:00 2001 From: sug Date: Tue, 28 Jan 2025 16:21:58 +0100 Subject: [PATCH 04/23] [mimimi] more closely track how many mails failed to import previously, we didn't increment the counter for all failure cases, leading to the finished import showing 0 failed mails even though some actually failed. we still leave some edge cases where we fail to import a mail but also fail to mark it as failed. --- packages/node-mimimi/src/importer.rs | 90 +++++++++++-------- .../node-mimimi/src/importer/file_reader.rs | 20 +++-- 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/packages/node-mimimi/src/importer.rs b/packages/node-mimimi/src/importer.rs index 44d04d9d66cd..9d85ed407790 100644 --- a/packages/node-mimimi/src/importer.rs +++ b/packages/node-mimimi/src/importer.rs @@ -138,14 +138,6 @@ impl ImportEssential { &self, updater: impl Fn(&mut ImportMailState) -> bool, ) -> Result<(), MailImportErrorMessage> { - // to-review: - // we should prevent race condition while reading and writing remote state - // example: we might update spawn new task in ImporterApi::set_progress_action which - // finished immediately and tried to update the server, and in ImporterApi::set_progress_action itself, - // we also try to update the remote server. and these two update might not be synchronised - // hence: we should keep some mutex lock while we have importMailState - // and should only update when that lock is free - let mut server_state = self .load_remote_state() .await @@ -469,15 +461,16 @@ impl Importer { } /// check the given directory for any failed mail files that have been left behind during iteration - pub(crate) fn have_failed_mails(import_directory: &Path) -> std::io::Result { + pub(crate) fn get_failed_mails_count(import_directory: &Path) -> std::io::Result { let failed_sub_dir = import_directory.join(FAILED_MAILS_SUB_DIR); - let have_failed_mails = fs::read_dir(failed_sub_dir)? + let failed_mails_count = fs::read_dir(failed_sub_dir)? .collect::>>()? .iter() .map(DirEntry::path) - .any(|path| path.extension() == Some(OsStr::new("eml"))); + .filter(|path| path.extension() == Some(OsStr::new("eml"))) + .count(); - Ok(have_failed_mails) + Ok(failed_mails_count) } pub(super) async fn resume_file_importer( @@ -615,23 +608,14 @@ impl Importer { None => Ok(None), // this chunk was too big to import - Some(Err(too_big_chunk)) => { - self.essentials - .update_remote_state(|remote_state| { - remote_state.failedMails += 1; - true - }) - .await?; - - Err(MailImportErrorMessage { - kind: ImportErrorKind::TooBigChunk, - path: too_big_chunk - .keyed_import_mail_data - .eml_file_path - .map(|path| path.to_string_lossy().to_string()) - .clone(), - })? - }, + Some(Err(too_big_chunk)) => Err(MailImportErrorMessage { + kind: ImportErrorKind::TooBigChunk, + path: too_big_chunk + .keyed_import_mail_data + .eml_file_path + .map(|path| path.to_string_lossy().to_string()) + .clone(), + })?, // these chunks can be imported in single request Some(Ok(chunked_import_data)) => { @@ -692,6 +676,7 @@ impl Importer { return Ok(ImportOkKind::UserPauseInterruption); }, ImportProgressAction::Stop => { + self.update_failed_mails_counter().await; return Ok(ImportOkKind::UserCancelInterruption); }, @@ -699,6 +684,10 @@ impl Importer { let import_chunk_res = self.import_next_chunk().await; match import_chunk_res { Ok(None) => { + self.update_failed_mails_counter().await; + self.set_remote_import_status(ImportStatus::Finished) + .await?; + // deleting the state file is enough to mark there is no import running, // do not delete the whole directory because we will leave some un-importable file // in the directory. and users should be able to inspect those @@ -709,14 +698,14 @@ impl Importer { self.essentials.import_directory.clone(), ) })?; - self.set_remote_import_status(ImportStatus::Finished) - .await?; - return if Importer::have_failed_mails(&self.essentials.import_directory) - // if we can not read import directory to check for failed files, - // pretend we have some failed mail - .unwrap_or(true) - { + let have_failed_mails = + Importer::get_failed_mails_count(&self.essentials.import_directory) + .map(|failed_mail_count| failed_mail_count > 0) + // if we can not read import directory to check for failed files, + // pretend we have some failed mail + .unwrap_or(true); + return if have_failed_mails { Err(ImportErrorKind::SourceExhaustedSomeError.into()) } else { Ok(ImportOkKind::SourceExhaustedNoError) @@ -728,7 +717,8 @@ impl Importer { }, Err(chunk_import_error) => { - self.handle_err_while_importing_chunk(chunk_import_error)?; + self.handle_err_while_importing_chunk(chunk_import_error) + .await?; }, } }, @@ -752,10 +742,11 @@ impl Importer { /// called if any chunk fails to import for any reason. if it returns `Ok`, we should continue with the next /// chunk, if it returns `Err`, the error should be propagated to the node process to maybe be displayed and /// the import should stop for now. - fn handle_err_while_importing_chunk( + async fn handle_err_while_importing_chunk( &self, import_error: MailImportErrorMessage, ) -> Result<(), MailImportErrorMessage> { + self.update_failed_mails_counter().await; match import_error.kind { // if the import is (temporary) disabled, we should give up and let user try again later ImportErrorKind::ImportFeatureDisabled => Err(ImportErrorKind::ImportFeatureDisabled)?, @@ -796,6 +787,29 @@ impl Importer { } } + /// We have some case where we don't update failedMails counter on failure, + /// but only move them to FAILED_EML_SUB_DIR directory ( example: we failed to parse eml while importing ), + /// we should also include those in state for user visibility, + /// so it is safe to always override the counter with number of failed mails file, + /// all failure case should make sure to move the failed emls in that sub-dir + async fn update_failed_mails_counter(&self) { + self.essentials + .update_remote_state(|remote_state| { + match Importer::get_failed_mails_count(&self.essentials.import_directory) { + Ok(failed_mail_count) => { + remote_state.failedMails = failed_mail_count as i64; + true + } + Err(e) => { + log::error!("Not incrementing failedMails on import state. Can not count failed emails: {e:?}"); + false + } + } + }) + .await + .ok(); + } + pub(super) fn get_existing_import_id( import_directory: &Path, ) -> std::io::Result> { diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index 83a0e1257f15..c8acfc50cf76 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -140,14 +140,18 @@ impl FileImport { loop { match self.next_importable_mail() { Ok(maybe_importable_mail) => return maybe_importable_mail, - Err(FileIterationError::FileReadError(_)) => { - // we don't have to do anything here, - // if user restarts the app, this file will be picked up again and retried - // if use do not restart app, they will have option to open the import directory, - // they can see for themselves - }, - Err(FileIterationError::ParseError(malformed_file)) => { - Self::move_failed_eml_file(&malformed_file).ok(); + + Err(FileIterationError::ParseError(failed_eml_path)) + | Err(FileIterationError::FileReadError(failed_eml_path)) => { + // to-review: + // since we do not account for the case where we can't move the file, + // it's possible that when we exhaust all sources we will still have some eml files + // in current_imports/mailbox_id + // and currently, in Importer::update_failed_mails_counter we only account for mails that + // we managed to move to failed dir, + // + // would be nice to also count at the end not just in failed-dir put also in parent dir + Self::move_failed_eml_file(&failed_eml_path).ok(); }, } } From 0952a5884816745b2f316032d0c9a4215a42ea85 Mon Sep 17 00:00:00 2001 From: sug Date: Wed, 29 Jan 2025 16:19:11 +0100 Subject: [PATCH 05/23] unify all rust project into single workspace --- .cargo/config.toml | 5 + .gitignore | 3 + .prettierignore | 4 +- tuta-sdk/rust/Cargo.lock => Cargo.lock | 151 ++++ Cargo.toml | 51 ++ buildSrc/bump-version.js | 33 +- packages/node-mimimi/.cargo/config.toml | 2 - packages/node-mimimi/.npmignore | 3 - packages/node-mimimi/Cargo.toml | 48 +- packages/node-mimimi/package.json | 2 +- packages/node-mimimi/rust-toolchain | 1 - packages/node-mimimi/rustfmt.toml | 16 - tuta-sdk/rust/rustfmt.toml => rustfmt.toml | 3 +- tuta-sdk/rust/.gitignore | 2 - tuta-sdk/rust/Cargo.toml | 8 - tuta-sdk/rust/README.md | 19 +- tuta-sdk/rust/make_android.sh | 6 +- tuta-sdk/rust/make_ios.sh | 4 +- tuta-sdk/rust/sdk/Cargo.toml | 57 +- tuta-sdk/rust/uniffi-bindgen/Cargo.lock | 696 ------------------ tuta-sdk/rust/uniffi-bindgen/Cargo.toml | 11 +- .../{uniffi-bindgen.rs => src/main.rs} | 0 22 files changed, 296 insertions(+), 829 deletions(-) create mode 100644 .cargo/config.toml rename tuta-sdk/rust/Cargo.lock => Cargo.lock (95%) create mode 100644 Cargo.toml delete mode 100644 packages/node-mimimi/.cargo/config.toml delete mode 100644 packages/node-mimimi/rust-toolchain delete mode 100644 packages/node-mimimi/rustfmt.toml rename tuta-sdk/rust/rustfmt.toml => rustfmt.toml (83%) delete mode 100644 tuta-sdk/rust/.gitignore delete mode 100644 tuta-sdk/rust/Cargo.toml delete mode 100644 tuta-sdk/rust/uniffi-bindgen/Cargo.lock rename tuta-sdk/rust/uniffi-bindgen/{uniffi-bindgen.rs => src/main.rs} (100%) diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000000..aff7a5c7cda3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.x86_64-pc-windows-msvc] +rustflags = ["-C", "target-feature=+crt-static"] + +[profile.release] +lto = true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0fc9348ec610..d1cf152bd524 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +target build-calendar-app dist node_modules @@ -24,3 +25,5 @@ tuta-sdk/out app-android/app/src/main/jniLibs app-android/app/src/main/java/de/tutao/tutasdk DerivedData/ +tuta-sdk/rust/out +tuta-sdk/android \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index ab64b02abecc..0760e99424f2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,5 +9,5 @@ app-android/ app-ios/ fdroid-metadata-workaround/ packages/tutanota-crypto/lib/internal/ -tuta-sdk/rust/target -packages/node-mimimi \ No newline at end of file +target/ +packages/node-mimimi/tests \ No newline at end of file diff --git a/tuta-sdk/rust/Cargo.lock b/Cargo.lock similarity index 95% rename from tuta-sdk/rust/Cargo.lock rename to Cargo.lock index 3bd1115e75f7..4e677524dbd1 100644 --- a/tuta-sdk/rust/Cargo.lock +++ b/Cargo.lock @@ -501,6 +501,15 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -536,6 +545,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -625,6 +644,15 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -782,6 +810,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30" +dependencies = [ + "rustix", + "windows-targets 0.52.6", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -847,6 +885,17 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "hashify" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f208758247e68e239acaa059e72e4ce1f30f2a4b6523f19c1b923d25b7e9cceb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "heck" version = "0.5.0" @@ -1127,6 +1176,25 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +[[package]] +name = "mail-builder" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75c72a0bf2070b4c2aa384f173439569a9e0e97293b90aad3469b6d9c183ec4" +dependencies = [ + "gethostname", +] + +[[package]] +name = "mail-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187a2b93c4c8c32f552ee06c2d99915e575de2fc7e04b07891c9edfee5b8edd6" +dependencies = [ + "encoding_rs", + "hashify", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1233,6 +1301,64 @@ dependencies = [ "syn", ] +[[package]] +name = "napi" +version = "2.16.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3437deb8b6ba2448b6a94260c5c6b9e5eeb5a5d6277e44b40b2532d457b0f0d" +dependencies = [ + "bitflags", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", + "tokio", +] + +[[package]] +name = "napi-build" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db836caddef23662b94e16bf1f26c40eceb09d6aee5d5b06a7ac199320b69b19" + +[[package]] +name = "napi-derive" +version = "2.16.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "semver", + "syn", +] + +[[package]] +name = "napi-sys" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +dependencies = [ + "libloading", +] + [[package]] name = "nom" version = "7.1.3" @@ -2245,6 +2371,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tutao_node-mimimi" +version = "267.250206.0" +dependencies = [ + "base64", + "log 0.4.25", + "mail-builder", + "mail-parser", + "napi", + "napi-build", + "napi-derive", + "rand", + "regex", + "serde", + "serde_json", + "tokio", + "tuta-sdk", +] + [[package]] name = "typenum" version = "1.17.0" @@ -2269,6 +2414,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "uniffi" version = "0.27.1" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000000..074024570c52 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,51 @@ +[workspace.package] +version = "267.250206.0" +rust-version = "1.84.0" +edition = "2021" +homepage = "https://tutao.de" +repository = "https://github.com/tutao/tutanota" +license-file = "./Lisence.txt" +publish = false + +[profile] +# these config should be configured in .cargo/config.toml + +[workspace] +resolver = "2" # todo: can use resolver 3 after 1.84.0+ +members = [ + "tuta-sdk/rust/uniffi-bindgen", + "tuta-sdk/rust/sdk", + "packages/node-mimimi" +] + +[workspace.dependencies] +tuta-sdk = { path = "tuta-sdk/rust/sdk", default-features = false } +thiserror = { version = "2.0" } +base64 = { version = "0.22.1" } +log = { version = "0.4.22" } +serde = { version = "1.0.210", features = ["derive"] } +serde_json = { version = "1.0.117" } +tokio = { version = "1.43", default-features = false } +uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c", default-features = false } + +# --------- lints + +# lints.cargo not supported on stable yet. +# [workspace.lints.cargo] + +[workspace.lints.clippy] +new_without_default = "allow" # we don't want to implement Default for everything +enum_variant_names = "allow" # creates more problems than it solves +let_and_return = "allow" # we commonly use this style +too_many_arguments = "allow" # this is fine + +# "pedantic" lints worth warning on +manual_let_else = "warn" +must_use_candidate = "warn" +unused_async = "warn" +implicit_clone = "warn" +explicit_iter_loop = "warn" + + +[workspace.lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci)'] } \ No newline at end of file diff --git a/buildSrc/bump-version.js b/buildSrc/bump-version.js index 67bae1ea610a..b1ba3bd5b865 100755 --- a/buildSrc/bump-version.js +++ b/buildSrc/bump-version.js @@ -25,11 +25,10 @@ await program async function run({ platform }) { console.log(`bumping version for ${platform ?? "all"}`) const { currentVersion, currentVersionString, newVersionString } = await calculateClientVersions() - await bumpSdkVersion(newVersionString) + await bumpVersionInCargoWorkspace(newVersionString) if (platform === "all" || platform === "webdesktop") { await bumpWorkspaces(newVersionString) - await bumpNodeMimimiVersion(newVersionString) await $`npm version --no-git-tag-version ${newVersionString}` // Need to clean and re-install to make sure that all packages @@ -54,31 +53,25 @@ async function run({ platform }) { console.log(`Bumped version ${currentVersionString} -> ${newVersionString}`) } -async function bumpSdkVersion(newVersionString) { - await updateCargoFile(newVersionString, "tuta-sdk", "tuta-sdk/rust/sdk/Cargo.toml") - await updateCargoFile(newVersionString, "tuta-sdk", "tuta-sdk/rust/Cargo.lock") -} - -async function bumpNodeMimimiVersion(newVersionString) { - await updateCargoFile(newVersionString, "tuta-sdk", "packages/node-mimimi/Cargo.lock") - await updateCargoFile(newVersionString, "tutao_node-mimimi", "packages/node-mimimi/Cargo.toml") - await updateCargoFile(newVersionString, "tutao_node-mimimi", "packages/node-mimimi/Cargo.lock") -} - -async function updateCargoFile(newVersionString, packageName, fileName) { - const versionRegex = new RegExp(`name = "${packageName}"\nversion = ".*"`) - const contents = await fs.promises.readFile(fileName, "utf8") +/** + * @param newVersionString {string} + * @return {Promise} + */ +async function bumpVersionInCargoWorkspace(newVersionString) { + const workspaceFilePath = "Cargo.toml" + const versionRegex = /\[workspace\.package]\nversion = ".*"/ + const contents = await fs.promises.readFile(workspaceFilePath, "utf8") let found = 0 const newContents = contents.replace(versionRegex, (_, __, ___, ____) => { found += 1 - return `name = "${packageName}"\nversion = "${newVersionString}"` + return `[workspace.package]\nversion = "${newVersionString}"` }) if (found !== 1) { - console.warn(`${fileName} had an unexpected format and couldn't be updated. Is it corrupted?`) + console.warn(`${workspaceFilePath} had an unexpected format and couldn't be updated. Is it corrupted?`) } else { - console.log(`rust: Updated ${fileName} for ${packageName} to ${newVersionString}`) - await fs.promises.writeFile(fileName, newContents) + console.log(`rust: Updated ${workspaceFilePath} package.version to ${newVersionString}`) + await fs.promises.writeFile(workspaceFilePath, newContents) } } diff --git a/packages/node-mimimi/.cargo/config.toml b/packages/node-mimimi/.cargo/config.toml deleted file mode 100644 index 0c17df095caa..000000000000 --- a/packages/node-mimimi/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.x86_64-pc-windows-msvc] -rustflags = ["-C", "target-feature=+crt-static"] \ No newline at end of file diff --git a/packages/node-mimimi/.npmignore b/packages/node-mimimi/.npmignore index ec144db2a711..69741c76e666 100644 --- a/packages/node-mimimi/.npmignore +++ b/packages/node-mimimi/.npmignore @@ -1,6 +1,3 @@ -target -Cargo.lock -.cargo .github npm .eslintrc diff --git a/packages/node-mimimi/Cargo.toml b/packages/node-mimimi/Cargo.toml index 6f24965138ea..aa65136bc4f6 100644 --- a/packages/node-mimimi/Cargo.toml +++ b/packages/node-mimimi/Cargo.toml @@ -1,44 +1,36 @@ [package] -edition = "2021" name = "tutao_node-mimimi" -version = "267.250206.0" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license-file.workspace = true +homepage.workspace = true [lib] crate-type = ["cdylib"] -[profile.release] -lto = true -strip = "symbols" - +[features] +default = ["javascript"] +# needed to turn off the autogenerated ffi when using the tests +javascript = ["dep:napi-derive"] [dependencies] -tuta-sdk = { path = "../../tuta-sdk/rust/sdk", features = ["net"] } -async-trait = "0.1.85" -rand = { version = "0.8.5" } -mail-parser = { version = "0.9.4", features = ["full_encoding"] } -thiserror = { version = "2.0" } # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix napi = { version = "2.16.13", default-features = false, features = ["napi9", "async", "tokio_rt"] } napi-derive = { version = "2.16.13", optional = true } -base64 = "0.22.1" -log = { version = "0.4.22" } -regex = "1.11.1" -serde = { version = "1.0.210", features = ["derive"] } +mail-parser = { version = "0.10.2", features = ["full_encoding"] } +tuta-sdk = { workspace = true, features = ["net"] } +rand = { version = "0.8.5" } +log = { workspace = true } +base64 = { workspace = true } +regex = { version = "1.11.1" } [build-dependencies] napi-build = { version = "2.1.4" } [dev-dependencies] -base64 = "0.22.1" -tokio = { version = "1", features = ["full"] } -serde_json = "1" -tuta-sdk = { path = "../../tuta-sdk/rust/sdk", features = ["net", "testing"] } -mail-builder = { version = "0.3.2" } - -[features] -default = ["javascript"] -# needed to turn off the autogenerated ffi when using the tests -javascript = ["dep:napi-derive"] - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci)'] } \ No newline at end of file +tokio = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } +tuta-sdk = { workspace = true, features = ["net", "testing"] } +mail-builder = { version = "0.4.0" } \ No newline at end of file diff --git a/packages/node-mimimi/package.json b/packages/node-mimimi/package.json index f215ecd4e325..fc6df43fad71 100644 --- a/packages/node-mimimi/package.json +++ b/packages/node-mimimi/package.json @@ -29,4 +29,4 @@ "universal": "napi universal", "version": "napi version" } -} \ No newline at end of file +} diff --git a/packages/node-mimimi/rust-toolchain b/packages/node-mimimi/rust-toolchain deleted file mode 100644 index 870bbe4e50e6..000000000000 --- a/packages/node-mimimi/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -stable \ No newline at end of file diff --git a/packages/node-mimimi/rustfmt.toml b/packages/node-mimimi/rustfmt.toml deleted file mode 100644 index c22683acdcd6..000000000000 --- a/packages/node-mimimi/rustfmt.toml +++ /dev/null @@ -1,16 +0,0 @@ -edition = "2024" - -tab_spaces = 4 -hard_tabs = true -newline_style = "Unix" -max_width = 100 -match_block_trailing_comma = true -use_field_init_shorthand = true - -# TODO: Very nice-to-have, but currently nightly only -#normalize_comments = true -#normalize_doc_attributes = true -#group_imports = "StdExternalCrate" -#reorder_impl_items = true -#combine_control_expr = false -#condense_wildcard_suffixes = true diff --git a/tuta-sdk/rust/rustfmt.toml b/rustfmt.toml similarity index 83% rename from tuta-sdk/rust/rustfmt.toml rename to rustfmt.toml index c6c9f6fc1482..d92f709df742 100644 --- a/tuta-sdk/rust/rustfmt.toml +++ b/rustfmt.toml @@ -15,4 +15,5 @@ use_field_init_shorthand = true #combine_control_expr = false #condense_wildcard_suffixes = true #format_generated_files = false -#ignore = ["sdk/src/entities/generated", "sdk/src/services/generated"] \ No newline at end of file +# todo: why? +# ignore = ["sdk/src/entities/generated", "sdk/src/services/generated"] \ No newline at end of file diff --git a/tuta-sdk/rust/.gitignore b/tuta-sdk/rust/.gitignore deleted file mode 100644 index 90de6fc9898a..000000000000 --- a/tuta-sdk/rust/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target/ -out/ diff --git a/tuta-sdk/rust/Cargo.toml b/tuta-sdk/rust/Cargo.toml deleted file mode 100644 index c14ace174bd1..000000000000 --- a/tuta-sdk/rust/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[workspace] - -resolver = "2" - -members = [ - "sdk", - "uniffi-bindgen", -] \ No newline at end of file diff --git a/tuta-sdk/rust/README.md b/tuta-sdk/rust/README.md index 493fe61dfa78..63a1d73778ee 100644 --- a/tuta-sdk/rust/README.md +++ b/tuta-sdk/rust/README.md @@ -1,5 +1,14 @@ ## Building +``` +# install bindgen-cli binary and export it to path +cargo install --locked bindgen-cli + +# export installed bindgen binary to $PATH. +# verify it works +bindgen +``` + ### Android You need at least NDK 23 @@ -11,13 +20,17 @@ Android Studio -> Android SDK Manager -> SDK Tools -> NDK (Side by Side) -> Inst # install the android targets for rust (part of rust-tuta pkg on dev machines) rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android -# add ANDROID_NDK_HOME to your shell profile (.bashrc). the version depends on your NDK version. + + +# add ANDROID_NDK to your shell profile (.bashrc). the version depends on your NDK version. +export ANDROID_NDK_ROOT=/opt/android-sdk-linux/ndk/26.1.10909125 export ANDROID_NDK_HOME=/opt/android-sdk-linux/ndk/26.1.10909125 # add NDK toolchain to path -export PATH=${PATH}:${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/ +export PATH=${PATH}:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/ # build the sdk for android +cd tuta-sdk/rust ./make_android.sh # setup the sdk project @@ -31,7 +44,7 @@ export PATH=${PATH}:${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bi ## iOS -Have the iOS 17.2 SDK installed before running these. +Have the iOS 17.2 SDK installed before running these. Also requires xcode. ``` # install the iOS targets for rust diff --git a/tuta-sdk/rust/make_android.sh b/tuta-sdk/rust/make_android.sh index 0ed4372365cd..d43aa8b9f250 100755 --- a/tuta-sdk/rust/make_android.sh +++ b/tuta-sdk/rust/make_android.sh @@ -15,16 +15,16 @@ for arch in "${TARGETS[@]}"; do JNILIBS_DIR="${arch}" esac - cargo build --lib --target "${arch}-linux-android" --release --config "target.${arch}-linux-android.linker=\"${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${arch}-linux-android30-clang\"" + cargo build --package tuta-sdk --lib --target "${arch}-linux-android" --release --config "target.${arch}-linux-android.linker=\"${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${arch}-linux-android30-clang\"" # generate bindings for our target - cargo run --bin uniffi-bindgen generate --library "target/${arch}-linux-android/release/libtutasdk.so" --language kotlin --out-dir out + cargo run --package uniffi-bindgen generate --library "../../target/${arch}-linux-android/release/libtutasdk.so" --language kotlin --out-dir out # And then consume one way or another. # Would be great to expose maven library for Android. # Ad-hoc: mkdir -p "../android/app/src/main/jniLibs/${JNILIBS_DIR}" mkdir -p "../android/app/src/main/java/de/tutao/tutasdk" - cp "target/${arch}-linux-android/release/libtutasdk.so" "../android/app/src/main/jniLibs/${JNILIBS_DIR}/libtutasdk.so" + cp "../../target/${arch}-linux-android/release/libtutasdk.so" "../android/app/src/main/jniLibs/${JNILIBS_DIR}/libtutasdk.so" cp "out/de/tutao/tutasdk/tutasdk.kt" "../android/app/src/main/java/de/tutao/tutasdk/tutasdk.kt" done diff --git a/tuta-sdk/rust/make_ios.sh b/tuta-sdk/rust/make_ios.sh index bef3e4716c2b..1ffdbf423b51 100755 --- a/tuta-sdk/rust/make_ios.sh +++ b/tuta-sdk/rust/make_ios.sh @@ -13,7 +13,7 @@ PATH="$(bash -l -c 'echo $PATH')" for target in "${TARGETS[@]}"; do OUT_DIR="out/${target}" - RELEASE_DIR="target/${target}/release" + RELEASE_DIR="../../target/${target}/release" SWIFTC_TARGET="" case "${target}" in @@ -67,7 +67,7 @@ for target in "${TARGETS[@]}"; do # Would be great to expose maven library for Android. # Ad-hoc: mkdir -p "../ios/lib/${target}" - cp "target/${target}/release/libtutasdk.a" "../ios/lib/${target}/libtutasdk.a" + cp "../../target/${target}/release/libtutasdk.a" "../ios/lib/${target}/libtutasdk.a" cp "${OUT_DIR}/tutasdkFFI.h" "../ios/lib" cp "${OUT_DIR}/tutasdk.swift" "../ios/lib" diff --git a/tuta-sdk/rust/sdk/Cargo.toml b/tuta-sdk/rust/sdk/Cargo.toml index 879abc6ce472..5577c70eaaeb 100644 --- a/tuta-sdk/rust/sdk/Cargo.toml +++ b/tuta-sdk/rust/sdk/Cargo.toml @@ -1,19 +1,24 @@ [package] -edition = "2021" name = "tuta-sdk" -version = "267.250206.0" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license-file.workspace = true +homepage.workspace = true [dependencies] async-trait = "0.1.77" -serde = { version = "1.0.201", features = ["derive"] } -serde_json = "1.0.117" -minicbor = { version = "0.25", features = ["std", "alloc"] } thiserror = "2.0" -base64 = "0.22.1" +futures = "0.3.31" +serde_bytes = "0.11.15" +num_enum = "0.7.3" +minicbor = { version = "0.25", features = ["std", "alloc"] } +const-hex = { version = "1.12.0", features = ["serde"] } +lz4_flex = { version = "0.11.3", default-features = false, features = ["safe-encode", "safe-decode", "std"] } + aes = { version = "0.8.4", features = ["zeroize"] } cbc = { version = "0.1.2", features = ["std", "zeroize"] } sha2 = "0.10.8" -const-hex = { version = "1.12.0", features = ["serde"] } hmac = "0.12.1" zeroize = { version = "1.8.1", features = ["zeroize_derive"] } hkdf = "0.12.4" @@ -24,14 +29,15 @@ pqcrypto-mlkem = { version = "0.1.0", default-features = false, features = [ ] } pqcrypto-traits = "0.3.5" rsa = "0.9.6" +sha3 = "0.10.8" rand_core = { version = "0.6.4", features = ["getrandom"] } -serde_bytes = "0.11.15" -futures = "0.3.31" -log = "0.4.25" -uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" } -num_enum = "0.7.3" -lz4_flex = { version = "0.11.3", default-features = false, features = ["safe-encode", "safe-decode", "std"] } -tokio = { version = "1.43", features = ["rt", "rt-multi-thread", "macros", "sync", "time"] } + +log = { workspace = true } +base64 = { workspace = true } +tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros", "sync", "time"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +uniffi = { workspace = true } # only used for the native rest client hyper = { version = "1.5", features = ["client"], optional = true } @@ -40,21 +46,17 @@ http-body-util = { version = "0.1.2", optional = true } hyper-rustls = { version = "0.27.5", features = ["ring", "http2"], optional = true } rustls = { version = "*", optional = true } form_urlencoded = "1" + # allow initializing a simple_logger if the consuming application (or examples) want to do that. simple_logger = { version = "5.0.0", optional = true } -sha3 = "0.10.8" [target.'cfg(target_os = "android")'.dependencies] android_log = "0.1.3" - [target.'cfg(target_os = "ios")'.dependencies] oslog = "0.2.0" [build-dependencies] -uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c", features = [ - "build", -] } - +uniffi = { workspace = true, features = ["build"] } [features] net = ["dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:hyper-rustls", "dep:rustls"] @@ -72,18 +74,3 @@ rand = { version = "0.8.5" } crate-type = ["cdylib", "staticlib", "lib"] name = "tutasdk" -[lints.clippy] -new_without_default = "allow" # we don't want to implement Default for everything -enum_variant_names = "allow" # creates more problems than it solves -let_and_return = "allow" # we commonly use this style -too_many_arguments = "allow" # this is fine - -# "pedantic" lints worth warning on -manual_let_else = "warn" -must_use_candidate = "warn" -unused_async = "warn" -implicit_clone = "warn" -explicit_iter_loop = "warn" - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci)'] } diff --git a/tuta-sdk/rust/uniffi-bindgen/Cargo.lock b/tuta-sdk/rust/uniffi-bindgen/Cargo.lock deleted file mode 100644 index 1c57e19f027c..000000000000 --- a/tuta-sdk/rust/uniffi-bindgen/Cargo.lock +++ /dev/null @@ -1,696 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "anstream" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" - -[[package]] -name = "anstyle-parse" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" -dependencies = [ - "anstyle", - "windows-sys", -] - -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "askama" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" -dependencies = [ - "askama_derive", - "askama_escape", -] - -[[package]] -name = "askama_derive" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" -dependencies = [ - "askama_parser", - "basic-toml", - "mime", - "mime_guess", - "proc-macro2", - "quote", - "serde", - "syn", -] - -[[package]] -name = "askama_escape" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" - -[[package]] -name = "askama_parser" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" -dependencies = [ - "nom", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "basic-toml" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" -dependencies = [ - "serde", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bytes" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" - -[[package]] -name = "camino" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "clap" -version = "4.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" - -[[package]] -name = "colorchoice" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" - -[[package]] -name = "fs-err" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "goblin" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" -dependencies = [ - "log", - "plain", - "scroll", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "scroll" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" -dependencies = [ - "scroll_derive", -] - -[[package]] -name = "scroll_derive" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.204" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.204" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "smawk" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" -dependencies = [ - "smawk", -] - -[[package]] -name = "thiserror" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "uniffi" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "anyhow", - "camino", - "clap", - "uniffi_bindgen", - "uniffi_core", - "uniffi_macros", -] - -[[package]] -name = "uniffi-bingen" -version = "0.28.0" -dependencies = [ - "uniffi", -] - -[[package]] -name = "uniffi_bindgen" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "anyhow", - "askama", - "camino", - "cargo_metadata", - "fs-err", - "glob", - "goblin", - "heck", - "once_cell", - "paste", - "serde", - "textwrap", - "toml", - "uniffi_meta", - "uniffi_testing", - "uniffi_udl", -] - -[[package]] -name = "uniffi_checksum_derive" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "uniffi_core" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "anyhow", - "bytes", - "camino", - "log", - "once_cell", - "paste", - "static_assertions", -] - -[[package]] -name = "uniffi_macros" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "bincode", - "camino", - "fs-err", - "once_cell", - "proc-macro2", - "quote", - "serde", - "syn", - "toml", - "uniffi_meta", -] - -[[package]] -name = "uniffi_meta" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "anyhow", - "bytes", - "siphasher", - "uniffi_checksum_derive", -] - -[[package]] -name = "uniffi_testing" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "anyhow", - "camino", - "cargo_metadata", - "fs-err", - "once_cell", -] - -[[package]] -name = "uniffi_udl" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "anyhow", - "textwrap", - "uniffi_meta", - "uniffi_testing", - "weedle2", -] - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "weedle2" -version = "5.0.0" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" -dependencies = [ - "nom", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/tuta-sdk/rust/uniffi-bindgen/Cargo.toml b/tuta-sdk/rust/uniffi-bindgen/Cargo.toml index f222bb54b08f..ba994b56fe6e 100644 --- a/tuta-sdk/rust/uniffi-bindgen/Cargo.toml +++ b/tuta-sdk/rust/uniffi-bindgen/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "uniffi-bindgen" version = "0.28.0" -edition = "2021" +edition.workspace = true +rust-version.workspace = true +license-file.workspace = true +homepage.workspace = true [dependencies] -uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c", features = ["cli"] } - -[[bin]] -name = "uniffi-bindgen" -path = "uniffi-bindgen.rs" +uniffi = { workspace = true, features = ["cli"] } diff --git a/tuta-sdk/rust/uniffi-bindgen/uniffi-bindgen.rs b/tuta-sdk/rust/uniffi-bindgen/src/main.rs similarity index 100% rename from tuta-sdk/rust/uniffi-bindgen/uniffi-bindgen.rs rename to tuta-sdk/rust/uniffi-bindgen/src/main.rs From 16b56372e420688c8908ecfae0b1140e3e07f3ef Mon Sep 17 00:00:00 2001 From: sug Date: Thu, 30 Jan 2025 10:49:34 +0100 Subject: [PATCH 06/23] - add comment why we need uniffi-bindgen as seperate crate - do not depend on un-needed tokio features - move rand to Cargo.toml - use latest uniffi-bindgen from crates.io --- Cargo.lock | 50 +++++++++++++++++++-------------- Cargo.toml | 13 +++++---- packages/node-mimimi/Cargo.toml | 8 +++--- tuta-sdk/rust/sdk/Cargo.toml | 13 +++++---- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e677524dbd1..7535fe6d0f95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2422,8 +2422,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "uniffi" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb08c58c7ed7033150132febe696bef553f891b1ede57424b40d87a89e3c170" dependencies = [ "anyhow", "camino", @@ -2443,8 +2444,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cade167af943e189a55020eda2c314681e223f1e42aca7c4e52614c2b627698f" dependencies = [ "anyhow", "askama", @@ -2460,14 +2462,14 @@ dependencies = [ "textwrap", "toml", "uniffi_meta", - "uniffi_testing", "uniffi_udl", ] [[package]] name = "uniffi_build" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7cf32576e08104b7dc2a6a5d815f37616e66c6866c2a639fe16e6d2286b75b" dependencies = [ "anyhow", "camino", @@ -2476,8 +2478,9 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" dependencies = [ "quote", "syn", @@ -2485,12 +2488,12 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7687007d2546c454d8ae609b105daceb88175477dac280707ad6d95bcd6f1f" dependencies = [ "anyhow", "bytes", - "camino", "log 0.4.25", "once_cell", "paste", @@ -2499,8 +2502,9 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c65a5b12ec544ef136693af8759fb9d11aefce740fb76916721e876639033b" dependencies = [ "bincode", "camino", @@ -2516,8 +2520,9 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a74ed96c26882dac1ca9b93ca23c827e284bacbd7ec23c6f0b0372f747d59e4" dependencies = [ "anyhow", "bytes", @@ -2527,8 +2532,9 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6f984f0781f892cc864a62c3a5c60361b1ccbd68e538e6c9fbced5d82268ac" dependencies = [ "anyhow", "camino", @@ -2539,8 +2545,9 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037820a4cfc4422db1eaa82f291a3863c92c7d1789dc513489c36223f9b4cdfc" dependencies = [ "anyhow", "textwrap", @@ -2585,7 +2592,8 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "weedle2" version = "5.0.0" -source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" dependencies = [ "nom", ] diff --git a/Cargo.toml b/Cargo.toml index 074024570c52..2473cded6586 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,20 +13,23 @@ publish = false [workspace] resolver = "2" # todo: can use resolver 3 after 1.84.0+ members = [ - "tuta-sdk/rust/uniffi-bindgen", - "tuta-sdk/rust/sdk", - "packages/node-mimimi" + "tuta-sdk/rust/sdk", + "packages/node-mimimi", + # uniffi-bindgen is a seperate crate as uniffi-bindgen does not have a cli yet: + # https://github.com/mozilla/uniffi-rs/blob/fbe146f/docs/manual/src/tutorial/foreign_language_bindings.md#creating-the-bindgen-binary + # note: not to be confused with `bindgen` binary which is from: https://github.com/rust-lang/rust-bindgen + "tuta-sdk/rust/uniffi-bindgen", ] [workspace.dependencies] -tuta-sdk = { path = "tuta-sdk/rust/sdk", default-features = false } thiserror = { version = "2.0" } base64 = { version = "0.22.1" } log = { version = "0.4.22" } serde = { version = "1.0.210", features = ["derive"] } serde_json = { version = "1.0.117" } tokio = { version = "1.43", default-features = false } -uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c", default-features = false } +uniffi = { version = "0.28.3", default-features = false } +rand = { version = "0.8" } # --------- lints diff --git a/packages/node-mimimi/Cargo.toml b/packages/node-mimimi/Cargo.toml index aa65136bc4f6..2e52cee5820f 100644 --- a/packages/node-mimimi/Cargo.toml +++ b/packages/node-mimimi/Cargo.toml @@ -19,8 +19,8 @@ javascript = ["dep:napi-derive"] napi = { version = "2.16.13", default-features = false, features = ["napi9", "async", "tokio_rt"] } napi-derive = { version = "2.16.13", optional = true } mail-parser = { version = "0.10.2", features = ["full_encoding"] } -tuta-sdk = { workspace = true, features = ["net"] } -rand = { version = "0.8.5" } +tuta-sdk = { path = "../../tuta-sdk/rust/sdk", features = ["net"] } +rand = { workspace = true } log = { workspace = true } base64 = { workspace = true } regex = { version = "1.11.1" } @@ -29,8 +29,8 @@ regex = { version = "1.11.1" } napi-build = { version = "2.1.4" } [dev-dependencies] -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } serde_json = { workspace = true } serde = { workspace = true } -tuta-sdk = { workspace = true, features = ["net", "testing"] } +tuta-sdk = { path = "../../tuta-sdk/rust/sdk", features = ["net", "testing"] } mail-builder = { version = "0.4.0" } \ No newline at end of file diff --git a/tuta-sdk/rust/sdk/Cargo.toml b/tuta-sdk/rust/sdk/Cargo.toml index 5577c70eaaeb..0b2448c644ac 100644 --- a/tuta-sdk/rust/sdk/Cargo.toml +++ b/tuta-sdk/rust/sdk/Cargo.toml @@ -6,6 +6,10 @@ rust-version.workspace = true license-file.workspace = true homepage.workspace = true +[lib] +name = "tutasdk" +crate-type = ["cdylib", "staticlib", "lib"] + [dependencies] async-trait = "0.1.77" thiserror = "2.0" @@ -34,7 +38,7 @@ rand_core = { version = "0.6.4", features = ["getrandom"] } log = { workspace = true } base64 = { workspace = true } -tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros", "sync", "time"] } +tokio = { workspace = true, features = ["time", "rt-multi-thread", "sync"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } uniffi = { workspace = true } @@ -65,12 +69,9 @@ logging = ["dep:simple_logger"] testing = ["logging"] [dev-dependencies] +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tuta-sdk = { path = ".", features = ["net", "testing"] } mockall = { version = "0.13.0" } mockall_double = { version = "0.3.1" } -rand = { version = "0.8.5" } - -[lib] -crate-type = ["cdylib", "staticlib", "lib"] -name = "tutasdk" +rand = { workspace = true } From 9c72adf547a9bfd32a0618e2f29e177cdbcf3a30 Mon Sep 17 00:00:00 2001 From: sug Date: Thu, 30 Jan 2025 15:00:40 +0100 Subject: [PATCH 07/23] [tutasdk] improve docs on SuspendableRestClient --- tuta-sdk/rust/sdk/src/bindings/suspendable_rest_client.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tuta-sdk/rust/sdk/src/bindings/suspendable_rest_client.rs b/tuta-sdk/rust/sdk/src/bindings/suspendable_rest_client.rs index 83ae63f5f085..71165b7767ff 100644 --- a/tuta-sdk/rust/sdk/src/bindings/suspendable_rest_client.rs +++ b/tuta-sdk/rust/sdk/src/bindings/suspendable_rest_client.rs @@ -32,6 +32,8 @@ pub struct Suspension { pub struct SuspendableRestClient { inner: Arc, date_provider: Arc, + // we use a RwLock so we can have a dedicated writer task that lifts the suspension when it's over but an arbitrary + // amount of waiting requests. suspension: Arc>>, } From f9c10d7f0a0e575dab7315ec58289dd821d9a8ea Mon Sep 17 00:00:00 2001 From: sug Date: Thu, 30 Jan 2025 15:08:12 +0100 Subject: [PATCH 08/23] [tutasdk] make the rust workspace work for the ios build --- tuta-sdk/.gitignore | 1 + tuta-sdk/ios/build-scripts/xc-universal-binary.sh | 6 +++--- tuta-sdk/rust/make_ios.sh | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tuta-sdk/.gitignore b/tuta-sdk/.gitignore index 0a1fe2f1f97d..92fcc72e0845 100644 --- a/tuta-sdk/.gitignore +++ b/tuta-sdk/.gitignore @@ -1,3 +1,4 @@ target/ ios/tutasdk/generated-src/ *.xcodeproj +ios/lib \ No newline at end of file diff --git a/tuta-sdk/ios/build-scripts/xc-universal-binary.sh b/tuta-sdk/ios/build-scripts/xc-universal-binary.sh index 00228c4a2828..e74bc6c6b73a 100755 --- a/tuta-sdk/ios/build-scripts/xc-universal-binary.sh +++ b/tuta-sdk/ios/build-scripts/xc-universal-binary.sh @@ -45,16 +45,16 @@ for arch in $ARCHS; do # Intel iOS simulator export CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios" - cargo rustc --manifest-path="${SRC_ROOT}/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target x86_64-apple-ios -v + cargo rustc --manifest-path="${SRC_ROOT}/sdk/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target x86_64-apple-ios -v ;; arm64) if [ $IS_SIMULATOR -eq 0 ]; then # Hardware iOS targets - cargo rustc --manifest-path="${SRC_ROOT}/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target aarch64-apple-ios -v + cargo rustc --manifest-path="${SRC_ROOT}/sdk/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target aarch64-apple-ios -v else # M1 iOS simulator - cargo rustc --manifest-path="${SRC_ROOT}/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target aarch64-apple-ios-sim -v + cargo rustc --manifest-path="${SRC_ROOT}/sdk/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target aarch64-apple-ios-sim -v fi esac done diff --git a/tuta-sdk/rust/make_ios.sh b/tuta-sdk/rust/make_ios.sh index 1ffdbf423b51..74c5f397d315 100755 --- a/tuta-sdk/rust/make_ios.sh +++ b/tuta-sdk/rust/make_ios.sh @@ -45,10 +45,10 @@ for target in "${TARGETS[@]}"; do SWIFTC_SDK_PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk" esac - cargo build --lib --target "${target}" --release + cargo build --package tuta-sdk --lib --target "${target}" --release # generate bindings for our target - cargo run --bin uniffi-bindgen generate --library "${RELEASE_DIR}/libtutasdk.dylib" --language swift --out-dir "${OUT_DIR}/" + cargo run --package uniffi-bindgen generate --library "${RELEASE_DIR}/libtutasdk.dylib" --language swift --out-dir "${OUT_DIR}/" # generate swift module from dynamic library and bindings # swiftc \ From f77d6bf6046d792a776f0c95da503c1c432ca9c3 Mon Sep 17 00:00:00 2001 From: sug Date: Thu, 30 Jan 2025 15:31:54 +0100 Subject: [PATCH 09/23] change Github rust workflow to run on project root --- .github/workflows/tuta-sdk-test.yml | 5 +- packages/node-mimimi/Cargo.toml | 5 +- packages/node-mimimi/src/importer.rs | 3 +- .../node-mimimi/src/importer/file_reader.rs | 2 + .../src/importer/importable_mail.rs | 4 +- .../mime_string_to_importable_mail_test.rs | 20 ++++---- packages/node-mimimi/src/importer/messages.rs | 4 ++ packages/node-mimimi/src/importer_api.rs | 2 +- tuta-sdk/rust/sdk/Cargo.toml | 2 + tuta-sdk/rust/sdk/src/blobs/blob_facade.rs | 46 ++++++++----------- tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs | 14 ++---- .../rust/sdk/src/entities/entity_facade.rs | 5 +- tuta-sdk/rust/uniffi-bindgen/Cargo.toml | 3 ++ 13 files changed, 57 insertions(+), 58 deletions(-) diff --git a/.github/workflows/tuta-sdk-test.yml b/.github/workflows/tuta-sdk-test.yml index 8fb6dc6f7cd1..439895002003 100644 --- a/.github/workflows/tuta-sdk-test.yml +++ b/.github/workflows/tuta-sdk-test.yml @@ -36,11 +36,8 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - uses: ./.github/shared/setup-rust - name: sdk format - working-directory: tuta-sdk/rust run: cargo fmt --check - name: sdk warning check with clippy - working-directory: tuta-sdk/rust - run: cargo clippy --package tuta-sdk --no-deps -- -Dwarnings # -Dwarnings changes warnings to errors so that the check fails + run: cargo clippy --no-deps -- -Dwarnings # -Dwarnings changes warnings to errors so that the check fails - name: sdk tests - working-directory: tuta-sdk/rust run: cargo test diff --git a/packages/node-mimimi/Cargo.toml b/packages/node-mimimi/Cargo.toml index 2e52cee5820f..f547ab4a4314 100644 --- a/packages/node-mimimi/Cargo.toml +++ b/packages/node-mimimi/Cargo.toml @@ -33,4 +33,7 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } serde_json = { workspace = true } serde = { workspace = true } tuta-sdk = { path = "../../tuta-sdk/rust/sdk", features = ["net", "testing"] } -mail-builder = { version = "0.4.0" } \ No newline at end of file +mail-builder = { version = "0.4.0" } + +[lints] +workspace = true \ No newline at end of file diff --git a/packages/node-mimimi/src/importer.rs b/packages/node-mimimi/src/importer.rs index 9d85ed407790..be80c68ca6e3 100644 --- a/packages/node-mimimi/src/importer.rs +++ b/packages/node-mimimi/src/importer.rs @@ -278,7 +278,7 @@ impl ImportEssential { Ok(unit_import_results) } - async fn make_serialized_chunk( + fn make_serialized_chunk( &self, importable_chunk: Vec, ) -> Result { @@ -636,7 +636,6 @@ impl Importer { .inspect_err(|_e| failed_count += import_count_in_this_chunk)?; let importable_post_data = import_essentials .make_serialized_chunk(unit_import_data) - .await .inspect_err(|_e| failed_count += import_count_in_this_chunk)?; import_essentials diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index c8acfc50cf76..6c2884fa96b8 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -32,6 +32,7 @@ impl FileImport { } impl FileImport { + #[must_use] pub fn new(eml_sources: Vec) -> Self { let message_parser = MessageParser::default(); Self { @@ -172,6 +173,7 @@ impl FileImport { fs::remove_file(import_dir.join(STATE_ID_FILE_NAME)) } + #[must_use] pub fn make_import_directory_path(config_directory: &str, mailbox_id: &str) -> PathBuf { [ config_directory.to_string(), diff --git a/packages/node-mimimi/src/importer/importable_mail.rs b/packages/node-mimimi/src/importer/importable_mail.rs index 769c2b32b19d..aa87ccbe4489 100644 --- a/packages/node-mimimi/src/importer/importable_mail.rs +++ b/packages/node-mimimi/src/importer/importable_mail.rs @@ -81,6 +81,7 @@ pub struct KeyedImportableMailAttachment { } impl ImportableMailAttachment { + #[must_use] pub fn make_keyed_importable_mail_attachment( self, essentials: &ImportEssential, @@ -96,6 +97,7 @@ impl ImportableMailAttachment { } impl ImportableMailAttachmentMetaData { + #[must_use] pub fn make_import_attachment_data( self, essentials: &ImportEssential, @@ -315,7 +317,7 @@ impl ImportableMail { .unwrap_or_else(|| Self::default_content_type().make_string().into_owned()) .to_string(); - let content = content.to_vec(); + let content = content.clone(); let attachment = ImportableMailAttachment { meta_data: ImportableMailAttachmentMetaData { filename, diff --git a/packages/node-mimimi/src/importer/importable_mail/mime_string_to_importable_mail_test.rs b/packages/node-mimimi/src/importer/importable_mail/mime_string_to_importable_mail_test.rs index 6e9b5ad3a877..3e91aec2afd0 100644 --- a/packages/node-mimimi/src/importer/importable_mail/mime_string_to_importable_mail_test.rs +++ b/packages/node-mimimi/src/importer/importable_mail/mime_string_to_importable_mail_test.rs @@ -461,7 +461,7 @@ Hello äöüß assert_eq!(m.attachments.len(), 1); let attachment = &m.attachments[0]; let attached = parse_mail( - String::from_utf8(attachment.content.to_vec()) + String::from_utf8(attachment.content.clone()) .unwrap() .as_str(), ); @@ -516,21 +516,21 @@ c2Vjb25kIGF0dGFjaG1lbnQ= assert_eq!("a1.txt", a1.meta_data.filename); assert_eq!( String::from_utf8(base64_decode(b"Zmlyc3QgYXR0YWNobWVudA==").unwrap()).unwrap(), - String::from_utf8(a1.content.to_vec()).unwrap() + String::from_utf8(a1.content.clone()).unwrap() ); assert_eq!("application/octet-stream", a1.meta_data.content_type); assert_eq!("a2.pdf", a2.meta_data.filename); assert_eq!( String::from_utf8(base64_decode(b"c2Vjb25kIGF0dGFjaG1lbnQ=").unwrap()).unwrap(), - String::from_utf8(a2.content.to_vec()).unwrap() + String::from_utf8(a2.content.clone()).unwrap() ); assert_eq!("application/pdf", a2.meta_data.content_type); assert_eq!("withoutContentType.pdf", a3.meta_data.filename); assert_eq!( String::from_utf8(base64_decode(b"c2Vjb25kIGF0dGFjaG1lbnQ=").unwrap()).unwrap(), - String::from_utf8(a3.content.to_vec()).unwrap() + String::from_utf8(a3.content.clone()).unwrap() ); assert_eq!( r#"text/plain;charset="us-ascii""#, @@ -567,7 +567,7 @@ Zmlyc3QgYXR0YWNobWVudA== assert_eq!("a1.png", a1.meta_data.filename); assert_eq!( String::from_utf8(base64_decode(b"Zmlyc3QgYXR0YWNobWVudA==").unwrap()).unwrap(), - String::from_utf8(a1.content.to_vec()).unwrap() + String::from_utf8(a1.content.clone()).unwrap() ); assert_eq!("application/octet-stream", a1.meta_data.content_type); assert_eq!(Some("123@tutanota.de".to_string()), a1.meta_data.content_id); @@ -617,7 +617,7 @@ Zmlyc3QgYXR0YWNobWVudA== ); assert_eq!( String::from_utf8(base64_decode(b"Zmlyc3QgYXR0YWNobWVudA==").unwrap()).unwrap(), - String::from_utf8(indirect_attachment.content.to_vec()).unwrap() + String::from_utf8(indirect_attachment.content.clone()).unwrap() ); } @@ -685,7 +685,7 @@ Content-Disposition: attachment; filename=a1.html; let a1 = &m.attachments[0]; assert_eq!(a1.meta_data.filename, "a1.html"); assert_eq!( - String::from_utf8(a1.content.to_vec()).unwrap(), + String::from_utf8(a1.content.clone()).unwrap(), "Hello äöüß
" ); } @@ -839,7 +839,7 @@ Abc, die Katze lief im Schnee ! äöü?ß ! "#; let a1 = &m.attachments[0]; assert_eq!(a1.meta_data.filename, "a1.txt"); assert_eq!( - String::from_utf8(a1.content.to_vec()).unwrap(), + String::from_utf8(a1.content.clone()).unwrap(), "Abc, die Katze lief im Schnee ! äöü?ß ! " ); } @@ -887,7 +887,7 @@ Zmlyc3QgYXR0YWNobWVudA=="#; ); assert_eq!( String::from_utf8(base64_decode(b"Zmlyc3QgYXR0YWNobWVudA==").unwrap()).unwrap(), - String::from_utf8(indirect_attachment.content.to_vec()).unwrap() + String::from_utf8(indirect_attachment.content.clone()).unwrap() ); } @@ -912,7 +912,7 @@ Zmlyc3QgYXR0YWNobWVudA=="#; assert_eq!("äö߀.txt", indirect_attachment.meta_data.filename); assert_eq!( String::from_utf8(base64_decode(b"Zmlyc3QgYXR0YWNobWVudA==").unwrap()).unwrap(), - String::from_utf8(indirect_attachment.content.to_vec()).unwrap() + String::from_utf8(indirect_attachment.content.clone()).unwrap() ); } diff --git a/packages/node-mimimi/src/importer/messages.rs b/packages/node-mimimi/src/importer/messages.rs index 177b2dccf00f..ddf6422441dc 100644 --- a/packages/node-mimimi/src/importer/messages.rs +++ b/packages/node-mimimi/src/importer/messages.rs @@ -172,6 +172,7 @@ impl ImportErrorKind { } impl MailImportErrorMessage { + #[must_use] pub fn sdk(action: &'static str, error: ApiCallError) -> Self { log::error!("ImportError::SdkError: {action} ({error})"); @@ -184,6 +185,7 @@ impl MailImportErrorMessage { Self { kind, path: None } } + #[must_use] pub fn with_path(kind: ImportErrorKind, path: PathBuf) -> Self { Self { kind, @@ -199,6 +201,7 @@ impl From for MailImportErrorMessage { } impl MailImportMessage { + #[must_use] pub fn ok(ok_message: ImportOkKind) -> Self { Self { ok_message: Some(ok_message), @@ -206,6 +209,7 @@ impl MailImportMessage { } } + #[must_use] pub fn err(err_message: MailImportErrorMessage) -> Self { Self { ok_message: None, diff --git a/packages/node-mimimi/src/importer_api.rs b/packages/node-mimimi/src/importer_api.rs index 9b85a0882e6b..68f255f5f706 100644 --- a/packages/node-mimimi/src/importer_api.rs +++ b/packages/node-mimimi/src/importer_api.rs @@ -298,7 +298,7 @@ impl From for Credentials { login: tuta_credentials.login, user_id: GeneratedId(tuta_credentials.user_id), access_token: tuta_credentials.access_token, - encrypted_passphrase_key: tuta_credentials.encrypted_passphrase_key.clone().to_vec(), + encrypted_passphrase_key: tuta_credentials.encrypted_passphrase_key.clone().clone(), credential_type, } } diff --git a/tuta-sdk/rust/sdk/Cargo.toml b/tuta-sdk/rust/sdk/Cargo.toml index 0b2448c644ac..fe9dd7e9d7bc 100644 --- a/tuta-sdk/rust/sdk/Cargo.toml +++ b/tuta-sdk/rust/sdk/Cargo.toml @@ -75,3 +75,5 @@ mockall = { version = "0.13.0" } mockall_double = { version = "0.3.1" } rand = { workspace = true } +[lints] +workspace = true \ No newline at end of file diff --git a/tuta-sdk/rust/sdk/src/blobs/blob_facade.rs b/tuta-sdk/rust/sdk/src/blobs/blob_facade.rs index bde132da1ec3..de5ca26fa9aa 100644 --- a/tuta-sdk/rust/sdk/src/blobs/blob_facade.rs +++ b/tuta-sdk/rust/sdk/src/blobs/blob_facade.rs @@ -571,7 +571,7 @@ mod tests { } fn make_blob_service_response( - expected_reference_tokens: &Vec, + expected_reference_tokens: Vec, type_model_provider: &Arc, ) -> Vec { let blob_service_response = BlobPostOut { @@ -664,7 +664,7 @@ mod tests { let type_model_provider = Arc::new(init_type_model_provider()); let response_binary = - make_blob_service_response(&expected_reference_tokens, &type_model_provider); + make_blob_service_response(expected_reference_tokens, &type_model_provider); let mut rest_client = MockRestClient::default(); rest_client @@ -703,19 +703,19 @@ mod tests { .unwrap(); assert_eq!( vec![first_attachment_token], - reference_tokens.get(0).unwrap().to_vec() + reference_tokens.first().unwrap().clone() ); assert_eq!( vec![second_attachment_token], - reference_tokens.get(1).unwrap().to_vec() + reference_tokens.get(1).unwrap().clone() ); assert_eq!( vec![third_attachment_token], - reference_tokens.get(2).unwrap().to_vec() + reference_tokens.get(2).unwrap().clone() ); assert_eq!( vec![fourth_attachment_token], - reference_tokens.get(3).unwrap().to_vec() + reference_tokens.get(3).unwrap().clone() ); } @@ -732,7 +732,7 @@ mod tests { let first_attachment: Vec = vec![0; 12 * 1024 * 1024]; let second_attachment: Vec = vec![0; 2 * 1024 * 1024]; let third_attachment: Vec = vec![0; 2 * 1024 * 1024]; - let fourth_attachment: Vec = vec![0; 1 * 1024 * 1024]; + let fourth_attachment: Vec = vec![0; 1024 * 1024]; let randomizer_facade1 = RandomizerFacade::from_core(DeterministicRng(1)); let randomizer_facade2 = RandomizerFacade::from_core(DeterministicRng(2)); @@ -793,9 +793,9 @@ mod tests { let type_model_provider = Arc::new(init_type_model_provider()); let binary1: Vec = - make_blob_service_response(&expected_reference_tokens1, &type_model_provider); + make_blob_service_response(expected_reference_tokens1, &type_model_provider); let binary2: Vec = - make_blob_service_response(&expected_reference_tokens2, &type_model_provider); + make_blob_service_response(expected_reference_tokens2, &type_model_provider); let mut rest_client = MockRestClient::default(); // first request @@ -809,11 +809,7 @@ mod tests { let RestClientOptions { body, .. } = options; let body = body.clone().unwrap(); let new_blob_wrappers = deserialize_new_blobs(body).unwrap(); - if new_blob_wrappers.len() == 1 { - true - } else { - false - } + new_blob_wrappers.len() == 1 }) .return_const(Ok(RestResponse { status: 200, @@ -832,11 +828,7 @@ mod tests { let RestClientOptions { body, .. } = options; let body = body.clone().unwrap(); let new_blob_wrappers = deserialize_new_blobs(body).unwrap(); - if new_blob_wrappers.len() == 4 { - true - } else { - false - } + new_blob_wrappers.len() == 4 }) .return_const(Ok(RestResponse { status: 200, @@ -860,19 +852,19 @@ mod tests { .unwrap(); assert_eq!( vec![first_attachment_first_token, first_attachment_second_token], - reference_tokens.get(0).unwrap().to_vec() + reference_tokens.first().unwrap().clone() ); assert_eq!( vec![second_attachment_token,], - reference_tokens.get(1).unwrap().to_vec() + reference_tokens.get(1).unwrap().clone() ); assert_eq!( vec![third_attachment_token,], - reference_tokens.get(2).unwrap().to_vec() + reference_tokens.get(2).unwrap().clone() ); assert_eq!( vec![fourth_attachment_token,], - reference_tokens.get(3).unwrap().to_vec() + reference_tokens.get(3).unwrap().clone() ); } @@ -940,13 +932,13 @@ mod tests { let type_model_provider = Arc::new(init_type_model_provider()); let binary1: Vec = - make_blob_service_response(&expected_reference_tokens1, &type_model_provider); + make_blob_service_response(expected_reference_tokens1, &type_model_provider); let binary2: Vec = - make_blob_service_response(&expected_reference_tokens2, &type_model_provider); + make_blob_service_response(expected_reference_tokens2, &type_model_provider); let binary3: Vec = - make_blob_service_response(&expected_reference_tokens3, &type_model_provider); + make_blob_service_response(expected_reference_tokens3, &type_model_provider); let binary4: Vec = - make_blob_service_response(&expected_reference_tokens4, &type_model_provider); + make_blob_service_response(expected_reference_tokens4, &type_model_provider); let mut rest_client = MockRestClient::default(); diff --git a/tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs b/tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs index 3d2229795894..2ccdfec63946 100644 --- a/tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs +++ b/tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs @@ -332,7 +332,7 @@ mod test { let sender_keypair = EccKeyPair::generate(&randomizer_facade); let protocol_version = CryptoProtocolVersion::TutaCrypt; - let mut raw_mail = make_raw_mail( + let raw_mail = make_raw_mail( &constants, constants.pub_enc_bucket_key.clone(), protocol_version.clone() as i64, @@ -364,7 +364,7 @@ mod test { ); let key = crypto_facade - .resolve_session_key(&mut raw_mail, &mail_type_model) + .resolve_session_key(&raw_mail, &mail_type_model) .await .expect("should not have errored") .expect("where is the key"); @@ -381,7 +381,7 @@ mod test { let constants = BucketKeyConstants::new(&randomizer_facade); let crypto_protocol_version = CryptoProtocolVersion::Rsa; - let mut raw_mail = make_raw_mail( + let raw_mail = make_raw_mail( &constants, constants.pub_enc_bucket_key.clone(), crypto_protocol_version.clone() as i64, @@ -412,7 +412,7 @@ mod test { let mail_type_model = get_mail_type_model(); let key = crypto_facade - .resolve_session_key(&mut raw_mail, &mail_type_model) + .resolve_session_key(&raw_mail, &mail_type_model) .await .expect("should not have errored") .expect("where is the key"); @@ -524,11 +524,7 @@ mod test { }) .once(); - let asymmetric_crypto_facade = if let Some(asymmetric_crypto) = asymmetric_crypto_facade { - asymmetric_crypto - } else { - MockAsymmetricCryptoFacade::default() - }; + let asymmetric_crypto_facade = asymmetric_crypto_facade.unwrap_or_default(); CryptoFacade { key_loader_facade: Arc::new(key_loader), diff --git a/tuta-sdk/rust/sdk/src/entities/entity_facade.rs b/tuta-sdk/rust/sdk/src/entities/entity_facade.rs index 064e758d59b0..5513e85cca1e 100644 --- a/tuta-sdk/rust/sdk/src/entities/entity_facade.rs +++ b/tuta-sdk/rust/sdk/src/entities/entity_facade.rs @@ -1197,7 +1197,6 @@ mod tests { .assert_dict_mut_ref() .insert(String::from(ID_FIELD), expected_aggregate_id.clone()); } - let encrypted_mail = encrypted_mail; let mut decrypted_mail = entity_facade .decrypt_and_map( @@ -1322,7 +1321,7 @@ mod tests { let encrypted_subject = encrypted_mail.get("subject").unwrap(); let subject_and_iv = sk - .decrypt_data_and_iv(&encrypted_subject.assert_bytes()) + .decrypt_data_and_iv(encrypted_subject.assert_bytes()) .unwrap(); assert_eq!( @@ -1339,7 +1338,7 @@ mod tests { .get("name") .unwrap() .assert_bytes() - .to_vec(); + .clone(); let recipient_and_iv = sk.decrypt_data_and_iv(&encrypted_recipient_name).unwrap(); assert_eq!(original_iv, recipient_and_iv.iv) } diff --git a/tuta-sdk/rust/uniffi-bindgen/Cargo.toml b/tuta-sdk/rust/uniffi-bindgen/Cargo.toml index ba994b56fe6e..b81fe10c6523 100644 --- a/tuta-sdk/rust/uniffi-bindgen/Cargo.toml +++ b/tuta-sdk/rust/uniffi-bindgen/Cargo.toml @@ -8,3 +8,6 @@ homepage.workspace = true [dependencies] uniffi = { workspace = true, features = ["cli"] } + +[lints] +workspace = true \ No newline at end of file From 527ae6b6179b174cb16cd23c64f6e90ff46062f2 Mon Sep 17 00:00:00 2001 From: nig Date: Thu, 23 Jan 2025 16:22:17 +0100 Subject: [PATCH 10/23] remove all default members from workspace commands like `cargo test`, `cargo clippy` or `cargo check` now require the `--all` or `--package` flags to choose which workspace members they should run for. this prevents the mobile apps from building node-mimimi --- .../{tuta-sdk-test.yml => rust-test.yml} | 13 +++++++------ Cargo.toml | 5 +++++ tuta-sdk/android/sdk/build.gradle.kts | 16 +++++++++++++++- 3 files changed, 27 insertions(+), 7 deletions(-) rename .github/workflows/{tuta-sdk-test.yml => rust-test.yml} (70%) diff --git a/.github/workflows/tuta-sdk-test.yml b/.github/workflows/rust-test.yml similarity index 70% rename from .github/workflows/tuta-sdk-test.yml rename to .github/workflows/rust-test.yml index 439895002003..6749d752aace 100644 --- a/.github/workflows/tuta-sdk-test.yml +++ b/.github/workflows/rust-test.yml @@ -5,7 +5,8 @@ on: types: [ opened, synchronize, edited ] paths: - 'tuta-sdk/**' - - '.github/workflows/tuta-sdk-test.yml' + - 'packages/node-mimimi/**' + - '.github/workflows/rust-test.yml' push: branches: - dev-* @@ -35,9 +36,9 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - uses: ./.github/shared/setup-rust - - name: sdk format + - name: rust format run: cargo fmt --check - - name: sdk warning check with clippy - run: cargo clippy --no-deps -- -Dwarnings # -Dwarnings changes warnings to errors so that the check fails - - name: sdk tests - run: cargo test + - name: rust warning check with clippy + run: cargo clippy --all --no-deps -- -Dwarnings # -Dwarnings changes warnings to errors so that the check fails + - name: rust tests + run: cargo test --all diff --git a/Cargo.toml b/Cargo.toml index 2473cded6586..7b442e738116 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,11 @@ members = [ "tuta-sdk/rust/uniffi-bindgen", ] +# in most cases, we don't want to build everything. +# this requires passing --all or --package to +# cargo for almost anything +default-members = [] + [workspace.dependencies] thiserror = { version = "2.0" } base64 = { version = "0.22.1" } diff --git a/tuta-sdk/android/sdk/build.gradle.kts b/tuta-sdk/android/sdk/build.gradle.kts index 5767ebfae235..09c4a9a433fe 100644 --- a/tuta-sdk/android/sdk/build.gradle.kts +++ b/tuta-sdk/android/sdk/build.gradle.kts @@ -95,6 +95,7 @@ dependencies { } cargo { + extraCargoBuildArguments = listOf("--package", "tuta-sdk") module = "../../rust" libname = "tutasdk" prebuiltToolchains = true @@ -114,7 +115,20 @@ tasks.register("generateBinding") { exec { this.workingDir("../../rust") this.executable("cargo") - this.args("run", "--bin", "uniffi-bindgen", "generate", "--library", "${layout.buildDirectory.asFile.get()}/rustJniLibs/android/${dir}/libtutasdk.so", "--language", "kotlin", "--out-dir", "${layout.buildDirectory.asFile.get()}/generated-sources/tuta-sdk") + this.args( + "run", + "--package", + "uniffi-bindgen", + "--bin", + "uniffi-bindgen", + "generate", + "--library", + "${layout.buildDirectory.asFile.get()}/rustJniLibs/android/${dir}/libtutasdk.so", + "--language", + "kotlin", + "--out-dir", + "${layout.buildDirectory.asFile.get()}/generated-sources/tuta-sdk" + ) } } } From 804d72c2ea5dd476531554669232f2a1fdb37ff9 Mon Sep 17 00:00:00 2001 From: Kinan <104761667+kibibytium@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:47:36 +0100 Subject: [PATCH 11/23] don't log mlock warnings for tests mlock is used by sqlcipher but is not allowed on our test containers see https://github.com/sqlcipher/sqlcipher-android/issues/48#issuecomment-2319088794 see https://github.com/systemd/systemd/issues/9414#issuecomment-400013088 --- src/common/desktop/db/DesktopSqlCipher.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/desktop/db/DesktopSqlCipher.ts b/src/common/desktop/db/DesktopSqlCipher.ts index 9465858cd699..bbab0cc32fb7 100644 --- a/src/common/desktop/db/DesktopSqlCipher.ts +++ b/src/common/desktop/db/DesktopSqlCipher.ts @@ -5,6 +5,7 @@ import { SqlCipherFacade } from "../../native/common/generatedipc/SqlCipherFacad import { OfflineDbClosedError } from "../../api/common/error/OfflineDbClosedError.js" import { ProgrammingError } from "../../api/common/error/ProgrammingError.js" import { TaggedSqlValue, tagSqlObject, untagSqlValue } from "../../api/worker/offline/SqlValue.js" +import { Mode } from "../../api/common/Env.js" export class DesktopSqlCipher implements SqlCipherFacade { private _db: Database | null = null @@ -81,6 +82,9 @@ export class DesktopSqlCipher implements SqlCipherFacade { // integrity check breaks tests integrityCheck: boolean }) { + if (env.mode === Mode.Test) { + this.db.pragma("cipher_log_source = NONE") + } if (enableMemorySecurity) { this.db.pragma("cipher_memory_security = ON") } From bb523d094c0218fea0d119d66740cd3ec586aa53 Mon Sep 17 00:00:00 2001 From: Kinan <104761667+kibibytium@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:38:10 +0100 Subject: [PATCH 12/23] delete import state id on cancel --- packages/node-mimimi/src/importer.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/node-mimimi/src/importer.rs b/packages/node-mimimi/src/importer.rs index be80c68ca6e3..2d3b6b8b53b9 100644 --- a/packages/node-mimimi/src/importer.rs +++ b/packages/node-mimimi/src/importer.rs @@ -676,6 +676,14 @@ impl Importer { }, ImportProgressAction::Stop => { self.update_failed_mails_counter().await; + FileImport::delete_state_file(&self.essentials.import_directory).map_err( + |_del_err| { + MailImportErrorMessage::with_path( + ImportErrorKind::FileDeletionError, + self.essentials.import_directory.clone(), + ) + }, + )?; return Ok(ImportOkKind::UserCancelInterruption); }, From 5650515537d34b71d9f0c25161e0e5f818a4e637 Mon Sep 17 00:00:00 2001 From: Kinan <104761667+kibibytium@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:30:52 +0100 Subject: [PATCH 13/23] use MailSetEntry ListId in process imported mails --- src/mail-app/mail/view/MailViewModel.ts | 31 +++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/mail-app/mail/view/MailViewModel.ts b/src/mail-app/mail/view/MailViewModel.ts index 676a857fb990..d018eedb1cb2 100644 --- a/src/mail-app/mail/view/MailViewModel.ts +++ b/src/mail-app/mail/view/MailViewModel.ts @@ -6,6 +6,7 @@ import { Mail, MailBox, MailFolder, + MailFolderTypeRef, MailSetEntry, MailSetEntryTypeRef, MailTypeRef, @@ -24,7 +25,7 @@ import { debounce, first, groupBy, - isNotEmpty, + isEmpty, lastThrow, lazyMemoized, mapWith, @@ -530,30 +531,30 @@ export class MailViewModel { private async processImportedMails(update: EntityUpdateData) { const importMailState = await this.entityClient.load(ImportMailStateTypeRef, [update.instanceListId, update.instanceId]) + const importedFolder = await this.entityClient.load(MailFolderTypeRef, importMailState.targetFolder) const listModelOfImport = this.listModelForFolder(elementIdPart(importMailState.targetFolder)) let status = parseInt(importMailState.status) as ImportStatus if (status === ImportStatus.Finished || status === ImportStatus.Canceled) { let importedMailEntries = await this.entityClient.loadAll(ImportedMailTypeRef, importMailState.importedMails) - if (importedMailEntries.length === 0) return Promise.resolve() + if (isEmpty(importedMailEntries)) return Promise.resolve() let mailSetEntryIds = importedMailEntries.map((importedMail) => elementIdPart(importedMail.mailSetEntry)) const mailSetEntryListId = listIdPart(importedMailEntries[0].mailSetEntry) const importedMailSetEntries = await this.entityClient.loadMultiple(MailSetEntryTypeRef, mailSetEntryListId, mailSetEntryIds) - if (isNotEmpty(importedMailSetEntries)) { - // put mails into cache before list model will download them one by one - await this.preloadMails(importedMailSetEntries) - - await promiseMap(importedMailSetEntries, (importedMailSetEntry) => { - return listModelOfImport.handleEntityUpdate({ - instanceListId: listIdPart(importedMailSetEntry._id), - instanceId: elementIdPart(importedMailSetEntry._id), - operation: OperationType.CREATE, - type: MailSetEntryTypeRef.type, - application: MailSetEntryTypeRef.app, - }) + if (isEmpty(importedMailSetEntries)) return Promise.resolve() + + // put mails into cache before list model will download them one by one + await this.preloadMails(importedMailSetEntries) + await promiseMap(importedMailSetEntries, (importedMailSetEntry) => { + return listModelOfImport.handleEntityUpdate({ + instanceId: elementIdPart(importedMailSetEntry._id), + instanceListId: importedFolder.entries, + operation: OperationType.CREATE, + type: MailSetEntryTypeRef.type, + application: MailSetEntryTypeRef.app, }) - } + }) } } From c7e8af2478b47934cf25eb106fe0dd94cbb4a63e Mon Sep 17 00:00:00 2001 From: nig Date: Tue, 4 Feb 2025 10:12:17 +0100 Subject: [PATCH 14/23] use the repository root as a cargo workspace this will improve incremental compilation times since we don't use multiple target folders anymore. Co-authored-by: sug --- Cargo.lock | 55 +++++++++---------- Cargo.toml | 8 ++- packages/node-mimimi/Cargo.toml | 6 +- tuta-sdk/ios/build-scripts/generate-swift.sh | 24 ++++---- tuta-sdk/ios/build-scripts/make-tutasdk.sh | 4 +- .../ios/build-scripts/xc-universal-binary.sh | 7 ++- tuta-sdk/rust/sdk/Cargo.toml | 2 +- 7 files changed, 54 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7535fe6d0f95..36d6c6e502d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2422,9 +2422,8 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "uniffi" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb08c58c7ed7033150132febe696bef553f891b1ede57424b40d87a89e3c170" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "anyhow", "camino", @@ -2444,9 +2443,8 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cade167af943e189a55020eda2c314681e223f1e42aca7c4e52614c2b627698f" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "anyhow", "askama", @@ -2462,14 +2460,14 @@ dependencies = [ "textwrap", "toml", "uniffi_meta", + "uniffi_testing", "uniffi_udl", ] [[package]] name = "uniffi_build" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7cf32576e08104b7dc2a6a5d815f37616e66c6866c2a639fe16e6d2286b75b" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "anyhow", "camino", @@ -2478,9 +2476,8 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "quote", "syn", @@ -2488,12 +2485,12 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7687007d2546c454d8ae609b105daceb88175477dac280707ad6d95bcd6f1f" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "anyhow", "bytes", + "camino", "log 0.4.25", "once_cell", "paste", @@ -2502,9 +2499,8 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c65a5b12ec544ef136693af8759fb9d11aefce740fb76916721e876639033b" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "bincode", "camino", @@ -2520,9 +2516,8 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a74ed96c26882dac1ca9b93ca23c827e284bacbd7ec23c6f0b0372f747d59e4" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "anyhow", "bytes", @@ -2532,9 +2527,8 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6f984f0781f892cc864a62c3a5c60361b1ccbd68e538e6c9fbced5d82268ac" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "anyhow", "camino", @@ -2545,9 +2539,8 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037820a4cfc4422db1eaa82f291a3863c92c7d1789dc513489c36223f9b4cdfc" +version = "0.27.1" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "anyhow", "textwrap", @@ -2592,8 +2585,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "weedle2" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +source = "git+https://github.com/mozilla/uniffi-rs.git?rev=13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c#13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" dependencies = [ "nom", ] @@ -2807,3 +2799,8 @@ dependencies = [ "quote", "syn", ] + +[[patch.unused]] +name = "pqcrypto-internals" +version = "0.2.7" +source = "git+https://github.com/rustpq/pqcrypto?rev=b39937410b149156de81fc8e3150753aff925aa4#b39937410b149156de81fc8e3150753aff925aa4" diff --git a/Cargo.toml b/Cargo.toml index 7b442e738116..ad19a76d8840 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,13 +27,14 @@ members = [ default-members = [] [workspace.dependencies] +tuta-sdk = { path = "tuta-sdk/rust/sdk", default-features = false } thiserror = { version = "2.0" } base64 = { version = "0.22.1" } log = { version = "0.4.22" } serde = { version = "1.0.210", features = ["derive"] } serde_json = { version = "1.0.117" } tokio = { version = "1.43", default-features = false } -uniffi = { version = "0.28.3", default-features = false } +uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c", default-features = false } rand = { version = "0.8" } # --------- lints @@ -56,4 +57,7 @@ explicit_iter_loop = "warn" [workspace.lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci)'] } \ No newline at end of file +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci)'] } + +[patch.'https://github.com/rust-lang/crates.io-index'] +pqcrypto-internals = { git = 'https://github.com/rustpq/pqcrypto', rev = 'b39937410b149156de81fc8e3150753aff925aa4' } \ No newline at end of file diff --git a/packages/node-mimimi/Cargo.toml b/packages/node-mimimi/Cargo.toml index f547ab4a4314..a957cbc62468 100644 --- a/packages/node-mimimi/Cargo.toml +++ b/packages/node-mimimi/Cargo.toml @@ -19,7 +19,7 @@ javascript = ["dep:napi-derive"] napi = { version = "2.16.13", default-features = false, features = ["napi9", "async", "tokio_rt"] } napi-derive = { version = "2.16.13", optional = true } mail-parser = { version = "0.10.2", features = ["full_encoding"] } -tuta-sdk = { path = "../../tuta-sdk/rust/sdk", features = ["net"] } +tuta-sdk = { workspace = true, features = ["net"] } rand = { workspace = true } log = { workspace = true } base64 = { workspace = true } @@ -29,10 +29,10 @@ regex = { version = "1.11.1" } napi-build = { version = "2.1.4" } [dev-dependencies] -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +tokio = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } -tuta-sdk = { path = "../../tuta-sdk/rust/sdk", features = ["net", "testing"] } +tuta-sdk = { workspace = true, features = ["net", "testing"] } mail-builder = { version = "0.4.0" } [lints] diff --git a/tuta-sdk/ios/build-scripts/generate-swift.sh b/tuta-sdk/ios/build-scripts/generate-swift.sh index 59defc098917..54108cc8c8ae 100755 --- a/tuta-sdk/ios/build-scripts/generate-swift.sh +++ b/tuta-sdk/ios/build-scripts/generate-swift.sh @@ -32,23 +32,23 @@ createFolderStructure() { mkdir "${SRC_ROOT}/target/combined/" mkdir "${SRC_ROOT}/target/combined/${RELFLAG}" - if [ -d "${SRC_ROOT}/../ios/tutasdk/generated-src" ]; then - rm -r "${SRC_ROOT}/../ios/tutasdk/generated-src" + if [ -d "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src" ]; then + rm -r "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src" fi - mkdir "${SRC_ROOT}/../ios/tutasdk/generated-src" - mkdir "${SRC_ROOT}/../ios/tutasdk/generated-src/headers" - mkdir "${SRC_ROOT}/../ios/tutasdk/generated-src/Sources" + mkdir "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src" + mkdir "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src/headers" + mkdir "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src/Sources" } generateLibrary() { lipo -create $ARCH_LIST -output "${SRC_ROOT}/target/combined/${RELFLAG}/libtutasdk.a" - cp "${SRC_ROOT}/target/combined/${RELFLAG}/libtutasdk.a" "${SRC_ROOT}/../ios/tutasdk/generated-src/tutasdk.a" + cp "${SRC_ROOT}/target/combined/${RELFLAG}/libtutasdk.a" "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src/tutasdk.a" - mv $SRC_ROOT/bindings/*.h "${SRC_ROOT}/../ios/tutasdk/generated-src/headers/" - mv $SRC_ROOT/bindings/*.modulemap "${SRC_ROOT}/../ios/tutasdk/generated-src/headers/" - mv $SRC_ROOT/bindings/*.swift "${SRC_ROOT}/../ios/tutasdk/generated-src/Sources/" + mv $SRC_ROOT/bindings/*.h "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src/headers/" + mv $SRC_ROOT/bindings/*.modulemap "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src/headers/" + mv $SRC_ROOT/bindings/*.swift "${SRC_ROOT}/tuta-sdk/ios/tutasdk/generated-src/Sources/" } cd $SRC_ROOT @@ -66,18 +66,18 @@ for arch in $ARCHS; do fi # Intel iOS simulator - cargo run --bin uniffi-bindgen generate --library "${SRC_ROOT}/target/x86_64-apple-ios/${RELFLAG}/libtutasdk.dylib" --out-dir "$SRC_ROOT/bindings" --language=swift + cargo run --package uniffi-bindgen -- generate --library "${SRC_ROOT}/target/x86_64-apple-ios/${RELFLAG}/libtutasdk.dylib" --out-dir "$SRC_ROOT/bindings" --language=swift includeArch "${SRC_ROOT}/target/x86_64-apple-ios/${RELFLAG}/libtutasdk.a"; ;; arm64) if [ $IS_SIMULATOR -eq 0 ]; then # Hardware iOS targets - cargo run --bin uniffi-bindgen generate --library "${SRC_ROOT}/target/aarch64-apple-ios/${RELFLAG}/libtutasdk.dylib" --out-dir "$SRC_ROOT/bindings" --language=swift + cargo run --package uniffi-bindgen -- generate --library "${SRC_ROOT}/target/aarch64-apple-ios/${RELFLAG}/libtutasdk.dylib" --out-dir "$SRC_ROOT/bindings" --language=swift includeArch "${SRC_ROOT}/target/aarch64-apple-ios/${RELFLAG}/libtutasdk.a"; else # M1 iOS simulator - cargo run --bin uniffi-bindgen generate --library "${SRC_ROOT}/target/aarch64-apple-ios-sim/${RELFLAG}/libtutasdk.dylib" --out-dir "$SRC_ROOT/bindings" --language=swift + cargo run --package uniffi-bindgen -- generate --library "${SRC_ROOT}/target/aarch64-apple-ios-sim/${RELFLAG}/libtutasdk.dylib" --out-dir "$SRC_ROOT/bindings" --language=swift includeArch "${SRC_ROOT}/target/aarch64-apple-ios-sim/${RELFLAG}/libtutasdk.a"; fi esac diff --git a/tuta-sdk/ios/build-scripts/make-tutasdk.sh b/tuta-sdk/ios/build-scripts/make-tutasdk.sh index 9f33675a6157..188af798ea6c 100755 --- a/tuta-sdk/ios/build-scripts/make-tutasdk.sh +++ b/tuta-sdk/ios/build-scripts/make-tutasdk.sh @@ -6,5 +6,5 @@ if [ "${LLVM_TARGET_TRIPLE_SUFFIX-}" = "-simulator" ]; then IS_SIMULATOR=1 fi -bash $SRCROOT/build-scripts/xc-universal-binary.sh tuta-sdk $SRCROOT/../rust "$CONFIGURATION" -env -i HOME="$HOME" bash -l -c "$SRCROOT/build-scripts/generate-swift.sh $SRCROOT/../rust \"$CONFIGURATION\" \"$IS_SIMULATOR\" \"${ARCHS[@]}\"" +bash $SRCROOT/build-scripts/xc-universal-binary.sh tuta-sdk $SRCROOT/../.. "$CONFIGURATION" +env -i HOME="$HOME" bash -l -c "$SRCROOT/build-scripts/generate-swift.sh $SRCROOT/../.. \"$CONFIGURATION\" \"$IS_SIMULATOR\" \"${ARCHS[@]}\"" diff --git a/tuta-sdk/ios/build-scripts/xc-universal-binary.sh b/tuta-sdk/ios/build-scripts/xc-universal-binary.sh index e74bc6c6b73a..ba46fb370327 100755 --- a/tuta-sdk/ios/build-scripts/xc-universal-binary.sh +++ b/tuta-sdk/ios/build-scripts/xc-universal-binary.sh @@ -22,6 +22,7 @@ fi FFI_TARGET=${1} # path to source code root SRC_ROOT=${2} +echo SRC_ROOT=$SRC_ROOT # buildvariant from our xcconfigs BUILDVARIANT=$(echo "${3}" | tr '[:upper:]' '[:lower:]') @@ -45,16 +46,16 @@ for arch in $ARCHS; do # Intel iOS simulator export CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios" - cargo rustc --manifest-path="${SRC_ROOT}/sdk/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target x86_64-apple-ios -v + cargo rustc --manifest-path="${SRC_ROOT}/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target x86_64-apple-ios -v ;; arm64) if [ $IS_SIMULATOR -eq 0 ]; then # Hardware iOS targets - cargo rustc --manifest-path="${SRC_ROOT}/sdk/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target aarch64-apple-ios -v + cargo rustc --manifest-path="${SRC_ROOT}/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target aarch64-apple-ios -v else # M1 iOS simulator - cargo rustc --manifest-path="${SRC_ROOT}/sdk/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target aarch64-apple-ios-sim -v + cargo rustc --manifest-path="${SRC_ROOT}/Cargo.toml" -p "${FFI_TARGET}" --lib $RELFLAG --target aarch64-apple-ios-sim -v fi esac done diff --git a/tuta-sdk/rust/sdk/Cargo.toml b/tuta-sdk/rust/sdk/Cargo.toml index fe9dd7e9d7bc..4c5f0ef3283e 100644 --- a/tuta-sdk/rust/sdk/Cargo.toml +++ b/tuta-sdk/rust/sdk/Cargo.toml @@ -38,7 +38,7 @@ rand_core = { version = "0.6.4", features = ["getrandom"] } log = { workspace = true } base64 = { workspace = true } -tokio = { workspace = true, features = ["time", "rt-multi-thread", "sync"] } +tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros", "sync", "time"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } uniffi = { workspace = true } From 92df0eefe0b0f3434aa0d8beb789a1f3cc437f01 Mon Sep 17 00:00:00 2001 From: nig Date: Wed, 29 Jan 2025 09:21:42 +0100 Subject: [PATCH 15/23] [calendar] fix calendar update notifications missing new exclusions closes #8416 fixes bbf68fbfda09c8b0b73cbc087b68b99ee5b3e8a2 --- .../calendar/gui/eventeditor-model/CalendarEventModelStrategy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calendar-app/calendar/gui/eventeditor-model/CalendarEventModelStrategy.ts b/src/calendar-app/calendar/gui/eventeditor-model/CalendarEventModelStrategy.ts index 7a151954b302..f8cf0ac668d2 100644 --- a/src/calendar-app/calendar/gui/eventeditor-model/CalendarEventModelStrategy.ts +++ b/src/calendar-app/calendar/gui/eventeditor-model/CalendarEventModelStrategy.ts @@ -218,7 +218,6 @@ export class CalendarEventApplyStrategies { CalendarOperation.DeleteThis, ) const recurrenceIds = await this.lazyRecurrenceIds(progenitor.uid) - recurrenceIds.push(existingInstance.startTime) await this.notificationModel.send(newEvent, recurrenceIds, sendModels) await this.calendarModel.updateEvent(newEvent, newAlarms, this.zone, calendar.groupRoot, progenitor) })(), From 6cf986a2927ace47896b843675647918af8289a3 Mon Sep 17 00:00:00 2001 From: sug Date: Wed, 5 Feb 2025 14:57:07 +0100 Subject: [PATCH 16/23] [mimimi] make it possible to import apple mail exports apple mail exports directories that masquerade as mbox files, but at least the structure is predictable. --- .../node-mimimi/src/importer/file_reader.rs | 165 +++++++++++++----- tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs | 54 +++++- tuta-sdk/rust/sdk/src/json_serializer.rs | 8 +- 3 files changed, 178 insertions(+), 49 deletions(-) diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index 6c2884fa96b8..fa32e132adc8 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -13,6 +13,47 @@ pub struct FileImport { message_parser: MessageParser, } +/// mail programs have different ways to structure their exports +/// we want to normalize these into either eml or mbox files, +/// so we take the path given to us by the user and apply +/// transformations that make the path point at the actual +/// mail content +#[cfg_attr(test, derive(PartialEq, Debug))] +enum ExportContent { + /// contains a single mail + Eml(PathBuf), + /// contains a bunch of mails + Mbox(PathBuf), +} + +impl ExportContent { + /// convert a user-provided path into a form that can be given to the importer + pub fn try_from_path(path: PathBuf) -> Option { + if path.is_file() { + let ext = path.extension(); + if ext == Some("mbox".as_ref()) { + Some(Self::Mbox(path)) + } else if ext == Some("eml".as_ref()) { + Some(Self::Eml(path)) + } else { + None + } + } else if path.is_dir() && path.extension() == Some("mbox".as_ref()) { + // apple mail exports are + // a directory with an .mbox extension that contains a file named mbox + // that contains the bunch of mails + let actual_mbox = path.join("mbox"); + if actual_mbox.is_file() { + Some(Self::Mbox(actual_mbox)) + } else { + None + } + } else { + None + } + } +} + impl FileImport { fn next_importable_mail(&mut self) -> Result, FileIterationError> { let Some(eml_path) = self.queued_eml_paths.pop() else { @@ -83,51 +124,53 @@ impl FileImport { .map_err(|_| PreparationError::CanNotCreateImportDir)?; for source_path in source_paths { - let is_mbox_file = source_path.extension() == Some("mbox".as_ref()); - let is_eml_file = source_path.extension() == Some("eml".as_ref()); - - if is_mbox_file { - filename_producer.new_mbox(&source_path); - - let file_buf_reader = - fs::File::open(&source_path) - .map(BufReader::new) - .map_err(|read_err| { - log::error!("Can not read file: {source_path:?}. Error: {read_err:?}"); - PreparationError::FileReadError + let export_content = ExportContent::try_from_path(source_path); + match export_content { + Some(ExportContent::Mbox(content)) => { + filename_producer.new_mbox(&content); + + let file_buf_reader = + fs::File::open(&content) + .map(BufReader::new) + .map_err(|read_err| { + log::error!("Can not read file: {content:?}. Error: {read_err:?}"); + PreparationError::FileReadError + })?; + + let msg_iterator = MessageIterator::new(file_buf_reader); + for parsed_message in msg_iterator { + let eml_filepath = filename_producer.new_file_of_current_mbox(); + + let parsed_message = parsed_message.map_err(|parse_err| { + log::error!("Can not parse a message from mbox: {parse_err:?}"); + PreparationError::NotAValidEmailFile })?; - let msg_iterator = MessageIterator::new(file_buf_reader); - for parsed_message in msg_iterator { - let eml_filepath = filename_producer.new_file_of_current_mbox(); - - let parsed_message = parsed_message.map_err(|parse_err| { - log::error!("Can not parse a message from mbox: {parse_err:?}"); - PreparationError::NotAValidEmailFile - })?; - - fs::write( - import_directory_path.join(eml_filepath.clone()), - parsed_message.contents(), - ) - .map_err(|write_e| { - log::error!( + fs::write( + import_directory_path.join(eml_filepath.clone()), + parsed_message.contents(), + ) + .map_err(|write_e| { + log::error!( "Can not write deconstructed eml: {eml_filepath:?}. Error: {write_e:?}" ); + PreparationError::EmlFileWriteFailure + })?; + } + }, + Some(ExportContent::Eml(content)) => { + let target_eml_file_path = filename_producer.new_plain_eml(&content); + + fs::copy(&content, &target_eml_file_path).map_err(|copy_err| { + log::error!("Can not copy eml: {content:?}. Error: {copy_err:?}"); PreparationError::EmlFileWriteFailure })?; - } - } else if is_eml_file { - let target_eml_file_path = filename_producer.new_plain_eml(&source_path); - - fs::copy(&source_path, &target_eml_file_path).map_err(|copy_err| { - log::error!("Can not copy eml: {source_path:?}. Error: {copy_err:?}"); - PreparationError::EmlFileWriteFailure - })?; - } else { - // we're ignoring files that are not eml or mbox because we try to - // configure the dialog to only allow selecting those. - // user probably uses some weird setup. + }, + None => { + // we're ignoring files that are not eml or mbox because we try to + // configure the dialog to only allow selecting those. + // user probably uses some weird setup. + }, } } @@ -187,9 +230,9 @@ impl FileImport { #[cfg(test)] mod test { - use crate::importer::file_reader::FileImport; + use crate::importer::file_reader::{ExportContent, FileImport}; use crate::importer::{Importer, STATE_ID_FILE_NAME}; - use crate::test_utils::get_test_id; + use crate::test_utils::{get_test_id, CleanDir}; use std::fs; use std::fs::File; use std::io::Write; @@ -380,6 +423,46 @@ Yeah, but I really did not like it. Had higher hopes after watching that Simpson } } + #[test] + fn should_find_mbox_in_apple_exported_directory() { + let test_dir = PathBuf::from("/tmp/should_find_mbox_in_apple_exported_directory"); + let _teardown = CleanDir { + dir: test_dir.clone(), + }; + fs::create_dir(&test_dir).unwrap(); + + let apple_export = test_dir.join("apple-export.mbox"); + fs::create_dir(&apple_export).unwrap(); + fs::write(test_dir.join("apple-export.mbox/mbox"), "").unwrap(); + + let export_content = ExportContent::try_from_path(apple_export).unwrap(); + assert_eq!( + ExportContent::Mbox(PathBuf::from( + "/tmp/should_find_mbox_in_apple_exported_directory/apple-export.mbox/mbox" + )), + export_content + ); + } + + #[test] + fn should_ignore_all_other_dirs() { + let test_dir = PathBuf::from("/tmp/should_ignore_all_other_dirs"); + let _teardown = CleanDir { + dir: test_dir.clone(), + }; + fs::create_dir(&test_dir).unwrap(); + + let apple_export = test_dir.join("apple-export.mbox"); + fs::create_dir(&apple_export).unwrap(); + fs::write(test_dir.join("apple-export.mbox/mbux"), "").unwrap(); + + let apple_export2 = test_dir.join("apple-export2.mbux"); + fs::create_dir(&apple_export2).unwrap(); + fs::write(test_dir.join("apple-export2.mbux/mbox"), "").unwrap(); + + assert_eq!(None, ExportContent::try_from_path(apple_export)); + assert_eq!(None, ExportContent::try_from_path(apple_export2)); + } #[tokio::test] async fn should_remove_previous_emls_while_preparing_new_import() { let config_dir_string = "/tmp/should_remove_previous_emls_while_preparing_new_import"; diff --git a/tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs b/tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs index 2ccdfec63946..478a2b20d05e 100644 --- a/tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs +++ b/tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs @@ -299,7 +299,6 @@ pub fn create_auth_verifier(user_passphrase_key: Aes256Key) -> String { BASE64_URL_SAFE_NO_PAD.encode(sha_user_passphrase) } -// FIXME: check for returned owner_enc_session_key #[cfg(test)] mod test { use crate::crypto::aes::{Aes256Key, Iv}; @@ -423,6 +422,49 @@ mod test { ); } + #[tokio::test] + async fn owner_enc_session_key_is_returned_if_no_bucket_key() { + let randomizer_facade = make_thread_rng_facade(); + + let constants = BucketKeyConstants::new(&randomizer_facade); + let session_key = GenericAesKey::from(Aes256Key::generate(&randomizer_facade)); + let owner_enc_session_key = constants + .group_key + .encrypt_key(&session_key, Iv::generate(&randomizer_facade)); + + let mail = Mail { + _id: Some(IdTupleGenerated { + list_id: constants.instance_list.clone(), + element_id: constants.instance_id.clone(), + }), + _ownerGroup: Some(constants.key_group.clone()), + bucketKey: None, + _ownerEncSessionKey: Some(owner_enc_session_key.clone()), + _ownerKeyVersion: Some(constants.recipient_key_version as i64), + ..create_test_entity() + }; + + let raw_mail = typed_entity_to_parsed_entity(mail); + + let asymmetric_crypto_facade = MockAsymmetricCryptoFacade::default(); + + let crypto_facade = make_crypto_facade( + randomizer_facade.clone(), + constants.group_key.clone(), + constants.sender_key_version, + Some(asymmetric_crypto_facade), + ); + + let mail_type_model = get_mail_type_model(); + let key = crypto_facade + .resolve_session_key(&raw_mail, &mail_type_model) + .await + .expect("should not have errored") + .expect("where is the key"); + + assert_eq!(session_key.as_bytes(), key.session_key.as_bytes()); + } + fn get_mail_type_model() -> TypeModel { let provider = init_type_model_provider(); let mail_type_ref = Mail::type_ref(); @@ -514,15 +556,19 @@ mod test { asymmetric_crypto_facade: Option, ) -> CryptoFacade { let mut key_loader = MockKeyLoaderFacade::default(); + let group_key_clone = group_key.clone(); key_loader .expect_get_current_sym_group_key() .returning(move |_| { Ok(VersionedAesKey { version: sender_key_version, - object: group_key.clone(), + object: group_key_clone.clone(), }) - }) - .once(); + }); + + key_loader + .expect_load_sym_group_key() + .returning(move |_, _, _| Ok(group_key.clone())); let asymmetric_crypto_facade = asymmetric_crypto_facade.unwrap_or_default(); diff --git a/tuta-sdk/rust/sdk/src/json_serializer.rs b/tuta-sdk/rust/sdk/src/json_serializer.rs index 07bb8c65f93a..44e6f8275b46 100644 --- a/tuta-sdk/rust/sdk/src/json_serializer.rs +++ b/tuta-sdk/rust/sdk/src/json_serializer.rs @@ -140,7 +140,7 @@ impl JsonSerializer { Cardinality::One | Cardinality::ZeroOrOne, JsonElement::String(id), ) => { - // FIXME it's not always generated id but it's fine probably + // NOTE: it's not always generated id but it's fine probably mapped.insert( association_name, ElementValue::IdGeneratedId(GeneratedId(id)), @@ -373,7 +373,7 @@ impl JsonSerializer { Cardinality::One | Cardinality::ZeroOrOne, ElementValue::IdGeneratedId(id), ) => { - // FIXME it's not always generated id but it's fine probably + // Note: it's not always generated id but it's fine probably JsonElement::String(id.into()) }, ( @@ -480,7 +480,7 @@ impl JsonSerializer { }) }; - // FIXME there are more null/empty cases we need to take care of + // NOTE: there are more null/empty cases we need to take care of if model_value.cardinality == Cardinality::ZeroOrOne && element_value == ElementValue::Null { Ok(JsonElement::Null) @@ -624,7 +624,7 @@ impl JsonSerializer { }) }; - // FIXME there are more null/empty cases we need to take care of + // NOTE there are more null/empty cases we need to take care of if model_value.cardinality == Cardinality::ZeroOrOne && json_value == JsonElement::Null { return Ok(ElementValue::Null); } From 28a02ac17bb59dcb69b452976af3f9150a0ffcb4 Mon Sep 17 00:00:00 2001 From: nig Date: Thu, 6 Feb 2025 14:07:02 +0100 Subject: [PATCH 17/23] [mimimi] fix formatting --- packages/node-mimimi/src/reduce_to_chunks.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node-mimimi/src/reduce_to_chunks.rs b/packages/node-mimimi/src/reduce_to_chunks.rs index b9a18c5a39c7..699055e338ee 100644 --- a/packages/node-mimimi/src/reduce_to_chunks.rs +++ b/packages/node-mimimi/src/reduce_to_chunks.rs @@ -29,8 +29,10 @@ impl AttachmentUploadData { let eml_file_path = importable_mail.eml_file_path.clone(); let attachments = importable_mail.take_out_attachments(); - let import_mail_data = importable_mail - .make_import_mail_data(owner_enc_session_key.object, owner_enc_session_key.version as i64); + let import_mail_data = importable_mail.make_import_mail_data( + owner_enc_session_key.object, + owner_enc_session_key.version as i64, + ); AttachmentUploadData { keyed_import_mail_data: KeyedImportMailData { From d65fea0267d51dab998a96c109bac49c677a7523 Mon Sep 17 00:00:00 2001 From: sug Date: Fri, 7 Feb 2025 11:04:01 +0100 Subject: [PATCH 18/23] do not lose mbox filename for apple export format mbox --- .../node-mimimi/src/importer/file_reader.rs | 150 +++++---- .../src/importer/filename_producer.rs | 302 ++++++++++-------- 2 files changed, 262 insertions(+), 190 deletions(-) diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index fa32e132adc8..63976061d59e 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -4,8 +4,10 @@ use crate::importer::messages::{FileIterationError, PreparationError}; use crate::importer::{FAILED_MAILS_SUB_DIR, STATE_ID_FILE_NAME}; use mail_parser::mailbox::mbox::MessageIterator; use mail_parser::MessageParser; +use std::fmt::Debug; use std::fs; use std::io::BufReader; +use std::ops::BitAnd; use std::path::{Path, PathBuf}; pub struct FileImport { @@ -19,37 +21,36 @@ pub struct FileImport { /// transformations that make the path point at the actual /// mail content #[cfg_attr(test, derive(PartialEq, Debug))] -enum ExportContent { +enum UserSelectedFileType { /// contains a single mail Eml(PathBuf), /// contains a bunch of mails Mbox(PathBuf), + /// actual selected file for apple file dialog will be a directory with .mbox at end of directory name, + /// ( for some reason they allow selecting directory ) and the actual mbox file will not have any extension + /// Apple mbox format: `.mbox/mbox` + AppleMbox(PathBuf), } -impl ExportContent { +impl UserSelectedFileType { /// convert a user-provided path into a form that can be given to the importer - pub fn try_from_path(path: PathBuf) -> Option { - if path.is_file() { - let ext = path.extension(); + pub fn from_path(source_path: PathBuf) -> Option { + if source_path.is_file() { + let ext = source_path.extension(); if ext == Some("mbox".as_ref()) { - Some(Self::Mbox(path)) + Some(Self::Mbox(source_path)) } else if ext == Some("eml".as_ref()) { - Some(Self::Eml(path)) - } else { - None - } - } else if path.is_dir() && path.extension() == Some("mbox".as_ref()) { - // apple mail exports are - // a directory with an .mbox extension that contains a file named mbox - // that contains the bunch of mails - let actual_mbox = path.join("mbox"); - if actual_mbox.is_file() { - Some(Self::Mbox(actual_mbox)) + Some(Self::Eml(source_path)) } else { None } } else { - None + let apple_mbox_file = source_path.join("mbox"); + let is_apple_mbox = source_path.is_dir() + && source_path.extension() == Some("mbox".as_ref()) + && apple_mbox_file.is_file(); + + is_apple_mbox.then_some(Self::Mbox(apple_mbox_file)) } } } @@ -120,53 +121,31 @@ impl FileImport { fs::create_dir_all(&import_directory_path) .map_err(|_| PreparationError::CanNotCreateImportDir)?; - fs::create_dir_all(failed_sub_directory_path) + fs::create_dir(failed_sub_directory_path) .map_err(|_| PreparationError::CanNotCreateImportDir)?; for source_path in source_paths { - let export_content = ExportContent::try_from_path(source_path); - match export_content { - Some(ExportContent::Mbox(content)) => { - filename_producer.new_mbox(&content); - - let file_buf_reader = - fs::File::open(&content) - .map(BufReader::new) - .map_err(|read_err| { - log::error!("Can not read file: {content:?}. Error: {read_err:?}"); - PreparationError::FileReadError - })?; - - let msg_iterator = MessageIterator::new(file_buf_reader); - for parsed_message in msg_iterator { - let eml_filepath = filename_producer.new_file_of_current_mbox(); - - let parsed_message = parsed_message.map_err(|parse_err| { - log::error!("Can not parse a message from mbox: {parse_err:?}"); - PreparationError::NotAValidEmailFile - })?; - - fs::write( - import_directory_path.join(eml_filepath.clone()), - parsed_message.contents(), - ) - .map_err(|write_e| { - log::error!( - "Can not write deconstructed eml: {eml_filepath:?}. Error: {write_e:?}" - ); - PreparationError::EmlFileWriteFailure - })?; - } - }, - Some(ExportContent::Eml(content)) => { - let target_eml_file_path = filename_producer.new_plain_eml(&content); + match UserSelectedFileType::from_path(source_path) { + Some(UserSelectedFileType::Eml(eml_file_path)) => { + let target_eml_file_path = filename_producer.new_plain_eml(&eml_file_path); - fs::copy(&content, &target_eml_file_path).map_err(|copy_err| { - log::error!("Can not copy eml: {content:?}. Error: {copy_err:?}"); + fs::copy(&eml_file_path, &target_eml_file_path).map_err(|copy_err| { + log::error!("Can not copy eml: {eml_file_path:?}. Error: {copy_err:?}"); PreparationError::EmlFileWriteFailure })?; }, + + Some(UserSelectedFileType::Mbox(mbox_path)) => { + filename_producer.new_mbox(&mbox_path); + Self::destruct_mbox_file(&mut filename_producer, &mbox_path)?; + }, + Some(UserSelectedFileType::AppleMbox(mbox_path)) => { + filename_producer.new_apple_mbox(&mbox_path); + Self::destruct_mbox_file(&mut filename_producer, &mbox_path)?; + }, + None => { + log::warn!("User was able to select non-mbox/non-eml files") // we're ignoring files that are not eml or mbox because we try to // configure the dialog to only allow selecting those. // user probably uses some weird setup. @@ -177,6 +156,45 @@ impl FileImport { Ok(import_directory_path) } + // When user select a mbox file, we can already destruct and directory write the parsed eml file in import dir + fn destruct_mbox_file( + filename_producer: &mut FileNameProducer, + mbox_file: &Path, + ) -> Result<(), PreparationError> { + let file_buf_reader = + fs::File::open(mbox_file) + .map(BufReader::new) + .map_err(|read_err| { + log::error!("Can not read file: {mbox_file:?}. Error: {read_err:?}"); + PreparationError::FileReadError + })?; + + let msg_iterator = MessageIterator::new(file_buf_reader); + for parsed_message in msg_iterator { + let eml_filepath = filename_producer.new_file_of_current_mbox(); + + let parsed_message = parsed_message.map_err(|parse_err| { + log::error!("Can not parse a message from mbox: {parse_err:?}"); + PreparationError::NotAValidEmailFile + })?; + + fs::write( + filename_producer + .import_directory + .join(eml_filepath.clone()), + parsed_message.contents(), + ) + .map_err(|write_e| { + log::error!( + "Can not write deconstructed eml: {eml_filepath:?}. Error: {write_e:?}" + ); + PreparationError::EmlFileWriteFailure + })?; + } + + Ok(()) + } + pub fn get_next_importable_mail(&mut self) -> Option { // try to get next mail from sources, // if it fails put the error in list ( which also contains the path itself ) @@ -230,7 +248,7 @@ impl FileImport { #[cfg(test)] mod test { - use crate::importer::file_reader::{ExportContent, FileImport}; + use crate::importer::file_reader::{FileImport, UserSelectedFileType}; use crate::importer::{Importer, STATE_ID_FILE_NAME}; use crate::test_utils::{get_test_id, CleanDir}; use std::fs; @@ -239,12 +257,14 @@ mod test { use std::path::PathBuf; use tutasdk::{GeneratedId, IdTupleGenerated}; - /// this is a macro because putting asserts in a function will not show where exactly the failure occured. + /// this is a macro because putting asserts in a function will not show where exactly the failure occurred. macro_rules! verify_file_contents { ($expected_path:expr, $actual_path:expr, $expected_contents:expr) => {{ assert_eq!($expected_path.to_owned(), $actual_path.to_owned()); - let msg = std::string::String::from_utf8(fs::read(&$actual_path).unwrap()).unwrap(); - assert_eq!($expected_contents.trim(), msg.trim()); + assert_eq!( + $expected_contents.trim(), + fs::read_to_string($actual_path).unwrap().trim() + ); }}; } @@ -435,9 +455,9 @@ Yeah, but I really did not like it. Had higher hopes after watching that Simpson fs::create_dir(&apple_export).unwrap(); fs::write(test_dir.join("apple-export.mbox/mbox"), "").unwrap(); - let export_content = ExportContent::try_from_path(apple_export).unwrap(); + let export_content = UserSelectedFileType::from_path(apple_export).unwrap(); assert_eq!( - ExportContent::Mbox(PathBuf::from( + UserSelectedFileType::Mbox(PathBuf::from( "/tmp/should_find_mbox_in_apple_exported_directory/apple-export.mbox/mbox" )), export_content @@ -460,8 +480,8 @@ Yeah, but I really did not like it. Had higher hopes after watching that Simpson fs::create_dir(&apple_export2).unwrap(); fs::write(test_dir.join("apple-export2.mbux/mbox"), "").unwrap(); - assert_eq!(None, ExportContent::try_from_path(apple_export)); - assert_eq!(None, ExportContent::try_from_path(apple_export2)); + assert_eq!(None, UserSelectedFileType::from_path(apple_export)); + assert_eq!(None, UserSelectedFileType::from_path(apple_export2)); } #[tokio::test] async fn should_remove_previous_emls_while_preparing_new_import() { diff --git a/packages/node-mimimi/src/importer/filename_producer.rs b/packages/node-mimimi/src/importer/filename_producer.rs index 4019b827d046..bbb189de0a5e 100644 --- a/packages/node-mimimi/src/importer/filename_producer.rs +++ b/packages/node-mimimi/src/importer/filename_producer.rs @@ -1,8 +1,16 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; +/// When user select single or multiple files to import, +/// we move all of those to our current_import directory in ~/.cong/tutanota-desktop- +/// and when user select mbox we destruct them to eml files, +/// In that case, we still want to preserve the filename of those destructed eml files +/// so that in case of failure or some eml file not being imported, +/// one can always trace back which mbox produced that file, +/// Also when user select multiple file with same filename ( example: they selected it from Recent menu in file dialog ) +/// we need some form of unique identifier to differentiate between those pub(super) struct FileNameProducer<'a> { - import_directory: &'a Path, + pub import_directory: &'a Path, eml_file_count: usize, mbox_file_count: usize, @@ -21,31 +29,61 @@ impl<'a> FileNameProducer<'a> { } } - pub fn new_plain_eml(&mut self, eml_path: &Path) -> PathBuf { - let original_filename = Self::format_new_file_name(eml_path, self.eml_file_count, ".eml"); + /// `eml_path` is already a single eml file path, + /// we can just add some counter to it to make it differentiate and + /// original filename will be part of new filename as well + pub fn new_plain_eml(&mut self, eml_path: impl AsRef) -> PathBuf { + let original_filename = + Self::format_new_file_name(eml_path.as_ref(), self.eml_file_count, ".eml"); self.eml_file_count += 1; - self.import_directory.join(original_filename) } - pub fn new_mbox(&mut self, mbox_path: &Path) { - let original_filename = - Self::format_new_file_name(mbox_path, self.mbox_file_count, ".mbox-item-"); + /// `mbox_path` is user selected mbox file, which we had/will destruct to it's eml file, + /// for now, just record the mbox file ( with unique counter ) + /// and all the eml of this mbox can call `Self::new_file_of_current_mbox` + /// once a new mbox is encountered, this will reset own state and start over + /// + /// Note: one should check for apple export beforehand and call `Self::new_apple_mbox` if that's the case + pub fn new_mbox(&mut self, mbox_path: impl AsRef) { + let mbox_marker = + Self::format_new_file_name(mbox_path.as_ref(), self.mbox_file_count, ".mbox-item-"); + self.current_mbox_prefix = mbox_marker; self.mbox_file_count += 1; + self.current_mbox_iter_count = 0; + } + + /// Same as `Self::new_mbox` + /// specific to Apple mail export mbox format, + /// See `file_reader::UserSelectedFileType::AppleMbox` for how this location of `mbox_path` layout is different + pub fn new_apple_mbox(&mut self, mbox_path: impl AsRef) { + let mbox_path = mbox_path.as_ref(); + let mbox_marker = Self::format_new_file_name( + mbox_path.parent().unwrap_or_else(|| { + // we were not able to get the parent of this file, how could we have selected + // root directory. this is very highly unlikely + log::info!("Can not get parent directory in new_apple_mbox"); + mbox_path + }), + self.mbox_file_count, + ".mbox-item-", + ); - self.current_mbox_prefix = original_filename; + self.current_mbox_prefix = mbox_marker; + self.mbox_file_count += 1; self.current_mbox_iter_count = 0; } + /// For all actual mail inside a mbox, each mail will be kept as eml file + /// but should preserve information about the actual mbox file it originated from pub fn new_file_of_current_mbox(&mut self) -> PathBuf { let current_prefix = &self.current_mbox_prefix; let count = self.current_mbox_iter_count; let new_filename = format!("{current_prefix}{count}.eml"); self.current_mbox_iter_count += 1; - self.import_directory.join(new_filename) } @@ -67,108 +105,101 @@ mod tests { fn producer_for_multiple_emls_only() { let import_directory = PathBuf::from("/tmp/"); let mut producer = FileNameProducer::new(&import_directory); - let first_name = producer - .new_plain_eml(&PathBuf::from("/usr/var/log/somefile.eml")) - .to_str() - .unwrap() - .to_string(); - let second_name = producer - .new_plain_eml(&PathBuf::from("/usr/var/log/someotherfile.eml")) - .to_str() - .unwrap() - .to_string(); - let third_name = producer - .new_plain_eml(&PathBuf::from("/usr/var/log/someotherfile")) - .to_str() - .unwrap() - .to_string(); - let fourth_name = producer - .new_plain_eml(&PathBuf::from("/usr/var/log/someotherdir/..")) - .to_str() - .unwrap() - .to_string(); - - assert_eq!("/tmp/somefile-0.eml", first_name); - assert_eq!("/tmp/someotherfile-1.eml", second_name); - assert_eq!("/tmp/someotherfile-2.eml", third_name); - assert_eq!("/tmp/unnamed-3.eml", fourth_name); + + assert_eq!( + Some("/tmp/somefile-0.eml"), + producer.new_plain_eml("/usr/var/log/somefile.eml").to_str() + ); + assert_eq!( + Some("/tmp/someotherfile-1.eml"), + producer + .new_plain_eml("/usr/var/log/someotherfile.eml") + .to_str() + ); + assert_eq!( + Some("/tmp/someotherfile-2.eml"), + producer + .new_plain_eml("/usr/var/log/someotherfile") + .to_str() + ); + assert_eq!( + Some("/tmp/unnamed-3.eml"), + producer + .new_plain_eml("/usr/var/log/someotherdir/..") + .to_str() + ); } #[test] fn producer_for_multiple_mbox_only() { let import_directory = PathBuf::from("/tmp/"); let mut producer = FileNameProducer::new(&import_directory); - producer.new_mbox(&PathBuf::from("/usr/var/log/somefile.mbox")); - let first_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - let second_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - producer.new_mbox(&PathBuf::from("/usr/var/log/someotherdir/..")); - let third_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - let fourth_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - producer.new_mbox(&PathBuf::from("/usr/var/log/somedir/..")); - let fifth_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - let sixth_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - assert_eq!("/tmp/somefile-0.mbox-item-0.eml", first_name); - assert_eq!("/tmp/somefile-0.mbox-item-1.eml", second_name); - assert_eq!("/tmp/unnamed-1.mbox-item-0.eml", third_name); - assert_eq!("/tmp/unnamed-1.mbox-item-1.eml", fourth_name); - assert_eq!("/tmp/unnamed-2.mbox-item-0.eml", fifth_name); - assert_eq!("/tmp/unnamed-2.mbox-item-1.eml", sixth_name); + + // first mbox + producer.new_mbox("/usr/var/log/somefile.mbox"); + assert_eq!( + Some("/tmp/somefile-0.mbox-item-0.eml"), + producer.new_file_of_current_mbox().to_str() + ); + assert_eq!( + Some("/tmp/somefile-0.mbox-item-1.eml"), + producer.new_file_of_current_mbox().to_str() + ); + + // second mbox + producer.new_mbox("/usr/var/log/someotherdir/.."); + assert_eq!( + Some("/tmp/unnamed-1.mbox-item-0.eml"), + producer.new_file_of_current_mbox().to_str() + ); + assert_eq!( + Some("/tmp/unnamed-1.mbox-item-1.eml"), + producer.new_file_of_current_mbox().to_str() + ); + + // third mbox + producer.new_mbox("/usr/var/log/somedir/.."); + assert_eq!( + Some("/tmp/unnamed-2.mbox-item-0.eml"), + producer.new_file_of_current_mbox().to_str() + ); + assert_eq!( + Some("/tmp/unnamed-2.mbox-item-1.eml"), + producer.new_file_of_current_mbox().to_str() + ); } #[test] fn producer_for_multiple_mbox_and_multiple_eml() { let import_directory = PathBuf::from("/tmp/"); let mut producer = FileNameProducer::new(&import_directory); - producer.new_mbox(&PathBuf::from("/usr/var/log/somefile.mbox")); - let first_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - let second_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - let third_name = producer - .new_plain_eml(&PathBuf::from("/usr/var/log/someotherfile")) - .to_str() - .unwrap() - .to_string(); - let fourth_name = producer - .new_plain_eml(&PathBuf::from("/usr/var/log/someotherdir/..")) - .to_str() - .unwrap() - .to_string(); - - assert_eq!("/tmp/somefile-0.mbox-item-0.eml", first_name); - assert_eq!("/tmp/somefile-0.mbox-item-1.eml", second_name); - assert_eq!("/tmp/someotherfile-0.eml", third_name); - assert_eq!("/tmp/unnamed-1.eml", fourth_name); + + // a mbox + producer.new_mbox("/usr/var/log/somefile.mbox"); + assert_eq!( + Some("/tmp/somefile-0.mbox-item-0.eml"), + producer.new_file_of_current_mbox().to_str() + ); + assert_eq!( + Some("/tmp/somefile-0.mbox-item-1.eml"), + producer.new_file_of_current_mbox().to_str() + ); + + // a plain eml + assert_eq!( + Some("/tmp/someotherfile-0.eml"), + producer + .new_plain_eml("/usr/var/log/someotherfile") + .to_str() + ); + + // unknown filestem + assert_eq!( + Some("/tmp/unnamed-1.eml"), + producer + .new_plain_eml("/usr/var/log/someotherdir/..") + .to_str() + ); } /// when we show file select window, it is possible to select files from multiple folders which can have @@ -177,31 +208,52 @@ mod tests { fn can_import_multiple_files_with_same_name() { let import_directory = PathBuf::from("/tmp/"); let mut producer = FileNameProducer::new(&import_directory); - producer.new_mbox(&PathBuf::from("/usr/var/log/somefile.mbox")); - let first_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - producer.new_mbox(&PathBuf::from("/usr/var/log/somefile.mbox")); - let second_name = producer - .new_file_of_current_mbox() - .to_str() - .unwrap() - .to_string(); - let third_name = producer - .new_plain_eml(&PathBuf::from("/usr/var/log/someotherfile")) - .to_str() - .unwrap() - .to_string(); - let fourth_name = producer - .new_plain_eml(&PathBuf::from("/usr/var/log/someotherfile")) - .to_str() - .unwrap() - .to_string(); - assert_eq!("/tmp/somefile-0.mbox-item-0.eml", first_name); - assert_eq!("/tmp/somefile-1.mbox-item-0.eml", second_name); - assert_eq!("/tmp/someotherfile-0.eml", third_name); - assert_eq!("/tmp/someotherfile-1.eml", fourth_name); + + // first mbox + producer.new_mbox("/usr/var/log/somefile.mbox"); + assert_eq!( + Some("/tmp/somefile-0.mbox-item-0.eml"), + producer.new_file_of_current_mbox().to_str() + ); + + // second mbox + producer.new_mbox("/usr/var/log/somefile.mbox"); + assert_eq!( + Some("/tmp/somefile-1.mbox-item-0.eml"), + producer.new_file_of_current_mbox().to_str() + ); + assert_eq!( + Some("/tmp/someotherfile-0.eml"), + producer + .new_plain_eml("/usr/var/log/someotherfile") + .to_str() + ); + + // plain eml + assert_eq!( + Some("/tmp/someotherfile-1.eml"), + producer + .new_plain_eml("/usr/var/log/someotherfile") + .to_str() + ); + } + + #[test] + fn apple_mbox_uses_directory_name() { + let import_directory = PathBuf::from("/tmp/"); + let mut producer = FileNameProducer::new(&import_directory); + + producer.new_apple_mbox("/usr/var/log/inbox.mbox/mbox"); + assert_eq!( + Some("/tmp/inbox-0.mbox-item-0.eml"), + producer.new_file_of_current_mbox().to_str() + ); + + // otherwise we can't get parent directory we can still use the actual mbox file name + producer.new_apple_mbox("mbox-in-root-with-no-parent"); + assert_eq!( + Some("/tmp/unnamed-1.mbox-item-0.eml"), + producer.new_file_of_current_mbox().to_str() + ); } } From a4a32a1884df515684619ac6a1d5a11ef0f9702d Mon Sep 17 00:00:00 2001 From: sug Date: Fri, 7 Feb 2025 15:58:49 +0100 Subject: [PATCH 19/23] typo: return AppleMbox not MBOX --- packages/node-mimimi/src/importer/file_reader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index 63976061d59e..6dba3518a5db 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -50,7 +50,7 @@ impl UserSelectedFileType { && source_path.extension() == Some("mbox".as_ref()) && apple_mbox_file.is_file(); - is_apple_mbox.then_some(Self::Mbox(apple_mbox_file)) + is_apple_mbox.then_some(Self::AppleMbox(apple_mbox_file)) } } } From c8bc52aad36cb1a185b736dc912569de9bb59e15 Mon Sep 17 00:00:00 2001 From: sug Date: Fri, 7 Feb 2025 16:05:23 +0100 Subject: [PATCH 20/23] clippy fix --- packages/node-mimimi/src/importer/file_reader.rs | 2 -- packages/node-mimimi/src/lib.rs | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index 6dba3518a5db..7d70dc863588 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -4,10 +4,8 @@ use crate::importer::messages::{FileIterationError, PreparationError}; use crate::importer::{FAILED_MAILS_SUB_DIR, STATE_ID_FILE_NAME}; use mail_parser::mailbox::mbox::MessageIterator; use mail_parser::MessageParser; -use std::fmt::Debug; use std::fs; use std::io::BufReader; -use std::ops::BitAnd; use std::path::{Path, PathBuf}; pub struct FileImport { diff --git a/packages/node-mimimi/src/lib.rs b/packages/node-mimimi/src/lib.rs index c1acaa266b38..928afca7c409 100644 --- a/packages/node-mimimi/src/lib.rs +++ b/packages/node-mimimi/src/lib.rs @@ -1,5 +1,8 @@ #![deny(clippy::all)] +//! Node mimimi +//! + pub mod importer; #[cfg(feature = "javascript")] mod importer_api; From 4c18a719f261ca55e8068df1d9999bbd3ab18598 Mon Sep 17 00:00:00 2001 From: nig Date: Fri, 7 Feb 2025 13:20:29 +0100 Subject: [PATCH 21/23] [ci] run workflows on jenkins instead of on github we can cut the time for the checks by 75%-80% by doing this. * exclude generated files from swiftlint/formatting * add a pre-push hook to generate a link for the ci check & merge * set the build result to unstable if run with DRY_RUN=true * remove Rustflags, they produce unneeded rebuilds * remove x86_64 target to speed up build * improve pre-push hook * run gradlew test for arm64 only in ci * also check the tuta-sdk folder for FIXMEs * don't run ios tests on sdk changes * make ios builds mutually exclusive on the same machine xcode likes to use a global directory for DerivedData and really doesn't like if the files there change while a build is running. to make sure our test workflow and the ios app builds don't clash, we define lockable resources per mac machine. * add rust style and lint to merge.groovy * make it possible to prune the ci workflow Co-authored-by: sug Co-authored-by: Kinan <104761667+kibibytium@users.noreply.github.com> Co-authored-by: map Co-authored-by: nig --- .github/shared/setup-emscripten/action.yml | 34 -- .github/shared/setup-rust/action.yml | 26 - .github/shared/setup/action.yml | 24 - .github/shared/swift-common/action.yml | 30 - .github/workflows/kotlin-test.yml | 61 -- .github/workflows/release.yml | 42 -- .github/workflows/rust-test.yml | 44 -- .github/workflows/swift-calendar-test.yml | 52 -- .github/workflows/swift-mail-test.yml | 52 -- .github/workflows/test.yml | 134 ----- .gitignore | 1 + Cargo.lock | 5 - Cargo.toml | 5 +- README.md | 2 - app-ios/fastlane/Fastfile | 54 +- app-ios/lint.sh | 2 +- ci/Ios-Calendar.Jenkinsfile | 40 +- ci/Ios.Jenkinsfile | 58 +- ci/merge.groovy | 553 ++++++++++++++++++ eslint.config.mjs | 28 +- githooks/pre-commit | 11 +- githooks/pre-push | 18 + package.json | 4 +- packages/node-mimimi/package.json | 1 - packages/node-mimimi/src/importer.rs | 2 +- test/tests/desktop/config/ConfigFileTest.ts | 2 +- tuta-sdk/android/sdk/build.gradle.kts | 118 ++-- tuta-sdk/rust/README.md | 4 - tuta-sdk/rust/make_android.sh | 30 - .../tests/can_manipulate_remote_instances.rs | 2 +- 30 files changed, 745 insertions(+), 694 deletions(-) delete mode 100644 .github/shared/setup-emscripten/action.yml delete mode 100644 .github/shared/setup-rust/action.yml delete mode 100644 .github/shared/setup/action.yml delete mode 100644 .github/shared/swift-common/action.yml delete mode 100644 .github/workflows/kotlin-test.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/rust-test.yml delete mode 100644 .github/workflows/swift-calendar-test.yml delete mode 100644 .github/workflows/swift-mail-test.yml delete mode 100644 .github/workflows/test.yml create mode 100644 ci/merge.groovy create mode 100755 githooks/pre-push delete mode 100755 tuta-sdk/rust/make_android.sh diff --git a/.github/shared/setup-emscripten/action.yml b/.github/shared/setup-emscripten/action.yml deleted file mode 100644 index 7b39e725bb79..000000000000 --- a/.github/shared/setup-emscripten/action.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: "Setup emscripten" -description: "Setup emcc" -inputs: - emscripten-version: - required: true - description: "emscripten version" -runs: - using: "composite" - - steps: - - name: "get emscripten cached location" - shell: bash - run: | - echo "emscripten_path=$(pwd)/emsdk" >> $GITHUB_ENV - - name: cache emscripten - id: cache-emscripten - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # 4.2.0 - with: - path: ${{ env.emscripten_path }} - key: emscripten-${{ inputs.emscripten-version }} - - name: install emscripten - shell: bash - if: steps.cache-emscripten.outputs.cache-hit != 'true' - run: | - git clone --branch ${{ inputs.emscripten-version }} https://github.com/emscripten-core/emsdk.git - cd emsdk - ./emsdk install latest - ./emsdk activate latest - source ./emsdk_env.sh - - name: add emscripten to path - shell: bash - run: | - echo ${{ env.emscripten_path }}/upstream/bin >> $GITHUB_PATH - echo ${{ env.emscripten_path }}/upstream/emscripten >> $GITHUB_PATH \ No newline at end of file diff --git a/.github/shared/setup-rust/action.yml b/.github/shared/setup-rust/action.yml deleted file mode 100644 index 0fa63238ac49..000000000000 --- a/.github/shared/setup-rust/action.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: "Setup Rust" -description: "Setup rust environment and dependencies" -runs: - using: "composite" - - steps: - - name: setup rust - shell: bash - run: | - # Specify an exact Rust version. - # - # Newer versions will enable newer features, but at the same time, you may get newer warnings which you have - # to fix. This should be a manual step. - rustup install 1.84.0 - rustup default 1.84.0 - rustup component add rustfmt clippy - - name: Versions - shell: bash - run: | - rustup --version - cargo --version - rustc --version - - name: Add rust targets - if: env.rust-targets != '' - shell: bash - run: rustup target add ${{ env.rust-targets }} diff --git a/.github/shared/setup/action.yml b/.github/shared/setup/action.yml deleted file mode 100644 index 4a53f6afb4df..000000000000 --- a/.github/shared/setup/action.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: "Setup" -description: "Setup js environment and dependencies" -runs: - using: "composite" - - steps: - - name: Use Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 - with: - node-version: 20.18.0 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - name: Get better-sqlite3 cached location - shell: bash - run: | - echo "better_sqlite3_path=$(node buildSrc/getNativeCacheLocation.js better-sqlite3)" >> $GITHUB_ENV - - name: try to use cached better-sqlite3 - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 #v4.2.0 - with: - path: ${{ env.better_sqlite3_path }} - key: ${{ env.better_sqlite3_path }} - - name: install packages - shell: bash - run: npm ci \ No newline at end of file diff --git a/.github/shared/swift-common/action.yml b/.github/shared/swift-common/action.yml deleted file mode 100644 index c5de5ea169e6..000000000000 --- a/.github/shared/swift-common/action.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: "Swift Common" -description: "Common Swift Checks" - -runs: - using: "composite" - - steps: - - name: Setup Swift - uses: swift-actions/setup-swift@cdbe0f7f4c77929b6580e71983e8606e55ffe7e4 # v1.26.2 - with: - swift-version: ${{ env.swift-version }} - - name: Install Homebrew - uses: Homebrew/actions/setup-homebrew@d54a6744d5fcdff54b45a9659f3e17f769389952 - - name: Install Homebrew dependencies - shell: bash - run: | - brew install swiftlint swift-format xcodegen - - uses: ./.github/shared/setup-rust - - name: Lint - working-directory: ./app-ios - shell: bash - run: ./lint.sh lint:check - - name: Format - working-directory: ./app-ios - shell: bash - run: ./lint.sh style:check - - name: Xcodegen sdk - working-directory: tuta-sdk/ios - shell: bash - run: xcodegen \ No newline at end of file diff --git a/.github/workflows/kotlin-test.yml b/.github/workflows/kotlin-test.yml deleted file mode 100644 index 1c02f0e185b9..000000000000 --- a/.github/workflows/kotlin-test.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Kotlin CI - -on: - pull_request: - types: [ opened, synchronize, edited ] - paths: - - 'app-android/**' - - '.github/workflows/kotlin-test.yml' - push: - branches: - - dev-* - - '*/dev' - paths: - - 'app-android/**' - workflow_dispatch: - -jobs: - test-kotlin: - runs-on: ubuntu-latest - env: - java-version: 21 - java-distribution: 'temurin' - TZ: "Europe/Berlin" # We have some tests for same day alarms that depends on this TimeZone - rust-targets: "aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android" - permissions: - actions: none - checks: none - contents: read - deployments: none - id-token: none - issues: none - discussions: none - packages: none - pages: none - pull-requests: none - repository-projects: none - security-events: none - statuses: none - - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - submodules: 'true' - - name: Create dummy build folder - run: mkdir build && mkdir build-calendar-app # We need this because gradlew lint searches for the app assets - - name: Set up JDK ${{ env.java-version }} - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 #4.2.1 - with: - java-version: ${{ env.java-version }} - distribution: ${{ env.java-distribution }} - - name: Setup Android SDK - uses: android-actions/setup-android@07976c6290703d34c16d382cb36445f98bb43b1f #3.2.0 - - name: Setup Android NDK - run: sdkmanager "ndk;26.1.10909125" - - uses: ./.github/shared/setup-rust - - name: Lint - working-directory: ./app-android - run: ./gradlew lint --quiet - - name: Test - working-directory: ./app-android - run: ./gradlew test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 9c6073ecc287..000000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Create a release draft - -on: - workflow_dispatch: - inputs: - tag: - required: true - -jobs: - release: - runs-on: ubuntu-latest - - permissions: - actions: none - checks: none - contents: write - deployments: none - id-token: none - issues: read - discussions: none - packages: none - pages: none - pull-requests: none - repository-projects: none - security-events: none - statuses: none - - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - - name: Use Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 - with: - node-version: 20.18.0 - - name: npm i - run: npm i @octokit/rest - - name: release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: "node buildSrc/releaseNotes --tag tutanota-release-${{ github.event.inputs.tag }} - --releaseName ${{ github.event.inputs.tag }} - --milestone ${{ github.event.inputs.tag }} - --platform all" diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml deleted file mode 100644 index 6749d752aace..000000000000 --- a/.github/workflows/rust-test.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Rust CI - -on: - pull_request: - types: [ opened, synchronize, edited ] - paths: - - 'tuta-sdk/**' - - 'packages/node-mimimi/**' - - '.github/workflows/rust-test.yml' - push: - branches: - - dev-* - - '*/dev' -env: - RUSTFLAGS: "--cfg ci" # turns off some integration tests that need a server to pass - -jobs: - test: - runs-on: ubuntu-latest - - permissions: - actions: none - checks: none - contents: read - deployments: none - id-token: none - issues: none - discussions: none - packages: none - pages: none - pull-requests: none - repository-projects: none - security-events: none - statuses: none - - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - - uses: ./.github/shared/setup-rust - - name: rust format - run: cargo fmt --check - - name: rust warning check with clippy - run: cargo clippy --all --no-deps -- -Dwarnings # -Dwarnings changes warnings to errors so that the check fails - - name: rust tests - run: cargo test --all diff --git a/.github/workflows/swift-calendar-test.yml b/.github/workflows/swift-calendar-test.yml deleted file mode 100644 index 1d2a1873480f..000000000000 --- a/.github/workflows/swift-calendar-test.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Swift CI - Calendar - -on: - pull_request: - types: [ opened, synchronize, edited ] - paths: - - 'app-ios/**' - - '.github/workflows/swift-calendar-test.yml' - - '!app-ios/**/Info.plist' - push: - branches: - - dev-* - - '*/dev' - paths: - - 'app-ios/**' - -env: - swift-version: "5.9.2" - swift-format-version: "509.0.0" - rust-targets: "aarch64-apple-ios-sim" - -jobs: - test-swift: - runs-on: macos-14 - - permissions: - actions: none - checks: none - contents: read - deployments: none - id-token: none - issues: none - discussions: none - packages: none - pages: none - pull-requests: none - repository-projects: none - security-events: none - statuses: none - - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - submodules: 'true' - - uses: ./.github/shared/swift-common - - name: Test Calendar - working-directory: app-ios - run: | - mkdir -p ../build-calendar-app - xcodegen --spec calendar-project.yml - fastlane test_calendar_github - diff --git a/.github/workflows/swift-mail-test.yml b/.github/workflows/swift-mail-test.yml deleted file mode 100644 index 4e2690c899ed..000000000000 --- a/.github/workflows/swift-mail-test.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Swift CI - Mail - -on: - pull_request: - types: [ opened, synchronize, edited ] - paths: - - 'app-ios/**' - - '.github/workflows/swift-mail-test.yml' - - '!app-ios/**/Info.plist' - push: - branches: - - dev-* - - '*/dev' - paths: - - 'app-ios/**' - -env: - swift-version: "5.9.2" - swift-format-version: "509.0.0" - rust-targets: "aarch64-apple-ios-sim" - -jobs: - test-swift: - runs-on: macos-14 - - permissions: - actions: none - checks: none - contents: read - deployments: none - id-token: none - issues: none - discussions: none - packages: none - pages: none - pull-requests: none - repository-projects: none - security-events: none - statuses: none - - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - submodules: 'true' - - uses: ./.github/shared/swift-common - - name: Test Mail - working-directory: app-ios - run: | - mkdir -p ../build - xcodegen --spec mail-project.yml - fastlane test_github - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 0a20df98676d..000000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,134 +0,0 @@ -name: Node CI - -on: - pull_request: - types: [ opened, synchronize, edited ] - push: - branches: - - dev-* - - '*/dev' -env: - emscripten_version: 3.1.59 - RUSTFLAGS: "--cfg ci" # turns off some integration tests that need a server to pass - -permissions: - actions: none - checks: none - contents: read - deployments: none - id-token: none - issues: none - discussions: none - packages: none - pages: none - pull-requests: none - repository-projects: none - security-events: none - statuses: none - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - - uses: "./.github/shared/setup" - - name: lint, formatting - run: | - npm run check - - name: check for FIXMEs - run: | - if grep "FIXME\|[fF]ixme" -r src buildSrc test/tests packages/*/lib app-android/app/src app-ios/tutanota/Sources; then - echo 'FIXMEs in src'; - exit 1; - else - echo 'No FIXMEs in src'; - fi - - - test-node: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - submodules: 'true' - - uses: "./.github/shared/setup" - - uses: "./.github/shared/setup-rust" - - uses: "./.github/shared/setup-emscripten" - with: - emscripten-version: ${{ env.emscripten_version }} - - name: run tests - run: | - npm run build-packages - npm run test-ci - - test-browser: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - submodules: 'true' - - uses: "./.github/shared/setup" - - uses: "./.github/shared/setup-rust" - - uses: "./.github/shared/setup-emscripten" - with: - emscripten-version: ${{ env.emscripten_version }} - - name: install chrome - id: setup-chrome - uses: browser-actions/setup-chrome@facf10a55b9caf92e0cc749b4f82bf8220989148 #v1.7.2 - with: - chrome-version: stable - - name: run test in browser - timeout-minutes: 10 - # --no-sandbox is needed because it fails to create sandbox in container otherwise. - # It is fine as we run our own tests in a throwaway container anyway - run: | - echo Chrome version: ${{ steps.setup-chrome.outputs.chrome-version }} - npm run test:app -- --no-run --browser --browser-cmd '${{ steps.setup-chrome.outputs.chrome-path }} --no-sandbox --enable-logging=stderr --headless=new --disable-gpu' - - build-webapp: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - submodules: 'true' - - uses: "./.github/shared/setup" - - uses: "./.github/shared/setup-rust" - - uses: "./.github/shared/setup-emscripten" - with: - emscripten-version: ${{ env.emscripten_version }} - - name: build webapp - run: | - npm run build-packages - node webapp --disable-minify - - build-calendar: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - submodules: 'true' - - uses: "./.github/shared/setup" - - uses: "./.github/shared/setup-rust" - - uses: "./.github/shared/setup-emscripten" - with: - emscripten-version: ${{ env.emscripten_version }} - - name: build webapp - run: | - npm run build-packages - node webapp --disable-minify --app calendar - - build-desktop: # will check that offline migrations are there - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - submodules: 'true' - - uses: "./.github/shared/setup" - - uses: "./.github/shared/setup-rust" - - uses: "./.github/shared/setup-emscripten" - with: - emscripten-version: ${{ env.emscripten_version }} - - name: build desktop - run: | - npm run build-packages - node desktop --disable-minify \ No newline at end of file diff --git a/.gitignore b/.gitignore index d1cf152bd524..4ea539087301 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +cache target build-calendar-app dist diff --git a/Cargo.lock b/Cargo.lock index 36d6c6e502d1..4e677524dbd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2799,8 +2799,3 @@ dependencies = [ "quote", "syn", ] - -[[patch.unused]] -name = "pqcrypto-internals" -version = "0.2.7" -source = "git+https://github.com/rustpq/pqcrypto?rev=b39937410b149156de81fc8e3150753aff925aa4#b39937410b149156de81fc8e3150753aff925aa4" diff --git a/Cargo.toml b/Cargo.toml index ad19a76d8840..b91976775026 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,4 @@ explicit_iter_loop = "warn" [workspace.lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci)'] } - -[patch.'https://github.com/rust-lang/crates.io-index'] -pqcrypto-internals = { git = 'https://github.com/rustpq/pqcrypto', rev = 'b39937410b149156de81fc8e3150753aff925aa4' } \ No newline at end of file +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(not_ci)'] } \ No newline at end of file diff --git a/README.md b/README.md index 23b3b2043e86..ac9657255389 100644 --- a/README.md +++ b/README.md @@ -30,5 +30,3 @@ See [BUILDING.md](doc/BUILDING.md). ### Developing Tuta Mail See [HACKING.md](doc/HACKING.md). - - diff --git a/app-ios/fastlane/Fastfile b/app-ios/fastlane/Fastfile index f6b6eaf6a604..0e162c4b4ae2 100644 --- a/app-ios/fastlane/Fastfile +++ b/app-ios/fastlane/Fastfile @@ -140,44 +140,40 @@ platform :ios do ) end - desc "Run iOS test cases (Jenkins version)" - lane :test do + desc "Run iOS mail test cases" + lane :test_tuta_app do # Create tutanota-3/build if it's not there because we try to copy it during build sh "mkdir -p ../../build" # Create tutanota-3/build-calendar-app if it's not there because we try to copy it during build sh "mkdir -p ../../build-calendar-app" - sh "if xcrun simctl list | grep iphone-15-ios-17-4; then echo 'Using existing simulator'; else xcrun simctl create iphone-15-ios-17-4 com.apple.CoreSimulator.SimDeviceType.iPhone-15 com.apple.CoreSimulator.SimRuntime.iOS-17-4; fi" + sh "if xcrun simctl list | grep iphone-15-ios-18-2; then echo 'Using existing simulator'; else xcrun simctl create iphone-15-ios-18-2 com.apple.CoreSimulator.SimDeviceType.iPhone-15 com.apple.CoreSimulator.SimRuntime.iOS-18-2; fi" clear_derived_data - # Test both apps so we make sure that changes made in one doesn't break the other one + # Test only one app because both link to the same test suite run_tests( scheme: "tuta debug", - devices: ["iphone-15-ios-17-4"] - ) - - run_tests( - scheme: "calendar debug", - devices: ["iphone-15-ios-17-4"] - ) - - run_tests( - scheme: "TutanotaSharedFramework", - devices: ["iphone-15-ios-17-4"] ) end - desc "Run iOS test cases (Github actions version)" - lane :test_github do - run_tests( - scheme: "tuta debug", - ) - run_tests( - scheme: "TutanotaSharedFramework", - ) - end + desc "Run iOS shared framework test cases" + lane :test_tuta_shared_framework do + # Create tutanota-3/build if it's not there because we try to copy it during build + sh "mkdir -p ../../build" + + # Create tutanota-3/build-calendar-app if it's not there because we try to copy it during build + sh "mkdir -p ../../build-calendar-app" + + sh "if xcrun simctl list | grep iphone-15-ios-18-2; then echo 'Using existing simulator'; else xcrun simctl create iphone-15-ios-18-2 com.apple.CoreSimulator.SimDeviceType.iPhone-15 com.apple.CoreSimulator.SimRuntime.iOS-18-2; fi" + + clear_derived_data + + run_tests( + scheme: "TutanotaSharedFramework", + ) + end desc "Renew prod adhoc cert" lane :renew_adhoc_cert_prod do @@ -235,16 +231,6 @@ platform :ios do ) end - desc "Run iOS test cases for Calendar app (Github actions version)" - lane :test_calendar_github do - run_tests( - scheme: "calendar debug", - ) - run_tests( - scheme: "TutanotaSharedFramework", - ) - end - desc "Build a new staging Calendar release for ad-hoc" lane :calendar_adhoc_staging do match( diff --git a/app-ios/lint.sh b/app-ios/lint.sh index c7bc911b2c7a..a98d3eae8833 100755 --- a/app-ios/lint.sh +++ b/app-ios/lint.sh @@ -24,7 +24,7 @@ format() { fi if which swift-format > /dev/null; then - FILES=$(find "${1}" -name "*.swift" -type f -not -path "*/GeneratedIpc/*") + FILES=$(find "${1}" -name "*.swift" -type f -not -path "*/GeneratedIpc/*" -not -name "*.generated.swift") capture_errors swift-format $fix_command --configuration .swift-format.json --recursive --parallel $FILES else echo "warning: swift-format not installed, download from https://github.com/apple/swift-format" diff --git a/ci/Ios-Calendar.Jenkinsfile b/ci/Ios-Calendar.Jenkinsfile index 95619f6d6ab3..138809f18785 100644 --- a/ci/Ios-Calendar.Jenkinsfile +++ b/ci/Ios-Calendar.Jenkinsfile @@ -68,17 +68,19 @@ pipeline { expression { params.STAGING } } steps { - script { - def util = load "ci/jenkins-lib/util.groovy" + lock('ios-build-intel') { + script { + def util = load "ci/jenkins-lib/util.groovy" - buildWebapp("test") - generateXCodeProjects() + buildWebapp("test") + generateXCodeProjects() - util.runFastlane("de.tutao.calendar.test", "calendar_adhoc_staging") - if (params.RELEASE) { - util.runFastlane("de.tutao.calendar.test", "calendar_testflight_staging") + util.runFastlane("de.tutao.calendar.test", "calendar_adhoc_staging") + if (params.RELEASE) { + util.runFastlane("de.tutao.calendar.test", "calendar_testflight_staging") + } + stash includes: "app-ios/releases/calendar-${VERSION}-adhoc-test.ipa", name: 'ipa-testing' } - stash includes: "app-ios/releases/calendar-${VERSION}-adhoc-test.ipa", name: 'ipa-testing' } } } @@ -87,18 +89,20 @@ pipeline { expression { params.PROD } } steps { - script { - def util = load "ci/jenkins-lib/util.groovy" + lock('ios-build-intel') { + script { + def util = load "ci/jenkins-lib/util.groovy" - buildWebapp("prod") - generateXCodeProjects() - util.runFastlane("de.tutao.calendar", "calendar_adhoc_prod") + buildWebapp("prod") + generateXCodeProjects() + util.runFastlane("de.tutao.calendar", "calendar_adhoc_prod") - if (params.RELEASE) { - util.runFastlane("de.tutao.calendar", "build_calendar_prod") - stash includes: "app-ios/releases/calendar-${VERSION}.ipa", name: 'ipa-production' - } else { - stash includes: "app-ios/releases/calendar-${VERSION}-adhoc.ipa", name: 'ipa-production' + if (params.RELEASE) { + util.runFastlane("de.tutao.calendar", "build_calendar_prod") + stash includes: "app-ios/releases/calendar-${VERSION}.ipa", name: 'ipa-production' + } else { + stash includes: "app-ios/releases/calendar-${VERSION}-adhoc.ipa", name: 'ipa-production' + } } } } diff --git a/ci/Ios.Jenkinsfile b/ci/Ios.Jenkinsfile index 1a0f2e587454..58507652f8b1 100644 --- a/ci/Ios.Jenkinsfile +++ b/ci/Ios.Jenkinsfile @@ -43,24 +43,6 @@ pipeline { } } } - stage("Run tests") { - agent { - label 'mac' - } - environment { - LC_ALL = "en_US.UTF-8" - LANG = "en_US.UTF-8" - } - steps { - script { - generateXCodeProjects() - dir('app-ios') { - sh 'fastlane test' - } - } - } // steps - } // stage run tests - stage("Build") { environment { MATCH_GIT_URL = "git@gitlab:/tuta/apple-certificates.git" @@ -78,16 +60,18 @@ pipeline { label 'mac-intel' } steps { - script { - def util = load "ci/jenkins-lib/util.groovy" - buildWebapp("test") - generateXCodeProjects() - util.runFastlane("de.tutao.tutanota.test", "adhoc_staging") - if (params.UPLOAD) { - util.runFastlane("de.tutao.tutanota.test", "build_mail_staging") - stash includes: "app-ios/releases/tutanota-${VERSION}-test.ipa", name: 'ipa-staging' + lock("ios-build-intel") { + script { + def util = load "ci/jenkins-lib/util.groovy" + buildWebapp("test") + generateXCodeProjects() + util.runFastlane("de.tutao.tutanota.test", "adhoc_staging") + if (params.UPLOAD) { + util.runFastlane("de.tutao.tutanota.test", "build_mail_staging") + stash includes: "app-ios/releases/tutanota-${VERSION}-test.ipa", name: 'ipa-staging' + } + stash includes: "app-ios/releases/tutanota-${VERSION}-adhoc-test.ipa", name: 'ipa-adhoc-staging' } - stash includes: "app-ios/releases/tutanota-${VERSION}-adhoc-test.ipa", name: 'ipa-adhoc-staging' } } } // stage staging @@ -101,16 +85,18 @@ pipeline { label 'mac-intel' } steps { - script { - def util = load "ci/jenkins-lib/util.groovy" - buildWebapp("prod") - generateXCodeProjects() - util.runFastlane("de.tutao.tutanota", "adhoc_prod") - if (params.UPLOAD) { - util.runFastlane("de.tutao.tutanota", "build_mail_prod") - stash includes: "app-ios/releases/tutanota-${VERSION}.ipa", name: 'ipa-production' + lock("ios-build-intel") { + script { + def util = load "ci/jenkins-lib/util.groovy" + buildWebapp("prod") + generateXCodeProjects() + util.runFastlane("de.tutao.tutanota", "adhoc_prod") + if (params.UPLOAD) { + util.runFastlane("de.tutao.tutanota", "build_mail_prod") + stash includes: "app-ios/releases/tutanota-${VERSION}.ipa", name: 'ipa-production' + } + stash includes: "app-ios/releases/tutanota-${VERSION}-adhoc.ipa", name: 'ipa-adhoc-production' } - stash includes: "app-ios/releases/tutanota-${VERSION}-adhoc.ipa", name: 'ipa-adhoc-production' } } // steps } // stage production diff --git a/ci/merge.groovy b/ci/merge.groovy new file mode 100644 index 000000000000..7f8577653b61 --- /dev/null +++ b/ci/merge.groovy @@ -0,0 +1,553 @@ +import groovy.json.JsonSlurper + +HashSet changeset = new HashSet() +String linuxWorkspace = '/opt/jenkins/jobs/bootleg-ci-merge/workspace-0' +ArrayList linuxWorkspaceClones = [ + '/opt/jenkins/jobs/bootleg-ci-merge/workspace-1', + '/opt/jenkins/jobs/bootleg-ci-merge/workspace-2', + '/opt/jenkins/jobs/bootleg-ci-merge/workspace-3', + '/opt/jenkins/jobs/bootleg-ci-merge/workspace-4', + '/opt/jenkins/jobs/bootleg-ci-merge/workspace-5', + '/opt/jenkins/jobs/bootleg-ci-merge/workspace-6', + '/opt/jenkins/jobs/bootleg-ci-merge/workspace-7' +] +String macWorkspace = '/Users/jenkins/jenkins/workspace/bootleg-ci-merge/' + +pipeline { + environment { + VERSION = sh(returnStdout: true, script: "${env.NODE_PATH}/node -p -e \"require('./package.json').version\" | tr -d \"\n\"") + APK_SIGN_STORE = '/opt/android-keystore/android.jks' + PATH = "${env.NODE_PATH}:${env.PATH}:/home/jenkins/emsdk/upstream/bin/:/home/jenkins/emsdk/:/home/jenkins/emsdk/upstream/emscripten:/usr/lib/bin:/opt/homebrew/bin" + ANDROID_SDK_ROOT = "/opt/android-sdk-linux" + ANDROID_HOME = "/opt/android-sdk-linux" + } + + agent { + label 'linux' + } + + options { + timeout(time: 10, unit: 'MINUTES') + // this prevents jenkins from running the "Check out from version control" step in every stage. + // we're running several stages in parallel on the same folder, which means git will run in parallel, which + // it can't because it places a lock file in .git + skipDefaultCheckout true + } + + tools { + jdk 'jdk-21.0.2' + } + + parameters { + validatingString( + // this branch will be the initial branch checked out on the job. + // this is important because if we update the jenkinsfile in a commit + // we need to run the new version, not the one from master. + name: 'SOURCE_BRANCH', + defaultValue: 'dummy-do-not-use', + description: "Branch that gets merged into TARGET_BRANCH", + regex: /^(?!dummy-do-not-use$).*$/, + failedValidationMessage: "please provide one source branch name!", + ) + validatingString( + name: 'TARGET_BRANCH', + defaultValue: 'dummy-do-not-use', + description: "Branch that gets updated", + regex: /^(?!dummy-do-not-use$).*$/, + failedValidationMessage: "please provide one target branch name!", + ) + booleanParam( + name: 'CLEAN_WORKSPACE', + defaultValue: false, + description: "run 'git clean -dfx' as the first step of the pipeline" + ) + booleanParam( + name: 'DRY_RUN', + defaultValue: false, + description: "run the tests, but don't push to TARGET_BRANCH" + ) + booleanParam( + name: 'FORCE_RUN_ALL', + defaultValue: false, + description: "run ALL the tests, even if they would normally be pruned because there are no relevant changes." + ) + } + + stages { + stage("repo prep") { + /** + * each physical node / container has to have a workspace prepared to do any of the checks we want to run. + * + * Most checks could run in parallel because they're read-only workloads, but jenkins really likes allocating + * new workspaces when it detects two stages running in parallel on the same workspace, even when explicitly + * told where to run the stages. + * + * we get around this by first preparing the workspace and then making symlinks for jenkins to use as + * "separate" workspaces that are already initialized. + */ + parallel { + stage("checkout linux") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspace + } + } + steps { + assertBranchWasSet("TARGET_BRANCH", params.TARGET_BRANCH) + assertBranchWasSet("SOURCE_BRANCH", params.SOURCE_BRANCH) + script { + currentBuild.displayName = "${params.DRY_RUN ? "DRY_RUN " : ""}${params.SOURCE_BRANCH} -> ${params.TARGET_BRANCH}" + } + initWorkspace(changeset, params.SOURCE_BRANCH, params.TARGET_BRANCH, params.CLEAN_WORKSPACE) + + // building better-sqlite3 is not parallelizable on the same directory and doesn't lock; so we + // pre-build it while we're not parallel yet. the browser and node tests will pick up the binary + // instead of each building it. we can parallelize it with build-packages though. + sh ''' + node buildSrc/getNodeGypLibrary.js better-sqlite3 --copy-target better_sqlite3 --environment node --root-dir . & + PID1=$! + npm run build-packages & + PID2=$! + wait $PID1 + EXIT_CODE1=$? + wait $PID2 + EXIT_CODE2=$? + exit $(node -p "$EXIT_CODE1 + $EXIT_CODE2") + ''' + duplicateWorkspace(linuxWorkspace, linuxWorkspaceClones) + } + } + stage("checkout mac m1") { + agent { + node { + label "mac-m1" + customWorkspace macWorkspace + } + } + steps { + initWorkspace(changeset, params.SOURCE_BRANCH, params.TARGET_BRANCH, params.CLEAN_WORKSPACE) + prepareSwift() + } + } + stage("checkout mac intel") { + agent { + node { + label "mac-intel" + customWorkspace macWorkspace + } + } + steps { + initWorkspace(changeset, params.SOURCE_BRANCH, params.TARGET_BRANCH, params.CLEAN_WORKSPACE) + prepareSwift() + } + } + } + } + stage("Lint and Style") { + parallel { + stage("find FIXMEs") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[0] + } + } + steps { + findFixmes() + } + } + stage("lint:check") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[1] + } + } + steps { + sh 'npm run lint:check' + } + } + stage("style:check") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[2] + } + } + steps { + sh 'npm run style:check' + } + } + stage("check rust formatting") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[3] + } + } + // this is so quick, we can run it every time. + steps { + sh "cargo fmt --check" + } + } + stage("lint swift") { + agent { + node { + label "mac-m1" + customWorkspace macWorkspace + } + } + when { + expression { extensionChanged(changeset, ".swift") } + } + steps { + lock("ios-build-m1") { + lintSwift() + } + } + } + } + } + stage("Testing and Building") { + parallel { + stage("packages test") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[0] + } + } + when { + expression { hasRelevantChangesIn(changeset, "packages") } + } + steps { + sh 'npm run --if-present test -ws' + } + } + stage("node tests") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[1] + } + } + steps { + sh 'cd test && node test' + } + } + stage("browser tests") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[2] + } + } + steps { + sh 'npm run test:app -- --no-run --browser --browser-cmd \'$(which chromium) --no-sandbox --enable-logging=stderr --headless=new --disable-gpu\'' + } + } + stage("build web app") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[3] + } + } + steps { + sh 'node webapp --disable-minify' + } + } + stage("build web app calendar") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[4] + } + } + steps { + sh 'node webapp --disable-minify --app calendar' + } + } + stage("sdk tests") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[5] + } + } + when { + expression { hasRelevantChangesIn(changeset, "tuta-sdk") } + } + steps { + sh "cargo test --package tuta-sdk" + } + } + stage("clippy lints") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspaceClones[6] + } + } + when { + expression { extensionChanged(changeset, ".rs", ".toml") } + } + steps { + // -Dwarnings changes warnings to errors so that the check fails + sh "cargo clippy --all --no-deps -- -Dwarnings" + } + } + stage("android tests") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspace + } + } + when { + expression { hasRelevantChangesIn(changeset, "app-android") } + } + steps { + testAndroid() + } + } + stage("ios app tests") { + agent { + node { + label "mac-m1" + customWorkspace macWorkspace + } + } + when { + expression { hasRelevantChangesIn(changeset, "app-ios") } + } + steps { + lock("ios-build-m1") { + testFastlane("test_tuta_app") + } + } + } + stage("ios framework tests") { + agent { + node { + label "mac-intel" + customWorkspace macWorkspace + } + } + when { + expression { hasRelevantChangesIn(changeset, "app-ios") } + } + steps { + lock("ios-build-infra") { + testFastlane("test_tuta_shared_framework") + } + } + } + } + } + stage("finalize") { + agent { + node { + label 'linux' + customWorkspace linuxWorkspace + } + } + steps { + finalize(params.DRY_RUN) + } + } + } +} + +void assertBranchWasSet(String name, String param) { + if (param.contains("dummy-do-not-use")) { + error("Parameter ${name} must be set to a valid branch name, not '${param}'.") + } +} + +void initWorkspace(HashSet changeset, String srcBranch, String targetBranch, boolean shouldClean) { + if (shouldClean) { + sh """ + git submodule deinit --all --force + git clean -dfx + cargo clean + """ + } + sh "git status && git remote -v" + fetch(srcBranch, targetBranch) + merge(srcBranch, targetBranch) + submodules() + sh "pwd && git status && git remote -v && git submodule status" + getChangeset(changeset, targetBranch) + if (shouldRunNpmCi()) { + sh "npm ci" + } +} + +void duplicateWorkspace(String source, ArrayList targets) { + // don't trust the jenkins primitives to do this correctly... + sh """ + pwd + cd ${source}/.. + TARGETS="${targets.join(" ")}" + ls -halt + rm -f \$TARGETS + ls -halt + for target in \${TARGETS}; do + ln -s ${source} \$target + done + ls -halt + """ +} + +void fetch(String srcBranch, String targetBranch) { + sh """ + git switch --detach HEAD~1 + git branch -D ${targetBranch} ${srcBranch} || true + git fetch + git switch ${srcBranch} + git switch ${targetBranch} + """ +} + +void submodules() { + sh """ + git submodule init + git submodule sync --recursive + git submodule update + """ +} + +void prepareSwift() { + sh ''' + mkdir -p ./build-calendar-app + mkdir -p ./build + + cd app-ios + xcodegen --spec calendar-project.yml + xcodegen --spec mail-project.yml + + cd ../tuta-sdk/ios + xcodegen + ''' +} + +void lintSwift() { + sh ''' + cd app-ios + ./lint.sh lint:check + ./lint.sh style:check + ''' +} + +void merge(String srcBranch, String targetBranch) { + def sucessfulMerge = sh(returnStatus: true, script: "git merge --ff-only ${srcBranch}").toInteger() + if (sucessfulMerge != 0) { + error("Failed to merge branch ${srcBranch} into ${targetBranch}. Please rebase and try again.") + } +} + +// must be called while checked out on targetBranch, after ff-merging srcBranch. +void getChangeset(HashSet changeset, String targetBranch) { + def out = sh(returnStdout: true, script: "git diff --name-only ${targetBranch} origin/${targetBranch}") + def lines = out.split('\n') + for (String line in lines) { + changeset.add(line.trim()) + } + println "changeset:\n\n${changeset.join("\n")}" +} + +// return whether any file in the given paths changed (recursively) +boolean hasRelevantChangesIn(HashSet changeset, String... paths) { + boolean relevant = false + for (String path in paths) { + relevant = relevant || changeset.any { f -> f.startsWith(path) } + } + + return relevant || extensionChanged(changeset, "groovy") || params.FORCE_RUN_ALL +} + +// return whether any file with the given extensions changed +boolean extensionChanged(HashSet changeset, String... exts) { + boolean changed = false + for (String ext in exts) { + changed = changed || changeset.any { f -> f.endsWith(ext) } + } + return changed || params.FORCE_RUN_ALL +} + +boolean shouldRunNpmCi() { + def current = readFile(file: 'package.json') + def old + try { + old = readFile(file: 'cache/package.json') + } catch (e) { + print e + return true + } finally { + writeFile(file: 'cache/package.json', text: current) + } + def json = new JsonSlurper() + def oldJson = json.parseText(old) + def currentJson = json.parseText(current) + def oldVersion = oldJson.version + def currentVersion = currentJson.version + def oldWithUpdatedVersion = old.replaceAll(oldVersion, currentVersion) + def expectedJSON = json.parseText(oldWithUpdatedVersion) + if (expectedJSON.equals(currentJson)) { + print "skipping npm ci as package.json is unchanged" + return false + } else { + return true + } +} + +void findFixmes() { + sh ''' + if grep "FIXME\\|[fF]ixme" -r src buildSrc test/tests packages/*/lib app-android/app/src app-ios/tutanota/Sources tuta-sdk; then + echo 'FIXMEs in src'; + exit 1; + else + echo 'No FIXMEs in src'; + fi + ''' +} + +void testAndroid() { + sh ''' + # We have some tests for same day alarms that depends on this TimeZone + export TZ=Europe/Berlin + mkdir -p build + mkdir -p build-calendar-app + cd app-android + ./gradlew lint -PtargetABI=arm64 --quiet + ./gradlew test -PtargetABI=arm64 + ''' +} + +void testFastlane(String task) { + sh """ + export LC_ALL="en_US.UTF-8" + export LANG="en_US.UTF-8" + cd app-ios + fastlane ${task} + """ +} + +/** + * push the resulting repo state to origin if it's not a dry run + */ +void finalize(boolean dryRun) { + if (dryRun) { + catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') { + sh "exit 1" + } + echo """everything is fine, but I'm not pushing (DRY_RUN)! + use the following link to re-run and merge: + https://next.tutao.de/jenkins/job/${env.JOB_NAME}/parambuild?TARGET_BRANCH=${params.TARGET_BRANCH}&SOURCE_BRANCH=${params.SOURCE_BRANCH}&CLEAN_WORKSPACE=false&DRY_RUN=false + """ + } else { + sh "git push origin HEAD:${params.TARGET_BRANCH}" + } +} \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index cc0bf25cb18c..81a17bd72204 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,9 +3,9 @@ import unicorn from "eslint-plugin-unicorn" import globals from "globals" import tsParser from "@typescript-eslint/parser" import path from "node:path" -import { fileURLToPath } from "node:url" +import {fileURLToPath} from "node:url" import js from "@eslint/js" -import { FlatCompat } from "@eslint/eslintrc" +import {FlatCompat} from "@eslint/eslintrc" const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -18,6 +18,25 @@ const compat = new FlatCompat({ export default [ { ignores: [ + "buildSrc/", + ".github/", + ".rollup.cache/", + ".run", + "app-android/", + "app-ios/", + "artifacts/", + "cache/", + "ci/", + "doc", + "fdroid-metadata-workaround/", + "githooks/", + "native-cache/", + "packages/node-mimimi/", + "packages/tutanota-crypto/lib/internal/", + "resources/", + "schemas/", + "tuta-sdk/", + "**/entities/", "**/translations/", "**/node_modules/", @@ -25,11 +44,6 @@ export default [ "**/build-calendar-app/", "**/dist/", "**/libs/", - "**/app-android/", - "**/app-ios/", - "packages/tutanota-crypto/lib/internal/", - "**/fdroid-metadata-workaround/", - "buildSrc/", ], }, ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"), diff --git a/githooks/pre-commit b/githooks/pre-commit index 583ef2705f2f..a8a1730f3c13 100755 --- a/githooks/pre-commit +++ b/githooks/pre-commit @@ -1,10 +1,19 @@ #!/bin/sh # pre-commit hook to check & fix formatting. does not deal with spaces in paths. +# this runs very quickly, so no need to filter the files +cargo fmt --all +# prettier is another thing though: # get staged files exclude deleted files | only match what prettier matches | transform newline & whitespace into spaces CHG=$(git diff --name-only --diff-filter=d --cached | grep -E ".*\.(ts|js|json|json5)$" | tr [:space:] " ") -# run prettier fix on them + +if [ "x$CHG" = "x" ]; then + echo "no js/ts/json files to format" + exit 0 +fi + +# run prettier fix on the changed files npx prettier -w $CHG > /dev/null # re-add the fixed files git add $CHG > /dev/null diff --git a/githooks/pre-push b/githooks/pre-push new file mode 100755 index 000000000000..7fe23251178c --- /dev/null +++ b/githooks/pre-push @@ -0,0 +1,18 @@ +#! /bin/sh + +# this hook prints a message that can be used to start a jenkins job on our CI server + +echo "inspect stdin (see 'man githooks'):" +while read line +do + echo "stdin: $line" +done < "/dev/stdin" + +# we could try and figure out which remote branch is the closest +# ancestor to make an educated guess at a default target branch. +# TARGET_BRANCH=$( ?? ) +SOURCE_BRANCH=$(git rev-parse --abbrev-ref HEAD) +TUTA_JENKINS_BASE_URL="https://next.tutao.de/jenkins/" + +echo "use this link to start the job to check your work and merge it into your target branch:" +echo "${TUTA_JENKINS_BASE_URL}job/bootleg-ci-merge/parambuild?SOURCE_BRANCH=${SOURCE_BRANCH}&CLEAN_WORKSPACE=false&DRY_RUN=false" \ No newline at end of file diff --git a/package.json b/package.json index f1f677bdfcb1..8c2343d07a72 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "postinstall": "node buildSrc/postinstall.js", "bump-version": "node buildSrc/bump-version.js", "generate-ipc": "npm run build -w @tutao/licc && licc ./ipc-schema", - "style:check": "prettier -c \"**/*.(ts|js|json|json5)\"", + "style:check": "prettier -c \"**/*.(ts|js|json|json5)\" --cache --cache-location cache/prettier", "style:fix": "prettier -w \"**/*.(ts|js|json|json5)\"", - "lint:check": "eslint .", + "lint:check": "eslint . --cache --cache-location cache/eslint", "lint:fix": "eslint --fix .", "check": "npm run style:check && npm run lint:check", "fix": "npm run style:fix && npm run lint:fix" diff --git a/packages/node-mimimi/package.json b/packages/node-mimimi/package.json index fc6df43fad71..a058416faf9a 100644 --- a/packages/node-mimimi/package.json +++ b/packages/node-mimimi/package.json @@ -25,7 +25,6 @@ "scripts": { "build": "node make", "prepublishOnly": "napi prepublish -t npm", - "test": "node make && cargo test", "universal": "napi universal", "version": "napi version" } diff --git a/packages/node-mimimi/src/importer.rs b/packages/node-mimimi/src/importer.rs index 2d3b6b8b53b9..5a125100ad61 100644 --- a/packages/node-mimimi/src/importer.rs +++ b/packages/node-mimimi/src/importer.rs @@ -858,7 +858,7 @@ impl From for IdTupleGenerated { } #[cfg(test)] -#[cfg(not(ci))] +#[cfg(not_ci)] pub mod tests { use super::*; use crate::test_utils::{init_file_importer, write_big_sample_email, CleanDir}; diff --git a/test/tests/desktop/config/ConfigFileTest.ts b/test/tests/desktop/config/ConfigFileTest.ts index c6747d9c00e3..694bddae7a76 100644 --- a/test/tests/desktop/config/ConfigFileTest.ts +++ b/test/tests/desktop/config/ConfigFileTest.ts @@ -49,7 +49,7 @@ o.spec("ConfigFileTest", function () { o.timeout(500) const cf = getConfigFile("path", "conf.json", n.mock("fs", fsMock).set()) - const cycles = 19 + const cycles = 9 const res: number[] = [] for (let i = 0; i < cycles + 1; ) { diff --git a/tuta-sdk/android/sdk/build.gradle.kts b/tuta-sdk/android/sdk/build.gradle.kts index 09c4a9a433fe..92a31a5172a1 100644 --- a/tuta-sdk/android/sdk/build.gradle.kts +++ b/tuta-sdk/android/sdk/build.gradle.kts @@ -6,6 +6,31 @@ plugins { id("org.jetbrains.kotlin.android") } +dependencies { + implementation("net.java.dev.jna:jna:5.14.0@aar") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") + implementation("androidx.annotation:annotation:1.8.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") +} + +val tutanota3Root = layout.projectDirectory + .dir("..") // tutanota-3/tuta-sdk/android + .dir("..") // tutanota-3/tuta-sdk/ + .dir("..") // tutanota-3 +val rustSdkCratePath = tutanota3Root.dir("tuta-sdk").dir("rust").dir("sdk") +val sdkUniffiConfigFile = tutanota3Root.dir("tuta-sdk").dir("rust").dir("sdk").file("uniffi.toml") + +cargo { + module = rustSdkCratePath.toString() + libname = "tutasdk" + prebuiltToolchains = true + pythonCommand = "python3" + targets = getABITargets() + profile = getActiveBuildType() +} + fun getActiveBuildType(): String { var buildType = "debug" val taskNames = gradle.parent?.startParameter?.taskNames @@ -21,25 +46,28 @@ fun getActiveBuildType(): String { } fun getABITargets(): List { - var abi = project.gradle.parent?.startParameter?.projectProperties?.get("targetABI") - if (abi.isNullOrBlank()) - abi = findProperty("targetABI") as String? - - return if (abi.isNullOrBlank()) - listOf("arm", "arm64", "x86_64") - else - listOf(abi) + val targetAbiPropertyValue = findProperty("targetABI") as String? + if(targetAbiPropertyValue == null) { + return listOf("arm", "arm64", "x86_64") + } + return targetAbiPropertyValue.orEmpty().split(",") } -fun getJNILibsDirs(): List { - val abiTargets = getABITargets() - return abiTargets.map { - when (it) { - "arm" -> "armeabi-v7a" - "arm64" -> "arm64-v8a" - "x86_64" -> "x86_64" - else -> "arm64-v8a" - } +fun abiTargetToJniTarget(abiTarget: String): String { + return when (abiTarget) { + "arm" -> "armeabi-v7a" + "arm64" -> "arm64-v8a" + "x86_64" -> "x86_64" + else -> throw RuntimeException("unknown abi target: $abiTarget") + } +} + +fun jniTargetToRustTargetName(jniTargetName: String): String { + return when (jniTargetName) { + "arm64-v8a" -> "aarch64-linux-android" + "armeabi-v7a" -> "armv7-linux-androideabi" + "x86_64" -> "x86_64-linux-android" + else -> throw RuntimeException("unknwon jni name $jniTargetName") } } @@ -85,49 +113,45 @@ android { ndkVersion = "26.1.10909125" } -dependencies { - implementation("net.java.dev.jna:jna:5.14.0@aar") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") - implementation("androidx.annotation:annotation:1.8.0") - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.2.1") - androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") -} - -cargo { - extraCargoBuildArguments = listOf("--package", "tuta-sdk") - module = "../../rust" - libname = "tutasdk" - prebuiltToolchains = true - pythonCommand = "python3" - targets = getABITargets() - profile = getActiveBuildType() -} - tasks.register("generateBinding") { dependsOn("cargoBuild") - getJNILibsDirs().forEach { dir -> + + if (!sdkUniffiConfigFile.asFile.exists()) throw RuntimeException("I would expect uniffi.toml for rust-sdk") + + val targets = getABITargets() + println("abi: $targets") + targets.forEach { abiTargetName -> + println("abi: $abiTargetName ") + val jniTargetName = abiTargetToJniTarget(abiTargetName) + val rustTargetName = jniTargetToRustTargetName(jniTargetName) + + val tutasdkSharedObjectPath = + tutanota3Root.dir("target").dir(rustTargetName).dir(getActiveBuildType()).file("libtutasdk.so") + val kotlinHeaderTargetDir = file("${layout.buildDirectory.asFile.get()}/generated-sources/tuta-sdk") + doLast { exec { - this.executable("mkdir") - this.args("-p", "${layout.buildDirectory.asFile.get()}/rustJniLibs/android/${dir}") + workingDir = tutanota3Root.asFile + executable = "cargo" + args = listOf("build", "--lib", "--package", "tuta-sdk") } + exec { - this.workingDir("../../rust") - this.executable("cargo") - this.args( + workingDir = tutanota3Root.asFile + executable = "cargo" + args = listOf( "run", "--package", "uniffi-bindgen", - "--bin", - "uniffi-bindgen", "generate", - "--library", - "${layout.buildDirectory.asFile.get()}/rustJniLibs/android/${dir}/libtutasdk.so", "--language", "kotlin", "--out-dir", - "${layout.buildDirectory.asFile.get()}/generated-sources/tuta-sdk" + kotlinHeaderTargetDir.toString(), + "--library", + tutasdkSharedObjectPath.toString(), + "--config", + sdkUniffiConfigFile.toString(), ) } } diff --git a/tuta-sdk/rust/README.md b/tuta-sdk/rust/README.md index 63a1d73778ee..c8cd2d3a191d 100644 --- a/tuta-sdk/rust/README.md +++ b/tuta-sdk/rust/README.md @@ -29,10 +29,6 @@ export ANDROID_NDK_HOME=/opt/android-sdk-linux/ndk/26.1.10909125 # add NDK toolchain to path export PATH=${PATH}:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/ -# build the sdk for android -cd tuta-sdk/rust -./make_android.sh - # setup the sdk project - open tutanota-3/tutasdk/android in Android Studio - Ctrl + A -> Project Structure -> Modules -> SDK -> select the NDK diff --git a/tuta-sdk/rust/make_android.sh b/tuta-sdk/rust/make_android.sh deleted file mode 100755 index d43aa8b9f250..000000000000 --- a/tuta-sdk/rust/make_android.sh +++ /dev/null @@ -1,30 +0,0 @@ -set -euxo pipefail - -TARGETS=( - x86_64 - aarch64 -) - -for arch in "${TARGETS[@]}"; do - JNILIBS_DIR="" - case "${arch}" in - aarch64) - JNILIBS_DIR="arm64-v8a" - ;; - *) - JNILIBS_DIR="${arch}" - esac - - cargo build --package tuta-sdk --lib --target "${arch}-linux-android" --release --config "target.${arch}-linux-android.linker=\"${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${arch}-linux-android30-clang\"" - - # generate bindings for our target - cargo run --package uniffi-bindgen generate --library "../../target/${arch}-linux-android/release/libtutasdk.so" --language kotlin --out-dir out - - # And then consume one way or another. - # Would be great to expose maven library for Android. - # Ad-hoc: - mkdir -p "../android/app/src/main/jniLibs/${JNILIBS_DIR}" - mkdir -p "../android/app/src/main/java/de/tutao/tutasdk" - cp "../../target/${arch}-linux-android/release/libtutasdk.so" "../android/app/src/main/jniLibs/${JNILIBS_DIR}/libtutasdk.so" - cp "out/de/tutao/tutasdk/tutasdk.kt" "../android/app/src/main/java/de/tutao/tutasdk/tutasdk.kt" -done diff --git a/tuta-sdk/rust/sdk/tests/can_manipulate_remote_instances.rs b/tuta-sdk/rust/sdk/tests/can_manipulate_remote_instances.rs index 7b325d932359..c8293a993930 100644 --- a/tuta-sdk/rust/sdk/tests/can_manipulate_remote_instances.rs +++ b/tuta-sdk/rust/sdk/tests/can_manipulate_remote_instances.rs @@ -1,4 +1,4 @@ -#![cfg(not(ci))] +#![cfg(not_ci)] use std::sync::Arc; use tutasdk::crypto::aes::Iv; use tutasdk::crypto::key::GenericAesKey; From b7a234d99c4250cbf3ca54bbc1589fc47fdb2337 Mon Sep 17 00:00:00 2001 From: nig Date: Mon, 10 Feb 2025 11:33:53 +0100 Subject: [PATCH 22/23] [mimimi] fix test for apple mbox import --- packages/node-mimimi/src/importer/file_reader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-mimimi/src/importer/file_reader.rs b/packages/node-mimimi/src/importer/file_reader.rs index 7d70dc863588..1850f0785bcc 100644 --- a/packages/node-mimimi/src/importer/file_reader.rs +++ b/packages/node-mimimi/src/importer/file_reader.rs @@ -455,7 +455,7 @@ Yeah, but I really did not like it. Had higher hopes after watching that Simpson let export_content = UserSelectedFileType::from_path(apple_export).unwrap(); assert_eq!( - UserSelectedFileType::Mbox(PathBuf::from( + UserSelectedFileType::AppleMbox(PathBuf::from( "/tmp/should_find_mbox_in_apple_exported_directory/apple-export.mbox/mbox" )), export_content From b577ea32c3796cd63f15912acb4f2d23cc29e973 Mon Sep 17 00:00:00 2001 From: nig Date: Mon, 10 Feb 2025 11:00:05 +0100 Subject: [PATCH 23/23] [ci] github workflows this commit can be reverted if we decide we don't need github workflows anymore. --- .github/shared/setup-emscripten/action.yml | 34 ++++ .github/shared/setup-rust/action.yml | 26 +++ .github/shared/setup/action.yml | 24 +++ .github/shared/swift-common/action.yml | 30 ++++ .github/workflows/kotlin-test.yml | 61 +++++++ .github/workflows/release.yml | 42 +++++ .github/workflows/rust-test.yml | 42 +++++ .github/workflows/swift-calendar-test.yml | 52 ++++++ .github/workflows/swift-mail-test.yml | 52 ++++++ .github/workflows/test.yml | 133 +++++++++++++++ Cargo.toml | 4 - app-ios/fastlane/Fastfile | 54 +++++-- ci/Ios.Jenkinsfile | 18 +++ packages/node-mimimi/package.json | 1 + packages/node-mimimi/src/importer.rs | 152 ------------------ .../tests/can_manipulate_remote_instances.rs | 136 ---------------- 16 files changed, 552 insertions(+), 309 deletions(-) create mode 100644 .github/shared/setup-emscripten/action.yml create mode 100644 .github/shared/setup-rust/action.yml create mode 100644 .github/shared/setup/action.yml create mode 100644 .github/shared/swift-common/action.yml create mode 100644 .github/workflows/kotlin-test.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/rust-test.yml create mode 100644 .github/workflows/swift-calendar-test.yml create mode 100644 .github/workflows/swift-mail-test.yml create mode 100644 .github/workflows/test.yml delete mode 100644 tuta-sdk/rust/sdk/tests/can_manipulate_remote_instances.rs diff --git a/.github/shared/setup-emscripten/action.yml b/.github/shared/setup-emscripten/action.yml new file mode 100644 index 000000000000..7b39e725bb79 --- /dev/null +++ b/.github/shared/setup-emscripten/action.yml @@ -0,0 +1,34 @@ +name: "Setup emscripten" +description: "Setup emcc" +inputs: + emscripten-version: + required: true + description: "emscripten version" +runs: + using: "composite" + + steps: + - name: "get emscripten cached location" + shell: bash + run: | + echo "emscripten_path=$(pwd)/emsdk" >> $GITHUB_ENV + - name: cache emscripten + id: cache-emscripten + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # 4.2.0 + with: + path: ${{ env.emscripten_path }} + key: emscripten-${{ inputs.emscripten-version }} + - name: install emscripten + shell: bash + if: steps.cache-emscripten.outputs.cache-hit != 'true' + run: | + git clone --branch ${{ inputs.emscripten-version }} https://github.com/emscripten-core/emsdk.git + cd emsdk + ./emsdk install latest + ./emsdk activate latest + source ./emsdk_env.sh + - name: add emscripten to path + shell: bash + run: | + echo ${{ env.emscripten_path }}/upstream/bin >> $GITHUB_PATH + echo ${{ env.emscripten_path }}/upstream/emscripten >> $GITHUB_PATH \ No newline at end of file diff --git a/.github/shared/setup-rust/action.yml b/.github/shared/setup-rust/action.yml new file mode 100644 index 000000000000..0fa63238ac49 --- /dev/null +++ b/.github/shared/setup-rust/action.yml @@ -0,0 +1,26 @@ +name: "Setup Rust" +description: "Setup rust environment and dependencies" +runs: + using: "composite" + + steps: + - name: setup rust + shell: bash + run: | + # Specify an exact Rust version. + # + # Newer versions will enable newer features, but at the same time, you may get newer warnings which you have + # to fix. This should be a manual step. + rustup install 1.84.0 + rustup default 1.84.0 + rustup component add rustfmt clippy + - name: Versions + shell: bash + run: | + rustup --version + cargo --version + rustc --version + - name: Add rust targets + if: env.rust-targets != '' + shell: bash + run: rustup target add ${{ env.rust-targets }} diff --git a/.github/shared/setup/action.yml b/.github/shared/setup/action.yml new file mode 100644 index 000000000000..4a53f6afb4df --- /dev/null +++ b/.github/shared/setup/action.yml @@ -0,0 +1,24 @@ +name: "Setup" +description: "Setup js environment and dependencies" +runs: + using: "composite" + + steps: + - name: Use Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 + with: + node-version: 20.18.0 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Get better-sqlite3 cached location + shell: bash + run: | + echo "better_sqlite3_path=$(node buildSrc/getNativeCacheLocation.js better-sqlite3)" >> $GITHUB_ENV + - name: try to use cached better-sqlite3 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 #v4.2.0 + with: + path: ${{ env.better_sqlite3_path }} + key: ${{ env.better_sqlite3_path }} + - name: install packages + shell: bash + run: npm ci \ No newline at end of file diff --git a/.github/shared/swift-common/action.yml b/.github/shared/swift-common/action.yml new file mode 100644 index 000000000000..c5de5ea169e6 --- /dev/null +++ b/.github/shared/swift-common/action.yml @@ -0,0 +1,30 @@ +name: "Swift Common" +description: "Common Swift Checks" + +runs: + using: "composite" + + steps: + - name: Setup Swift + uses: swift-actions/setup-swift@cdbe0f7f4c77929b6580e71983e8606e55ffe7e4 # v1.26.2 + with: + swift-version: ${{ env.swift-version }} + - name: Install Homebrew + uses: Homebrew/actions/setup-homebrew@d54a6744d5fcdff54b45a9659f3e17f769389952 + - name: Install Homebrew dependencies + shell: bash + run: | + brew install swiftlint swift-format xcodegen + - uses: ./.github/shared/setup-rust + - name: Lint + working-directory: ./app-ios + shell: bash + run: ./lint.sh lint:check + - name: Format + working-directory: ./app-ios + shell: bash + run: ./lint.sh style:check + - name: Xcodegen sdk + working-directory: tuta-sdk/ios + shell: bash + run: xcodegen \ No newline at end of file diff --git a/.github/workflows/kotlin-test.yml b/.github/workflows/kotlin-test.yml new file mode 100644 index 000000000000..1c02f0e185b9 --- /dev/null +++ b/.github/workflows/kotlin-test.yml @@ -0,0 +1,61 @@ +name: Kotlin CI + +on: + pull_request: + types: [ opened, synchronize, edited ] + paths: + - 'app-android/**' + - '.github/workflows/kotlin-test.yml' + push: + branches: + - dev-* + - '*/dev' + paths: + - 'app-android/**' + workflow_dispatch: + +jobs: + test-kotlin: + runs-on: ubuntu-latest + env: + java-version: 21 + java-distribution: 'temurin' + TZ: "Europe/Berlin" # We have some tests for same day alarms that depends on this TimeZone + rust-targets: "aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android" + permissions: + actions: none + checks: none + contents: read + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + submodules: 'true' + - name: Create dummy build folder + run: mkdir build && mkdir build-calendar-app # We need this because gradlew lint searches for the app assets + - name: Set up JDK ${{ env.java-version }} + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 #4.2.1 + with: + java-version: ${{ env.java-version }} + distribution: ${{ env.java-distribution }} + - name: Setup Android SDK + uses: android-actions/setup-android@07976c6290703d34c16d382cb36445f98bb43b1f #3.2.0 + - name: Setup Android NDK + run: sdkmanager "ndk;26.1.10909125" + - uses: ./.github/shared/setup-rust + - name: Lint + working-directory: ./app-android + run: ./gradlew lint --quiet + - name: Test + working-directory: ./app-android + run: ./gradlew test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000000..9c6073ecc287 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Create a release draft + +on: + workflow_dispatch: + inputs: + tag: + required: true + +jobs: + release: + runs-on: ubuntu-latest + + permissions: + actions: none + checks: none + contents: write + deployments: none + id-token: none + issues: read + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - name: Use Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 + with: + node-version: 20.18.0 + - name: npm i + run: npm i @octokit/rest + - name: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: "node buildSrc/releaseNotes --tag tutanota-release-${{ github.event.inputs.tag }} + --releaseName ${{ github.event.inputs.tag }} + --milestone ${{ github.event.inputs.tag }} + --platform all" diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml new file mode 100644 index 000000000000..df468d6c50f5 --- /dev/null +++ b/.github/workflows/rust-test.yml @@ -0,0 +1,42 @@ +name: Rust CI + +on: + pull_request: + types: [ opened, synchronize, edited ] + paths: + - 'tuta-sdk/**' + - 'packages/node-mimimi/**' + - '.github/workflows/rust-test.yml' + push: + branches: + - dev-* + - '*/dev' + +jobs: + test: + runs-on: ubuntu-latest + + permissions: + actions: none + checks: none + contents: read + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: ./.github/shared/setup-rust + - name: rust format + run: cargo fmt --check + - name: rust warning check with clippy + run: cargo clippy --all --no-deps -- -Dwarnings # -Dwarnings changes warnings to errors so that the check fails + - name: rust tests + run: cargo test --all diff --git a/.github/workflows/swift-calendar-test.yml b/.github/workflows/swift-calendar-test.yml new file mode 100644 index 000000000000..1d2a1873480f --- /dev/null +++ b/.github/workflows/swift-calendar-test.yml @@ -0,0 +1,52 @@ +name: Swift CI - Calendar + +on: + pull_request: + types: [ opened, synchronize, edited ] + paths: + - 'app-ios/**' + - '.github/workflows/swift-calendar-test.yml' + - '!app-ios/**/Info.plist' + push: + branches: + - dev-* + - '*/dev' + paths: + - 'app-ios/**' + +env: + swift-version: "5.9.2" + swift-format-version: "509.0.0" + rust-targets: "aarch64-apple-ios-sim" + +jobs: + test-swift: + runs-on: macos-14 + + permissions: + actions: none + checks: none + contents: read + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + submodules: 'true' + - uses: ./.github/shared/swift-common + - name: Test Calendar + working-directory: app-ios + run: | + mkdir -p ../build-calendar-app + xcodegen --spec calendar-project.yml + fastlane test_calendar_github + diff --git a/.github/workflows/swift-mail-test.yml b/.github/workflows/swift-mail-test.yml new file mode 100644 index 000000000000..4e2690c899ed --- /dev/null +++ b/.github/workflows/swift-mail-test.yml @@ -0,0 +1,52 @@ +name: Swift CI - Mail + +on: + pull_request: + types: [ opened, synchronize, edited ] + paths: + - 'app-ios/**' + - '.github/workflows/swift-mail-test.yml' + - '!app-ios/**/Info.plist' + push: + branches: + - dev-* + - '*/dev' + paths: + - 'app-ios/**' + +env: + swift-version: "5.9.2" + swift-format-version: "509.0.0" + rust-targets: "aarch64-apple-ios-sim" + +jobs: + test-swift: + runs-on: macos-14 + + permissions: + actions: none + checks: none + contents: read + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + submodules: 'true' + - uses: ./.github/shared/swift-common + - name: Test Mail + working-directory: app-ios + run: | + mkdir -p ../build + xcodegen --spec mail-project.yml + fastlane test_github + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000000..ca62a8ed854b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,133 @@ +name: Node CI + +on: + pull_request: + types: [ opened, synchronize, edited ] + push: + branches: + - dev-* + - '*/dev' +env: + emscripten_version: 3.1.59 + +permissions: + actions: none + checks: none + contents: read + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: "./.github/shared/setup" + - name: lint, formatting + run: | + npm run check + - name: check for FIXMEs + run: | + if grep "FIXME\|[fF]ixme" -r src buildSrc test/tests packages/*/lib app-android/app/src app-ios/tutanota/Sources; then + echo 'FIXMEs in src'; + exit 1; + else + echo 'No FIXMEs in src'; + fi + + + test-node: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + submodules: 'true' + - uses: "./.github/shared/setup" + - uses: "./.github/shared/setup-rust" + - uses: "./.github/shared/setup-emscripten" + with: + emscripten-version: ${{ env.emscripten_version }} + - name: run tests + run: | + npm run build-packages + npm run test-ci + + test-browser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + submodules: 'true' + - uses: "./.github/shared/setup" + - uses: "./.github/shared/setup-rust" + - uses: "./.github/shared/setup-emscripten" + with: + emscripten-version: ${{ env.emscripten_version }} + - name: install chrome + id: setup-chrome + uses: browser-actions/setup-chrome@facf10a55b9caf92e0cc749b4f82bf8220989148 #v1.7.2 + with: + chrome-version: stable + - name: run test in browser + timeout-minutes: 10 + # --no-sandbox is needed because it fails to create sandbox in container otherwise. + # It is fine as we run our own tests in a throwaway container anyway + run: | + echo Chrome version: ${{ steps.setup-chrome.outputs.chrome-version }} + npm run test:app -- --no-run --browser --browser-cmd '${{ steps.setup-chrome.outputs.chrome-path }} --no-sandbox --enable-logging=stderr --headless=new --disable-gpu' + + build-webapp: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + submodules: 'true' + - uses: "./.github/shared/setup" + - uses: "./.github/shared/setup-rust" + - uses: "./.github/shared/setup-emscripten" + with: + emscripten-version: ${{ env.emscripten_version }} + - name: build webapp + run: | + npm run build-packages + node webapp --disable-minify + + build-calendar: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + submodules: 'true' + - uses: "./.github/shared/setup" + - uses: "./.github/shared/setup-rust" + - uses: "./.github/shared/setup-emscripten" + with: + emscripten-version: ${{ env.emscripten_version }} + - name: build webapp + run: | + npm run build-packages + node webapp --disable-minify --app calendar + + build-desktop: # will check that offline migrations are there + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + submodules: 'true' + - uses: "./.github/shared/setup" + - uses: "./.github/shared/setup-rust" + - uses: "./.github/shared/setup-emscripten" + with: + emscripten-version: ${{ env.emscripten_version }} + - name: build desktop + run: | + npm run build-packages + node desktop --disable-minify \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b91976775026..e92bcfa94e8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,3 @@ must_use_candidate = "warn" unused_async = "warn" implicit_clone = "warn" explicit_iter_loop = "warn" - - -[workspace.lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(not_ci)'] } \ No newline at end of file diff --git a/app-ios/fastlane/Fastfile b/app-ios/fastlane/Fastfile index 0e162c4b4ae2..94cebd9c2e4b 100644 --- a/app-ios/fastlane/Fastfile +++ b/app-ios/fastlane/Fastfile @@ -140,7 +140,7 @@ platform :ios do ) end - desc "Run iOS mail test cases" + desc "Run iOS mail test cases (Jenkins)" lane :test_tuta_app do # Create tutanota-3/build if it's not there because we try to copy it during build sh "mkdir -p ../../build" @@ -158,22 +158,32 @@ platform :ios do ) end - desc "Run iOS shared framework test cases" - lane :test_tuta_shared_framework do - # Create tutanota-3/build if it's not there because we try to copy it during build - sh "mkdir -p ../../build" - - # Create tutanota-3/build-calendar-app if it's not there because we try to copy it during build - sh "mkdir -p ../../build-calendar-app" - - sh "if xcrun simctl list | grep iphone-15-ios-18-2; then echo 'Using existing simulator'; else xcrun simctl create iphone-15-ios-18-2 com.apple.CoreSimulator.SimDeviceType.iPhone-15 com.apple.CoreSimulator.SimRuntime.iOS-18-2; fi" - - clear_derived_data - - run_tests( - scheme: "TutanotaSharedFramework", - ) - end + desc "Run iOS shared framework test cases (Jenkins)" + lane :test_tuta_shared_framework do + # Create tutanota-3/build if it's not there because we try to copy it during build + sh "mkdir -p ../../build" + + # Create tutanota-3/build-calendar-app if it's not there because we try to copy it during build + sh "mkdir -p ../../build-calendar-app" + + sh "if xcrun simctl list | grep iphone-15-ios-18-2; then echo 'Using existing simulator'; else xcrun simctl create iphone-15-ios-18-2 com.apple.CoreSimulator.SimDeviceType.iPhone-15 com.apple.CoreSimulator.SimRuntime.iOS-18-2; fi" + + clear_derived_data + + run_tests( + scheme: "TutanotaSharedFramework", + ) + end + + desc "Run iOS test cases (Github actions version)" + lane :test_github do + run_tests( + scheme: "tuta debug", + ) + run_tests( + scheme: "TutanotaSharedFramework", + ) + end desc "Renew prod adhoc cert" lane :renew_adhoc_cert_prod do @@ -231,6 +241,16 @@ platform :ios do ) end + desc "Run iOS test cases for Calendar app (Github actions version)" + lane :test_calendar_github do + run_tests( + scheme: "calendar debug", + ) + run_tests( + scheme: "TutanotaSharedFramework", + ) + end + desc "Build a new staging Calendar release for ad-hoc" lane :calendar_adhoc_staging do match( diff --git a/ci/Ios.Jenkinsfile b/ci/Ios.Jenkinsfile index 58507652f8b1..3b09d50c9ea9 100644 --- a/ci/Ios.Jenkinsfile +++ b/ci/Ios.Jenkinsfile @@ -43,6 +43,24 @@ pipeline { } } } + stage("Run tests") { + agent { + label 'mac' + } + environment { + LC_ALL = "en_US.UTF-8" + LANG = "en_US.UTF-8" + } + steps { + script { + generateXCodeProjects() + dir('app-ios') { + sh 'fastlane test' + } + } + } // steps + } // stage run tests + stage("Build") { environment { MATCH_GIT_URL = "git@gitlab:/tuta/apple-certificates.git" diff --git a/packages/node-mimimi/package.json b/packages/node-mimimi/package.json index a058416faf9a..fc6df43fad71 100644 --- a/packages/node-mimimi/package.json +++ b/packages/node-mimimi/package.json @@ -25,6 +25,7 @@ "scripts": { "build": "node make", "prepublishOnly": "napi prepublish -t npm", + "test": "node make && cargo test", "universal": "napi universal", "version": "napi version" } diff --git a/packages/node-mimimi/src/importer.rs b/packages/node-mimimi/src/importer.rs index 5a125100ad61..b8b0171a99b7 100644 --- a/packages/node-mimimi/src/importer.rs +++ b/packages/node-mimimi/src/importer.rs @@ -856,155 +856,3 @@ impl From for IdTupleGenerated { } } } - -#[cfg(test)] -#[cfg(not_ci)] -pub mod tests { - use super::*; - use crate::test_utils::{init_file_importer, write_big_sample_email, CleanDir}; - - #[tokio::test] - async fn can_import_single_eml_file_without_attachment() { - let importer = init_file_importer(vec!["sample.eml"]).await; - importer.start_stateful_import().await.unwrap(); - - let remote_state = importer.essentials.load_remote_state().await.unwrap(); - assert_eq!(remote_state.status, ImportStatus::Finished as i64); - assert_eq!(remote_state.failedMails, 0); - assert_eq!(remote_state.successfulMails, 1); - } - - #[tokio::test] - async fn can_import_single_eml_file_with_attachment() { - let importer = init_file_importer(vec!["attachment_sample.eml"]).await; - importer.start_stateful_import().await.unwrap(); - - let remote_state = importer.essentials.load_remote_state().await.unwrap(); - assert_eq!(remote_state.status, ImportStatus::Finished as i64); - assert_eq!(remote_state.failedMails, 0); - assert_eq!(remote_state.successfulMails, 1); - } - - #[test] - fn max_request_size_in_test_is_different() { - assert_eq!(1024 * 5, MAX_REQUEST_SIZE); - } - - #[tokio::test] - async fn existing_import_should_be_none_if_no_state_file() { - let config_dir_string = "/tmp/existing_import_should_be_none_if_no_state_file"; - let mailbox_id = "some_mailbox_id"; - let import_dir: PathBuf = [ - config_dir_string.to_string(), - "current_imports".to_string(), - mailbox_id.to_string(), - ] - .iter() - .collect(); - - let result = Importer::get_existing_import_id(&import_dir); - assert!(matches!(result, Ok(None))); - } - - #[tokio::test] - async fn importing_too_big_eml_should_fail() { - let whither = "/tmp/toobig.eml"; - write_big_sample_email(whither); - - let importer = init_file_importer(vec![whither]).await; - let import_res = importer.start_stateful_import().await; - assert!(importer - .essentials - .import_directory - .join(FAILED_MAILS_SUB_DIR) - .join("toobig-0.eml") - .try_exists() - .unwrap()); - - assert_eq!( - ImportErrorKind::SourceExhaustedSomeError, - import_res.unwrap_err().kind - ); - - let remote_state = importer.essentials.load_remote_state().await.unwrap(); - assert_eq!(remote_state.status, ImportStatus::Finished as i64); - assert_eq!(remote_state.failedMails, 1); - assert_eq!(remote_state.successfulMails, 0); - } - - #[tokio::test] - async fn get_resumable_state_id_invalid_content() { - let config_dir_string = "/tmp/get_resumable_state_id_invalid_content"; - let mailbox_id = "some_mailbox_id"; - let import_dir: PathBuf = [ - config_dir_string.to_string(), - "current_imports".to_string(), - mailbox_id.to_string(), - ] - .iter() - .collect(); - let config_dir = PathBuf::from(config_dir_string); - - let _tear_down = CleanDir { - dir: config_dir.clone(), - }; - - if !import_dir.exists() { - fs::create_dir_all(&import_dir).unwrap(); - } - let mut state_id_file_path = import_dir.clone(); - state_id_file_path.push(STATE_ID_FILE_NAME); - let invalid_id = "blah"; - fs::write(&state_id_file_path, invalid_id).unwrap(); - - let result = Importer::get_existing_import_id(&import_dir) - .unwrap_err() - .kind(); - assert_eq!(result, std::io::ErrorKind::InvalidData); - } - - #[tokio::test] - async fn should_stop_if_on_stop_action() { - let importer = init_file_importer(vec!["sample.eml"; 1]).await; - importer - .set_next_progress_action(ImportProgressAction::Stop) - .await; - importer.start_stateful_import().await.unwrap(); - - let mut iterator = importer.chunked_import_source.lock().await; - - // if we set progress action to stop, it should not have consumed any iterator - assert!(iterator.next().is_some()); - assert!(iterator.next().is_none()); - } - - #[tokio::test] - async fn should_pause_if_on_pause_action() { - let importer = init_file_importer(vec!["sample.eml"; 2]).await; - importer - .set_next_progress_action(ImportProgressAction::Pause) - .await; - importer.start_stateful_import().await.unwrap(); - - let mut iterator = importer.chunked_import_source.lock().await; - - // if we set progress action to pause, it should not have consumed any iterator - assert!(iterator.next().is_some()); - assert!(iterator.next().is_some()); - assert!(iterator.next().is_none()); - } - - #[tokio::test] - async fn should_continue_if_on_continue_action() { - let importer = init_file_importer(vec!["sample.eml"; 1]).await; - importer - .set_next_progress_action(ImportProgressAction::Continue) - .await; - importer.start_stateful_import().await.unwrap(); - - let mut iterator = importer.chunked_import_source.lock().await; - - // if we set progress action to continue, it should have consumed all the chunks - assert!(iterator.next().is_none()); - } -} diff --git a/tuta-sdk/rust/sdk/tests/can_manipulate_remote_instances.rs b/tuta-sdk/rust/sdk/tests/can_manipulate_remote_instances.rs deleted file mode 100644 index c8293a993930..000000000000 --- a/tuta-sdk/rust/sdk/tests/can_manipulate_remote_instances.rs +++ /dev/null @@ -1,136 +0,0 @@ -#![cfg(not_ci)] -use std::sync::Arc; -use tutasdk::crypto::aes::Iv; -use tutasdk::crypto::key::GenericAesKey; -use tutasdk::crypto::randomizer_facade::RandomizerFacade; -use tutasdk::crypto::{Aes256Key, IV_BYTE_SIZE}; -use tutasdk::entities::generated::sys::PushIdentifier; -use tutasdk::entities::generated::tutanota::Mail; -use tutasdk::net::native_rest_client::NativeRestClient; -use tutasdk::{GeneratedId, IdTupleGenerated, ListLoadDirection, Sdk}; - -#[tokio::test] -async fn can_create_remote_instance() { - let logged_in_sdk = Sdk::new( - "http://localhost:9000".to_string(), - Arc::new(NativeRestClient::try_new().unwrap()), - ) - .create_session("map-free@tutanota.de", "map") - .await - .expect("Can not create session"); - let randomizer = RandomizerFacade::from_core(rand_core::OsRng); - let crypto_entity_client = logged_in_sdk.mail_facade().get_crypto_entity_client(); - - let session_key = GenericAesKey::Aes256(Aes256Key::generate(&randomizer)); - let user_group_id = logged_in_sdk.get_user_group_id(); - let user_group_key = logged_in_sdk - .get_current_sym_group_key(&user_group_id) - .await - .unwrap(); - - let _owner_enc_session_key = user_group_key.encrypt_key( - &session_key, - Iv::from_bytes(&rand::random::<[u8; IV_BYTE_SIZE]>()).unwrap(), - ); - let user_push_identifier_list_id = logged_in_sdk - .get_user() - .pushIdentifierList - .as_ref() - .unwrap() - .list - .clone(); - - let mut push_identifier = PushIdentifier { - _area: 0, - _owner: user_group_id.clone(), - _ownerGroup: Some(user_group_id), - _ownerEncSessionKey: Some(_owner_enc_session_key.object), - _ownerKeyVersion: Some(_owner_enc_session_key.version as i64), - _id: Some(IdTupleGenerated { - list_id: user_push_identifier_list_id.clone(), - element_id: Default::default(), - }), - app: 1, // AppType.Mail - disabled: false, - displayName: "display name for push identifier".to_string(), - identifier: "map-free@tutanota.de".to_string(), - language: "en".to_string(), - lastNotificationDate: None, - lastUsageTime: Default::default(), - pushServiceType: 2, // PushServiceType.EMAIL - // when this is returned and deserialized, this will be set but empty - _errors: Some(Default::default()), - // none of these need to be set - _permissions: Default::default(), - _finalIvs: Default::default(), - _format: 0, - }; - - let response = crypto_entity_client - .create_instance(push_identifier.clone(), Some(session_key)) - .await - .unwrap(); - - push_identifier._id = Some(IdTupleGenerated { - list_id: user_push_identifier_list_id, - element_id: response - .generatedId - .expect("Expected server to return generatedId for elementId"), - }); - push_identifier._permissions = response.permissionListId; - - let expected_mail_import_state = crypto_entity_client - .load::(push_identifier._id.as_ref().unwrap()) - .await - .as_ref() - .unwrap() - .clone(); - assert_eq!(expected_mail_import_state, push_identifier); -} - -#[tokio::test] -async fn can_update_remote_instance() { - let logged_in_sdk = Sdk::new( - "http://localhost:9000".to_string(), - Arc::new(NativeRestClient::try_new().unwrap()), - ) - .create_session("map-free@tutanota.de", "map") - .await - .expect("Can not create session"); - let crypto_entity_client = logged_in_sdk.mail_facade().get_crypto_entity_client(); - - let current_mailbag_mail_list = logged_in_sdk - .mail_facade() - .load_user_mailbox() - .await - .unwrap() - .currentMailBag - .as_ref() - .unwrap() - .mails - .clone(); - let mut sample_mail = crypto_entity_client - .load_range::( - ¤t_mailbag_mail_list, - &GeneratedId::max_id(), - 1, - ListLoadDirection::DESC, - ) - .await - .unwrap() - .first() - .unwrap() - .clone(); - sample_mail.unread = !sample_mail.unread; - - crypto_entity_client - .update_instance(sample_mail.clone()) - .await - .unwrap(); - - let updated_mail: Mail = crypto_entity_client - .load(sample_mail._id.as_ref().unwrap()) - .await - .unwrap(); - assert_eq!(updated_mail, sample_mail); -}