//
//  ZYJOSSClient.m
//  ZYJOSS_ios_sdk
//
//  Created by zhouzhuo on 8/16/15.
//  Copyright (c) 2015 aliyun.com. All rights reserved.
//

#import "ZYJOSSClient.h"
#import "ZYJOSSDefine.h"
#import "ZYJOSSModel.h"
#import "ZYJOSSUtil.h"
#import "ZYJOSSLog.h"
#import "ZYJOSSBolts.h"
#import "ZYJOSSNetworking.h"
#import "ZYJOSSXMLDictionary.h"
#import "ZYJOSSReachabilityManager.h"
#import "ZYJOSSIPv6Adapter.h"

#import "ZYJOSSNetworkingRequestDelegate.h"
#import "ZYJOSSAllRequestNeededMessage.h"
#import "ZYJOSSURLRequestRetryHandler.h"
#import "ZYJOSSHttpResponseParser.h"
#import "ZYJOSSGetObjectACLRequest.h"
#import "ZYJOSSDeleteMultipleObjectsRequest.h"
#import "ZYJOSSGetBucketInfoRequest.h"
#import "ZYJOSSPutSymlinkRequest.h"
#import "ZYJOSSGetSymlinkRequest.h"
#import "ZYJOSSRestoreObjectRequest.h"

static NSString * const kClientRecordNameWithCommonPrefix = @"oss_partInfos_storage_name";
static NSString * const kClientRecordNameWithCRC64Suffix = @"-crc64";
static NSString * const kClientRecordNameWithSequentialSuffix = @"-sequential";
static NSUInteger const kClientMaximumOfChunks = 5000;   //max part number

static NSString * const kClientErrorMessageForEmptyFile = @"the length of file should not be 0!";
static NSString * const kClientErrorMessageForCancelledTask = @"This task has been cancelled!";

/**
 * extend ZYJOSSRequest to include the ref to networking request object
 */
@interface ZYJOSSRequest ()

@property (nonatomic, strong) ZYJOSSNetworkingRequestDelegate * requestDelegate;

@end

@interface ZYJOSSClient()

- (void)enableCRC64WithFlag:(ZYJOSSRequestCRCFlag)flag requestDelegate:(ZYJOSSNetworkingRequestDelegate *)delegate;
- (ZYJOSSTask *)preChecksForRequest:(ZYJOSSMultipartUploadRequest *)request;
- (void)checkRequestCrc64Setting:(ZYJOSSRequest *)request;
- (ZYJOSSTask *)checkNecessaryParamsOfRequest:(ZYJOSSMultipartUploadRequest *)request;
- (ZYJOSSTask *)checkPartSizeForRequest:(ZYJOSSMultipartUploadRequest *)request;
- (NSUInteger)judgePartSizeForMultipartRequest:(ZYJOSSMultipartUploadRequest *)request fileSize:(unsigned long long)fileSize;
- (unsigned long long)getSizeWithFilePath:(nonnull NSString *)filePath error:(NSError **)error;
- (NSString *)readUploadIdForRequest:(ZYJOSSResumableUploadRequest *)request recordFilePath:(NSString **)recordFilePath sequential:(BOOL)sequential;
- (NSMutableDictionary *)localPartInfosDictoryWithUploadId:(NSString *)uploadId;
- (ZYJOSSTask *)persistencePartInfos:(NSDictionary *)partInfos withUploadId:(NSString *)uploadId;
- (ZYJOSSTask *)checkFileSizeWithRequest:(ZYJOSSMultipartUploadRequest *)request;
+ (NSError *)cancelError;

@end

@implementation ZYJOSSClient

static NSObject *lock;

- (instancetype)initWithEndpoint:(NSString *)endpoint credentialProvider:(id<ZYJOSSCredentialProvider>)credentialProvider {
    return [self initWithEndpoint:endpoint credentialProvider:credentialProvider clientConfiguration:[ZYJOSSClientConfiguration new]];
}

- (instancetype)initWithEndpoint:(NSString *)endpoint
              credentialProvider:(id<ZYJOSSCredentialProvider>)credentialProvider
             clientConfiguration:(ZYJOSSClientConfiguration *)conf {
    if (self = [super init]) {
        if (!lock) {
            lock = [NSObject new];
        }
        // Monitor the network. If the network type is changed, recheck the IPv6 status.
        [ZYJOSSReachabilityManager shareInstance];

        NSOperationQueue * queue = [NSOperationQueue new];
        // using for resumable upload and compat old interface
        queue.maxConcurrentOperationCount = 3;
        _ZYJOSSOperationExecutor = [ZYJOSSExecutor executorWithOperationQueue:queue];
        
        if (![endpoint ZYJOSS_isNotEmpty]) {
            [NSException raise:NSInvalidArgumentException
                        format:@"endpoint should not be nil or empty!"];
        }
        
        if ([endpoint rangeOfString:@"://"].location == NSNotFound) {
            endpoint = [@"https://" stringByAppendingString:endpoint];
        }
        
        NSURL *endpointURL = [NSURL URLWithString:endpoint];
        if ([endpointURL.scheme.lowercaseString isEqualToString:@"https"]) {
            if ([[ZYJOSSIPv6Adapter getInstance] isIPv4Address: endpointURL.host] || [[ZYJOSSIPv6Adapter getInstance] isIPv6Address: endpointURL.host]) {
                [NSException raise:NSInvalidArgumentException
                            format:@"unsupported format of endpoint, please use right endpoint format!"];
            }
        }
        
        self.endpoint = [endpoint ZYJOSS_trim];
        self.credentialProvider = credentialProvider;
        self.clientConfiguration = conf;

        ZYJOSSNetworkingConfiguration * netConf = [ZYJOSSNetworkingConfiguration new];
        if (conf) {
            netConf.maxRetryCount = conf.maxRetryCount;
            netConf.timeoutIntervalForRequest = conf.timeoutIntervalForRequest;
            netConf.timeoutIntervalForResource = conf.timeoutIntervalForResource;
            netConf.enableBackgroundTransmitService = conf.enableBackgroundTransmitService;
            netConf.backgroundSessionIdentifier = conf.backgroundSesseionIdentifier;
            netConf.proxyHost = conf.proxyHost;
            netConf.proxyPort = conf.proxyPort;
            netConf.maxConcurrentRequestCount = conf.maxConcurrentRequestCount;
        }
        self.networking = [[ZYJOSSNetworking alloc] initWithConfiguration:netConf];
    }
    return self;
}

- (ZYJOSSTask *)invokeRequest:(ZYJOSSNetworkingRequestDelegate *)request requireAuthentication:(BOOL)requireAuthentication {
    /* if content-type haven't been set, we set one */
    if (!request.allNeededMessage.contentType.ZYJOSS_isNotEmpty
        && ([request.allNeededMessage.httpMethod isEqualToString:@"POST"] || [request.allNeededMessage.httpMethod isEqualToString:@"PUT"])) {

        request.allNeededMessage.contentType = [ZYJOSSUtil detemineMimeTypeForFilePath:request.uploadingFileURL.path               uploadName:request.allNeededMessage.objectKey];
    }

    // Checks if the endpoint is in the excluded CName list.
    [self.clientConfiguration.cnameExcludeList enumerateObjectsUsingBlock:^(NSString *exclude, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([self.endpoint hasSuffix:exclude]) {
            request.allNeededMessage.isHostInCnameExcludeList = YES;
            *stop = YES;
        }
    }];

    id<ZYJOSSRequestInterceptor> uaSetting = [[ZYJOSSUASettingInterceptor alloc] initWithClientConfiguration:self.clientConfiguration];
    [request.interceptors addObject:uaSetting];

    /* check if the authentication is required */
    if (requireAuthentication) {
        id<ZYJOSSRequestInterceptor> signer = [[ZYJOSSSignerInterceptor alloc] initWithCredentialProvider:self.credentialProvider];
        [request.interceptors addObject:signer];
    }

    request.isHttpdnsEnable = self.clientConfiguration.isHttpdnsEnable;

    return [_networking sendRequest:request];
}

#pragma implement restful apis

- (ZYJOSSTask *)getService:(ZYJOSSGetServiceRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;

    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeGetService];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.params = [request requestParams];
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeGetService;

    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}



# pragma mark - Private Methods

- (void)enableCRC64WithFlag:(ZYJOSSRequestCRCFlag)flag requestDelegate:(ZYJOSSNetworkingRequestDelegate *)delegate
{
    switch (flag) {
        case ZYJOSSRequestCRCOpen:
            delegate.crc64Verifiable = YES;
            break;
        case ZYJOSSRequestCRCClosed:
            delegate.crc64Verifiable = NO;
            break;
        default:
            delegate.crc64Verifiable = self.clientConfiguration.crc64Verifiable;
            break;
    }
}

- (ZYJOSSTask *)preChecksForRequest:(ZYJOSSMultipartUploadRequest *)request
{
    ZYJOSSTask *preTask = [self checkFileSizeWithRequest:request];
    if (preTask) {
        return preTask;
    }
    
    preTask = [self checkNecessaryParamsOfRequest:request];
    if (preTask) {
        return preTask;
    }
    
    preTask = [self checkPartSizeForRequest:request];
    if (preTask) {
        return preTask;
    }
    
    
    return preTask;
}

- (void)checkRequestCrc64Setting:(ZYJOSSRequest *)request
{
    if (request.crcFlag == ZYJOSSRequestCRCUninitialized)
    {
        if (self.clientConfiguration.crc64Verifiable)
        {
            request.crcFlag = ZYJOSSRequestCRCOpen;
        }else
        {
            request.crcFlag = ZYJOSSRequestCRCClosed;
        }
    }
}

