AFNetworking + queue + cancel operations + junk files - objective-c

i 'm downloading some files using AFNetworking using a queue. Here is my code:
apiClient =[[AFHTTPClient alloc]initWithBaseURL: [NSURL URLWithString:ZFREMOTEHOST]];
for (NSString *element in self.productsArray) {
NSURL *url = [NSURL URLWithString:element];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *documentsDirectory = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
NSString *targetFilename = [url lastPathComponent];
NSString *targetPath = [documentsDirectory stringByAppendingPathComponent:targetFilename];
op.outputStream = [NSOutputStream outputStreamToFileAtPath:targetPath append:NO];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure case
NSLog(#"BaaZ File NOT Saved %#", targetPath);
//remove the file if saved a part of it!
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:targetPath error:&error];
if (error) {
NSLog(#"error dude");
}
if ([operation isCancelled]) {
//that doesn't work.
NSLog(#"Canceled");
}
}];
[op setDownloadProgressBlock:^(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
if (totalBytesExpectedToRead > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.alpha = 1;
self.progressView.progress = (float)totalBytesRead / (float)totalBytesExpectedToRead;
NSString *label = [NSString stringWithFormat:#"Downloaded %lld of %lld bytes", totalBytesRead,totalBytesExpectedToRead];
self.progressLabel.text = label;
});
}
}];
[self.resourcesArray addObject:op];
}
for (AFHTTPRequestOperation *zabols in self.resourcesArray) {
[apiClient.operationQueue addOperation:zabols];
}
the code is working fine on file downloading but i want some cancel functionality so i have a button with an action that has the code below:
[apiClient.operationQueue cancelAllOperations];
the operations cancel file but then there are some junk files on the Documents folder. By saying junk i mean file that started downloading i canceled them and the i get a file with the same name but useless can't be opened cause it's damaged.
How can i prevent AF from doing that and keep only the completed files when i cancel it?
any help would be grateful.
Also tried canceling job by job like that:
for (AFHTTPRequestOperation *ap in apiClient.operationQueue.operations) {
[ap cancel];
NSLog(#"%#",ap.responseFilePath);
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:ap.responseFilePath error:nil];
}
and deleting the junk files but that doesn't work either.

Try using AFDownloadRequestOperation extension, it additionally supports resume a partial download, uses a temporary directory and has a special block that helps with calculating the correct download progress.

You can also try that : github

Related

Objective-C Trying to download PDF that is from a short url

I have been trying to get this to work without having it load on the web view first and getting the absoluteString from that so I can download the URL. I have tried many shortURL solutions and they never fully load the URL. They always give me the URL that is not the final url and does not that the PDF url. Any help would be amazing. I am trying to download the PDF when the app first opens or when it checks for updates, but at the time it just gets the short url and I have to wait till the web view is called to get the full url to be able to download the PDF a head of time.
You download the PDF, just like you would download any other file.
Take a look at NSURLDownload
- (void)startDownloadingURL:sender
{
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com/index.html"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the download with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest delegate:self];
if (!theDownload) {
// Inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename
{
NSString *destinationFilename;
NSString *homeDirectory = NSHomeDirectory();
destinationFilename = [[homeDirectory stringByAppendingPathComponent:#"Desktop"]
stringByAppendingPathComponent:filename];
[download setDestination:destinationFilename allowOverwrite:NO];
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Dispose of any references to the download object
// that your app might keep.
...
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
// Dispose of any references to the download object
// that your app might keep.
...
// Do something with the data.
NSLog(#"%#",#"downloadDidFinish");
}
Please check AppleDocs about handling redirect request.
try using afnetworking to download pdf file in to the server
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"https://letuscsolutions.files.wordpress.com/2015/07/five-point-someone-chetan-bhagat_ebook.pdf"]];
[request setTimeoutInterval:120];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *pdfName = #"2.zip";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:pdfName];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
};
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Download = %f", (float)totalBytesRead / totalBytesExpectedToRead);
NSLog(#"total bytesread%f",(float)totalBytesRead );
NSLog(#"total bytesexpected%lld",totalBytesExpectedToRead );
});
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation start];
This is a method to open a pdf file from an URL with the UIDocumentInteractionController:
- (void)openURL:(NSURL*)fileURL{
//Request the data from the URL
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:fileURL completionHandler:^(NSData *data, NSURLResponse *response,NSError *error){
if(!error){
//Save the document in a temporary file
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[response suggestedFilename]];
[data writeToFile:filePath atomically:YES];
//Open it with the Document Interaction Controller
_docController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
_docController.delegate = self;
_docController.UTI = #"com.adobe.pdf";
[_docController presentOpenInMenuFromRect:CGRectZero inView:self.view animated:YES];
}
}] resume];
}
And myViewController.h:
#interface myViewController : UIViewController <UIDocumentInteractionControllerDelegate>
#property UIDocumentInteractionController *docController;

Saving image from url to disk not working

I'm trying to download an image from an url in my app, so that it can be used by the Today Extension.
So I call [self downloadImageFromUrl:imageOne];, which uses this method:
- (void)downloadImageFromUrl:(NSString *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *storeUrl = [fileManager containerURLForSecurityApplicationGroupIdentifier:#"group.myapp.TodayExtensionDefaults"];
NSString *path = [storeUrl absoluteString];
path = [path stringByAppendingPathComponent:#"Test/imageOne.png"];
NSLog(#"Path is %#", path);
NSLog(#"Response: %#", responseObject);
UIImage *image = responseObject;
[UIImagePNGRepresentation(image) writeToFile:path atomically:YES];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image error: %#", error);
}];
[requestOperation start];
}
So when I run this, everything executes fine. I get no errors. Yet, when I go to the folder logged in the simulator, no file is present. The error I get from the write action is Write returned error: The operation couldn’t be completed. (Cocoa error 4.). And so I changed the write action to:
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(#"Error: %#. %s, %i", error.localizedDescription, __PRETTY_FUNCTION__, __LINE__);
}
}
[UIImagePNGRepresentation(image) writeToFile:path options:NSDataWritingAtomic error:&error];
But I still get Write returned error: The operation couldn’t be completed. (Cocoa error 4.)
You are grabbing the absoluteString (which includes the scheme, amongst other things):
NSString *path = [storeUrl absoluteString];
I believe you intended the path:
NSString *path = [storeUrl path];

Using apples suggested code isnt excluding files from icloud backup

I have an app that has been rejected 3 times due to icloud backup issues. Apple have written back to say that I need to use there bit of code to exclude the files from being backed up. However this isnt working and i am at wits end.
Here is the code i've used
- (BOOL)downloadFile:(NSString *)fileURI targetFolder:(NSString *)targetFolder targetFilename:(NSString *) targetFilename{
#try{
NSError *error = nil;
NSURL *url = [NSURL URLWithString:fileURI];
if(![url setResourceValue:#"YES" forKey:NSURLIsExcludedFromBackupKey error:&error]){
NSLog(#"KCDM: Error excluding %# from backup %#", fileURI, error);
}else{
NSData *urlData = [NSData dataWithContentsOfURL:url];
if ( urlData )
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dataPath = [documentsDirectory stringByAppendingString:targetFolder];
NSError *error = nil;
if(![[NSFileManager defaultManager] fileExistsAtPath:dataPath]){
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:YES attributes:nil error:&error];
}
NSString *filePath = [NSString stringWithFormat:#"%#/%#%#", documentsDirectory,targetFolder,targetFilename];
return [urlData writeToFile:filePath atomically:YES];
}
}
}
#catch(NSException * e){
NSLog(#"Error download: %#",e);
}
return false;
}
what am i doing wrong?
You try to set NSURLIsExcludedFromBackupKey for the http://-Url you download from the web. That won't work.
You have to set this key-value pair for the actual file that is saved on the device.
Additionally you are not supposed to set this value to the string #"YES", you must use a NSNumber object representing the boolean value YES.
For example:
NSString *filePath = [NSString stringWithFormat:#"%#/%#%#", documentsDirectory,targetFolder,targetFilename];
if ([urlData writeToFile:filePath atomically:YES]) {
// did write correctly
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
if(![fileURL setResourceValue:#YES forKey:NSURLIsExcludedFromBackupKey error:&error]){
NSLog(#"KCDM: Error excluding %# from backup %#", fileURI, error);
return NO;
}
// could set NSURLIsExcludedFromBackupKey
return YES;
}
// could not write to file
return NO;

Corrupted files downloaded from Amazon S3 using AFAmazonS3Client

I created an app that download a plist file from Amazon S3. I was using AFAmazonS3Client a client based in AFNetworking framework.
-(void) getFile:(NSString *)fileName{
self.s3Manager = [[AFAmazonS3Manager alloc] initWithAccessKeyID:#"..." secret:#"..."];
self.s3Manager.requestSerializer.region = AFAmazonS3SAEast1Region;
self.s3Manager.requestSerializer.bucket = #"verba";
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
documentsPath = [documentsPath stringByAppendingPathComponent:fileName];
NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:documentsPath append:NO];
[self.s3Manager getObjectWithPath:#""
outputStream:stream
progress:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
NSLog(#"%f%% Downloaded", (totalBytesRead / (totalBytesExpectedToRead * 1.0f) * 100));
} success:^(id responseObject) {
NSLog(#"Download Complete");
} failure:^(NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Then I checked if the plist file was in document folder. And it was. So I tried to open plist file and the result was nil:
-(NSString*) loadListName:(NSString*)fileName{
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* filePath = [documentsPath stringByAppendingPathComponent:fileName];
NSDictionary *temp;
if ([[NSFileManager defaultManager] fileExistsAtPath: filePath]){
temp = [NSDictionary dictionaryWithContentsOfFile:filePath];
} else {
NSLog(#"File not found.");
}
NSString *listName = [temp objectForKey:#"name"];
return listName;
}
So I tried to add plist file manually. I downloaded and copied it to the documents folder and then dictionaryWithContentsOfFile could open the file. So I suppose that plist file was corrupted when I download the file using AFAmazonS3Client.
What I am doing wrong ?
Update 1
I realize that every single file that I downloaded from S3 are corrupted. I don't know if I handle the NSOutputStream in the right way or maybe another stuff.
For some reason getObjectWithPath method from AFAmazonS3Manager is not working properly.
So I rewrite my method using AFHTTPRequestOperation directly from AFNetworking
- (void)downloadFile:(NSString *)fileName block:(void (^)(NSError *error))block {
NSString *urlString = #"https://[bucket].[server area].amazonaws.com/";
urlString = [urlString stringByAppendingPathComponent:fileName];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSSet *set = operation.responseSerializer.acceptableContentTypes;
if ([[fileName pathExtension] isEqualToString:#"m4a"]) {
NSLog(#"%# set as audio/mp4", fileName);
operation.responseSerializer.acceptableContentTypes = [set setByAddingObject:#"audio/mp4"];
} else if ([[fileName pathExtension] isEqualToString:#"png"]) {
NSLog(#"%# set as image/png", fileName);
operation.responseSerializer.acceptableContentTypes = [set setByAddingObject:#"image/png"];
} else if ([[fileName pathExtension] isEqualToString:#"plist"]) {
NSLog(#"%# set as application/x-plist", fileName);
operation.responseSerializer.acceptableContentTypes = [set setByAddingObject:#"application/x-plist"];
}
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *fullPath = [documentsPath stringByAppendingPathComponent:[url lastPathComponent]];
[operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:fullPath append:NO]];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
NSLog(#"bytesRead: %lu, totalBytesRead: %lld, totalBytesExpectedToRead: %lld", (unsigned long)bytesRead, totalBytesRead, totalBytesExpectedToRead);
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (block) {
block(nil);
}
NSLog(#"RES: %#", [[[operation response] allHeaderFields] description]);
NSError *error;
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:&error];
if (error) {
NSLog(#"ERR: %#", [error description]);
} else {
NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
long long fileSize = [fileSizeNumber longLongValue];
NSLog(#"%lld", fileSize);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (block) {
block(error);
}
NSLog(#"ERR: %#", [error description]);
}];
[operation start];
}
Be carefull cause in last versión of Xcode, every time you restart your app in simulator, the documents folder is deleted

AFnetworking downloading multiple files

I'm using this code to loop through an array to download multiple files and write to disk.
-(void)download
{
//set url paths
for (NSString *filename in syncArray)
{
NSString *urlpath = [NSString stringWithFormat:#"http://foo.bar/photos/%#", filename];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlpath]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:filename];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation start];
but the problem is it calls the success block after each file is done, (which it should) but I just need one final call back to reload some data and end a progress HUD.
Any pointers in the right direction would be great.
Maybe someday this will help someone, but I was able to use a workaround that probably has major issues but its okay for my simple usage.
I just deleted each line from the sync array after it was processed then ran my code i needed.
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
[SVProgressHUD showWithStatus:#"Updating Photos"];
[syncArray removeObject:filename];
if (!syncArray || !syncArray.count)
{
NSLog(#"array empty");
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification" object:self];
[SVProgressHUD dismissWithSuccess:#"Photos Updated"];
}
You can use AFHTTPClient to enqueueBatchOperations and this has a completionBlock which is called when all operations are finished. Should be exactly what you're looking for.