Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

.vscode/

## Build generated
Build/
Expand Down
6 changes: 6 additions & 0 deletions Contentstack.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
23A53F501E277BBE001DBE35 /* NSObject+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 23A53F331E276BA5001DBE35 /* NSObject+Extensions.m */; };
23A53F5A1E277CD3001DBE35 /* Contentstack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 230B38C01C16E98B00444A14 /* Contentstack.framework */; };
23B6F12A1B5662EE00A9E983 /* ISO8601DateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 23B6F1281B5662EE00A9E983 /* ISO8601DateFormatter.m */; };
47CD50872B7625B90032747B /* BSONObjectIdGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 47CD50862B7625B90032747B /* BSONObjectIdGenerator.m */; };
565E11BB1BD76654005AD47F /* MMDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 565E11AA1BD76654005AD47F /* MMDocument.m */; };
565E11BC1BD76654005AD47F /* MMElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 565E11AD1BD76654005AD47F /* MMElement.m */; };
565E11BD1BD76654005AD47F /* MMGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 565E11AF1BD76654005AD47F /* MMGenerator.m */; };
Expand Down Expand Up @@ -205,6 +206,8 @@
23B6F1281B5662EE00A9E983 /* ISO8601DateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ISO8601DateFormatter.m; sourceTree = "<group>"; };
23C545FB1C1976FE007BBD27 /* ios-build-framework-script.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "ios-build-framework-script.sh"; sourceTree = "<group>"; };
3CF581B9F7526EDA48ED5C6F /* Pods-ContentstackTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContentstackTest.debug.xcconfig"; path = "Target Support Files/Pods-ContentstackTest/Pods-ContentstackTest.debug.xcconfig"; sourceTree = "<group>"; };
47CD50852B76243E0032747B /* BSONObjectIdGenerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSONObjectIdGenerator.h; sourceTree = "<group>"; };
47CD50862B7625B90032747B /* BSONObjectIdGenerator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSONObjectIdGenerator.m; sourceTree = "<group>"; };
565E11A91BD76654005AD47F /* MMDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMDocument.h; sourceTree = "<group>"; };
565E11AA1BD76654005AD47F /* MMDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMDocument.m; sourceTree = "<group>"; };
565E11AB1BD76654005AD47F /* MMDocument_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMDocument_Private.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -383,6 +386,8 @@
23A53F311E276BA5001DBE35 /* CSIOURLCache.m */,
23A53F321E276BA5001DBE35 /* NSObject+Extensions.h */,
23A53F331E276BA5001DBE35 /* NSObject+Extensions.m */,
47CD50852B76243E0032747B /* BSONObjectIdGenerator.h */,
47CD50862B7625B90032747B /* BSONObjectIdGenerator.m */,
);
path = ContentstackInternal;
sourceTree = "<group>";
Expand Down Expand Up @@ -790,6 +795,7 @@
0FEAEF2A2361A18600985FF9 /* CSURLSessionManager.m in Sources */,
0FEAEF2C2361A18600985FF9 /* CSError.m in Sources */,
23B6F12A1B5662EE00A9E983 /* ISO8601DateFormatter.m in Sources */,
47CD50872B7625B90032747B /* BSONObjectIdGenerator.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
21 changes: 21 additions & 0 deletions Contentstack/Stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,27 @@ BUILT_ASSUME_NONNULL_BEGIN
*/
- (void)sync:(void (^)(SyncStack * BUILT_NULLABLE_P syncStack, NSError * BUILT_NULLABLE_P error))completionBlock;

