Sample code for 30+ languages & platforms
Tcl Requires Chilkat v11.7.0+

Box: Upload a Large File in Parts and Commit the Session

See more Box Examples

Run the reusable setup once per HttpCurl object, then select names and execute the operation. The setup is repeated here only to make this published example independently runnable. Resolve documents by name, then perform the explicit chunked-upload session, part, and commit loop. Supports 20 MB through 2 GB using int. Saves recovery information and honors bounded commit Retry-After.

See the Box API guide for setup and the complete workflow.

Chilkat Tcl Downloads

Tcl

load ./chilkat.dll

set success 0

#  Requires Chilkat 11.7.0 or later and a previously unlocked Chilkat bundle.
#  See Global Unlock Sample.
#  Guide: Box API Guide.

set success 0

#  BEGIN ONE-TIME SETUP
#  This block is repeated so each published example can run on its own.
#  In an application, run the setup example once and pass the SAME curl object
#  to operation code below. Do not recreate it for every operation.
#  See Reusable Box HttpCurl Setup.
set curl [new_CkHttpCurl]

CkHttpCurl_SetVar $curl "access_token" "BOX_ACCESS_TOKEN"
CkHttpCurl_SetVar $curl "parent_name" "/"
#  Box's fixed root ID is an implementation detail; callers supply names.
CkHttpCurl_SetVar $curl "parent_id" "0"
set setupCommand [new_CkStringBuilder]

#  Register findParent; registration sends no request.
CkStringBuilder_SetString $setupCommand "curl -X GET \"https://api.box.com/2.0/folders/0/items?limit=1000&fields=id,type,name\" -H \"Authorization: Bearer ACCESS_TOKEN\""
set setupReplacements [CkStringBuilder_Replace $setupCommand "ACCESS_TOKEN" [CkHttpCurl_getVar $curl "access_token"]]
set success [CkHttpCurl_AddFunction $curl "findParent" [CkStringBuilder_getAsString $setupCommand]]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

#  Register findFolder; registration sends no request.
CkStringBuilder_SetString $setupCommand "curl -X GET \"https://api.box.com/2.0/folders/{{parent_id}}/items?limit=1000&fields=id,type,name\" -H \"Authorization: Bearer ACCESS_TOKEN\""
set setupReplacements [CkStringBuilder_Replace $setupCommand "ACCESS_TOKEN" [CkHttpCurl_getVar $curl "access_token"]]
set success [CkHttpCurl_AddFunction $curl "findFolder" [CkStringBuilder_getAsString $setupCommand]]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

#  Register findFile; registration sends no request.
CkStringBuilder_SetString $setupCommand "curl -X GET \"https://api.box.com/2.0/folders/{{folder_id}}/items?limit=1000&fields=id,type,name\" -H \"Authorization: Bearer ACCESS_TOKEN\""
set setupReplacements [CkStringBuilder_Replace $setupCommand "ACCESS_TOKEN" [CkHttpCurl_getVar $curl "access_token"]]
set success [CkHttpCurl_AddFunction $curl "findFile" [CkStringBuilder_getAsString $setupCommand]]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

#  Register findDestination; registration sends no request.
CkStringBuilder_SetString $setupCommand "curl -X GET \"https://api.box.com/2.0/folders/0/items?limit=1000&fields=id,type,name\" -H \"Authorization: Bearer ACCESS_TOKEN\""
set setupReplacements [CkStringBuilder_Replace $setupCommand "ACCESS_TOKEN" [CkHttpCurl_getVar $curl "access_token"]]
set success [CkHttpCurl_AddFunction $curl "findDestination" [CkStringBuilder_getAsString $setupCommand]]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

#  Register getFileDetails; registration sends no request.
CkStringBuilder_SetString $setupCommand "curl -X GET \"https://api.box.com/2.0/files/{{file_id}}?fields=id,sha1,file_version\" -H \"Authorization: Bearer ACCESS_TOKEN\""
set setupReplacements [CkStringBuilder_Replace $setupCommand "ACCESS_TOKEN" [CkHttpCurl_getVar $curl "access_token"]]
set success [CkHttpCurl_AddFunction $curl "getFileDetails" [CkStringBuilder_getAsString $setupCommand]]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

