PubNub local messages duplicating? - ios

For my pubnub chat app, I am storing some messages locally to keep it from using tons of wifi/data. It seems to work fine, but sometimes the last message is duplicated. Here is my saving,loading,reloading code.
#pragma mark - PubNub manager methods
- (NSString *)parseMessage:(id)message
{
if ([message isKindOfClass:[NSDictionary class]]) {
NSDictionary *messageAsDict = message;
if ([[messageAsDict objectForKey:#"text"] isKindOfClass:[NSString class]]) {
NSString *messageString = [messageAsDict objectForKey:#"text"];
if (messageString || messageString.length > 0) {
return messageString;
} else {
return #"Unable To Parse Message";
}
} else {
return #"Unable To Parse Message";
}
} else if ([message isKindOfClass:[NSString class]]) {
NSString *messageString = message;
if (messageString.length > 0) {
return messageString;
} else {
return #"Unable To Parse Message";
}
} else {
return #"Unable To Parse Message";
}
}
- (void)saveObjects
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *messagesDirectoryPath = [docDir stringByAppendingPathComponent:#"Messaging"];
if (![[NSFileManager defaultManager] fileExistsAtPath:messagesDirectoryPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:messagesDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
}
NSString *messagesPath = [messagesDirectoryPath stringByAppendingPathComponent:messageFile];
NSString *timeTokenPath = [messagesDirectoryPath stringByAppendingPathComponent:timeTokenFile];
NSString *timeTokenString = [NSString stringWithFormat:#"%ld", (long)lastTimeToken];
[messagesArray writeToFile:messagesPath atomically:YES];
[timeTokenString writeToFile:timeTokenPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
}
- (void)loadObjects
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *messagesDirectoryPath = [docDir stringByAppendingPathComponent:#"Messaging"];
NSString *messagesPath = [messagesDirectoryPath stringByAppendingPathComponent:messageFile];
NSString *timeTokenPath = [messagesDirectoryPath stringByAppendingPathComponent:timeTokenFile];
messagesArray = [NSMutableArray arrayWithContentsOfFile:messagesPath];
if (!messagesArray) {
messagesArray = [[NSMutableArray alloc] init];
[self saveObjects];
}
NSString *timeTokenString = [NSString stringWithContentsOfFile:timeTokenPath encoding:NSUTF8StringEncoding error:&error];
if (![timeTokenString isEqualToString:#""]) {
lastTimeToken = [timeTokenString integerValue];
} else {
lastTimeToken = [self currentTimeToken];
[self saveObjects];
}
}
- (void)reloadMessages
{
messagesArray = [[NSMutableArray alloc] init];
//Get all the chats you missed
[self.pnClient historyForChannel:kCHAT_CHANNEL withCompletion:^(PNHistoryResult *result, PNErrorStatus *status) {
// Check whether request successfully completed or not.
if (!status.isError) {
// Handle downloaded history using:
// result.data.start - oldest message time stamp in response
// result.data.end - newest message time stamp in response
// result.data.messages - list of messages
// Get messages
for (id message in result.data.messages) {
[messagesArray addObject:[self parseMessage:message]];
}
// Set timetoken
lastTimeToken = [self parsePNTimeToken:result.data.end];
// Save stuff
[self saveObjects];
dispatch_async(dispatch_get_main_queue(), ^{
[self.messagesTable reloadData];
[self scrollToBottom];
});
} else {
// Request processing failed.
UIAlertController *errorController = [UIAlertController alertControllerWithTitle:#"Error" message:#"Could not recieve messages" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *retryAction = [UIAlertAction actionWithTitle:#"Retry" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[status retry];
}];
[errorController addAction:cancelAction];
[errorController addAction:retryAction];
[self presentViewController:errorController animated:YES completion:nil];
}
}];
}
- (void)addMessage:(PNMessageResult *)message
{
[messagesArray addObject:[self parseMessage:message.data.message]];
lastTimeToken = [message.data.timetoken integerValue] + 1;
[self saveObjects];
}
- (NSInteger)parsePNTimeToken:(NSNumber *)timeToken
{
return trunc([timeToken integerValue] / pow(10, 7));
}
- (NSInteger)currentTimeToken
{
return [[NSDate date] timeIntervalSince1970];
}
- (void)updateLostMessages
{
[self.pnClient historyForChannel:kCHAT_CHANNEL start:[NSNumber numberWithInteger:lastTimeToken] end:[NSNumber numberWithInteger:[self currentTimeToken]] limit:NSIntegerMax withCompletion:^(PNHistoryResult *result, PNErrorStatus *status) {
NSArray *tempResultArray = result.data.messages;
for (id message in tempResultArray) {
[messagesArray addObject:[self parseMessage:message]];
NSLog(#"%#", [self parseMessage:message]);
}
lastTimeToken = [self currentTimeToken] + 1;
[self saveObjects];
[self loadObjects];
dispatch_async(dispatch_get_main_queue(), ^{
[self.messagesTable reloadData];
[self scrollToBottom];
});
}];
}
These methods are pretty straightforward. the parseMessage: one takes a message from whatever and parses the text to be displayed. The saveObjects saves the timeToken and the messages to the disk, and load loads them. The timetoken methods just convert PN timetokens to a less precision format and get the current time token. The updateLostMessages gets all messages from the last messages timetoken to current, and to not get all the messages.
In viewDidLoad I call [self loadObjects] and then [self updateLostMessages] problem is that messages are duplicating! Help appreciated. Also, sorry for long code.

Haha, silly me. I just forgot a + 1 in the reload method. Well, for anyone who wants to use this code to locally store messages instead of getting all of them, here you go. This code stores a timetoken of the last message (+1), and gets all messages from x time token to now.

Related

Messages deleted in chat room re-appear after re-enter the chat room

This below code is fired when I press delete button after selecting messages I want to delete in chat room.
- (void)deleteButtonPressed:(id)sender {
if (arrayToDelete.count) {
for (NSString *str in arrayToDelete) {
NSLog(#"msgID --> %#",str);
[self.chatModel.dataSource removeObject:str]; //??? Remove data from the screen
[[FMDBManager sharedInstance] deleteMessageByMessageId:str]; //??? Delete data from database
}
[arrayToDelete removeAllObjects];
[self.chatTableView reloadData];
}
}
This line successfully removes selected messages from the chat room.
[self.chatModel.dataSource removeObject:str]; //??? Remove data from the screen
When I go out the chat room and re-enter, those messages still exist, so I have this line below.
[[FMDBManager sharedInstance] deleteMessageByMessageId:str]; //??? Delete data from database
I think the above line should delete those selected messages from the database but when I re-enter the chat room I still see those messages. Here below are related code to that.
- (void)deleteMessageByMessageId:(NSString *)messageId {
FMDatabase *db = [self getterDataBase];
[db open];
NSString *sqlString = [NSString stringWithFormat:#"DELETE FROM message WHERE messageId = '%#'",messageId];
BOOL status = [db executeUpdate:sqlString];
NSLog(#"Delete MessageById:%# Status:%d",messageId,status);
[db close];
}
I've found that when chat room calls viewDidLoad it will eventually call the method callBackGetChannelLogNew where server will sync-up data with chat room tableview and local database.
- (void)callBackGetChannelLogNew:(NSDictionary *)resultDataDic status:(enumAPI_STATUS)eAPI_STATUS {
if (isFirstTimeUpdate) {
}
if (eAPI_STATUS == API_STATUS_SUCCEE) {
NSString *readString=[NSString stringWithFormat:#"%#",resultDataDic[#"read_arr"]];
if ([readString isEqualToString:#""]) {
// NSLog(#"read_arr is empty");
}
else {
NSArray *read_arr=resultDataDic[#"read_arr"];
// Copy read_arr
self.readArray=[read_arr mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[self dealWithReadArray:read_arr];
});
}
NSArray *data = [resultDataDic objectForKey:#"msg"];
if (data.count > 0) {
apiHaveData = YES;
} else {
apiHaveData = NO;
self.loadIngView.hidden = YES;
isLoadingData = NO;
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
// Reverse order of data
NSArray* reversedArray = [[data reverseObjectEnumerator] allObjects];
NSMutableArray *messageFromOtherArray = [NSMutableArray new];
NSMutableArray *messageAllArray = [NSMutableArray new];
for (int i = 0; i < reversedArray.count; i++) {
NSDictionary *_dic = reversedArray[i];
NSString *fromId = [_dic objectForKey:#"fid"];
NSString *message = [NSString stringWithFormat:#"%#",[_dic objectForKey:#"say"]];
if ([ObjectManager getChatMessageKindWithString:message] == MessageTypeText) {
message = [ObjectManager decryptWithString:message];
}
NSString *messageId = [_dic objectForKey:#"mid"];
NSString *toId = [_dic objectForKey:#"tid"];
NSDateFormatter *_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = #"yyyy-MM-dd HH:mm:ss.SSS";
NSDate *date_t = [NSDate dateWithTimeIntervalSince1970:[[_dic objectForKey:#"t"] doubleValue]/1000.0]; //換算成日期
NSString *stringDate = [_formatter stringFromDate:date_t];
NSString *sendDate = stringDate;
NSString *lid = _dic[#"lid"];
NSMutableDictionary *myDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
fromId,#"fromId",
message,#"message",
messageId,#"messageId",
sendDate,#"sendDate",
toId,#"toId",
lid,#"lid",
nil];
NSString *isRead;
if (_chatRoomType == ChatRoomTypePrivate) {
if ([_dic[#"r"] intValue]) {
isRead = #"1";
myDic[#"isRead"] = isRead;
lastReadMessageId = [NSString stringWithFormat:#"%#",messageId];
}
}
if (i == 0) {
if (lidForAPI != [_dic[#"lid"] intValue]) {
lidForAPI = [_dic[#"lid"] intValue];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
apiHaveData = NO;
self.loadIngView.hidden = YES;
isLoadingData = NO;
});
return ;
}
}
if (![myDic[#"fromId"] isEqualToString:[User sharedUser].account]) {
[messageFromOtherArray addObject:myDic];
}
if (_chatRoomType == ChatRoomTypeGroup) {
[myDic setObject:#"1" forKey:#"isGroupMessage"];
}
[myDic setObject:#"1" forKey:#"did_I_Read"];
[messageAllArray addObject:myDic];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self setupViewWithMessageArray:messageAllArray]; //???? Here server sync-up data with tableview
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
if (_chatRoomType == ChatRoomTypePrivate) {
if (messageFromOtherArray.count > 0 && isUplaodLastRead == NO) {
isUplaodLastRead = YES;
NSDictionary *lastReadMsgDic = messageFromOtherArray.lastObject;
[self callMsgReadAPI:lastReadMsgDic];
}
} else {
if (messageAllArray.count > 0 && isUplaodLastRead == NO) {
isUplaodLastRead = YES;
NSDictionary *lastReadMsgDic = messageAllArray.lastObject;
[self callMsgReadAPI:lastReadMsgDic];
}
}
self.chatModel.channelTopic = _topic;
NSArray *read_arr=resultDataDic[#"read_arr"];
[self dealMySendMessageReadedWithReadArray:read_arr AndMessageArray:messageAllArray];
[self saveMessageWithArray:messageAllArray]; //???? Here server sync-up data with local db
});
});
}
}
This lines will sync-up data from server to tableview
dispatch_async(dispatch_get_main_queue(), ^{
[self setupViewWithMessageArray:messageAllArray]; //???? Here server sync-up data with tableview
});
Here below is the method setupViewWithMessageArray
- (void)setupViewWithMessageArray:(NSArray *)messageAllArray {
if (!isFirstTimeUpdate) {
isFirstTimeUpdate = YES;
self.chatModel.dataSource = nil;
[self.chatTableView reloadData];
self.chatModel.dataSource = [[NSMutableArray alloc] init];
[self addMessageWithArray:messageAllArray];
[self.chatTableView reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.chatModel.dataSource.count-1 inSection:0];
[self.chatTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
} else {
[self addMessageWithArray:messageAllArray];
[self reloadTableViewWithoutMove];
}
self.loadIngView.hidden = YES;
isLoadingData = NO;
if (_chatRoomType == ChatRoomTypePrivate) {
if (lastReadMessageId) {
[self.chatModel setPrivateChatListAllReadFormMessageId:lastReadMessageId];
}
}
}
This line will sync-up data from server to local db
[self saveMessageWithArray:messageAllArray]; //???? Here server sync-up data with local db
Here below is the method saveMessageWithArray
- (void)saveMessageWithArray:(NSArray *)messageArray {
for (NSDictionary *myDic in messageArray) {
if (![[FMDBManager sharedInstance] didMessageExistWithMessageID:[myDic objectForKey:#"messageId"]]) {
[[FMDBManager sharedInstance] SaveMessage:myDic];
}
else {
NSString *mid=[NSString stringWithFormat:#"%#",myDic[#"messageId"]];
NSString *isRead = myDic[#"isReaed"];
if (isRead) {
[[FMDBManager sharedInstance] UpdateisReadWithMessageID:mid];
}
}
}
}
So I think now my question is how I can update messageAllArray with arrayToDelete before server sync-up?

Need to share songs with different devices using multipeer connectivity.

Requirement:
We are able to make connection from host device to multiple slave devices. For example, if device A initiate connection to device B and C, the contributor devices can accept peer connection and connected to device A. Here A is master device and B and C are contributors device. Now if B share their songs to A, A can play songs and see songs information. In meantime C will be idle but should connected. When B will finish to play songs, then C can also able to share their songs to device A.
Here are the problem that we have faced to achieve above tasks:
1. As soon as B started sharing songs to A, C got crashed. But A still able to play song shared by B.
array = [[NSMutableArray alloc] initWithObjects:[self.session connectedPeers], nil];
[_session sendData:[NSKeyedArchiver archivedDataWithRootObject:[info mutableCopy]] toPeers:[array objectAtIndex:0] withMode:MCSessionSendDataUnreliable error: &error];
NSLog(#"localizedDescription %#",error);
To overcome from this problem directly pass array without index here toPeers:array. It is working and we are able to share and play songs with device A, but song information not receive to device A.
Here are full code that we are using:
contributor Controller :
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
[self dismissViewControllerAnimated:YES completion:nil];
someMutableArray = [mediaItemCollection items];
counter = 0;
if(someMutableArray.count>1){
BOOL isselectsongone=YES;
[[NSUserDefaults standardUserDefaults] setBool:isselectsongone forKey:#"isselectsongone"];
}
[self showSpinner];
[self someSelector:nil];
}
- (void)someSelector:(NSNotification *)notification {
if(notification && someMutableArray.count>1 && counter <someMutableArray.count-1){
NSDate *start = [NSDate date];
counter=counter+1;
[self.outputStreamer stop];
self.outputStreamer = nil;
self.outputStream = nil;
}
song=[someMutableArray objectAtIndex:counter];
NSMutableDictionary *info = [NSMutableDictionary dictionary];
info=[[NSMutableDictionary alloc] init];
info[#"title"] = [song valueForProperty:MPMediaItemPropertyTitle] ? [song valueForProperty:MPMediaItemPropertyTitle] : #"";
info[#"artist"] = [song valueForProperty:MPMediaItemPropertyArtist] ? [song valueForProperty:MPMediaItemPropertyArtist] : #"";
//NSNumber *duration=[song valueForProperty:MPMediaItemPropertyPlaybackDuration];
int fullminutes = floor([timeinterval floatValue] / 60); // fullminutes is an int
int fullseconds = trunc([duration floatValue] - fullminutes * 60); // fullseconds is an int
[NSTimer scheduledTimerWithTimeInterval:[duration doubleValue]target:self selector:#selector(getdata) userInfo:nil repeats:YES];
}
-(void)getdata {
NSMutableDictionary *info = [NSMutableDictionary dictionary];
info=[[NSMutableDictionary alloc] init];
info[#"title"] = [song valueForProperty:MPMediaItemPropertyTitle] ? [song valueForProperty:MPMediaItemPropertyTitle] : #"";
info[#"artist"] = [song valueForProperty:MPMediaItemPropertyArtist] ? [song valueForProperty:MPMediaItemPropertyArtist] : #"";
NSNumber *duration=[song valueForProperty:MPMediaItemPropertyPlaybackDuration];
int fullminutes = floor([duration floatValue] / 60); // fullminutes is an int
int fullseconds = trunc([duration floatValue] - fullminutes * 60); // fullseconds is an int
info[#"duration"] = [NSString stringWithFormat:#"%d:%d", fullminutes, fullseconds];
MPMediaItemArtwork *artwork = [song valueForProperty:MPMediaItemPropertyArtwork];
UIImage *image = [artwork imageWithSize:CGSizeMake(150, 150)];
NSData * data = UIImageJPEGRepresentation(image, 0.0);
image = [UIImage imageWithData:data];
array = [[NSMutableArray alloc] initWithObjects:[self.session connectedPeers], nil];
MCPeerID* peerID11 = self.session.myPeerID;
NSMutableArray *arr=[[NSMutableArray alloc] initWithObjects:peerID11, nil];
NSLog(#"%#",arr);
if (image)
self.songArtWorkImageView.image = image;
else
self.songArtWorkImageView.image = nil;
self.songTitleLbl.text = [NSString stringWithFormat:#"%# \n[Artist : %#]", info[#"title"], info[#"artist"]];
NSError *error;
[_session sendData:[NSKeyedArchiver archivedDataWithRootObject:[info mutableCopy]] toPeers:[array objectAtIndex:0] withMode:MCSessionSendDataUnreliable error: &error];
NSLog(#"localizedDescription %#",error);
#try {
if(_session && _session.connectedPeers && [_session.connectedPeers count] > 0) {
NSLog(#"%#",[song valueForProperty:MPMediaItemPropertyAssetURL]);
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[song valueForProperty:MPMediaItemPropertyAssetURL] options:nil];
[self convertAsset: asset complition:^(BOOL Success, NSString *filePath) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
if(Success) {
if(image) {
[self saveImage: image withComplition:^(BOOL status, NSString *imageName, NSURL *imageURL) {
if(status) {
#try {
[_session sendResourceAtURL:imageURL withName:imageName toPeer:[_session.connectedPeers objectAtIndex:0]withCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Failed to send picture to %#", error.localizedDescription);
return;
}
//Clean up the temp file
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtURL:imageURL error:nil];
}];
}
#catch (NSException *exception) {
}
}
}];
}
#try {
[self hideSpinner];
if(!self.outputStream) {
NSArray * connnectedPeers = [_session connectedPeers];
if([connnectedPeers count] != 0) {
[self outputStreamForPeer:[_session.connectedPeers objectAtIndex:0]];
}
}
}
#catch (NSException *exception) {
}
if(self.outputStream) {
self.outputStreamer = [[TDAudioOutputStreamer alloc] initWithOutputStream:self.outputStream];
//
[self.outputStreamer initStream:filePath];
NSLog(#"%#",filePath);
if(self.outputStreamer) {
[self.outputStreamer start];
}
else{
NSLog(#"Error: output streamer not found");
}
}
else{
//self.outputStream=[[NSOutputStream alloc] init];
self.outputStreamer = [[TDAudioOutputStreamer alloc] initWithOutputStream:self.outputStream];
[self.outputStreamer initStream:filePath];
NSLog(#"%#",filePath);
if(self.outputStreamer) {
[self.outputStreamer start];
}
}
}
else {
[UIView showMessageWithTitle:#"Error!" message:#"Error occured!" showInterval:1.5];
}
});
}];
// }
}
}
#catch (NSException *exception) {
NSLog(#"Expection: %#", [exception debugDescription]);
}
//}
}
HostViewcontroller :
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID
{
NSLog(#"%#",peerID);
NSLog(#"sessions%#",session);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
#try {
// NSData *myData = [NSKeyedArchiver archivedDataWithRootObject:data];
info = [NSKeyedUnarchiver unarchiveObjectWithData:data];
self.songTitleLbl.text = [NSString stringWithFormat:#"%# \n[Duration: %#] [Artist : %#] ", info[#"title"], info[#"duration"], info[#"artist"]];
NSLog(#"eeret%#",self.songTitleLbl.text);
self.songArtWorkImageView.image = nil;
[self showSpinner];
}
#catch (NSException *exception) {
self.songTitleLbl.text = #"Some error occured...\nPlease try again";
self.songArtWorkImageView.image = nil;
}
});
}
Please let us know if I have missed anything here or the better way to achieve the above requirement. Any help really appreciated.

Threading loading images from a device to a tableView in swift

I can't find anything online about threading loading an image from a device and scrolling smoothly through a tableview. There is one on ray wen about this, but it doesn't really help me for my situation.
Does anybody have any advice or code which would help to allow a tableview to scroll smoothly and load images from the device's temporary directory?
i did exactly as mentioned at tutorial, but with modification for nsoperation subclass
this is methods for fetch
-(void) updateData
{
[self.pendingOperations.downloadQueue addOperationWithBlock:^{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSArray *filePathes = [self recursiveRecordsForResourcesOfType:#[#"png", #"jpeg", #"jpg",#"pdf"] inDirectory:documentsDirectory];
#synchronized (self) {
self.documents = filePathes;
NSLog(#"documents count %#", #([self.documents count]));
}
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[self.delegate modelDidUpdate:self];
});
}];
}
- (NSArray *)recursiveRecordsForResourcesOfType:(NSArray *)types inDirectory:(NSString *)directoryPath{
NSMutableArray *filePaths = [[NSMutableArray alloc] init];
NSMutableDictionary *typesDic = [NSMutableDictionary dictionary];
for (NSString *type in types)
[typesDic setObject:type forKey:type];
// Enumerators are recursive
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:directoryPath];
NSString *filePath;
while ((filePath = [enumerator nextObject]) != nil){
// If we have the right type of file, add it to the list
// Make sure to prepend the directory path
if([typesDic objectForKey:[filePath pathExtension]]){
//[filePaths addObject:[directoryPath stringByAppendingPathComponent:filePath]];
CURFileRecord *record = [CURFileRecord new];
record.filePath =[directoryPath stringByAppendingPathComponent:filePath];
record.fileName = filePath;
[filePaths addObject:record];
}
}
return filePaths;
}
this is .m for subclass
- (void)main {
// 4
#autoreleasepool {
if (self.isCancelled)
return;
NSData *fileData = [[NSFileManager defaultManager] contentsAtPath:self.fileRecord.filePath];
// self.fileRecord.fileData = fileData;
if (self.isCancelled) {
fileData = nil;
return;
}
if (fileData) {
UIImage *newImage;
if ([[self.fileRecord.filePath pathExtension] isEqualToString:#"pdf"])
{
CGPDFDocumentRef doc = [CURDocumentViewerUtilities MyGetPDFDocumentRef:fileData];
newImage = [CURDocumentViewerUtilities buildThumbnailImage:doc withSize:CGSizeMake(64, 96)];
}
else
{
newImage = [CURDocumentViewerUtilities makePreviewImageFromData:fileData];
}
self.fileRecord.previewImage = newImage;
}
else {
self.fileRecord.failed = YES;
}
fileData = nil;
if (self.isCancelled)
return;
// 5
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(imageDownloaderDidFinish:) withObject:self waitUntilDone:NO];
}
}
With update func i've fetched pathes to proccess, and nsoperation subclass loads images. Works fine with 2000 images in fullhd - smoothly and without any lugs

AWS SDK for IOS from v1 to v2 about the "S3objectsummary"

Here is a function of progressbar. In the sdk v1 I can use "S3Objectsummary" to know the summary of the the file, but in the sdk v2 i can not found the "S3Objectsummary".
Which one is the similar one in the v2? If any one can show an example that will be great.
Also, i have the same question with
S3GetObjectRequest/S3GetObjectResponse/S3PutObjectRequest/AmazonClientException
Code is in the sdk ios v1:
-(AmazonS3Client *)s3{
[self validateCredentials];
return s3;}
-(void)validateCredentials{
NSLog(#"validating credentials.");
if (s3 == nil) {
[self clearCredentials];
s3 = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY];
}
}
-(void)setProgressBar{
[delegate setProgressStatus:progPercent];
}
-(void)downloadPlists{
#try {
NSArray *Plists = [[self s3] listObjectsInBucket:#"~~~~"];
float numfile = 1;
float totalfiles = [Plists count];
for (S3ObjectSummary *file in Plists) {
float percent = numfile/totalfiles;
progPercent = [NSNumber numberWithFloat:percent];
[self performSelectorOnMainThread:#selector(setProgressBar) withObject:progPercent waitUntilDone:YES];
numfile++;
NSString *key = [file key];
NSLog(#"key: %#", key);
if ([key rangeOfString:#".plist"].location != NSNotFound) {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistFilePath = [NSString stringWithFormat:#"%#/Plists/%#",docDir, key];
NSLog(#"plistFilePath: %#", plistFilePath);
S3GetObjectRequest *plist = [[S3GetObjectRequest alloc] initWithKey:key withBucket:#"~~~~~"];
S3GetObjectResponse *getObjectResponse = [[self s3] getObject:plist];
NSData *data2 = [NSData dataWithData: getObjectResponse.body];
NSString *courseFilePath = [plistFilePath substringToIndex:[plistFilePath rangeOfString:#"/" options:NSBackwardsSearch].location];
bool testDirectoryCreated = [[NSFileManager defaultManager]createDirectoryAtPath: courseFilePath
withIntermediateDirectories: YES
attributes: nil
error: NULL];
if (!testDirectoryCreated)
NSLog(#"error creating test directory.");
if (![data2 writeToFile:plistFilePath atomically:YES])
NSLog(#"error writing to path.");
}
}
}
#catch (NSException *exception) {
UIAlertView *failureAlert = [[UIAlertView alloc] initWithTitle:#"Oops!" message:[NSString stringWithFormat: #"There was an error performing this operation. Please try again later. Error: %#", exception] delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles: nil];
[failureAlert show];
}
}
I try to do the same thing in v2 in the code as follow, is the code right?
-(void)downloadPlists
{
AWSS3 *s3 = [AWSS3 defaultS3];
AWSS3ListObjectsRequest *listObjectReq=[AWSS3ListObjectsRequest new];
listObjectReq.bucket=#"PLists";
[[[s3 listObjects:listObjectReq] continueWithBlock:^id(BFTask *task) {
if(task.error){
NSLog(#"the request failed. error %#",task.error);
}
if(task.result){
AWSS3ListObjectsOutput *listObjectsOutput=task.result;
NSArray *Plists = task.result; //Is the result of task in listObjectOutput a NSArray?
float numfile = 1;
float totalfiles = [Plists count];
for(AWSS3Object *file in listObjectsOutput.contents){
float percent = numfile/totalfiles;
progPercent = [NSNumber numberWithFloat:percent];
[self performSelectorOnMainThread:#selector(setProgressBar) withObject:progPercent waitUntilDone:YES];
numfile++;
NSString *key = [file key];
NSLog(#"key: %#", key);
if ([key rangeOfString:#".plist"].location != NSNotFound) {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistFilePath = [NSString stringWithFormat:#"%#/Plists/%#",docDir, key];
NSLog(#"plistFilePath: %#", plistFilePath);
AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];
AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new];
downloadRequest.bucket = #"PLists";
downloadRequest.key = key;
//downloadRequest.downloadingFileURL=[NSURL fileURLWithPath: #"???"]; I'm not sure the path. In sdk V1 there is no URL ?
[[transferManager download: downloadRequest] continueWithBlock:^id(BFTask *task) {
if (task.error) {
UIAlertView *failureAlert = [[UIAlertView alloc] initWithTitle:#"Oops!"
message:[NSString stringWithFormat: #"There was an error performing this operation. Please try again later. Error: %#", task.error]
delegate:nil
cancelButtonTitle:#"Okay"
otherButtonTitles: nil];
[failureAlert show];
}
if (task.result) {
AWSS3TransferManagerDownloadOutput *downloadOutput = task.result;
NSData *data2 = [NSData dataWithData: downloadOutput.body];
NSString *courseFilePath = [plistFilePath substringToIndex:[plistFilePath rangeOfString:#"/" options:NSBackwardsSearch].location];
bool testDirectoryCreated = [[NSFileManager defaultManager]createDirectoryAtPath: courseFilePath
withIntermediateDirectories: YES
attributes: nil
error: NULL];
if (!testDirectoryCreated)
NSLog(#"error creating test directory.");
if (![data2 writeToFile:plistFilePath atomically:YES])
NSLog(#"error writing to path.");
}
return nil;
}];
}
}
return nil;
}
return nil;
}] waitUntilFinished]; //In the integration test still use the "waitUntilFinisher".But in the "Working with BFTask" said the continueWithBolck won't execute until the previous asychronous call has already finished exceuting?
}
AWSS3Object in v2 is equivalent to S3ObjectSummary in v1.
You are not invoking - listObjects:, so your v2 code snippet does not work. You should take a look at the integration test as an example. Note that you should avoid calling - waitUntilFinished in your production app. See Working with BFTask for further details.

iOS: Method doesn't wait for the other methods it calls to finish [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I have this model in one of my iPhone apps:
//
// CatapultAccount.m
// Catapult
//
// Created by Aziz Light on 4/9/13.
// Copyright (c) 2013 Catapult Technology Ltd. All rights reserved.
//
#import "CatapultAccount.h"
#interface CatapultAccount ()
- (BOOL)createAccountsTable;
- (BOOL)accountWithNameIsAlreadyAdded:(NSString *)accountName;
- (NSDictionary *)getAccountLogosForClient:(NSDictionary *)client;
- (NSString *)getAccountLogoFromURL:(NSURL *)url;
- (NSString *)saveImage:(UIImage *)image
withFileName:(NSString *)imageName
ofType:(NSString *)extension
inDirectory:(NSString *)directoryPath;
#end
#implementation CatapultAccount
- (BOOL)createAccountWithAccountID:(NSString *)accountID
{
__block BOOL operationSuccessfull;
NXOAuth2Account *account = [[NXOAuth2AccountStore sharedStore] accountWithIdentifier:accountID];
if (account == nil) {
operationSuccessfull = NO;
} else {
if ([self openDatabaseConnection]) {
if ([self createAccountsTable]) {
[NXOAuth2Request performMethod:#"GET"
onResource:[NSURL URLWithString:[NSString stringWithFormat:#"%#/users/me", kCatapultHost]]
usingParameters:nil
withAccount:account
sendProgressHandler:nil
responseHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
if (error != nil) {
operationSuccessfull = NO;
#if DEBUG
NSLog(#"ERROR: %#", error);
#endif
_lastError = error;
} else {
NSError *jsonError;
NSDictionary *serializedResponse = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions
error:&jsonError];
if (jsonError != nil) {
operationSuccessfull = NO;
#if DEBUG
NSLog(#"ERROR: %#", jsonError);
#endif
_lastError = jsonError;
} else {
NSDictionary *user = [serializedResponse objectForKey:#"user"];
NSDictionary *client = [serializedResponse objectForKey:#"client"];
NSString *forename = [user objectForKey:#"forename"];
NSString *surname = [user objectForKey:#"surname"];
NSString *accountName = [client objectForKey:#"account_name"];
NSString *clientName = [client objectForKey:#"client_name"];
if ([self accountWithNameIsAlreadyAdded:accountName]) {
operationSuccessfull = NO;
_lastError = [NSError errorWithDomain:kCatapultAccountErrorDomain
code:kCatapultDuplicateAccountErrorCode
userInfo:#{#"message": #"You have already added this account"}];
#if DEBUG
NSLog(#"ERROR: %#", _lastError);
#endif
} else {
NSDictionary *logos = [self getAccountLogosForClient:client];
operationSuccessfull = [self.db executeUpdate:#"insert into accounts(account_id, forename, surname, account_name, client_name, smallest_logo, thumb_logo) values(?,?,?,?,?,?,?)",
accountID, forename, surname, accountName, clientName, logos[#"smallest_logo"], logos[#"thumb_logo"]];
}
}
}
}];
} else {
operationSuccessfull = NO;
_lastError = [NSError errorWithDomain:kCatapultDatabaseErrorDomain
code:kCatapultUnableToCreateTableErrorCode
userInfo:#{#"message": #"Unable to create the accounts table"}];
#if DEBUG
NSLog(#"ERROR: %#", _lastError);
#endif
}
[self closeDatabaseConnection];
} else {
operationSuccessfull = NO;
_lastError = [NSError errorWithDomain:kCatapultDatabaseErrorDomain
code:kCatapultUnableToOpenDatabaseConnectionErrorCode
userInfo:#{#"message": #"Unable to open database connection"}];
#if DEBUG
NSLog(#"ERROR: %#", _lastError);
#endif
}
}
return operationSuccessfull;
}
- (BOOL)createAccountsTable
{
// Accounts table schema
// id integer primary key autoincrement
// account_id varchar(36) not null - unique
// forename varchar(255) not null
// surname varchar(255) not null
// account_name varchar(255) not null - unique
// client_name varchar(255) not null
// smallest_account_logo text
// thumb_account_logo text
BOOL tableCreationWasSuccessfull = [self.db executeUpdate:#"create table if not exists accounts(id integer primary key autoincrement, account_id varchar(36) not null, forename varchar(255) not null, surname varchar(255) not null, account_name varchar(255) not null, client_name varchar(255) not null, smallest_logo text, thumb_logo text, unique(account_id, account_name) on conflict abort)"];
if (tableCreationWasSuccessfull) {
_lastError = nil;
} else {
_lastError = [self.db lastError];
#if DEBUG
NSLog(#"Failed to create users table: %#", _lastError);
#endif
}
return tableCreationWasSuccessfull;
}
- (BOOL)accountWithNameIsAlreadyAdded:(NSString *)accountName
{
FMResultSet *account = [self.db executeQuery:#"select count(*) from accounts"];
return [account next];
}
- (NSDictionary *)getAccountLogosForClient:(NSDictionary *)client
{
NSString *smallestLogoURLString = [[[client objectForKey:#"logo"] objectForKey:#"smallest"] objectForKey:#"url"];
NSString *smallestLogoPath = [self getAccountLogoFromURL:[NSURL URLWithString:smallestLogoURLString]];
NSString *thumbLogoURLString = [[[client objectForKey:#"logo"] objectForKey:#"thumb"] objectForKey:#"url"];
NSString *thumbLogoPath = [self getAccountLogoFromURL:[NSURL URLWithString:thumbLogoURLString]];
return #{#"smallest_logo": smallestLogoPath, #"thumb_logo": thumbLogoPath};
}
- (NSString *)getAccountLogoFromURL:(NSURL *)url
{
NSString *urlWithoutGETParams = [[[url absoluteString] componentsSeparatedByString:#"?"] objectAtIndex:0];
NSString *lastSegmentOfURL = [[urlWithoutGETParams componentsSeparatedByString:#"/"] lastObject];
NSString *logoName = [[lastSegmentOfURL componentsSeparatedByString:#"."] objectAtIndex:0];
NSString *logoExtension = [[lastSegmentOfURL componentsSeparatedByString:#"."] lastObject];
NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *logoPath = [documentsDirectoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", logoName, logoExtension]];
BOOL logoExists = [[NSFileManager defaultManager] fileExistsAtPath:logoPath];
if (logoExists) {
return logoPath;
} else {
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *logo = [UIImage imageWithData:data];
logoPath = [self saveImage:logo withFileName:logoName ofType:logoExtension inDirectory:documentsDirectoryPath];
return (logoPath == nil) ? nil : logoPath;
}
}
- (NSString *)saveImage:(UIImage *)image
withFileName:(NSString *)imageName
ofType:(NSString *)extension
inDirectory:(NSString *)directoryPath
{
NSData *imageRepresentation;
if ([[extension lowercaseString] isEqualToString:#"png"] || [[extension lowercaseString] isEqualToString:#"gif"]) {
imageRepresentation = UIImagePNGRepresentation(image);
} else if ([[extension lowercaseString] isEqualToString:#"jpg"] || [[extension lowercaseString] isEqualToString:#"jpeg"]) {
imageRepresentation = UIImageJPEGRepresentation(image, 1.0);
} else {
#if DEBUG
NSLog(#"Image Save Failed\nExtension: (%#) is not recognized, use (PNG/JPG/GIF)", extension);
#endif
return nil;
}
NSString *imagePath = [directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, extension]];
NSError *error;
BOOL imageDidSave = [imageRepresentation writeToFile:imagePath
options:NSAtomicWrite
error:&error];
if (error != nil) {
#if DEBUG
NSLog(#"Error saving the file: %#", error);
#endif
}
return (imageDidSave) ? imagePath : nil;
}
#end
And this method in one of my view controllers:
- (void)createAccount:(NSNotification *)notification
{
NXOAuth2Account *account = [notification.userInfo objectForKey:#"NXOAuth2AccountStoreNewAccountUserInfoKey"];
if ([_accountModel createAccountWithAccountID:account.identifier]) {
// Do something
NSLog(#"Yay");
} else {
// Delete the newly created account
[[NXOAuth2AccountStore sharedStore] removeAccount:account];
UIAlertView *errorMessage = [[UIAlertView alloc] initWithTitle:#"Account Error"
message:#"Unable to add new account"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[errorMessage show];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
The problem is that, following my (naive) observations, most of the methods that use the database in the model execute too slowly and the createAccountWithAccountID: method doesn't wait for the method it calls to finish. The result of that is that the record is not saved to the database but for some reason the createAccountWithAccountID method returns YES… here are the logs that illustrate what I am saying:
2013-04-09 20:07:46.261 Catapult[21004:c07] Yay
2013-04-09 20:07:46.276 Catapult[21004:c07] The FMDatabase <FMDatabase: 0x7288300> is not open.
2013-04-09 20:07:46.606 Catapult[21004:c07] The FMDatabase <FMDatabase: 0x7288300> is not open.
The record is not saved to the database because the database connection gets closed too fast…
Does anybody know how I can solve my problem please?
Most methods which use a completion block execute asynchronously. The method will return immediately and the block will be executed when the request actually completes. You need to handle it accordingly in your code. Don't release anything after you send the request, but do anything that depends on the result in the response handler block and then release it.

Resources