Thursday 14 August 2014

MultiPart Image Upload. Sequencial UIImage mutlipart upload.

Code from Github

In this the UIImage is converted into in NSData
Data is divided into chunks.
Chucks are send to server sequentially , 
if failed to upload same chuck is resend,
else if successful next chuck is sent server.

//HDMultiPartImageUpload.h file----------------

Add these file to your project.

//================////////////////////.h file /////////////////////////=============

//
//  HDMultiPartImageUpload.h
//  HDMultiPartImageUpload
//
//  Created by HDDEV 2014.
//  Copyright (c) 2014 HDDev. All rights reserved.
//
//
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.
//



#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>



//static const int oneChunkSize = (1024 * 4);

#define BOUNDARY @"--ARCFormBoundaryb6kap934u6jemi"

#define uploadingImageMappingKeyOnServer @"file" //Use Same key string which your read as input for image filename at your server implementation

typedef enum : NSUInteger {
    eImageTypePNG,
    eImageTypeJPG,
} eImageType;



@interface HDMultiPartImageUpload : NSObject

@property (nonatomic,assign) int                  oneChunkSize;
@property (nonatomic,assign) eImageType           selectedImageType;
@property (nonatomic,strong) NSString            *imageFilePath;
@property (nonatomic,strong) NSString            *uploadURLString;
@property (nonatomic,strong) NSMutableDictionary *postParametersDict;

-(void)startUploadImagesToServer;

@end

//================////////////////////.h file /////////////////////////=============








//   HDMultiPartImageUpload.m file-------------------

Also the below the file below

//================////////////////////.m file /////////////////////////=============

//
//  HDMultiPartImageUpload.m
//  HDMultiPartImageUpload
//
//  Created by HarshDuggal on 13/08/14.
//  Copyright (c) 2014 HDDev. All rights reserved.
//

#import "HDMultiPartImageUpload.h"




@interface HDMultiPartImageUpload ()
{
    int totalChunksTobeUploaded;
    int chunksUploadedSuccessfully;
}
-(void)uploadImageChunkToServerFullImageData:(NSData*)imageData withParam:(NSMutableDictionary*)param withOffset:(NSUInteger)offset;

@end

@implementation HDMultiPartImageUpload