- (ZYJOSSTask *)checkNecessaryParamsOfRequest:(ZYJOSSMultipartUploadRequest *)request
{
    NSError *error = nil;
    if (![request.objectKey ZYJOSS_isNotEmpty]) {
        error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                    code:ZYJOSSClientErrorCodeInvalidArgument
                                userInfo:@{ZYJOSSErrorMessageTOKEN: @"checkNecessaryParamsOfRequest requires nonnull objectKey!"}];
    }else if (![request.bucketName ZYJOSS_isNotEmpty]) {
        error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                    code:ZYJOSSClientErrorCodeInvalidArgument
                                userInfo:@{ZYJOSSErrorMessageTOKEN: @"checkNecessaryParamsOfRequest requires nonnull bucketName!"}];
    }else if (![request.uploadingFileURL.path ZYJOSS_isNotEmpty]) {
        error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                    code:ZYJOSSClientErrorCodeInvalidArgument
                                userInfo:@{ZYJOSSErrorMessageTOKEN: @"checkNecessaryParamsOfRequest requires nonnull uploadingFileURL!"}];
    }
    
    ZYJOSSTask *errorTask = nil;
    if (error) {
        errorTask = [ZYJOSSTask taskWithError:error];
    }
    
    return errorTask;
}

- (ZYJOSSTask *)checkPartSizeForRequest:(ZYJOSSMultipartUploadRequest *)request
{
    ZYJOSSTask *errorTask = nil;
    unsigned long long fileSize = [self getSizeWithFilePath:request.uploadingFileURL.path error:nil];
    
    if (request.partSize == 0 || (fileSize > 102400 && request.partSize < 102400)) {
        NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                             code:ZYJOSSClientErrorCodeInvalidArgument
                                         userInfo:@{ZYJOSSErrorMessageTOKEN: @"Part size must be greater than equal to 100KB"}];
        errorTask = [ZYJOSSTask taskWithError:error];
    }
    return errorTask;
}

- (NSUInteger)judgePartSizeForMultipartRequest:(ZYJOSSMultipartUploadRequest *)request fileSize:(unsigned long long)fileSize
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
    BOOL divisible = (fileSize % request.partSize == 0);
    NSUInteger partCount = (fileSize / request.partSize) + (divisible? 0 : 1);
    
    if(partCount > kClientMaximumOfChunks)
    {
        request.partSize = fileSize / kClientMaximumOfChunks;
        partCount = kClientMaximumOfChunks;
    }
    return partCount;
#pragma clang diagnostic pop
}

- (unsigned long long)getSizeWithFilePath:(nonnull NSString *)filePath error:(NSError **)error
{
    NSFileManager *fm = [NSFileManager defaultManager];
    NSDictionary *attributes = [fm attributesOfItemAtPath:filePath error:error];
    return attributes.fileSize;
}

- (NSString *)readUploadIdForRequest:(ZYJOSSResumableUploadRequest *)request recordFilePath:(NSString **)recordFilePath sequential:(BOOL)sequential
{
    NSString *uploadId = nil;
    NSString *record = [NSString stringWithFormat:@"%@%@%@%lu", request.md5String, request.bucketName, request.objectKey, (unsigned long)request.partSize];
    if (sequential) {
        record = [record stringByAppendingString:kClientRecordNameWithSequentialSuffix];
    }
    if (request.crcFlag == ZYJOSSRequestCRCOpen) {
        record = [record stringByAppendingString:kClientRecordNameWithCRC64Suffix];
    }
    
    NSData *data = [record dataUsingEncoding:NSUTF8StringEncoding];
    NSString *recordFileName = [ZYJOSSUtil dataMD5String:data];
    *recordFilePath = [request.recordDirectoryPath stringByAppendingPathComponent: recordFileName];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath: *recordFilePath]) {
        NSFileHandle * read = [NSFileHandle fileHandleForReadingAtPath:*recordFilePath];
        uploadId = [[NSString alloc] initWithData:[read readDataToEndOfFile] encoding:NSUTF8StringEncoding];
        [read closeFile];
    } else {
        [fileManager createFileAtPath:*recordFilePath contents:nil attributes:nil];
    }
    return uploadId;
}

#pragma mark - sequential multipart upload

- (NSMutableDictionary *)localPartInfosDictoryWithUploadId:(NSString *)uploadId
{
    NSMutableDictionary *localPartInfoDict = nil;
    NSString *partInfosDirectory = [[NSString ZYJOSS_documentDirectory] stringByAppendingPathComponent:kClientRecordNameWithCommonPrefix];
    NSString *partInfosPath = [partInfosDirectory stringByAppendingPathComponent:uploadId];
    BOOL isDirectory;
    NSFileManager *defaultFM = [NSFileManager defaultManager];
    if (!([defaultFM fileExistsAtPath:partInfosDirectory isDirectory:&isDirectory] && isDirectory))
    {
        if (![defaultFM createDirectoryAtPath:partInfosDirectory
                                       withIntermediateDirectories:NO
                                                        attributes:nil error:nil]) {
            ZYJOSSLogError(@"create Directory(%@) failed!",partInfosDirectory);
        };
    }
    
    if (![defaultFM fileExistsAtPath:partInfosPath])
    {
        if (![defaultFM createFileAtPath:partInfosPath
                               contents:nil
                             attributes:nil])
        {
            ZYJOSSLogError(@"create local partInfo file failed!");
        }
    }
    localPartInfoDict = [[NSMutableDictionary alloc] initWithContentsOfURL:[NSURL fileURLWithPath:partInfosPath]];
    return localPartInfoDict;
}

- (ZYJOSSTask *)persistencePartInfos:(NSDictionary *)partInfos withUploadId:(NSString *)uploadId
{
    NSString *filePath = [[[NSString ZYJOSS_documentDirectory] stringByAppendingPathComponent:kClientRecordNameWithCommonPrefix] stringByAppendingPathComponent:uploadId];
    if (![partInfos writeToFile:filePath atomically:YES])
    {
        NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                             code:ZYJOSSClientErrorCodeFileCantWrite
                                         userInfo:@{ZYJOSSErrorMessageTOKEN: @"uploadId for this task can't be stored persistentially!"}];
        ZYJOSSLogDebug(@"[Error]: %@", error);
        return [ZYJOSSTask taskWithError:error];
    }
    return nil;
}

- (ZYJOSSTask *)checkPutObjectFileURL:(ZYJOSSPutObjectRequest *)request {
    NSError *error = nil;
    if (!request.uploadingFileURL || ![request.uploadingFileURL.path ZYJOSS_isNotEmpty]) {
        error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                    code:ZYJOSSClientErrorCodeInvalidArgument
                                userInfo:@{ZYJOSSErrorMessageTOKEN: @"Please check your request's uploadingFileURL!"}];
    } else {
        NSFileManager *dfm = [NSFileManager defaultManager];
        NSDictionary *attributes = [dfm attributesOfItemAtPath:request.uploadingFileURL.path error:&error];
        unsigned long long fileSize = [attributes[NSFileSize] unsignedLongLongValue];
        if (!error && fileSize == 0) {
            error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                        code:ZYJOSSClientErrorCodeInvalidArgument
                                    userInfo:@{ZYJOSSErrorMessageTOKEN: kClientErrorMessageForEmptyFile}];
        }
    }
    
    if (error) {
        return [ZYJOSSTask taskWithError:error];
    } else {
        return [ZYJOSSTask taskWithResult:nil];
    }
}

- (ZYJOSSTask *)checkFileSizeWithRequest:(ZYJOSSMultipartUploadRequest *)request {
    NSError *error = nil;
    if (!request.uploadingFileURL || ![request.uploadingFileURL.path ZYJOSS_isNotEmpty]) {
        error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                    code:ZYJOSSClientErrorCodeInvalidArgument
                                userInfo:@{ZYJOSSErrorMessageTOKEN: @"Please check your request's uploadingFileURL!"}];
    }
    else
    {
        NSFileManager *dfm = [NSFileManager defaultManager];
        NSDictionary *attributes = [dfm attributesOfItemAtPath:request.uploadingFileURL.path error:&error];
        unsigned long long fileSize = [attributes[NSFileSize] unsignedLongLongValue];
        if (!error && fileSize == 0) {
            error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                        code:ZYJOSSClientErrorCodeInvalidArgument
                                    userInfo:@{ZYJOSSErrorMessageTOKEN: kClientErrorMessageForEmptyFile}];
        }
    }
    
    if (error) {
        return [ZYJOSSTask taskWithError:error];
    } else {
        return nil;
    }
}

+ (NSError *)cancelError{
    static NSError *error = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                    code:ZYJOSSClientErrorCodeTaskCancelled
                                userInfo:@{ZYJOSSErrorMessageTOKEN: kClientErrorMessageForCancelledTask}];
    });
    return error;
}

- (void)dealloc{
    [self.networking.session invalidateAndCancel];
}

@end


@implementation ZYJOSSClient (Bucket)

- (ZYJOSSTask *)createBucket:(ZYJOSSCreateBucketRequest *)request {
    ZYJOSSNetworkingRequestDelegate *requestDelegate = request.requestDelegate;
    NSMutableDictionary *headerParams = [NSMutableDictionary dictionary];
    [headerParams ZYJOSS_setObject:request.xZYJOSSACL forKey:ZYJOSSHttpHeaderBucketACL];
    
    if (request.location) {
        requestDelegate.uploadingData = [ZYJOSSUtil constructHttpBodyForCreateBucketWithLocation:request.location];
    }
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeCreateBucket];
    
    NSString *bodyString = [NSString stringWithFormat:@"<?xml version='1.0' encoding='UTF-8'?><CreateBucketConfiguration><StorageClass>%@</StorageClass></CreateBucketConfiguration>", request.storageClassAsString];
    requestDelegate.uploadingData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
    NSString *md5String = [ZYJOSSUtil base64Md5ForData:requestDelegate.uploadingData];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPUT;
    neededMsg.bucketName = request.bucketName;
    neededMsg.headerParams = headerParams;
    neededMsg.contentMd5 = md5String;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeCreateBucket;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)deleteBucket:(ZYJOSSDeleteObjectRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeDeleteBucket];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodDELETE;
    neededMsg.bucketName = request.bucketName;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeDeleteBucket;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)getBucket:(ZYJOSSGetBucketRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeGetBucket];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.bucketName = request.bucketName;
    neededMsg.params = request.requestParams;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeGetBucket;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)getBucketInfo:(ZYJOSSGetBucketInfoRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeGetBucketInfo];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.bucketName = request.bucketName;
    neededMsg.params = request.requestParams;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeGetBucketInfo;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)getBucketACL:(ZYJOSSGetBucketACLRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeGetBucketACL];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.bucketName = request.bucketName;
    neededMsg.params = request.requestParams;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeGetBucketACL;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

