add attachment download service: fetch section from IMAP, cache to disk

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 13:43:11 +01:00
parent 004d1ad027
commit 0ba309c525
2 changed files with 62 additions and 0 deletions

View File

@@ -464,6 +464,12 @@ public final class MailStore: Sendable {
}
}
public func attachment(id: String) throws -> AttachmentRecord? {
try dbWriter.read { db in
try AttachmentRecord.fetchOne(db, key: id)
}
}
public func attachments(messageId: String) throws -> [AttachmentRecord] {
try dbWriter.read { db in
try AttachmentRecord

View File

@@ -334,6 +334,62 @@ public final class SyncCoordinator {
idleClient = nil
}
// MARK: - Attachment Download
/// Download an attachment on demand, cache it to disk, update the DB record.
/// Returns the local file URL for preview.
public func downloadAttachment(
attachmentId: String,
messageUid: Int,
mailboxName: String
) async throws -> URL {
guard let record = try store.attachment(id: attachmentId) else {
throw AttachmentError.notFound
}
// Return cached file if it exists on disk
if let cachePath = record.cachePath {
let url = URL(fileURLWithPath: cachePath)
if FileManager.default.fileExists(atPath: cachePath) {
return url
}
}
guard let sectionPath = record.sectionPath else {
throw AttachmentError.noSectionPath
}
// Fetch the section data from IMAP
try await imapClient.connect()
let data = try await imapClient.fetchSection(uid: messageUid, mailbox: mailboxName, section: sectionPath)
try? await imapClient.disconnect()
// Build cache directory
let cacheDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
.appendingPathComponent("MagnumOpus")
.appendingPathComponent(accountConfig.id)
.appendingPathComponent("attachments")
try FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true)
// Determine file extension from filename
let ext = record.filename.flatMap { URL(fileURLWithPath: $0).pathExtension } ?? "bin"
let fileName = "\(attachmentId).\(ext)"
let fileURL = cacheDir.appendingPathComponent(fileName)
// Write data to disk
try data.write(to: fileURL)
// Update cache path in DB
try store.updateAttachmentCachePath(id: attachmentId, cachePath: fileURL.path)
return fileURL
}
public enum AttachmentError: Error {
case notFound
case noSectionPath
}
// MARK: - Periodic Sync
public func startPeriodicSync(interval: Duration = .seconds(300)) {