Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions .github/workflows/release-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Contentstack iOS Persistence SDK Release

on:
push:
branches:
- main

jobs:
release:
name: Release Contentstack iOS Persistence SDK Release
runs-on: macos-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode.app && /usr/bin/xcodebuild -version

- name: Install dependencies
run: |
pod install --repo-update

- name: Build
run: |
# Add commands to build and test your package
xcodebuild -workspace ContentstackPersistence.xcworkspace -scheme 'ContentstackPersistence' -destination 'platform=iOS Simulator,name=iPhone 13 Pro'

- name: Tag release
id: tag
run: |
git tag v0.1.2 # Replace with your desired version number
echo "::set-output name=tag::v0.1.2" # Replace with the same version number as above

- name: Push tag
uses: ad-m/github-push-action@v0.6.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ steps.tag.outputs.tag }}

- name: CocoaPods trunk push
run: pod trunk push
env:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
6 changes: 6 additions & 0 deletions ContentstackPersistence.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
0F449A752AE0EAD400693DE1 /* PersistenceModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F449A732AE0EAD400693DE1 /* PersistenceModel.h */; };
0F449A762AE0EAD400693DE1 /* PersistenceModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F449A742AE0EAD400693DE1 /* PersistenceModel.m */; };
44D20D49439592B697575ADF /* libPods-ContentstackPersistenceTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0181A1F1F3118F9E9E07F420 /* libPods-ContentstackPersistenceTests.a */; };
477F8B052B7B9EB600455CB3 /* BSONOIDGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 477F8B042B7B9EB600455CB3 /* BSONOIDGenerator.m */; };
4E38FC5F2DF0928B8F15875A /* libPods-ContentstackPersistence.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 554203F0D46030984D06105D /* libPods-ContentstackPersistence.a */; };
FA629A6A2186C80E00440BA5 /* ContentstackPersistence.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA629A602186C80E00440BA5 /* ContentstackPersistence.framework */; };
FA629A6F2186C80F00440BA5 /* ContentstackPersistenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA629A6E2186C80F00440BA5 /* ContentstackPersistenceTests.m */; };
Expand Down Expand Up @@ -45,6 +46,8 @@
0FA9B00A22651307003AAE25 /* ContentstackPersistenceRealm.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = ContentstackPersistenceRealm.podspec; sourceTree = SOURCE_ROOT; };
18AA084F9E0C06DDAB20E086 /* Pods-ContentstackPersistenceTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContentstackPersistenceTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ContentstackPersistenceTests/Pods-ContentstackPersistenceTests.release.xcconfig"; sourceTree = "<group>"; };
2C54B17E607841EF4F9652D3 /* Pods-ContentstackPersistence.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContentstackPersistence.release.xcconfig"; path = "Pods/Target Support Files/Pods-ContentstackPersistence/Pods-ContentstackPersistence.release.xcconfig"; sourceTree = "<group>"; };
477F8B032B7B9E2C00455CB3 /* BSONOIDGenerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSONOIDGenerator.h; sourceTree = "<group>"; };
477F8B042B7B9EB600455CB3 /* BSONOIDGenerator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSONOIDGenerator.m; sourceTree = "<group>"; };
554203F0D46030984D06105D /* libPods-ContentstackPersistence.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ContentstackPersistence.a"; sourceTree = BUILT_PRODUCTS_DIR; };
DA6A5F28D3C66575D458C1FA /* Pods-ContentstackPersistenceTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContentstackPersistenceTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ContentstackPersistenceTests/Pods-ContentstackPersistenceTests.debug.xcconfig"; sourceTree = "<group>"; };
FA629A602186C80E00440BA5 /* ContentstackPersistence.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ContentstackPersistence.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -144,6 +147,8 @@
FA629A7D2186C88600440BA5 /* SyncProtocol.h */,
FA629A632186C80E00440BA5 /* ContentstackPersistence.h */,
FA629A642186C80E00440BA5 /* Info.plist */,
477F8B032B7B9E2C00455CB3 /* BSONOIDGenerator.h */,
477F8B042B7B9EB600455CB3 /* BSONOIDGenerator.m */,
);
path = ContentstackPersistence;
sourceTree = "<group>";
Expand Down Expand Up @@ -338,6 +343,7 @@
FA629A832186C88600440BA5 /* SyncManager.m in Sources */,
FA629A8A2186C8BC00440BA5 /* RealmStore.m in Sources */,
0F449A762AE0EAD400693DE1 /* PersistenceModel.m in Sources */,
477F8B052B7B9EB600455CB3 /* BSONOIDGenerator.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
25 changes: 25 additions & 0 deletions ContentstackPersistence/BSONOIDGenerator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// BSONOIDGenerator.h
// ContentstackPersistence
//
// Created by Vikram Kalta on 13/02/2024.
// Copyright © 2024 Contentstack. All rights reserved.
//

#ifndef BSONOIDGenerator_h
#define BSONOIDGenerator_h

#import <Foundation/Foundation.h>

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

@interface BSONOIDGenerator : NSObject

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

@end

#endif /* BSONOIDGenerator_h */
103 changes: 103 additions & 0 deletions ContentstackPersistence/BSONOIDGenerator.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// BSONOIDGenerator.m
// ContentstackPersistence
//
// Created by Vikram Kalta on 13/02/2024.
// Copyright © 2024 Contentstack. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#import "BSONOIDGenerator.h"
#import <UIKit/UIKit.h>

