Skip to content

Commit

Permalink
Allow restart of a persistent replication
Browse files Browse the repository at this point in the history
Deleting the _replicator_state property of a replicator document starts a new replication.
(This is also supported by CouchDB, but undocumented.)
This makes persistent non-continuous replications more useful.
  • Loading branch information
snej committed Feb 21, 2012
1 parent 20d9fad commit 56af126
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 9 deletions.
6 changes: 6 additions & 0 deletions Source/TDDatabase+Replication.m
Expand Up @@ -67,6 +67,12 @@ - (TDReplicator*) replicatorWithRemoteURL: (NSURL*)remote
}


- (void) stopAndForgetReplicator: (TDReplicator*)repl {
[repl databaseClosing];
[_activeReplicators removeObjectIdenticalTo: repl];
}


- (void) replicatorDidStop: (NSNotification*)n {
TDReplicator* repl = n.object;
if (repl.error) // Leave it around a while so clients can see the error
Expand Down
1 change: 1 addition & 0 deletions Source/TDInternal.h
Expand Up @@ -54,6 +54,7 @@ extern NSString* const kTDAttachmentBlobKeyProperty;
@end

@interface TDDatabase (Replication_Internal)
- (void) stopAndForgetReplicator: (TDReplicator*)repl;
- (NSString*) lastSequenceWithRemoteURL: (NSURL*)url
push: (BOOL)push;
- (BOOL) setLastSequence: (NSString*)lastSequence
Expand Down
30 changes: 22 additions & 8 deletions Source/TDReplicatorManager.m
Expand Up @@ -146,10 +146,14 @@ - (BOOL) validateRevision: (TDRevision*)newRev context: (id<TDValidationContext>
}

// Only certain keys can be changed or removed:
NSSet* deletableProperties = [NSSet setWithObjects: @"_replication_state", nil];
NSSet* mutableProperties = [NSSet setWithObjects: @"filter", @"query_params", nil];
for (NSString* key in curProperties) {
if (![mutableProperties containsObject: key] &&
!$equal([curProperties objectForKey: key], [newProperties objectForKey: key])) {
id newValue = [newProperties objectForKey: key];
if (!newValue && [deletableProperties containsObject: key])
;
else if (![mutableProperties containsObject: key] &&
!$equal([curProperties objectForKey: key], newValue)) {
context.errorMessage = $sprintf(@"Cannot modify the '%@' property", key);
return NO;
}
Expand Down Expand Up @@ -263,8 +267,17 @@ - (void) processInsertion: (TDRevision*)rev {


// A replication document has been changed:
- (void) processUpdate: (TDRevision*)rev ofReplicator: (TDReplicator*)repl {
LogTo(Sync, @"ReplicationManager: %@ was updated", rev);
- (void) processUpdate: (TDRevision*)rev {
if (![rev.properties objectForKey: @"_replication_state"]) {
// Client deleted the _replication_state property; restart the replicator:
LogTo(Sync, @"ReplicatorManager: Restarting replicator for %@", rev);
TDReplicator* repl = [_replicatorsByDocID objectForKey: rev.docID];
if (repl) {
[repl.db stopAndForgetReplicator: repl];
[_replicatorsByDocID removeObjectForKey: rev.docID];
}
[self processInsertion: rev];
}
}


Expand All @@ -280,11 +293,10 @@ - (void) processDeletion: (TDRevision*)rev ofReplicator: (TDReplicator*)repl {


- (void) processRevision: (TDRevision*)rev {
TDReplicator* repl = [_replicatorsByDocID objectForKey: rev.docID];
if (repl)
[self processUpdate: rev ofReplicator: repl];
else
if (rev.generation == 1)
[self processInsertion: rev];
else
[self processUpdate: rev];
}


Expand All @@ -309,6 +321,8 @@ - (void) processAllDocs {

// Notified that a _replicator database document has been created/updated/deleted:
- (void) dbChanged: (NSNotification*)n {
if (_updateInProgress)
return;
TDRevision* rev = [n.userInfo objectForKey: @"rev"];
LogTo(SyncVerbose, @"ReplicatorManager: %@ %@", n.name, rev);
NSString* docID = rev.docID;
Expand Down
24 changes: 23 additions & 1 deletion Source/TDReplicator_Tests.m
Expand Up @@ -205,9 +205,31 @@ static void deleteRemoteDB(void) {
CAssert(repl);
CAssertEqual(repl.sessionID, sessionID);
CAssert(repl.running);

// Delete the _replication_state property:
NSMutableDictionary* updatedProps = [[newRev.properties mutableCopy] autorelease];
[updatedProps removeObjectForKey: @"_replication_state"];
rev = [TDRevision revisionWithProperties: updatedProps];
rev = [replicatorDb putRevision: rev prevRevisionID: rev.revID allowConflict: NO status: &status];
CAssertEq(status, 201);

// Get back the document and verify it's been updated with replicator properties:
newRev = [replicatorDb getDocumentWithID: rev.docID revisionID: nil options: 0];
Log(@"Updated doc = %@", newRev.properties);
sessionID = [newRev.properties objectForKey: @"_replication_id"];
CAssert([sessionID length] >= 10);
CAssertEqual([newRev.properties objectForKey: @"_replication_state"], @"triggered");
CAssert([[newRev.properties objectForKey: @"_replication_state_time"] longLongValue] >= 1000);

// Check that this restarted the replicator:
TDReplicator* newRepl = [sourceDB activeReplicatorWithRemoteURL: remote push: YES];
CAssert(newRepl);
CAssert(newRepl != repl);
CAssertEqual(newRepl.sessionID, sessionID);
CAssert(newRepl.running);

// Now delete it:
rev = [[[TDRevision alloc] initWithDocID: rev.docID revID: newRev.revID deleted: YES] autorelease];
rev = [[[TDRevision alloc] initWithDocID: newRev.docID revID: newRev.revID deleted: YES] autorelease];
rev = [replicatorDb putRevision: rev prevRevisionID: rev.revID allowConflict: NO status: &status];
CAssertEq(status, 200);
}
Expand Down

0 comments on commit 56af126

Please sign in to comment.