IOS-1480 Move PersistentImapFolder. IOS-1480
authorDirk Zimmermann <dz@pep.security>
Thu, 21 Feb 2019 13:42:30 +0100
branchIOS-1480
changeset 1299a7ad6c937402
parent 1298 cc003fb005c5
child 1300 544f09d52789
IOS-1480 Move PersistentImapFolder.
MessageModel/MessageModel.xcodeproj/project.pbxproj
MessageModel/MessageModel/NetworkService/Model/PersistentImapFolder.swift
     1.1 --- a/MessageModel/MessageModel.xcodeproj/project.pbxproj	Thu Feb 21 13:42:30 2019 +0100
     1.2 +++ b/MessageModel/MessageModel.xcodeproj/project.pbxproj	Thu Feb 21 13:42:30 2019 +0100
     1.3 @@ -45,6 +45,7 @@
     1.4  		43AE48F41EEFECD400B92BB6 /* NSMergeConflict+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AE48F31EEFECD400B92BB6 /* NSMergeConflict+Extension.swift */; };
     1.5  		43AE48F81EF01EE900B92BB6 /* NSManagedObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AE48F71EF01EE900B92BB6 /* NSManagedObject+Extension.swift */; };
     1.6  		43B1B105221EB6C900DB26AB /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B1B104221EB6C900DB26AB /* Log.swift */; };
     1.7 +		43B1B10C221ED15000DB26AB /* PersistentImapFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B1B10B221ED15000DB26AB /* PersistentImapFolder.swift */; };
     1.8  		43C32E261DBF9C69007CFB1A /* MessageModelDelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CB60A21DA4EBB60015281E /* MessageModelDelegates.swift */; };
     1.9  		43CB60911DA4EB4E0015281E /* MessageModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CB60901DA4EB4E0015281E /* MessageModelTests.swift */; };
    1.10  		43CB60931DA4EB4E0015281E /* MessageModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 43CB60851DA4EB4E0015281E /* MessageModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
    1.11 @@ -227,6 +228,7 @@
    1.12  		43AE48F31EEFECD400B92BB6 /* NSMergeConflict+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMergeConflict+Extension.swift"; sourceTree = "<group>"; };
    1.13  		43AE48F71EF01EE900B92BB6 /* NSManagedObject+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Extension.swift"; sourceTree = "<group>"; };
    1.14  		43B1B104221EB6C900DB26AB /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
    1.15 +		43B1B10B221ED15000DB26AB /* PersistentImapFolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentImapFolder.swift; sourceTree = "<group>"; };
    1.16  		43CB60821DA4EB4E0015281E /* MessageModel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessageModel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
    1.17  		43CB60851DA4EB4E0015281E /* MessageModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MessageModel.h; sourceTree = "<group>"; };
    1.18  		43CB60861DA4EB4E0015281E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
    1.19 @@ -683,6 +685,7 @@
    1.20  		43EA4831221E9518006E8F83 /* Model */ = {
    1.21  			isa = PBXGroup;
    1.22  			children = (
    1.23 +				43B1B10B221ED15000DB26AB /* PersistentImapFolder.swift */,
    1.24  				43EA4832221E9518006E8F83 /* UnifiedInbox.swift */,
    1.25  			);
    1.26  			path = Model;
    1.27 @@ -1010,6 +1013,7 @@
    1.28  				43EA4892221E9518006E8F83 /* ErrorPropagator.swift in Sources */,
    1.29  				43EA485F221E9518006E8F83 /* KickOffMySelfProtocol.swift in Sources */,
    1.30  				43EA488F221E9518006E8F83 /* FetchNumberOfNewMailsService.swift in Sources */,
    1.31 +				43B1B10C221ED15000DB26AB /* PersistentImapFolder.swift in Sources */,
    1.32  				43B1B105221EB6C900DB26AB /* Log.swift in Sources */,
    1.33  				43EA4850221E9518006E8F83 /* Folder+VirtualMailbox.swift in Sources */,
    1.34  				43EA485E221E9518006E8F83 /* MiscUtil.swift in Sources */,
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/MessageModel/MessageModel/NetworkService/Model/PersistentImapFolder.swift	Thu Feb 21 13:42:30 2019 +0100
     2.3 @@ -0,0 +1,319 @@
     2.4 +//
     2.5 +//  PersistentImapFolder.swift
     2.6 +//  pEpForiOS
     2.7 +//
     2.8 +//  Created by Dirk Zimmermann on 18/04/16.
     2.9 +//  Copyright © 2016 p≡p Security S.A. All rights reserved.
    2.10 +//
    2.11 +
    2.12 +import CoreData
    2.13 +import pEpIOSToolbox
    2.14 +import MessageModel
    2.15 +
    2.16 +/**
    2.17 + A `CWFolder`/`CWIMAPFolder` that is backed by core data. Use on the main thread.
    2.18 + */
    2.19 +class PersistentImapFolder: CWIMAPFolder {
    2.20 +    let accountID: NSManagedObjectID
    2.21 +    let folderID: NSManagedObjectID
    2.22 +
    2.23 +    /** The underlying core data object. Only use from the internal context. */
    2.24 +    var folder: CdFolder
    2.25 +
    2.26 +    let backgroundQueue: OperationQueue
    2.27 +
    2.28 +    let logName: String
    2.29 +
    2.30 +    let privateMOC: NSManagedObjectContext
    2.31 +
    2.32 +    override var nextUID: UInt {
    2.33 +        get {
    2.34 +            var uid: UInt = 0
    2.35 +            privateMOC.performAndWait({
    2.36 +                uid = UInt(self.folder.uidNext)
    2.37 +            })
    2.38 +            return uid
    2.39 +        }
    2.40 +        set {
    2.41 +            privateMOC.performAndWait({
    2.42 +                self.folder.uidNext = NSNumber(value: newValue).int64Value
    2.43 +                self.privateMOC.saveAndLogErrors()
    2.44 +            })
    2.45 +        }
    2.46 +    }
    2.47 +
    2.48 +     override var existsCount: UInt {
    2.49 +        get {
    2.50 +            var count: UInt = 0
    2.51 +            privateMOC.performAndWait({
    2.52 +                count = UInt(self.folder.existsCount)
    2.53 +            })
    2.54 +            return count
    2.55 +        }
    2.56 +        set {
    2.57 +            privateMOC.performAndWait({
    2.58 +                self.folder.existsCount = NSNumber(value: newValue).int64Value
    2.59 +                self.privateMOC.saveAndLogErrors()
    2.60 +            })
    2.61 +        }
    2.62 +    }
    2.63 +
    2.64 +    let messageFetchedBlock: MessageFetchedBlock?
    2.65 +
    2.66 +    init?(name: String, accountID: NSManagedObjectID, backgroundQueue: OperationQueue,
    2.67 +          logName: String? = #function, messageFetchedBlock: MessageFetchedBlock? = nil) {
    2.68 +        self.accountID = accountID
    2.69 +        self.backgroundQueue = backgroundQueue
    2.70 +        self.logName = "PersistentImapFolder (\(logName ?? "<unknown>"))"
    2.71 +        self.messageFetchedBlock = messageFetchedBlock
    2.72 +        let context = Record.Context.background
    2.73 +        self.privateMOC = context
    2.74 +
    2.75 +        if let f = PersistentImapFolder.folderObject(
    2.76 +            context: context, logName: logName, name: name, accountID: accountID) {
    2.77 +            self.folder = f
    2.78 +            self.folderID = f.objectID
    2.79 +        } else {
    2.80 +            return nil
    2.81 +        }
    2.82 +        super.init(name: name)
    2.83 +        self.setCacheManager(self)
    2.84 +    }
    2.85 +
    2.86 +    func functionName(_ name: String) -> String {
    2.87 +        return PersistentImapFolder.functionName(logName: logName, functionName: name)
    2.88 +    }
    2.89 +
    2.90 +    static func functionName(logName: String? = #function, functionName: String) -> String {
    2.91 +        if let ln = logName {
    2.92 +            return "\(ln): \(functionName)"
    2.93 +        } else {
    2.94 +            return functionName
    2.95 +        }
    2.96 +    }
    2.97 +
    2.98 +    static func folderObject(context: NSManagedObjectContext,
    2.99 +                             logName: String? = #function,
   2.100 +                             name: String,
   2.101 +                             accountID: NSManagedObjectID) -> CdFolder? {
   2.102 +        var folder: CdFolder? = nil
   2.103 +        context.performAndWait() {
   2.104 +            guard let account = context.object(with: accountID)
   2.105 +                as? CdAccount else {
   2.106 +                    Logger.backendLogger.error(
   2.107 +                        "Given objectID is not an account: %{public}@",
   2.108 +                        accountID.description)
   2.109 +                    return
   2.110 +            }
   2.111 +            if let (fo, _) = CdFolder.insertOrUpdate(
   2.112 +                folderName: name, folderSeparator: nil, folderType: nil, account: account) {
   2.113 +                context.saveAndLogErrors()
   2.114 +                folder = fo
   2.115 +            }
   2.116 +        }
   2.117 +        return folder
   2.118 +    }
   2.119 +
   2.120 +    override func allMessages() -> [Any] {
   2.121 +        var result = [Any]()
   2.122 +        privateMOC.performAndWait({
   2.123 +            if let messages = CdMessage.all(
   2.124 +                predicate: self.folder.allMessagesIncludingDeletedPredicate()) {
   2.125 +                for m in messages {
   2.126 +                    result.append(m)
   2.127 +                }
   2.128 +            }
   2.129 +        })
   2.130 +        return result
   2.131 +    }
   2.132 +
   2.133 +    /**
   2.134 +     This implementation assumes that the index is typically referred to by pantomime
   2.135 +     as the messageNumber.
   2.136 +     Relying on that is dangerous and should be avoided.
   2.137 +     */
   2.138 +    override func message(at theIndex: UInt) -> CWMessage? {
   2.139 +        var result: CWMessage?
   2.140 +        privateMOC.performAndWait({
   2.141 +            let isNotFake = CdMessage.PredicateFactory.isNotFakeMessage()
   2.142 +            let msgAtIdx = NSPredicate(
   2.143 +                format: "parent = %@ and imap.messageNumber = %d", self.folder, theIndex)
   2.144 +            let p = NSCompoundPredicate(andPredicateWithSubpredicates: [isNotFake,
   2.145 +                                                                        msgAtIdx])
   2.146 +            let msg = CdMessage.first(predicate: p)
   2.147 +            result = msg?.pantomimeQuick(folder: self)
   2.148 +        })
   2.149 +        return result
   2.150 +    }
   2.151 +
   2.152 +    override func count() -> UInt {
   2.153 +        var count: Int = 0
   2.154 +        privateMOC.performAndWait({
   2.155 +            count = self.folder.allMessages().count
   2.156 +        })
   2.157 +        return UInt(count)
   2.158 +    }
   2.159 +
   2.160 +    override func lastMSN() -> UInt {
   2.161 +        var msn: UInt = 0
   2.162 +        privateMOC.performAndWait() {
   2.163 +            let lastUID = self.folder.lastUID()
   2.164 +            if let cwMsg = self.cwMessage(withUID: lastUID, context: self.privateMOC) {
   2.165 +                msn = cwMsg.messageNumber()
   2.166 +            }
   2.167 +        }
   2.168 +        return msn
   2.169 +    }
   2.170 +
   2.171 +    override func firstUID() -> UInt {
   2.172 +        var uid: UInt = 0
   2.173 +        privateMOC.performAndWait({
   2.174 +            uid = self.folder.firstUID()
   2.175 +        })
   2.176 +        return uid
   2.177 +    }
   2.178 +
   2.179 +    override func lastUID() -> UInt {
   2.180 +        var uid: UInt = 0
   2.181 +        privateMOC.performAndWait({
   2.182 +            uid = self.folder.lastUID()
   2.183 +        })
   2.184 +        return uid
   2.185 +    }
   2.186 +
   2.187 +    func cdMessage(withUID theUID: UInt, context: NSManagedObjectContext) -> CdMessage? {
   2.188 +        let pUid = NSPredicate(format: "uid = %d", theUID)
   2.189 +        let pFolder = NSPredicate(format: "parent = %@", self.folder)
   2.190 +        let p = NSCompoundPredicate(andPredicateWithSubpredicates: [pUid, pFolder])
   2.191 +
   2.192 +        return CdMessage.first(predicate: p)
   2.193 +    }
   2.194 +
   2.195 +    func cwMessage(withUID theUID: UInt, context: NSManagedObjectContext) -> CWIMAPMessage? {
   2.196 +        if let cdMsg = cdMessage(withUID: theUID, context: context),
   2.197 +            let cwFolder = folder.cwFolder() {
   2.198 +            return cdMsg.pantomimeQuick(folder: cwFolder)
   2.199 +        } else {
   2.200 +            return nil
   2.201 +        }
   2.202 +    }
   2.203 +
   2.204 +    override func remove(_ cwMessage: CWMessage) {
   2.205 +        if let cwImapMessage = cwMessage as? CWIMAPMessage {
   2.206 +            let uid = cwImapMessage.uid()
   2.207 +            removeMessage(withUID: uid)
   2.208 +        } else {
   2.209 +            Logger.backendLogger.log("Should remove/expunge message that is not a CWIMAPMessage")
   2.210 +        }
   2.211 +    }
   2.212 +
   2.213 +    override func matchUID(_ uid: UInt, withMSN msn: UInt) {
   2.214 +        super.matchUID(uid, withMSN: msn)
   2.215 +        let opMatch = MatchUidToMsnOperation(
   2.216 +            parentName: functionName(#function),
   2.217 +            folderID: folderID, uid: uid, msn: msn)
   2.218 +        backgroundQueue.addOperation(opMatch)
   2.219 +        // We might have feched a message soley to update its MSN, we rely on it, so we have to wait
   2.220 +        opMatch.waitUntilFinished()
   2.221 +    }
   2.222 +}
   2.223 +
   2.224 +//MARK: - CWCache
   2.225 +extension PersistentImapFolder: CWCache {
   2.226 +    func invalidate() {
   2.227 +        // if intentionally, please mark so
   2.228 +    }
   2.229 +
   2.230 +    func synchronize() -> Bool {
   2.231 +        return true
   2.232 +    }
   2.233 +}
   2.234 +
   2.235 +//MARK: - CWIMAPCache
   2.236 +extension PersistentImapFolder: CWIMAPCache {
   2.237 +    func message(withUID theUID: UInt) -> CWIMAPMessage? {
   2.238 +        var result: CWIMAPMessage?
   2.239 +        privateMOC.performAndWait {
   2.240 +            result = self.cwMessage(withUID: theUID, context: self.privateMOC)
   2.241 +        }
   2.242 +        return result
   2.243 +    }
   2.244 +
   2.245 +    func removeMessage(withUID: UInt) {
   2.246 +        privateMOC.performAndWait {
   2.247 +            if let cdMsg = self.cdMessage(withUID: withUID, context: self.privateMOC) {
   2.248 +                let cdFolder = cdMsg.parent
   2.249 +                let msn = cdMsg.imap?.messageNumber
   2.250 +                cdMsg.deleteAndInformDelegate(context: self.privateMOC)
   2.251 +                if let theCdFolder = cdFolder, let theMsn = msn {
   2.252 +                    let p1 = NSPredicate(format: "parent = %@ and imap.messageNumber > %d",
   2.253 +                                         theCdFolder, theMsn)
   2.254 +                    let cdMsgs = CdMessage.all(predicate: p1,
   2.255 +                                               in: self.privateMOC) as? [CdMessage] ?? []
   2.256 +                    for aCdMsg in cdMsgs {
   2.257 +                        let oldMsn = aCdMsg.imapFields().messageNumber
   2.258 +                        if oldMsn > 0 {
   2.259 +                            aCdMsg.imapFields().messageNumber = oldMsn - 1
   2.260 +                        }
   2.261 +                    }
   2.262 +                    Record.saveAndWait(context: privateMOC)
   2.263 +                }
   2.264 +            } else {
   2.265 +                Logger.backendLogger.log("Could not find message by UID for expunging.")
   2.266 +            }
   2.267 +        }
   2.268 +    }
   2.269 +
   2.270 +    override func uidValidity() -> UInt {
   2.271 +        var i: Int32 = 0
   2.272 +        privateMOC.performAndWait({
   2.273 +            i = self.folder.uidValidity
   2.274 +        })
   2.275 +        return UInt(i)
   2.276 +    }
   2.277 +
   2.278 +    override func setUIDValidity(_ theUIDValidity: UInt) {
   2.279 +        guard let context = self.folder.managedObjectContext else {
   2.280 +            Logger.backendLogger.errorAndCrash("Dangling folder")
   2.281 +            return
   2.282 +        }
   2.283 +        context.performAndWait() {
   2.284 +            if self.folder.uidValidity != Int32(theUIDValidity) {
   2.285 +                Logger.backendLogger.warn(
   2.286 +                    "UIValidity changed, deleting all messages. %{public}@",
   2.287 +                    String(describing: self.folder.name))
   2.288 +                // For some reason messages are not deleted when removing it from folder
   2.289 +                // (even cascade is the delete rule). This causes crashes saving the context,
   2.290 +                // as it holds invalid messages that have no parent folder.
   2.291 +                // That is why we are deleting the messages manually.
   2.292 +                if let messages =  self.folder.messages?.allObjects as? [CdMessage] {
   2.293 +                    for cdMessage in messages  {
   2.294 +                        cdMessage.deleteAndInformDelegate(context: context)
   2.295 +                    }
   2.296 +                }
   2.297 +                self.folder.uidValidity = Int32(theUIDValidity)
   2.298 +                context.saveAndLogErrors()
   2.299 +            }
   2.300 +        }
   2.301 +    }
   2.302 +
   2.303 +    public func write(_ theRecord: CWCacheRecord?, message: CWIMAPMessage,
   2.304 +                      messageUpdate: CWMessageUpdate) {
   2.305 +        let opStore = StorePrefetchedMailOperation(
   2.306 +            parentName: functionName(#function),
   2.307 +            accountID: accountID, message: message, messageUpdate: messageUpdate,
   2.308 +            messageFetchedBlock: messageFetchedBlock)
   2.309 +        let opID = unsafeBitCast(opStore, to: UnsafeRawPointer.self)
   2.310 +        Logger.backendLogger.warn("Writing message %{public}@, %{public}@ for %{public}@",
   2.311 +                                  message,
   2.312 +                                  messageUpdate,
   2.313 +                                  String(describing: opID))
   2.314 +        backgroundQueue.addOperation(opStore)
   2.315 +
   2.316 +        // While it would be desirable to store messages asynchronously,
   2.317 +        // it's not the correct semantics pantomime, and therefore the layers above, expect.
   2.318 +        // It might correctly work in-app, but can mess up the unit tests since they might signal
   2.319 +        // "finish" before all messages have been stored.
   2.320 +        opStore.waitUntilFinished()
   2.321 +    }
   2.322 +}