Skip to content

Instantly share code, notes, and snippets.

@HarshDuggal
Last active June 2, 2019 06:46
Show Gist options
  • Save HarshDuggal/e003e580a98005018808 to your computer and use it in GitHub Desktop.
Save HarshDuggal/e003e580a98005018808 to your computer and use it in GitHub Desktop.
Sequencial Multipart Image Upload iOS
//
// HDMultiPartImageUpload.h
// HDMultiPartImageUpload
//
// Created by HarshDuggal on 13/08/14.
// Copyright (c) 2014 Infoedge. 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
//
// HDMultiPartImageUpload.m
// HDMultiPartImageUpload
//
// Created by HarshDuggal on 13/08/14.
// Copyright (c) 2014 Infoedge. 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;
}
}];
}
// Copyright (c) 2014 Infoedge. 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.
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
-(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;
}
@varun04
Copy link

varun04 commented Aug 18, 2014

It's very well explained and it works perfect...cheers Man

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