Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit cef1698
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Thu May 19 03:30:09 2011 +0800

    SimplenoteEntryCollector now sets post data content type to "application/json" when sending JSON.

commit 28048ae
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Thu May 19 03:27:29 2011 +0800

    Added support for specifying content type when posting data in SyncResponseFetcher

commit 968f475
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Wed May 18 09:55:24 2011 +0800

    Fixed unicode characters in BSJSONAdditions

commit 9002183
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Wed May 4 06:41:03 2011 +0800

    Whitespace fix to match coding style.

commit 5109b2c
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Wed May 4 06:40:18 2011 +0800

    Removed a redundant redundancy.

commit 4901e3e
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Wed May 4 06:22:02 2011 +0800

    Added a check to see whether local tags need to be updated after syncing a local change to simplenote.

commit 8725f27
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Wed May 4 06:20:42 2011 +0800

    Tag sync fixes. Notes are now given the opportunity to update/upgrade metadata/tags with info returned by the list even when content versions are considered the same.

commit c072074
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Sun May 1 19:37:17 2011 +0800

    Update note title/body when content key is present in simplenote update response.

commit f939131
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Sun May 1 09:49:11 2011 +0800

    Issue 104: Adds support for syncing tags via simplenote (depends on commit 2bcec8b).

commit 129daa2
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Sun May 1 07:52:35 2011 +0800

    Increased simplenote index batch size from 20 to 100, which is the max.

commit e9baf6e
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Sat Apr 30 15:20:02 2011 +0800

    Flagged possibly obsolete code.

commit 089154c
Author: Darryl H. Thomas <darrylhthomas@me.com>
Date:   Sat Apr 30 15:18:27 2011 +0800

    Update Simplenote Sync to api2.
  • Loading branch information
ttscoff committed May 20, 2011
1 parent da8d8d7 commit 8db9db8
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 64 deletions.
2 changes: 1 addition & 1 deletion JSON/NSString+BSJSONAdditions.m
Expand Up @@ -61,7 +61,7 @@ - (NSString *)jsonStringValue
break;
*/
default:
[jsonString appendFormat:@"%c", nextChar];
[jsonString appendFormat:@"%C", nextChar];
break;
}
}
Expand Down
3 changes: 3 additions & 0 deletions NSString_NV.m
Expand Up @@ -143,6 +143,7 @@ + (NSString*)relativeDateStringWithAbsoluteTime:(CFAbsoluteTime)absTime {
return dateString;
}