@end

@implementation ZYJOSSClient (Object)

- (ZYJOSSTask *)headObject:(ZYJOSSHeadObjectRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeHeadObject];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodHEAD;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeHeadObject;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)getObject:(ZYJOSSGetObjectRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    NSString * rangeString = nil;
    if (request.range) {
        rangeString = [request.range toHeaderString];
    }
    if (request.downloadProgress) {
        requestDelegate.downloadProgress = request.downloadProgress;
    }
    if (request.onRecieveData) {
        requestDelegate.onRecieveData = request.onRecieveData;
    }
    NSMutableDictionary * params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject:request.xZYJOSSProcess forKey:ZYJOSSHttpQueryProcess];
    
    [self enableCRC64WithFlag:request.crcFlag requestDelegate:requestDelegate];
    ZYJOSSHttpResponseParser *responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeGetObject];
    responseParser.crc64Verifiable = requestDelegate.crc64Verifiable;
    
    requestDelegate.responseParser = responseParser;
    requestDelegate.responseParser.downloadingFileURL = request.downloadToFileURL;
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.range = rangeString;
    neededMsg.params = params;
    neededMsg.headerParams = request.headerFields;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeGetObject;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)getObjectACL:(ZYJOSSGetObjectACLRequest *)request
{
    ZYJOSSNetworkingRequestDelegate *requestDelegate = request.requestDelegate;
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeGetObjectACL];
    
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject:@"" forKey:@"acl"];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectName;
    neededMsg.params = params;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeGetObjectACL;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)putObject:(ZYJOSSPutObjectRequest *)request
{
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    NSMutableDictionary * headerParams = [NSMutableDictionary dictionaryWithDictionary:request.objectMeta];
    [self enableCRC64WithFlag:request.crcFlag requestDelegate:requestDelegate];
    
    if (request.uploadingData) {
        requestDelegate.uploadingData = request.uploadingData;
        if (requestDelegate.crc64Verifiable)
        {
            NSMutableData *mutableData = [NSMutableData dataWithData:request.uploadingData];
            requestDelegate.contentCRC = [NSString stringWithFormat:@"%llu",[mutableData ZYJOSS_crc64]];
        }
    }
    if (request.uploadingFileURL) {
        ZYJOSSTask *checkIfEmptyTask = [self checkPutObjectFileURL:request];
        if (checkIfEmptyTask.error) {
            return checkIfEmptyTask;
        }
        requestDelegate.uploadingFileURL = request.uploadingFileURL;
    }
    
    if (request.uploadProgress) {
        requestDelegate.uploadProgress = request.uploadProgress;
    }
    if (request.uploadRetryCallback) {
        requestDelegate.retryCallback = request.uploadRetryCallback;
    }
    
    [headerParams ZYJOSS_setObject:[request.callbackParam base64JsonString] forKey:ZYJOSSHttpHeaderXZYJOSSCallback];
    [headerParams ZYJOSS_setObject:[request.callbackVar base64JsonString] forKey:ZYJOSSHttpHeaderXZYJOSSCallbackVar];
    [headerParams ZYJOSS_setObject:request.contentDisposition forKey:ZYJOSSHttpHeaderContentDisposition];
    [headerParams ZYJOSS_setObject:request.contentEncoding forKey:ZYJOSSHttpHeaderContentEncoding];
    [headerParams ZYJOSS_setObject:request.expires forKey:ZYJOSSHttpHeaderExpires];
    [headerParams ZYJOSS_setObject:request.cacheControl forKey:ZYJOSSHttpHeaderCacheControl];
    
    ZYJOSSHttpResponseParser *responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypePutObject];
    responseParser.crc64Verifiable = requestDelegate.crc64Verifiable;
    requestDelegate.responseParser = responseParser;
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPUT;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.contentMd5 = request.contentMd5;
    neededMsg.contentType = request.contentType;
    neededMsg.headerParams = headerParams;
    neededMsg.contentSHA1 = request.contentSHA1;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypePutObject;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)putObjectACL:(ZYJOSSPutObjectACLRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    if (request.uploadRetryCallback) {
        requestDelegate.retryCallback = request.uploadRetryCallback;
    }
    NSMutableDictionary * headerParams = [NSMutableDictionary dictionary];
    [headerParams ZYJOSS_setObject:request.acl forKey:ZYJOSSHttpHeaderObjectACL];
    
    NSMutableDictionary * params = [NSMutableDictionary dictionary]; 
    [params ZYJOSS_setObject:@"" forKey:@"acl"];
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypePutObjectACL];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPUT;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.params = params;
    neededMsg.headerParams = headerParams;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypePutObjectACL;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)appendObject:(ZYJOSSAppendObjectRequest *)request
{
    return [self appendObject:request withCrc64ecma:nil];
}

- (ZYJOSSTask *)appendObject:(ZYJOSSAppendObjectRequest *)request withCrc64ecma:(nullable NSString *)crc64ecma
{
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    requestDelegate.lastCRC = crc64ecma;
    [self enableCRC64WithFlag:request.crcFlag requestDelegate:requestDelegate];
    
    if (request.uploadingData)
    {
        requestDelegate.uploadingData = request.uploadingData;
        if (requestDelegate.crc64Verifiable)
        {
            NSMutableData *mutableData = [NSMutableData dataWithData:request.uploadingData];
            requestDelegate.contentCRC = [NSString stringWithFormat:@"%llu",[mutableData ZYJOSS_crc64]];
        }
    }
    if (request.uploadingFileURL) {
        requestDelegate.uploadingFileURL = request.uploadingFileURL;
    }
    if (request.uploadProgress) {
        requestDelegate.uploadProgress = request.uploadProgress;
    }
    
    NSMutableDictionary * headerParams = [NSMutableDictionary dictionaryWithDictionary:request.objectMeta];
    [headerParams ZYJOSS_setObject:request.contentDisposition forKey:ZYJOSSHttpHeaderContentDisposition];
    [headerParams ZYJOSS_setObject:request.contentEncoding forKey:ZYJOSSHttpHeaderContentEncoding];
    [headerParams ZYJOSS_setObject:request.expires forKey:ZYJOSSHttpHeaderExpires];
    [headerParams ZYJOSS_setObject:request.cacheControl forKey:ZYJOSSHttpHeaderCacheControl];
    
    NSMutableDictionary* params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject:@"" forKey:@"append"];
    [params ZYJOSS_setObject:[@(request.appendPosition) stringValue] forKey:@"position"];
    
    ZYJOSSHttpResponseParser *responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeAppendObject];
    responseParser.crc64Verifiable = requestDelegate.crc64Verifiable;
    requestDelegate.responseParser = responseParser;
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPOST;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.contentType = request.contentType;
    neededMsg.contentMd5 = request.contentMd5;
    neededMsg.headerParams = headerParams;
    neededMsg.params = params;
    neededMsg.contentSHA1 = request.contentSHA1;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeAppendObject;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)deleteObject:(ZYJOSSDeleteObjectRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypePutObject];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodDELETE;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeDeleteObject;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)deleteMultipleObjects:(ZYJOSSDeleteMultipleObjectsRequest *)request
{
    if ([request.keys count] == 0) {
        NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                             code:ZYJOSSClientErrorCodeInvalidArgument
                                         userInfo:@{ZYJOSSErrorMessageTOKEN: @"keys should not be empty"}];
        return [ZYJOSSTask taskWithError:error];
    }
    
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    requestDelegate.uploadingData = [ZYJOSSUtil constructHttpBodyForDeleteMultipleObjects:request.keys quiet:request.quiet];
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeDeleteMultipleObjects];
    NSString *md5String = [ZYJOSSUtil base64Md5ForData:requestDelegate.uploadingData];
    
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject:@"" forKey:@"delete"];
    [params ZYJOSS_setObject:request.encodingType forKey:@"encoding-type"];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPOST;
    neededMsg.bucketName = request.bucketName;
    neededMsg.contentMd5 = md5String;
    neededMsg.params = params;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeDeleteMultipleObjects;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)copyObject:(ZYJOSSCopyObjectRequest *)request {
    NSString *copySourceHeader = nil;
    if (request.sourceCopyFrom) {
        copySourceHeader = request.sourceCopyFrom;
    } else {
        if (![request.sourceBucketName ZYJOSS_isNotEmpty]) {
            NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain code:ZYJOSSClientErrorCodeInvalidArgument userInfo:@{NSLocalizedDescriptionKey: @"sourceBucketName should not be empty!"}];
            return [ZYJOSSTask taskWithError:error];
        }
        
        if (![request.sourceObjectKey ZYJOSS_isNotEmpty]) {
            NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain code:ZYJOSSClientErrorCodeInvalidArgument userInfo:@{NSLocalizedDescriptionKey: @"sourceObjectKey should not be empty!"}];
            return [ZYJOSSTask taskWithError:error];
        }
        
        copySourceHeader = [NSString stringWithFormat:@"/%@/%@",request.bucketName, request.sourceObjectKey.ZYJOSS_urlEncodedString];
    }
    
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    NSMutableDictionary * headerParams = [NSMutableDictionary dictionaryWithDictionary:request.objectMeta];
    [headerParams ZYJOSS_setObject:copySourceHeader forKey:ZYJOSSHttpHeaderCopySource];
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeCopyObject];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPUT;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.contentType = request.contentType;
    neededMsg.contentMd5 = request.contentMd5;
    neededMsg.headerParams = headerParams;
    neededMsg.contentSHA1 = request.contentSHA1;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeCopyObject;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)putSymlink:(ZYJOSSPutSymlinkRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypePutSymlink];
    
    NSMutableDictionary *headerFields = [NSMutableDictionary dictionary];
    [headerFields ZYJOSS_setObject:[request.targetObjectName ZYJOSS_urlEncodedString] forKey:ZYJOSSHttpHeaderSymlinkTarget];
    if (request.objectMeta) {
        [headerFields addEntriesFromDictionary:request.objectMeta];
    }
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPUT;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.params = request.requestParams;
    neededMsg.headerParams = headerFields;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypePutSymlink;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)getSymlink:(ZYJOSSGetSymlinkRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeGetSymlink];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.params = request.requestParams;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeGetSymlink;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)restoreObject:(ZYJOSSRestoreObjectRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeRestoreObject];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPOST;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.params = request.requestParams;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeRestoreObject;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

