diff --git a/.gitmodules b/.gitmodules index 4bee5f39..32520cec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,3 @@ [submodule "External/FontReplacer"] path = External/FontReplacer url = git://github.com/0xced/FontReplacer.git -[submodule "External/facebook-ios-sdk"] - path = External/facebook-ios-sdk - url = https://github.com/facebook/facebook-ios-sdk diff --git a/External/iCloudStoreManager b/External/iCloudStoreManager index b7480927..39ac68e0 160000 --- a/External/iCloudStoreManager +++ b/External/iCloudStoreManager @@ -1 +1 @@ -Subproject commit b748092775b2ee07c4c494434b43c72fb589ab67 +Subproject commit 39ac68e0fb34f0c6e2f455f132b56fd158630ee7 diff --git a/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword-iOS.xcodeproj/project.pbxproj index 8a16a04e..d396c6c9 100644 --- a/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -129,13 +129,15 @@ DA609F5C1600CE980030AE31 /* LocalyticsUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = DA609F551600CE980030AE31 /* LocalyticsUploader.h */; }; DA609F5D1600CE980030AE31 /* LocalyticsUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = DA609F561600CE980030AE31 /* LocalyticsUploader.m */; }; DA609F5E1600CE980030AE31 /* WebserviceConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = DA609F571600CE980030AE31 /* WebserviceConstants.h */; }; - DA6701B816406A4100B61001 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701B716406A4100B61001 /* Accounts.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - DA6701BA16406B0600B61001 /* FacebookSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701B916406B0600B61001 /* FacebookSDK.framework */; }; - DA6701DC16406B3600B61001 /* FacebookSDKResources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DA6701DB16406B3600B61001 /* FacebookSDKResources.bundle */; }; - DA6701DE16406B7300B61001 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DD16406B7300B61001 /* Social.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - DA6701E016406BB400B61001 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DF16406BB400B61001 /* AdSupport.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + DA6701B816406A4100B61001 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701B716406A4100B61001 /* Accounts.framework */; settings = {ATTRIBUTES = (Required, ); }; }; + DA6701DE16406B7300B61001 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DD16406B7300B61001 /* Social.framework */; settings = {ATTRIBUTES = (Required, ); }; }; + DA6701E016406BB400B61001 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DF16406BB400B61001 /* AdSupport.framework */; settings = {ATTRIBUTES = (Required, ); }; }; DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; }; DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; + DA81253516B8546A00F4732F /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81253416B8546A00F4732F /* MPElementEntity.m */; }; + DA81253816B8546B00F4732F /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81253716B8546B00F4732F /* MPElementStoredEntity.m */; }; + DA81253B16B8546B00F4732F /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81253A16B8546B00F4732F /* MPUserEntity.m */; }; + DA81253E16B8546C00F4732F /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81253D16B8546C00F4732F /* MPElementGeneratedEntity.m */; }; DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DA829E6015984813002417D3 /* UIFont+Replacement.h in Headers */ = {isa = PBXBuildFile; fileRef = DA829E5E15984812002417D3 /* UIFont+Replacement.h */; }; DA829E6115984813002417D3 /* UIFont+Replacement.m in Sources */ = {isa = PBXBuildFile; fileRef = DA829E5F15984812002417D3 /* UIFont+Replacement.m */; }; @@ -179,10 +181,6 @@ DA95D5F614DF0B9F008D1B94 /* IASKPSTextFieldSpecifierViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CC14DF0691008D1B94 /* IASKPSTextFieldSpecifierViewCell.xib */; }; DA95D5F714DF0B9F008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */; }; DA95D5F814DF0B9F008D1B94 /* IASKSpecifierValuesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */; }; - DAA096FC15E0C59B00912D63 /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA096FB15E0C59B00912D63 /* MPElementEntity.m */; }; - DAA096FF15E0C59B00912D63 /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA096FE15E0C59B00912D63 /* MPElementGeneratedEntity.m */; }; - DAA0970215E0C59B00912D63 /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA0970115E0C59B00912D63 /* MPElementStoredEntity.m */; }; - DAA0970515E0C59B00912D63 /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA0970415E0C59B00912D63 /* MPUserEntity.m */; }; DAB8D45D15036BCF00CED3BC /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */; }; DAB8D45E15036BCF00CED3BC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D43F15036BCF00CED3BC /* InfoPlist.strings */; }; DAB8D45F15036BCF00CED3BC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D44115036BCF00CED3BC /* main.m */; }; @@ -1163,44 +1161,20 @@ DA609F561600CE980030AE31 /* LocalyticsUploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalyticsUploader.m; sourceTree = ""; }; DA609F571600CE980030AE31 /* WebserviceConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebserviceConstants.h; sourceTree = ""; }; DA6701B716406A4100B61001 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; }; - DA6701B916406B0600B61001 /* FacebookSDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FacebookSDK.framework; path = "External/facebook-ios-sdk/build/FacebookSDK.framework"; sourceTree = ""; }; - DA6701BC16406B1C00B61001 /* Facebook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Facebook.h; sourceTree = ""; }; - DA6701BD16406B1C00B61001 /* FacebookSDK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookSDK.h; sourceTree = ""; }; - DA6701BE16406B1C00B61001 /* FBCacheDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBCacheDescriptor.h; sourceTree = ""; }; - DA6701BF16406B1C00B61001 /* FBConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBConnect.h; sourceTree = ""; }; - DA6701C016406B1C00B61001 /* FBDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBDialog.h; sourceTree = ""; }; - DA6701C116406B1C00B61001 /* FBError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBError.h; sourceTree = ""; }; - DA6701C216406B1C00B61001 /* FBFrictionlessRequestSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFrictionlessRequestSettings.h; sourceTree = ""; }; - DA6701C316406B1C00B61001 /* FBFriendPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFriendPickerViewController.h; sourceTree = ""; }; - DA6701C416406B1C00B61001 /* FBGraphLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphLocation.h; sourceTree = ""; }; - DA6701C516406B1C00B61001 /* FBGraphObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphObject.h; sourceTree = ""; }; - DA6701C616406B1C00B61001 /* FBGraphPlace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphPlace.h; sourceTree = ""; }; - DA6701C716406B1C00B61001 /* FBGraphUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphUser.h; sourceTree = ""; }; - DA6701C816406B1C00B61001 /* FBLoginDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBLoginDialog.h; sourceTree = ""; }; - DA6701C916406B1C00B61001 /* FBLoginView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBLoginView.h; sourceTree = ""; }; - DA6701CA16406B1C00B61001 /* FBNativeDialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBNativeDialogs.h; sourceTree = ""; }; - DA6701CB16406B1C00B61001 /* FBOpenGraphAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBOpenGraphAction.h; sourceTree = ""; }; - DA6701CC16406B1C00B61001 /* FBPlacePickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBPlacePickerViewController.h; sourceTree = ""; }; - DA6701CD16406B1C00B61001 /* FBProfilePictureView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBProfilePictureView.h; sourceTree = ""; }; - DA6701CE16406B1C00B61001 /* FBRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBRequest.h; sourceTree = ""; }; - DA6701CF16406B1C00B61001 /* FBRequestConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBRequestConnection.h; sourceTree = ""; }; - DA6701D016406B1C00B61001 /* FBSBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSBJSON.h; sourceTree = ""; }; - DA6701D116406B1C00B61001 /* FBSBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSBJsonBase.h; sourceTree = ""; }; - DA6701D216406B1C00B61001 /* FBSBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSBJsonParser.h; sourceTree = ""; }; - DA6701D316406B1C00B61001 /* FBSBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSBJsonWriter.h; sourceTree = ""; }; - DA6701D416406B1C00B61001 /* FBSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSession.h; sourceTree = ""; }; - DA6701D516406B1C00B61001 /* FBSessionManualTokenCachingStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSessionManualTokenCachingStrategy.h; sourceTree = ""; }; - DA6701D616406B1C00B61001 /* FBSessionTokenCachingStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSessionTokenCachingStrategy.h; sourceTree = ""; }; - DA6701D716406B1C00B61001 /* FBSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSettings.h; sourceTree = ""; }; - DA6701D816406B1C00B61001 /* FBTestSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBTestSession.h; sourceTree = ""; }; - DA6701D916406B1C00B61001 /* FBUserSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBUserSettingsViewController.h; sourceTree = ""; }; - DA6701DA16406B1C00B61001 /* FBViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBViewController.h; sourceTree = ""; }; - DA6701DB16406B3600B61001 /* FacebookSDKResources.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = FacebookSDKResources.bundle; path = "External/facebook-ios-sdk/build/FacebookSDK.framework/Versions/A/Resources/FacebookSDKResources.bundle"; sourceTree = ""; }; DA6701DD16406B7300B61001 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; DA6701DF16406BB400B61001 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios.a"; sourceTree = ""; }; DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = ""; }; + DA81252E16B8544400F4732F /* MasterPassword 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 4.xcdatamodel"; sourceTree = ""; }; + DA81253316B8546A00F4732F /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = ""; }; + DA81253416B8546A00F4732F /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = ""; }; + DA81253616B8546B00F4732F /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = ""; }; + DA81253716B8546B00F4732F /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = ""; }; + DA81253916B8546B00F4732F /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = ""; }; + DA81253A16B8546B00F4732F /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = ""; }; + DA81253C16B8546C00F4732F /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = ""; }; + DA81253D16B8546C00F4732F /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = ""; }; DA829E51159847E0002417D3 /* libFontReplacer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFontReplacer.a; sourceTree = BUILT_PRODUCTS_DIR; }; DA829E5E15984812002417D3 /* UIFont+Replacement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+Replacement.h"; sourceTree = ""; }; DA829E5F15984812002417D3 /* UIFont+Replacement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+Replacement.m"; sourceTree = ""; }; @@ -1243,14 +1217,6 @@ DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKPSToggleSwitchSpecifierViewCell.xib; sourceTree = ""; }; DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = ""; }; DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; - DAA096FA15E0C59B00912D63 /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = ""; }; - DAA096FB15E0C59B00912D63 /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = ""; }; - DAA096FD15E0C59B00912D63 /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = ""; }; - DAA096FE15E0C59B00912D63 /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = ""; }; - DAA0970015E0C59B00912D63 /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = ""; }; - DAA0970115E0C59B00912D63 /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = ""; }; - DAA0970315E0C59B00912D63 /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = ""; }; - DAA0970415E0C59B00912D63 /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = ""; }; DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; DAB8D43D15036BCF00CED3BC /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = ""; }; DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -2119,7 +2085,6 @@ DAD3126715528C9C00A3F9ED /* Crashlytics.framework in Frameworks */, 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */, DA4ECA2B160D94A80012ABB9 /* libTestFlight.a in Frameworks */, - DA6701BA16406B0600B61001 /* FacebookSDK.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2177,16 +2142,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - DA3EE95B1601C06000C68F6D /* Facebook */ = { - isa = PBXGroup; - children = ( - DA6701DB16406B3600B61001 /* FacebookSDKResources.bundle */, - DA6701BB16406B1C00B61001 /* DeprecatedHeaders */, - DA6701B916406B0600B61001 /* FacebookSDK.framework */, - ); - name = Facebook; - sourceTree = ""; - }; DA3EF17E15A47744003ABF4E /* Tests */ = { isa = PBXGroup; children = ( @@ -2355,7 +2310,6 @@ DAD3126115528C9C00A3F9ED /* TestFlight */, DAD3127315528CD200A3F9ED /* Localytics */, DA5587F715E8B7B200860B4F /* Google+ */, - DA3EE95B1601C06000C68F6D /* Facebook */, DA4425D71557BF260052177D /* iCloudStoreManager */, DA829E5D15984812002417D3 /* FontReplacer */, DA3EF17E15A47744003ABF4E /* Tests */, @@ -2419,15 +2373,15 @@ 93D398E394E311C545E0A057 /* MPAlgorithm.h */, DA0E07941577FE490008A67E /* MPEntities.h */, DA0E07951577FE490008A67E /* MPEntities.m */, + DA81253316B8546A00F4732F /* MPElementEntity.h */, + DA81253C16B8546C00F4732F /* MPElementGeneratedEntity.h */, + DA81253D16B8546C00F4732F /* MPElementGeneratedEntity.m */, + DA81253916B8546B00F4732F /* MPUserEntity.h */, + DA81253A16B8546B00F4732F /* MPUserEntity.m */, + DA81253616B8546B00F4732F /* MPElementStoredEntity.h */, + DA81253716B8546B00F4732F /* MPElementStoredEntity.m */, + DA81253416B8546A00F4732F /* MPElementEntity.m */, DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */, - DAA096FA15E0C59B00912D63 /* MPElementEntity.h */, - DAA096FB15E0C59B00912D63 /* MPElementEntity.m */, - DAA096FD15E0C59B00912D63 /* MPElementGeneratedEntity.h */, - DAA096FE15E0C59B00912D63 /* MPElementGeneratedEntity.m */, - DAA0970015E0C59B00912D63 /* MPElementStoredEntity.h */, - DAA0970115E0C59B00912D63 /* MPElementStoredEntity.m */, - DAA0970315E0C59B00912D63 /* MPUserEntity.h */, - DAA0970415E0C59B00912D63 /* MPUserEntity.m */, DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */, DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */, DA4426041557C1990052177D /* MPAppDelegate_Shared.h */, @@ -2441,45 +2395,6 @@ path = MasterPassword; sourceTree = ""; }; - DA6701BB16406B1C00B61001 /* DeprecatedHeaders */ = { - isa = PBXGroup; - children = ( - DA6701BC16406B1C00B61001 /* Facebook.h */, - DA6701BD16406B1C00B61001 /* FacebookSDK.h */, - DA6701BE16406B1C00B61001 /* FBCacheDescriptor.h */, - DA6701BF16406B1C00B61001 /* FBConnect.h */, - DA6701C016406B1C00B61001 /* FBDialog.h */, - DA6701C116406B1C00B61001 /* FBError.h */, - DA6701C216406B1C00B61001 /* FBFrictionlessRequestSettings.h */, - DA6701C316406B1C00B61001 /* FBFriendPickerViewController.h */, - DA6701C416406B1C00B61001 /* FBGraphLocation.h */, - DA6701C516406B1C00B61001 /* FBGraphObject.h */, - DA6701C616406B1C00B61001 /* FBGraphPlace.h */, - DA6701C716406B1C00B61001 /* FBGraphUser.h */, - DA6701C816406B1C00B61001 /* FBLoginDialog.h */, - DA6701C916406B1C00B61001 /* FBLoginView.h */, - DA6701CA16406B1C00B61001 /* FBNativeDialogs.h */, - DA6701CB16406B1C00B61001 /* FBOpenGraphAction.h */, - DA6701CC16406B1C00B61001 /* FBPlacePickerViewController.h */, - DA6701CD16406B1C00B61001 /* FBProfilePictureView.h */, - DA6701CE16406B1C00B61001 /* FBRequest.h */, - DA6701CF16406B1C00B61001 /* FBRequestConnection.h */, - DA6701D016406B1C00B61001 /* FBSBJSON.h */, - DA6701D116406B1C00B61001 /* FBSBJsonBase.h */, - DA6701D216406B1C00B61001 /* FBSBJsonParser.h */, - DA6701D316406B1C00B61001 /* FBSBJsonWriter.h */, - DA6701D416406B1C00B61001 /* FBSession.h */, - DA6701D516406B1C00B61001 /* FBSessionManualTokenCachingStrategy.h */, - DA6701D616406B1C00B61001 /* FBSessionTokenCachingStrategy.h */, - DA6701D716406B1C00B61001 /* FBSettings.h */, - DA6701D816406B1C00B61001 /* FBTestSession.h */, - DA6701D916406B1C00B61001 /* FBUserSettingsViewController.h */, - DA6701DA16406B1C00B61001 /* FBViewController.h */, - ); - name = DeprecatedHeaders; - path = "External/facebook-ios-sdk/build/FacebookSDK.framework/Versions/A/DeprecatedHeaders"; - sourceTree = ""; - }; DA79A9BE1557DDC700BAA07A /* Products */ = { isa = PBXGroup; children = ( @@ -3830,7 +3745,6 @@ isa = PBXNativeTarget; buildConfigurationList = DA5BFA6D147E415C00F98B1E /* Build configuration list for PBXNativeTarget "MasterPassword" */; buildPhases = ( - DA55878315E82C8500860B4F /* Run Script: FacebookSDK */, DA5BFA40147E415C00F98B1E /* Sources */, DA5BFA41147E415C00F98B1E /* Frameworks */, DA5BFA42147E415C00F98B1E /* Resources */, @@ -4698,7 +4612,6 @@ DA3EE946160145C700C68F6D /* Default-568h.png in Resources */, DA3EE947160145C700C68F6D /* Default-568h@2x.png in Resources */, DA4ECA2E160D94A80012ABB9 /* TestFlight.plist in Resources */, - DA6701DC16406B3600B61001 /* FacebookSDKResources.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4718,23 +4631,6 @@ shellPath = /bin/sh; shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; }; - DA55878315E82C8500860B4F /* Run Script: FacebookSDK */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/External/facebook-ios-sdk/src", - ); - name = "Run Script: FacebookSDK"; - outputPaths = ( - "$(SRCROOT)/External/facebook-ios-sdk/build", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "env -i PATH=\"$PATH\" ./External/facebook-ios-sdk/scripts/build_framework.sh"; - showEnvVarsInLog = 0; - }; DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -4747,7 +4643,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/bin/bash -e"; - shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --always --dirty --long)\nbuild=${description%-g*} build=${build//-/.}\ntag=${description%%-*}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n"; + shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" 2>/dev/null || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --always --dirty --long)\nbuild=${description%-g*} build=${build//-/.}\ntag=${description%%-*}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n"; showEnvVarsInLog = 0; }; DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */ = { @@ -4845,12 +4741,12 @@ 93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */, 93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */, 93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */, - DAA096FC15E0C59B00912D63 /* MPElementEntity.m in Sources */, - DAA096FF15E0C59B00912D63 /* MPElementGeneratedEntity.m in Sources */, - DAA0970215E0C59B00912D63 /* MPElementStoredEntity.m in Sources */, - DAA0970515E0C59B00912D63 /* MPUserEntity.m in Sources */, 93D39BCE5F69D8EBE7E9F6EC /* MPAppViewController.m in Sources */, 93D399B873AF89808151D2F5 /* MPAppsViewController.m in Sources */, + DA81253516B8546A00F4732F /* MPElementEntity.m in Sources */, + DA81253816B8546B00F4732F /* MPElementStoredEntity.m in Sources */, + DA81253B16B8546B00F4732F /* MPUserEntity.m in Sources */, + DA81253E16B8546C00F4732F /* MPElementGeneratedEntity.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5290,11 +5186,9 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Crashlytics\"", - "\"$(SRCROOT)/External/facebook-ios-sdk/build\"", ); GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/TestFlight\"", @@ -5315,11 +5209,9 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Crashlytics\"", - "\"$(SRCROOT)/External/facebook-ios-sdk/build\"", ); GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/TestFlight\"", @@ -5447,11 +5339,9 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Crashlytics\"", - "\"$(SRCROOT)/External/facebook-ios-sdk/build\"", ); GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/TestFlight\"", @@ -5744,11 +5634,12 @@ DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DA81252E16B8544400F4732F /* MasterPassword 4.xcdatamodel */, DAF83D6B15E02E04009C8D49 /* MasterPassword 3.xcdatamodel */, DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */, DAB8D43D15036BCF00CED3BC /* MasterPassword 1.xcdatamodel */, ); - currentVersion = DAF83D6B15E02E04009C8D49 /* MasterPassword 3.xcdatamodel */; + currentVersion = DA81252E16B8544400F4732F /* MasterPassword 4.xcdatamodel */; path = MasterPassword.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword (Development).xcscheme b/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword (Development).xcscheme index fab2b241..33ed0f7c 100644 --- a/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword (Development).xcscheme +++ b/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword (Development).xcscheme @@ -50,14 +50,13 @@ -+ (NSManagedObjectContext *)managedObjectContextIfReady; -- (NSManagedObjectContext *)managedObjectContextIfReady; - -- (UbiquityStoreManager *)storeManager; -- (void)saveContext; ++ (NSManagedObjectContext *)managedObjectContextForThreadIfReady; ++ (BOOL)managedObjectContextPerform:(void (^)(NSManagedObjectContext *moc))mocBlock; - (MPImportResult)importSites:(NSString *)importedSitesString askImportPassword:(NSString *(^)(NSString *userName))importPassword diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m index 20db1884..f27c448b 100644 --- a/MasterPassword/MPAppDelegate_Store.m +++ b/MasterPassword/MPAppDelegate_Store.m @@ -11,33 +11,67 @@ @implementation MPAppDelegate_Shared (Store) -static char managedObjectContextKey; +static char privateManagedObjectContextKey, mainManagedObjectContextKey; #pragma mark - Core Data setup -+ (NSManagedObjectContext *)managedObjectContextIfReady { ++ (NSManagedObjectContext *)managedObjectContextForThreadIfReady { - return [[self get] managedObjectContextIfReady]; + NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; + if ([[NSThread currentThread] isMainThread]) + return mainManagedObjectContext; + + NSManagedObjectContext *threadManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType]; + threadManagedObjectContext.parentContext = mainManagedObjectContext; + + return threadManagedObjectContext; } -- (NSManagedObjectContext *)managedObjectContextIfReady { ++ (BOOL)managedObjectContextPerform:(void (^)(NSManagedObjectContext *))mocBlock { - NSManagedObjectContext *managedObjectContext = objc_getAssociatedObject(self, &managedObjectContextKey); - if (!managedObjectContext) { - managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; - [managedObjectContext performBlockAndWait:^{ - managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; - managedObjectContext.persistentStoreCoordinator = self.storeManager.persistentStoreCoordinator; + NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; + if (!mainManagedObjectContext) + return NO; + + NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + moc.parentContext = mainManagedObjectContext; + [moc performBlock:^{ + mocBlock(moc); + }]; + + return YES; +} + +- (NSManagedObjectContext *)mainManagedObjectContextIfReady { + + if (![self privateManagedObjectContextIfReady]) + return nil; + + return objc_getAssociatedObject(self, &mainManagedObjectContextKey); +} + +- (NSManagedObjectContext *)privateManagedObjectContextIfReady { + + NSManagedObjectContext *privateManagedObjectContext = objc_getAssociatedObject(self, &privateManagedObjectContextKey); + if (!privateManagedObjectContext) { + privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + [privateManagedObjectContext performBlockAndWait:^{ + privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; + privateManagedObjectContext.persistentStoreCoordinator = self.storeManager.persistentStoreCoordinator; }]; - objc_setAssociatedObject(self, &managedObjectContextKey, managedObjectContext, OBJC_ASSOCIATION_RETAIN); + NSManagedObjectContext *mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; + mainManagedObjectContext.parentContext = privateManagedObjectContext; + + objc_setAssociatedObject(self, &privateManagedObjectContextKey, privateManagedObjectContext, OBJC_ASSOCIATION_RETAIN); + objc_setAssociatedObject(self, &mainManagedObjectContextKey, mainManagedObjectContext, OBJC_ASSOCIATION_RETAIN); } - if (![managedObjectContext.persistentStoreCoordinator.persistentStores count]) + if (![privateManagedObjectContext.persistentStoreCoordinator.persistentStores count]) // Store not available yet. return nil; - return managedObjectContext; + return privateManagedObjectContext; } - (void)migrateStoreForManager:(UbiquityStoreManager *)storeManager { @@ -48,68 +82,74 @@ static char managedObjectContextKey; return; if ([cloudEnabled boolValue]) { - NSURL *newCloudStoreURL = [storeManager URLForCloudStore]; - if ([[NSFileManager defaultManager] fileExistsAtPath:newCloudStoreURL.path isDirectory:NO]) - // New store already exists, migration has already been done. - return; + if ([storeManager cloudSafeForSeeding]) { + NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"]; + NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"]; + NSURL *newCloudStoreURL = [storeManager URLForCloudStore]; + NSURL *newCloudContentURL = [storeManager URLForCloudContent]; + //NSURL *oldCloudContentURL = [[cloudContainerURL URLByAppendingPathComponent:@"Data" isDirectory:YES] + // URLByAppendingPathComponent:uuid isDirectory:YES]; + NSURL *oldCloudStoreDirectoryURL = [cloudContainerURL URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES]; + NSURL *oldCloudStoreURL = [[oldCloudStoreDirectoryURL URLByAppendingPathComponent:uuid isDirectory:NO] + URLByAppendingPathExtension:@"sqlite"]; + if (![[NSFileManager defaultManager] fileExistsAtPath:oldCloudStoreURL.path isDirectory:NO]) { + // No old store to migrate from, cannot migrate. + wrn(@"Cannot migrate cloud store, old store not found at: %@", oldCloudStoreURL.path); + return; + } - NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"]; - NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"]; - NSURL *newCloudContentURL = [storeManager URLForCloudContent]; - //NSURL *oldCloudContentURL = [[cloudContainerURL URLByAppendingPathComponent:@"Data" isDirectory:YES] - // URLByAppendingPathComponent:uuid isDirectory:YES]; - NSURL *oldCloudStoreDirectoryURL = [cloudContainerURL URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES]; - NSURL *oldCloudStoreURL = [[oldCloudStoreDirectoryURL URLByAppendingPathComponent:uuid isDirectory:NO] - URLByAppendingPathExtension:@"sqlite"]; - if (![[NSFileManager defaultManager] fileExistsAtPath:oldCloudStoreURL.path isDirectory:NO]) { - // No old store to migrate from, cannot migrate. - wrn(@"Cannot migrate cloud store, old store not found at: %@", oldCloudStoreURL.path); - return; + NSError *error = nil; + NSDictionary *oldCloudStoreOptions = @{ + // This is here in an attempt to have iCloud recreate the old store file from + // the baseline and transaction logs from the iCloud account. + // In my tests however only the baseline was used to recreate the store which then ended up being empty. + /*NSPersistentStoreUbiquitousContentNameKey : uuid, + NSPersistentStoreUbiquitousContentURLKey : oldCloudContentURL,*/ + // So instead, we'll just open up the old store as read-only, if it exists. + NSReadOnlyPersistentStoreOption : @YES, + NSMigratePersistentStoresAutomaticallyOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES}; + NSDictionary *newCloudStoreOptions = @{ + NSPersistentStoreUbiquitousContentNameKey : [storeManager valueForKey:@"contentName"], + NSPersistentStoreUbiquitousContentURLKey : newCloudContentURL, + NSMigratePersistentStoresAutomaticallyOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES}; + + // Create the directory to hold the new cloud store. + // This is only necessary if we want to try to rebuild the old store. See comment above about how that failed. + //if (![[NSFileManager defaultManager] createDirectoryAtPath:oldCloudStoreDirectoryURL.path + // withIntermediateDirectories:YES attributes:nil error:&error]) + //err(@"While creating directory for old cloud store: %@", error); + if (![[NSFileManager defaultManager] createDirectoryAtPath:[storeManager URLForCloudStoreDirectory].path + withIntermediateDirectories:YES attributes:nil error:&error]) { + err(@"While creating directory for new cloud store: %@", error); + return; + } + + NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; + NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; + + // Open the old cloud store. + NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldCloudStoreURL + options:oldCloudStoreOptions error:&error]; + if (!oldStore) { + err(@"While opening old store for migration %@: %@", oldCloudStoreURL.path, error); + return; + } + + // Migrate to the new cloud store. + if (![psc migratePersistentStore:oldStore toURL:newCloudStoreURL options:newCloudStoreOptions withType:NSSQLiteStoreType + error:&error]) { + err(@"While migrating cloud store from %@ -> %@: %@", oldCloudStoreURL.path, newCloudStoreURL.path, error); + return; + } + + // Clean-up. + if (![psc removePersistentStore:[psc.persistentStores lastObject] error:&error]) + err(@"While removing the migrated store from the store context: %@", error); + if (![[NSFileManager defaultManager] removeItemAtURL:oldCloudStoreURL error:&error]) + err(@"While deleting the old cloud store: %@", error); } - - NSError *error = nil; - NSDictionary *oldCloudStoreOptions = @{ - // This is here in an attempt to have iCloud recreate the old store file from - // the baseline and transaction logs from the iCloud account. - // In my tests however only the baseline was used to recreate the store which then ended up being empty. - /*NSPersistentStoreUbiquitousContentNameKey : uuid, - NSPersistentStoreUbiquitousContentURLKey : oldCloudContentURL,*/ - // So instead, we'll just open up the old store as read-only, if it exists. - NSReadOnlyPersistentStoreOption : @YES, - NSMigratePersistentStoresAutomaticallyOption : @YES, - NSInferMappingModelAutomaticallyOption : @YES}; - NSDictionary *newCloudStoreOptions = @{ - NSPersistentStoreUbiquitousContentNameKey : [storeManager valueForKey:@"contentName"], - NSPersistentStoreUbiquitousContentURLKey : newCloudContentURL, - NSMigratePersistentStoresAutomaticallyOption : @YES, - NSInferMappingModelAutomaticallyOption : @YES}; - - // This is only necessary if we want to try to rebuild the old store. See comment above about how that failed. - //if (![[NSFileManager defaultManager] createDirectoryAtPath:oldCloudStoreDirectoryURL.path - // withIntermediateDirectories:YES attributes:nil error:&error]) - //err(@"While creating directory for old cloud store: %@", error); - if (![[NSFileManager defaultManager] createDirectoryAtPath:[storeManager URLForCloudStoreDirectory].path - withIntermediateDirectories:YES attributes:nil error:&error]) - err(@"While creating directory for new cloud store: %@", error); - - NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; - NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; - NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldCloudStoreURL - options:oldCloudStoreOptions error:&error]; - if (!oldStore) { - err(@"While opening old store for migration %@: %@", oldCloudStoreURL.path, error); - return; - } - - if (![psc migratePersistentStore:oldStore toURL:newCloudStoreURL options:newCloudStoreOptions withType:NSSQLiteStoreType - error:&error]) { - err(@"While migrating cloud store from %@ -> %@: %@", oldCloudStoreURL.path, newCloudStoreURL.path, error); - return; - } - if (![psc removePersistentStore:[psc.persistentStores lastObject] error:&error]) - err(@"While removing the migrated store from the store context: %@", error); - if (![[NSFileManager defaultManager] removeItemAtURL:oldCloudStoreURL error:&error]) - err(@"While deleting the old cloud store: %@", error); } else { NSURL *applicationFilesDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; @@ -124,16 +164,34 @@ static char managedObjectContextKey; NSInferMappingModelAutomaticallyOption : @YES}; NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; + + // Create the directory to hold the new local store. + if (![[NSFileManager defaultManager] createDirectoryAtPath:[storeManager URLForLocalStoreDirectory].path + withIntermediateDirectories:YES attributes:nil error:&error]) { + err(@"While creating directory for new local store: %@", error); + return; + } + + // Open the old local store. NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldLocalStoreURL options:options error:&error]; - if (oldStore) - [psc migratePersistentStore:oldStore toURL:newLocalStoreURL options:options withType:NSSQLiteStoreType error:&error]; - if (error) - err(@"While migrating local store from %@ -> %@: %@", oldLocalStoreURL, newLocalStoreURL, error); - else { - [psc removePersistentStore:[psc.persistentStores lastObject] error:nil]; - [[NSFileManager defaultManager] removeItemAtURL:oldLocalStoreURL error:nil]; + if (!oldStore) { + err(@"While opening old store for migration %@: %@", oldLocalStoreURL.path, error); + return; } + + // Migrate to the new local store. + if (![psc migratePersistentStore:oldStore toURL:newLocalStoreURL options:options withType:NSSQLiteStoreType error:&error]) { + err(@"While migrating local store from %@ -> %@: %@", oldLocalStoreURL, newLocalStoreURL, error); + return; + } + + // Clean-up. + if (![psc removePersistentStore:[psc.persistentStores lastObject] error:&error]) + err(@"While removing the migrated store from the store context: %@", error); + + if (![[NSFileManager defaultManager] removeItemAtURL:oldLocalStoreURL error:&error]) + err(@"While deleting the old local store: %@", error); } } [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"LocalUUIDKey"]; @@ -163,32 +221,49 @@ static char managedObjectContextKey; [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification object:storeManager queue:nil usingBlock:^(NSNotification *note) { - objc_setAssociatedObject(self, &managedObjectContextKey, nil, OBJC_ASSOCIATION_RETAIN); + objc_setAssociatedObject(self, &privateManagedObjectContextKey, nil, OBJC_ASSOCIATION_RETAIN); }]; + [[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:nil usingBlock: + ^(NSNotification *note) { + if ([[MPConfig get].iCloud boolValue] != [self.storeManager cloudEnabled]) + self.storeManager.cloudEnabled = [[MPConfig get].iCloud boolValue]; + }]; #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *note) { - [self saveContext]; + [self saveContexts]; + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification + object:[UIApplication sharedApplication] queue:nil + usingBlock:^(NSNotification *note) { + [self saveContexts]; }]; #else [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication] queue:nil usingBlock:^(NSNotification *note) { - [self saveContext]; + [self saveContexts]; }]; #endif return storeManager; } -- (void)saveContext { +- (void)saveContexts { - [self.managedObjectContextIfReady performBlock:^{ + NSManagedObjectContext *mainManagedObjectContext = objc_getAssociatedObject(self, &mainManagedObjectContextKey); + [mainManagedObjectContext performBlockAndWait:^{ NSError *error = nil; - if ([self.managedObjectContextIfReady hasChanges]) - if (![self.managedObjectContextIfReady save:&error]) - err(@"While saving context: %@", error); + if (![mainManagedObjectContext save:&error]) + err(@"While saving main context: %@", error); + }]; + + NSManagedObjectContext *privateManagedObjectContext = [self privateManagedObjectContextIfReady]; + [privateManagedObjectContext performBlockAndWait:^{ + NSError *error = nil; + if (![privateManagedObjectContext save:&error]) + err(@"While saving private context: %@", error); }]; } @@ -196,7 +271,7 @@ static char managedObjectContextKey; - (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm { - return self.managedObjectContextIfReady; + return [self privateManagedObjectContextIfReady]; } - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message { @@ -294,28 +369,34 @@ static char managedObjectContextKey; askImportPassword:(NSString *(^)(NSString *userName))importPassword askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword { - while (![self managedObjectContextIfReady]) - usleep((useconds_t)(USEC_PER_SEC * 0.2)); - - inf(@"Importing sites."); - + // Compile patterns. static NSRegularExpression *headerPattern, *sitePattern; - __block NSError *error = nil; + NSError *error = nil; if (!headerPattern) { headerPattern = [[NSRegularExpression alloc] initWithPattern:@"^#[[:space:]]*([^:]+): (.*)" options:0 error:&error]; - if (error) - err(@"Error loading the header pattern: %@", error); + if (error) { + err(@"Error loading the header pattern: %@", error); + return MPImportResultInternalError; + } } if (!sitePattern) { sitePattern = [[NSRegularExpression alloc] initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)" options:0 error:&error]; - if (error) - err(@"Error loading the site pattern: %@", error); + if (error) { + err(@"Error loading the site pattern: %@", error); + return MPImportResultInternalError; + } } - if (!headerPattern || !sitePattern) - return MPImportResultInternalError; + // Get a MOC. + NSAssert(![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread."); + NSManagedObjectContext *moc; + while (!(moc = [MPAppDelegate_Shared managedObjectContextForThreadIfReady])) + usleep((useconds_t)(USEC_PER_SEC * 0.2)); + + // Parse import data. + inf(@"Importing sites."); __block MPUserEntity *user = nil; id importAlgorithm = nil; NSString *importBundleVersion = nil, *importUserName = nil; @@ -354,10 +435,7 @@ static char managedObjectContextKey; NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName]; - __block NSArray *users = nil; - [self.managedObjectContextIfReady performBlockAndWait:^{ - users = [self.managedObjectContextIfReady executeFetchRequest:userFetchRequest error:&error]; - }]; + NSArray *users = [moc executeFetchRequest:userFetchRequest error:&error]; if (!users) { err(@"While looking for user: %@, error: %@", importUserName, error); return MPImportResultInternalError; @@ -407,10 +485,7 @@ static char managedObjectContextKey; // Find existing site. if (user) { elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user]; - __block NSArray *existingSites = nil; - [self.managedObjectContextIfReady performBlockAndWait:^{ - existingSites = [self.managedObjectContextIfReady executeFetchRequest:elementFetchRequest error:&error]; - }]; + NSArray *existingSites = [moc executeFetchRequest:elementFetchRequest error:&error]; if (!existingSites) { err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error); return MPImportResultInternalError; @@ -437,97 +512,77 @@ static char managedObjectContextKey; if ([importKey.keyID isEqualToData:importKeyID]) importKey = nil; - BOOL success = NO; - [self.managedObjectContextIfReady.undoManager beginUndoGrouping]; - @try { + // Delete existing sites. + if (elementsToDelete.count) + [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { + inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]); + [moc deleteObject:obj]; + }]; - // Delete existing sites. - if (elementsToDelete.count) - [self.managedObjectContextIfReady performBlockAndWait:^{ - [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { - inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]); - dbg(@"Deleted Element: %@", [obj debugDescription]); - [self.managedObjectContextIfReady deleteObject:obj]; - }]; - }]; + // Make sure there is a user. + if (!user) { + user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) + inManagedObjectContext:moc]; + user.name = importUserName; + user.keyID = importKeyID; + dbg(@"Created User: %@", [user debugDescription]); + } - // Make sure there is a user. - if (!user) { - [self.managedObjectContextIfReady performBlockAndWait:^{ - user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) - inManagedObjectContext:self.managedObjectContextIfReady]; - user.name = importUserName; - user.keyID = importKeyID; - }]; - dbg(@"Created User: %@", [user debugDescription]); - } - [self saveContext]; + // Import new sites. + for (NSArray *siteElements in importedSiteElements) { + NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]]; + NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue]; + MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue]; + NSUInteger version = (unsigned)[[siteElements objectAtIndex:3] integerValue]; + NSString *name = [siteElements objectAtIndex:4]; + NSString *exportContent = [siteElements objectAtIndex:5]; - // Import new sites. - for (NSArray *siteElements in importedSiteElements) { - NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]]; - NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue]; - MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue]; - NSUInteger version = (unsigned)[[siteElements objectAtIndex:3] integerValue]; - NSString *name = [siteElements objectAtIndex:4]; - NSString *exportContent = [siteElements objectAtIndex:5]; + // Create new site. + MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion( + version) classNameOfType:type] + inManagedObjectContext:moc]; + element.name = name; + element.user = user; + element.type = type; + element.uses = uses; + element.lastUsed = lastUsed; + element.version = version; + if ([exportContent length]) { + if (clearText) + [element importClearTextContent:exportContent usingKey:userKey]; + else { + if (!importKey) + importKey = [importAlgorithm keyForPassword:importPassword(user.name) ofUserNamed:user.name]; + if (![importKey.keyID isEqualToData:importKeyID]) + return MPImportResultInvalidPassword; - // Create new site. - __block MPImportResult result = MPImportResultSuccess; - [self.managedObjectContextIfReady performBlockAndWait:^{ - MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion( - version) classNameOfType:type] - inManagedObjectContext:self.managedObjectContextIfReady]; - element.name = name; - element.user = user; - element.type = type; - element.uses = uses; - element.lastUsed = lastUsed; - element.version = version; - if ([exportContent length]) { - if (clearText) - [element importClearTextContent:exportContent usingKey:userKey]; - else { - if (!importKey) - importKey = [importAlgorithm keyForPassword:importPassword(user.name) ofUserNamed:user.name]; - if (![importKey.keyID isEqualToData:importKeyID]) { - result = MPImportResultInvalidPassword; - return; - } - - [element importProtectedContent:exportContent protectedByKey:importKey usingKey:userKey]; - } - } - - dbg(@"Created Element: %@", [element debugDescription]); - }]; - if (result != MPImportResultSuccess) - return result; + [element importProtectedContent:exportContent protectedByKey:importKey usingKey:userKey]; + } } - [self saveContext]; - success = YES; - inf(@"Import completed successfully."); + dbg(@"Created Element: %@", [element debugDescription]); + } + + if (![moc save:&error]) { + err(@"While saving imported sites: %@", error); + return MPImportResultInternalError; + } + + inf(@"Import completed successfully."); #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointSitesImported]; + [TestFlight passCheckpoint:MPCheckpointSitesImported]; #endif #ifdef LOCALYTICS - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil]; #endif - return MPImportResultSuccess; - } - @finally { - [self.managedObjectContextIfReady.undoManager endUndoGrouping]; - - if (!success) - [self.managedObjectContextIfReady.undoManager undoNestedGroup]; - } + return MPImportResultSuccess; } - (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords { - inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", self.activeUser.userID); + MPUserEntity *activeUser = self.activeUser; + inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", activeUser.userID); // Header. NSMutableString *export = [NSMutableString new]; @@ -539,8 +594,8 @@ static char managedObjectContextKey; [export appendFormat:@"# \n"]; [export appendFormat:@"##\n"]; [export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion]; - [export appendFormat:@"# User Name: %@\n", self.activeUser.name]; - [export appendFormat:@"# Key ID: %@\n", [self.activeUser.keyID encodeHex]]; + [export appendFormat:@"# User Name: %@\n", activeUser.name]; + [export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]]; [export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]]; if (showPasswords) [export appendFormat:@"# Passwords: VISIBLE\n"]; @@ -552,7 +607,7 @@ static char managedObjectContextKey; [export appendFormat:@"# used used type name\tpassword\n"]; // Sites. - for (MPElementEntity *element in self.activeUser.elements) { + for (MPElementEntity *element in activeUser.elements) { NSDate *lastUsed = element.lastUsed; NSUInteger uses = element.uses; MPElementType type = element.type; diff --git a/MasterPassword/MPElementEntity.h b/MasterPassword/MPElementEntity.h index a1f94a55..3877ef1b 100644 --- a/MasterPassword/MPElementEntity.h +++ b/MasterPassword/MPElementEntity.h @@ -2,8 +2,8 @@ // MPElementEntity.h // MasterPassword-iOS // -// Created by Maarten Billemont on 2012-08-19. -// Copyright (c) 2012 Lyndir. All rights reserved. +// Created by Maarten Billemont on 2013-01-29. +// Copyright (c) 2013 Lyndir. All rights reserved. // #import diff --git a/MasterPassword/MPElementEntity.m b/MasterPassword/MPElementEntity.m index e91a237c..fecb52e7 100644 --- a/MasterPassword/MPElementEntity.m +++ b/MasterPassword/MPElementEntity.m @@ -2,11 +2,12 @@ // MPElementEntity.m // MasterPassword-iOS // -// Created by Maarten Billemont on 2012-08-19. -// Copyright (c) 2012 Lyndir. All rights reserved. +// Created by Maarten Billemont on 2013-01-29. +// Copyright (c) 2013 Lyndir. All rights reserved. // #import "MPElementEntity.h" +#import "MPUserEntity.h" @implementation MPElementEntity diff --git a/MasterPassword/MPElementGeneratedEntity.h b/MasterPassword/MPElementGeneratedEntity.h index a40a7658..294519fe 100644 --- a/MasterPassword/MPElementGeneratedEntity.h +++ b/MasterPassword/MPElementGeneratedEntity.h @@ -2,8 +2,8 @@ // MPElementGeneratedEntity.h // MasterPassword-iOS // -// Created by Maarten Billemont on 2012-08-19. -// Copyright (c) 2012 Lyndir. All rights reserved. +// Created by Maarten Billemont on 2013-01-29. +// Copyright (c) 2013 Lyndir. All rights reserved. // #import diff --git a/MasterPassword/MPElementGeneratedEntity.m b/MasterPassword/MPElementGeneratedEntity.m index d59f8de4..2c5f1d46 100644 --- a/MasterPassword/MPElementGeneratedEntity.m +++ b/MasterPassword/MPElementGeneratedEntity.m @@ -2,8 +2,8 @@ // MPElementGeneratedEntity.m // MasterPassword-iOS // -// Created by Maarten Billemont on 2012-08-19. -// Copyright (c) 2012 Lyndir. All rights reserved. +// Created by Maarten Billemont on 2013-01-29. +// Copyright (c) 2013 Lyndir. All rights reserved. // #import "MPElementGeneratedEntity.h" diff --git a/MasterPassword/MPElementStoredEntity.h b/MasterPassword/MPElementStoredEntity.h index 2a4f571c..6c4730cc 100644 --- a/MasterPassword/MPElementStoredEntity.h +++ b/MasterPassword/MPElementStoredEntity.h @@ -2,8 +2,8 @@ // MPElementStoredEntity.h // MasterPassword-iOS // -// Created by Maarten Billemont on 2012-08-19. -// Copyright (c) 2012 Lyndir. All rights reserved. +// Created by Maarten Billemont on 2013-01-29. +// Copyright (c) 2013 Lyndir. All rights reserved. // #import diff --git a/MasterPassword/MPElementStoredEntity.m b/MasterPassword/MPElementStoredEntity.m index 36441c54..24d23611 100644 --- a/MasterPassword/MPElementStoredEntity.m +++ b/MasterPassword/MPElementStoredEntity.m @@ -2,8 +2,8 @@ // MPElementStoredEntity.m // MasterPassword-iOS // -// Created by Maarten Billemont on 2012-08-19. -// Copyright (c) 2012 Lyndir. All rights reserved. +// Created by Maarten Billemont on 2013-01-29. +// Copyright (c) 2013 Lyndir. All rights reserved. // #import "MPElementStoredEntity.h" diff --git a/MasterPassword/MPEntities.h b/MasterPassword/MPEntities.h index b0829fb7..779da898 100644 --- a/MasterPassword/MPEntities.h +++ b/MasterPassword/MPEntities.h @@ -15,6 +15,12 @@ #define MPAvatarCount 19 +@interface NSManagedObject (MP) + +- (BOOL)saveContext; + +@end + @interface MPElementEntity (MP) @property (assign) MPElementType type; @@ -49,7 +55,6 @@ @property (assign) NSUInteger avatar; @property (assign) BOOL saveKey; @property (assign) MPElementType defaultType; -@property (assign) BOOL requiresExplicitMigration; @property (readonly) NSString *userID; + (NSString *)idFor:(NSString *)userName; diff --git a/MasterPassword/MPEntities.m b/MasterPassword/MPEntities.m index 7a49596b..974469ab 100644 --- a/MasterPassword/MPEntities.m +++ b/MasterPassword/MPEntities.m @@ -9,6 +9,22 @@ #import "MPEntities.h" #import "MPAppDelegate.h" +@implementation NSManagedObject (MP) + +- (BOOL)saveContext { + + NSError *error; + NSManagedObjectContext *moc = [self managedObjectContext]; + if (![moc save:&error]) { + err(@"While saving %@: %@", NSStringFromClass([self class]), error); + return NO; + } + + return YES; +} + +@end + @implementation MPElementEntity (MP) - (MPElementType)type { @@ -295,16 +311,6 @@ self.defaultType_ = @(aDefaultType); } -- (BOOL)requiresExplicitMigration { - - return [self.requiresExplicitMigration_ boolValue]; -} - -- (void)setRequiresExplicitMigration:(BOOL)requiresExplicitMigration { - - self.requiresExplicitMigration_ = @(requiresExplicitMigration); -} - - (NSString *)userID { return [MPUserEntity idFor:self.name]; diff --git a/MasterPassword/MPTypes.h b/MasterPassword/MPTypes.h index e6eb50e3..74489553 100644 --- a/MasterPassword/MPTypes.h +++ b/MasterPassword/MPTypes.h @@ -70,7 +70,8 @@ typedef enum { #define MPCheckpointAppGorillas @"MPCheckpointAppGorillas" #define MPCheckpointAppDeBlock @"MPCheckpointAppDeBlock" -#define MPNotificationSignedIn @"MPNotificationSignedIn" -#define MPNotificationSignedOut @"MPNotificationSignedOut" -#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten" -#define MPNotificationElementUpdated @"MPNotificationElementUpdated" +#define MPSignedInNotification @"MPSignedInNotification" +#define MPSignedOutNotification @"MPSignedOutNotification" +#define MPKeyForgottenNotification @"MPKeyForgottenNotification" +#define MPElementUpdatedNotification @"MPElementUpdatedNotification" +#define MPCheckConfigNotification @"MPCheckConfigNotification" diff --git a/MasterPassword/MPUserEntity.h b/MasterPassword/MPUserEntity.h index 8284c637..473980af 100644 --- a/MasterPassword/MPUserEntity.h +++ b/MasterPassword/MPUserEntity.h @@ -2,8 +2,8 @@ // MPUserEntity.h // MasterPassword-iOS // -// Created by Maarten Billemont on 2012-08-19. -// Copyright (c) 2012 Lyndir. All rights reserved. +// Created by Maarten Billemont on 2013-01-29. +// Copyright (c) 2013 Lyndir. All rights reserved. // #import @@ -18,7 +18,6 @@ @property (nonatomic, retain) NSData * keyID; @property (nonatomic, retain) NSDate * lastUsed; @property (nonatomic, retain) NSString * name; -@property (nonatomic, retain) NSNumber * requiresExplicitMigration_; @property (nonatomic, retain) NSNumber * saveKey_; @property (nonatomic, retain) NSSet *elements; @end diff --git a/MasterPassword/MPUserEntity.m b/MasterPassword/MPUserEntity.m index 60d190d9..5269aa19 100644 --- a/MasterPassword/MPUserEntity.m +++ b/MasterPassword/MPUserEntity.m @@ -2,11 +2,12 @@ // MPUserEntity.m // MasterPassword-iOS // -// Created by Maarten Billemont on 2012-08-19. -// Copyright (c) 2012 Lyndir. All rights reserved. +// Created by Maarten Billemont on 2013-01-29. +// Copyright (c) 2013 Lyndir. All rights reserved. // #import "MPUserEntity.h" +#import "MPElementEntity.h" @implementation MPUserEntity @@ -16,7 +17,6 @@ @dynamic keyID; @dynamic lastUsed; @dynamic name; -@dynamic requiresExplicitMigration_; @dynamic saveKey_; @dynamic elements; diff --git a/MasterPassword/Mac/MPAppDelegate.m b/MasterPassword/Mac/MPAppDelegate.m index 1558a4ec..842cdde8 100644 --- a/MasterPassword/Mac/MPAppDelegate.m +++ b/MasterPassword/Mac/MPAppDelegate.m @@ -74,7 +74,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [[self.usersItem submenu] removeItem:obj]; }]; - NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextIfReady]; + NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady]; if (!moc) { self.createUserItem.title = @"New User (Not ready)"; self.createUserItem.enabled = NO; @@ -87,38 +87,35 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven self.createUserItem.title = @"New User"; self.createUserItem.enabled = YES; self.createUserItem.toolTip = nil; - - [moc performBlockAndWait:^{ - NSArray *users = nil; - NSError *error = nil; - NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; - fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]]; - users = [moc executeFetchRequest:fetchRequest error:&error]; - if (!users) - err(@"Failed to load users: %@", error); + + NSError *error = nil; + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; + fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]]; + NSArray *users = [moc executeFetchRequest:fetchRequest error:&error]; + if (!users) + err(@"Failed to load users: %@", error); + + if (![users count]) { + NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""]; + noUsersItem.enabled = NO; + noUsersItem.toolTip = @"Use the iOS app to create users and make sure iCloud is enabled in its preferences as well. " + @"Then give iCloud some time to sync the new user to your Mac."; + } + + for (MPUserEntity *user in users) { + NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector(selectUser:) keyEquivalent:@""]; + [userItem setTarget:self]; + [userItem setRepresentedObject:[user objectID]]; + [[self.usersItem submenu] addItem:userItem]; - if (![users count]) { - NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""]; - noUsersItem.enabled = NO; - noUsersItem.toolTip = @"Use the iOS app to create users and make sure iCloud is enabled in its preferences as well. " - @"Then give iCloud some time to sync the new user to your Mac."; - } - - for (MPUserEntity *user in users) { - NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector(selectUser:) keyEquivalent:@""]; - [userItem setTarget:self]; - [userItem setRepresentedObject:user.objectID]; - [[self.usersItem submenu] addItem:userItem]; - - if ([user.name isEqualToString:[MPMacConfig get].usedUserName]) - [self selectUser:userItem]; - } - }]; + if ([user.name isEqualToString:[MPMacConfig get].usedUserName]) + [self selectUser:userItem]; + } } - (void)selectUser:(NSMenuItem *)item { - self.activeUserObjectID = item.representedObject; + self.activeUser = (MPUserEntity *)[[MPAppDelegate managedObjectContextForThreadIfReady] objectRegisteredForID:[item representedObject]]; } - (void)showMenu { @@ -143,15 +140,16 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (IBAction)togglePreference:(NSMenuItem *)sender { if (sender == useICloudItem) - self.storeManager.cloudEnabled = (sender.state == NSOnState); + [MPConfig get].iCloud = @(sender.state == NSOnState); if (sender == rememberPasswordItem) [MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]]; if (sender == savePasswordItem) { - if (([MPAppDelegate get].activeUser.saveKey = ![MPAppDelegate get].activeUser.saveKey)) - [[MPAppDelegate get] storeSavedKeyFor:[MPAppDelegate get].activeUser]; + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; + if ((activeUser.saveKey = !activeUser.saveKey)) + [[MPAppDelegate get] storeSavedKeyFor:activeUser]; else - [[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser]; - [[MPAppDelegate get] saveContext]; + [[MPAppDelegate get] forgetSavedKeyFor:activeUser]; + [activeUser saveContext]; } } @@ -170,10 +168,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue { - if (configKey == @selector(rememberLogin)) - self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState; - if (configKey == @selector(saveKey)) - self.savePasswordItem.state = [MPAppDelegate get].activeUser.saveKey? NSOnState: NSOffState; + [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification + object:NSStringFromSelector(configKey) userInfo:nil]; } #pragma mark - NSApplicationDelegate @@ -190,10 +186,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [weakSelf updateMenuItems]; } forKeyPath:@"activeUser" options:NSKeyValueObservingOptionInitial context:nil]; - // Initially, use iCloud. - if ([[MPConfig get].firstRun boolValue]) - [self storeManager].cloudEnabled = YES; - // Status item. self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; self.statusItem.image = [NSImage imageNamed:@"menu-icon"]; @@ -201,15 +193,17 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven self.statusItem.target = self; self.statusItem.action = @selector(showMenu); + __weak MPAppDelegate *wSelf = self; [self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { - [[[self.usersItem submenu] itemArray] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - if ([[obj representedObject] isEqual:self.activeUserObjectID]) + MPUserEntity *activeUser = wSelf.activeUser; + [[[wSelf.usersItem submenu] itemArray] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if ([[obj representedObject] isEqual:[activeUser objectID]]) [obj setState:NSOnState]; else [obj setState:NSOffState]; }]; - [MPMacConfig get].usedUserName = self.activeUser.name; + [MPMacConfig get].usedUserName = activeUser.name; } forKeyPath:@"activeUserObjectID" options:0 context:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification object:nil queue:nil usingBlock: ^(NSNotification *note) { @@ -220,6 +214,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven ^(NSNotification *note) { [self updateUsers]; }]; + [[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:nil usingBlock: + ^(NSNotification *note) { + self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState; + self.savePasswordItem.state = [MPAppDelegate get].activeUser.saveKey? NSOnState: NSOffState; + }]; [self updateUsers]; // Global hotkey. @@ -309,58 +308,33 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { // Save changes in the application's managed object context before the application terminates. - if (![self managedObjectContextIfReady]) { + NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady]; + if (!moc) return NSTerminateNow; - } - if (![[self managedObjectContextIfReady] commitEditing]) { - NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd)); + if (![moc commitEditing]) return NSTerminateCancel; - } - if (![[self managedObjectContextIfReady] hasChanges]) { + if (![moc hasChanges]) return NSTerminateNow; - } NSError *error = nil; - if (![[self managedObjectContextIfReady] save:&error]) { - - // Customize this code block to include application-specific recovery steps. - BOOL result = [sender presentError:error]; - if (result) { - return NSTerminateCancel; - } - - NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message"); - NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info"); - NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title"); - NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title"); - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:question]; - [alert setInformativeText:info]; - [alert addButtonWithTitle:quitButton]; - [alert addButtonWithTitle:cancelButton]; - - NSInteger answer = [alert runModal]; - - if (answer == NSAlertAlternateReturn) { - return NSTerminateCancel; - } - } + if (![moc save:&error]) + err(@"While terminating: %@", error); return NSTerminateNow; } #pragma mark - UbiquityStoreManagerDelegate -- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { +- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToCloud:(BOOL)cloudEnabled { - [super ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; + [super ubiquityStoreManager:manager didSwitchToCloud:cloudEnabled]; [self updateMenuItems]; if (![[MPConfig get].iCloudDecided boolValue]) { - if (iCloudEnabled) + if (cloudEnabled) return; switch ([[NSAlert alertWithMessageText:@"iCloud Is Disabled" @@ -387,7 +361,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven @"a user-specified password: these are sent to iCloud after being encrypted " @"with your master password.\n\n" @"Apple can never see any of your passwords."] runModal]; - [self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; + [self ubiquityStoreManager:manager didSwitchToCloud:cloudEnabled]; break; } diff --git a/MasterPassword/Mac/MPPasswordWindowController.m b/MasterPassword/Mac/MPPasswordWindowController.m index 67d04b56..dd8cbb8d 100644 --- a/MasterPassword/Mac/MPPasswordWindowController.m +++ b/MasterPassword/Mac/MPPasswordWindowController.m @@ -34,16 +34,20 @@ [self.userLabel setStringValue:PearlString(@"%@'s password for:", [MPAppDelegate get].activeUser.name)]; } forKeyPath:@"activeUser" options:NSKeyValueObservingOptionInitial context:nil]; [[MPAppDelegate get] addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { - if (![MPAppDelegate get].key) + if (![MPAppDelegate get].key) { [self unlock]; - if ([MPAppDelegate get].activeUser && [MPAppDelegate get].key) - [MPAlgorithmDefault migrateUser:[MPAppDelegate get].activeUser completion:^(BOOL userRequiresNewMigration) { - if (userRequiresNewMigration) - [NSAlert alertWithMessageText:@"Migration Needed" defaultButton:@"OK" alternateButton:nil otherButton:nil - informativeTextWithFormat:@"Certain sites require explicit migration to get updated to the latest version of the " - @"Master Password algorithm. For these sites, a migration button will appear. Migrating these sites will cause " - @"their passwords to change. You'll need to update your profile for that site with the new password."]; - }]; + return; + } + + [MPAppDelegate managedObjectContextPerform:^(NSManagedObjectContext *moc) { + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; + if (![MPAlgorithmDefault migrateUser:activeUser]) + [NSAlert alertWithMessageText:@"Migration Needed" defaultButton:@"OK" alternateButton:nil otherButton:nil + informativeTextWithFormat:@"Certain sites require explicit migration to get updated to the latest version of the " + @"Master Password algorithm. For these sites, a migration button will appear. Migrating these sites will cause " + @"their passwords to change. You'll need to update your profile for that site with the new password."]; + [activeUser saveContext]; + }]; } forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil usingBlock:^(NSNotification *note) { @@ -55,7 +59,7 @@ usingBlock:^(NSNotification *note) { [[NSApplication sharedApplication] hide:self]; }]; - [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil + [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil queue:nil usingBlock:^(NSNotification *note) { [self.window close]; }]; @@ -174,7 +178,7 @@ query, [MPAppDelegate get].activeUser]; NSError *error = nil; - self.siteResults = [[MPAppDelegate managedObjectContextIfReady] executeFetchRequest:fetchRequest error:&error]; + self.siteResults = [[MPAppDelegate managedObjectContextForThreadIfReady] executeFetchRequest:fetchRequest error:&error]; if (error) err(@"While fetching elements for completion: %@", error); diff --git a/MasterPassword/MasterPassword.xcdatamodeld/.xccurrentversion b/MasterPassword/MasterPassword.xcdatamodeld/.xccurrentversion index 7a0e27df..000aa55d 100644 --- a/MasterPassword/MasterPassword.xcdatamodeld/.xccurrentversion +++ b/MasterPassword/MasterPassword.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MasterPassword 3.xcdatamodel + MasterPassword 4.xcdatamodel diff --git a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents index e3c92a0d..1c30ad42 100644 --- a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents +++ b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -32,9 +32,9 @@ - - - - + + + + \ No newline at end of file diff --git a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 4.xcdatamodel/contents b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 4.xcdatamodel/contents new file mode 100644 index 00000000..b68daae4 --- /dev/null +++ b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 4.xcdatamodel/contents @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MasterPassword/iOS/MPAppDelegate.h b/MasterPassword/iOS/MPAppDelegate.h index 032aec33..ca74207d 100644 --- a/MasterPassword/iOS/MPAppDelegate.h +++ b/MasterPassword/iOS/MPAppDelegate.h @@ -14,7 +14,6 @@ + (MPAppDelegate *)get; -- (void)checkConfig; - (void)showGuide; - (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController; diff --git a/MasterPassword/iOS/MPAppDelegate.m b/MasterPassword/iOS/MPAppDelegate.m index 31f4b8d3..4d8ab151 100644 --- a/MasterPassword/iOS/MPAppDelegate.m +++ b/MasterPassword/iOS/MPAppDelegate.m @@ -6,11 +6,6 @@ // Copyright (c) 2011 Lyndir. All rights reserved. // -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnewline-eof" -#import -#pragma clang diagnostic pop - #import "MPAppDelegate.h" #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" @@ -195,7 +190,7 @@ [[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault]; */ - [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil + [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil queue:nil usingBlock:^(NSNotification *note) { if ([[note.userInfo objectForKey:@"animated"] boolValue]) [self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil]; @@ -203,9 +198,60 @@ [self.navigationController presentViewController:[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"] animated:NO completion:nil]; }]; + [[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:nil usingBlock: + ^(NSNotification *note) { + if ([[MPiOSConfig get].sendInfo boolValue]) { + if ([PearlLogger get].printLevel > PearlLogLevelInfo) + [PearlLogger get].printLevel = PearlLogLevelInfo; + + [[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"]; + [[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloud boolValue] forKey:@"iCloud"]; + [[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloudDecided boolValue] forKey:@"iCloudDecided"]; + [[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].sendInfo boolValue] forKey:@"sendInfo"]; + [[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].helpHidden boolValue] forKey:@"helpHidden"]; + [[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].showQuickStart boolValue] forKey:@"showQuickStart"]; + [[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].firstRun boolValue] forKey:@"firstRun"]; + [[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].launchCount intValue] forKey:@"launchCount"]; + [[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].askForReviews boolValue] forKey:@"askForReviews"]; + [[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].reviewAfterLaunches intValue] forKey:@"reviewAfterLaunches"]; + [[Crashlytics sharedInstance] setObjectValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"]; + +#ifdef TESTFLIGHT_SDK_VERSION + [TestFlight addCustomEnvironmentInformation:[[MPConfig get].rememberLogin boolValue]? @"YES": @"NO" forKey:@"rememberLogin"]; + [TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloud boolValue]? @"YES": @"NO" forKey:@"iCloud"]; + [TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloudDecided boolValue]? @"YES": @"NO" forKey:@"iCloudDecided"]; + [TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].sendInfo boolValue]? @"YES": @"NO" forKey:@"sendInfo"]; + [TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].helpHidden boolValue]? @"YES": @"NO" forKey:@"helpHidden"]; + [TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].showQuickStart boolValue]? @"YES": @"NO" + forKey:@"showQuickStart"]; + [TestFlight addCustomEnvironmentInformation:[[PearlConfig get].firstRun boolValue]? @"YES": @"NO" forKey:@"firstRun"]; + [TestFlight addCustomEnvironmentInformation:[[PearlConfig get].launchCount description] forKey:@"launchCount"]; + [TestFlight addCustomEnvironmentInformation:[[PearlConfig get].askForReviews boolValue]? @"YES": @"NO" + forKey:@"askForReviews"]; + [TestFlight addCustomEnvironmentInformation:[[PearlConfig get].reviewAfterLaunches description] forKey:@"reviewAfterLaunches"]; + [TestFlight addCustomEnvironmentInformation:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"]; + + [TestFlight passCheckpoint:MPCheckpointConfig]; +#endif + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointConfig attributes:@{ + @"rememberLogin" : [[MPConfig get].rememberLogin boolValue]? @"YES": @"NO", + @"iCloud" : [[MPConfig get].iCloud boolValue]? @"YES": @"NO", + @"iCloudDecided" : [[MPConfig get].iCloudDecided boolValue]? @"YES": @"NO", + @"sendInfo" : [[MPiOSConfig get].sendInfo boolValue]? @"YES": @"NO", + @"helpHidden" : [[MPiOSConfig get].helpHidden boolValue]? @"YES": @"NO", + @"showQuickStart" : [[MPiOSConfig get].showQuickStart boolValue]? @"YES": @"NO", + @"firstRun" : [[PearlConfig get].firstRun boolValue]? @"YES": @"NO", + @"launchCount" : NilToNSNull([[PearlConfig get].launchCount description]), + @"askForReviews" : [[PearlConfig get].askForReviews boolValue]? @"YES": @"NO", + @"reviewAfterLaunches" : NilToNSNull([[PearlConfig get].reviewAfterLaunches description]), + @"reviewedVersion" : NilToNSNull([PearlConfig get].reviewedVersion) + }]; + } + }]; [[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:^(NSNotification *note) { - [self checkConfig]; + [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification + object:note userInfo:nil]; }]; #ifdef ADHOC @@ -234,10 +280,6 @@ if (!url) return NO; - // Check if this is a Facebook login URL. - if ([FBSession.activeSession handleOpenURL:url]) - return YES; - // Arbitrary URL to mpsites data. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSError *error; @@ -352,22 +394,15 @@ - (void)applicationWillTerminate:(UIApplication *)application { - [self saveContext]; - [[LocalyticsSession sharedLocalyticsSession] close]; [[LocalyticsSession sharedLocalyticsSession] upload]; - [FBSession.activeSession close]; - [super applicationWillTerminate:application]; } - (void)applicationWillResignActive:(UIApplication *)application { inf(@"Will deactivate"); - - [self saveContext]; - if (![[MPiOSConfig get].rememberLogin boolValue]) [self signOutAnimated:NO]; @@ -380,9 +415,8 @@ - (void)applicationDidBecomeActive:(UIApplication *)application { inf(@"Re-activated"); - [[MPAppDelegate get] checkConfig]; - - [FBSession.activeSession handleDidBecomeActive]; + [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification + object:application userInfo:nil]; [[LocalyticsSession sharedLocalyticsSession] resume]; [[LocalyticsSession sharedLocalyticsSession] upload]; @@ -392,57 +426,6 @@ #pragma mark - Behavior -- (void)checkConfig { - - if ([[MPConfig get].iCloud boolValue] != [self.storeManager cloudEnabled]) - self.storeManager.cloudEnabled = [[MPConfig get].iCloud boolValue]; - if ([[MPiOSConfig get].sendInfo boolValue]) { - if ([PearlLogger get].printLevel > PearlLogLevelInfo) - [PearlLogger get].printLevel = PearlLogLevelInfo; - - [[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"]; - [[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloud boolValue] forKey:@"iCloud"]; - [[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloudDecided boolValue] forKey:@"iCloudDecided"]; - [[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].sendInfo boolValue] forKey:@"sendInfo"]; - [[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].helpHidden boolValue] forKey:@"helpHidden"]; - [[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].showQuickStart boolValue] forKey:@"showQuickStart"]; - [[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].firstRun boolValue] forKey:@"firstRun"]; - [[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].launchCount intValue] forKey:@"launchCount"]; - [[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].askForReviews boolValue] forKey:@"askForReviews"]; - [[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].reviewAfterLaunches intValue] forKey:@"reviewAfterLaunches"]; - [[Crashlytics sharedInstance] setObjectValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"]; - -#ifdef TESTFLIGHT_SDK_VERSION - [TestFlight addCustomEnvironmentInformation:[[MPConfig get].rememberLogin boolValue]? @"YES": @"NO" forKey:@"rememberLogin"]; - [TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloud boolValue]? @"YES": @"NO" forKey:@"iCloud"]; - [TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloudDecided boolValue]? @"YES": @"NO" forKey:@"iCloudDecided"]; - [TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].sendInfo boolValue]? @"YES": @"NO" forKey:@"sendInfo"]; - [TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].helpHidden boolValue]? @"YES": @"NO" forKey:@"helpHidden"]; - [TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].showQuickStart boolValue]? @"YES": @"NO" forKey:@"showQuickStart"]; - [TestFlight addCustomEnvironmentInformation:[[PearlConfig get].firstRun boolValue]? @"YES": @"NO" forKey:@"firstRun"]; - [TestFlight addCustomEnvironmentInformation:[[PearlConfig get].launchCount description] forKey:@"launchCount"]; - [TestFlight addCustomEnvironmentInformation:[[PearlConfig get].askForReviews boolValue]? @"YES": @"NO" forKey:@"askForReviews"]; - [TestFlight addCustomEnvironmentInformation:[[PearlConfig get].reviewAfterLaunches description] forKey:@"reviewAfterLaunches"]; - [TestFlight addCustomEnvironmentInformation:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"]; - - [TestFlight passCheckpoint:MPCheckpointConfig]; -#endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointConfig attributes:@{ - @"rememberLogin": [[MPConfig get].rememberLogin boolValue]? @"YES": @"NO", - @"iCloud": [[MPConfig get].iCloud boolValue]? @"YES": @"NO", - @"iCloudDecided": [[MPConfig get].iCloudDecided boolValue]? @"YES": @"NO", - @"sendInfo": [[MPiOSConfig get].sendInfo boolValue]? @"YES": @"NO", - @"helpHidden": [[MPiOSConfig get].helpHidden boolValue]? @"YES": @"NO", - @"showQuickStart": [[MPiOSConfig get].showQuickStart boolValue]? @"YES": @"NO", - @"firstRun": [[PearlConfig get].firstRun boolValue]? @"YES": @"NO", - @"launchCount": NilToNSNull([[PearlConfig get].launchCount description]), - @"askForReviews": [[PearlConfig get].askForReviews boolValue]? @"YES": @"NO", - @"reviewAfterLaunches": NilToNSNull([[PearlConfig get].reviewAfterLaunches description]), - @"reviewedVersion": NilToNSNull([PearlConfig get].reviewedVersion) - }]; - } -} - - (void)showGuide { [self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self]; @@ -602,18 +585,20 @@ if (buttonIndex == [alert cancelButtonIndex]) return; - inf(@"Unsetting master password for: %@.", user.userID); - user.keyID = nil; - [self forgetSavedKeyFor:user]; - [self signOutAnimated:YES]; + [user.managedObjectContext performBlock:^{ + inf(@"Unsetting master password for: %@.", user.userID); + user.keyID = nil; + [self forgetSavedKeyFor:user]; + [self signOutAnimated:YES]; - if (didReset) - didReset(); + if (didReset) + didReset(); #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointChangeMP]; + [TestFlight passCheckpoint:MPCheckpointChangeMP]; #endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP attributes:nil]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP attributes:nil]; + }]; } cancelTitle:[PearlStrings get].commonButtonAbort otherTitles:[PearlStrings get].commonButtonContinue, nil]; @@ -623,7 +608,8 @@ - (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value { - [self checkConfig]; + [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification + object:NSStringFromSelector(configKey) userInfo:nil]; } #pragma mark - UbiquityStoreManagerDelegate diff --git a/MasterPassword/iOS/MPGuideViewController.m b/MasterPassword/iOS/MPGuideViewController.m index 2140395e..3b2f7adf 100644 --- a/MasterPassword/iOS/MPGuideViewController.m +++ b/MasterPassword/iOS/MPGuideViewController.m @@ -11,9 +11,14 @@ @implementation MPGuideViewController -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { +- (BOOL)shouldAutorotate { + + return NO; +} - return (interfaceOrientation == UIInterfaceOrientationPortrait); +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + + return UIInterfaceOrientationPortrait; } - (void)viewDidLoad { @@ -50,7 +55,7 @@ - (IBAction)close { - [self.presentingViewController dismissModalViewControllerAnimated:YES]; + [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView_ { diff --git a/MasterPassword/iOS/MPMainViewController.h b/MasterPassword/iOS/MPMainViewController.h index b057c067..b7bd4b0a 100644 --- a/MasterPassword/iOS/MPMainViewController.h +++ b/MasterPassword/iOS/MPMainViewController.h @@ -8,13 +8,11 @@ #import #import "MPTypeViewController.h" -#import "MPElementEntity.h" #import "MPSearchDelegate.h" @interface MPMainViewController : UIViewController @property (assign, nonatomic) BOOL siteInfoHidden; -@property (strong, nonatomic) MPElementEntity *activeElement; @property (strong, nonatomic) IBOutlet MPSearchDelegate *searchDelegate; @property (strong, nonatomic) IBOutlet UIPanGestureRecognizer *pullDownGesture; @property (strong, nonatomic) IBOutlet UIPanGestureRecognizer *pullUpGesture; diff --git a/MasterPassword/iOS/MPMainViewController.m b/MasterPassword/iOS/MPMainViewController.m index 804e9b2b..133db9a8 100644 --- a/MasterPassword/iOS/MPMainViewController.m +++ b/MasterPassword/iOS/MPMainViewController.m @@ -12,51 +12,29 @@ #import "MPAppDelegate_Store.h" -@implementation MPMainViewController -@synthesize siteInfoHidden = _siteInfoHidden; -@synthesize activeElement = _activeElement; -@synthesize searchDelegate = _searchDelegate; -@synthesize pullDownGesture = _pullDownGesture; -@synthesize pullUpGesture = _pullUpGesture; -@synthesize typeButton = _typeButton; -@synthesize helpView = _helpView; -@synthesize siteName = _siteName; -@synthesize passwordCounter = _passwordCounter; -@synthesize passwordIncrementer = _passwordIncrementer; -@synthesize passwordEdit = _passwordEdit; -@synthesize passwordUpgrade = _passwordUpgrade; -@synthesize contentContainer = _contentContainer; -@synthesize displayContainer = _displayContainer; -@synthesize helpContainer = _helpContainer; -@synthesize contentTipContainer = _copiedContainer; -@synthesize loginNameTipContainer = _loginNameTipContainer; -@synthesize alertContainer = _alertContainer; -@synthesize alertTitle = _alertTitle; -@synthesize alertBody = _alertBody; -@synthesize contentTipBody = _contentTipBody; -@synthesize loginNameTipBody = _loginNameTipBody; -@synthesize toolTipEditIcon = _contentTipEditIcon; -@synthesize searchTipContainer = _searchTipContainer; -@synthesize actionsTipContainer = _actionsTipContainer; -@synthesize typeTipContainer = _typeTipContainer; -@synthesize toolTipContainer = _toolTipContainer; -@synthesize toolTipBody = _toolTipBody; -@synthesize loginNameContainer = _loginNameContainer; -@synthesize loginNameField = _loginNameField; -@synthesize passwordUser = _passwordUser; -@synthesize outdatedAlertContainer = _outdatedAlertContainer; -@synthesize outdatedAlertBack = _outdatedAlertBack; -@synthesize outdatedAlertCloseButton = _outdatedAlertCloseButton; -@synthesize pullUpView = _pullUpView; -@synthesize pullDownView = _pullDownView; -@synthesize contentField = _contentField; -@synthesize contentTipCleanup = _contentTipCleanup, toolTipCleanup = _toolTipCleanup; +@interface MPMainViewController() +@property (nonatomic)BOOL suppressOutdatedAlert; +@end + +@implementation MPMainViewController { + NSManagedObjectID *_activeElementOID; +} #pragma mark - View lifecycle -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { +- (BOOL)shouldAutorotate { + + return YES; +} - return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown; +- (NSUInteger)supportedInterfaceOrientations { + + return UIInterfaceOrientationMaskAllButUpsideDown; +} + +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + + return UIInterfaceOrientationPortrait; } - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { @@ -100,19 +78,22 @@ [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:self queue:nil usingBlock:^(NSNotification *note) { - [MPAppDelegate get].activeUser.requiresExplicitMigration = NO; + self.suppressOutdatedAlert = NO; }]; - [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationElementUpdated object:nil queue:nil + [[NSNotificationCenter defaultCenter] addObserverForName:MPElementUpdatedNotification object:nil queue:nil usingBlock: + ^void(NSNotification *note) { + [self activeElementDo:^(MPElementEntity *activeElement) { + if (activeElement.type & MPElementTypeClassStored && ![[activeElement.content description] length]) + [self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon]; + if (activeElement.requiresExplicitMigration) + [self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil]; + }]; + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil queue:nil usingBlock:^void(NSNotification *note) { - if (self.activeElement.type & MPElementTypeClassStored - && ![[self.activeElement.content description] length]) - [self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon]; - if (self.activeElement.requiresExplicitMigration) - [self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil]; - }]; - [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil - usingBlock:^void(NSNotification *note) { - self.activeElement = nil; + _activeElementOID = nil; + self.suppressOutdatedAlert = NO; + [self updateAnimated:NO]; }]; [super viewDidLoad]; @@ -123,22 +104,24 @@ if ([[MPiOSConfig get].showQuickStart boolValue]) [[MPAppDelegate get] showGuide]; if (![MPAppDelegate get].activeUser) + // FIXME: Remove either this one or the one in -viewDidAppear: [self presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"] animated:animated completion:nil]; - if (self.activeElement.user != [MPAppDelegate get].activeUser) - self.activeElement = nil; + [self activeElementDo:^(MPElementEntity *activeElement) { + if (activeElement.user != [MPAppDelegate get].activeUser) + _activeElementOID = nil; + }]; self.searchDisplayController.searchBar.text = nil; + self.alertContainer.alpha = 0; + self.outdatedAlertContainer.alpha = 0; + self.searchTipContainer.alpha = 0; + self.actionsTipContainer.alpha = 0; + self.typeTipContainer.alpha = 0; + self.toolTipContainer.alpha = 0; - self.alertContainer.alpha = 0; - self.outdatedAlertContainer.alpha = 0; - self.searchTipContainer.alpha = 0; - self.actionsTipContainer.alpha = 0; - self.typeTipContainer.alpha = 0; - self.toolTipContainer.alpha = 0; - - [self updateAnimated:animated]; + [self updateAnimated:NO]; [super viewWillAppear:animated]; } @@ -151,8 +134,16 @@ [[self.view.window findFirstResponderInHierarchy] resignFirstResponder]; // Needed for when we appear after a modal VC dismisses: - // We can't present until the other modal VC has been fully dismissed and presenting in viewDidAppear will fail. - if (![MPAppDelegate get].activeUser) + // We can't present until the other modal VC has been fully dismissed and presenting in -viewWillAppear: will fail. + if ([MPAppDelegate get].activeUser) + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + if ([MPAlgorithmDefault migrateUser:[MPAppDelegate get].activeUser] && !self.suppressOutdatedAlert) + [UIView animateWithDuration:0.3f animations:^{ + self.outdatedAlertContainer.alpha = 1; + self.suppressOutdatedAlert = YES; + }]; + }); + else [self presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"] animated:animated completion:nil]; @@ -160,28 +151,21 @@ [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ self.actionsTipContainer.alpha = 1; } completion:^(BOOL finished) { - if (finished) { - [MPiOSConfig get].actionsTipShown = PearlBool(YES); + if (!finished) + return; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [UIView animateWithDuration:0.2f animations:^{ - self.actionsTipContainer.alpha = 0; - } completion:^(BOOL finished_) { - if (![self.activeElement.name length]) - [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ - self.searchTipContainer.alpha = 1; - }]; - }]; - }); - } - }]; + [MPiOSConfig get].actionsTipShown = @YES; - if ([MPAppDelegate get].activeUser) - [MPAlgorithmDefault migrateUser:[MPAppDelegate get].activeUser completion:^(BOOL userRequiresNewMigration) { - if (userRequiresNewMigration) - [UIView animateWithDuration:0.3f animations:^{ - self.outdatedAlertContainer.alpha = 1; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [UIView animateWithDuration:0.2f animations:^{ + self.actionsTipContainer.alpha = 0; + } completion:^(BOOL finished_) { + if (!_activeElementOID) + [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ + self.searchTipContainer.alpha = 1; + }]; }]; + }); }]; [[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Main"]; @@ -204,55 +188,59 @@ return; } - [self setHelpChapter:self.activeElement? @"2": @"1"]; - [self updateHelpHiddenAnimated:NO]; + [self activeElementDo:^(MPElementEntity *activeElement) { + [self setHelpChapter:activeElement? @"2": @"1"]; + [self updateHelpHiddenAnimated:NO]; - self.passwordCounter.alpha = 0; - self.passwordIncrementer.alpha = 0; - self.passwordEdit.alpha = 0; - self.passwordUpgrade.alpha = 0; - self.passwordUser.alpha = 0; + self.passwordCounter.alpha = 0; + self.passwordIncrementer.alpha = 0; + self.passwordEdit.alpha = 0; + self.passwordUpgrade.alpha = 0; + self.passwordUser.alpha = 0; + self.displayContainer.alpha = 0; - if (self.activeElement) - self.passwordUser.alpha = 0.5f; + if (activeElement) { + self.passwordUser.alpha = 0.5f; + self.displayContainer.alpha = 1.0f; + } - if (self.activeElement.requiresExplicitMigration) - self.passwordUpgrade.alpha = 0.5f; + if (activeElement.requiresExplicitMigration) + self.passwordUpgrade.alpha = 0.5f; - else { - if (self.activeElement.type & MPElementTypeClassGenerated) { - self.passwordCounter.alpha = 0.5f; - self.passwordIncrementer.alpha = 0.5f; - } else - if (self.activeElement.type & MPElementTypeClassStored) - self.passwordEdit.alpha = 0.5f; - } + else { + if (activeElement.type & MPElementTypeClassGenerated) { + self.passwordCounter.alpha = 0.5f; + self.passwordIncrementer.alpha = 0.5f; + } else + if (activeElement.type & MPElementTypeClassStored) + self.passwordEdit.alpha = 0.5f; + } - self.siteName.text = self.activeElement.name; + self.siteName.text = activeElement.name; - self.typeButton.alpha = self.activeElement? 1: 0; - [self.typeButton setTitle:self.activeElement.typeName - forState:UIControlStateNormal]; + self.typeButton.alpha = activeElement? 1: 0; + [self.typeButton setTitle:activeElement.typeName + forState:UIControlStateNormal]; - if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) - self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter); + if ([activeElement isKindOfClass:[MPElementGeneratedEntity class]]) + self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)activeElement).counter); - self.contentField.enabled = NO; - self.contentField.text = @""; - if (self.activeElement.name && ![self.activeElement isDeleted]) - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - NSString *description = [self.activeElement.content description]; + self.contentField.enabled = NO; + self.contentField.text = @""; + if (activeElement.name && ![activeElement isDeleted]) + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + NSString *description = [activeElement.content description]; - dispatch_async(dispatch_get_main_queue(), ^{ - self.contentField.text = description; + dispatch_async(dispatch_get_main_queue(), ^{ + self.contentField.text = description; + }); }); - }); - self.loginNameField.enabled = NO; - self.loginNameField.text = self.activeElement.loginName; - self.siteInfoHidden = !self.activeElement || ([[MPiOSConfig get].siteInfoHidden boolValue] && (self.activeElement.loginName - == nil)); - [self updateUserHiddenAnimated:NO]; + self.loginNameField.enabled = NO; + self.loginNameField.text = activeElement.loginName; + self.siteInfoHidden = !activeElement || ([[MPiOSConfig get].siteInfoHidden boolValue] && (activeElement.loginName == nil)); + [self updateUserHiddenAnimated:NO]; + }]; } - (void)toggleHelpAnimated:(BOOL)animated { @@ -362,10 +350,12 @@ - (void)webViewDidFinishLoad:(UIWebView *)webView { - NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString: - PearlString(@"setClass('%@');", self.activeElement.typeClassName)]; - if (error.length) - err(@"helpView.setClass: %@", error); + [self activeElementDo:^(MPElementEntity *activeElement) { + NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString: + PearlString(@"setClass('%@');", activeElement.typeClassName)]; + if (error.length) + err(@"helpView.setClass: %@", error); + }]; } - (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon { @@ -374,10 +364,11 @@ if (self.contentTipCleanup) self.contentTipCleanup(NO); + __weak MPMainViewController *wSelf = self; self.contentTipBody.text = message; self.contentTipCleanup = ^(BOOL finished) { - icon.hidden = YES; - self.contentTipCleanup = nil; + icon.hidden = YES; + wSelf.contentTipCleanup = nil; }; icon.hidden = NO; @@ -422,10 +413,11 @@ if (self.toolTipCleanup) self.toolTipCleanup(NO); + __weak MPMainViewController *wSelf = self; self.toolTipBody.text = message; self.toolTipCleanup = ^(BOOL finished) { - icon.hidden = YES; - self.toolTipCleanup = nil; + icon.hidden = YES; + wSelf.toolTipCleanup = nil; }; icon.hidden = NO; @@ -465,63 +457,69 @@ - (IBAction)copyContent { - id content = self.activeElement.content; - if (!content) - // Nothing to copy. - return; + [self activeElementDo:^(MPElementEntity *activeElement) { + id content = activeElement.content; + if (!content) + // Nothing to copy. + return; - inf(@"Copying password for: %@", self.activeElement.name); - [UIPasteboard generalPasteboard].string = [content description]; + inf(@"Copying password for: %@", activeElement.name); + [UIPasteboard generalPasteboard].string = [content description]; - [self showContentTip:@"Copied!" withIcon:nil]; + [self showContentTip:@"Copied!" withIcon:nil]; #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointCopyToPasteboard]; + [TestFlight passCheckpoint:MPCheckpointCopyToPasteboard]; #endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard - attributes:@{@"type": self.activeElement.typeName, - @"version": @(self.activeElement.version)}]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard attributes:@{@"type" : activeElement.typeName, + @"version" : @(activeElement.version)}]; + }]; } - (IBAction)copyLoginName:(UITapGestureRecognizer *)sender { - if (!self.activeElement.loginName) - return; + [self activeElementDo:^(MPElementEntity *activeElement) { + if (!activeElement.loginName) + return; - inf(@"Copying user name for: %@", self.activeElement.name); - [UIPasteboard generalPasteboard].string = self.activeElement.loginName; + inf(@"Copying user name for: %@", activeElement.name); + [UIPasteboard generalPasteboard].string = activeElement.loginName; - [self showLoginNameTip:@"Copied!"]; + [self showLoginNameTip:@"Copied!"]; #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointCopyLoginNameToPasteboard]; + [TestFlight passCheckpoint:MPCheckpointCopyLoginNameToPasteboard]; #endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyLoginNameToPasteboard - attributes:@{@"type": self.activeElement.typeName, - @"version": @(self.activeElement.version)}]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyLoginNameToPasteboard + attributes:@{@"type" : activeElement.typeName, + @"version" : @(activeElement.version)}]; + }]; } - (IBAction)incrementPasswordCounter { - if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) - // Not of a type that supports a password counter. - return; - - [self changeElementWithWarning: + [self changeActiveElementWithWarning: @"You are incrementing the site's password counter.\n\n" @"If you continue, a new password will be generated for this site. " @"You will then need to update your account's old password to this newly generated password.\n\n" @"You can reset the counter by holding down on this button." - do:^{ - inf(@"Incrementing password counter for: %@", self.activeElement.name); - ++((MPElementGeneratedEntity *)self.activeElement).counter; + do:^BOOL(MPElementEntity *activeElement) { + if (![activeElement isKindOfClass:[MPElementGeneratedEntity class]]) { + // Not of a type that supports a password counter. + err(@"Cannot increment password counter: Element is not generated: %@", activeElement.name); + return NO; + } + + inf(@"Incrementing password counter for: %@", activeElement.name); + ++((MPElementGeneratedEntity *)activeElement).counter; #ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter]; #endif [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter - attributes:@{@"type": self.activeElement.typeName, - @"version": @(self.activeElement.version)}]; + attributes:@{@"type": activeElement.typeName, + @"version": @(activeElement.version)}]; + return YES; }]; } @@ -530,27 +528,35 @@ if (sender.state != UIGestureRecognizerStateBegan) // Only fire when the gesture was first detected. return; - if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) - // Not of a type that supports a password counter. - return; - if (((MPElementGeneratedEntity *)self.activeElement).counter == 1) - // Counter has initial value, no point resetting. + __block BOOL abort = NO; + [self activeElementDo:^(MPElementEntity *activeElement) { + if (![activeElement isKindOfClass:[MPElementGeneratedEntity class]]) { + // Not of a type that supports a password counter. + err(@"Cannot reset password counter: Element is not generated: %@", activeElement.name); + abort = YES; + } else + if (((MPElementGeneratedEntity *)activeElement).counter == 1) + // Counter has initial value, no point resetting. + abort = YES; + }]; + if (abort) return; - [self changeElementWithWarning: + [self changeActiveElementWithWarning: @"You are resetting the site's password counter.\n\n" @"If you continue, the site's password will change back to its original value. " @"You will then need to update your account's password back to this original value." - do:^{ - inf(@"Resetting password counter for: %@", self.activeElement.name); - ((MPElementGeneratedEntity *)self.activeElement).counter = 1; + do:^BOOL(MPElementEntity *activeElement){ + inf(@"Resetting password counter for: %@", activeElement.name); + ((MPElementGeneratedEntity *)activeElement).counter = 1; #ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPCheckpointResetPasswordCounter]; #endif [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter - attributes:@{@"type": self.activeElement.typeName, - @"version": @(self.activeElement.version)}]; + attributes:@{@"type": activeElement.typeName, + @"version": @(activeElement.version)}]; + return YES; }]; } @@ -560,86 +566,137 @@ // Only fire when the gesture was first detected. return; - if (!self.activeElement) - return; + [self activeElementDo:^(MPElementEntity *activeElement) { + if (!activeElement) + return; - self.loginNameField.enabled = YES; - [self.loginNameField becomeFirstResponder]; + self.loginNameField.enabled = YES; + [self.loginNameField becomeFirstResponder]; #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointEditLoginName]; + [TestFlight passCheckpoint:MPCheckpointEditLoginName]; #endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditLoginName attributes:@{@"type": self.activeElement.typeName, - @"version": @(self.activeElement.version)}]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditLoginName attributes:@{@"type" : activeElement.typeName, + @"version" : @(activeElement.version)}]; + }]; } -- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; { +- (void)changeActiveElementWithWarning:(NSString *)warning do:(BOOL (^)(MPElementEntity *activeElement))task; { [PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { if (buttonIndex == [alert cancelButtonIndex]) return; - [self changeElementWithoutWarningDo:task]; + [self changeActiveElementWithoutWarningDo:task]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil]; } -- (void)changeElementWithoutWarningDo:(void (^)(void))task; { +- (void)changeActiveElementWithoutWarningDo:(BOOL (^)(MPElementEntity *activeElement))task; { // Update element, keeping track of the old password. - NSString *oldPassword = [self.activeElement.content description]; - task(); - NSString *newPassword = [self.activeElement.content description]; - [[MPAppDelegate get] saveContext]; - [self updateAnimated:YES]; + [self activeElementDo:^(MPElementEntity *activeElement) { + NSManagedObjectContext *moc = activeElement.managedObjectContext; + [moc performBlock:^{ - // Show new and old password. - if ([oldPassword length] && ![oldPassword isEqualToString:newPassword]) - [self showAlertWithTitle:@"Password Changed!" - message:PearlString(@"The password for %@ has changed.\n\n" - @"IMPORTANT:\n" - @"Don't forget to update the site with your new password! " - @"Your old password was:\n" - @"%@", self.activeElement.name, oldPassword)]; + // Perform the task. + NSString *oldPassword = [activeElement.content description]; + if (!task(activeElement)) + return; + NSString *newPassword = [activeElement.content description]; + + // Save. + NSError *error; + if (![moc save:&error]) + err(@"While saving changes to: %@, error: %@", activeElement.name, error); + + // Update the UI. + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateAnimated:YES]; + + // Show new and old password. + if ([oldPassword length] && ![oldPassword isEqualToString:newPassword]) + [self showAlertWithTitle:@"Password Changed!" + message:PearlString(@"The password for %@ has changed.\n\n" + @"IMPORTANT:\n" + @"Don't forget to update the site with your new password! " + @"Your old password was:\n" + @"%@", activeElement.name, oldPassword)]; + }); + }]; + }]; +} + +- (void)activeElementDo:(void (^)(MPElementEntity *activeElement))task { + + if (!_activeElementOID) { + task(nil); + return; + } + + NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady]; + if (!moc) { + task(nil); + return; + } + + NSError *error; + MPElementEntity *activeElement = (MPElementEntity *)[moc existingObjectWithID:_activeElementOID error:&error]; + if (!activeElement) + err(@"Couldn't retrieve active element: %@", error); + + task(activeElement); } - (IBAction)editPassword { - if (self.activeElement.type & MPElementTypeClassStored) { + [self activeElementDo:^(MPElementEntity *activeElement) { + if (!(activeElement.type & MPElementTypeClassStored)) { + // Not of a type that supports editing the content. + err(@"Cannot edit content: Element is not stored: %@", activeElement.name); + return; + } + self.contentField.enabled = YES; [self.contentField becomeFirstResponder]; #ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPCheckpointEditPassword]; #endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword - attributes:@{@"type": self.activeElement.typeName, - @"version": @(self.activeElement.version)}]; - } + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword attributes:@{@"type" : activeElement.typeName, + @"version" : @(activeElement.version)}]; + }]; } - (IBAction)upgradePassword { - [self changeElementWithWarning: - self.activeElement.type & MPElementTypeClassGenerated? - @"You are upgrading the site.\n\n" - @"This upgrade improves the site's compatibility with the latest version of Master Password.\n\n" - @"Your password will change and you will need to update your site's account." - : - @"You are upgrading the site.\n\n" - @"This upgrade improves the site's compatibility with the latest version of Master Password." - do:^{ - inf(@"Explicitly migrating element: %@", self.activeElement); - [self.activeElement migrateExplicitly:YES]; + __block NSString *warning = nil; + [self activeElementDo:^(MPElementEntity *activeElement) { + warning = activeElement.type & MPElementTypeClassGenerated? + @"You are upgrading the site.\n\n" + @"This upgrade improves the site's compatibility with the latest version of Master Password.\n\n" + @"Your password will change and you will need to update your site's account." + : + @"You are upgrading the site.\n\n" + @"This upgrade improves the site's compatibility with the latest version of Master Password."; + }]; + if (!warning) + return; + + [self changeActiveElementWithWarning:warning + do:^BOOL(MPElementEntity *activeElement) { + inf(@"Explicitly migrating element: %@", activeElement); + [activeElement migrateExplicitly:YES]; #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointExplicitMigration]; + [TestFlight passCheckpoint:MPCheckpointExplicitMigration]; #endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration - attributes:@{@"type": self.activeElement.typeName, - @"version": @(self.activeElement.version)}]; - }]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration + attributes:@{@"type" : activeElement.typeName, + @"version" : @(activeElement.version)}]; + return YES; + }]; } - (IBAction)searchOutdatedElements { @@ -671,7 +728,7 @@ [self setHelpChapter:@"outdated"]; [self setHelpHidden:NO animated:YES]; [self closeOutdatedAlert]; - [MPAppDelegate get].activeUser.requiresExplicitMigration = NO; + self.suppressOutdatedAlert = NO; } - (IBAction)action:(id)sender { @@ -738,56 +795,65 @@ - (MPElementType)selectedType { - return self.activeElement.type; + __block MPElementType selectedType; + [self activeElementDo:^(MPElementEntity *activeElement) { + selectedType = activeElement.type; + }]; + + return selectedType; } - (void)didSelectType:(MPElementType)type { - [self changeElementWithWarning: + [self changeActiveElementWithWarning: @"You are about to change the type of this password.\n\n" @"If you continue, the password for this site will change. " @"You will need to update your account's old password to the new one." - do:^{ - // Update password type. - if ([self.activeElement.algorithm classOfType:type] != self.activeElement.typeClass) - // Type requires a different class of element. Recreate the element. - [[MPAppDelegate managedObjectContextIfReady] performBlockAndWait:^{ - MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:[self.activeElement.algorithm classNameOfType:type] - inManagedObjectContext:[MPAppDelegate managedObjectContextIfReady]]; - newElement.name = self.activeElement.name; - newElement.user = self.activeElement.user; - newElement.uses = self.activeElement.uses; - newElement.lastUsed = self.activeElement.lastUsed; - newElement.version = self.activeElement.version; + do:^BOOL(MPElementEntity *activeElement){ + if ([activeElement.algorithm classOfType:type] != activeElement.typeClass) { + // Type requires a different class of element. Recreate the element. + MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:[activeElement.algorithm classNameOfType:type] + inManagedObjectContext:activeElement.managedObjectContext]; + newElement.name = activeElement.name; + newElement.user = activeElement.user; + newElement.uses = activeElement.uses; + newElement.lastUsed = activeElement.lastUsed; + newElement.version = activeElement.version; + newElement.loginName = activeElement.loginName; - [[MPAppDelegate managedObjectContextIfReady] deleteObject:self.activeElement]; - self.activeElement = newElement; - }]; + [activeElement.managedObjectContext deleteObject:activeElement]; + _activeElementOID = newElement.objectID; + activeElement = newElement; + } + activeElement.type = type; - self.activeElement.type = type; - - [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated - object:self.activeElement]; - }]; + [[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification + object:activeElement.objectID]; + return YES; + }]; } - (void)didSelectElement:(MPElementEntity *)element { - inf(@"Selected: %@", element.name); - dbg(@"Element:\n%@", [element debugDescription]); + if (!element) + return; + _activeElementOID = element.objectID; [self closeAlert]; - - if (element) { - self.activeElement = element; - if ([self.activeElement use] == 1) + [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement) { + if ([activeElement use] == 1) [self showAlertWithTitle:@"New Site" message: PearlString(@"You've just created a password for %@.\n\n" @"IMPORTANT:\n" @"Go to %@ and set or change the password for your account to the password above.\n" @"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.", - self.activeElement.name, self.activeElement.name)]; - [[MPAppDelegate get] saveContext]; + activeElement.name, activeElement.name)]; + return YES; + }]; + + [self activeElementDo:^(MPElementEntity *activeElement) { + inf(@"Selected: %@", activeElement.name); + dbg(@"Element:\n%@", [activeElement debugDescription]); if (![[MPiOSConfig get].typeTipShown boolValue]) [UIView animateWithDuration:0.5f animations:^{ @@ -805,17 +871,16 @@ } }]; - [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated object:self.activeElement]; + [self.searchDisplayController setActive:NO animated:YES]; + self.searchDisplayController.searchBar.text = activeElement.name; + + [[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:activeElement.objectID]; #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", self.activeElement.typeShortName)]; + [TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", activeElement.typeShortName)]; #endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:@{@"type": self.activeElement.typeName, - @"version": @(self.activeElement.version)}]; - } - - [self.searchDisplayController setActive:NO animated:YES]; - self.searchDisplayController.searchBar.text = self.activeElement.name; - + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:@{@"type" : activeElement.typeName, + @"version" : @(activeElement.version)}]; + }]; [self updateAnimated:YES]; } @@ -833,16 +898,22 @@ if (textField == self.contentField) { self.contentField.enabled = NO; - if (![self.activeElement isKindOfClass:[MPElementStoredEntity class]]) - // Not of a type whose content can be edited. + __block BOOL abort = NO; + [self activeElementDo:^(MPElementEntity *activeElement) { + if (![activeElement isKindOfClass:[MPElementStoredEntity class]]) { + // Not of a type whose content can be edited. + err(@"Cannot update element content: Element is not stored: %@", activeElement.name); + abort = YES; + } else if ([((MPElementStoredEntity *)activeElement).content isEqual:self.contentField.text]) + // Content hasn't changed. + abort = YES; + }]; + if (abort) return; - if ([((MPElementStoredEntity *)self.activeElement).content isEqual:self.contentField.text]) - // Content hasn't changed. - return; - - [self changeElementWithoutWarningDo:^{ - ((MPElementStoredEntity *)self.activeElement).content = self.contentField.text; + [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement) { + ((MPElementStoredEntity *)activeElement).content = self.contentField.text; + return YES; }]; } @@ -850,15 +921,17 @@ self.loginNameField.enabled = NO; if (![[MPiOSConfig get].loginNameTipShown boolValue]) { [self showLoginNameTip:@"Tap to copy or hold to edit."]; - [MPiOSConfig get].loginNameTipShown = PearlBool(YES); + [MPiOSConfig get].loginNameTipShown = @YES; } - if ([self.loginNameField.text length]) - self.activeElement.loginName = self.loginNameField.text; - else - self.activeElement.loginName = nil; + [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement) { + if ([self.loginNameField.text length]) + activeElement.loginName = self.loginNameField.text; + else + activeElement.loginName = nil; - [[MPAppDelegate get] saveContext]; + return YES; + }]; } } diff --git a/MasterPassword/iOS/MPPreferencesViewController.m b/MasterPassword/iOS/MPPreferencesViewController.m index f3e815e4..825bc20f 100644 --- a/MasterPassword/iOS/MPPreferencesViewController.m +++ b/MasterPassword/iOS/MPPreferencesViewController.m @@ -47,8 +47,9 @@ } options:0]; [avatar onSelect:^(BOOL selected) { if (selected) { - [MPAppDelegate get].activeUser.avatar = (unsigned)avatar.tag; - [[MPAppDelegate get] saveContext]; + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; + activeUser.avatar = (unsigned)avatar.tag; + [activeUser saveContext]; } } options:0]; avatar.selected = (a == [MPAppDelegate get].activeUser.avatar); @@ -86,9 +87,14 @@ [super viewWillDisappear:animated]; } -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { +- (BOOL)shouldAutorotate { + + return NO; +} - return (interfaceOrientation == UIInterfaceOrientationPortrait); +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + + return UIInterfaceOrientationPortrait; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { @@ -106,8 +112,11 @@ [[MPAppDelegate get] export]; else - if (cell == self.changeMPCell) - [[MPAppDelegate get] changeMasterPasswordFor:[MPAppDelegate get].activeUser didResetBlock:nil]; + if (cell == self.changeMPCell) { + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; + [[MPAppDelegate get] changeMasterPasswordFor:activeUser didResetBlock:nil]; + [activeUser saveContext]; + } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } @@ -124,10 +133,11 @@ - (void)didSelectType:(MPElementType)type { - [MPAppDelegate get].activeUser.defaultType = type; - [[MPAppDelegate get] saveContext]; + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; + activeUser.defaultType = type; + [activeUser saveContext]; - self.defaultTypeLabel.text = [[MPAppDelegate get].key.algorithm shortNameOfType:[MPAppDelegate get].activeUser.defaultType]; + self.defaultTypeLabel.text = [[MPAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType]; } - (MPElementType)selectedType { @@ -139,11 +149,12 @@ - (IBAction)didToggleSwitch:(UISwitch *)sender { - if (([MPAppDelegate get].activeUser.saveKey = sender.on)) - [[MPAppDelegate get] storeSavedKeyFor:[MPAppDelegate get].activeUser]; + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; + if ((activeUser.saveKey = sender.on)) + [[MPAppDelegate get] storeSavedKeyFor:activeUser]; else - [[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser]; - [[MPAppDelegate get] saveContext]; + [[MPAppDelegate get] forgetSavedKeyFor:activeUser]; + [activeUser saveContext]; } - (IBAction)settings:(UIBarButtonItem *)sender { diff --git a/MasterPassword/iOS/MPSearchDelegate.m b/MasterPassword/iOS/MPSearchDelegate.m index 71f76ac4..57e2a102 100644 --- a/MasterPassword/iOS/MPSearchDelegate.m +++ b/MasterPassword/iOS/MPSearchDelegate.m @@ -53,7 +53,8 @@ - (NSFetchedResultsController *)fetchedResultsController { if (!_fetchedResultsController) { - NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextIfReady]; + NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must run on the main thread."); + NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady]; if (!moc) return nil; @@ -153,10 +154,11 @@ - (void)fetchData { + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; assert(self.query); - assert([MPAppDelegate get].activeUser); + assert(activeUser); - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@", [MPAppDelegate get].activeUser]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@", activeUser]; if (self.query.length) predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[[NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", self.query], @@ -292,10 +294,11 @@ if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"]; - UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]]; + UIImage *backgroundImage = [[UIImage imageNamed:@"ui_list_middle"] resizableImageWithCapInsets:UIEdgeInsetsMake(3, 3, 3, 3) + resizingMode:UIImageResizingModeStretch]; + UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage]; backgroundImageView.frame = CGRectMake(-5, 0, 330, 34); backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f); UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)]; [backgroundView addSubview:backgroundImageView]; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; @@ -346,24 +349,30 @@ if (buttonIndex == [alert cancelButtonIndex]) return; - [self.fetchedResultsController.managedObjectContext performBlock:^{ - MPElementType type = [MPAppDelegate get].activeUser.defaultType; + [MPAppDelegate managedObjectContextPerform:^(NSManagedObjectContext *moc) { + MPUserEntity *activeUser = [[MPAppDelegate get] activeUserInContext:moc]; + assert(activeUser); + + MPElementType type = activeUser.defaultType; if (!type) { // Really shouldn't happen, but a few people crashed on this anyway. Uhh. Data store corruption? Old bugs? - type = [MPAppDelegate get].activeUser.defaultType = MPElementTypeGeneratedLong; + type = activeUser.defaultType = MPElementTypeGeneratedLong; } MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmDefault classNameOfType:type] - inManagedObjectContext:self.fetchedResultsController.managedObjectContext]; - assert([MPAppDelegate get].activeUser); + inManagedObjectContext:moc]; element.name = siteName; - element.user = [MPAppDelegate get].activeUser; + element.user = activeUser; element.type = type; element.version = MPAlgorithmDefaultVersion; + [element saveContext]; + NSManagedObjectID *elementOID = [element objectID]; dispatch_async(dispatch_get_main_queue(), ^{ - [self.delegate didSelectElement:element]; + MPElementEntity *element_ = (MPElementEntity *)[[MPAppDelegate managedObjectContextForThreadIfReady] + objectRegisteredForID:elementOID]; + [self.delegate didSelectElement:element_]; }); }]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; @@ -402,9 +411,9 @@ forRowAtIndexPath:(NSIndexPath *)indexPath { #ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPCheckpointDeleteElement]; #endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointDeleteElement - attributes:@{@"type": element.typeName, - @"version": @(element.version)}]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointDeleteElement attributes:@{ + @"type" : element.typeName, + @"version" : @(element.version)}]; }]; } } diff --git a/MasterPassword/iOS/MPTypeViewController.m b/MasterPassword/iOS/MPTypeViewController.m index d230aab2..6c681923 100644 --- a/MasterPassword/iOS/MPTypeViewController.m +++ b/MasterPassword/iOS/MPTypeViewController.m @@ -60,11 +60,6 @@ [super viewWillDisappear:animated]; } -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - - return YES; -} - - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; diff --git a/MasterPassword/iOS/MPUnlockViewController.m b/MasterPassword/iOS/MPUnlockViewController.m index df869123..fc072ae5 100644 --- a/MasterPassword/iOS/MPUnlockViewController.m +++ b/MasterPassword/iOS/MPUnlockViewController.m @@ -7,12 +7,7 @@ // #import -#import - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnewline-eof" -#import "Facebook.h" -#pragma clang diagnostic pop +#import #import "GooglePlusShare.h" #import "MPUnlockViewController.h" @@ -22,15 +17,15 @@ @interface MPUnlockViewController () -@property (strong, nonatomic) MPUserEntity *selectedUser; -@property (strong, nonatomic) NSMutableDictionary *avatarToUser; +@property (strong, nonatomic) NSMutableDictionary *avatarToUserOID; @property (nonatomic) BOOL wordWallAnimating; @property (nonatomic, strong) NSArray *wordList; -@property (nonatomic, strong) NSOperationQueue *fbOperationQueue; @end -@implementation MPUnlockViewController +@implementation MPUnlockViewController { + NSManagedObjectID *_selectedUserOID; +} - (void)initializeAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user { @@ -104,18 +99,21 @@ alertNameLabel.backgroundColor = [UIColor blackColor]; } -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { +- (BOOL)shouldAutorotate { - return (interfaceOrientation == UIInterfaceOrientationPortrait); + return NO; +} + +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + + return UIInterfaceOrientationPortrait; } - (void)viewDidLoad { [self.newsView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.masterpasswordapp.com/news.html"]]]; - self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3]; - self.fbOperationQueue = [NSOperationQueue new]; - [self.fbOperationQueue setSuspended:YES]; + self.avatarToUserOID = [NSMutableDictionary dictionaryWithCapacity:3]; [self.avatarsView addGestureRecognizer:self.targetedUserActionGesture]; self.avatarsView.decelerationRate = UIScrollViewDecelerationRateFast; @@ -159,7 +157,7 @@ [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide]; inf(@"Lock screen will appear"); - self.selectedUser = nil; + _selectedUserOID = nil; [self updateUsers]; self.uiContainer.alpha = 0; @@ -169,6 +167,7 @@ - (void)viewDidAppear:(BOOL)animated { + dbg(@"Lock screen did appear: %@", animated? @"animated": @"not animated"); if (!animated) [[self findTargetedAvatar] setSelected:YES]; else @@ -193,7 +192,7 @@ - (void)updateUsers { - NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextIfReady]; + NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady]; if (!moc) { self.tip.text = @"Loading..."; [self.loadingUsersIndicator startAnimating]; @@ -215,10 +214,10 @@ // Clean up avatars. for (UIView *subview in [self.avatarsView subviews]) - if ([[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) + if ([[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) // This subview is a former avatar. [subview removeFromSuperview]; - [self.avatarToUser removeAllObjects]; + [self.avatarToUserOID removeAllObjects]; // Create avatars. [moc performBlockAndWait:^{ @@ -235,7 +234,7 @@ - (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user { - avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y); + avatar.center = CGPointMake(avatar.center.x + [self.avatarToUserOID count] * 160, avatar.center.y); avatar.hidden = NO; avatar.layer.cornerRadius = avatar.bounds.size.height / 2; avatar.layer.shadowColor = [UIColor blackColor].CGColor; @@ -266,9 +265,9 @@ } } options:0]; - [self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]]; + [self.avatarToUserOID setObject:NilToNSNull([user objectID]) forKey:[NSValue valueWithNonretainedObject:avatar]]; - if ([self.selectedUser.objectID isEqual:user.objectID]) { + if ([_selectedUserOID isEqual:[user objectID]]) { self.selectedUser = user; avatar.selected = YES; } @@ -278,10 +277,11 @@ - (void)didToggleUserSelection { - if (!self.selectedUser) + MPUserEntity *selectedUser = self.selectedUser; + if (!selectedUser) [self.passwordField resignFirstResponder]; else - if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) { + if ([[MPAppDelegate get] signInAsUser:selectedUser usingMasterPassword:nil]) { [self dismissViewControllerAnimated:YES completion:nil]; return; } @@ -294,18 +294,16 @@ - (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar { - __block MPUserEntity *newUser = nil; - [[MPAppDelegate managedObjectContextIfReady] performBlockAndWait:^{ - newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) - inManagedObjectContext:[MPAppDelegate managedObjectContextIfReady]]; - }]; + [MPAppDelegate managedObjectContextPerform:^(NSManagedObjectContext *moc) { + MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) + inManagedObjectContext:moc]; - [self showNewUserNameAlertFor:newUser completion:^(BOOL finished) { - newUserAvatar.selected = NO; - if (!finished) - [[MPAppDelegate managedObjectContextIfReady] performBlock:^{ - [[MPAppDelegate managedObjectContextIfReady] deleteObject:newUser]; - }]; + [self showNewUserNameAlertFor:newUser completion:^(BOOL finished) { + newUserAvatar.selected = NO; + + if (finished) + [newUser saveContext]; + }]; }]; } @@ -328,16 +326,18 @@ if (![alert textFieldAtIndex:0].text.length) { [PearlAlert showAlertWithTitle:@"Name Is Required" message:nil viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { - [self showNewUserNameAlertFor:newUser completion:completion]; - } cancelTitle:@"Try Again" otherTitles:nil]; + [newUser.managedObjectContext performBlock:^{ + [self showNewUserNameAlertFor:newUser completion:completion]; + }]; + } cancelTitle:@"Try Again" otherTitles:nil]; return; } // Save [newUser.managedObjectContext performBlock:^{ newUser.name = [alert textFieldAtIndex:0].text; + [self showNewUserAvatarAlertFor:newUser completion:completion]; }]; - [self showNewUserAvatarAlertFor:newUser completion:completion]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil]; } @@ -352,7 +352,9 @@ tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) { // Okay - [self showNewUserConfirmationAlertFor:newUser completion:completion]; + [newUser.managedObjectContext performBlock:^{ + [self showNewUserConfirmationAlertFor:newUser completion:completion]; + }]; } cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil]; } @@ -368,7 +370,9 @@ } tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) { if (__buttonIndex == [__alert cancelButtonIndex]) { - [self showNewUserNameAlertFor:newUser completion:completion]; + [newUser.managedObjectContext performBlock:^{ + [self showNewUserNameAlertFor:newUser completion:completion]; + }]; return; } @@ -460,7 +464,7 @@ } [self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { - if (![[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) + if (![[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) // This subview is not one of the user avatars. return; UIButton *avatar = (UIButton *)subview; @@ -481,7 +485,8 @@ } // Lay out user name label. - self.nameLabel.text = targetedAvatar? targetedUser? targetedUser.name: @"New User": nil; + self.nameLabel.text = targetedAvatar? (targetedUser? targetedUser.name: @"New User"): nil; + dbg(@"targetedAvatar: %@, targetedUser: %@, nameLabel: %@", targetedAvatar, targetedUser, self.nameLabel.text); self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds, [self.nameLabel.text sizeWithFont:self.nameLabel.font constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100) @@ -565,10 +570,11 @@ - (UIButton *)avatarForUser:(MPUserEntity *)user { + NSManagedObjectID *userOID = [user objectID]; __block UIButton *avatar = nil; - if (user) - [self.avatarToUser enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - if (obj == user) + if (userOID) + [self.avatarToUserOID enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if ([obj isEqual:userOID]) avatar = [key nonretainedObjectValue]; }]; @@ -577,7 +583,20 @@ - (MPUserEntity *)userForAvatar:(UIButton *)avatar { - return NSNullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]); + NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady]; + if (!moc) + return nil; + + NSManagedObjectID *userOID = NSNullToNil([self.avatarToUserOID objectForKey:[NSValue valueWithNonretainedObject:avatar]]); + if (!userOID) + return nil; + + NSError *error; + MPUserEntity *user = (MPUserEntity *)[moc existingObjectWithID:userOID error:&error]; + if (!user) + err(@"Failed retrieving user for avatar: %@", error); + + return user; } - (void)setSpinnerActive:(BOOL)active { @@ -763,17 +782,24 @@ return; if (buttonIndex == [sheet destructiveButtonIndex]) { - [[MPAppDelegate get].managedObjectContextIfReady performBlockAndWait:^{ - [[MPAppDelegate get].managedObjectContextIfReady deleteObject:targetedUser]; + [targetedUser.managedObjectContext performBlock:^{ + [targetedUser.managedObjectContext deleteObject:targetedUser]; + [targetedUser saveContext]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateUsers]; + }); }]; - [[MPAppDelegate get] saveContext]; - [self updateUsers]; return; } if (buttonIndex == [sheet firstOtherButtonIndex]) - [[MPAppDelegate get] changeMasterPasswordFor:targetedUser didResetBlock:^{ - [[self avatarForUser:targetedUser] setSelected:YES]; + [targetedUser.managedObjectContext performBlock:^{ + [[MPAppDelegate get] changeMasterPasswordFor:targetedUser didResetBlock:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [[self avatarForUser:targetedUser] setSelected:YES]; + }); + }]; }]; } cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil]; @@ -781,37 +807,31 @@ - (IBAction)facebook:(UIButton *)sender { - [self.fbOperationQueue addOperationWithBlock:^{ - Facebook *facebook = [[Facebook alloc] initWithAppId:FBSession.activeSession.appID andDelegate:nil]; - facebook.accessToken = FBSession.activeSession.accessToken; - facebook.expirationDate = FBSession.activeSession.expirationDate; - - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - [[self.view findFirstResponderInHierarchy] resignFirstResponder]; - [facebook dialog:@"feed" andParams:[@{ - @"link": @"http://masterpasswordapp.com", - @"picture": @"http://masterpasswordapp.com/img/iTunesArtwork-Rounded.png", - @"name": @"Master Password", - @"description": @"Actually secure passwords that cannot get lost.", - @"ref": @"iOS_Unlock" - } mutableCopy] andDelegate:nil]; - }]; - }]; - if ([self.fbOperationQueue isSuspended]) - [self openSessionWithAllowLoginUI:YES]; + if (![SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) { + [PearlAlert showAlertWithTitle:@"Facebook Not Enabled" message:@"To send tweets, configure Facebook from Settings." + viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil cancelTitle:nil otherTitles:@"OK", nil]; + return; + } + + SLComposeViewController *vc = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook]; + [vc setInitialText:@"I've started doing passwords properly thanks to Master Password for iOS."]; + [vc addImage:[UIImage imageNamed:@"iTunesArtwork-Rounded"]]; + [vc addURL:[NSURL URLWithString:@"http://masterpasswordapp.com"]]; + [self presentViewController:vc animated:YES completion:nil]; } - (IBAction)twitter:(UIButton *)sender { - if (![TWTweetComposeViewController canSendTweet]) { + if (![SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) { [PearlAlert showAlertWithTitle:@"Twitter Not Enabled" message:@"To send tweets, configure Twitter from Settings." viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil cancelTitle:nil otherTitles:@"OK", nil]; return; } - TWTweetComposeViewController *vc = [TWTweetComposeViewController new]; - [vc addImage:[UIImage imageNamed:@"iTunesArtwork-Rounded-73"]]; - [vc setInitialText:@"I've secured my accounts with Master Password: masterpasswordapp.com"]; + SLComposeViewController *vc = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter]; + [vc setInitialText:@"I've started doing passwords properly thanks to Master Password for iOS."]; + [vc addImage:[UIImage imageNamed:@"iTunesArtwork-Rounded"]]; + [vc addURL:[NSURL URLWithString:@"http://masterpasswordapp.com"]]; [self presentViewController:vc animated:YES completion:nil]; } @@ -886,41 +906,28 @@ destructiveTitle:nil otherTitles:@"Google+", @"Facebook", @"Twitter", @"Mailing List", @"GitHub", nil]; } -- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState)state error:(NSError *)error { +#pragma mark - Core Data - switch (state) { - case FBSessionStateOpen: - if (!error) { - [self.fbOperationQueue setSuspended:NO]; - return; - } +- (MPUserEntity *)selectedUser { - break; - case FBSessionStateClosed: - case FBSessionStateClosedLoginFailed: - [FBSession.activeSession closeAndClearTokenInformation]; - break; - default: - break; - } - [self.fbOperationQueue setSuspended:YES]; + if (!_selectedUserOID) + return nil; - if (error) - [PearlAlert showError:error.localizedDescription]; + NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady]; + if (!moc) + return nil; + + NSError *error; + MPUserEntity *selectedUser = (MPUserEntity *)[moc existingObjectWithID:_selectedUserOID error:&error]; + if (!selectedUser) + err(@"Failed to retrieve selected user: %@", error); + + return selectedUser; } -- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI { +- (void)setSelectedUser:(MPUserEntity *)selectedUser { - return [FBSession openActiveSessionWithPublishPermissions:nil - defaultAudience:FBSessionDefaultAudienceEveryone - allowLoginUI:YES - completionHandler:^(FBSession *session, FBSessionState state, NSError *error) { - [self sessionStateChanged:session state:state error:error]; - }]; + _selectedUserOID = selectedUser.objectID; } -- (void)viewDidUnload { - [self setNewsView:nil]; - [super viewDidUnload]; -} @end diff --git a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard index 79bdd589..1e4ff3fd 100644 --- a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard +++ b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard @@ -1,7 +1,7 @@ - + - + @@ -2127,6 +2127,6 @@ You could use the word wall for inspiration in finding a memorable master passw - + \ No newline at end of file