diff --git a/app-android/app/src/main/java/de/tutao/tutanota/AndroidFileFacade.kt b/app-android/app/src/main/java/de/tutao/tutanota/AndroidFileFacade.kt index 7ab9fa242d..a3fd40f7ce 100644 --- a/app-android/app/src/main/java/de/tutao/tutanota/AndroidFileFacade.kt +++ b/app-android/app/src/main/java/de/tutao/tutanota/AndroidFileFacade.kt @@ -126,6 +126,10 @@ class AndroidFileFacade( error("not implemented for this platform") } + override suspend fun openMacImportFileChooser(): List { + error("not implemented for this platform") + } + @Throws(IOException::class) override suspend fun writeTempDataFile(file: DataFile): String = withContext(Dispatchers.IO) { val fileHandle = File(tempDir.decrypt, file.name) diff --git a/app-android/calendar/src/main/java/de/tutao/calendar/AndroidFileFacade.kt b/app-android/calendar/src/main/java/de/tutao/calendar/AndroidFileFacade.kt index d904f15534..a106e4428d 100644 --- a/app-android/calendar/src/main/java/de/tutao/calendar/AndroidFileFacade.kt +++ b/app-android/calendar/src/main/java/de/tutao/calendar/AndroidFileFacade.kt @@ -126,6 +126,10 @@ class AndroidFileFacade( error("not implemented for this platform") } + override suspend fun openMacImportFileChooser(): List { + error("not implemented for this platform") + } + @Throws(IOException::class) override suspend fun writeTempDataFile(file: DataFile): String = withContext(Dispatchers.IO) { val fileHandle = File(tempDir.decrypt, file.name) diff --git a/app-android/tutashared/src/main/java/de/tutao/tutashared/generated_ipc/FileFacade.kt b/app-android/tutashared/src/main/java/de/tutao/tutashared/generated_ipc/FileFacade.kt index 732c0364e7..81acfed4b4 100644 --- a/app-android/tutashared/src/main/java/de/tutao/tutashared/generated_ipc/FileFacade.kt +++ b/app-android/tutashared/src/main/java/de/tutao/tutashared/generated_ipc/FileFacade.kt @@ -30,6 +30,11 @@ interface FileFacade { */ suspend fun openFolderChooser( ): String? + /** + * Opens OS file picker for selecting either a file or folder for Email Import. Works only on macOS + */ + suspend fun openMacImportFileChooser( + ): List suspend fun deleteFile( file: String, ): Unit diff --git a/app-android/tutashared/src/main/java/de/tutao/tutashared/generated_ipc/FileFacadeReceiveDispatcher.kt b/app-android/tutashared/src/main/java/de/tutao/tutashared/generated_ipc/FileFacadeReceiveDispatcher.kt index 124203666e..30c43ccd8a 100644 --- a/app-android/tutashared/src/main/java/de/tutao/tutashared/generated_ipc/FileFacadeReceiveDispatcher.kt +++ b/app-android/tutashared/src/main/java/de/tutao/tutashared/generated_ipc/FileFacadeReceiveDispatcher.kt @@ -39,6 +39,11 @@ class FileFacadeReceiveDispatcher( ) return json.encodeToString(result) } + "openMacImportFileChooser" -> { + val result: List = this.facade.openMacImportFileChooser( + ) + return json.encodeToString(result) + } "deleteFile" -> { val file: String = json.decodeFromString(arg[0]) val result: Unit = this.facade.deleteFile( diff --git a/app-ios/TutanotaSharedFramework/GeneratedIpc/FileFacade.swift b/app-ios/TutanotaSharedFramework/GeneratedIpc/FileFacade.swift index ed86188182..5bb6b4cef5 100644 --- a/app-ios/TutanotaSharedFramework/GeneratedIpc/FileFacade.swift +++ b/app-ios/TutanotaSharedFramework/GeneratedIpc/FileFacade.swift @@ -27,6 +27,11 @@ public protocol FileFacade { */ func openFolderChooser( ) async throws -> String? + /** + * Opens OS file picker for selecting either a file or folder for Email Import. Works only on macOS + */ + func openMacImportFileChooser( + ) async throws -> [String] func deleteFile( _ file: String ) async throws -> Void diff --git a/app-ios/TutanotaSharedFramework/GeneratedIpc/FileFacadeReceiveDispatcher.swift b/app-ios/TutanotaSharedFramework/GeneratedIpc/FileFacadeReceiveDispatcher.swift index 0be0e5d146..d72e5231a6 100644 --- a/app-ios/TutanotaSharedFramework/GeneratedIpc/FileFacadeReceiveDispatcher.swift +++ b/app-ios/TutanotaSharedFramework/GeneratedIpc/FileFacadeReceiveDispatcher.swift @@ -32,6 +32,10 @@ public class FileFacadeReceiveDispatcher { let result = try await self.facade.openFolderChooser( ) return toJson(result) + case "openMacImportFileChooser": + let result = try await self.facade.openMacImportFileChooser( + ) + return toJson(result) case "deleteFile": let file = try! JSONDecoder().decode(String.self, from: arg[0].data(using: .utf8)!) try await self.facade.deleteFile( diff --git a/app-ios/calendar/Sources/Files/IosFileFacade.swift b/app-ios/calendar/Sources/Files/IosFileFacade.swift index 6747bdc41f..9618c0037c 100644 --- a/app-ios/calendar/Sources/Files/IosFileFacade.swift +++ b/app-ios/calendar/Sources/Files/IosFileFacade.swift @@ -4,6 +4,7 @@ import TutanotaSharedFramework import UniformTypeIdentifiers class IosFileFacade: FileFacade { + func openMacImportFileChooser() async throws -> [String] { fatalError("not implemented for this platform") } let chooser: TUTFileChooser let viewer: FileViewer diff --git a/app-ios/tutanota/Sources/Files/IosFileFacade.swift b/app-ios/tutanota/Sources/Files/IosFileFacade.swift index 59dff78640..be6e4fbace 100644 --- a/app-ios/tutanota/Sources/Files/IosFileFacade.swift +++ b/app-ios/tutanota/Sources/Files/IosFileFacade.swift @@ -4,6 +4,7 @@ import TutanotaSharedFramework import UniformTypeIdentifiers class IosFileFacade: FileFacade { + func openMacImportFileChooser() async throws -> [String] { fatalError("not implemented for this platform") } let chooser: TUTFileChooser let viewer: FileViewer diff --git a/ipc-schema/facades/FileFacade.json b/ipc-schema/facades/FileFacade.json index 9db6f1c396..dee3d17016 100644 --- a/ipc-schema/facades/FileFacade.json +++ b/ipc-schema/facades/FileFacade.json @@ -37,6 +37,11 @@ "arg": [], "ret": "string?" }, + "openMacImportFileChooser": { + "doc": "Opens OS file picker for selecting either a file or folder for Email Import. Works only on macOS", + "arg": [], + "ret": "List" + }, "deleteFile": { "arg": [ { diff --git a/src/common/desktop/files/DesktopFileFacade.ts b/src/common/desktop/files/DesktopFileFacade.ts index 61cb3b86f1..823cebfe57 100644 --- a/src/common/desktop/files/DesktopFileFacade.ts +++ b/src/common/desktop/files/DesktopFileFacade.ts @@ -204,6 +204,17 @@ export class DesktopFileFacade implements FileFacade { .then(({ filePaths }) => filePaths[0] ?? null) } + /** + * Opens OS file picker for selecting either a file or a folder. The options "openDirectory", "openFile" simultaneously are only supported on macOS + * This is needed because Apple Mail uses a custom MBOX when format when exporting, which is a directory and not a file. + */ + async openMacImportFileChooser(): Promise> { + const opts: OpenDialogOptions = { properties: ["openDirectory", "openFile", "multiSelections"] } + opts.filters = [{ name: "Filter", extensions: ["eml", "mbox"].slice() }] + const { filePaths } = await this.electron.dialog.showOpenDialog(this.win._browserWindow, opts) + return filePaths + } + async putFileIntoDownloadsFolder(localFileUri: string, fileNameToUse: string): Promise { const savePath = await this.pickSavePath(fileNameToUse) await this.fs.promises.mkdir(path.dirname(savePath), { diff --git a/src/common/native/common/FileApp.ts b/src/common/native/common/FileApp.ts index bdf0edc1c9..f55e5b9efd 100644 --- a/src/common/native/common/FileApp.ts +++ b/src/common/native/common/FileApp.ts @@ -47,6 +47,11 @@ export class NativeFileApp { return this.fileFacade.openFolderChooser() } + async openMacImportFileChooser(): Promise> { + const files = await this.fileFacade.openMacImportFileChooser() + return promiseMap(files, this.uriToFileRef.bind(this)) + } + /** * Deletes the file. * @param file The uri of the file to delete. diff --git a/src/common/native/common/generatedipc/FileFacade.ts b/src/common/native/common/generatedipc/FileFacade.ts index 932a5ce888..a2387ec63a 100644 --- a/src/common/native/common/generatedipc/FileFacade.ts +++ b/src/common/native/common/generatedipc/FileFacade.ts @@ -23,6 +23,11 @@ export interface FileFacade { */ openFolderChooser(): Promise + /** + * Opens OS file picker for selecting either a file or folder for Email Import. Works only on macOS + */ + openMacImportFileChooser(): Promise> + deleteFile(file: string): Promise getName(file: string): Promise diff --git a/src/common/native/common/generatedipc/FileFacadeReceiveDispatcher.ts b/src/common/native/common/generatedipc/FileFacadeReceiveDispatcher.ts index 19a90c221f..7119580b02 100644 --- a/src/common/native/common/generatedipc/FileFacadeReceiveDispatcher.ts +++ b/src/common/native/common/generatedipc/FileFacadeReceiveDispatcher.ts @@ -22,6 +22,9 @@ export class FileFacadeReceiveDispatcher { case "openFolderChooser": { return this.facade.openFolderChooser() } + case "openMacImportFileChooser": { + return this.facade.openMacImportFileChooser() + } case "deleteFile": { const file: string = arg[0] return this.facade.deleteFile(file) diff --git a/src/common/native/common/generatedipc/FileFacadeSendDispatcher.ts b/src/common/native/common/generatedipc/FileFacadeSendDispatcher.ts index 3e74a8c1a2..260d4965e2 100644 --- a/src/common/native/common/generatedipc/FileFacadeSendDispatcher.ts +++ b/src/common/native/common/generatedipc/FileFacadeSendDispatcher.ts @@ -16,6 +16,9 @@ export class FileFacadeSendDispatcher implements FileFacade { async openFolderChooser(...args: Parameters) { return this.transport.invokeNative("ipc", ["FileFacade", "openFolderChooser", ...args]) } + async openMacImportFileChooser(...args: Parameters) { + return this.transport.invokeNative("ipc", ["FileFacade", "openMacImportFileChooser", ...args]) + } async deleteFile(...args: Parameters) { return this.transport.invokeNative("ipc", ["FileFacade", "deleteFile", ...args]) } diff --git a/src/mail-app/settings/DesktopMailImportSettingsViewer.ts b/src/mail-app/settings/DesktopMailImportSettingsViewer.ts index 51a8692b54..75f4e8fccc 100644 --- a/src/mail-app/settings/DesktopMailImportSettingsViewer.ts +++ b/src/mail-app/settings/DesktopMailImportSettingsViewer.ts @@ -20,6 +20,7 @@ import { ColumnWidth, Table, TableLineAttrs } from "../../common/gui/base/Table. import { mailLocator } from "../mailLocator.js" import { formatDate } from "../../common/misc/Formatter.js" import { LoginButton, LoginButtonType } from "../../common/gui/base/buttons/LoginButton" +import { client } from "../../common/misc/ClientDetector" /** * Settings viewer for mail import rendered only in the Desktop client. @@ -59,7 +60,9 @@ export class DesktopMailImportSettingsViewer implements UpdatableSettingsViewer } const allowedExtensions = ["eml", "mbox"] - const filePaths = await mailLocator.fileApp.openFileChooser(dom.getBoundingClientRect(), allowedExtensions, true) + const filePaths = client.isMacOS + ? await mailLocator.fileApp.openMacImportFileChooser() + : await mailLocator.fileApp.openFileChooser(dom.getBoundingClientRect(), allowedExtensions, true) await this.mailImporter().onStartBtnClick(filePaths.map((fp) => fp.location)) }