@end

@implementation ZYJOSSClient (MultipartUpload)

- (ZYJOSSTask *)listMultipartUploads:(ZYJOSSListMultipartUploadsRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithDictionary:[request requestParams]];
    [params ZYJOSS_setObject:@"" forKey:@"uploads"];
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeListMultipartUploads];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.bucketName = request.bucketName;
    neededMsg.params = params;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeListMultipartUploads;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)multipartUploadInit:(ZYJOSSInitMultipartUploadRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    NSMutableDictionary * headerParams = [NSMutableDictionary dictionaryWithDictionary:request.objectMeta];
    
    [headerParams ZYJOSS_setObject:request.contentDisposition forKey:ZYJOSSHttpHeaderContentDisposition];
    [headerParams ZYJOSS_setObject:request.contentEncoding forKey:ZYJOSSHttpHeaderContentEncoding];
    [headerParams ZYJOSS_setObject:request.expires forKey:ZYJOSSHttpHeaderExpires];
    [headerParams ZYJOSS_setObject:request.cacheControl forKey:ZYJOSSHttpHeaderCacheControl];
    
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject:@"" forKey:@"uploads"];
    if (request.sequential) {
        [params ZYJOSS_setObject:@"" forKey:@"sequential"];
    }
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeInitMultipartUpload];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPOST;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.contentType = request.contentType;
    neededMsg.params = params;
    neededMsg.headerParams = headerParams;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeInitMultipartUpload;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)uploadPart:(ZYJOSSUploadPartRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    NSMutableDictionary * params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject:[@(request.partNumber) stringValue] forKey:@"partNumber"];
    [params ZYJOSS_setObject:request.uploadId forKey:@"uploadId"];
    
    [self enableCRC64WithFlag:request.crcFlag requestDelegate:requestDelegate];
    if (request.uploadPartData) {
        requestDelegate.uploadingData = request.uploadPartData;
        if (requestDelegate.crc64Verifiable)
        {
            NSMutableData *mutableData = [NSMutableData dataWithData:request.uploadPartData];
            requestDelegate.contentCRC = [NSString stringWithFormat:@"%llu",[mutableData ZYJOSS_crc64]];
        }
    }
    if (request.uploadPartFileURL) {
        requestDelegate.uploadingFileURL = request.uploadPartFileURL;
    }
    if (request.uploadPartProgress) {
        requestDelegate.uploadProgress = request.uploadPartProgress;
    }
    
    ZYJOSSHttpResponseParser *responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeUploadPart];
    responseParser.crc64Verifiable = requestDelegate.crc64Verifiable;
    requestDelegate.responseParser = responseParser;
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPUT;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectkey;
    neededMsg.contentMd5 = request.contentMd5;
    neededMsg.params = params;
    neededMsg.contentSHA1 = request.contentSHA1;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeUploadPart;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)completeMultipartUpload:(ZYJOSSCompleteMultipartUploadRequest *)request
{
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    NSMutableDictionary * headerParams = [NSMutableDictionary dictionary];
    if (request.partInfos) {
        requestDelegate.uploadingData = [ZYJOSSUtil constructHttpBodyFromPartInfos:request.partInfos];
    }
    
    [headerParams ZYJOSS_setObject:[request.callbackParam base64JsonString] forKey:ZYJOSSHttpHeaderXZYJOSSCallback];
    [headerParams ZYJOSS_setObject:[request.callbackVar base64JsonString] forKey:ZYJOSSHttpHeaderXZYJOSSCallbackVar];
    
    if (request.completeMetaHeader) {
        [headerParams addEntriesFromDictionary:request.completeMetaHeader];
    }
    NSMutableDictionary * params = [NSMutableDictionary dictionaryWithObjectsAndKeys:request.uploadId, @"uploadId", nil];
    
    ZYJOSSHttpResponseParser *responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeCompleteMultipartUpload];
    responseParser.crc64Verifiable = requestDelegate.crc64Verifiable;
    requestDelegate.responseParser = responseParser;
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPOST;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.contentMd5 = request.contentMd5;
    neededMsg.headerParams = headerParams;
    neededMsg.params = params;
    neededMsg.contentSHA1 = request.contentSHA1;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeCompleteMultipartUpload;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)listParts:(ZYJOSSListPartsRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject: request.uploadId forKey: @"uploadId"];
    [params ZYJOSS_setObject: [NSString stringWithFormat:@"%d",request.partNumberMarker] forKey: @"part-number-marker"];
    
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeListMultipart];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodGET;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.params = params;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeListMultipart;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)abortMultipartUpload:(ZYJOSSAbortMultipartUploadRequest *)request {
    ZYJOSSNetworkingRequestDelegate * requestDelegate = request.requestDelegate;
    
    NSMutableDictionary * params = [NSMutableDictionary dictionaryWithObjectsAndKeys:request.uploadId, @"uploadId", nil];
    requestDelegate.responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeAbortMultipartUpload];
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodDELETE;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectKey;
    neededMsg.params = params;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeAbortMultipartUpload;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

- (ZYJOSSTask *)abortResumableMultipartUpload:(ZYJOSSResumableUploadRequest *)request
{
    return [self abortMultipartUpload:request sequential:NO resumable:YES];
}

- (ZYJOSSTask *)abortMultipartUpload:(ZYJOSSMultipartUploadRequest *)request sequential:(BOOL)sequential resumable:(BOOL)resumable {
    
    ZYJOSSTask *errorTask = nil;
    if(resumable) {
        ZYJOSSResumableUploadRequest *resumableRequest = (ZYJOSSResumableUploadRequest *)request;
        NSString *nameInfoString = [NSString stringWithFormat:@"%@%@%@%lu",request.md5String, resumableRequest.bucketName, resumableRequest.objectKey, (unsigned long)resumableRequest.partSize];
        if (sequential) {
            nameInfoString = [nameInfoString stringByAppendingString:kClientRecordNameWithSequentialSuffix];
        }
        if (request.crcFlag == ZYJOSSRequestCRCOpen) {
            nameInfoString = [nameInfoString stringByAppendingString:kClientRecordNameWithCRC64Suffix];
        }
        
        NSData *data = [nameInfoString dataUsingEncoding:NSUTF8StringEncoding];
        NSString *recordFileName = [ZYJOSSUtil dataMD5String:data];
        NSString *recordFilePath = [NSString stringWithFormat:@"%@/%@",resumableRequest.recordDirectoryPath,recordFileName];
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSString *partInfosFilePath = [[[NSString ZYJOSS_documentDirectory] stringByAppendingPathComponent:kClientRecordNameWithCommonPrefix] stringByAppendingPathComponent:resumableRequest.uploadId];
        
        if([fileManager fileExistsAtPath:recordFilePath])
        {
            NSError *error;
            if (![fileManager removeItemAtPath:recordFilePath error:&error])
            {
                ZYJOSSLogDebug(@"[ZYJOSSSDKError]: %@", error);
            }
        }
        
        if ([fileManager fileExistsAtPath:partInfosFilePath]) {
            NSError *error;
            if (![fileManager removeItemAtPath:partInfosFilePath error:&error])
            {
                ZYJOSSLogDebug(@"[ZYJOSSSDKError]: %@", error);
            }
        }
        
        ZYJOSSAbortMultipartUploadRequest * abort = [ZYJOSSAbortMultipartUploadRequest new];
        abort.bucketName = request.bucketName;
        abort.objectKey = request.objectKey;
        if (request.uploadId) {
            abort.uploadId = request.uploadId;
        } else {
            abort.uploadId = [[NSString alloc] initWithData:[[NSFileHandle fileHandleForReadingAtPath:recordFilePath] readDataToEndOfFile] encoding:NSUTF8StringEncoding];
        }
        
        errorTask = [self abortMultipartUpload:abort];
    }else
    {
        ZYJOSSAbortMultipartUploadRequest * abort = [ZYJOSSAbortMultipartUploadRequest new];
        abort.bucketName = request.bucketName;
        abort.objectKey = request.objectKey;
        abort.uploadId = request.uploadId;
        errorTask = [self abortMultipartUpload:abort];
    }
    
    return errorTask;
}

- (ZYJOSSTask *)multipartUpload:(ZYJOSSMultipartUploadRequest *)request {
    return [self multipartUpload: request resumable: NO sequential: NO];
}