#  Register getEnterprise; registration sends no request.
CkStringBuilder_SetString $setupCommand "curl -X GET \"https://api.box.com/2.0/users/me?fields=enterprise\" -H \"Authorization: Bearer ACCESS_TOKEN\""
set setupReplacements [CkStringBuilder_Replace $setupCommand "ACCESS_TOKEN" [CkHttpCurl_getVar $curl "access_token"]]
set success [CkHttpCurl_AddFunction $curl "getEnterprise" [CkStringBuilder_getAsString $setupCommand]]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

#  Register output rules once. Chilkat 11.7.0 evaluates {{name}} variables
#  when extracting each output, using the current value at that time.
set success [CkHttpCurl_AddOutput2 $curl "findParent" "entries" "name" "{{parent_name}}" 1 "id" "parent_id"]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

set success [CkHttpCurl_AddOutput2 $curl "findFolder" "entries" "name" "{{folder_name}}" 1 "id" "folder_id"]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

set success [CkHttpCurl_AddOutput2 $curl "findFile" "entries" "name" "{{file_name}}" 1 "id" "file_id"]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

set success [CkHttpCurl_AddOutput2 $curl "findDestination" "entries" "name" "{{destination_name}}" 1 "id" "destination_id"]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

set success [CkHttpCurl_AddOutput $curl "getFileDetails" "file_version.id" "file_version_id"]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

set success [CkHttpCurl_AddOutput $curl "getFileDetails" "sha1" "file_sha1"]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

