diff --git a/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.h b/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.h index 78ea6764b45eddca1fdd1c95a5e2fbaa1312e0b6..222f7247ccc180fed9a1137f66945e6feb6a54df 100644 --- a/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.h +++ b/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.h @@ -24,6 +24,26 @@ #import <Foundation/Foundation.h> #import "CISDOBShared.h" +// +// The names of the notifications posted by the service manager +// +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceWillLoginNotification; +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceDidLoginNotification; + +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceWillRetrieveRootLevelEntitiesNotification; +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceDidRetrieveRootLevelEntitiesNotification; + +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceWillDrillOnEntityNotification; +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceDidDrillOnEntityNotification; + +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceWillRetrieveDetailsForEntityNotification; +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceDidRetrieveDetailsForEntityNotification; + +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceWillSynchEntitiesNotification; +FOUNDATION_EXPORT NSString *const CISDOBIpadServiceDidSynchEntitiesNotification; + + + @class CISDOBIpadService, CISDOBAsyncCall, CISDOBIpadEntity; /** * \brief A class that manages a connection to the openBIS iPad service, caching data locally as CISDOBIpadEntity objects. diff --git a/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.m b/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.m index c47d11fe71a9a0ee581bc591628b3832b7a4ac77..1abd1837bdc8c453dd146c3668c31f4e2c7e10b9 100644 --- a/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.m +++ b/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.m @@ -21,21 +21,40 @@ // // + #import "CISDOBIpadServiceManager.h" #import "CISDOBIpadService.h" #import "CISDOBIpadEntity.h" #import "CISDOBConnection.h" #import "CISDOBAsyncCall.h" +NSString *const CISDOBIpadServiceWillLoginNotification = @"CISDOBIpadServiceWillLoginNotification"; +NSString *const CISDOBIpadServiceDidLoginNotification = @"CISDOBIpadServiceDidLoginNotification"; +NSString *const CISDOBIpadServiceWillRetrieveRootLevelEntitiesNotification = @"CISDOBIpadServiceWillRetrieveRootLevelEntitiesNotification"; +NSString *const CISDOBIpadServiceDidRetrieveRootLevelEntitiesNotification = @"CISDOBIpadServiceDidRetrieveRootLevelEntitiesNotification"; +NSString *const CISDOBIpadServiceWillDrillOnEntityNotification = @"CISDOBIpadServiceWillDrillOnEntityNotification"; +NSString *const CISDOBIpadServiceDidDrillOnEntityNotification = @"CISDOBIpadServiceDidDrillOnEntityNotification"; +NSString *const CISDOBIpadServiceWillRetrieveDetailsForEntityNotification = @"CISDOBIpadServiceWillRetrieveDetailsForEntityNotification"; +NSString *const CISDOBIpadServiceDidRetrieveDetailsForEntityNotification = @"CISDOBIpadServiceDidRetrieveDetailsForEntityNotification"; +NSString *const CISDOBIpadServiceWillSynchEntitiesNotification = @"CISDOBIpadServiceWillSynchEntitiesNotification"; +NSString *const CISDOBIpadServiceDidSynchEntitiesNotification = @"CISDOBIpadServiceDidSynchEntitiesNotification"; + // Internal service call that includes the private state @interface CISDOBIpadServiceManagerCall : CISDOBAsyncCall @property(weak) CISDOBIpadServiceManager *serviceManager; @property(nonatomic) CISDOBAsyncCall *serviceCall; +@property(copy) NSString *willCallNotificationName; +@property(copy) NSString *didCallNotificationName; + +@property(nonatomic) BOOL sendSynchNotifications; // Initialization - (id)initWithServiceManager:(CISDOBIpadServiceManager *)serviceManager serviceCall:(CISDOBAsyncCall *)call; +- (void)notifySuccess:(id)result; +- (void)notifyFailure:(NSError *)error; + @end // Internal class that synchronizes result data to the managed object context @@ -112,11 +131,19 @@ static NSManagedObjectContext* GetMainThreadManagedObjectContext(NSURL* storeUrl - (void)syncEntities:(NSArray *)rawEntities pruning:(BOOL)prune notifying:(CISDOBIpadServiceManagerCall *)managerCall { void (^syncBlock)(void) = ^{ + if (managerCall.didCallNotificationName) { + [[NSNotificationCenter defaultCenter] postNotificationName: managerCall.didCallNotificationName object: self]; + } + + [[NSNotificationCenter defaultCenter] postNotificationName: CISDOBIpadServiceWillSynchEntitiesNotification object: self]; + // Run the synchronizer in the background thread CISDOBBackgroundDataSynchronizer *synchronizer = [[CISDOBBackgroundDataSynchronizer alloc] initWithServiceManager: self managerCall: managerCall rawEntities: rawEntities]; synchronizer.prune = prune; [synchronizer run]; + [[NSNotificationCenter defaultCenter] postNotificationName: CISDOBIpadServiceDidSynchEntitiesNotification object: self]; + void (^notifyBlock)(void) = ^ { // Save the MOC and notifiy the client on the main thread CISDOBBackgroundDataSynchronizer *notifySynchronizer = synchronizer; @@ -154,26 +181,50 @@ static NSManagedObjectContext* GetMainThreadManagedObjectContext(NSURL* storeUrl - (CISDOBAsyncCall *)loginUser:(NSString *)user password:(NSString *)password { - return [self.service loginUser: user password: password]; + CISDOBAsyncCall *call = [self.service loginUser: user password: password]; + CISDOBIpadServiceManagerCall *managerCall = [self managerCallWrappingServiceCall: call pruning: NO]; + call.success = ^(id result) { [managerCall notifySuccess: result]; }; + managerCall.willCallNotificationName = CISDOBIpadServiceWillLoginNotification; + managerCall.didCallNotificationName = CISDOBIpadServiceDidLoginNotification; + managerCall.sendSynchNotifications = NO; + return managerCall; } - (CISDOBAsyncCall *)retrieveRootLevelEntities { CISDOBAsyncCall *call = [self.service listRootLevelEntities]; // get rid of entities not mentioned in the original call - return [self managerCallWrappingServiceCall: call pruning: YES]; + CISDOBIpadServiceManagerCall *managerCall = [self managerCallWrappingServiceCall: call pruning: YES]; + + managerCall.willCallNotificationName = CISDOBIpadServiceWillRetrieveRootLevelEntitiesNotification; + managerCall.didCallNotificationName = CISDOBIpadServiceDidRetrieveRootLevelEntitiesNotification; + managerCall.sendSynchNotifications = YES; + + return managerCall; } - (CISDOBAsyncCall *)drillOnEntity:(CISDOBIpadEntity *)entity { CISDOBAsyncCall *call = [self.service drillOnEntityWithPermId: entity.permId refcon: entity.refcon]; - return [self managerCallWrappingServiceCall: call]; + CISDOBIpadServiceManagerCall *managerCall = [self managerCallWrappingServiceCall: call]; + + managerCall.willCallNotificationName = CISDOBIpadServiceWillDrillOnEntityNotification; + managerCall.didCallNotificationName = CISDOBIpadServiceDidDrillOnEntityNotification; + managerCall.sendSynchNotifications = YES; + + return managerCall; } - (CISDOBAsyncCall *)detailsForEntity:(CISDOBIpadEntity *)entity { CISDOBAsyncCall *call = [self.service detailsForEntityWithPermId: entity.permId refcon: entity.refcon]; - return [self managerCallWrappingServiceCall: call]; + CISDOBIpadServiceManagerCall *managerCall = [self managerCallWrappingServiceCall: call]; + + managerCall.willCallNotificationName = CISDOBIpadServiceWillRetrieveDetailsForEntityNotification; + managerCall.didCallNotificationName = CISDOBIpadServiceDidRetrieveDetailsForEntityNotification; + managerCall.sendSynchNotifications = YES; + + return managerCall; } - (NSArray *)allIpadEntitiesOrError:(NSError **)error; @@ -234,8 +285,32 @@ static NSManagedObjectContext* GetMainThreadManagedObjectContext(NSURL* storeUrl return self; } +- (void)sendCompletionNotification +{ + if (self.sendSynchNotifications) { + // This is handled elsewhere + } else if (self.didCallNotificationName) { + [[NSNotificationCenter defaultCenter] postNotificationName: self.didCallNotificationName object: self.serviceManager]; + } +} + +- (void)notifySuccess:(id)result +{ + if (self.success) self.success(result); + [self sendCompletionNotification]; +} + +- (void)notifyFailure:(NSError *)error +{ + if (self.fail) self.fail(error); + [self sendCompletionNotification]; +} + - (void)start { + if (self.willCallNotificationName) { + [[NSNotificationCenter defaultCenter] postNotificationName: self.willCallNotificationName object: self.serviceManager]; + } [_serviceCall start]; } @@ -316,9 +391,9 @@ static NSManagedObjectContext* GetMainThreadManagedObjectContext(NSURL* storeUrl - (void)notifyCallOfResult:(id)args { if (self.error) { - self.managerCall.serviceCall.fail(self.error); + [self.managerCall notifyFailure: self.error]; } else if (self.managerCall.success) { - self.managerCall.success(self.rawEntities); + [self.managerCall notifySuccess: self.rawEntities]; } } diff --git a/openbis-ipad/BisKit/Tests/CISDOBIpadServiceManagerTest.h b/openbis-ipad/BisKit/Tests/CISDOBIpadServiceManagerTest.h index 9f94f9e2251c2e12c47d6040b92e031ca57db4ed..6d61184aa9489ae8168f75538744c6d54d604daa 100644 --- a/openbis-ipad/BisKit/Tests/CISDOBIpadServiceManagerTest.h +++ b/openbis-ipad/BisKit/Tests/CISDOBIpadServiceManagerTest.h @@ -28,4 +28,21 @@ @property(strong) CISDOBIpadServiceManager *serviceManager; +// Tracking Notifications +@property(nonatomic) BOOL willLogin; +@property(nonatomic) BOOL didLogin; + + +@property(nonatomic) BOOL willRetrieveRootLevel; +@property(nonatomic) BOOL didRetrieveRootLevel; + +@property(nonatomic) BOOL willSynchEntities; +@property(nonatomic) BOOL didSynchEntities; + +@property(nonatomic) BOOL willDrill; +@property(nonatomic) BOOL didDrill; + +@property(nonatomic) BOOL willRetrieveDetails; +@property(nonatomic) BOOL didRetrieveDetails; + @end diff --git a/openbis-ipad/BisKit/Tests/CISDOBIpadServiceManagerTest.m b/openbis-ipad/BisKit/Tests/CISDOBIpadServiceManagerTest.m index 31e6efbb7ee0e345fdb0aa0b9641bb76de455c45..d8244a0fbc4889490175cc28eaeb31c21c38d584 100644 --- a/openbis-ipad/BisKit/Tests/CISDOBIpadServiceManagerTest.m +++ b/openbis-ipad/BisKit/Tests/CISDOBIpadServiceManagerTest.m @@ -29,6 +29,72 @@ @implementation CISDOBIpadServiceManagerTest +- (void)processNotification:(NSNotification *)note +{ + if ([CISDOBIpadServiceWillLoginNotification isEqualToString: [note name]]) { + self.willLogin = YES; + } + if ([CISDOBIpadServiceDidLoginNotification isEqualToString: [note name]]) { + self.didLogin = YES; + } + if ([CISDOBIpadServiceWillRetrieveRootLevelEntitiesNotification isEqualToString: [note name]]) { + self.willRetrieveRootLevel = YES; + } + if ([CISDOBIpadServiceDidRetrieveRootLevelEntitiesNotification isEqualToString: [note name]]) { + self.didRetrieveRootLevel = YES; + } + if ([CISDOBIpadServiceWillSynchEntitiesNotification isEqualToString: [note name]]) { + self.willSynchEntities = YES; + } + if ([CISDOBIpadServiceDidSynchEntitiesNotification isEqualToString: [note name]]) { + self.didSynchEntities = YES; + } + if ([CISDOBIpadServiceWillDrillOnEntityNotification isEqualToString: [note name]]) { + self.willDrill = YES; + } + if ([CISDOBIpadServiceDidDrillOnEntityNotification isEqualToString: [note name]]) { + self.didDrill = YES; + } + if ([CISDOBIpadServiceWillRetrieveDetailsForEntityNotification isEqualToString: [note name]]) { + self.willRetrieveDetails = YES; + } + if ([CISDOBIpadServiceDidRetrieveDetailsForEntityNotification isEqualToString: [note name]]) { + self.didRetrieveDetails = YES; + } +} + +- (void)registerForNotifications +{ + self.willLogin = NO; + self.didLogin = NO; + + self.willRetrieveRootLevel = NO; + self.didRetrieveRootLevel = NO; + self.willSynchEntities = NO; + self.didSynchEntities = NO; + + self.willDrill = NO; + self.didDrill = NO; + + self.willRetrieveDetails = NO; + self.didRetrieveDetails = NO; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter + addObserverForName: nil + object: self.serviceManager + queue: [NSOperationQueue mainQueue] + usingBlock: ^(NSNotification *note) { + [self processNotification: note]; + }]; +} + +- (void)unregisterForNotifications +{ + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter removeObserver: self]; +} + - (void)setUp { [super setUp]; @@ -38,10 +104,13 @@ NSError *error; self.serviceManager = [[CISDOBIpadServiceManager alloc] initWithStoreUrl: databaseUrl openbisUrl: url trusted: YES error: &error]; STAssertNotNil(self.serviceManager, @"Service Manager file could not be created:\n%@", error); + + [self registerForNotifications]; } - (void)tearDown { + [self unregisterForNotifications]; [[NSFileManager defaultManager] removeItemAtURL: self.serviceManager.storeUrl error: nil]; [super tearDown]; } @@ -86,14 +155,71 @@ } +- (void)assertNotLoggedIn +{ + STAssertFalse(self.willLogin, @"Should not be logged in yet"); + STAssertFalse(self.didLogin, @"Should not be logged in yet"); +} + +- (void)assertLoggedIn +{ + STAssertTrue(self.willLogin, @"Should be logged in."); + STAssertTrue(self.didLogin, @"Should be logged in."); +} + +- (void)assertBeforeRootLevelCall +{ + STAssertFalse(self.willRetrieveRootLevel, @"Should not have retrieved root level"); + STAssertFalse(self.didRetrieveRootLevel, @"Should not have retrieved root level"); + STAssertFalse(self.willSynchEntities, @"Should not have processed root level"); + STAssertFalse(self.didSynchEntities, @"Should not have processed root level"); +} + +- (void)assertAfterRootLevelCall +{ + STAssertTrue(self.willRetrieveRootLevel, @"Should have retrieved root level"); + STAssertTrue(self.didRetrieveRootLevel, @"Should have retrieved root level"); + STAssertTrue(self.willSynchEntities, @"Should have processed root level"); + STAssertTrue(self.didSynchEntities, @"Should have processed root level"); +} + +- (void)assertBeforeDrill +{ + STAssertFalse(self.willDrill, @"Should not have drilled"); + STAssertFalse(self.didDrill, @"Should not have drilled"); +} + +- (void)assertAfterDrill +{ + STAssertTrue(self.willDrill, @"Should have drilled"); + STAssertTrue(self.didDrill, @"Should have drilled"); +} + +- (void)assertBeforeDetails +{ + STAssertFalse(self.willRetrieveDetails, @"Should not have retrieved details"); + STAssertFalse(self.didRetrieveDetails, @"Should not have retrieved details"); +} + +- (void)assertAfterDetails +{ + STAssertTrue(self.willRetrieveDetails, @"Should have retrieved details"); + STAssertTrue(self.didRetrieveDetails, @"Should have retrieved details"); +} + - (void)testPersistEntities { CISDOBAsyncCall *call; + + [self assertNotLoggedIn]; call = [self.serviceManager loginUser: GetDefaultUserName() password: GetDefaultUserPassword()]; [self configureAndRunCallSynchronously: call]; + [self assertLoggedIn]; + [self assertBeforeRootLevelCall]; call = [self.serviceManager retrieveRootLevelEntities]; [self configureAndRunCallSynchronously: call]; + [self assertAfterRootLevelCall]; STAssertNotNil(_callResult, @"The service manager should have returned some entities."); @@ -101,14 +227,21 @@ NSArray *entitiesWithChildren = [self entitiesWithChildren]; STAssertTrue([entitiesWithChildren count] > 0, @"There should be some entities with children"); + + [self assertBeforeDrill]; call = [self.serviceManager drillOnEntity: [entitiesWithChildren objectAtIndex: 0]]; [self configureAndRunCallSynchronously: call]; STAssertNotNil(_callResult, @"The iPad service should have returned some entities."); + [self assertAfterDrill]; + // Get detail information on some entities + [self assertBeforeDetails]; call = [self.serviceManager detailsForEntity: [entitiesWithChildren objectAtIndex: 1]]; [self configureAndRunCallSynchronously: call]; STAssertNotNil(_callResult, @"The iPad service should have returned some entities."); + [self assertAfterDetails]; + // Check that the children could be found