- (ZYJOSSTask *)processCompleteMultipartUpload:(ZYJOSSMultipartUploadRequest *)request partInfos:(NSArray<ZYJOSSPartInfo *> *)partInfos clientCrc64:(uint64_t)clientCrc64 recordFilePath:(NSString *)recordFilePath localPartInfosPath:(NSString *)localPartInfosPath
{
    ZYJOSSCompleteMultipartUploadRequest * complete = [ZYJOSSCompleteMultipartUploadRequest new];
    complete.bucketName = request.bucketName;
    complete.objectKey = request.objectKey;
    complete.uploadId = request.uploadId;
    complete.partInfos = partInfos;
    complete.crcFlag = request.crcFlag;
    complete.contentSHA1 = request.contentSHA1;
    
    if (request.completeMetaHeader != nil) {
        complete.completeMetaHeader = request.completeMetaHeader;
    }
    if (request.callbackParam != nil) {
        complete.callbackParam = request.callbackParam;
    }
    if (request.callbackVar != nil) {
        complete.callbackVar = request.callbackVar;
    }
    
    ZYJOSSTask * completeTask = [self completeMultipartUpload:complete];
    [completeTask waitUntilFinished];
    
    if (completeTask.error) {
        ZYJOSSLogVerbose(@"completeTask.error %@: ",completeTask.error);
        return completeTask;
    } else
    {
        if(recordFilePath && [[NSFileManager defaultManager] fileExistsAtPath:recordFilePath])
        {
            NSError *deleteError;
            if (![[NSFileManager defaultManager] removeItemAtPath:recordFilePath error:&deleteError])
            {
                ZYJOSSLogError(@"delete localUploadIdPath failed!Error: %@",deleteError);
            }
        }
        
        if (localPartInfosPath && [[NSFileManager defaultManager] fileExistsAtPath:localPartInfosPath])
        {
            NSError *deleteError;
            if (![[NSFileManager defaultManager] removeItemAtPath:localPartInfosPath error:&deleteError])
            {
                ZYJOSSLogError(@"delete localPartInfosPath failed!Error: %@",deleteError);
            }
        }
        ZYJOSSCompleteMultipartUploadResult * completeResult = completeTask.result;
        if (complete.crcFlag == ZYJOSSRequestCRCOpen && completeResult.remoteCRC64ecma)
        {
            uint64_t remote_crc64 = 0;
            NSScanner *scanner = [NSScanner scannerWithString:completeResult.remoteCRC64ecma];
            if ([scanner scanUnsignedLongLong:&remote_crc64])
            {
                ZYJOSSLogVerbose(@"resumableUpload local_crc64 %llu",clientCrc64);
                ZYJOSSLogVerbose(@"resumableUpload remote_crc64 %llu", remote_crc64);
                if (remote_crc64 != clientCrc64)
                {
                    NSString *errorMessage = [NSString stringWithFormat:@"local_crc64(%llu) is not equal to remote_crc64(%llu)!",clientCrc64,remote_crc64];
                    NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                                         code:ZYJOSSClientErrorCodeInvalidCRC
                                                     userInfo:@{ZYJOSSErrorMessageTOKEN:errorMessage}];
                    return [ZYJOSSTask taskWithError:error];
                }
            }
        }
        
        ZYJOSSResumableUploadResult * result = [ZYJOSSResumableUploadResult new];
        result.requestId = completeResult.requestId;
        result.httpResponseCode = completeResult.httpResponseCode;
        result.httpResponseHeaderFields = completeResult.httpResponseHeaderFields;
        result.serverReturnJsonString = completeResult.serverReturnJsonString;
        result.remoteCRC64ecma = completeResult.remoteCRC64ecma;
        
        return [ZYJOSSTask taskWithResult:result];
    }
}


- (ZYJOSSTask *)resumableUpload:(ZYJOSSResumableUploadRequest *)request
{
    return [self multipartUpload: request resumable: YES sequential: NO];
}

- (ZYJOSSTask *)processListPartsWithObjectKey:(nonnull NSString *)objectKey bucket:(nonnull NSString *)bucket uploadId:(NSString * _Nonnull *)uploadId uploadedParts:(nonnull NSMutableArray *)uploadedParts uploadedLength:(NSUInteger *)uploadedLength totalSize:(unsigned long long)totalSize partSize:(NSUInteger)partSize
{
    BOOL isTruncated = NO;
    int nextPartNumberMarker = 0;
    
    do {
        ZYJOSSListPartsRequest * listParts = [ZYJOSSListPartsRequest new];
        listParts.bucketName = bucket;
        listParts.objectKey = objectKey;
        listParts.uploadId = *uploadId;
        listParts.partNumberMarker = nextPartNumberMarker;
        ZYJOSSTask * listPartsTask = [self listParts:listParts];
        [listPartsTask waitUntilFinished];
        
        if (listPartsTask.error)
        {
            isTruncated = NO;
            [uploadedParts removeAllObjects];
            if ([listPartsTask.error.domain isEqualToString: ZYJOSSServerErrorDomain] && labs(listPartsTask.error.code) == 404)
            {
                ZYJOSSLogVerbose(@"local record existes but the remote record is deleted");
                *uploadId = nil;
            } else
            {
                return listPartsTask;
            }
        }
        else
        {
            ZYJOSSListPartsResult *res = listPartsTask.result;
            isTruncated = res.isTruncated;
            nextPartNumberMarker = res.nextPartNumberMarker;
            ZYJOSSLogVerbose(@"resumableUpload listpart ok");
            if (res.parts.count > 0) {
                [uploadedParts addObjectsFromArray:res.parts];
            }
        }
    } while (isTruncated);
    
    __block NSUInteger firstPartSize = 0;
    __block NSUInteger bUploadedLength = 0;
    [uploadedParts enumerateObjectsUsingBlock:^(NSDictionary *part, NSUInteger idx, BOOL * _Nonnull stop) {
        unsigned long long iPartSize = 0;
        NSString *partSizeString = [part objectForKey:ZYJOSSSizeXMLTOKEN];
        NSScanner *scanner = [NSScanner scannerWithString:partSizeString];
        [scanner scanUnsignedLongLong:&iPartSize];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
        bUploadedLength += iPartSize;
        if (idx == 0)
        {
            firstPartSize = iPartSize;
        }
#pragma clang diagnostic pop
    }];
    *uploadedLength = bUploadedLength;
    
    if (totalSize < bUploadedLength)
    {
        NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                             code:ZYJOSSClientErrorCodeCannotResumeUpload
                                         userInfo:@{ZYJOSSErrorMessageTOKEN: @"The uploading file is inconsistent with before"}];
        return [ZYJOSSTask taskWithError: error];
    }
    else if (firstPartSize != 0 && firstPartSize != partSize && totalSize != firstPartSize)
    {
        NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                             code:ZYJOSSClientErrorCodeCannotResumeUpload
                                         userInfo:@{ZYJOSSErrorMessageTOKEN: @"The part size setting is inconsistent with before"}];
        return [ZYJOSSTask taskWithError: error];
    }
    return nil;
}

- (ZYJOSSTask *)processResumableInitMultipartUpload:(ZYJOSSInitMultipartUploadRequest *)request recordFilePath:(NSString *)recordFilePath
{
    ZYJOSSTask *task = [self multipartUploadInit:request];
    [task waitUntilFinished];
    
    if(task.result && [recordFilePath ZYJOSS_isNotEmpty])
    {
        ZYJOSSInitMultipartUploadResult *result = task.result;
        if (![result.uploadId ZYJOSS_isNotEmpty])
        {
            NSString *errorMessage = [NSString stringWithFormat:@"Can not get uploadId!"];
            NSError *error = [NSError errorWithDomain:ZYJOSSServerErrorDomain
                                                 code:ZYJOSSClientErrorCodeNilUploadid userInfo:@{ZYJOSSErrorMessageTOKEN:   errorMessage}];
            return [ZYJOSSTask taskWithError:error];
        }
        
        NSFileManager *defaultFM = [NSFileManager defaultManager];
        if (![defaultFM fileExistsAtPath:recordFilePath])
        {
            if (![defaultFM createFileAtPath:recordFilePath contents:nil attributes:nil]) {
                NSError *error = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                                     code:ZYJOSSClientErrorCodeFileCantWrite
                                                 userInfo:@{ZYJOSSErrorMessageTOKEN: @"uploadId for this task can't be stored persistentially!"}];
                ZYJOSSLogDebug(@"[Error]: %@", error);
                return [ZYJOSSTask taskWithError:error];
            }
        }
        NSFileHandle * write = [NSFileHandle fileHandleForWritingAtPath:recordFilePath];
        [write writeData:[result.uploadId dataUsingEncoding:NSUTF8StringEncoding]];
        [write closeFile];
    }
    return task;
}

