diff --git a/Parse.podspec b/Parse.podspec index 59a8d67b2..35e2ff30b 100644 --- a/Parse.podspec +++ b/Parse.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = 'Parse' s.version = '1.14.4' s.license = { :type => 'BSD', :file => 'LICENSE' } - s.homepage = 'https://www.parse.com/' + s.homepage = 'http://parseplatform.org/' s.summary = 'A library that gives you access to the powerful Parse cloud platform from your iOS/OS X/watchOS/tvOS app.' s.authors = 'Parse' diff --git a/Parse.xcodeproj/project.pbxproj b/Parse.xcodeproj/project.pbxproj index 8d9e5a874..4747f2810 100644 --- a/Parse.xcodeproj/project.pbxproj +++ b/Parse.xcodeproj/project.pbxproj @@ -2566,6 +2566,13 @@ 97DE045A16321492007154E8 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97DE045916321492007154E8 /* Security.framework */; }; 97DE045C163214C0007154E8 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97DE045B163214C0007154E8 /* SystemConfiguration.framework */; }; 97EB055516F7CCE400E09147 /* PFAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 9739513916B9D28E0010B884 /* PFAnalytics.m */; }; + A6E2958C1E96173D009917BF /* ParseManagerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = A6E295801E961727009917BF /* ParseManagerPrivate.h */; }; + A6E2958D1E96173F009917BF /* ParseManagerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = A6E295801E961727009917BF /* ParseManagerPrivate.h */; }; + A6E2958E1E961741009917BF /* ParseManagerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = A6E295801E961727009917BF /* ParseManagerPrivate.h */; }; + A6E2958F1E961742009917BF /* ParseManagerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = A6E295801E961727009917BF /* ParseManagerPrivate.h */; }; + A6E295901E961744009917BF /* ParseManagerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = A6E295801E961727009917BF /* ParseManagerPrivate.h */; }; + A6E295911E961744009917BF /* ParseManagerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = A6E295801E961727009917BF /* ParseManagerPrivate.h */; }; + A6E295921E961744009917BF /* ParseManagerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = A6E295801E961727009917BF /* ParseManagerPrivate.h */; }; B141169E1E5BC24B00F70D7A /* PFFileUploadController.h in Headers */ = {isa = PBXBuildFile; fileRef = B141169D1E5BC24B00F70D7A /* PFFileUploadController.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14116F41E5D075C00F70D7A /* PFFileUploadController.h in Headers */ = {isa = PBXBuildFile; fileRef = B141169D1E5BC24B00F70D7A /* PFFileUploadController.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14116F51E5D075E00F70D7A /* PFFileUploadController.h in Headers */ = {isa = PBXBuildFile; fileRef = B141169D1E5BC24B00F70D7A /* PFFileUploadController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -3491,6 +3498,7 @@ 97DE045B163214C0007154E8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 97E18AE41623835600B17A67 /* PFLocationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFLocationManager.h; sourceTree = ""; }; 97E18AE51623835600B17A67 /* PFLocationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFLocationManager.m; sourceTree = ""; }; + A6E295801E961727009917BF /* ParseManagerPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParseManagerPrivate.h; sourceTree = ""; }; B141169D1E5BC24B00F70D7A /* PFFileUploadController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFileUploadController.h; sourceTree = ""; }; B14116FB1E5D078E00F70D7A /* PFFileUploadResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFileUploadResult.m; sourceTree = ""; }; B141170A1E5D081500F70D7A /* PFFileUploadResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFileUploadResult.h; sourceTree = ""; }; @@ -3759,6 +3767,7 @@ 09EEA1351435143500E3A3FA /* ParseInternal.h */, 81A245F11B1FB188006A6953 /* PFDataProvider.h */, 812714861AE6F1270076AE8D /* ParseManager.h */, + A6E295801E961727009917BF /* ParseManagerPrivate.h */, 812714871AE6F1270076AE8D /* ParseManager.m */, 8124C8811B27542A00758E00 /* PFCoreDataProvider.h */, 8196D58B1B0BD23B000465A1 /* PFCoreManager.h */, @@ -5418,6 +5427,7 @@ 810156481BB3832700D7C7BD /* PFURLSessionDataTaskDelegate.h in Headers */, 81CA29D21C28DF8F00C4F34A /* PFAnonymousUtils+Deprecated.h in Headers */, 810156491BB3832700D7C7BD /* PFDateFormatter.h in Headers */, + A6E295911E961744009917BF /* ParseManagerPrivate.h in Headers */, 8101564A1BB3832700D7C7BD /* PFCloudCodeController.h in Headers */, 8101564B1BB3832700D7C7BD /* PFMultiProcessFileLockController.h in Headers */, 8101564C1BB3832700D7C7BD /* PFCurrentUserController.h in Headers */, @@ -5580,6 +5590,7 @@ 815F23C51BD04D150054659F /* PFURLSessionCommandRunner.h in Headers */, 815F23C61BD04D150054659F /* PFRESTObjectCommand.h in Headers */, 815F23C71BD04D150054659F /* PFCommandRunning.h in Headers */, + A6E2958F1E961742009917BF /* ParseManagerPrivate.h in Headers */, 815F23C81BD04D150054659F /* PFRESTCloudCommand.h in Headers */, 815F23C91BD04D150054659F /* PFProduct.h in Headers */, 815F23CA1BD04D150054659F /* PFQuery.h in Headers */, @@ -5685,6 +5696,7 @@ 818AAA7619D36B1C00FC1B3C /* PFFile.h in Headers */, 816AC9BA1A3F48250031D94C /* PFApplication.h in Headers */, F5B0B2EC1B449F1D00F3EBC4 /* BFTask+Private.h in Headers */, + A6E2958C1E96173D009917BF /* ParseManagerPrivate.h in Headers */, F5B0B2ED1B449F1D00F3EBC4 /* PFCategoryLoader.h in Headers */, F5B0B2EE1B449F1D00F3EBC4 /* PFThreadsafety.h in Headers */, F5B0B2EF1B449F1D00F3EBC4 /* PFRelationState_Private.h in Headers */, @@ -6031,6 +6043,7 @@ 81C584121C3B0A98000063C6 /* PFFilePersistenceGroup.h in Headers */, 81C584131C3B0A98000063C6 /* PFRESTQueryCommand.h in Headers */, 81C584141C3B0A98000063C6 /* PFACLState_Private.h in Headers */, + A6E2958D1E96173F009917BF /* ParseManagerPrivate.h in Headers */, 81C584151C3B0A98000063C6 /* PFConfig+Synchronous.h in Headers */, 81C584161C3B0A98000063C6 /* PFObject.h in Headers */, 81C584171C3B0A98000063C6 /* PFPushUtilities.h in Headers */, @@ -6238,6 +6251,7 @@ 81C585701C3B0AA1000063C6 /* PFURLSessionCommandRunner.h in Headers */, 81C585711C3B0AA1000063C6 /* PFRESTObjectCommand.h in Headers */, 81C585721C3B0AA1000063C6 /* PFCommandRunning.h in Headers */, + A6E295901E961744009917BF /* ParseManagerPrivate.h in Headers */, 81C585731C3B0AA1000063C6 /* PFRESTCloudCommand.h in Headers */, 81C585741C3B0AA1000063C6 /* PFProduct.h in Headers */, 81C585751C3B0AA1000063C6 /* PFQuery.h in Headers */, @@ -6483,6 +6497,7 @@ 81C586EF1C3B0AA9000063C6 /* PFURLSessionDataTaskDelegate.h in Headers */, 81C586F01C3B0AA9000063C6 /* PFAnonymousUtils+Deprecated.h in Headers */, 81C586F11C3B0AA9000063C6 /* PFDateFormatter.h in Headers */, + A6E295921E961744009917BF /* ParseManagerPrivate.h in Headers */, 81C586F21C3B0AA9000063C6 /* PFCloudCodeController.h in Headers */, 81C586F31C3B0AA9000063C6 /* PFMultiProcessFileLockController.h in Headers */, 81C586F41C3B0AA9000063C6 /* PFCurrentUserController.h in Headers */, @@ -6558,6 +6573,7 @@ F5B0B3261B44A33100F3EBC4 /* PFAssert.h in Headers */, F5B0B3271B44A33100F3EBC4 /* PFAsyncTaskQueue.h in Headers */, F5B0B3281B44A33100F3EBC4 /* PFBaseState.h in Headers */, + A6E2958E1E961741009917BF /* ParseManagerPrivate.h in Headers */, F51535091B57240900C49F56 /* PFMutableACLState.h in Headers */, F5B0B32B1B44A33100F3EBC4 /* PFCoreDataProvider.h in Headers */, 814881611B795CD4008763BF /* PFMultiProcessFileLock.h in Headers */, diff --git a/Parse/Internal/LocalDataStore/OfflineStore/PFOfflineStore.m b/Parse/Internal/LocalDataStore/OfflineStore/PFOfflineStore.m index 262d3aaaa..f34386f6d 100644 --- a/Parse/Internal/LocalDataStore/OfflineStore/PFOfflineStore.m +++ b/Parse/Internal/LocalDataStore/OfflineStore/PFOfflineStore.m @@ -1000,6 +1000,16 @@ - (void)updateObjectIdForObject:(PFObject *)object oldObjectId:(NSString *)oldObjectId newObjectId:(NSString *)newObjectId { if (oldObjectId != nil) { +#if TARGET_OS_IOS + if ([object isKindOfClass:[PFInstallation class]] + && newObjectId == nil) { + NSString *key = [self _generateKeyForClassName:object.parseClassName objectId:oldObjectId]; + @synchronized(self.lock) { + [self.classNameAndObjectIdToObjectMap removeObjectForKey:key]; + } + return; + } +#endif PFConsistencyAssert([oldObjectId isEqualToString:newObjectId], @"objectIds cannot be changed in offline mode."); return; } diff --git a/Parse/Internal/ParseManager.m b/Parse/Internal/ParseManager.m index daa908252..3c8be934b 100644 --- a/Parse/Internal/ParseManager.m +++ b/Parse/Internal/ParseManager.m @@ -27,6 +27,7 @@ #import "PFUser.h" #import "PFURLSessionCommandRunner.h" #import "PFPersistenceController.h" +#import "ParseManagerPrivate.h" #if !TARGET_OS_WATCH && !TARGET_OS_TV #import "PFPushManager.h" @@ -304,6 +305,11 @@ - (PFInstallationIdentifierStore *)installationIdentifierStore { #pragma mark CommandRunner +// Set Command Runner. Used for testing. +- (void)setCommandRunner:(id)commandRunner { + _commandRunner = commandRunner; +} + - (id)commandRunner { __block id runner = nil; dispatch_sync(_commandRunnerAccessQueue, ^{ diff --git a/Parse/Internal/ParseManagerPrivate.h b/Parse/Internal/ParseManagerPrivate.h new file mode 100644 index 000000000..dc95e93db --- /dev/null +++ b/Parse/Internal/ParseManagerPrivate.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ParseManager.h" + +@interface ParseManager () + +- (void)setCommandRunner:(id)commandRunner; + +@end diff --git a/Parse/PFInstallation.m b/Parse/PFInstallation.m index 18b25223c..161ade665 100644 --- a/Parse/PFInstallation.m +++ b/Parse/PFInstallation.m @@ -28,6 +28,8 @@ #import "PFQueryPrivate.h" #import "Parse_Private.h" #import "PFErrorUtilities.h" +#import "PFObjectState_Private.h" +#import "PFObjectConstants.h" @implementation PFInstallation (Private) @@ -36,7 +38,8 @@ @implementation PFInstallation (Private) + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - protectedKeys = PF_SET(PFInstallationKeyDeviceType, + protectedKeys = PF_SET(PFObjectObjectIdRESTKey, + PFInstallationKeyDeviceType, PFInstallationKeyInstallationId, PFInstallationKeyTimeZone, PFInstallationKeyLocaleIdentifier, @@ -83,6 +86,14 @@ - (NSString *)displayClassName { return NSStringFromClass([PFInstallation class]); } +///-------------------------------------- +#pragma mark - Properties +///-------------------------------------- + +- (void) setObjectId:(NSString *)objectId { + PFParameterAssertionFailure(@"Installation's objectIds cannot be changed"); +} + ///-------------------------------------- #pragma mark - Command Handlers ///-------------------------------------- @@ -223,16 +234,13 @@ - (void)setChannels:(NSArray *)channels { - (BFTask *)saveAsync:(BFTask *)toAwait { return [[super saveAsync:toAwait] continueWithBlock:^id(BFTask *task) { - // Do not attempt to resave an object if LDS is enabled, since changing objectId is not allowed. - if ([Parse _currentManager].offlineStoreLoaded) { - return task; - } - if (task.error.code == kPFErrorObjectNotFound) { @synchronized (self.lock) { // Retry the fetch as a save operation because this Installation was deleted on the server. // We always want [currentInstallation save] to succeed. - self.objectId = nil; + PFObjectState *state = [PFObjectState stateWithState:self._state]; + state.objectId = nil; + self._state = state; [self _markAllFieldsDirty]; return [super saveAsync:nil]; } diff --git a/Tests/Unit/InstallationUnitTests.m b/Tests/Unit/InstallationUnitTests.m index 5aad50ab0..3d5941873 100644 --- a/Tests/Unit/InstallationUnitTests.m +++ b/Tests/Unit/InstallationUnitTests.m @@ -6,10 +6,16 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ +#import #import "PFInstallation.h" #import "PFUnitTestCase.h" #import "Parse.h" +#import "Parse_Private.h" +#import "PFCommandRunning.h" +#import "ParseManagerPrivate.h" +#import "PFObjectState.h" +#import "PFObjectPrivate.h" @interface InstallationUnitTests : PFUnitTestCase @@ -17,6 +23,47 @@ @interface InstallationUnitTests : PFUnitTestCase @implementation InstallationUnitTests +- (void)testInstallationObjectIdCannotBeChanged { + PFInstallation *installation = [PFInstallation currentInstallation]; + PFAssertThrowsInvalidArgumentException(installation.objectId = nil); + PFAssertThrowsInvalidArgumentException(installation[@"objectId"] = @"abc"); +} + +- (void)testObjectNotFoundWhenSave { +#if TARGET_OS_IOS + // enable LDS + [[Parse _currentManager]loadOfflineStoreWithOptions:0]; + PFOfflineStore *offlineStoreSpy = PFPartialMock([Parse _currentManager].offlineStore); + [Parse _currentManager].offlineStore = offlineStoreSpy; + + // create and save installation + PFInstallation *installation = [PFInstallation currentInstallation]; + PFObjectState *state = [PFObjectState stateWithParseClassName:[PFInstallation parseClassName] objectId:@"abc" isComplete:YES]; + installation._state = state; + [installation save]; + + // mocking installation was deleted on the server + id commandRunner = PFStrictProtocolMock(@protocol(PFCommandRunning)); + [Parse _currentManager].commandRunner = commandRunner; + + BFTask *mockedTask = [BFTask taskWithError:[NSError errorWithDomain:@"Object Not Found" code:kPFErrorObjectNotFound userInfo:nil]]; + + __block int callCount = 0; + OCMStub([commandRunner runCommandAsync:[OCMArg any] withOptions:PFCommandRunningOptionRetryIfFailed]) + .andReturn(mockedTask) + .andDo(^(NSInvocation *invocation) { + callCount++; + }); + + installation.deviceToken = @"11433856eed2f1285fb3aa11136718c1198ed5647875096952c66bf8cb976306"; + [installation save]; + OCMVerifyAll(commandRunner); + XCTAssertEqual(2, callCount); + OCMVerify([offlineStoreSpy updateObjectIdForObject:installation oldObjectId:nil newObjectId:@"abc"]); + OCMVerify([offlineStoreSpy updateObjectIdForObject:installation oldObjectId:@"abc" newObjectId:nil]); +#endif +} + - (void)testInstallationImmutableFieldsCannotBeChanged { PFInstallation *installation = [PFInstallation currentInstallation]; installation.deviceToken = @"11433856eed2f1285fb3aa11136718c1198ed5647875096952c66bf8cb976306";