/**
You can use the seq_id (that you receive after every sync call) to get the updated content next time.
The seq id is alternative to pagination token where it fetches all the events data in sequence, and the details of the content that was deleted or updated.

//Obj-C

NSString *seqId = @"seq_id"; //Seq id
[stack seqId:seqId completion:^(SyncStack * _Nullable syncStack, NSError * _Nullable error) {

}];

//Swift
var seqId = @"seq_id"; //Seq id
stack.seqId(seqId, completion: { ( syncStack:SyncStack, error: NSError) in

})

@param token Sync token from where to perform sync
@param completionBlock called synchronization is done.
*/
-(void)syncSeqId:(NSString *)seqId completion:(void (^)(SyncStack * BUILT_NULLABLE_P syncResult, NSError * BUILT_NULLABLE_P error))completionBlock;
/**
If the result of the initial sync (or subsequent sync) contains more than 100 records,
the response would be paginated. It provides pagination token in the response. However,
Expand Down
11 changes: 8 additions & 3 deletions Contentstack/Stack.m
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ -(void)sync:(void (^)(SyncStack * BUILT_NULLABLE_P syncResult, NSError * BUILT_N
}];
}

-(void)syncSeqId:(NSString *)seqId completion:(void (^)(SyncStack * BUILT_NULLABLE_P syncResult, NSError * BUILT_NULLABLE_P error))completionBlock {
SyncStack *syncStack = [self getCurrentSyncStack:@{@"seq_id": seqId}];
[self sync:syncStack completion:^(SyncStack * _Nullable syncResult, NSError * _Nullable error) {
completionBlock(syncResult, error);
}];
}

-(void)syncToken:(NSString *)token completion:(void (^)(SyncStack * BUILT_NULLABLE_P syncResult, NSError * BUILT_NULLABLE_P error))completionBlock {
SyncStack *syncStack = [self getCurrentSyncStack:@{@"sync_token": token}];
[self sync:syncStack completion:^(SyncStack * _Nullable syncResult, NSError * _Nullable error) {
Expand Down Expand Up @@ -227,8 +234,6 @@ -(void)syncOnly:(NSString *)contentType locale:(NSString*)locale from:(NSDate *)
[self sync:syncStack completion:^(SyncStack * _Nullable syncResult, NSError * _Nullable error) {
completionBlock(syncResult, error);
}];


}

-(void)syncOnly:(NSString *)contentType locale:(NSString*)locale from:(NSDate *)date publishType:(PublishType)publishType completion:(void (^)(SyncStack * _Nullable, NSError * _Nullable))completionBlock {
Expand All @@ -247,7 +252,7 @@ -(void)syncOnly:(NSString *)contentType locale:(NSString*)locale from:(NSDate *)

- (void)sync:(SyncStack *)syncResult completion:(void (^)(SyncStack * BUILT_NULLABLE_P syncResult, NSError * BUILT_NULLABLE_P error))completionBlock {
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:[syncResult getParameters]];
if (syncResult.paginationToken == nil && syncResult.syncToken == nil) {
if (syncResult.seqId == nil && syncResult.syncToken == nil) {
[params setValue:self.environment forKey:@"environment"];
}
[self syncCallWithParams:params withCompletion:^(NSDictionary *responseDictionary, NSError *error) {
Expand Down
4 changes: 4 additions & 0 deletions Contentstack/SyncStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ NS_ASSUME_NONNULL_BEGIN
*Readonly property to delta sync.
*/
@property (nonatomic, copy, readonly) NSString * _Nullable syncToken;
/**
*Readonly property to sync.
*/
@property (nonatomic, copy, readonly) NSString * _Nullable seqId;
/**
* Readonly property to check skip count
*/
Expand Down
72 changes: 67 additions & 5 deletions Contentstack/SyncStack.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#import "SyncStack.h"
#import "CSIOInternalHeaders.h"
#import "BSONObjectIdGenerator.h"

@implementation SyncStack