set success [CkHttpCurl_AddOutput $curl "getEnterprise" "enterprise.id" "enterprise_id"]
if {$success != 1} then {
    puts [CkHttpCurl_lastErrorText $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    exit
}

#  END ONE-TIME SETUP
#  The name lookups below assume each listing fits in one page (1000 items).
#  Parent and destination names select root children; folder_name selects a child
#  of parent_name. "/" selects the root parent. These are not arbitrary paths.

#  BEGIN OPERATION -- keep the configured object when reusing it.
#  Inputs are names. Retain cached IDs when the selected names have not changed.
set parentName "/"
set folderName "documents"
if {expr [[CkHttpCurl_getVar $curl "parent_name"] != $parentName]  ||  [expr [$parentName == "/"]  &&  [[CkHttpCurl_VarDefined $curl "parent_id"] == 0]]} then {
    CkHttpCurl_SetVar $curl "parent_name" $parentName
    CkHttpCurl_ClearVar $curl "parent_id"
    CkHttpCurl_ClearVar $curl "folder_id"
    CkHttpCurl_ClearVar $curl "file_id"
    CkHttpCurl_ClearVar $curl "file_version_id"
    CkHttpCurl_ClearVar $curl "file_sha1"
    if {$parentName == "/"} then {
        CkHttpCurl_SetVar $curl "parent_id" "0"
    }

}

if {[CkHttpCurl_getVar $curl "folder_name"] != $folderName} then {
    CkHttpCurl_SetVar $curl "folder_name" $folderName
    CkHttpCurl_ClearVar $curl "folder_id"
    CkHttpCurl_ClearVar $curl "file_id"
    CkHttpCurl_ClearVar $curl "file_version_id"
    CkHttpCurl_ClearVar $curl "file_sha1"
}

#  Resolve the named destination first. HttpCurl does not expand variables
#  inside multipart -F attributes in the tested build, so this upload is two phases.
CkHttpCurl_ClearTargetOutput $curl "*"
set authorizedCommand1 [new_CkStringBuilder]

CkStringBuilder_SetString $authorizedCommand1 "curl -X GET \"https://api.box.com/2.0/folders/{{folder_id}}?fields=id\" -H \"Authorization: Bearer {{access_token}}\""
set headerReplacement1 [CkStringBuilder_Replace $authorizedCommand1 "{{access_token}}" [CkHttpCurl_getVar $curl "access_token"]]
set success [CkHttpCurl_DoYourThing $curl [CkStringBuilder_getAsString $authorizedCommand1]]
if {expr [$success != 1]  ||  [[CkHttpCurl_get_StatusCode $curl] != 200]} then {
    puts "Box request failed. HTTP status: [CkHttpCurl_get_StatusCode $curl]"
    puts "Failure reason: [CkHttpCurl_get_FailReason $curl]"
    puts [CkHttpCurl_responseBodyStr $curl]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    exit
}

#  This example limits size to 2 GB so offsets fit all generated languages' integers.
#  The source must not change during hashing or upload. Create qa_output first.
set localPath "qa_data/large-document.zip"
set file [new_CkFileAccess]

#  Check the decimal size before converting so an oversized file cannot overflow int.
set sizeString [CkFileAccess_fileSizeStr $file $localPath]
if {[CkFileAccess_get_LastMethodSuccess $file] != 1} then {
    puts [CkFileAccess_lastErrorText $file]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    exit
}

set sizeText [new_CkStringBuilder]

CkStringBuilder_SetString $sizeText $sizeString
set sizeMatch [new_CkJsonObject]

if {[CkStringBuilder_RegexMatch $sizeText "^([0-9]{1,9}|1[0-9]{9}|2000000000)$" $sizeMatch 100] != 1} then {
    puts "Choose an input between 20 MB and 2 GB for this example."
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    exit
}

set fileSize $sizeString
if {$fileSize < 20971520} then {
    puts "Choose an input between 20 MB and 2 GB for this example."
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    exit
}

set http [new_CkHttp]

CkHttp_put_AuthToken $http [CkHttpCurl_getVar $curl "access_token"]
set response [new_CkHttpResponse]

set request [new_CkJsonObject]

CkJsonObject_UpdateString $request "folder_id" [CkHttpCurl_getVar $curl "folder_id"]
CkJsonObject_UpdateString $request "file_name" "large-document.zip"
CkJsonObject_UpdateInt $request "file_size" $fileSize
set success [CkHttp_HttpJson $http "POST" "https://upload.box.com/api/2.0/files/upload_sessions" $request "application/json" $response]
if {$success != 1} then {
    puts [CkHttp_lastErrorText $http]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    delete_CkHttp $http
    delete_CkHttpResponse $response
    delete_CkJsonObject $request
    exit
}

if {[CkHttpResponse_get_StatusCode $response] != 201} then {
    puts [CkHttpResponse_bodyStr $response]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    delete_CkHttp $http
    delete_CkHttpResponse $response
    delete_CkJsonObject $request
    exit
}

set session [new_CkJsonObject]

set success [CkJsonObject_Load $session [CkHttpResponse_bodyStr $response]]
if {$success != 1} then {
    puts [CkJsonObject_lastErrorText $session]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    delete_CkHttp $http
    delete_CkHttpResponse $response
    delete_CkJsonObject $request
    delete_CkJsonObject $session
    exit
}

set success [CkJsonObject_WriteFile $session "qa_output/box_upload_session.json"]
if {$success != 1} then {
    puts [CkJsonObject_lastErrorText $session]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    delete_CkHttp $http
    delete_CkHttpResponse $response
    delete_CkJsonObject $request
    delete_CkJsonObject $session
    exit
}

set partSize [CkJsonObject_IntOf $session "part_size"]
set uploadUrl [CkJsonObject_stringOf $session "session_endpoints.upload_part"]
set commitUrl [CkJsonObject_stringOf $session "session_endpoints.commit"]
if {expr [$partSize <= 0]  ||  [expr [$uploadUrl == ""]  ||  [$commitUrl == ""]]} then {
    puts "Invalid session response."
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    delete_CkHttp $http
    delete_CkHttpResponse $response
    delete_CkJsonObject $request
    delete_CkJsonObject $session
    exit
}

set hash [new_CkCrypt2]

CkCrypt2_put_HashAlgorithm $hash "sha1"
CkCrypt2_put_EncodingMode $hash "base64"
set fileDigest [CkCrypt2_hashFileENC $hash $localPath]
if {[CkCrypt2_get_LastMethodSuccess $hash] != 1} then {
    puts [CkCrypt2_lastErrorText $hash]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    delete_CkHttp $http
    delete_CkHttpResponse $response
    delete_CkJsonObject $request
    delete_CkJsonObject $session
    delete_CkCrypt2 $hash
    exit
}

set success [CkFileAccess_FileOpen $file $localPath 2147483648 1 3 0]
if {$success != 1} then {
    puts [CkFileAccess_lastErrorText $file]
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    delete_CkHttp $http
    delete_CkHttpResponse $response
    delete_CkJsonObject $request
    delete_CkJsonObject $session
    delete_CkCrypt2 $hash
    exit
}

set commit [new_CkJsonObject]

set partResponse [new_CkJsonObject]

set chunk [new_CkBinData]

set range [new_CkStringBuilder]

set digestHeader [new_CkStringBuilder]

set offset 0
set partIndex 0

while {$offset < $fileSize} {
    CkBinData_Clear $chunk
    set success [CkFileAccess_FileReadBd $file $partSize $chunk]
    if {$success != 1} then {
        puts [CkFileAccess_lastErrorText $file]
        CkFileAccess_FileClose $file
        delete_CkHttpCurl $curl
        delete_CkStringBuilder $setupCommand
        delete_CkStringBuilder $authorizedCommand1
        delete_CkFileAccess $file
        delete_CkStringBuilder $sizeText
        delete_CkJsonObject $sizeMatch
        delete_CkHttp $http
        delete_CkHttpResponse $response
        delete_CkJsonObject $request
        delete_CkJsonObject $session
        delete_CkCrypt2 $hash
        delete_CkJsonObject $commit
        delete_CkJsonObject $partResponse
        delete_CkBinData $chunk
        delete_CkStringBuilder $range
        delete_CkStringBuilder $digestHeader
        exit
    }

    set partLength [CkBinData_get_NumBytes $chunk]
    if {$partLength <= 0} then {
        puts "Unexpected end of file."
        CkFileAccess_FileClose $file
        delete_CkHttpCurl $curl
        delete_CkStringBuilder $setupCommand
        delete_CkStringBuilder $authorizedCommand1
        delete_CkFileAccess $file
        delete_CkStringBuilder $sizeText
        delete_CkJsonObject $sizeMatch
        delete_CkHttp $http
        delete_CkHttpResponse $response
        delete_CkJsonObject $request
        delete_CkJsonObject $session
        delete_CkCrypt2 $hash
        delete_CkJsonObject $commit
        delete_CkJsonObject $partResponse
        delete_CkBinData $chunk
        delete_CkStringBuilder $range
        delete_CkStringBuilder $digestHeader
        exit
    }

    set lastByte [expr $offset + [expr $partLength - 1]]
    CkStringBuilder_Clear $range
    CkStringBuilder_Append $range "bytes "
    CkStringBuilder_AppendInt $range $offset
    CkStringBuilder_Append $range "-"
    CkStringBuilder_AppendInt $range $lastByte
    CkStringBuilder_Append $range "/"
    CkStringBuilder_AppendInt $range $fileSize
    set partDigest [CkCrypt2_hashBdENC $hash $chunk]
    if {[CkCrypt2_get_LastMethodSuccess $hash] != 1} then {
        puts [CkCrypt2_lastErrorText $hash]
        CkFileAccess_FileClose $file
        delete_CkHttpCurl $curl
        delete_CkStringBuilder $setupCommand
        delete_CkStringBuilder $authorizedCommand1
        delete_CkFileAccess $file
        delete_CkStringBuilder $sizeText
        delete_CkJsonObject $sizeMatch
        delete_CkHttp $http
        delete_CkHttpResponse $response
        delete_CkJsonObject $request
        delete_CkJsonObject $session
        delete_CkCrypt2 $hash
        delete_CkJsonObject $commit
        delete_CkJsonObject $partResponse
        delete_CkBinData $chunk
        delete_CkStringBuilder $range
        delete_CkStringBuilder $digestHeader
        exit
    }

    CkStringBuilder_SetString $digestHeader "sha="
    CkStringBuilder_Append $digestHeader $partDigest
    CkHttp_SetRequestHeader $http "Digest" [CkStringBuilder_getAsString $digestHeader]
    CkHttp_SetRequestHeader $http "Content-Range" [CkStringBuilder_getAsString $range]
    set success [CkHttp_HttpBd $http "PUT" $uploadUrl $chunk "application/octet-stream" $response]
    if {expr [$success != 1]  ||  [[CkHttpResponse_get_StatusCode $response] != 200]} then {
        puts "Part failed. HTTP status: [CkHttpResponse_get_StatusCode $response]"
        puts [CkHttpResponse_bodyStr $response]
        puts "Inspect the saved session and server list_parts before retrying this part."
        CkFileAccess_FileClose $file
        delete_CkHttpCurl $curl
        delete_CkStringBuilder $setupCommand
        delete_CkStringBuilder $authorizedCommand1
        delete_CkFileAccess $file
        delete_CkStringBuilder $sizeText
        delete_CkJsonObject $sizeMatch
        delete_CkHttp $http
        delete_CkHttpResponse $response
        delete_CkJsonObject $request
        delete_CkJsonObject $session
        delete_CkCrypt2 $hash
        delete_CkJsonObject $commit
        delete_CkJsonObject $partResponse
        delete_CkBinData $chunk
        delete_CkStringBuilder $range
        delete_CkStringBuilder $digestHeader
        exit
    }

    set success [CkJsonObject_Load $partResponse [CkHttpResponse_bodyStr $response]]
    if {$success != 1} then {
        CkFileAccess_FileClose $file
        delete_CkHttpCurl $curl
        delete_CkStringBuilder $setupCommand
        delete_CkStringBuilder $authorizedCommand1
        delete_CkFileAccess $file
        delete_CkStringBuilder $sizeText
        delete_CkJsonObject $sizeMatch
        delete_CkHttp $http
        delete_CkHttpResponse $response
        delete_CkJsonObject $request
        delete_CkJsonObject $session
        delete_CkCrypt2 $hash
        delete_CkJsonObject $commit
        delete_CkJsonObject $partResponse
        delete_CkBinData $chunk
        delete_CkStringBuilder $range
        delete_CkStringBuilder $digestHeader
        exit
    }

    CkJsonObject_put_I $commit $partIndex
    #  Copy the server's acknowledged part fields into the commit manifest.
    CkJsonObject_UpdateString $commit "parts[i].part_id" [CkJsonObject_stringOf $partResponse "part.part_id"]
    CkJsonObject_UpdateInt $commit "parts[i].offset" [CkJsonObject_IntOf $partResponse "part.offset"]
    CkJsonObject_UpdateInt $commit "parts[i].size" [CkJsonObject_IntOf $partResponse "part.size"]
    CkJsonObject_UpdateString $commit "parts[i].sha1" [CkJsonObject_stringOf $partResponse "part.sha1"]
    set success [CkJsonObject_WriteFile $commit "qa_output/box_upload_parts.json"]
    if {$success != 1} then {
        puts [CkJsonObject_lastErrorText $commit]
        CkFileAccess_FileClose $file
        delete_CkHttpCurl $curl
        delete_CkStringBuilder $setupCommand
        delete_CkStringBuilder $authorizedCommand1
        delete_CkFileAccess $file
        delete_CkStringBuilder $sizeText
        delete_CkJsonObject $sizeMatch
        delete_CkHttp $http
        delete_CkHttpResponse $response
        delete_CkJsonObject $request
        delete_CkJsonObject $session
        delete_CkCrypt2 $hash
        delete_CkJsonObject $commit
        delete_CkJsonObject $partResponse
        delete_CkBinData $chunk
        delete_CkStringBuilder $range
        delete_CkStringBuilder $digestHeader
        exit
    }

    set offset [expr $offset + $partLength]
    set partIndex [expr $partIndex + 1]
}
CkFileAccess_FileClose $file
CkHttp_RemoveRequestHeader $http "Content-Range"
CkStringBuilder_SetString $digestHeader "sha="
CkStringBuilder_Append $digestHeader $fileDigest
CkHttp_SetRequestHeader $http "Digest" [CkStringBuilder_getAsString $digestHeader]
set attempts 0

set retryHeader [new_CkStringBuilder]

set retryMatch [new_CkJsonObject]

set completed 0
while {expr [$attempts < 10]  &&  [$completed == 0]} {
    set success [CkHttp_HttpJson $http "POST" $commitUrl $commit "application/json" $response]
    if {$success != 1} then {
        puts "Commit failed: [CkHttpResponse_get_StatusCode $response]"
        puts [CkHttpResponse_bodyStr $response]
        delete_CkHttpCurl $curl
        delete_CkStringBuilder $setupCommand
        delete_CkStringBuilder $authorizedCommand1
        delete_CkFileAccess $file
        delete_CkStringBuilder $sizeText
        delete_CkJsonObject $sizeMatch
        delete_CkHttp $http
        delete_CkHttpResponse $response
        delete_CkJsonObject $request
        delete_CkJsonObject $session
        delete_CkCrypt2 $hash
        delete_CkJsonObject $commit
        delete_CkJsonObject $partResponse
        delete_CkBinData $chunk
        delete_CkStringBuilder $range
        delete_CkStringBuilder $digestHeader
        delete_CkStringBuilder $retryHeader
        delete_CkJsonObject $retryMatch
        exit
    }

    if {[CkHttpResponse_get_StatusCode $response] == 201} then {
        set completed 1
    }     else {
        if {[CkHttpResponse_get_StatusCode $response] != 202} then {
            puts "Unexpected commit status: [CkHttpResponse_get_StatusCode $response]"
            delete_CkHttpCurl $curl
            delete_CkStringBuilder $setupCommand
            delete_CkStringBuilder $authorizedCommand1
            delete_CkFileAccess $file
            delete_CkStringBuilder $sizeText
            delete_CkJsonObject $sizeMatch
            delete_CkHttp $http
            delete_CkHttpResponse $response
            delete_CkJsonObject $request
            delete_CkJsonObject $session
            delete_CkCrypt2 $hash
            delete_CkJsonObject $commit
            delete_CkJsonObject $partResponse
            delete_CkBinData $chunk
            delete_CkStringBuilder $range
            delete_CkStringBuilder $digestHeader
            delete_CkStringBuilder $retryHeader
            delete_CkJsonObject $retryMatch
            exit
        }

        #  Box's asynchronous commit response supplies Retry-After in seconds.
        CkStringBuilder_SetString $retryHeader [CkHttpResponse_getHeaderField $response "Retry-After"]
        if {[CkStringBuilder_RegexMatch $retryHeader "^[0-9]{1,2}$" $retryMatch 100] != 1} then {
            puts "Commit pending. Preserve the session and inspect Retry-After before resuming: [CkStringBuilder_getAsString $retryHeader]"
            delete_CkHttpCurl $curl
            delete_CkStringBuilder $setupCommand
            delete_CkStringBuilder $authorizedCommand1
            delete_CkFileAccess $file
            delete_CkStringBuilder $sizeText
            delete_CkJsonObject $sizeMatch
            delete_CkHttp $http
            delete_CkHttpResponse $response
            delete_CkJsonObject $request
            delete_CkJsonObject $session
            delete_CkCrypt2 $hash
            delete_CkJsonObject $commit
            delete_CkJsonObject $partResponse
            delete_CkBinData $chunk
            delete_CkStringBuilder $range
            delete_CkStringBuilder $digestHeader
            delete_CkStringBuilder $retryHeader
            delete_CkJsonObject $retryMatch
            exit
        }

        set waitSeconds [CkStringBuilder_getAsString $retryHeader]
        if {expr [$waitSeconds < 1]  ||  [$waitSeconds > 60]} then {
            puts "Commit pending. Resume commit after Retry-After: [CkHttpResponse_getHeaderField $response Retry-After]"
            delete_CkHttpCurl $curl
            delete_CkStringBuilder $setupCommand
            delete_CkStringBuilder $authorizedCommand1
            delete_CkFileAccess $file
            delete_CkStringBuilder $sizeText
            delete_CkJsonObject $sizeMatch
            delete_CkHttp $http
            delete_CkHttpResponse $response
            delete_CkJsonObject $request
            delete_CkJsonObject $session
            delete_CkCrypt2 $hash
            delete_CkJsonObject $commit
            delete_CkJsonObject $partResponse
            delete_CkBinData $chunk
            delete_CkStringBuilder $range
            delete_CkStringBuilder $digestHeader
            delete_CkStringBuilder $retryHeader
            delete_CkJsonObject $retryMatch
            exit
        }

        CkHttp_SleepMs $http [expr $waitSeconds * 1000]
    }

    set attempts [expr $attempts + 1]
}
if {$completed == 0} then {
    puts "Commit still pending; retain session and part manifests to resume commit later."
    delete_CkHttpCurl $curl
    delete_CkStringBuilder $setupCommand
    delete_CkStringBuilder $authorizedCommand1
    delete_CkFileAccess $file
    delete_CkStringBuilder $sizeText
    delete_CkJsonObject $sizeMatch
    delete_CkHttp $http
    delete_CkHttpResponse $response
    delete_CkJsonObject $request
    delete_CkJsonObject $session
    delete_CkCrypt2 $hash
    delete_CkJsonObject $commit
    delete_CkJsonObject $partResponse
    delete_CkBinData $chunk
    delete_CkStringBuilder $range
    delete_CkStringBuilder $digestHeader
    delete_CkStringBuilder $retryHeader
    delete_CkJsonObject $retryMatch
    exit
}

puts "Upload committed: [CkHttpResponse_bodyStr $response]"

delete_CkHttpCurl $curl
delete_CkStringBuilder $setupCommand
delete_CkStringBuilder $authorizedCommand1
delete_CkFileAccess $file
delete_CkStringBuilder $sizeText
delete_CkJsonObject $sizeMatch
delete_CkHttp $http
delete_CkHttpResponse $response
delete_CkJsonObject $request
delete_CkJsonObject $session
delete_CkCrypt2 $hash
delete_CkJsonObject $commit
delete_CkJsonObject $partResponse
delete_CkBinData $chunk
delete_CkStringBuilder $range
delete_CkStringBuilder $digestHeader
delete_CkStringBuilder $retryHeader
delete_CkJsonObject $retryMatch