Skip to content

Commit

Permalink
Merge pull request scrod#224 from darrylhthomas/simplenote-tags
Browse files Browse the repository at this point in the history
Issue scrod#104 Simplenote tags. (Initial merge, pending database update logic)
  • Loading branch information
scrod committed May 20, 2011
2 parents e240917 + 3c7aff9 commit 3a45ca9
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 64 deletions.
2 changes: 1 addition & 1 deletion JSON/NSString+BSJSONAdditions.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ - (NSString *)jsonStringValue
break; break;
*/ */
default: default:
[jsonString appendFormat:@"%c", nextChar]; [jsonString appendFormat:@"%C", nextChar];
break; break;
} }
} }
Expand Down
3 changes: 3 additions & 0 deletions NSString_NV.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ + (NSString*)relativeDateStringWithAbsoluteTime:(CFAbsoluteTime)absTime {
return dateString; return dateString;
} }


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


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


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


CFAbsoluteTime absTime = 0; CFAbsoluteTime absTime = 0;
Expand Down
1 change: 1 addition & 0 deletions Notation.xcodeproj/project.pbxproj
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1098,6 +1098,7 @@
isa = PBXProject; isa = PBXProject;
buildConfigurationList = 212B842709780B5000F3597F /* Build configuration list for PBXProject "Notation" */; buildConfigurationList = 212B842709780B5000F3597F /* Build configuration list for PBXProject "Notation" */;
compatibilityVersion = "Xcode 3.1"; compatibilityVersion = "Xcode 3.1";
developmentRegion = English;
hasScannedForEncodings = 1; hasScannedForEncodings = 1;
knownRegions = ( knownRegions = (
English, English,
Expand Down
4 changes: 4 additions & 0 deletions NotationSyncServiceManager.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ - (void)makeNotesMatchList:(NSArray*)MDEntries fromSyncSession:(id <SyncServiceS
[locallyChangedNotes addObject:note]; [locallyChangedNotes addObject:note];
} else if (changeDiff == NSOrderedAscending) { } else if (changeDiff == NSOrderedAscending) {
[remotelyChangedNotes addObject:note]; [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) { } else if (changeDiff != NSOrderedDescending) {
//nah ah ah, a delete should not stick if local mod time is newer! otherwise local changes will be lost //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
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import "SyncResponseFetcher.h" #import "SyncResponseFetcher.h"
#import "SimplenoteSession.h" #import "SimplenoteSession.h"
#import "NSString_NV.h" #import "NSString_NV.h"
#import "NSDictionary+BSJSONAdditions.h"
#import "SynchronizedNoteProtocol.h" #import "SynchronizedNoteProtocol.h"
#import "NoteObject.h" #import "NoteObject.h"
#import "DeletedNoteObject.h" #import "DeletedNoteObject.h"
Expand Down Expand Up @@ -117,9 +118,9 @@ - (SyncResponseFetcher*)fetcherForEntry:(id)entry {
originalNote = entry; originalNote = entry;
entry = [[entry syncServicesMD] objectForKey:SimplenoteServiceName]; 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", [NSDictionary dictionaryWithObjectsAndKeys: email, @"email",
authToken, @"auth", [entry objectForKey:@"key"], @"key", nil]]; authToken, @"auth", nil]];
SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL POSTData:nil delegate:self]; SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL POSTData:nil delegate:self];
//remember the note for later? why not. //remember the note for later? why not.
if (originalNote) [fetcher setRepresentedObject:originalNote]; if (originalNote) [fetcher setRepresentedObject:originalNote];
Expand All @@ -141,18 +142,41 @@ - (NSDictionary*)preparedDictionaryWithFetcher:(SyncResponseFetcher*)fetcher rec
//logic abstracted for subclassing //logic abstracted for subclassing


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


NSMutableDictionary *entry = [NSMutableDictionary dictionaryWithCapacity:5]; NSDictionary *rawObject = nil;
[entry setObject:[headers objectForKey:@"Note-Key"] forKey:@"key"]; @try {
[entry setObject:[NSNumber numberWithInt:[[headers objectForKey:@"Note-Deleted"] intValue]] forKey:@"deleted"]; rawObject = [NSDictionary dictionaryWithJSONString:bodyString];
[entry setObject:[NSNumber numberWithDouble:[[headers objectForKey:@"Note-Createdate"] absoluteTimeFromSimplenoteDate]] forKey:@"create"]; }
[entry setObject:[NSNumber numberWithDouble:[[headers objectForKey:@"Note-Modifydate"] absoluteTimeFromSimplenoteDate]] forKey:@"modify"]; @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"]; 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); //NSLog(@"fetched entry %@" , entry);

return entry; return entry;
} }