// TODO: possibly obsolete? SN api2 formats dates as doubles from start of unix epoch
CFDateFormatterRef simplenoteDateFormatter(int lowPrecision) {
//CFStringRef dateStr = CFSTR("2010-01-02 23:23:31.876229");
static CFDateFormatterRef dateFormatter = NULL;
Expand All @@ -162,11 +163,13 @@ CFDateFormatterRef simplenoteDateFormatter(int lowPrecision) {
return lowPrecision ? lowPrecisionDateFormatter : dateFormatter;
}

// TODO: possibly obsolete? SN api2 formats dates as doubles from start of unix epoch
+ (NSString*)simplenoteDateWithAbsoluteTime:(CFAbsoluteTime)absTime {
CFStringRef str = CFDateFormatterCreateStringWithAbsoluteTime(NULL, simplenoteDateFormatter(0), absTime);
return [(id)str autorelease];
}

// TODO: possibly obsolete? SN api2 formats dates as doubles from start of unix epoch
- (CFAbsoluteTime)absoluteTimeFromSimplenoteDate {

CFAbsoluteTime absTime = 0;
Expand Down
4 changes: 4 additions & 0 deletions NotationSyncServiceManager.m
Expand Up @@ -159,6 +159,10 @@ - (void)makeNotesMatchList:(NSArray*)MDEntries fromSyncSession:(id <SyncServiceS
[locallyChangedNotes addObject:note];
} else if (changeDiff == NSOrderedAscending) {
[remotelyChangedNotes addObject:note];
} else {
//if the note is considered unchanged, still give the sync service an
//opportunity to update metadata/tags with info returned by the list
[syncSession applyMetadataUpdatesToNote:note localEntry:thisServiceInfo remoteEntry:remoteInfo];
}
} else if (changeDiff != NSOrderedDescending) {
//nah ah ah, a delete should not stick if local mod time is newer! otherwise local changes will be lost
Expand Down
145 changes: 118 additions & 27 deletions SimplenoteEntryCollector.m
Expand Up @@ -21,6 +21,7 @@
#import "SyncResponseFetcher.h"
#import "SimplenoteSession.h"
#import "NSString_NV.h"
#import "NSDictionary+BSJSONAdditions.h"
#import "SynchronizedNoteProtocol.h"
#import "NoteObject.h"
#import "DeletedNoteObject.h"
Expand Down Expand Up @@ -112,9 +113,9 @@ - (SyncResponseFetcher*)fetcherForEntry:(id)entry {
originalNote = entry;
entry = [[entry syncServicesMD] objectForKey:SimplenoteServiceName];
}
NSURL *noteURL = [SimplenoteSession servletURLWithPath:@"/api/note" parameters:
NSURL *noteURL = [SimplenoteSession servletURLWithPath: [NSString stringWithFormat:@"/api2/data/%@", [entry objectForKey: @"key"]] parameters:
[NSDictionary dictionaryWithObjectsAndKeys: email, @"email",
authToken, @"auth", [entry objectForKey:@"key"], @"key", nil]];
authToken, @"auth", nil]];
SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL POSTData:nil delegate:self];
//remember the note for later? why not.
if (originalNote) [fetcher setRepresentedObject:originalNote];
Expand All @@ -136,18 +137,41 @@ - (NSDictionary*)preparedDictionaryWithFetcher:(SyncResponseFetcher*)fetcher rec
//logic abstracted for subclassing

NSString *bodyString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSDictionary *headers = [fetcher headers];

NSMutableDictionary *entry = [NSMutableDictionary dictionaryWithCapacity:5];
[entry setObject:[headers objectForKey:@"Note-Key"] forKey:@"key"];
[entry setObject:[NSNumber numberWithInt:[[headers objectForKey:@"Note-Deleted"] intValue]] forKey:@"deleted"];
[entry setObject:[NSNumber numberWithDouble:[[headers objectForKey:@"Note-Createdate"] absoluteTimeFromSimplenoteDate]] forKey:@"create"];
[entry setObject:[NSNumber numberWithDouble:[[headers objectForKey:@"Note-Modifydate"] absoluteTimeFromSimplenoteDate]] forKey:@"modify"];
NSDictionary *rawObject = nil;
@try {
rawObject = [NSDictionary dictionaryWithJSONString:bodyString];
}
@catch (NSException *e) {
NSLog(@"Exception while parsing Simplenote JSON note object: %@", [e reason]);
}
@finally {
if (!rawObject)
return nil;
}

NSMutableDictionary *entry = [NSMutableDictionary dictionaryWithCapacity:12];
[entry setObject:[rawObject objectForKey:@"key"] forKey:@"key"];
[entry setObject:[NSNumber numberWithInt:[[rawObject objectForKey:@"deleted"] intValue]] forKey:@"deleted"];
// Normalize dates from unix epoch timestamps to mac os x epoch timestamps
[entry setObject:[NSNumber numberWithDouble:[[NSDate dateWithTimeIntervalSince1970:[[rawObject objectForKey:@"createdate"] doubleValue]] timeIntervalSinceReferenceDate]] forKey:@"create"];
[entry setObject:[NSNumber numberWithDouble:[[NSDate dateWithTimeIntervalSince1970:[[rawObject objectForKey:@"modifydate"] doubleValue]] timeIntervalSinceReferenceDate]] forKey:@"modify"];
[entry setObject:[NSNumber numberWithInt:[[rawObject objectForKey:@"syncnum"] intValue]] forKey:@"syncnum"];
[entry setObject:[NSNumber numberWithInt:[[rawObject objectForKey:@"version"] intValue]] forKey:@"version"];
[entry setObject:[NSNumber numberWithInt:[[rawObject objectForKey:@"minversion"] intValue]] forKey:@"minversion"];
if ([rawObject objectForKey:@"sharekey"]) {
[entry setObject:[rawObject objectForKey:@"sharekey"] forKey:@"sharekey"];
}
if ([rawObject objectForKey:@"publishkey"]) {
[entry setObject:[rawObject objectForKey:@"publishkey"] forKey:@"publishkey"];
}
[entry setObject:[rawObject objectForKey:@"systemtags"] forKey:@"systemtags"];
[entry setObject:[rawObject objectForKey:@"tags"] forKey:@"tags"];
if ([[fetcher representedObject] conformsToProtocol:@protocol(SynchronizedNote)]) [entry setObject:[fetcher representedObject] forKey:@"NoteObject"];
[entry setObject:bodyString forKey:@"content"];
[entry setObject:[rawObject objectForKey:@"content"] forKey:@"content"];

