Social update, CoreData overhaul & iOS6/rotation fixes.
[REMOVED] Facebook SDK. [ADDED] Social framework integration for Twitter & Facebook. [MOVED] User migration warning state moved into MainVC, out of data model. [UPDATED] Major overhaul of Core Data integration. Multiple contexts and making sure we're on the right thread and the right context even for read access. [FIXED] Some iOS 6 deprecation fixes. [FIXED] Some VC rotation issues.
This commit is contained in:
parent
725da285da
commit
f2ee139db6
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
2
External/iCloudStoreManager
vendored
2
External/iCloudStoreManager
vendored
@ -1 +1 @@
|
||||
Subproject commit b748092775b2ee07c4c494434b43c72fb589ab67
|
||||
Subproject commit 39ac68e0fb34f0c6e2f455f132b56fd158630ee7
|
@ -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 = "<group>"; };
|
||||
DA609F571600CE980030AE31 /* WebserviceConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebserviceConstants.h; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
DA6701BC16406B1C00B61001 /* Facebook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Facebook.h; sourceTree = "<group>"; };
|
||||
DA6701BD16406B1C00B61001 /* FacebookSDK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookSDK.h; sourceTree = "<group>"; };
|
||||
DA6701BE16406B1C00B61001 /* FBCacheDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBCacheDescriptor.h; sourceTree = "<group>"; };
|
||||
DA6701BF16406B1C00B61001 /* FBConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBConnect.h; sourceTree = "<group>"; };
|
||||
DA6701C016406B1C00B61001 /* FBDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBDialog.h; sourceTree = "<group>"; };
|
||||
DA6701C116406B1C00B61001 /* FBError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBError.h; sourceTree = "<group>"; };
|
||||
DA6701C216406B1C00B61001 /* FBFrictionlessRequestSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFrictionlessRequestSettings.h; sourceTree = "<group>"; };
|
||||
DA6701C316406B1C00B61001 /* FBFriendPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFriendPickerViewController.h; sourceTree = "<group>"; };
|
||||
DA6701C416406B1C00B61001 /* FBGraphLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphLocation.h; sourceTree = "<group>"; };
|
||||
DA6701C516406B1C00B61001 /* FBGraphObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphObject.h; sourceTree = "<group>"; };
|
||||
DA6701C616406B1C00B61001 /* FBGraphPlace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphPlace.h; sourceTree = "<group>"; };
|
||||
DA6701C716406B1C00B61001 /* FBGraphUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphUser.h; sourceTree = "<group>"; };
|
||||
DA6701C816406B1C00B61001 /* FBLoginDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBLoginDialog.h; sourceTree = "<group>"; };
|
||||
DA6701C916406B1C00B61001 /* FBLoginView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBLoginView.h; sourceTree = "<group>"; };
|
||||
DA6701CA16406B1C00B61001 /* FBNativeDialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBNativeDialogs.h; sourceTree = "<group>"; };
|
||||
DA6701CB16406B1C00B61001 /* FBOpenGraphAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBOpenGraphAction.h; sourceTree = "<group>"; };
|
||||
DA6701CC16406B1C00B61001 /* FBPlacePickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBPlacePickerViewController.h; sourceTree = "<group>"; };
|
||||
DA6701CD16406B1C00B61001 /* FBProfilePictureView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBProfilePictureView.h; sourceTree = "<group>"; };
|
||||
DA6701CE16406B1C00B61001 /* FBRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBRequest.h; sourceTree = "<group>"; };
|
||||
DA6701CF16406B1C00B61001 /* FBRequestConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBRequestConnection.h; sourceTree = "<group>"; };
|
||||
DA6701D016406B1C00B61001 /* FBSBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSBJSON.h; sourceTree = "<group>"; };
|
||||
DA6701D116406B1C00B61001 /* FBSBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSBJsonBase.h; sourceTree = "<group>"; };
|
||||
DA6701D216406B1C00B61001 /* FBSBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSBJsonParser.h; sourceTree = "<group>"; };
|
||||
DA6701D316406B1C00B61001 /* FBSBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSBJsonWriter.h; sourceTree = "<group>"; };
|
||||
DA6701D416406B1C00B61001 /* FBSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSession.h; sourceTree = "<group>"; };
|
||||
DA6701D516406B1C00B61001 /* FBSessionManualTokenCachingStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSessionManualTokenCachingStrategy.h; sourceTree = "<group>"; };
|
||||
DA6701D616406B1C00B61001 /* FBSessionTokenCachingStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSessionTokenCachingStrategy.h; sourceTree = "<group>"; };
|
||||
DA6701D716406B1C00B61001 /* FBSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSettings.h; sourceTree = "<group>"; };
|
||||
DA6701D816406B1C00B61001 /* FBTestSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBTestSession.h; sourceTree = "<group>"; };
|
||||
DA6701D916406B1C00B61001 /* FBUserSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBUserSettingsViewController.h; sourceTree = "<group>"; };
|
||||
DA6701DA16406B1C00B61001 /* FBViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBViewController.h; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = "<group>"; };
|
||||
DA81252E16B8544400F4732F /* MasterPassword 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 4.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA81253316B8546A00F4732F /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
||||
DA81253416B8546A00F4732F /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
||||
DA81253616B8546B00F4732F /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DA81253716B8546B00F4732F /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DA81253916B8546B00F4732F /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||
DA81253A16B8546B00F4732F /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||
DA81253C16B8546C00F4732F /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DA81253D16B8546C00F4732F /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
DA829E5F15984812002417D3 /* UIFont+Replacement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+Replacement.m"; sourceTree = "<group>"; };
|
||||
@ -1243,14 +1217,6 @@
|
||||
DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKPSToggleSwitchSpecifierViewCell.xib; sourceTree = "<group>"; };
|
||||
DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
DAA096FB15E0C59B00912D63 /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
||||
DAA096FD15E0C59B00912D63 /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DAA096FE15E0C59B00912D63 /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
DAA0970015E0C59B00912D63 /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DAA0970115E0C59B00912D63 /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DAA0970315E0C59B00912D63 /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||
DAA0970415E0C59B00912D63 /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@ -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 = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
@ -50,14 +50,13 @@
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
enableOpenGLFrameCaptureMode = "0"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
@required
|
||||
- (NSUInteger)version;
|
||||
- (void)migrateUser:(MPUserEntity *)user completion:(void(^)(BOOL userRequiresNewMigration))completion;
|
||||
- (BOOL)migrateUser:(MPUserEntity *)user;
|
||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
||||
|
@ -31,29 +31,23 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)migrateUser:(MPUserEntity *)user completion:(void(^)(BOOL userRequiresNewMigration))completion {
|
||||
- (BOOL)migrateUser:(MPUserEntity *)user {
|
||||
|
||||
BOOL didRequireExplicitMigration = user.requiresExplicitMigration;
|
||||
[user.managedObjectContext performBlock:^void() {
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d", MPAlgorithmDefaultVersion];
|
||||
NSArray *migrationElements = [user.managedObjectContext executeFetchRequest:migrationRequest error:&error];
|
||||
if (!migrationElements) {
|
||||
err(@"While looking for elements to migrate: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (didRequireExplicitMigration)
|
||||
user.requiresExplicitMigration = NO;
|
||||
for (MPElementEntity *migrationElement in migrationElements)
|
||||
if (![migrationElement migrateExplicitly:NO])
|
||||
user.requiresExplicitMigration = YES;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completion(!didRequireExplicitMigration && user.requiresExplicitMigration);
|
||||
});
|
||||
}];
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", MPAlgorithmDefaultVersion, user];
|
||||
NSArray *migrationElements = [user.managedObjectContext executeFetchRequest:migrationRequest error:&error];
|
||||
if (!migrationElements) {
|
||||
err(@"While looking for elements to migrate: %@", error);
|
||||
return NO;
|
||||
}
|
||||
|
||||
BOOL requiresExplicitMigration = NO;
|
||||
for (MPElementEntity *migrationElement in migrationElements)
|
||||
if (![migrationElement migrateExplicitly:NO])
|
||||
requiresExplicitMigration = YES;
|
||||
|
||||
return requiresExplicitMigration;
|
||||
}
|
||||
|
||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
||||
|
@ -28,9 +28,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
inf(@"Found key in keychain for: %@", user.userID);
|
||||
|
||||
else {
|
||||
[user.managedObjectContext performBlockAndWait:^{
|
||||
user.saveKey = NO;
|
||||
}];
|
||||
user.saveKey = NO;
|
||||
inf(@"No key found in keychain for: %@", user.userID);
|
||||
}
|
||||
|
||||
@ -46,12 +44,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
inf(@"Saving key in keychain for: %@", user.userID);
|
||||
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key.keyData, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
withAttributes:@{
|
||||
(__bridge id)kSecValueData : self.key.keyData,
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
#endif
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -60,14 +58,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
|
||||
if (result == noErr || result == errSecItemNotFound) {
|
||||
[user.managedObjectContext performBlockAndWait:^{
|
||||
user.saveKey = NO;
|
||||
}];
|
||||
user.saveKey = NO;
|
||||
|
||||
if (result == noErr) {
|
||||
inf(@"Removed key from keychain for: %@", user.userID);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,7 +75,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
if (self.activeUser) {
|
||||
self.activeUser = nil;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self userInfo:
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self userInfo:
|
||||
@{@"animated": @(animated)}];
|
||||
}
|
||||
}
|
||||
@ -93,9 +89,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
if (!user.keyID) {
|
||||
if ([password length])
|
||||
if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
|
||||
[user.managedObjectContext performBlockAndWait:^{
|
||||
user.keyID = tryKey.keyID;
|
||||
}];
|
||||
user.keyID = tryKey.keyID;
|
||||
|
||||
// Migrate existing elements.
|
||||
MPKey *recoverKey = nil;
|
||||
@ -133,7 +127,6 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
|
||||
dispatch_group_wait(recoverPasswordGroup, DISPATCH_TIME_FOREVER);
|
||||
#endif
|
||||
|
||||
if (!masterPassword)
|
||||
// Don't Migrate
|
||||
break;
|
||||
@ -146,12 +139,10 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
// Don't Migrate
|
||||
break;
|
||||
|
||||
[element.managedObjectContext performBlockAndWait:^{
|
||||
[element setContent:content usingKey:tryKey];
|
||||
}];
|
||||
[element setContent:content usingKey:tryKey];
|
||||
}
|
||||
}
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
[user saveContext];
|
||||
#ifdef PEARL_UIKIT
|
||||
[activityAlert dismissAlert];
|
||||
#endif
|
||||
@ -224,15 +215,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
err(@"While setting username: %@", exception);
|
||||
}
|
||||
|
||||
user.lastUsed = [NSDate date];
|
||||
[user saveContext];
|
||||
self.activeUser = user;
|
||||
|
||||
[user.managedObjectContext performBlockAndWait:^{
|
||||
user.lastUsed = [NSDate date];
|
||||
self.activeUser = user;
|
||||
self.activeUser.requiresExplicitMigration = NO;
|
||||
}];
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedInNotification object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointSignedIn];
|
||||
#endif
|
||||
|
@ -16,9 +16,10 @@
|
||||
#endif
|
||||
|
||||
@property (strong, nonatomic) MPUserEntity *activeUser;
|
||||
@property (strong, nonatomic) NSManagedObjectID *activeUserObjectID;
|
||||
@property (strong, nonatomic) MPKey *key;
|
||||
|
||||
+ (MPAppDelegate_Shared *)get;
|
||||
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
@end
|
||||
|
@ -9,7 +9,9 @@
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
@implementation MPAppDelegate_Shared
|
||||
@implementation MPAppDelegate_Shared {
|
||||
NSManagedObjectID *_activeUserOID;
|
||||
}
|
||||
|
||||
+ (MPAppDelegate_Shared *)get {
|
||||
|
||||
@ -24,15 +26,37 @@
|
||||
|
||||
- (MPUserEntity *)activeUser {
|
||||
|
||||
if (!self.activeUserObjectID)
|
||||
if (!_activeUserOID)
|
||||
return nil;
|
||||
|
||||
return (MPUserEntity *)[self.managedObjectContextIfReady objectWithID:self.activeUserObjectID];
|
||||
NSManagedObjectContext *moc = [MPAppDelegate_Shared managedObjectContextForThreadIfReady];
|
||||
if (!moc)
|
||||
return nil;
|
||||
|
||||
NSError *error;
|
||||
MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:_activeUserOID error:&error];
|
||||
if (!activeUser)
|
||||
err(@"Failed to retrieve active user: %@", error);
|
||||
|
||||
return activeUser;
|
||||
}
|
||||
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc {
|
||||
|
||||
if (!_activeUserOID || !moc)
|
||||
return nil;
|
||||
|
||||
NSError *error;
|
||||
MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:_activeUserOID error:&error];
|
||||
if (!activeUser)
|
||||
err(@"Failed to retrieve active user: %@", error);
|
||||
|
||||
return activeUser;
|
||||
}
|
||||
|
||||
- (void)setActiveUser:(MPUserEntity *)activeUser {
|
||||
|
||||
self.activeUserObjectID = activeUser.objectID;
|
||||
_activeUserOID = activeUser.objectID;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -20,11 +20,8 @@ typedef enum {
|
||||
|
||||
@interface MPAppDelegate_Shared (Store)<UbiquityStoreManagerDelegate>
|
||||
|
||||
+ (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
|
||||
|
@ -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<MPAlgorithm> 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;
|
||||
|
@ -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 <Foundation/Foundation.h>
|
||||
|
@ -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
|
||||
|
@ -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 <Foundation/Foundation.h>
|
||||
|
@ -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"
|
||||
|
@ -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 <Foundation/Foundation.h>
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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"
|
||||
|
@ -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 <Foundation/Foundation.h>
|
||||
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MasterPassword 3.xcdatamodel</string>
|
||||
<string>MasterPassword 4.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1487" systemVersion="12A269" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="2057" systemVersion="12C60" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||
@ -32,9 +32,9 @@
|
||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="MPElementEntity" positionX="0" positionY="0" width="0" height="0"/>
|
||||
<element name="MPElementGeneratedEntity" positionX="0" positionY="0" width="0" height="0"/>
|
||||
<element name="MPElementStoredEntity" positionX="0" positionY="0" width="0" height="0"/>
|
||||
<element name="MPUserEntity" positionX="0" positionY="0" width="0" height="0"/>
|
||||
<element name="MPElementEntity" positionX="0" positionY="0" width="128" height="180"/>
|
||||
<element name="MPElementGeneratedEntity" positionX="216" positionY="-0" width="128" height="60"/>
|
||||
<element name="MPElementStoredEntity" positionX="216" positionY="144" width="128" height="60"/>
|
||||
<element name="MPUserEntity" positionX="-216" positionY="0" width="128" height="165"/>
|
||||
</elements>
|
||||
</model>
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="2057" systemVersion="12C60" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO">
|
||||
<userInfo/>
|
||||
</attribute>
|
||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||
<userInfo/>
|
||||
</attribute>
|
||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="178"/>
|
||||
<element name="MPElementGeneratedEntity" positionX="216" positionY="-288" width="128" height="58"/>
|
||||
<element name="MPElementStoredEntity" positionX="214" positionY="-171" width="128" height="58"/>
|
||||
<element name="MPUserEntity" positionX="-218" positionY="-288" width="128" height="148"/>
|
||||
</elements>
|
||||
</model>
|
@ -14,7 +14,6 @@
|
||||
|
||||
+ (MPAppDelegate *)get;
|
||||
|
||||
- (void)checkConfig;
|
||||
- (void)showGuide;
|
||||
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
|
||||
|
||||
|
@ -6,11 +6,6 @@
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wnewline-eof"
|
||||
#import <FacebookSDK/FacebookSDK.h>
|
||||
#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
|
||||
|
@ -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_ {
|
||||
|
@ -8,13 +8,11 @@
|
||||
|
||||
#import <MessageUI/MessageUI.h>
|
||||
#import "MPTypeViewController.h"
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPSearchDelegate.h"
|
||||
|
||||
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, UIGestureRecognizerDelegate>
|
||||
|
||||
@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;
|
||||
|
@ -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;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)}];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -7,12 +7,7 @@
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <Twitter/Twitter.h>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wnewline-eof"
|
||||
#import "Facebook.h"
|
||||
#pragma clang diagnostic pop
|
||||
#import <Social/Social.h>
|
||||
#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
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="2.0" toolsVersion="2840" systemVersion="12B19" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="2.0" toolsVersion="3084" systemVersion="12C60" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1926"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="2083"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Type View Controller - Type-->
|
||||
@ -2127,6 +2127,6 @@ You could use the word wall for inspiration in finding a memorable master passw
|
||||
<simulatedScreenMetrics key="destination"/>
|
||||
</simulatedMetricsContainer>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="KIl-ZW-M7G"/>
|
||||
<segue reference="9Bs-cD-ddF"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
Loading…
Reference in New Issue
Block a user