fix sync: INSERT OR IGNORE for duplicate messages, fix leaked IMAP continuations
- insertMessages uses INSERT OR IGNORE on (mailboxId, uid) conflict instead of crashing on UNIQUE constraint when messages are re-fetched after restart - IMAPResponseHandler.sendCommand resumes any leaked previous continuation before registering a new one, preventing DuplicateCommandTag errors - add channelInactive handler to resume pending continuations on connection drop - add error type to sync failure log for better diagnostics Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -207,8 +207,10 @@ final class MailViewModel {
|
||||
try await coordinator.syncNow()
|
||||
syncState = coordinator.syncState
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
syncState = .error(error.localizedDescription)
|
||||
let desc = "\(type(of: error)): \(error.localizedDescription)"
|
||||
print("[MailViewModel] syncNow failed: \(desc)")
|
||||
errorMessage = desc
|
||||
syncState = .error(desc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,15 @@ final class IMAPResponseHandler: ChannelInboundHandler, RemovableChannelHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func channelInactive(context: ChannelHandlerContext) {
|
||||
let error = IMAPError.notConnected
|
||||
continuation?.resume(throwing: error)
|
||||
continuation = nil
|
||||
greetingContinuation?.resume(throwing: error)
|
||||
greetingContinuation = nil
|
||||
context.fireChannelInactive()
|
||||
}
|
||||
|
||||
func errorCaught(context: ChannelHandlerContext, error: Error) {
|
||||
continuation?.resume(throwing: error)
|
||||
continuation = nil
|
||||
@@ -68,6 +77,11 @@ final class IMAPResponseHandler: ChannelInboundHandler, RemovableChannelHandler,
|
||||
}
|
||||
|
||||
func sendCommand(tag: String, continuation cont: CheckedContinuation<[Response], Error>) {
|
||||
// Resume any leaked continuation from a previous command to avoid
|
||||
// "SWIFT TASK CONTINUATION MISUSE: leaked its continuation"
|
||||
if let old = continuation {
|
||||
old.resume(throwing: IMAPError.serverError("Previous command interrupted"))
|
||||
}
|
||||
expectedTag = tag
|
||||
continuation = cont
|
||||
buffer = []
|
||||
|
||||
@@ -58,7 +58,8 @@ public final class MailStore: Sendable {
|
||||
public func insertMessages(_ messages: [MessageRecord]) throws {
|
||||
try dbWriter.write { db in
|
||||
for message in messages {
|
||||
try message.save(db)
|
||||
// INSERT OR IGNORE: skip if (mailboxId, uid) already exists from a prior sync
|
||||
try message.insert(db, onConflict: .ignore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user