//NSLog(@"fetched entry %@" , entry);

return entry;
}

Expand All @@ -161,7 +185,17 @@ - (void)syncResponseFetcher:(SyncResponseFetcher*)fetcher receivedData:(NSData*)
[NSNumber numberWithInt:[fetcher statusCode]], @"StatusCode", nil]];
}
} else {
[entriesCollected addObject:[self preparedDictionaryWithFetcher:fetcher receivedData:data]];
NSDictionary *preparedDictionary = [self preparedDictionaryWithFetcher:fetcher receivedData:data];
if (!preparedDictionary) {
// Parsing JSON failed. Is this the right way to handle the error?
id obj = [fetcher representedObject];
if (obj) {
[entriesInError addObject: [NSDictionary dictionaryWithObjectsAndKeys: obj, @"NoteObject",
[NSNumber numberWithInt:[fetcher statusCode]], @"StatusCode", nil]];
}
} else {
[entriesCollected addObject: preparedDictionary];
}
}

if (entryFinishedCount >= [entriesToCollect count] || stopped) {
Expand Down Expand Up @@ -219,18 +253,32 @@ - (SyncResponseFetcher*)_fetcherForNote:(NoteObject*)aNote creator:(BOOL)doesCre
CFAbsoluteTime modNum = doesCreate ? modifiedDateOfNote(aNote) : [[info objectForKey:@"modify"] doubleValue];

//always set the mod date, set created date if we are creating, set the key if we are updating
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys: email, @"email", authToken, @"auth", nil];
if (modNum > 0.0) [params setObject:[NSString simplenoteDateWithAbsoluteTime:modNum] forKey:@"modify"];
if (doesCreate) [params setObject:[NSString simplenoteDateWithAbsoluteTime:createdDateOfNote(aNote)] forKey:@"create"];
if (!doesCreate) [params setObject:[info objectForKey:@"key"] forKey:@"key"]; //raises its own exception if key is nil
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys: email, @"email", authToken, @"auth", nil];

NSMutableString *noteBody = [[[aNote combinedContentWithContextSeparator: /* explicitly assume default separator if creating */
doesCreate ? nil : [info objectForKey:SimplenoteSeparatorKey]] mutableCopy] autorelease];
//simpletext iPhone app loses any tab characters
[noteBody replaceTabsWithSpacesOfWidth:[[GlobalPrefs defaultPrefs] numberOfSpacesInTab]];

NSURL *noteURL = [SimplenoteSession servletURLWithPath:@"/api/note" parameters:params];
SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL bodyStringAsUTF8B64:noteBody delegate:self];
NSMutableDictionary *rawObject = [NSMutableDictionary dictionaryWithCapacity: 12];
if (modNum > 0.0) [rawObject setObject:[NSNumber numberWithDouble:[[NSDate dateWithTimeIntervalSinceReferenceDate:modNum] timeIntervalSince1970]] forKey:@"modifydate"];
if (doesCreate) [rawObject setObject:[NSNumber numberWithDouble:[[NSDate dateWithTimeIntervalSinceReferenceDate:createdDateOfNote(aNote)] timeIntervalSince1970]] forKey:@"createdate"];

