Skip to content
Browse files

TDPusher supports servers that don't accept multipart PUTs

  • Loading branch information...
1 parent c595ba4 commit 38327b320e610efc4993b8f260c67eedd3a8febc @snej snej committed Oct 12, 2012
Showing with 133 additions and 40 deletions.
  1. +84 −31 Source/TDDatabase+Attachments.m
  2. +1 −0 Source/TDInternal.h
  3. +1 −0 Source/TDPusher.h
  4. +47 −9 Source/TDPusher.m
View
115 Source/TDDatabase+Attachments.m
@@ -322,50 +322,103 @@ - (NSURL*) fileForAttachmentDict: (NSDictionary*)attachmentDict
}
-+ (void) stubOutAttachmentsIn: (TDRevision*)rev
- beforeRevPos: (int)minRevPos
- attachmentsFollow: (BOOL)attachmentsFollow
+// Calls the block on every attachment dictionary. The block can return a different dictionary,
+// which will be replaced in the rev's properties. If it returns nil, the operation aborts.
+// Returns YES if any changes were made.
++ (BOOL) mutateAttachmentsIn: (TDRevision*)rev
+ withBlock: (NSDictionary*(^)(NSString*, NSDictionary*))block
{
- if (minRevPos <= 1 && !attachmentsFollow)
- return;
NSDictionary* properties = rev.properties;
NSMutableDictionary* editedProperties = nil;
NSDictionary* attachments = (id)properties[@"_attachments"];
NSMutableDictionary* editedAttachments = nil;
for (NSString* name in attachments) {
- NSDictionary* attachment = attachments[name];
+ @autoreleasepool {
+ NSDictionary* attachment = attachments[name];
+ NSDictionary* editedAttachment = block(name, attachment);
+ if (!editedAttachment) {
+ [editedProperties release];
+ return NO; // block canceled
+ }
+ if (editedAttachment != attachment) {
+ if (!editedProperties) {
+ // Make the document properties and _attachments dictionary mutable:
+ editedProperties = [properties mutableCopy];
+ editedAttachments = [[attachments mutableCopy] autorelease];
+ editedProperties[@"_attachments"] = editedAttachments;
+ }
+ editedAttachments[name] = editedAttachment;
+ }
+ }
+ }
+ if (editedProperties) {
+ rev.properties = [editedProperties autorelease];
+ return YES;
+ }
+ return NO;
+}
+
+
+// Replaces attachment data whose revpos is < minRevPos with stubs.
+// If attachmentsFollow==YES, replaces data with "follows" key.
++ (void) stubOutAttachmentsIn: (TDRevision*)rev
+ beforeRevPos: (int)minRevPos
+ attachmentsFollow: (BOOL)attachmentsFollow
+{
+ if (minRevPos <= 1 && !attachmentsFollow)
+ return;
+ [self mutateAttachmentsIn: rev
+ withBlock: ^NSDictionary *(NSString *name, NSDictionary *attachment) {
int revPos = [attachment[@"revpos"] intValue];
bool includeAttachment = (revPos == 0 || revPos >= minRevPos);
bool stubItOut = !includeAttachment && !attachment[@"stub"];
bool addFollows = includeAttachment && attachmentsFollow
&& !attachment[@"follows"];
- if (stubItOut || addFollows) {
- // Need to modify attachment entry:
- if (!editedProperties) {
- // Make the document properties and _attachments dictionary mutable:
- editedProperties = [[properties mutableCopy] autorelease];
- editedAttachments = [[attachments mutableCopy] autorelease];
- editedProperties[@"_attachments"] = editedAttachments;
- }
+ if (!stubItOut && !addFollows)
+ return attachment; // no change
+ // Need to modify attachment entry:
+ NSMutableDictionary* editedAttachment = [[attachment mutableCopy] autorelease];
+ [editedAttachment removeObjectForKey: @"data"];
+ if (stubItOut) {
+ // ...then remove the 'data' and 'follows' key:
+ [editedAttachment removeObjectForKey: @"follows"];
+ editedAttachment[@"stub"] = $true;
+ LogTo(SyncVerbose, @"Stubbed out attachment %@/'%@': revpos %d < %d",
+ rev, name, revPos, minRevPos);
+ } else if (addFollows) {
+ [editedAttachment removeObjectForKey: @"stub"];
+ editedAttachment[@"follows"] = $true;
+ LogTo(SyncVerbose, @"Added 'follows' for attachment %@/'%@': revpos %d >= %d",
+ rev, name, revPos, minRevPos);
+ }
+ return editedAttachment;
+ }];
+}
+
+
+// Replaces the "follows" key with the real attachment data in all attachments to 'doc'.
+- (BOOL) inlineFollowingAttachmentsIn: (TDRevision*)rev error: (NSError**)outError {
+ __block NSError *error = nil;
+ [[self class] mutateAttachmentsIn: rev
+ withBlock:
+ ^NSDictionary *(NSString *name, NSDictionary *attachment) {
+ if (!attachment[@"follows"])
+ return attachment;
+ NSURL* fileURL = [self fileForAttachmentDict: attachment];
+ NSData* fileData = [NSData dataWithContentsOfURL: fileURL
+ options: NSDataReadingMappedIfSafe
+ error: &error];
+ if (!fileData)
+ return nil;
NSMutableDictionary* editedAttachment = [[attachment mutableCopy] autorelease];
- [editedAttachment removeObjectForKey: @"data"];
- if (stubItOut) {
- // ...then remove the 'data' and 'follows' key:
- [editedAttachment removeObjectForKey: @"follows"];
- editedAttachment[@"stub"] = $true;
- LogTo(SyncVerbose, @"Stubbed out attachment %@/'%@': revpos %d < %d",
- rev, name, revPos, minRevPos);
- } else if (addFollows) {
- [editedAttachment removeObjectForKey: @"stub"];
- editedAttachment[@"follows"] = $true;
- LogTo(SyncVerbose, @"Added 'follows' for attachment %@/'%@': revpos %d >= %d",
- rev, name, revPos, minRevPos);
- }
- editedAttachments[name] = editedAttachment;
+ [editedAttachment removeObjectForKey: @"follows"];
+ editedAttachment[@"data"] = [TDBase64 encode: fileData];
+ return editedAttachment;
}
- }
- if (editedProperties)
- rev.properties = editedProperties;
+ ];
+ if (outError)
+ *outError = error;
+ return (error == nil);
}
View
1 Source/TDInternal.h
@@ -53,6 +53,7 @@
- (TDStatus) copyAttachmentNamed: (NSString*)name
fromSequence: (SequenceNumber)fromSequence
toSequence: (SequenceNumber)toSequence;
+- (BOOL) inlineFollowingAttachmentsIn: (TDRevision*)rev error: (NSError**)outError;
@end
@interface TDDatabase (Replication_Internal)
View
1 Source/TDPusher.h
@@ -18,6 +18,7 @@
BOOL _observing;
BOOL _uploading;
NSMutableArray* _uploaderQueue;
+ BOOL _dontSendMultipart;
}
@property BOOL createTarget;
View
56 Source/TDPusher.m
@@ -97,6 +97,10 @@ - (void) beginReplicating {
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(dbChanged:)
name: TDDatabaseChangeNotification object: _db];
}
+
+#ifdef GNUSTEP // TODO: Multipart upload on GNUstep
+ _dontSendMultipart = YES;
+#endif
}
@@ -178,17 +182,15 @@ - (void) processInbox: (TDRevisionList*)changes {
NSDictionary* properties;
@autoreleasepool {
// Is this revision in the server's 'missing' list?
- NSDictionary* revResults = results[[rev docID]];
+ NSDictionary* revResults = results[rev.docID];
NSArray* missing = revResults[@"missing"];
if (![missing containsObject: [rev revID]])
return nil;
// Get the revision's properties:
- TDContentOptions options = kTDIncludeAttachments | kTDIncludeRevs
- | kTDBigAttachmentsFollow;
-#ifdef GNUSTEP
- options &= ~kTDBigAttachmentsFollow; // TODO: Multipart upload on GNUstep
-#endif
+ TDContentOptions options = kTDIncludeAttachments | kTDIncludeRevs;
+ if (!_dontSendMultipart)
+ options |= kTDBigAttachmentsFollow;
if ([_db loadRevisionBody: rev options: options] >= 300) {
Warn(@"%@: Couldn't get local contents of %@", self, rev);
[self revisionFailed];
@@ -206,7 +208,7 @@ - (void) processInbox: (TDRevisionList*)changes {
attachmentsFollow: NO];
properties = rev.properties;
// If the rev has huge attachments, send it under separate cover:
- if ([self uploadMultipartRevision: rev])
+ if (!_dontSendMultipart && [self uploadMultipartRevision: rev])
return nil;
}
[properties retain]; // (to survive impending autorelease-pool drain)
@@ -313,8 +315,15 @@ - (BOOL) uploadMultipartRevision: (TDRevision*)rev {
requestHeaders: self.requestHeaders
onCompletion: ^(id response, NSError *error) {
if (error) {
- self.error = error;
- [self revisionFailed];
+ if ($equal(error.domain, TDHTTPErrorDomain)
+ && error.code == kTDStatusUnsupportedType) {
+ // Server doesn't like multipart, eh? Fall back to JSON.
+ _dontSendMultipart = YES;
+ [self uploadJSONRevision: rev];
+ } else {
+ self.error = error;
+ [self revisionFailed];
+ }
} else {
LogTo(SyncVerbose, @"%@: Sent %@, response=%@", self, rev, response);
self.lastSequence = $sprintf(@"%lld", rev.sequence);
@@ -338,6 +347,35 @@ - (BOOL) uploadMultipartRevision: (TDRevision*)rev {
}
+// Fallback to upload a revision if uploadMultipartRevision failed due to the server's rejecting
+// multipart format.
+- (void) uploadJSONRevision: (TDRevision*)rev {
+ // Get the revision's properties:
+ NSError* error;
+ if (![_db inlineFollowingAttachmentsIn: rev error: &error]) {
+ self.error = error;
+ [self revisionFailed];
+ return;
+ }
+
+ [self asyncTaskStarted];
+ NSString* path = $sprintf(@"/%@?new_edits=false", TDEscapeID(rev.docID));
+ [self sendAsyncRequest: @"PUT"
+ path: path
+ body: rev.properties
+ onCompletion: ^(id response, NSError *error) {
+ if (error) {
+ self.error = error;
+ [self revisionFailed];
+ } else {
+ LogTo(SyncVerbose, @"%@: Sent %@ (JSON), response=%@", self, rev, response);
+ self.lastSequence = $sprintf(@"%lld", rev.sequence);
+ }
+ [self asyncTasksFinished: 1];
+ }];
+}
+
+
- (void) startNextUpload {
if (!_uploading && _uploaderQueue.count > 0) {
_uploading = YES;

0 comments on commit 38327b3

Please sign in to comment.
Something went wrong with that request. Please try again.