- (ZYJOSSTask *)upload:(ZYJOSSMultipartUploadRequest *)request
        uploadIndex:(NSMutableArray *)alreadyUploadIndex
         uploadPart:(NSMutableArray *)alreadyUploadPart
              count:(NSUInteger)partCout
     uploadedLength:(NSUInteger *)uploadedLength
           fileSize:(unsigned long long)uploadFileSize
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue setMaxConcurrentOperationCount: 5];
    
    NSObject *localLock = [[NSObject alloc] init];
    
    ZYJOSSRequestCRCFlag crcFlag = request.crcFlag;
    __block ZYJOSSTask *errorTask;
    __block NSMutableDictionary *localPartInfos = nil;
    
    if (crcFlag == ZYJOSSRequestCRCOpen) {
        localPartInfos = [self localPartInfosDictoryWithUploadId:request.uploadId];
    }
    
    if (!localPartInfos) {
        localPartInfos = [NSMutableDictionary dictionary];
    }
    
    NSError *readError;
    NSFileHandle *fileHande = [NSFileHandle fileHandleForReadingFromURL:request.uploadingFileURL error:&readError];
    if (readError) {
        return [ZYJOSSTask taskWithError: readError];
    }
    
    NSData * uploadPartData;
    NSInteger realPartLength = request.partSize;
    __block BOOL hasError = NO;
    
    for (NSUInteger idx = 1; idx <= partCout; idx++)
    {
        if (request.isCancelled)
        {
            [queue cancelAllOperations];
            break;
        }
        
        if ([alreadyUploadIndex containsObject:@(idx)])
        {
            continue;
        }
        
        // while operationCount >= 5,the loop will stay here
        while (queue.operationCount >= 5) {
            [NSThread sleepForTimeInterval: 0.15f];
        }
        
        if (idx == partCout) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
            realPartLength = uploadFileSize - request.partSize * (idx - 1);
#pragma clang diagnostic pop
        }
        @autoreleasepool
        {
            [fileHande seekToFileOffset: request.partSize * (idx - 1)];
            uploadPartData = [fileHande readDataOfLength:realPartLength];
            
            NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
                ZYJOSSTask *uploadPartErrorTask = nil;
                
                [self executePartUpload:request
               totalBytesExpectedToSend:uploadFileSize
                         totalBytesSent:uploadedLength
                                  index:idx
                               partData:uploadPartData
                      alreadyUploadPart:alreadyUploadPart
                             localParts:localPartInfos
                              errorTask:&uploadPartErrorTask];
                
                if (uploadPartErrorTask != nil) {
                    @synchronized(localLock) {
                        if (!hasError) {
                            hasError = YES;
                            errorTask = uploadPartErrorTask;
                        }
                    }
                    uploadPartErrorTask = nil;
                }
            }];
            [queue addOperation:operation];
        }
    }
    [fileHande closeFile];
    [queue waitUntilAllOperationsAreFinished];
    
    localLock = nil;
    
    if (!errorTask && request.isCancelled) {
        errorTask = [ZYJOSSTask taskWithError:[ZYJOSSClient cancelError]];
    }
    
    return errorTask;
}

- (void)executePartUpload:(ZYJOSSMultipartUploadRequest *)request totalBytesExpectedToSend:(unsigned long long)totalBytesExpectedToSend totalBytesSent:(NSUInteger *)totalBytesSent index:(NSUInteger)idx partData:(NSData *)partData alreadyUploadPart:(NSMutableArray *)uploadedParts localParts:(NSMutableDictionary *)localParts errorTask:(ZYJOSSTask **)errorTask
{
    NSUInteger bytesSent = partData.length;
    
    ZYJOSSUploadPartRequest * uploadPart = [ZYJOSSUploadPartRequest new];
    uploadPart.bucketName = request.bucketName;
    uploadPart.objectkey = request.objectKey;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
    uploadPart.partNumber = idx;
#pragma clang diagnostic pop
    uploadPart.uploadId = request.uploadId;
    uploadPart.uploadPartData = partData;
    uploadPart.contentMd5 = [ZYJOSSUtil base64Md5ForData:partData];
    uploadPart.crcFlag = request.crcFlag;
    
    ZYJOSSTask * uploadPartTask = [self uploadPart:uploadPart];
    [uploadPartTask waitUntilFinished];
    if (uploadPartTask.error) {
        if (labs(uploadPartTask.error.code) != 409) {
            *errorTask = uploadPartTask;
        }
    } else {
        ZYJOSSUploadPartResult * result = uploadPartTask.result;
        ZYJOSSPartInfo * partInfo = [ZYJOSSPartInfo new];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
        partInfo.partNum = idx;
#pragma clang diagnostic pop
        partInfo.eTag = result.eTag;
        partInfo.size = bytesSent;
        uint64_t crc64OfPart;
        @try {
            NSScanner *scanner = [NSScanner scannerWithString:result.remoteCRC64ecma];
            [scanner scanUnsignedLongLong:&crc64OfPart];
            partInfo.crc64 = crc64OfPart;
        } @catch (NSException *exception) {
            ZYJOSSLogError(@"multipart upload error with nil remote crc64!");
        }
        
        @synchronized(lock){
            [uploadedParts addObject:partInfo];
            
            if (request.crcFlag == ZYJOSSRequestCRCOpen)
            {
                [self processForLocalPartInfos:localParts
                                      partInfo:partInfo
                                      uploadId:request.uploadId];
                [self persistencePartInfos:localParts
                              withUploadId:request.uploadId];
            }
            
            *totalBytesSent += bytesSent;
            if (request.uploadProgress)
            {
                request.uploadProgress(bytesSent, *totalBytesSent, totalBytesExpectedToSend);
            }
        }
    }
}

- (void)processForLocalPartInfos:(NSMutableDictionary *)localPartInfoDict partInfo:(ZYJOSSPartInfo *)partInfo uploadId:(NSString *)uploadId
{
    NSDictionary *partInfoDict = [partInfo entityToDictionary];
    NSString *keyString = [NSString stringWithFormat:@"%i",partInfo.partNum];
    [localPartInfoDict ZYJOSS_setObject:partInfoDict forKey:keyString];
}

- (ZYJOSSTask *)sequentialMultipartUpload:(ZYJOSSResumableUploadRequest *)request
{
    return [self multipartUpload:request resumable:YES sequential:YES];
}

- (ZYJOSSTask *)multipartUpload:(ZYJOSSMultipartUploadRequest *)request resumable:(BOOL)resumable sequential:(BOOL)sequential
{
    if (resumable) {
        if (![request isKindOfClass:[ZYJOSSResumableUploadRequest class]]) {
            NSError *typoError = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                                     code:ZYJOSSClientErrorCodeInvalidArgument
                                                 userInfo:@{ZYJOSSErrorMessageTOKEN: @"resumable multipart request should use instance of class ZYJOSSMultipartUploadRequest!"}];
            return [ZYJOSSTask taskWithError: typoError];
        }
    }
    
    [self checkRequestCrc64Setting:request];
    ZYJOSSTask *preTask = [self preChecksForRequest:request];
    if (preTask) {
        return preTask;
    }
    
    return [[ZYJOSSTask taskWithResult:nil] continueWithExecutor:self.ZYJOSSOperationExecutor withBlock:^id(ZYJOSSTask *task) {
        
        __block NSUInteger uploadedLength = 0;
        uploadedLength = 0;
        __block ZYJOSSTask * errorTask;
        __block NSString *uploadId;
        
        NSError *error;
        unsigned long long uploadFileSize = [self getSizeWithFilePath:request.uploadingFileURL.path error:&error];
        if (error) {
            return [ZYJOSSTask taskWithError:error];
        }
        
        NSUInteger partCount = [self judgePartSizeForMultipartRequest:request fileSize:uploadFileSize];
        
        if (partCount > 1 && request.partSize < 102400) {
            NSError *checkPartSizeError = [NSError errorWithDomain:ZYJOSSClientErrorDomain
                                                 code:ZYJOSSClientErrorCodeInvalidArgument
                                             userInfo:@{ZYJOSSErrorMessageTOKEN: @"Part size must be greater than equal to 100KB"}];
            return [ZYJOSSTask taskWithError:checkPartSizeError];
        }
        
        if (request.isCancelled) {
            return [ZYJOSSTask taskWithError:[ZYJOSSClient cancelError]];
        }
        
        NSString *recordFilePath = nil;
        NSMutableArray * uploadedPart = [NSMutableArray array];
        NSString *localPartInfosPath = nil;
        NSDictionary *localPartInfos = nil;
        
        NSMutableArray<ZYJOSSPartInfo *> *uploadedPartInfos = [NSMutableArray array];
        NSMutableArray * alreadyUploadIndex = [NSMutableArray array];
        
        if (resumable) {
            ZYJOSSResumableUploadRequest *resumableRequest = (ZYJOSSResumableUploadRequest *)request;
            NSString *recordDirectoryPath = resumableRequest.recordDirectoryPath;
            request.md5String = [ZYJOSSUtil fileMD5String:request.uploadingFileURL.path];
            if ([recordDirectoryPath ZYJOSS_isNotEmpty])
            {
                uploadId = [self readUploadIdForRequest:resumableRequest recordFilePath:&recordFilePath sequential:sequential];
                ZYJOSSLogVerbose(@"local uploadId: %@,recordFilePath: %@",uploadId, recordFilePath);
            }
            
            if([uploadId ZYJOSS_isNotEmpty])
            {
                localPartInfosPath = [[[NSString ZYJOSS_documentDirectory] stringByAppendingPathComponent:kClientRecordNameWithCommonPrefix] stringByAppendingPathComponent:uploadId];
                
                localPartInfos = [[NSDictionary alloc] initWithContentsOfFile:localPartInfosPath];
                
                ZYJOSSTask *listPartTask = [self processListPartsWithObjectKey:request.objectKey
                                                                     bucket:request.bucketName
                                                                   uploadId:&uploadId
                                                              uploadedParts:uploadedPart
                                                             uploadedLength:&uploadedLength
                                                                  totalSize:uploadFileSize
                                                                   partSize:request.partSize];
                if (listPartTask.error)
                {
                    return listPartTask;
                }
            }
            
            [uploadedPart enumerateObjectsUsingBlock:^(NSDictionary *partInfo, NSUInteger idx, BOOL * _Nonnull stop) {
                unsigned long long remotePartNumber = 0;
                NSString *partNumberString = [partInfo objectForKey: ZYJOSSPartNumberXMLTOKEN];
                NSScanner *scanner = [NSScanner scannerWithString: partNumberString];
                [scanner scanUnsignedLongLong: &remotePartNumber];
                
                NSString *remotePartEtag = [partInfo objectForKey:ZYJOSSETagXMLTOKEN];
                
                unsigned long long remotePartSize = 0;
                NSString *partSizeString = [partInfo objectForKey:ZYJOSSSizeXMLTOKEN];
                scanner = [NSScanner scannerWithString:partSizeString];
                [scanner scanUnsignedLongLong:&remotePartSize];
                
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
                
                ZYJOSSPartInfo * info = [[ZYJOSSPartInfo alloc] init];
                info.partNum = remotePartNumber;
                info.size = remotePartSize;
                info.eTag = remotePartEtag;
                
#pragma clang diagnostic pop
            
                NSDictionary *tPartInfo = [localPartInfos objectForKey: [@(remotePartNumber) stringValue]];
                info.crc64 = [tPartInfo[@"crc64"] unsignedLongLongValue];
                
                [uploadedPartInfos addObject:info];
                [alreadyUploadIndex addObject:@(remotePartNumber)];
            }];
            
            if ([alreadyUploadIndex count] > 0 && request.uploadProgress && uploadFileSize) {
                request.uploadProgress(0, uploadedLength, uploadFileSize);
            }
        }
        
        if (![uploadId ZYJOSS_isNotEmpty]) {
            ZYJOSSInitMultipartUploadRequest *initRequest = [ZYJOSSInitMultipartUploadRequest new];
            initRequest.bucketName = request.bucketName;
            initRequest.objectKey = request.objectKey;
            initRequest.contentType = request.contentType;
            initRequest.objectMeta = request.completeMetaHeader;
            initRequest.sequential = sequential;
            initRequest.crcFlag = request.crcFlag;
            
            ZYJOSSTask *task = [self processResumableInitMultipartUpload:initRequest
                                                       recordFilePath:recordFilePath];
            if (task.error)
            {
                return task;
            }
            ZYJOSSInitMultipartUploadResult *initResult = (ZYJOSSInitMultipartUploadResult *)task.result;
            uploadId = initResult.uploadId;
        }
        
        request.uploadId = uploadId;
        localPartInfosPath = [[[NSString ZYJOSS_documentDirectory] stringByAppendingPathComponent:kClientRecordNameWithCommonPrefix] stringByAppendingPathComponent:uploadId];
        
        if (request.isCancelled)
        {
            if(resumable)
            {
                ZYJOSSResumableUploadRequest *resumableRequest = (ZYJOSSResumableUploadRequest *)request;
                if (resumableRequest.deleteUploadIdOnCancelling) {
                    ZYJOSSTask *abortTask = [self abortMultipartUpload:request sequential:sequential resumable:resumable];
                    [abortTask waitUntilFinished];
                }
            }
            
            return [ZYJOSSTask taskWithError:[ZYJOSSClient cancelError]];
        }
        
        if (sequential) {
            errorTask = [self sequentialUpload:request
                                   uploadIndex:alreadyUploadIndex
                                    uploadPart:uploadedPartInfos
                                         count:partCount
                                uploadedLength:&uploadedLength
                                      fileSize:uploadFileSize];
        } else {
            errorTask = [self upload:request
                         uploadIndex:alreadyUploadIndex
                          uploadPart:uploadedPartInfos
                               count:partCount
                      uploadedLength:&uploadedLength
                            fileSize:uploadFileSize];
        }
        
        if(errorTask.error)
        {
            ZYJOSSTask *abortTask;
            if(resumable)
            {
                ZYJOSSResumableUploadRequest *resumableRequest = (ZYJOSSResumableUploadRequest *)request;
                if (resumableRequest.deleteUploadIdOnCancelling || errorTask.error.code == ZYJOSSClientErrorCodeFileCantWrite) {
                    abortTask = [self abortMultipartUpload:request sequential:sequential resumable:resumable];
                }
            }else
            {
                abortTask =[self abortMultipartUpload:request sequential:sequential resumable:resumable];
            }
            [abortTask waitUntilFinished];
            
            return errorTask;
        }
        
        [uploadedPartInfos sortUsingComparator:^NSComparisonResult(ZYJOSSPartInfo *part1,ZYJOSSPartInfo* part2) {
            if(part1.partNum < part2.partNum){
                return NSOrderedAscending;
            }else if(part1.partNum > part2.partNum){
                return NSOrderedDescending;
            }else{
                return NSOrderedSame;
            }
        }];
        
        // 如果开启了crc64的校验
        uint64_t local_crc64 = 0;
        if (request.crcFlag == ZYJOSSRequestCRCOpen)
        {
            for (NSUInteger index = 0; index< uploadedPartInfos.count; index++)
            {
                uint64_t partCrc64 = uploadedPartInfos[index].crc64;
                int64_t partSize = uploadedPartInfos[index].size;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
                local_crc64 = [ZYJOSSUtil crc64ForCombineCRC1:local_crc64 CRC2:partCrc64 length:partSize];
#pragma clang diagnostic pop
            }
        }
        return [self processCompleteMultipartUpload:request
                                          partInfos:uploadedPartInfos
                                        clientCrc64:local_crc64
                                     recordFilePath:recordFilePath
                                 localPartInfosPath:localPartInfosPath];
    }];
}