NSArray *tags = [aNote orderedLabelTitles];
// Don't send an empty tagset if this note has never been synced via sn-api2
if ([tags count] || ([info objectForKey:@"syncnum"] != nil)) {
[rawObject setObject:tags forKey:@"tags"];
}

[rawObject setObject:noteBody forKey:@"content"];

NSURL *noteURL = nil;
if (doesCreate) {
noteURL = [SimplenoteSession servletURLWithPath:@"/api2/data" parameters:params];
} else {
noteURL = [SimplenoteSession servletURLWithPath:[NSString stringWithFormat:@"/api2/data/%@", [info objectForKey:@"key"]] parameters:params];
}
SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL POSTData:[[rawObject jsonStringValue] dataUsingEncoding:NSUTF8StringEncoding] contentType:@"application/json" delegate:self];
[fetcher setRepresentedObject:aNote];
return [fetcher autorelease];
}
Expand All @@ -257,10 +305,13 @@ - (SyncResponseFetcher*)fetcherForDeletingNote:(DeletedNoteObject*)aDeletedNote
}
NSAssert([info objectForKey:@"key"], @"fetcherForDeletingNote: got deleted note and couldn't find a key anywhere!");

NSURL *noteURL = [SimplenoteSession servletURLWithPath:@"/api/delete" parameters:
//in keeping with nv's behavior with sn api1, deleting only marks a note as deleted.
//may want to implement actual purging (using HTTP DELETE) in the future
NSURL *noteURL = [SimplenoteSession servletURLWithPath:[NSString stringWithFormat:@"/api2/data/%@", [info objectForKey:@"key"]] parameters:
[NSDictionary dictionaryWithObjectsAndKeys: email, @"email",
authToken, @"auth", [info objectForKey:@"key"], @"key", nil]];
SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL POSTData:nil delegate:self];
authToken, @"auth", nil]];
NSData *postData = [[[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:1] forKey:@"deleted"] jsonStringValue] dataUsingEncoding:NSUTF8StringEncoding];
SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL POSTData:postData contentType:@"application/json" delegate:self];
[fetcher setRepresentedObject:aDeletedNote];
return [fetcher autorelease];

Expand Down Expand Up @@ -301,9 +352,31 @@ - (void)stop {
#endif

- (NSDictionary*)preparedDictionaryWithFetcher:(SyncResponseFetcher*)fetcher receivedData:(NSData*)data {
NSString *keyString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];

NSString *bodyString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];

NSDictionary *rawObject = nil;
@try {
rawObject = [NSDictionary dictionaryWithJSONString:bodyString];
}
@catch (NSException *e) {
NSLog(@"Exception while parsing Simplenote JSON note object: %@", [e reason]);
}
@finally {
if (!rawObject)
return nil;
}

NSString *keyString = [rawObject objectForKey:@"key"];

NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:5];
NSMutableDictionary *syncMD = [NSMutableDictionary dictionaryWithCapacity:5];
[syncMD setObject:[rawObject objectForKey:@"key"] forKey:@"key"];
[syncMD setObject:[NSNumber numberWithDouble:[[NSDate dateWithTimeIntervalSince1970:[[rawObject objectForKey:@"createdate"] doubleValue]] timeIntervalSinceReferenceDate]] forKey:@"create"];
[syncMD setObject:[NSNumber numberWithDouble:[[NSDate dateWithTimeIntervalSince1970:[[rawObject objectForKey:@"modifydate"] doubleValue]] timeIntervalSinceReferenceDate]] forKey:@"modify"];
[syncMD setObject:[NSNumber numberWithInt:[[rawObject objectForKey:@"syncnum"] intValue]] forKey:@"syncnum"];
[syncMD setObject:[NSNumber numberWithBool:NO] forKey:@"dirty"];

