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