- (NSData*)getPostDataFromDictionary:(NSDictionary*)dict
{
    id boundary = BOUNDARY;
    NSArray* keys = [dict allKeys];
    NSMutableData* result = [NSMutableData data];
    
    // add params (all params are strings)
    [result appendData:[[NSString stringWithFormat:@"\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    for (int i = 0; i < [keys count]; i++)
    {
        id tmpKey = [keys objectAtIndex:i];
        id tmpValue = [dict valueForKey: tmpKey];
        
        [result appendData: [[NSString stringWithFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n \n%@", tmpKey,tmpValue] dataUsingEncoding:NSUTF8StringEncoding]];
        // Append boundary after every key-value
        [result appendData:[[NSString stringWithFormat:@"\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    }

    return result;
}



-(void)startUploadImagesToServer
{
    
#warning these variable shud be set properly before starting upload
//    // Set the following parameter and start upload---
//    self.oneChunkSize;
//    self.selectedImageType;
//    self.imageFilePath;
//    self.uploadURLString;
//    self.postParametersDict;
#warning these variable shud be set properly before starting upload
    
    UIImage *imageTobeUploaded = [UIImage imageWithContentsOfFile:self.imageFilePath];
    
    NSData *imageData;
    NSString *fileType;
    
    if (self.selectedImageType == eImageTypeJPG){
        imageData = UIImageJPEGRepresentation(imageTobeUploaded, 1.0);
        fileType = @"image/jpg";
    }
    else if (self.selectedImageType == eImageTypePNG) {
        imageData = UIImagePNGRepresentation(imageTobeUploaded);
        fileType = @"image/png";
    }
    
    
//    unsigned long long totalFileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil] fileSize];
    
    
    NSUInteger totalFileSize = [imageData length];
    //    int totalChunks = ceil(totalFileSize/oneChunkSize);
    int totalChunks = round((totalFileSize/self.oneChunkSize)+0.5);//round-off to nearest  largest valua 1.01 is considered as 2
    

    // Start multipart upload chunk sequentially-
    NSUInteger offset = 0;
    totalChunksTobeUploaded = totalChunks;
    chunksUploadedSuccessfully = 0;
    [self uploadImageChunkToServerFullImageData:imageData withParam:self.postParametersDict withOffset:offset];
    
}







-(void)uploadImageChunkToServerFullImageData:(NSData*)imageData withParam:(NSMutableDictionary*)param withOffset:(NSUInteger)offset
{
    
    //    NSData* myBlob = imageData;
    NSUInteger totalBlobLength = [imageData length];
    NSUInteger chunkSize = self.oneChunkSize;
    NSUInteger thisChunkSize = totalBlobLength - offset > chunkSize ? chunkSize : totalBlobLength - offset;
    NSData* chunk = [NSData dataWithBytesNoCopy:(char *)[imageData bytes] + offset
                                         length:thisChunkSize
                                   freeWhenDone:NO];
    // upload the chunk
    
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.uploadURLString]];
    
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
    [request setHTTPShouldHandleCookies:NO];
    [request setTimeoutInterval:60];
    [request setHTTPMethod:@"POST"];
    
    NSString *boundary = BOUNDARY;
    //
    //    // set Content-Type in HTTP header
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request setValue:contentType forHTTPHeaderField: @"Content-Type"];
    //
    // post body
    NSMutableData *body = [[NSMutableData alloc]init];
    
    // add params (all params are strings)
    [body appendData:[self getPostDataFromDictionary:param]];
    
    // add image data
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=%@; filename=blob\r\n", @"file"] dataUsingEncoding:NSUTF8StringEncoding]];
    
    [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    [body appendData:chunk];
    [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    

    // setting the body of the post to the reqeust
    NSString * bodyString =[[NSString alloc]initWithData:body encoding:NSASCIIStringEncoding];
    NSLog(@"body sent to server: \n %@ \n",bodyString);
    
    [request setHTTPBody:body];
    
    // set the content-length
    //    NSString *postLength = [NSString stringWithFormat:@"%d", [body length]];
    //    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    
    
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        
        NSLog(@"response:\n %@, \n error = %@",response,error.localizedDescription);
        if (error) { // Failed
            NSLog(@"error = %@",error.localizedDescription);
            
            // Retry resending same chuck
            [self uploadImageChunkToServerFullImageData:imageData withParam:param withOffset:offset];
            return;
        }
        else if(data.length > 0) {// Data recieved from server
            
#warning parse the revieved data from server accordingly
//                NSString* newStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//                NSLog(@"MsgFromServer:%@ \n Response: %@ \n ",newStr,response);
//                // Convert String to dict
//                NSError* parsingError;
//                NSDictionary *responseRxDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&parsingError];
//                
//                if (parsingError) {
//                    NSLog(@"%@",parsingError.localizedDescription);
//                    // Retry resending same chuck
//                    [self uploadImageChunkToServerFullImageData:imageData withParam:param withOffset:offset];
//                    return;
//                }
#warning parse the revieved data from server accordingly
            
            //check success
            NSLog(@"Offset:%lu totalLenght:%lu",(unsigned long)offset,(unsigned long)totalBlobLength);
            NSLog(@"Chunk:%d Total Chunks:%d",chunksUploadedSuccessfully,totalChunksTobeUploaded);
            
            BOOL successfulUpload = YES; // Check success msg from server in "responseRxDict" .
            if (successfulUpload) {
                chunksUploadedSuccessfully += 1;

#warning update your post param dict if needed, accoording to server implementation
                [param setObject:[NSString stringWithFormat:@"%d",chunksUploadedSuccessfully] forKey:@"chunk"];
// above line is example should altered according to the server key in needed
#warning update your post param dict if needed, accoording to server implementation
                
                
                    NSUInteger thisChunkSize = totalBlobLength - offset > chunkSize ? chunkSize : totalBlobLength - offset;
                    NSUInteger newOffset= offset + thisChunkSize;
                    
                    // stop no more data to upload
                    if(offset >= totalBlobLength){
                        NSLog(@"offset >= totalBlobLength");
                        return;
                    }
                    if (chunksUploadedSuccessfully > totalChunksTobeUploaded-1) {
                        NSLog(@"chunk > chunks-1");
                        return;
                    }
                    
                    // send next Chunck To server
                    [self uploadImageChunkToServerFullImageData:imageData withParam:param withOffset:newOffset];
                }
                else { // Retry resending same chuck
                    [self uploadImageChunkToServerFullImageData:imageData withParam:param withOffset:offset];
                    return;
                }
            }
            else { // Retry resending same chuck
                [self uploadImageChunkToServerFullImageData:imageData withParam:param withOffset:offset];
                return;
            }
    }];
}

//================////////////////////.m file /////////////////////////=============







Usage Example:-

Import the above two files "HDMultiPartImageUpload.h" and "HDMultiPartImageUpload.m" to your project and write following code to start multipart upload.


#Import "HDMultiPartImageUpload.h"


-(void)demoupload
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    documentsDirectory = [NSString stringWithFormat:@"%@/ProfilePic/",documentsDirectory];
    NSFileManager*fmanager = [NSFileManager defaultManager];
    if(![fmanager fileExistsAtPath:documentsDirectory])
    {
        [fmanager createDirectoryAtPath:documentsDirectory withIntermediateDirectories:YES attributes:nil error:nil];
    }
    NSString * filePath =  [NSString stringWithFormat:@"%@",documentsDirectory];

    NSMutableDictionary *postParam = [[NSMutableDictionary alloc]init];
    [postParam addEntriesFromDictionary:[self demoPostDict]];
    
    HDMultiPartImageUpload *obj = [[HDMultiPartImageUpload alloc]init];

    obj.oneChunkSize = 1024 *10;
    obj.selectedImageType = eImageTypePNG;
    obj.imageFilePath =filePath;
    obj.uploadURLString = @"http://example.com/upload";
    obj.postParametersDict = postParam;
    
    [obj startUploadImagesToServer];
    
}

-(NSMutableDictionary*)demoPostDict
{
    NSMutableDictionary *param = [[NSMutableDictionary alloc]init];
    
#warning - These key values in post dictionary varies according to the server implementation----
    UIImage *imageTobeUploaded = [UIImage imageWithContentsOfFile:self.imageFilePath];
    
    NSData *imageData;
    NSString *fileType;
    
    if (self.selectedImageType == eImageTypeJPG){
        imageData = UIImageJPEGRepresentation(imageTobeUploaded, 1.0);
        fileType = @"image/jpg";
    }
    else if (self.selectedImageType == eImageTypePNG) {
        imageData = UIImagePNGRepresentation(imageTobeUploaded);
        fileType = @"image/png";
    }
    
    NSUInteger totalFileSize = [imageData length];
    //    int totalChunks = ceil(totalFileSize/oneChunkSize);
    int totalChunks = round((totalFileSize/self.oneChunkSize)+0.5);//round-off to nearest  largest valua 1.01 is considered as 2
    
    // Create your Post parameter dict according to server
    NSString* originalFilename = @"tmpImageToUpload.png";//uniqueFileName;
    
    //Creating a unique file to upload to server
    NSString *prefixString = @"Album";
    //    This method generates a new string each time it is invoked, so it also uses a counter to guarantee that strings created from the same process are unique.
    NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] ;//
    NSString *uniqueFileName = [NSString stringWithFormat:@"%@_%@", prefixString, guid];
    
    //Add key values your post param Dict
    [param setObject:uniqueFileName
              forKey:@"uniqueFilename"];
    [param setObject:[NSString stringWithFormat:@"%lu",(unsigned long)totalFileSize]
              forKey:@"totalFileSize"];
    [param setObject:@"0" forKey:@"chunk"];
    [param setObject:[NSString stringWithFormat:@"%d",totalChunks]
              forKey:@"chunks"];
    [param setObject:fileType
              forKey:@"fileType"];
    [param setObject:originalFilename
              forKey:@"originalFilename"];
    
#warning - These key values in post dictionary varies according to the server implementation----
    return param;
    
}


//  Copyright (c) HDDev. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.



Read More: 

Tuesday 12 August 2014

Convert NSData to NSDictionary or Convert NSData to NSArray.

-(void)parseDataFromServer:(NSData*)responseData 
{
NSError* error;
// If response JSON starts with {}, it represents dictionary
NSDictionary *dicData = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
// If response JSON starts with [], it represents array
NSArray* arrData = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error]; 

if (error) NSLog(@"%@",error.localizedDescription);
}
Class: NSJSONSerialization
Method: + (NSData *)dataWithJSONObject:(id)obj
options:(NSJSONWritingOptions)opt  
error:(NSError **)error;
Reference Links: Apple Doc