AFNetworking + queue + cancel operations + junk files #657

Closed
zaabalonso opened this Issue Nov 23, 2012 · 8 comments

Comments

Projects
None yet
5 participants

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.

MY THOUGHTS :

THERE SHOULD BE SOME KIND OF TEMP_FILES FOLDER AND ON_COMPLETE ACTION MOVE THEM IN THE ACTUAL FILE PATH ELSE DELETE THEM(WE DON"T NEED SEMI DOWNLOADED FILES).

SO i GUESS the problem is here : op.outputStream = [NSOutputStream outputStreamToFileAtPath:targetPath append:NO];

and the way AF manages its downloaded files.

Contributor

mattt commented Nov 24, 2012

completionBlock is run whether the operation finishes or was cancelled. If you need specific logic to clean up temp files when cancelled, you can configure that yourself. Otherwise, I'm very happy with the current behavior of AFNetworking.

@mattt mattt closed this Nov 24, 2012

no matt i don't get in completionBlock on cancel just when full downloaded. On cancel it doesn't get in completionBlock or failedBlock

Contributor

mattt commented Nov 25, 2012

I would encourage you to read the AFNetworking documentation more closely, as well as the Apple documentation for NSOperation. This behavior is clearly explained between those two sources.

Contributor

danielctull commented Dec 19, 2012

While the completionBlock on the operation is called, the blocks given to setCompletionBlockWithSuccess:failure: do not, which is what @zaabalonso is enquiring about.

From the source of AFHTTPRequestOperation, AFImageRequestOperation, AFPropertyListRequestOperation, AFJSONRequestOperation and AFXMLRequestOperation you can see the block is just returning if the operation is cancelled.

It seems to me there is no obvious (blocky) way to know when an AFNetworking operation is cancelled.

PeteC commented Dec 19, 2012

Just to add to @danielctull's comment, for operations created via AFHTTPClient's getPath:parameters:success:failure: the operation isn't returned or exposed in anyway. I don't believe, therefore, that there's a way for the caller to set a completion block to handle the request being cancelled.

I had also (wrongly it seems) assumed that the failure block would be called if the request was cancelled.

danielctull added a commit to danielctull/AFNetworking that referenced this issue Dec 19, 2012

Contributor

mattt commented Dec 19, 2012

@zaabalonso @PeteC To be clear: completionBlock is a property of NSOperation. getPath: and other convenience methods use setCompletionBlockWithSuccess:failure:, which returns early if the operation was cancelled. Cancelled operations are not failing operations by AFNetworking, and if you need to define behavior to execute when the operation is cancelled, do setCompletionBlock: directly yourself.

PeteC commented Dec 20, 2012

Thanks for getting back on this @mattt . I fully understand completionBlock is a property of NSOperation.

I often have code that depends on an operation to complete, whether successfully or not. For example I may have dispatch groups waiting on operations. This is why I feel there should be some feedback when an operation is cancelled. While I could write my own completion blocks to handle this, they'd look very similar to AFNetworking's current implementation.

In a quick straw poll of developers at a London developer group meeting, everyone assumed the failure block would be called if an operation was cancelled. And I was very careful how the question was asked :)

@implementation UploadOperation

-(void)backgroundStartProcess
{
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];

NSString *libDir = [ZipUploader getLibraryDirectoryPath];

request = [httpClient multipartFormRequestWithMethod:@"POST" path:methodName parameters:nil
constructingBodyWithBlock:^(id formData) {

                               [formData appendPartWithFileData:videoData name:@"motordata" fileName:[NSString stringWithFormat:@"%@.tar.bz2",self.serialNumber] mimeType:@"application/zip"];
                               [formData appendPartWithFormData:[comments dataUsingEncoding:NSUTF8StringEncoding] name:@"comments"];

}];

[request setTimeoutInterval:20.0];

self.operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

//handle progress
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {

    if (operation.cancelled) {
        [operation cancel];

        [operation pause];
    }
    NSLog(@"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];

[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[shared hideLoadingHUD];

if (self.operationCompletion)
{
self.operationCompletion();
}

} failure:^(AFHTTPRequestOperation *operation, NSError *error)
 {
     NSLog(@"upload failed");
     dispatch_async( dispatch_get_main_queue(), ^{

         iToast *toast = [iToast makeText:[NSString stringWithFormat:@"%@",[error localizedDescription]]];
         toast.theSettings.gravity = iToastGravityBottom;
         [toast show];

         });

         if (self.operationCompletion)
         {
             self.operationCompletion();
         }


 }];
if (self.operationstart)
{
    self.operationstart(operation);
}



[operation start];

}

  • (BOOL)isConcurrent {

    return YES;

}

  • (BOOL)isExecuting {

    return executing;

}

  • (BOOL)isFinished {

    return finished;

}

  • (void)main {

    if ([self isCancelled]) { return; }
    [self backgroundStartProcess];

}

-(id)initWithData:(MotorMeasurement_)measurementD fromBatchUpload:(BOOL)batch batchCount:(NSUInteger)batchCoun serialNumber:(NSString_)serial
{
if (![super init]) return nil;
self.measurementDict = measurementD;
self.fromBatchUpload = batch;
self.batchCount = batchCoun;
self.serialNumber = serial;
return self;
}

-(void)cancel
{
[super cancel];

[self.operation cancel];

}

@EnD

@implementation ZipUploader
UploadOperation *upload = [[UploadOperation alloc] initWithData:measureDict fromBatchUpload:self.fromBatchUpload batchCount:self.batchCount serialNumber:motor.SerialNumber];
[self.operationQueue addOperation:upload];

            self.operationCount++;

@EnD

Hello is there any update to cancel an operation in an operation initiated from main of an NSOperation

Thanks
Dhara

greghe pushed a commit to skillz/AFNetworking that referenced this issue Sep 3, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment