From f9b8aa833dd1a8dddd24709a9c3ef2d9545b0ac6 Mon Sep 17 00:00:00 2001 From: Oliver Wiese <oliver.wiese@fu-berlin.de> Date: Thu, 1 Oct 2020 17:20:40 +0200 Subject: [PATCH] new data model --- enzevalos_iphone.xcodeproj/project.pbxproj | 62 +++- enzevalos_iphone/Certificate.swift | 2 +- .../PersistentMail +CoreDataClass.swift | 4 +- enzevalos_iphone/UserData.swift | 2 +- .../persistentData/CoreAddress.swift | 23 ++ .../persistentData/CoreMail.swift | 49 +++ .../DataModel.xcdatamodel/contents | 21 ++ .../persistentData/DataProvider.swift | 291 ++++++++++++++++++ .../persistentData/PersistentDataError.swift | 19 ++ enzevalos_iphoneTests/CoreAddressTest.swift | 36 +++ enzevalos_iphoneTests/CoreMailTest.swift | 56 ++++ enzevalos_iphoneTests/GeneratedMocks.swift | 4 +- 12 files changed, 559 insertions(+), 10 deletions(-) create mode 100644 enzevalos_iphone/persistentData/CoreAddress.swift create mode 100644 enzevalos_iphone/persistentData/CoreMail.swift create mode 100644 enzevalos_iphone/persistentData/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents create mode 100644 enzevalos_iphone/persistentData/DataProvider.swift create mode 100644 enzevalos_iphone/persistentData/PersistentDataError.swift create mode 100644 enzevalos_iphoneTests/CoreAddressTest.swift create mode 100644 enzevalos_iphoneTests/CoreMailTest.swift diff --git a/enzevalos_iphone.xcodeproj/project.pbxproj b/enzevalos_iphone.xcodeproj/project.pbxproj index 546e8bc7..aa519c40 100644 --- a/enzevalos_iphone.xcodeproj/project.pbxproj +++ b/enzevalos_iphone.xcodeproj/project.pbxproj @@ -194,6 +194,13 @@ 47F867E02052B47C00AA832F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47F867DF2052B47C00AA832F /* Security.framework */; }; 47F867E22052B48E00AA832F /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 47F867E12052B48E00AA832F /* libz.tbd */; }; 47F867E42052B49800AA832F /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 47F867E32052B49800AA832F /* libbz2.tbd */; }; + 47FAE3072524AA4B005A1BCB /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE3062524AA4B005A1BCB /* DataProvider.swift */; }; + 47FAE30E2524AA97005A1BCB /* DataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE30C2524AA97005A1BCB /* DataModel.xcdatamodeld */; }; + 47FAE3122524BFDB005A1BCB /* PersistentDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE3112524BFDB005A1BCB /* PersistentDataError.swift */; }; + 47FAE31C2524C07B005A1BCB /* CoreMail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE31B2524C07B005A1BCB /* CoreMail.swift */; }; + 47FAE3222524C1C0005A1BCB /* CoreMailTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE3212524C1C0005A1BCB /* CoreMailTest.swift */; }; + 47FAE33F2524FAD3005A1BCB /* CoreAddressTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE33E2524FAD3005A1BCB /* CoreAddressTest.swift */; }; + 47FAE3492524FB58005A1BCB /* CoreAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE3482524FB58005A1BCB /* CoreAddress.swift */; }; 50F2E7D66366C779705987A7 /* Pods_enzevalos_iphoneUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF67EF30BB065CC9C0D17940 /* Pods_enzevalos_iphoneUITests.framework */; }; 676C2D3024321F8100B631B3 /* TyposquattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676C2D2F24321F8100B631B3 /* TyposquattingTests.swift */; }; 6789425F2430C3B300C746D1 /* MailComparison.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6789425E2430C3B300C746D1 /* MailComparison.swift */; }; @@ -645,6 +652,13 @@ 47F867DF2052B47C00AA832F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 47F867E12052B48E00AA832F /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 47F867E32052B49800AA832F /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; }; + 47FAE3062524AA4B005A1BCB /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = "<group>"; }; + 47FAE30D2524AA97005A1BCB /* DataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DataModel.xcdatamodel; sourceTree = "<group>"; }; + 47FAE3112524BFDB005A1BCB /* PersistentDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentDataError.swift; sourceTree = "<group>"; }; + 47FAE31B2524C07B005A1BCB /* CoreMail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMail.swift; sourceTree = "<group>"; }; + 47FAE3212524C1C0005A1BCB /* CoreMailTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMailTest.swift; sourceTree = "<group>"; }; + 47FAE33E2524FAD3005A1BCB /* CoreAddressTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreAddressTest.swift; sourceTree = "<group>"; }; + 47FAE3482524FB58005A1BCB /* CoreAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreAddress.swift; sourceTree = "<group>"; }; 48C250BB32BF11B683003BA1 /* Pods-enzevalos_iphone-enzevalos_iphoneUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-enzevalos_iphone-enzevalos_iphoneUITests.debug.xcconfig"; path = "../enzevalos_iphone_workspace/Pods/Target Support Files/Pods-enzevalos_iphone-enzevalos_iphoneUITests/Pods-enzevalos_iphone-enzevalos_iphoneUITests.debug.xcconfig"; sourceTree = "<group>"; }; 66E758F271CD65AB3E5FE7A7 /* Pods-enzevalos_iphoneUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-enzevalos_iphoneUITests.debug.xcconfig"; path = "../enzevalos_iphone_workspace/Pods/Target Support Files/Pods-enzevalos_iphoneUITests/Pods-enzevalos_iphoneUITests.debug.xcconfig"; sourceTree = "<group>"; }; 670159DF240FB4E800797FA5 /* enzevalos_iphone 9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "enzevalos_iphone 9.xcdatamodel"; sourceTree = "<group>"; }; @@ -1365,6 +1379,27 @@ path = private; sourceTree = "<group>"; }; + 47FAE3052524AA30005A1BCB /* persistentData */ = { + isa = PBXGroup; + children = ( + 47FAE3062524AA4B005A1BCB /* DataProvider.swift */, + 47FAE30C2524AA97005A1BCB /* DataModel.xcdatamodeld */, + 47FAE3112524BFDB005A1BCB /* PersistentDataError.swift */, + 47FAE31B2524C07B005A1BCB /* CoreMail.swift */, + 47FAE3482524FB58005A1BCB /* CoreAddress.swift */, + ); + path = persistentData; + sourceTree = "<group>"; + }; + 47FAE3202524C1A8005A1BCB /* PersistentDataTest */ = { + isa = PBXGroup; + children = ( + 47FAE3212524C1C0005A1BCB /* CoreMailTest.swift */, + 47FAE33E2524FAD3005A1BCB /* CoreAddressTest.swift */, + ); + name = PersistentDataTest; + sourceTree = "<group>"; + }; 6789425D2430C38800C746D1 /* Phishing */ = { isa = PBXGroup; children = ( @@ -1539,6 +1574,7 @@ A13526771D955BDF00D3BFE1 /* enzevalos_iphone */ = { isa = PBXGroup; children = ( + 47FAE3052524AA30005A1BCB /* persistentData */, 0EFEF0932417C08B00BB2FF7 /* C Helpers */, 6789425D2430C38800C746D1 /* Phishing */, 971D40482428C852002FCD31 /* gamification_2 */, @@ -1585,6 +1621,7 @@ A135268F1D955BE000D3BFE1 /* enzevalos_iphoneTests */ = { isa = PBXGroup; children = ( + 47FAE3202524C1A8005A1BCB /* PersistentDataTest */, 0ED9072F24338E3C008CF9D0 /* SMIMETests.swift */, 71DF0896242151E200162B74 /* phishing */, 97C5279D241A9F690030BBC9 /* authentication */, @@ -2264,6 +2301,7 @@ 4775D7AC243F18BC0052F2CC /* SecurityBriefingView.swift in Sources */, 971D404A2428C87E002FCD31 /* BadgeCaseView.swift in Sources */, 47C8225B24379EAE005BCE73 /* MessageViewMain.swift in Sources */, + 47FAE30E2524AA97005A1BCB /* DataModel.xcdatamodeld in Sources */, 8428A8651F436A11007649A5 /* BadgeCaseCollectionViewCell.swift in Sources */, 472F39811E1E5347009260FB /* Mail_Address+CoreDataClass.swift in Sources */, A1EB05821D95685B008659C1 /* CollectionDataDelegate.swift in Sources */, @@ -2331,6 +2369,7 @@ A1EB057A1D956829008659C1 /* ContactCell.swift in Sources */, A12FC23120221A1400196008 /* ExportInfoViewController.swift in Sources */, 4751C6EE233CA583006B2A4D /* DateExtension.swift in Sources */, + 47FAE31C2524C07B005A1BCB /* CoreMail.swift in Sources */, 477548DE21F5DABE000B22A8 /* MailServerConnectionError.swift in Sources */, 475DF47A1F0D54C9009D807F /* Folder+CoreDataProperties.swift in Sources */, 475B00431F7BB6D6006CDD41 /* PersistentKey+CoreDataProperties.swift in Sources */, @@ -2350,6 +2389,7 @@ 475B00421F7BB6D6006CDD41 /* PersistentKey+CoreDataClass.swift in Sources */, A10DAA5721F37600005D8BBB /* IntroInfoButton.swift in Sources */, 47A2A57223599D180013883D /* FeedbackButtonHelper.swift in Sources */, + 47FAE3072524AA4B005A1BCB /* DataProvider.swift in Sources */, 476406842416AA9100C7D426 /* TestOpenSSL.swift in Sources */, 3EC35F2420037651008BDF95 /* InvitationHelper.swift in Sources */, A1B49E6421E55ECD00ED86FC /* IntroPageViewController.swift in Sources */, @@ -2360,6 +2400,7 @@ A1EB05841D956867008659C1 /* TableViewDataDelegate.swift in Sources */, 8428A85E1F436A05007649A5 /* CircleView.swift in Sources */, A182182C21E5072200918A29 /* IntroDescriptionViewController.swift in Sources */, + 47FAE3492524FB58005A1BCB /* CoreAddress.swift in Sources */, 4775D7AA243F0E260052F2CC /* SimulatorData.swift in Sources */, F1C7AC821FED6473007629DB /* AboutViewController.swift in Sources */, 47C8225324379EAE005BCE73 /* AttachmentsViewMain.swift in Sources */, @@ -2421,6 +2462,7 @@ F12041FB1DA3FBF7002E4940 /* ListViewController.swift in Sources */, F18B445E1E7044B70080C041 /* FlipTransition.swift in Sources */, 47C8225E24379EAE005BCE73 /* ReadMainView.swift in Sources */, + 47FAE3122524BFDB005A1BCB /* PersistentDataError.swift in Sources */, 472F397E1E1D0B0B009260FB /* EnzevalosContact+CoreDataProperties.swift in Sources */, 4751C6FA23449699006B2A4D /* CryptoManagementViewController.swift in Sources */, 478154AC21FF6A9600A931EC /* Mailbot.swift in Sources */, @@ -2437,12 +2479,14 @@ 676C2D3024321F8100B631B3 /* TyposquattingTests.swift in Sources */, 8428A8831F436AC9007649A5 /* GamificationDataUnitTest.swift in Sources */, 71DF08982421520D00162B74 /* EmailStringExtensionTests.swift in Sources */, + 47FAE33F2524FAD3005A1BCB /* CoreAddressTest.swift in Sources */, 3EC35F302003838E008BDF95 /* InvitationTests.swift in Sources */, 474054982244D7A9007CF83B /* MailServerConfigurationTest.swift in Sources */, 678942632430C40600C746D1 /* MailComparisonTests.swift in Sources */, 479B5977206914BE00B3944D /* CryptoTests.swift in Sources */, A15D215F223BE6E4003E0CE0 /* MailTest.swift in Sources */, 0ED9073024338E3C008CF9D0 /* SMIMETests.swift in Sources */, + 47FAE3222524C1C0005A1BCB /* CoreMailTest.swift in Sources */, 47EABF0F2420C63600774A93 /* AuthenticationTests.swift in Sources */, 988C9C5D240D507A006213F0 /* UrlStringExtensionTests.swift in Sources */, 4715F637202A0248001BFFD0 /* CoreDataTests.swift in Sources */, @@ -2643,7 +2687,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-fprofile-instr-generate"; @@ -2699,7 +2743,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = "-fprofile-instr-generate"; SDKROOT = iphoneos; @@ -2746,7 +2790,7 @@ "$(SRCROOT)/enzevalos_iphone/OpenSSL/include", ); INFOPLIST_FILE = "enzevalos_iphone/PLists/enzevalos-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2825,7 +2869,7 @@ "$(SRCROOT)/enzevalos_iphone/OpenSSL/include", ); INFOPLIST_FILE = "enzevalos_iphone/PLists/enzevalos-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2989,6 +3033,16 @@ /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ + 47FAE30C2524AA97005A1BCB /* DataModel.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 47FAE30D2524AA97005A1BCB /* DataModel.xcdatamodel */, + ); + currentVersion = 47FAE30D2524AA97005A1BCB /* DataModel.xcdatamodel */; + path = DataModel.xcdatamodeld; + sourceTree = "<group>"; + versionGroupType = wrapper.xcdatamodel; + }; A135267F1D955BDF00D3BFE1 /* enzevalos_iphone.xcdatamodeld */ = { isa = XCVersionGroup; children = ( diff --git a/enzevalos_iphone/Certificate.swift b/enzevalos_iphone/Certificate.swift index 5ad3a33b..6231d85a 100644 --- a/enzevalos_iphone/Certificate.swift +++ b/enzevalos_iphone/Certificate.swift @@ -32,7 +32,7 @@ class Certificate { self.hasPrivateKey = pem.contains("PRIVATE KEY") - let (fingerPrint, _, errors) = getFingerprintFromPem(pem: pem) + let (fingerPrint, _, _) = getFingerprintFromPem(pem: pem) // if it crashes on this line, you can loop through the errors and see what's wrong self.fingerPrint = fingerPrint! diff --git a/enzevalos_iphone/PersistentMail +CoreDataClass.swift b/enzevalos_iphone/PersistentMail +CoreDataClass.swift index 4764e26c..170ee700 100644 --- a/enzevalos_iphone/PersistentMail +CoreDataClass.swift +++ b/enzevalos_iphone/PersistentMail +CoreDataClass.swift @@ -103,7 +103,7 @@ open class PersistentMail: NSManagedObject, Mail { } else { flag.insert(MCOMessageFlag.seen) } - _ = DataHandler.handler.save(during: "set read flag") + DataHandler.handler.save(during: "set read flag") } } @@ -121,7 +121,7 @@ open class PersistentMail: NSManagedObject, Mail { } else { flag.insert(MCOMessageFlag.answered) } - _ = DataHandler.handler.save(during: "set answer flag") + DataHandler.handler.save(during: "set answer flag") } } diff --git a/enzevalos_iphone/UserData.swift b/enzevalos_iphone/UserData.swift index c86d1ea2..39cf6eeb 100644 --- a/enzevalos_iphone/UserData.swift +++ b/enzevalos_iphone/UserData.swift @@ -200,7 +200,7 @@ struct UserManager { return attribute.defaultValue } else { - _ = storeUserValue(attribute.defaultValue, attribute: attribute) + storeUserValue(attribute.defaultValue, attribute: attribute) return attribute.defaultValue } } diff --git a/enzevalos_iphone/persistentData/CoreAddress.swift b/enzevalos_iphone/persistentData/CoreAddress.swift new file mode 100644 index 00000000..2693b4c6 --- /dev/null +++ b/enzevalos_iphone/persistentData/CoreAddress.swift @@ -0,0 +1,23 @@ +// +// CoreAddress.swift +// enzevalos_iphone +// +// Created by Oliver Wiese on 30.09.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +extension CoreAddress { + func update(addressProperties: AddressProperties){ + email = addressProperties.email + } +} + +struct AddressProperties { + let email: String + + var dictionary: [String: Any] { + var dic = [String: Any]() + dic["email"] = email + return dic + } +} diff --git a/enzevalos_iphone/persistentData/CoreMail.swift b/enzevalos_iphone/persistentData/CoreMail.swift new file mode 100644 index 00000000..d33f5fff --- /dev/null +++ b/enzevalos_iphone/persistentData/CoreMail.swift @@ -0,0 +1,49 @@ +// +// CoreMail.swift +// enzevalos_iphone +// +// Created by Oliver Wiese on 30.09.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +/** + Managed object subclass extension for the Quake entity. + */ +extension CoreMail { + + func update(with mailProperties: MailProperties) { + messageID = mailProperties.messageID + subject = mailProperties.subject + } +} + +/** + A struct encapsulating the properties of a E-Mail. + TODO: Folder, routing information, gmailMessageID, gmailThreadID, ThreadID, signatureKey, decryptionKey, attachments, autocrypt, xMailer + + What about attached keys (public, secret keys) + */ +struct MailProperties { + + // Header properties + let messageID: String + let subject: String + let date: Date + let flags: Int16 + + // Content properties + var body: String = "" + + // Security properties + let signatureState: Int16 + let encryptionState: Int16 + + + + var dictionary: [String: Any] { + var dic = [String: Any]() + dic["messageID"] = messageID + dic["subject"] = subject + return dic + } +} diff --git a/enzevalos_iphone/persistentData/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents b/enzevalos_iphone/persistentData/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents new file mode 100644 index 00000000..07e1b266 --- /dev/null +++ b/enzevalos_iphone/persistentData/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17192" systemVersion="19H2" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> + <entity name="CoreAddress" representedClassName="CoreAddress" syncable="YES" codeGenerationType="class"> + <attribute name="email" attributeType="String"/> + <relationship name="inFromField" toMany="YES" deletionRule="Nullify" destinationEntity="CoreMail" inverseName="fromAddress" inverseEntity="CoreMail"/> + </entity> + <entity name="CoreMail" representedClassName="CoreMail" syncable="YES" codeGenerationType="class"> + <attribute name="messageID" attributeType="String"/> + <attribute name="subject" optional="YES" attributeType="String"/> + <relationship name="fromAddress" maxCount="1" deletionRule="Nullify" destinationEntity="CoreAddress" inverseName="inFromField" inverseEntity="CoreAddress"/> + <uniquenessConstraints> + <uniquenessConstraint> + <constraint value="messageID"/> + </uniquenessConstraint> + </uniquenessConstraints> + </entity> + <elements> + <element name="CoreMail" positionX="-54" positionY="-9" width="128" height="88"/> + <element name="CoreAddress" positionX="-36" positionY="27" width="128" height="73"/> + </elements> +</model> \ No newline at end of file diff --git a/enzevalos_iphone/persistentData/DataProvider.swift b/enzevalos_iphone/persistentData/DataProvider.swift new file mode 100644 index 00000000..f701c9ec --- /dev/null +++ b/enzevalos_iphone/persistentData/DataProvider.swift @@ -0,0 +1,291 @@ +// +// DataProvider.swift +// enzevalos_iphone +// +// Created by Oliver Wiese on 30.09.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +import CoreData +let MAILNAME = "CoreMail" + + +/* + One Entity needs a name, type, a struct with dict and a update function with the struct... + */ + +public class DataProvider { + + let fastImport = false + + // MARK: - Core Data + + /** + A persistent container to set up the Core Data stack. + */ + lazy var persistentContainer: NSPersistentContainer = { + let container = NSPersistentContainer(name: "DataModel") + + // Enable remote notifications + guard let description = container.persistentStoreDescriptions.first else { + fatalError("Failed to retrieve a persistent store description.") + } + description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) + + container.loadPersistentStores { storeDesription, error in + guard error == nil else { + fatalError("Unresolved error \(error!)") + } + } + + // This sample refreshes UI by refetching data, so doesn't need to merge the changes. + container.viewContext.automaticallyMergesChangesFromParent = false + container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + container.viewContext.undoManager = nil + container.viewContext.shouldDeleteInaccessibleFaults = true + + // Observe Core Data remote change notifications. + NotificationCenter.default.addObserver( + self, selector: #selector(type(of: self).storeRemoteChange(_:)), + name: .NSPersistentStoreRemoteChange, object: nil) + return container + }() + + /** + Creates and configures a private queue context. + */ + private func newTaskContext() -> NSManagedObjectContext { + // Create a private queue context. + let taskContext = persistentContainer.newBackgroundContext() + taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + // Set unused undoManager to nil for macOS (it is nil by default on iOS) + // to reduce resource requirements. + taskContext.undoManager = nil + return taskContext + } + + + func importMails(from emails: [MailProperties], completionHandler: @escaping (Error?) -> Void, withBIR: Bool = true) { + guard !emails.isEmpty else { + completionHandler(nil) + return + } + var performError: Error? = nil + + DispatchQueue.global(qos: .background).async { + do { + if withBIR { + try self.importMailsUsingBIR(from: emails) + } + else { + try self.importEmailsBeforeBIR(from: emails) + } + } catch { + performError = error + } + DispatchQueue.main.asyncAfter(deadline: .now(), execute: { + completionHandler(performError) + }) + + } + } + + + /** + Uses NSBatchInsertRequest (BIR) to import a list of emails into the Core Data store on a private queue. + NSBatchInsertRequest is available since iOS 13 and macOS 10.15. + */ + private func importMailsUsingBIR(from emails: [MailProperties]) throws { + var performError: Error? + + let taskContext = self.newTaskContext() + taskContext.performAndWait { + let batchInsert = self.newBatchInsertRequest(with: emails) + batchInsert.resultType = .statusOnly + + if let batchInsertResult = try? taskContext.execute(batchInsert) as? NSBatchInsertResult, + let success = batchInsertResult.result as? Bool, success { + return + } + performError = PersistentDataError.batchInsertError + } + if let error = performError { + throw error + } + } + + private func newBatchInsertRequest(with mails: [MailProperties]) -> NSBatchInsertRequest { + let batchInsert: NSBatchInsertRequest + var index = 0 + let total = mails.count + batchInsert = NSBatchInsertRequest(entityName: MAILNAME, dictionaryHandler: { dictionary in + guard index < total else { return true } + dictionary.addEntries(from: mails[index].dictionary) + index += 1 + return false + }) + return batchInsert + } + + /** + Imports a JSON dictionary into the Core Data store on a private queue, + processing the record in batches to avoid a high memory footprint. + */ + private func importEmailsBeforeBIR(from emails: [MailProperties]) throws { + guard !emails.isEmpty else { return } + + // Process records in batches to avoid a high memory footprint. + let batchSize = 256 + let count = emails.count + + // Determine the total number of batches. + var numBatches = count / batchSize + numBatches += count % batchSize > 0 ? 1 : 0 + + for batchNumber in 0 ..< numBatches { + // Determine the range for this batch. + let batchStart = batchNumber * batchSize + let batchEnd = batchStart + min(batchSize, count - batchNumber * batchSize) + let range = batchStart..<batchEnd + + // Create a batch for this range from the decoded JSON. + // Stop importing if any batch is unsuccessful. + try importOneBatch(Array(emails[range])) + } + } + + /** + Imports one batch of quakes, creating managed objects from the new data, + and saving them to the persistent store, on a private queue. After saving, + resets the context to clean up the cache and lower the memory footprint. + + NSManagedObjectContext.performAndWait doesn't rethrow so this function + catches throws within the closure and uses a return value to indicate + whether the import is successful. + */ + private func importOneBatch(_ emails: [MailProperties]) throws { + let taskContext = newTaskContext() + var performError: Error? + + // taskContext.performAndWait runs on the URLSession's delegate queue + // so it won’t block the main thread. + taskContext.performAndWait { + // Create a new record for each quake in the batch. + for m in emails { + // Create a Quake managed object on the private queue context. + guard let email = NSEntityDescription.insertNewObject(forEntityName: MAILNAME, into: taskContext) as? CoreMail else { + performError = PersistentDataError.creationError + return + } + email.update(with: m) + } + + // Save all insertions and deletions from the context to the store. + if taskContext.hasChanges { + do { + try taskContext.save() + } catch { + performError = error + return + } + // Reset the taskContext to free the cache and lower the memory footprint. + taskContext.reset() + } + } + + if let error = performError { + throw error + } + } + + /** + Deletes all the records in the Core Data store. + */ + func deleteAll(completionHandler: @escaping (Error?) -> Void) { + let taskContext = newTaskContext() + taskContext.perform { + let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: MAILNAME) + let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + batchDeleteRequest.resultType = .resultTypeCount + + // Execute the batch insert + if let batchDeleteResult = try? taskContext.execute(batchDeleteRequest) as? NSBatchDeleteResult, + batchDeleteResult.result != nil { + completionHandler(nil) + + } else { + completionHandler(PersistentDataError.batchDeleteError) + } + } + } + + // MARK: - NSFetchedResultsController + + /** + A fetched results controller delegate to give consumers a chance to update + the user interface when content changes. + */ + weak var fetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate? + + /** + A fetched results controller to fetch Quake records sorted by time. + */ + lazy var fetchedResultsController: NSFetchedResultsController<CoreMail> = { + + // Create a fetch request for the Quake entity sorted by time. + let fetchRequest = NSFetchRequest<CoreMail>(entityName: MAILNAME) + // Create a fetched results controller and set its fetch request, context, and delegate. + fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageID", ascending: false)] + fetchRequest.propertiesToFetch = ["messageID", "subject"] + let controller = NSFetchedResultsController(fetchRequest: fetchRequest, + managedObjectContext: persistentContainer.viewContext, + sectionNameKeyPath: nil, cacheName: nil) + controller.delegate = fetchedResultsControllerDelegate + + // Perform the fetch. + do { + try controller.performFetch() + } catch { + fatalError("Unresolved error \(error)") + } + return controller + }() + + /** + Resets viewContext and refetches the data from the store. + */ + func resetAndRefetch() { + persistentContainer.viewContext.reset() + do { + try fetchedResultsController.performFetch() + } catch { + fatalError("Unresolved error \(error)") + } + } + + // MARK: - NSPersistentStoreRemoteChange handler + + /** + Handles remote store change notifications (.NSPersistentStoreRemoteChange). + storeRemoteChange runs on the queue where the changes were made. + */ + @objc + func storeRemoteChange(_ notification: Notification) { + //print("\(#function): Got a persistent store remote change notification!") + } +} + +extension DispatchQueue { + + static func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) { + DispatchQueue.global(qos: .background).async { + background?() + if let completion = completion { + DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: { + completion() + }) + } + } + } + +} diff --git a/enzevalos_iphone/persistentData/PersistentDataError.swift b/enzevalos_iphone/persistentData/PersistentDataError.swift new file mode 100644 index 00000000..134b0cee --- /dev/null +++ b/enzevalos_iphone/persistentData/PersistentDataError.swift @@ -0,0 +1,19 @@ +// +// PersistentDataError.swift +// enzevalos_iphone +// +// Created by Oliver Wiese on 30.09.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +import Foundation + +enum PersistentDataError: Error { + case urlError + case networkUnavailable + case wrongDataFormat + case missingData + case creationError + case batchInsertError + case batchDeleteError +} diff --git a/enzevalos_iphoneTests/CoreAddressTest.swift b/enzevalos_iphoneTests/CoreAddressTest.swift new file mode 100644 index 00000000..4f6aaf27 --- /dev/null +++ b/enzevalos_iphoneTests/CoreAddressTest.swift @@ -0,0 +1,36 @@ +// +// CoreAddressTest.swift +// enzevalos_iphoneTests +// +// Created by Oliver Wiese on 30.09.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +import XCTest + +@testable import enzevalos_iphone +class CoreAddressTest: XCTestCase { + var provider = DataProvider() + + override func setUpWithError() throws { + provider.deleteAll(completionHandler: {_ in }) + } + + override func tearDownWithError() throws { + provider.deleteAll(completionHandler: {_ in }) + } + + func testCreateAddress() throws { + let addr1 = AddressProperties(email: "vic@example.com") + + let addresses = [addr1] + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/enzevalos_iphoneTests/CoreMailTest.swift b/enzevalos_iphoneTests/CoreMailTest.swift new file mode 100644 index 00000000..79574a89 --- /dev/null +++ b/enzevalos_iphoneTests/CoreMailTest.swift @@ -0,0 +1,56 @@ +// +// CoreMailTest.swift +// enzevalos_iphoneTests +// +// Created by Oliver Wiese on 30.09.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +import XCTest + +@testable import enzevalos_iphone +class CoreMailTest: XCTestCase { + + var provider = DataProvider() + + override func setUpWithError() throws { + provider.deleteAll(completionHandler: {_ in }) + } + + override func tearDownWithError() throws { + provider.deleteAll(completionHandler: {_ in }) + } + + func testImport(withBIR: Bool) { + let n = 100 + + var msgs = [MailProperties]() + for i in 1...n { + let m = MailProperties(messageID: "\(i)", subject: "MSG\(i)", date: Date(), flags: 0, body: "THIS IS MSG\(i)", signatureState: 0, encryptionState: 0) + msgs.append(m) + } + + let m = MailProperties(messageID: "1", subject: "MSG1", date: Date(), flags: 0, body: "THIS IS MY SECOND MSG1", signatureState: 0, encryptionState: 0) + msgs.append(m) + provider.resetAndRefetch() + XCTAssertEqual(provider.fetchedResultsController.fetchedObjects?.count ?? 0, 0, "Too much messages! \(provider.fetchedResultsController.fetchedObjects?.count ?? 0)") + let importExpectation = expectation(description: "Import new data!") + provider.importMails(from: msgs, completionHandler: {error in + if let error = error { + XCTFail("Error while importing messages! \(error)") + } + importExpectation.fulfill() + }, withBIR: withBIR) + wait(for: [importExpectation], timeout: TimeInterval(20)) + provider.resetAndRefetch() + XCTAssertEqual(provider.fetchedResultsController.fetchedObjects?.count ?? 0, n, "Missing message! \(provider.fetchedResultsController.fetchedObjects?.count ?? 0)") + } + + func testImportWithBIR() { + testImport(withBIR: true) + } + + func testImportBeforeBIR() { + testImport(withBIR: false) + } +} diff --git a/enzevalos_iphoneTests/GeneratedMocks.swift b/enzevalos_iphoneTests/GeneratedMocks.swift index ca02ed66..eaaae2dd 100644 --- a/enzevalos_iphoneTests/GeneratedMocks.swift +++ b/enzevalos_iphoneTests/GeneratedMocks.swift @@ -1,4 +1,4 @@ -// MARK: - Mocks generated from file: enzevalos_iphone/AuthenticationModel.swift at 2020-04-28 19:16:47 +0000 +// MARK: - Mocks generated from file: enzevalos_iphone/AuthenticationModel.swift at 2020-09-30 17:29:22 +0000 // // AuthenticationModel.swift @@ -654,7 +654,7 @@ import Foundation } -// MARK: - Mocks generated from file: enzevalos_iphone/AuthenticationViewModel.swift at 2020-04-28 19:16:47 +0000 +// MARK: - Mocks generated from file: enzevalos_iphone/AuthenticationViewModel.swift at 2020-09-30 17:29:22 +0000 // // AuthenticationViewModel.swift -- GitLab