fix selecting apple MBOX format when importing emails

Co-authored-by: amm <amm@tutao.de>
Co-authored-by: map <mpfau@users.noreply.github.com>
This commit is contained in:
kib 2025-10-27 11:53:52 +01:00
parent 4c4309139c
commit b6fe5d3e97
No known key found for this signature in database
GPG key ID: 79B7666BAB6012A0
15 changed files with 65 additions and 1 deletions

View file

@ -126,6 +126,10 @@ class AndroidFileFacade(
error("not implemented for this platform") error("not implemented for this platform")
} }
override suspend fun openMacImportFileChooser(): List<String> {
error("not implemented for this platform")
}
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun writeTempDataFile(file: DataFile): String = withContext(Dispatchers.IO) { override suspend fun writeTempDataFile(file: DataFile): String = withContext(Dispatchers.IO) {
val fileHandle = File(tempDir.decrypt, file.name) val fileHandle = File(tempDir.decrypt, file.name)

View file

@ -126,6 +126,10 @@ class AndroidFileFacade(
error("not implemented for this platform") error("not implemented for this platform")
} }
override suspend fun openMacImportFileChooser(): List<String> {
error("not implemented for this platform")
}
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun writeTempDataFile(file: DataFile): String = withContext(Dispatchers.IO) { override suspend fun writeTempDataFile(file: DataFile): String = withContext(Dispatchers.IO) {
val fileHandle = File(tempDir.decrypt, file.name) val fileHandle = File(tempDir.decrypt, file.name)

View file

@ -30,6 +30,11 @@ interface FileFacade {
*/ */
suspend fun openFolderChooser( suspend fun openFolderChooser(
): String? ): String?
/**
* Opens OS file picker for selecting either a file or folder for Email Import. Works only on macOS
*/
suspend fun openMacImportFileChooser(
): List<String>
suspend fun deleteFile( suspend fun deleteFile(
file: String, file: String,
): Unit ): Unit

View file

@ -39,6 +39,11 @@ class FileFacadeReceiveDispatcher(
) )
return json.encodeToString(result) return json.encodeToString(result)
} }
"openMacImportFileChooser" -> {
val result: List<String> = this.facade.openMacImportFileChooser(
)
return json.encodeToString(result)
}
"deleteFile" -> { "deleteFile" -> {
val file: String = json.decodeFromString(arg[0]) val file: String = json.decodeFromString(arg[0])
val result: Unit = this.facade.deleteFile( val result: Unit = this.facade.deleteFile(

View file

@ -27,6 +27,11 @@ public protocol FileFacade {
*/ */
func openFolderChooser( func openFolderChooser(
) async throws -> String? ) 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( func deleteFile(
_ file: String _ file: String
) async throws -> Void ) async throws -> Void

View file

@ -32,6 +32,10 @@ public class FileFacadeReceiveDispatcher {
let result = try await self.facade.openFolderChooser( let result = try await self.facade.openFolderChooser(
) )
return toJson(result) return toJson(result)
case "openMacImportFileChooser":
let result = try await self.facade.openMacImportFileChooser(
)
return toJson(result)
case "deleteFile": case "deleteFile":
let file = try! JSONDecoder().decode(String.self, from: arg[0].data(using: .utf8)!) let file = try! JSONDecoder().decode(String.self, from: arg[0].data(using: .utf8)!)
try await self.facade.deleteFile( try await self.facade.deleteFile(

View file

@ -4,6 +4,7 @@ import TutanotaSharedFramework
import UniformTypeIdentifiers import UniformTypeIdentifiers
class IosFileFacade: FileFacade { class IosFileFacade: FileFacade {
func openMacImportFileChooser() async throws -> [String] { fatalError("not implemented for this platform") }
let chooser: TUTFileChooser let chooser: TUTFileChooser
let viewer: FileViewer let viewer: FileViewer

View file

@ -4,6 +4,7 @@ import TutanotaSharedFramework
import UniformTypeIdentifiers import UniformTypeIdentifiers
class IosFileFacade: FileFacade { class IosFileFacade: FileFacade {
func openMacImportFileChooser() async throws -> [String] { fatalError("not implemented for this platform") }
let chooser: TUTFileChooser let chooser: TUTFileChooser
let viewer: FileViewer let viewer: FileViewer

View file

@ -37,6 +37,11 @@
"arg": [], "arg": [],
"ret": "string?" "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<string>"
},
"deleteFile": { "deleteFile": {
"arg": [ "arg": [
{ {

View file

@ -204,6 +204,17 @@ export class DesktopFileFacade implements FileFacade {
.then(({ filePaths }) => filePaths[0] ?? null) .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<Array<string>> {
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<string> { async putFileIntoDownloadsFolder(localFileUri: string, fileNameToUse: string): Promise<string> {
const savePath = await this.pickSavePath(fileNameToUse) const savePath = await this.pickSavePath(fileNameToUse)
await this.fs.promises.mkdir(path.dirname(savePath), { await this.fs.promises.mkdir(path.dirname(savePath), {

View file

@ -47,6 +47,11 @@ export class NativeFileApp {
return this.fileFacade.openFolderChooser() return this.fileFacade.openFolderChooser()
} }
async openMacImportFileChooser(): Promise<Array<FileReference>> {
const files = await this.fileFacade.openMacImportFileChooser()
return promiseMap(files, this.uriToFileRef.bind(this))
}
/** /**
* Deletes the file. * Deletes the file.
* @param file The uri of the file to delete. * @param file The uri of the file to delete.

View file

@ -23,6 +23,11 @@ export interface FileFacade {
*/ */
openFolderChooser(): Promise<string | null> openFolderChooser(): Promise<string | null>
/**
* Opens OS file picker for selecting either a file or folder for Email Import. Works only on macOS
*/
openMacImportFileChooser(): Promise<ReadonlyArray<string>>
deleteFile(file: string): Promise<void> deleteFile(file: string): Promise<void>
getName(file: string): Promise<string> getName(file: string): Promise<string>

View file

@ -22,6 +22,9 @@ export class FileFacadeReceiveDispatcher {
case "openFolderChooser": { case "openFolderChooser": {
return this.facade.openFolderChooser() return this.facade.openFolderChooser()
} }
case "openMacImportFileChooser": {
return this.facade.openMacImportFileChooser()
}
case "deleteFile": { case "deleteFile": {
const file: string = arg[0] const file: string = arg[0]
return this.facade.deleteFile(file) return this.facade.deleteFile(file)

View file

@ -16,6 +16,9 @@ export class FileFacadeSendDispatcher implements FileFacade {
async openFolderChooser(...args: Parameters<FileFacade["openFolderChooser"]>) { async openFolderChooser(...args: Parameters<FileFacade["openFolderChooser"]>) {
return this.transport.invokeNative("ipc", ["FileFacade", "openFolderChooser", ...args]) return this.transport.invokeNative("ipc", ["FileFacade", "openFolderChooser", ...args])
} }
async openMacImportFileChooser(...args: Parameters<FileFacade["openMacImportFileChooser"]>) {
return this.transport.invokeNative("ipc", ["FileFacade", "openMacImportFileChooser", ...args])
}
async deleteFile(...args: Parameters<FileFacade["deleteFile"]>) { async deleteFile(...args: Parameters<FileFacade["deleteFile"]>) {
return this.transport.invokeNative("ipc", ["FileFacade", "deleteFile", ...args]) return this.transport.invokeNative("ipc", ["FileFacade", "deleteFile", ...args])
} }

View file

@ -20,6 +20,7 @@ import { ColumnWidth, Table, TableLineAttrs } from "../../common/gui/base/Table.
import { mailLocator } from "../mailLocator.js" import { mailLocator } from "../mailLocator.js"
import { formatDate } from "../../common/misc/Formatter.js" import { formatDate } from "../../common/misc/Formatter.js"
import { LoginButton, LoginButtonType } from "../../common/gui/base/buttons/LoginButton" 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. * 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 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)) await this.mailImporter().onStartBtnClick(filePaths.map((fp) => fp.location))
} }