@@ -362,6 +362,8 @@ class Shotgun(object):
362362 "^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])" \
363363 "(\D?([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?)?$" )
364364
365+ _MULTIPART_UPLOAD_CHUNK_SIZE = 20000000
366+
365367 def __init__ (self ,
366368 base_url ,
367369 script_name = None ,
@@ -2215,13 +2217,14 @@ def _upload_to_storage(self, entity_type, entity_id, path, field_name, display_n
22152217
22162218 # Step 1: get the upload url
22172219
2218- multipart_upload = (os .path .getsize (path ) > 20000000 )
2220+ is_multipart_upload = (os .path .getsize (path ) > _MULTIPART_UPLOAD_CHUNK_SIZE )
22192221
2220- upload_info = self ._get_attachment_upload_info (is_thumbnail , filename , multipart_upload )
2222+ upload_info = self ._get_attachment_upload_info (is_thumbnail , filename , is_multipart_upload )
22212223
22222224 # Step 2: upload the file
2223-
2224- if multipart_upload :
2225+ # We upload large files in multiple parts because it is more robust
2226+ # (and required when using S3 storage)
2227+ if is_multipart_upload :
22252228 self ._multipart_upload_file_to_storage (path , upload_info )
22262229 else :
22272230 self ._upload_file_to_storage (path , upload_info ["upload_url" ])
@@ -2321,16 +2324,16 @@ def _upload_to_sg(self, entity_type, entity_id, path, field_name, display_name,
23212324 attachment_id = int (str (result ).split (":" )[1 ].split ("\n " )[0 ])
23222325 return attachment_id
23232326
2324- def _get_attachment_upload_info (self , is_thumbnail , filename , multipart_upload ):
2327+ def _get_attachment_upload_info (self , is_thumbnail , filename , is_multipart_upload ):
23252328 """
23262329 Internal function to get the information needed to upload a file to Cloud storage.
23272330
23282331 :param bool is_thumbnail: indicates if the attachment is a thumbnail.
23292332 :param str filename: name of the file that will be uploaded.
2330- :param bool multipart_upload : Indicates if we want multi-part upload information back.
2333+ :param bool is_multipart_upload : Indicates if we want multi-part upload information back.
23312334
2332- :returns: dictionary containing the upload url and
2333- upload_info (passed back to the SG server once the upload is completed) .
2335+ :returns: dictionary containing upload details from the server.
2336+ These details are used throughout the upload process .
23342337 :rtype: dict
23352338 """
23362339
@@ -2344,8 +2347,7 @@ def _get_attachment_upload_info(self, is_thumbnail, filename, multipart_upload):
23442347 "filename" : filename
23452348 }
23462349
2347- if multipart_upload :
2348- params ['multipart_upload' ] = True
2350+ params ["multipart_upload" ] = is_multipart_upload
23492351
23502352 upload_url = "/upload/api_get_upload_link_info"
23512353 url = urlparse .urlunparse ((self .config .scheme , self .config .server ,
@@ -3564,7 +3566,7 @@ def _multipart_upload_file_to_storage(self, path, upload_info):
35643566 etags = []
35653567 part_number = 1
35663568 bytes_read = 0
3567- chunk_size = 20000000
3569+ chunk_size = _MULTIPART_UPLOAD_CHUNK_SIZE
35683570 while bytes_read < file_size :
35693571 data = fd .read (chunk_size )
35703572 bytes_read += len (data )
@@ -3601,9 +3603,13 @@ def _get_upload_part_link(self, upload_info, filename, part_number):
36013603 "/upload/api_get_upload_link_for_part" , None , None , None ))
36023604 result = self ._send_form (url , params )
36033605
3606+ # Response is of the form: 1\n<url> (for success) or 0\n (for failure).
3607+ # In case of success, we know we the second line of the response contains the
3608+ # requested URL.
36043609 if not str (result ).startswith ("1" ):
36053610 raise ShotgunError ("Unable get upload part link: %s" % result )
36063611
3612+ LOG .debug ("Got next upload link from server for multipart upload." )
36073613 return str (result ).split ("\n " )[1 ]
36083614
36093615 def _upload_data_to_storage (self , data , content_type , size , storage_url ):
@@ -3631,6 +3637,8 @@ def _upload_data_to_storage(self, data, content_type, size, storage_url):
36313637 raise ShotgunError ("Server encountered an internal error.\n %s\n %s\n \n " % (url , e ))
36323638 else :
36333639 raise ShotgunError ("Unanticipated error occurred uploading %s: %s" % (path , e ))
3640+
3641+ LOG .debug ("Part upload completed successfully." )
36343642 return etag
36353643
36363644 def _complete_multipart_upload (self , upload_info , filename , etags ):
@@ -3654,6 +3662,7 @@ def _complete_multipart_upload(self, upload_info, filename, etags):
36543662 "/upload/api_complete_multipart_upload" , None , None , None ))
36553663 result = self ._send_form (url , params )
36563664
3665+ # Response is of the form: 1\n or 0\n to indicate success or failure of the call.
36573666 if not str (result ).startswith ("1" ):
36583667 raise ShotgunError ("Unable get upload part link: %s" % result )
36593668
0 commit comments