2
0

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:
Maarten Billemont 2013-01-31 00:42:32 -05:00
parent 725da285da
commit f2ee139db6
37 changed files with 1078 additions and 1021 deletions

3
.gitmodules vendored
View File

@ -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

@ -1 +1 @@
Subproject commit b748092775b2ee07c4c494434b43c72fb589ab67
Subproject commit 39ac68e0fb34f0c6e2f455f132b56fd158630ee7

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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];
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;
return NO;
}
if (didRequireExplicitMigration)
user.requiresExplicitMigration = NO;
BOOL requiresExplicitMigration = NO;
for (MPElementEntity *migrationElement in migrationElements)
if (![migrationElement migrateExplicitly:NO])
user.requiresExplicitMigration = YES;
requiresExplicitMigration = YES;
dispatch_async(dispatch_get_main_queue(), ^{
completion(!didRequireExplicitMigration && user.requiresExplicitMigration);
});
}];
return requiresExplicitMigration;
}
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {

View File

@ -28,9 +28,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
inf(@"Found key in keychain for: %@", user.userID);
else {
[user.managedObjectContext performBlockAndWait:^{
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;
}];
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;
}];
// 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];
}];
}
}
[[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.managedObjectContext performBlockAndWait:^{
user.lastUsed = [NSDate date];
[user saveContext];
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}];
objc_setAssociatedObject(self, &managedObjectContextKey, managedObjectContext, OBJC_ASSOCIATION_RETAIN);
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;
}];
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,13 +82,10 @@ 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];
@ -84,16 +115,21 @@ static char managedObjectContextKey;
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])
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) {
@ -101,15 +137,19 @@ static char managedObjectContextKey;
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);
}
} 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)
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)
if (error) {
err(@"Error loading the site pattern: %@", error);
}
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,31 +512,21 @@ static char managedObjectContextKey;
if ([importKey.keyID isEqualToData:importKeyID])
importKey = nil;
BOOL success = NO;
[self.managedObjectContextIfReady.undoManager beginUndoGrouping];
@try {
// 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];
}];
[moc deleteObject:obj];
}];
// Make sure there is a user.
if (!user) {
[self.managedObjectContextIfReady performBlockAndWait:^{
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:self.managedObjectContextIfReady];
inManagedObjectContext:moc];
user.name = importUserName;
user.keyID = importKeyID;
}];
dbg(@"Created User: %@", [user debugDescription]);
}
[self saveContext];
// Import new sites.
for (NSArray *siteElements in importedSiteElements) {
@ -473,11 +538,9 @@ static char managedObjectContextKey;
NSString *exportContent = [siteElements objectAtIndex:5];
// Create new site.
__block MPImportResult result = MPImportResultSuccess;
[self.managedObjectContextIfReady performBlockAndWait:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion(
version) classNameOfType:type]
inManagedObjectContext:self.managedObjectContextIfReady];
inManagedObjectContext:moc];
element.name = name;
element.user = user;
element.type = type;
@ -490,23 +553,21 @@ static char managedObjectContextKey;
else {
if (!importKey)
importKey = [importAlgorithm keyForPassword:importPassword(user.name) ofUserNamed:user.name];
if (![importKey.keyID isEqualToData:importKeyID]) {
result = MPImportResultInvalidPassword;
return;
}
if (![importKey.keyID isEqualToData:importKeyID])
return MPImportResultInvalidPassword;
[element importProtectedContent:exportContent protectedByKey:importKey usingKey:userKey];
}
}
dbg(@"Created Element: %@", [element debugDescription]);
}];
if (result != MPImportResultSuccess)
return result;
}
[self saveContext];
success = YES;
if (![moc save:&error]) {
err(@"While saving imported sites: %@", error);
return MPImportResultInternalError;
}
inf(@"Import completed successfully.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesImported];
@ -516,18 +577,12 @@ static char managedObjectContextKey;
#endif
return MPImportResultSuccess;
}
@finally {
[self.managedObjectContextIfReady.undoManager endUndoGrouping];
if (!success)
[self.managedObjectContextIfReady.undoManager undoNestedGroup];
}
}
- (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;

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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"

View File

@ -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;

View File

@ -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];

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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;
@ -88,12 +88,10 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
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];
NSArray *users = [moc executeFetchRequest:fetchRequest error:&error];
if (!users)
err(@"Failed to load users: %@", error);
@ -107,18 +105,17 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
for (MPUserEntity *user in users) {
NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector(selectUser:) keyEquivalent:@""];
[userItem setTarget:self];
[userItem setRepresentedObject:user.objectID];
[userItem setRepresentedObject:[user objectID]];
[[self.usersItem submenu] addItem: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;
}