Expand All @@ -166,7 +190,17 @@ - (void)syncResponseFetcher:(SyncResponseFetcher*)fetcher receivedData:(NSData*)
[NSNumber numberWithInt:[fetcher statusCode]], @"StatusCode", nil]]; [NSNumber numberWithInt:[fetcher statusCode]], @"StatusCode", nil]];
} }
} else { } 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) { if (entryFinishedCount >= [entriesToCollect count] || stopped) {
Expand Down Expand Up @@ -224,18 +258,32 @@ - (SyncResponseFetcher*)_fetcherForNote:(NoteObject*)aNote creator:(BOOL)doesCre
CFAbsoluteTime modNum = doesCreate ? modifiedDateOfNote(aNote) : [[info objectForKey:@"modify"] doubleValue]; 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 //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]; NSDictionary *params = [NSDictionary 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


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


NSURL *noteURL = [SimplenoteSession servletURLWithPath:@"/api/note" parameters:params]; NSMutableDictionary *rawObject = [NSMutableDictionary dictionaryWithCapacity: 12];
SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL bodyStringAsUTF8B64:noteBody delegate:self]; 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]; [fetcher setRepresentedObject:aNote];
return [fetcher autorelease]; return [fetcher autorelease];
} }
Expand All @@ -262,10 +310,13 @@ - (SyncResponseFetcher*)fetcherForDeletingNote:(DeletedNoteObject*)aDeletedNote
} }
NSAssert([info objectForKey:@"key"], @"fetcherForDeletingNote: got deleted note and couldn't find a key anywhere!"); 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", [NSDictionary dictionaryWithObjectsAndKeys: email, @"email",
authToken, @"auth", [info objectForKey:@"key"], @"key", nil]]; authToken, @"auth", nil]];
SyncResponseFetcher *fetcher = [[SyncResponseFetcher alloc] initWithURL:noteURL POSTData:nil delegate:self]; 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]; [fetcher setRepresentedObject:aDeletedNote];
return [fetcher autorelease]; return [fetcher autorelease];


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


- (NSDictionary*)preparedDictionaryWithFetcher:(SyncResponseFetcher*)fetcher receivedData:(NSData*)data { - (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 *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]) { if ([fetcher representedObject]) {
id <SynchronizedNote> aNote = [fetcher representedObject]; id <SynchronizedNote> aNote = [fetcher representedObject];
[result setObject:aNote forKey:@"NoteObject"]; [result setObject:aNote forKey:@"NoteObject"];
Expand All @@ -323,19 +396,37 @@ - (NSDictionary*)preparedDictionaryWithFetcher:(SyncResponseFetcher*)fetcher rec


NSAssert([aNote isKindOfClass:[NoteObject class]], @"received a non-noteobject from a fetcherForCreatingNote: operation!"); 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 //don't need to store a separator for newly-created notes; when nil it is presumed the default separator
[aNote setSyncObjectAndKeyMD:[NSDictionary dictionaryWithObjectsAndKeys: [aNote setSyncObjectAndKeyMD:syncMD forService:SimplenoteServiceName];
[NSNumber numberWithDouble:modifiedDateOfNote(aNote)], @"modify",
[NSNumber numberWithDouble:createdDateOfNote(aNote)], @"create",
keyString, @"key", nil] forService:SimplenoteServiceName];
[(NoteObject*)aNote makeNoteDirtyUpdateTime:NO updateFile:NO]; [(NoteObject*)aNote makeNoteDirtyUpdateTime:NO updateFile:NO];
} else if (@selector(fetcherForDeletingNote:) == fetcherOpSEL) { } else if (@selector(fetcherForDeletingNote:) == fetcherOpSEL) {
//this note has been successfully deleted, and can now have its Simplenote syncServiceMD entry removed //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 //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"); NSAssert([aNote isKindOfClass:[DeletedNoteObject class]], @"received a non-deletednoteobject from a fetcherForDeletingNote: operation");
[aNote removeAllSyncMDForService:SimplenoteServiceName]; [aNote removeAllSyncMDForService:SimplenoteServiceName];
} else if (@selector(fetcherForUpdatingNote:) == fetcherOpSEL) { } 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 { } else {
NSLog(@"%s called with unknown opSEL: %s", _cmd, fetcherOpSEL); NSLog(@"%s called with unknown opSEL: %s", _cmd, fetcherOpSEL);
} }
Expand Down
6 changes: 6 additions & 0 deletions SimplenoteSession.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ extern NSString *SimplenoteSeparatorKey;


NSMutableSet *collectorsInProgress; NSMutableSet *collectorsInProgress;


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

id delegate; id delegate;
} }


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


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


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


Expand Down
Loading

0 comments on commit 3a45ca9

Please sign in to comment.