- (ZYJOSSTask *)sequentialUpload:(ZYJOSSMultipartUploadRequest *)request
                  uploadIndex:(NSMutableArray *)alreadyUploadIndex
                   uploadPart:(NSMutableArray *)alreadyUploadPart
                        count:(NSUInteger)partCout
               uploadedLength:(NSUInteger *)uploadedLength
                     fileSize:(unsigned long long)uploadFileSize
{
    ZYJOSSRequestCRCFlag crcFlag = request.crcFlag;
    __block ZYJOSSTask *errorTask;
    __block NSMutableDictionary *localPartInfos = nil;
    
    if (crcFlag == ZYJOSSRequestCRCOpen) {
        localPartInfos = [self localPartInfosDictoryWithUploadId:request.uploadId];
    }
    
    if (!localPartInfos) {
        localPartInfos = [NSMutableDictionary dictionary];
    }
    
    NSError *readError;
    NSFileHandle *fileHande = [NSFileHandle fileHandleForReadingFromURL:request.uploadingFileURL error:&readError];
    if (readError) {
        return [ZYJOSSTask taskWithError: readError];
    }
    
    NSUInteger realPartLength = request.partSize;
    
    for (int i = 1; i <= partCout; i++) {
        if (errorTask) {
            break;
        }
        
        if (request.isCancelled) {
            errorTask = [ZYJOSSTask taskWithError:[ZYJOSSClient cancelError]];
            break;
        }
        
        if ([alreadyUploadIndex containsObject:@(i)]) {
            continue;
        }
        
        realPartLength = request.partSize;
        [fileHande seekToFileOffset:request.partSize * (i - 1)];
        if (i == partCout) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
            realPartLength = uploadFileSize - request.partSize * (i - 1);
#pragma clang diagnostic pop
        }
        NSData *uploadPartData = [fileHande readDataOfLength:realPartLength];
        
        @autoreleasepool {
            ZYJOSSUploadPartRequest * uploadPart = [ZYJOSSUploadPartRequest new];
            uploadPart.bucketName = request.bucketName;
            uploadPart.objectkey = request.objectKey;
            uploadPart.partNumber = i;
            uploadPart.uploadId = request.uploadId;
            uploadPart.uploadPartData = uploadPartData;
            uploadPart.contentMd5 = [ZYJOSSUtil base64Md5ForData:uploadPartData];
            uploadPart.crcFlag = request.crcFlag;
            
            ZYJOSSTask * uploadPartTask = [self uploadPart:uploadPart];
            [uploadPartTask waitUntilFinished];
            
            if (uploadPartTask.error) {
                if (labs(uploadPartTask.error.code) != 409) {
                    errorTask = uploadPartTask;
                    break;
                } else {
                    NSDictionary *partDict = uploadPartTask.error.userInfo;
                    ZYJOSSPartInfo *partInfo = [[ZYJOSSPartInfo alloc] init];
                    partInfo.eTag = partDict[@"PartEtag"];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
                    partInfo.partNum = [(NSString *)partDict[@"PartNumber"] integerValue];
                    partInfo.size = realPartLength;
#pragma clang diagnostic push
                    partInfo.crc64 = [[uploadPartData mutableCopy] ZYJOSS_crc64];

                    [alreadyUploadPart addObject:partInfo];
                }
            } else {
                ZYJOSSUploadPartResult * result = uploadPartTask.result;
                ZYJOSSPartInfo * partInfo = [ZYJOSSPartInfo new];
                partInfo.partNum = i;
                partInfo.eTag = result.eTag;
                partInfo.size = realPartLength;
                uint64_t crc64OfPart;
                @try {
                    NSScanner *scanner = [NSScanner scannerWithString:result.remoteCRC64ecma];
                    [scanner scanUnsignedLongLong:&crc64OfPart];
                    partInfo.crc64 = crc64OfPart;
                } @catch (NSException *exception) {
                    ZYJOSSLogError(@"multipart upload error with nil remote crc64!");
                }
                
                [alreadyUploadPart addObject:partInfo];
                if (crcFlag == ZYJOSSRequestCRCOpen)
                {
                    [self processForLocalPartInfos:localPartInfos
                                          partInfo:partInfo
                                          uploadId:request.uploadId];
                    [self persistencePartInfos:localPartInfos
                                  withUploadId:request.uploadId];
                }
                
                @synchronized(lock) {
                    *uploadedLength += realPartLength;
                    if (request.uploadProgress)
                    {
                        request.uploadProgress(realPartLength, *uploadedLength, uploadFileSize);
                    }
                }
            }
        }
    }
    [fileHande closeFile];
    
    return errorTask;
}

@end

@implementation ZYJOSSClient (PresignURL)

- (ZYJOSSTask *)presignConstrainURLWithBucketName:(NSString *)bucketName
                                 withObjectKey:(NSString *)objectKey
                        withExpirationInterval:(NSTimeInterval)interval {
    
    return [self presignConstrainURLWithBucketName:bucketName
                                     withObjectKey:objectKey
                            withExpirationInterval:interval
                                    withParameters:@{}];
}

- (ZYJOSSTask *)presignConstrainURLWithBucketName:(NSString *)bucketName
                                 withObjectKey:(NSString *)objectKey
                        withExpirationInterval:(NSTimeInterval)interval
                                withParameters:(NSDictionary *)parameters {
    
    return [self presignConstrainURLWithBucketName: bucketName
                                     withObjectKey: objectKey
                                        httpMethod: @"GET"
                            withExpirationInterval: interval
                                    withParameters: parameters];
}