-(instancetype)initWithParmas:(NSDictionary*) parmas {
Expand All @@ -17,6 +19,11 @@ -(instancetype)initWithParmas:(NSDictionary*) parmas {
if ([[self.params objectForKey:@"sync_token"] isKindOfClass:[NSString class]]) {
self.syncToken = [self.params objectForKey:@"sync_token"];
}
if ([[self.params objectForKey:@"seq_id"]
isKindOfClass:[NSString
class]]) {
self.seqId = [self.params objectForKey:@"seq_id"];
}
self.items = [NSArray array];
}
return self;
Expand All @@ -27,8 +34,15 @@ -(void)parseSyncResult:(NSDictionary *)dictionary {
self.syncToken = nil;
self.paginationToken = nil;
self.hasMorePages = false;

if ([dictionary objectForKey:@"sync_token"]) {
self.syncToken = [dictionary objectForKey:@"sync_token"];
/* For existing persisted data sync token should be set to nil. Seq Id will be generated for sync with the help of event_at field.
*/
[self setExistingTokensNilAndGenerateSeqId:@"sync_token" dict:dictionary];
}

if ([dictionary objectForKey:@"last_seq_id"]) {
self.seqId = [dictionary objectForKey:@"last_seq_id"];
}
if ([dictionary objectForKey:@"pagination_token"]) {
self.hasMorePages = true;
Expand All @@ -44,21 +58,69 @@ -(void)parseSyncResult:(NSDictionary *)dictionary {
self.limit = [[dictionary objectForKey:@"limit"] unsignedIntValue];
}
if ([dictionary objectForKey:@"items"] && [[dictionary objectForKey:@"items"] isKindOfClass:[NSArray class]]) {
self.items = [dictionary objectForKey:@"items"];//[[self.items mutableCopy] arrayByAddingObjectsFromArray:[dictionary objectForKey:@"items"]];
self.items = [dictionary objectForKey:@"items"];
if (self.items.count > 0) {
self.hasMorePages = true;
}
}
}
}

-(NSDictionary*)getParameters {
NSMutableDictionary *syncParams = [NSMutableDictionary dictionary];
if (self.syncToken != nil) {
if (self.seqId != nil) {
[syncParams setValue:self.seqId forKey:@"seq_id"];
} else if (self.syncToken != nil) {
[syncParams setValue:self.syncToken forKey:@"sync_token"];
}else if (self.paginationToken != nil) {
} else if (self.paginationToken != nil) {
[syncParams setValue:self.paginationToken forKey:@"pagination_token"];
}else {
} else {
syncParams = [NSMutableDictionary dictionaryWithDictionary:self.params];
[syncParams setValue:@"true" forKey:@"seq_id"];
[syncParams setValue:@"true" forKey:@"init"];
}
return syncParams;
}

-(NSString*)generateSeqId:(NSString*) eventAt {
// Create a date formatter to parse the date string
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
NSDate *date = [dateFormatter dateFromString:eventAt];
if (date) {
// Convert the NSDate object to an NSTimeInterval
NSTimeInterval timeInterval = [date timeIntervalSince1970];
NSInteger timeIntervalInSeconds = (NSInteger)timeInterval;

return [BSONObjectIdGenerator generate:timeIntervalInSeconds];
} else {
// Handle case where date conversion failed.
[NSException raise:@"Unable to parse date string" format:@"Invalid date format %@", eventAt];
return nil;
}
}

-(void)setExistingTokensNilAndGenerateSeqId:(NSString *) key dict:(NSDictionary *)dictionary {
NSMutableArray * items = [NSMutableArray array];
items = [dictionary objectForKey: @"items"];
if ([items isKindOfClass:[NSArray class]] && items.count > 0) {
// Get the last object's event_at
NSDictionary *lastObject = nil;
for (NSInteger i = items.count - 1; i >= 0; i--) {
id object = items[i];
if ([object isKindOfClass:[NSDictionary class]]) {
lastObject = object;
break;
}
}
self.seqId = [self generateSeqId:[lastObject objectForKey:@"event_at"]];
} else {
if ([key isEqual: @"sync_token"]) {
self.syncToken = [dictionary objectForKey:@"sync_token"];
} else {
self.paginationToken = [dictionary objectForKey:@"pagination_token"];
}
}
}

@end
24 changes: 24 additions & 0 deletions ContentstackInternal/BSONObjectIdGenerator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// BSONObjectIdGenerator.h
// Contentstack
//
// Created by Vikram Kalta on 09/02/2024.
// Copyright © 2024 Contentstack. All rights reserved.
//
#ifndef BSONObjectIdGenerator_h
#define BSONObjectIdGenerator_h

#import <Foundation/Foundation.h>

typedef union {
char bytes[12];
int ints[3];
} bson_oid_t;

@interface BSONObjectIdGenerator : NSObject

+ (NSString *)generate:(NSInteger)timestamp;

@end

#endif /* BSONObjectIdGenerator_h */
102 changes: 102 additions & 0 deletions ContentstackInternal/BSONObjectIdGenerator.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//
// BSONObjectIdGenerator.m
// ThirdPartyExtension
//
// Created by Vikram Kalta on 09/02/2024.
// Copyright © 2024 Contentstack. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#import "BSONObjectIdGenerator.h"

@implementation BSONObjectIdGenerator
static int _incr = 0;

+ (NSString *) generate:(NSInteger)timestamp {
int i = _incr++;
bson_oid_t *oid = malloc(sizeof(bson_oid_t));
time_t t = time(NULL);

// Grab the PID
int pid = [NSProcessInfo processInfo].processIdentifier;

// Get a device identifier. The specification usually has this as the MAC address
// or hostname but we already have a unique device identifier.
//
NSString *identifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];

// MD5 hash the device identifier
NSString *md5HashOfIdentifier = [self md5HashFromString:identifier];
const char *cIdentifier = [md5HashOfIdentifier cStringUsingEncoding:NSUTF8StringEncoding];

// Copy bytes over to our object id. Specification taken from http://www.mongodb.org/display/DOCS/Object+IDs
bson_swap_endian_len(&oid->bytes[0], &timestamp, 4);
bson_swap_endian_len(&oid->bytes[4], &cIdentifier, 3);
bson_swap_endian_len(&oid->bytes[7], &pid, 2);
bson_swap_endian_len(&oid->bytes[9], &i, 3);
NSString *str = [self bson_oid_to_string:oid];

free(oid);

return str;
}

/**
@discussion Given an NSString, returns the MD5 hash of it. Taken from
http://stackoverflow.com/questions/1524604/md5-algorithm-in-objective-c
@param source The source string
@return MD5 hash as a string
*/
+ (NSString *) md5HashFromString:(NSString *)source {
const char *cStr = [source UTF8String];
unsigned char result[16];
CC_MD5(cStr, strlen(cStr), result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}

/**
@discussion Converts a bson_oid_t to an NSString. Mostly taken from https://github.com/mongodb/mongo-c-driver/blob/master/src/bson.c
@param oid The bson_oid_t to convert
@return Autoreleased NSString of 24 hex characters
*/
+ (NSString *) bson_oid_to_string:(bson_oid_t *)oid {
char *str = malloc(sizeof(char) * 25);
static const char hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
int i;
for ( i=0; i<12; i++ ) {
str[2*i] = hex[( oid->bytes[i] & 0xf0 ) >> 4];
str[2*i + 1] = hex[ oid->bytes[i] & 0x0f ];
}
str[24] = '\0';
NSString *string = [NSString stringWithCString:str encoding:NSUTF8StringEncoding];
free(str);
return string;
}


/**
@discussion The ARM architecture is little endian while intel macs are big Endian, so we need to swap endianness if we're compiling on a big Endian architecture.
@param outp The destination pointer
@param inp The source pointer
@param len The length to copy
*/
void bson_swap_endian_len(void *outp, const void *inp, int len) {
const char *in = (const char *)inp;
char *out = (char *)outp;
for (int i = 0; i < len; i ++) {
#if __DARWIN_BIG_ENDIAN
out[i] = in[len - 1 - i];
#elif __DARWIN_LITTLE_ENDIAN
out[i] = in[i];
#endif
}
}

@end
1 change: 1 addition & 0 deletions ContentstackInternal/CSIOInternalHeaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
@property (nonatomic, copy) NSArray *items;
@property (nonatomic, copy) NSString *paginationToken;
@property (nonatomic, copy) NSString *syncToken;
@property (nonatomic, copy) NSString *seqId;

@property (nonatomic, assign) BOOL hasMorePages;
@property (nonatomic, assign) unsigned int skip;
Expand Down