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:
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user