- (ZYJOSSTask *)presignConstrainURLWithBucketName:(NSString *)bucketName
                                 withObjectKey:(NSString *)objectKey
                                    httpMethod:(NSString *)method
                        withExpirationInterval:(NSTimeInterval)interval
                                withParameters:(NSDictionary *)parameters
{
    return [[ZYJOSSTask taskWithResult:nil] continueWithBlock:^id(ZYJOSSTask *task) {
        NSString * resource = [NSString stringWithFormat:@"/%@/%@", bucketName, objectKey];
        NSString * expires = [@((int64_t)[[NSDate ZYJOSS_clockSkewFixedDate] timeIntervalSince1970] + interval) stringValue];
        
        NSMutableDictionary * params = [NSMutableDictionary dictionary];
        if (parameters.count > 0) {
            [params addEntriesFromDictionary:parameters];
        }
        
        NSString * wholeSign = nil;
        ZYJOSSFederationToken *token = nil;
        NSError *error = nil;
        
        if ([self.credentialProvider isKindOfClass:[ZYJOSSFederationCredentialProvider class]]) {
            token = [(ZYJOSSFederationCredentialProvider *)self.credentialProvider getToken:&error];
            if (error) {
                return [ZYJOSSTask taskWithError:error];
            }
        } else if ([self.credentialProvider isKindOfClass:[ZYJOSSStsTokenCredentialProvider class]]) {
            token = [(ZYJOSSStsTokenCredentialProvider *)self.credentialProvider getToken];
        }
        
        if ([self.credentialProvider isKindOfClass:[ZYJOSSFederationCredentialProvider class]]
            || [self.credentialProvider isKindOfClass:[ZYJOSSStsTokenCredentialProvider class]])
        {
            [params ZYJOSS_setObject:token.tToken forKey:@"security-token"];
            resource = [NSString stringWithFormat:@"%@?%@", resource, [ZYJOSSUtil populateSubresourceStringFromParameter:params]];
            NSString * string2sign = [NSString stringWithFormat:@"%@\n\n\n%@\n%@", method, expires, resource];
            wholeSign = [ZYJOSSUtil sign:string2sign withToken:token];
        } else {
            NSString * subresource = [ZYJOSSUtil populateSubresourceStringFromParameter:params];
            if ([subresource length] > 0) {
                resource = [NSString stringWithFormat:@"%@?%@", resource, [ZYJOSSUtil populateSubresourceStringFromParameter:params]];
            }
            NSString * string2sign = [NSString stringWithFormat:@"%@\n\n\n%@\n%@",  method, expires, resource];
            wholeSign = [self.credentialProvider sign:string2sign error:&error];
            if (error) {
                return [ZYJOSSTask taskWithError:error];
            }
        }
        
        NSArray * splitResult = [wholeSign componentsSeparatedByString:@":"];
        if ([splitResult count] != 2
            || ![((NSString *)[splitResult objectAtIndex:0]) hasPrefix:@"OSS "]) {
            return [ZYJOSSTask taskWithError:[NSError errorWithDomain:ZYJOSSClientErrorDomain
                                                              code:ZYJOSSClientErrorCodeSignFailed
                                                          userInfo:@{ZYJOSSErrorMessageTOKEN: @"the returned signature is invalid"}]];
        }
        NSString * accessKey = [(NSString *)[splitResult objectAtIndex:0] substringFromIndex:4];
        NSString * signature = [splitResult objectAtIndex:1];
        
        NSURL * endpointURL = [NSURL URLWithString:self.endpoint];
        NSString * host = endpointURL.host;
        if ([ZYJOSSUtil isZYJOSSOriginBucketHost:host]) {
            host = [NSString stringWithFormat:@"%@.%@", bucketName, host];
        }
        
        [params ZYJOSS_setObject:signature forKey:@"Signature"];
        [params ZYJOSS_setObject:accessKey forKey:@"OSSAccessKeyId"];
        [params ZYJOSS_setObject:expires forKey:@"Expires"];
        NSString * stringURL = [NSString stringWithFormat:@"%@://%@/%@?%@",
                                endpointURL.scheme,
                                host,
                                [ZYJOSSUtil encodeURL:objectKey],
                                [ZYJOSSUtil populateQueryStringFromParameter:params]];
        return [ZYJOSSTask taskWithResult:stringURL];
    }];
}

- (ZYJOSSTask *)presignPublicURLWithBucketName:(NSString *)bucketName
                              withObjectKey:(NSString *)objectKey {
    
    return [self presignPublicURLWithBucketName:bucketName
                                  withObjectKey:objectKey
                                 withParameters:@{}];
}

- (ZYJOSSTask *)presignPublicURLWithBucketName:(NSString *)bucketName
                              withObjectKey:(NSString *)objectKey
                             withParameters:(NSDictionary *)parameters {
    
    return [[ZYJOSSTask taskWithResult:nil] continueWithBlock:^id(ZYJOSSTask *task) {
        NSURL * endpointURL = [NSURL URLWithString:self.endpoint];
        NSString * host = endpointURL.host;
        if ([ZYJOSSUtil isZYJOSSOriginBucketHost:host]) {
            host = [NSString stringWithFormat:@"%@.%@", bucketName, host];
        }
        if ([parameters count] > 0) {
            NSString * stringURL = [NSString stringWithFormat:@"%@://%@/%@?%@",
                                    endpointURL.scheme,
                                    host,
                                    [ZYJOSSUtil encodeURL:objectKey],
                                    [ZYJOSSUtil populateQueryStringFromParameter:parameters]];
            return [ZYJOSSTask taskWithResult:stringURL];
        } else {
            NSString * stringURL = [NSString stringWithFormat:@"%@://%@/%@",
                                    endpointURL.scheme,
                                    host,
                                    [ZYJOSSUtil encodeURL:objectKey]];
            return [ZYJOSSTask taskWithResult:stringURL];
        }
    }];
}

@end

@implementation ZYJOSSClient (Utilities)

- (BOOL)doesObjectExistInBucket:(NSString *)bucketName
                      objectKey:(NSString *)objectKey
                          error:(const NSError **)error {
    
    ZYJOSSHeadObjectRequest * headRequest = [ZYJOSSHeadObjectRequest new];
    headRequest.bucketName = bucketName;
    headRequest.objectKey = objectKey;
    ZYJOSSTask * headTask = [self headObject:headRequest];
    [headTask waitUntilFinished];
    NSError *headError = headTask.error;
    if (!headError) {
        return YES;
    } else {
        if ([headError.domain isEqualToString: ZYJOSSServerErrorDomain] && labs(headError.code) == 404) {
            return NO;
        } else {
            if (error != nil) {
                *error = headError;
            }
            return NO;
        }
    }
}

@end

@implementation ZYJOSSClient (ImageService)

- (ZYJOSSTask *)imageActionPersist:(ZYJOSSImagePersistRequest *)request
{
    if (![request.fromBucket ZYJOSS_isNotEmpty]
        || ![request.fromObject ZYJOSS_isNotEmpty]
        || ![request.toBucket ZYJOSS_isNotEmpty]
        || ![request.toObject ZYJOSS_isNotEmpty]
        || ![request.action ZYJOSS_isNotEmpty]) {
        NSError *error = [NSError errorWithDomain:ZYJOSSTaskErrorDomain
                                             code:ZYJOSSClientErrorCodeInvalidArgument
                                         userInfo:@{ZYJOSSErrorMessageTOKEN: @"imagePersist parameters not be empty!"}];
        return [ZYJOSSTask taskWithError:error];
    }
    
    ZYJOSSNetworkingRequestDelegate *requestDelegate = request.requestDelegate;
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject:@"" forKey:ZYJOSSHttpQueryProcess];
    
    requestDelegate.uploadingData = [ZYJOSSUtil constructHttpBodyForImagePersist:request.action toBucket:request.toBucket toObjectKey:request.toObject];
    
    ZYJOSSHttpResponseParser *responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeImagePersist];
    requestDelegate.responseParser = responseParser;
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPOST;
    neededMsg.bucketName = request.fromBucket;
    neededMsg.objectKey = request.fromObject;
    neededMsg.params = params;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeImagePersist;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

@end

@implementation ZYJOSSClient (Callback)

- (ZYJOSSTask *)triggerCallBack:(ZYJOSSCallBackRequest *)request
{
    ZYJOSSNetworkingRequestDelegate *requestDelegate = request.requestDelegate;
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    [params ZYJOSS_setObject:@"" forKey:ZYJOSSHttpQueryProcess];
    NSString *paramString = [request.callbackParam base64JsonString];
    NSString *variblesString = [request.callbackVar base64JsonString];
    requestDelegate.uploadingData = [ZYJOSSUtil constructHttpBodyForTriggerCallback:paramString callbackVaribles:variblesString];
    NSString *md5String = [ZYJOSSUtil base64Md5ForData:requestDelegate.uploadingData];
    
    ZYJOSSHttpResponseParser *responseParser = [[ZYJOSSHttpResponseParser alloc] initForOperationType:ZYJOSSOperationTypeTriggerCallBack];
    requestDelegate.responseParser = responseParser;
    
    ZYJOSSAllRequestNeededMessage *neededMsg = [[ZYJOSSAllRequestNeededMessage alloc] init];
    neededMsg.endpoint = self.endpoint;
    neededMsg.httpMethod = ZYJOSSHTTPMethodPOST;
    neededMsg.bucketName = request.bucketName;
    neededMsg.objectKey = request.objectName;
    neededMsg.contentMd5 = md5String;
    neededMsg.params = params;
    requestDelegate.allNeededMessage = neededMsg;
    
    requestDelegate.operType = ZYJOSSOperationTypeTriggerCallBack;
    
    return [self invokeRequest:requestDelegate requireAuthentication:request.isAuthenticationRequired];
}

@end