View File

@ -34,15 +34,19 @@
[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)
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
@ -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);

View File

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MasterPassword 3.xcdatamodel</string>
<string>MasterPassword 4.xcdatamodel</string>
</dict>
</plist>

View File

@ -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>

View File

@ -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>

View File

@ -14,7 +14,6 @@
+ (MPAppDelegate *)get;
- (void)checkConfig;
- (void)showGuide;
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;

View File

@ -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,6 +585,7 @@
if (buttonIndex == [alert cancelButtonIndex])
return;
[user.managedObjectContext performBlock:^{
inf(@"Unsetting master password for: %@.", user.userID);
user.keyID = nil;
[self forgetSavedKeyFor:user];
@ -614,6 +598,7 @@
[TestFlight passCheckpoint:MPCheckpointChangeMP];
#endif
[[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

View File

@ -11,9 +11,14 @@
@implementation MPGuideViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
- (BOOL)shouldAutorotate {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
return NO;
}
- (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_ {

View File

@ -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;

View File

@ -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 interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
return YES;
}
- (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
usingBlock:^void(NSNotification *note) {
if (self.activeElement.type & MPElementTypeClassStored
&& ![[self.activeElement.content description] length])
[[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 (self.activeElement.requiresExplicitMigration)
if (activeElement.requiresExplicitMigration)
[self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil queue:nil
usingBlock:^void(NSNotification *note) {
self.activeElement = nil;
_activeElementOID = nil;
self.suppressOutdatedAlert = NO;
[self updateAnimated:NO];
}];
[super viewDidLoad];
@ -123,14 +104,16 @@
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;
@ -138,7 +121,7 @@
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;
[MPiOSConfig get].actionsTipShown = @YES;
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])
if (!_activeElementOID)
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.searchTipContainer.alpha = 1;
}];
}];
});
}
}];
if ([MPAppDelegate get].activeUser)
[MPAlgorithmDefault migrateUser:[MPAppDelegate get].activeUser completion:^(BOOL userRequiresNewMigration) {
if (userRequiresNewMigration)
[UIView animateWithDuration:0.3f animations:^{
self.outdatedAlertContainer.alpha = 1;
}];
}];
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Main"];
@ -204,7 +188,8 @@
return;
}
[self setHelpChapter:self.activeElement? @"2": @"1"];
[self activeElementDo:^(MPElementEntity *activeElement) {
[self setHelpChapter:activeElement? @"2": @"1"];
[self updateHelpHiddenAnimated:NO];
self.passwordCounter.alpha = 0;
@ -212,36 +197,39 @@
self.passwordEdit.alpha = 0;
self.passwordUpgrade.alpha = 0;
self.passwordUser.alpha = 0;
self.displayContainer.alpha = 0;
if (self.activeElement)
if (activeElement) {
self.passwordUser.alpha = 0.5f;
self.displayContainer.alpha = 1.0f;
}
if (self.activeElement.requiresExplicitMigration)
if (activeElement.requiresExplicitMigration)
self.passwordUpgrade.alpha = 0.5f;
else {
if (self.activeElement.type & MPElementTypeClassGenerated) {
if (activeElement.type & MPElementTypeClassGenerated) {
self.passwordCounter.alpha = 0.5f;
self.passwordIncrementer.alpha = 0.5f;
} else
if (self.activeElement.type & MPElementTypeClassStored)
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
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])
if (activeElement.name && ![activeElement isDeleted])
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [self.activeElement.content description];
NSString *description = [activeElement.content description];
dispatch_async(dispatch_get_main_queue(), ^{
self.contentField.text = description;
@ -249,10 +237,10 @@
});
self.loginNameField.enabled = NO;
self.loginNameField.text = self.activeElement.loginName;
self.siteInfoHidden = !self.activeElement || ([[MPiOSConfig get].siteInfoHidden boolValue] && (self.activeElement.loginName
== nil));
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 {
[self activeElementDo:^(MPElementEntity *activeElement) {
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
PearlString(@"setClass('%@');", self.activeElement.typeClassName)];
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;
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;
wSelf.toolTipCleanup = nil;
};
icon.hidden = NO;
@ -465,12 +457,13 @@
- (IBAction)copyContent {
id content = self.activeElement.content;
[self activeElementDo:^(MPElementEntity *activeElement) {
id content = activeElement.content;
if (!content)
// Nothing to copy.
return;
inf(@"Copying password for: %@", self.activeElement.name);
inf(@"Copying password for: %@", activeElement.name);
[UIPasteboard generalPasteboard].string = [content description];
[self showContentTip:@"Copied!" withIcon:nil];
@ -478,18 +471,19 @@
#ifdef TESTFLIGHT_SDK_VERSION
[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)
[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!"];
@ -497,31 +491,35 @@
[TestFlight passCheckpoint:MPCheckpointCopyLoginNameToPasteboard];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyLoginNameToPasteboard
attributes:@{@"type": self.activeElement.typeName,
@"version": @(self.activeElement.version)}];
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]])
__block BOOL abort = NO;
[self activeElementDo:^(MPElementEntity *activeElement) {
if (![activeElement isKindOfClass:[MPElementGeneratedEntity class]]) {
// Not of a type that supports a password counter.
return;
if (((MPElementGeneratedEntity *)self.activeElement).counter == 1)
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,7 +566,8 @@
// Only fire when the gesture was first detected.
return;
if (!self.activeElement)
[self activeElementDo:^(MPElementEntity *activeElement) {
if (!activeElement)
return;
self.loginNameField.enabled = YES;
@ -569,28 +576,42 @@
#ifdef TESTFLIGHT_SDK_VERSION
[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 activeElementDo:^(MPElementEntity *activeElement) {
NSManagedObjectContext *moc = activeElement.managedObjectContext;
[moc performBlock:^{
// 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.
@ -600,45 +621,81 @@
@"IMPORTANT:\n"
@"Don't forget to update the site with your new password! "
@"Your old password was:\n"
@"%@", self.activeElement.name, oldPassword)];
@"%@", 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?
__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."
do:^{
inf(@"Explicitly migrating element: %@", self.activeElement);
[self.activeElement migrateExplicitly:YES];
@"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];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration
attributes:@{@"type": self.activeElement.typeName,
@"version": @(self.activeElement.version)}];
attributes:@{@"type" : activeElement.typeName,
@"version" : @(activeElement.version)}];
return YES;
}];
}
@ -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)
do:^BOOL(MPElementEntity *activeElement){
if ([activeElement.algorithm classOfType:type] != 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;
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];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", self.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;
self.searchDisplayController.searchBar.text = activeElement.name;
[[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:activeElement.objectID];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", activeElement.typeShortName)];
#endif
[[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]])
__block BOOL abort = NO;
[self activeElementDo:^(MPElementEntity *activeElement) {
if (![activeElement isKindOfClass:[MPElementStoredEntity class]]) {
// Not of a type whose content can be edited.
return;
if ([((MPElementStoredEntity *)self.activeElement).content isEqual:self.contentField.text])
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;
[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;
}
[self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement) {
if ([self.loginNameField.text length])
self.activeElement.loginName = self.loginNameField.text;
activeElement.loginName = self.loginNameField.text;
else
self.activeElement.loginName = nil;
activeElement.loginName = nil;
[[MPAppDelegate get] saveContext];
return YES;
}];
}
}

View File

@ -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 (interfaceOrientation == UIInterfaceOrientationPortrait);
return NO;
}
- (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 {

View File

@ -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)}];
}];
}
}

View File

@ -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];

View File

@ -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,17 +294,15 @@
- (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];
if (finished)
[newUser saveContext];
}];
}];
}
@ -328,7 +326,9 @@
if (![alert textFieldAtIndex:0].text.length) {
[PearlAlert showAlertWithTitle:@"Name Is Required" message:nil viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
[newUser.managedObjectContext performBlock:^{
[self showNewUserNameAlertFor:newUser completion:completion];
}];
} cancelTitle:@"Try Again" otherTitles:nil];
return;
}
@ -336,8 +336,8 @@
// Save
[newUser.managedObjectContext performBlock:^{
newUser.name = [alert textFieldAtIndex:0].text;
}];
[self showNewUserAvatarAlertFor:newUser completion:completion];
}];
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
}
@ -352,7 +352,9 @@
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
// Okay
[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]) {
[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];
}];
[[MPAppDelegate get] saveContext];
[targetedUser.managedObjectContext performBlock:^{
[targetedUser.managedObjectContext deleteObject:targetedUser];
[targetedUser saveContext];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUsers];
});
}];
return;
}
if (buttonIndex == [sheet firstOtherButtonIndex])
[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;
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;
}
[[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];
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

View File

@ -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>