if ([fetcher representedObject]) {
id <SynchronizedNote> aNote = [fetcher representedObject];
[result setObject:aNote forKey:@"NoteObject"];
Expand All @@ -318,19 +391,37 @@ - (NSDictionary*)preparedDictionaryWithFetcher:(SyncResponseFetcher*)fetcher rec

NSAssert([aNote isKindOfClass:[NoteObject class]], @"received a non-noteobject from a fetcherForCreatingNote: operation!");
//don't need to store a separator for newly-created notes; when nil it is presumed the default separator
[aNote setSyncObjectAndKeyMD:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:modifiedDateOfNote(aNote)], @"modify",
[NSNumber numberWithDouble:createdDateOfNote(aNote)], @"create",
keyString, @"key", nil] forService:SimplenoteServiceName];
[aNote setSyncObjectAndKeyMD:syncMD forService:SimplenoteServiceName];

[(NoteObject*)aNote makeNoteDirtyUpdateTime:NO updateFile:NO];
} else if (@selector(fetcherForDeletingNote:) == fetcherOpSEL) {
//this note has been successfully deleted, and can now have its Simplenote syncServiceMD entry removed
//so that _purgeAlreadyDistributedDeletedNotes can remove it permanently once the deletion has been synced with all other registered services
NSAssert([aNote isKindOfClass:[DeletedNoteObject class]], @"received a non-deletednoteobject from a fetcherForDeletingNote: operation");
[aNote removeAllSyncMDForService:SimplenoteServiceName];
} else if (@selector(fetcherForUpdatingNote:) == fetcherOpSEL) {
// SN api2 can return a content key in an update response containing
// the merged changes from other clients....
if ([rawObject objectForKey:@"content"]) {
NSUInteger bodyLoc = 0;
NSString *separator = nil;
NSString *combinedContent = [rawObject objectForKey:@"content"];
NSString *newTitle = [combinedContent syntheticTitleAndSeparatorWithContext:&separator bodyLoc:&bodyLoc oldTitle:titleOfNote(aNote) maxTitleLen:60];

[(NoteObject *)aNote updateWithSyncBody:[combinedContent substringFromIndex:bodyLoc] andTitle:newTitle];
}

//[aNote removeKey:@"dirty" forService:SimplenoteServiceName];
// Tags may have been changed by another client...
NSSet *localTags = [NSSet setWithArray:[(NoteObject *)aNote orderedLabelTitles]];
NSSet *remoteTags = [NSSet setWithArray:[rawObject objectForKey:@"tags"]];
if (![localTags isEqualToSet:remoteTags]) {
NSLog(@"Updating tags with remote values.");
NSString *newLabelString = [[remoteTags allObjects] componentsJoinedByString:@" "];
[(NoteObject *)aNote setLabelString:newLabelString];
}

[aNote setSyncObjectAndKeyMD:syncMD forService: SimplenoteServiceName];
//NSLog(@"note update:\n %@", [aNote syncServicesMD]);
} else {
NSLog(@"%s called with unknown opSEL: %s", _cmd, fetcherOpSEL);
}
Expand Down
6 changes: 6 additions & 0 deletions SimplenoteSession.h
Expand Up @@ -55,6 +55,10 @@ extern NSString *SimplenoteSeparatorKey;

NSMutableSet *collectorsInProgress;

//used to span multiple partial index fetches (when mark is present in response)
NSMutableArray *indexEntryBuffer;
NSString *indexMark;

id delegate;
}

Expand All @@ -70,8 +74,10 @@ extern NSString *SimplenoteSeparatorKey;
- (BOOL)reachabilityFailed;

- (NSComparisonResult)localEntry:(NSDictionary*)localEntry compareToRemoteEntry:(NSDictionary*)remoteEntry;
-(void)applyMetadataUpdatesToNote:(id <SynchronizedNote>)aNote localEntry:(NSDictionary *)localEntry remoteEntry: (NSDictionary *)remoteEntry;
- (BOOL)remoteEntryWasMarkedDeleted:(NSDictionary*)remoteEntry;
- (BOOL)entryHasLocalChanges:(NSDictionary*)entry;
- (BOOL)tagsShouldBeMergedForEntry:(NSDictionary*)entry;

+ (void)registerLocalModificationForNote:(id <SynchronizedNote>)aNote;

Expand Down

0 comments on commit 8db9db8

Please sign in to comment.