@implementation BSONOIDGenerator
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
133 changes: 127 additions & 6 deletions ContentstackPersistence/SyncManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#import "PersistenceModel.h"
#import <CoreData/CoreData.h>
#import <objc/runtime.h>
#import "CSIOInternalHeaders.h"
#import "BSONOIDGenerator.h"

@implementation SyncManageSwiftSupport
+ (BOOL)isSwiftClassName:(NSString *)className {
Expand Down Expand Up @@ -92,24 +94,35 @@ -(NSString*) getSyncToken {
return syncToken;
}

-(NSString*) getSeqId {
__block NSString *seqId;
[self.persistanceDelegate performBlockAndWait:^{
id<SyncStoreProtocol> syncStack = [self findOrCreate:self->_syncStack predicate:nil];
seqId = syncStack.seqId;
}];
return seqId;
}

-(void)updateSyncStack:(SyncStack*)syncStack {
id<SyncStoreProtocol> syncStore = (id<SyncStoreProtocol>)[self findOrCreate:_syncStack predicate:nil];
syncStore.syncToken = syncStack.syncToken;
syncStore.paginationToken = syncStack.paginationToken;
syncStore.seqId = syncStack.seqId;
}

-(void)syncWithInit:(BOOL) shouldInit onCompletion:(void (^)(double, BOOL, NSError * _Nullable))completion {
NSString *paginationToken = [self getPaginationToken];//csb0e8704474a9624785098d233edd2715`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented tokens should be removed

NSString *syncToken = [self getSyncToken];//cse053899d15e9e94cd3751df26f719c87
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented tokens should be removed

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ishaileshmishra , yes they are removed.

/* The following method will be deprecated soon */
-(void)syncWithInit:(BOOL) shouldInit syncToken:(NSString *)syncToken onCompletion:(void (^)(double, BOOL, NSError * _Nullable))completion {
__weak typeof (self) weakSelf = self;
NSString *paginationToken = [self getPaginationToken];

id completionBlock = ^(SyncStack * _Nullable syncStack, NSError * _Nullable error) {
if (error != nil) {
//Init the sync API on pagination and sync token errors
if (error.code == 422) {
if (error.userInfo && error.userInfo[@"errors"]) {
NSDictionary *errors = error.userInfo[@"errors"];
if (errors[@"pagination_token"] || errors[@"sync_token"]) {
[weakSelf syncWithInit:true onCompletion:completion];
[weakSelf syncWithInit:true syncToken:nil onCompletion:completion];
return;
}
}
Expand Down Expand Up @@ -153,7 +166,6 @@ -(void)syncWithInit:(BOOL) shouldInit onCompletion:(void (^)(double, BOOL, NSErr
}];
completion(self.percentageComplete, isSyncCompleted, error);
}

};
if (shouldInit) {
self.percentageComplete = 0;
Expand All @@ -168,8 +180,117 @@ -(void)syncWithInit:(BOOL) shouldInit onCompletion:(void (^)(double, BOOL, NSErr
}
}

-(void)syncWithSeqId:(NSString *)seqId syncToken:(NSString *)syncToken onCompletion:(void (^)(double, BOOL, NSError * _Nullable))completion {
__weak typeof (self) weakSelf = self;
id completionBlock = ^(SyncStack * _Nullable syncStack, NSError * _Nullable error) {
if (error != nil) {
//Init the sync API on pagination and sync token errors
if (error.code == 422) {
if (error.userInfo && error.userInfo[@"errors"]) {
NSDictionary *errors = error.userInfo[@"errors"];
if (errors[@"seq_id"]) {
[weakSelf syncWithSeqId:nil syncToken:nil onCompletion:completion];
return;
}
}
}
completion(self.percentageComplete, false, error);
} else {
__block BOOL isSyncCompleted = false;
[self.persistanceDelegate performBlockAndWait:^{

if (syncStack.items) {
self.percentageComplete = ((double)(syncStack.skip) + (double)syncStack.items.count) / (double)syncStack.totalCount;

if (syncToken && syncStack.seqId == nil && syncStack.items.count > 0) {
// Generate seq id.
[self generateAndPersistSeqId:syncStack];
}
}

//entry_unpublished || entry_deleted
NSArray *deletedEntryArray = [syncStack.items filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"type = 'entry_unpublished' || type = 'entry_deleted' "]];
[self deleteEntries:deletedEntryArray];

//asset_unpublished || asset_deleted
if (self->_asset != nil) {
NSArray *deletedAssetsArray = [syncStack.items filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"type = 'asset_unpublished' || type = 'asset_deleted'"]];
[self.persistanceDelegate delete:self->_asset inUid:[deletedAssetsArray valueForKeyPath:@"data.uid"]];
}

//asset_published
NSArray *publishAssetArray = [syncStack.items filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"type = 'asset_published'"]];
[self createAssets:publishAssetArray];

//entry_published
NSArray *publishEntryArray = [syncStack.items filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"type = 'entry_published'"]];
[self createEntries:publishEntryArray];

//Sync toke Update
if (syncStack.seqId != nil) {
isSyncCompleted = true;
[self updateSyncStack:syncStack];
}
//Save context
if ([self.persistanceDelegate respondsToSelector:@selector(save)]) {
[self.persistanceDelegate save];
}
}];
completion(self.percentageComplete, isSyncCompleted, error);
}
};
if (seqId != nil) {
[_stack syncSeqId:seqId syncToken:syncToken completion:completionBlock];
} else if (syncToken != nil) {
[_stack syncSeqId:seqId syncToken:syncToken completion:completionBlock];
} else {
self.percentageComplete = 0;
[_stack initSeqSync:completionBlock];
}
}

-(void)generateAndPersistSeqId:(SyncStack * _Nullable)syncStack {
// Get the last object's event_at
NSDictionary *lastObject = nil;
for (NSInteger i = syncStack.items.count - 1; i >= 0; i--) {
id object = syncStack.items[i];
if ([object isKindOfClass:[NSDictionary class]]) {
lastObject = object;
break;
}
}
syncStack.seqId = [self generateSeqId:[lastObject objectForKey:@"event_at"]];
syncStack.syncToken = nil;

}

-(NSString *)generateSeqId:(NSString *)eventAt {
// Create a date formatter to parse the date string
NSDateFormatter *dateFormater = [[NSDateFormatter alloc] init];
dateFormater.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
NSDate *date = [dateFormater dateFromString:eventAt];
if (date) {
// Convert the NSDate object to an NSTimeInterval
NSTimeInterval timeInterval = [date timeIntervalSince1970];
NSInteger timeIntervalInSeconds = (NSInteger)timeInterval;
return [BSONOIDGenerator generate:timeIntervalInSeconds];
} else {
// Handle case where date conversion failed.
[NSException raise:@"Unable to parse date string" format:@"Invalid date format %@", eventAt];
return nil;
}
}

-(void)sync:(void (^)(double, BOOL, NSError * _Nullable))completion {
[self syncWithInit:false onCompletion:completion];
NSString *syncToken = [self getSyncToken];
NSString *seqId = [self getSeqId];
if (syncToken) {
[self syncWithSeqId:nil syncToken:syncToken onCompletion:completion];
} else if (seqId) {
[self syncWithSeqId:seqId syncToken:nil onCompletion:completion];
} else {
[self syncWithSeqId:nil syncToken:nil onCompletion:completion];
}
}

-(void)createAssets:(NSArray*)assetsArray {
Expand Down
1 change: 1 addition & 0 deletions ContentstackPersistence/SyncProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
@required
@property (nonatomic)NSString* paginationToken;
@property (nonatomic)NSString* syncToken;
@property (nonatomic)NSString* seqId;
@end

/**
Expand Down
Loading