diff --git a/openbis-ipad/BisKit/Classes/CISDOBIpadEntity.h b/openbis-ipad/BisKit/Classes/CISDOBIpadEntity.h index 7376be327e783355a1b9298b188569ae93196a73..5a93a3d26d5cf3b932f3b06059984aca32aabeaf 100644 --- a/openbis-ipad/BisKit/Classes/CISDOBIpadEntity.h +++ b/openbis-ipad/BisKit/Classes/CISDOBIpadEntity.h @@ -41,6 +41,8 @@ @property (nonatomic, retain) NSString * permId; @property (nonatomic, retain) NSString * refconJson; @property (readonly) id refcon; +@property (nonatomic, retain) NSString * serverUrlString; +@property (nonatomic, retain) NSDate * lastUpdateDate; // Potentially nil properties @property (nonatomic, retain) NSString * summaryHeader; diff --git a/openbis-ipad/BisKit/Classes/CISDOBIpadEntity.m b/openbis-ipad/BisKit/Classes/CISDOBIpadEntity.m index e356f825160fb58bc8282ed79793469c53401bcb..0a4c2d1a85ceb8bfce37df84fd666beb133ea2a1 100644 --- a/openbis-ipad/BisKit/Classes/CISDOBIpadEntity.m +++ b/openbis-ipad/BisKit/Classes/CISDOBIpadEntity.m @@ -45,6 +45,8 @@ id ObjectFromJsonData(NSString *jsonData, NSError **error) @dynamic childrenPermIdsJson; @dynamic propertiesJson; @dynamic rootLevel; +@dynamic serverUrlString; +@dynamic lastUpdateDate; @synthesize image; diff --git a/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.h b/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.h index b1a5743c8cdc91ea3e3c0b7f85f6287244d73975..d6d4a095b739c252736f136c7727bbf007a17786 100644 --- a/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.h +++ b/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.h @@ -58,6 +58,7 @@ // Local Actions -- actions that do not require a network connection - (NSArray *)allIpadEntitiesOrError:(NSError **)error; - (NSArray *)entitiesByPermId:(NSArray *)permIds error:(NSError **)error; +- (NSArray *)entitiesNotUpdatedSince:(NSDate *)date error:(NSError **)error; - (NSFetchRequest *)entityFetchRequest; - (NSArray *)executeFetchRequest:(NSFetchRequest *)fetchRequest error:(NSError **)error; diff --git a/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.m b/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.m index b6ddd1cd29b847ae06f7a6d3220a96595f094e2d..c4c85041a64ad11e66e532939db516aa600c6853 100644 --- a/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.m +++ b/openbis-ipad/BisKit/Classes/CISDOBIpadServiceManager.m @@ -61,25 +61,6 @@ static NSManagedObjectContext* GetDatabaseManagedObjectContext(NSURL* storeUrl, return moc; } -static BOOL SynchEntityWithManagedObjectContext(CISDOBIpadRawEntity *rawEntity, NSManagedObjectModel *model, NSManagedObjectContext *moc, NSError **error) -{ - // Create new entities in the moc, and store them. - CISDOBIpadEntity *entity; - NSDictionary *fetchVariables = [NSDictionary dictionaryWithObject: [NSArray arrayWithObject: rawEntity.permId] forKey: @"PERM_IDS"]; - NSFetchRequest *request = [model fetchRequestFromTemplateWithName: @"EntitiesByPermIds" substitutionVariables: fetchVariables]; - NSArray *matchedEntities = [moc executeFetchRequest: request error: error]; - if (!matchedEntities) return NO; - if ([matchedEntities count] > 0) { - entity = [matchedEntities objectAtIndex: 0]; - [entity updateFromRawEntity: rawEntity]; - } else { - entity = [NSEntityDescription insertNewObjectForEntityForName: @"CISDOBIpadEntity" inManagedObjectContext: moc]; - [entity initializeFromRawEntity: rawEntity]; - } - return YES; -} - - @implementation CISDOBIpadServiceManager @@ -101,25 +82,57 @@ static BOOL SynchEntityWithManagedObjectContext(CISDOBIpadRawEntity *rawEntity, return self; } -- (BOOL)syncEntities:(NSArray *)rawEntities error:(NSError **)error +- (BOOL)synchEntity:(CISDOBIpadRawEntity *)rawEntity lastUpdateDate:(NSDate *)date error:(NSError **)error { +{ + // Create new entities in the moc, and store them. + CISDOBIpadEntity *entity; + NSArray *matchedEntities = [self entitiesByPermId: [NSArray arrayWithObject: rawEntity.permId] error: error]; + if (!matchedEntities) return NO; + if ([matchedEntities count] > 0) { + entity = [matchedEntities objectAtIndex: 0]; + [entity updateFromRawEntity: rawEntity]; + } else { + entity = [NSEntityDescription insertNewObjectForEntityForName: @"CISDOBIpadEntity" inManagedObjectContext: self.managedObjectContext]; + [entity initializeFromRawEntity: rawEntity]; + } + entity.lastUpdateDate = date; + entity.serverUrlString = [((CISDOBLiveConnection *)(self.service.connection)).url absoluteString]; + + return YES; +} +} + +- (BOOL)syncEntities:(NSArray *)rawEntities pruning:(BOOL)prune error:(NSError **)error +{ + NSDate *lastUpdateDate = [NSDate date]; BOOL success; for (CISDOBIpadRawEntity *rawEntity in rawEntities) { - success = SynchEntityWithManagedObjectContext(rawEntity, self.managedObjectModel, self.managedObjectContext, error); + success = [self synchEntity: rawEntity lastUpdateDate: lastUpdateDate error: error]; if (!success) return NO; } + // If pruning is requested, remove entities that cannot be reached from the server result set. + // TODO : we should treat the intial results as a root set and trace out to do a gc, but the simpler implementation is just to remove everything that is not mentioned + if (prune) { + // Remove all entities that were not mentioned + NSArray *entitiesToDelete = [self entitiesNotUpdatedSince: lastUpdateDate error: error]; + for (CISDOBIpadEntity *entity in entitiesToDelete) { + [self.managedObjectContext deleteObject: entity]; + } + } + success = [self.managedObjectContext save: error]; return success; } -- (CISDOBIpadServiceManagerCall *)managerCallWrappingServiceCall:(CISDOBAsyncCall *)serviceCall +- (CISDOBIpadServiceManagerCall *)managerCallWrappingServiceCall:(CISDOBAsyncCall *)serviceCall pruning:(BOOL)prune { CISDOBIpadServiceManagerCall *managerCall = [[CISDOBIpadServiceManagerCall alloc] initWithServiceManager: self serviceCall: serviceCall]; serviceCall.success = ^(id result) { // Update the cache NSError *error; - BOOL didSync = [self syncEntities: result error: &error]; + BOOL didSync = [self syncEntities: result pruning: prune error: &error]; if (!didSync) { serviceCall.fail(error); } else if (managerCall.success) { @@ -132,6 +145,11 @@ static BOOL SynchEntityWithManagedObjectContext(CISDOBIpadRawEntity *rawEntity, return managerCall; } +- (CISDOBIpadServiceManagerCall *)managerCallWrappingServiceCall:(CISDOBAsyncCall *)serviceCall +{ + return [self managerCallWrappingServiceCall: serviceCall pruning: NO]; +} + - (CISDOBAsyncCall *)loginUser:(NSString *)user password:(NSString *)password { return [self.service loginUser: user password: password]; @@ -140,7 +158,8 @@ static BOOL SynchEntityWithManagedObjectContext(CISDOBIpadRawEntity *rawEntity, - (CISDOBAsyncCall *)retrieveRootLevelEntities { CISDOBAsyncCall *call = [self.service listRootLevelEntities]; - return [self managerCallWrappingServiceCall: call]; + // get rid of entities not mentioned in the original call + return [self managerCallWrappingServiceCall: call pruning: YES]; } - (CISDOBAsyncCall *)drillOnEntity:(CISDOBIpadEntity *)entity @@ -168,6 +187,13 @@ static BOOL SynchEntityWithManagedObjectContext(CISDOBIpadRawEntity *rawEntity, return [self executeFetchRequest: request error: error]; } +- (NSArray *)entitiesNotUpdatedSince:(NSDate *)date error:(NSError **)error +{ + NSDictionary *fetchVariables = [NSDictionary dictionaryWithObject: date forKey: @"LAST_UPDATE_DATE"]; + NSFetchRequest *request = [self.managedObjectModel fetchRequestFromTemplateWithName: @"EntitiesNotUpdatedSince" substitutionVariables: fetchVariables]; + return [self executeFetchRequest: request error: error]; +} + - (NSFetchRequest *)entityFetchRequest { NSFetchRequest *request = [[NSFetchRequest alloc] init]; diff --git a/openbis-ipad/BisKit/Classes/persistent-data-model.xcdatamodeld/persistent-data-model.xcdatamodel/contents b/openbis-ipad/BisKit/Classes/persistent-data-model.xcdatamodeld/persistent-data-model.xcdatamodel/contents index 50b6c58dc1e124f569e4befa250c1fe809c2bbf3..156f3a1b4565e729eaab3f04fdbc9efd16a794be 100644 --- a/openbis-ipad/BisKit/Classes/persistent-data-model.xcdatamodeld/persistent-data-model.xcdatamodel/contents +++ b/openbis-ipad/BisKit/Classes/persistent-data-model.xcdatamodeld/persistent-data-model.xcdatamodel/contents @@ -6,19 +6,22 @@ <attribute name="childrenPermIdsJson" optional="YES" attributeType="String" indexed="YES" syncable="YES"/> <attribute name="identifier" optional="YES" attributeType="String" indexed="YES" syncable="YES"/> <attribute name="imageUrl" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="lastUpdateDate" optional="YES" attributeType="Date" indexed="YES" syncable="YES"/> <attribute name="permId" optional="YES" attributeType="String" indexed="YES" syncable="YES"/> <attribute name="properties" optional="YES" transient="YES" syncable="YES"/> <attribute name="propertiesJson" optional="YES" attributeType="String" syncable="YES"/> <attribute name="refcon" optional="YES" transient="YES" syncable="YES"/> <attribute name="refconJson" optional="YES" attributeType="String" syncable="YES"/> - <attribute name="rootLevel" optional="YES" attributeType="Boolean" syncable="YES"/> + <attribute name="rootLevel" optional="YES" attributeType="Boolean" indexed="YES" syncable="YES"/> + <attribute name="serverUrlString" optional="YES" attributeType="String" syncable="YES"/> <attribute name="summary" optional="YES" attributeType="String" syncable="YES"/> <attribute name="summaryHeader" optional="YES" attributeType="String" syncable="YES"/> </entity> <fetchRequest name="EntitiesByPermIds" entity="CISDOBIpadEntity" predicateString="permId IN $PERM_IDS" fetchBatchSize="20"/> + <fetchRequest name="EntitiesNotUpdatedSince" entity="CISDOBIpadEntity" predicateString="lastUpdateDate < $LAST_UPDATE_DATE"/> <fetchRequest name="EntityAndChildren" entity="CISDOBIpadEntity" predicateString="SELF == $ENTITY OR permId IN $CHILDREN" fetchBatchSize="20"/> <fetchRequest name="RootEntities" entity="CISDOBIpadEntity" predicateString="rootLevel == 0" fetchBatchSize="20"/> <elements> - <element name="CISDOBIpadEntity" positionX="0" positionY="0" width="128" height="240"/> + <element name="CISDOBIpadEntity" positionX="0" positionY="0" width="128" height="270"/> </elements> </model> \ No newline at end of file