U
    W¨+d»z  ã                   @   s¬   d dl Z d dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlm	Z	 d dl
mZmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ G d	d
„ d
eƒZdS )é    N)Úmd5)ÚconfigÚ	UserAgent)ÚAWSAuthConnection)ÚInvalidUriError)ÚResumableTransferDisposition)ÚResumableUploadException)ÚKeyFilec                   @   s¸   e Zd ZdZejeejej	fZ
dZd*dd„Zdd„ Zdd	„ Zd
d„ Zdd„ Zdd„ Zdd„ Zd+dd„Zdd„ Zdd„ Zd,dd„Zdd„ Zdd„ Zdd „ Zd!d"„ Zd-d%d&„Zd.d(d)„ZdS )/ÚResumableUploadHandleri    )r   éÿÿÿÿNc                 C   s.   || _ || _d| _d| _|r$|  ¡  d| _dS )a
  
        Constructor. Instantiate once for each uploaded file.

        :type tracker_file_name: string
        :param tracker_file_name: optional file name to save tracker URI.
            If supplied and the current process fails the upload, it can be
            retried in a new process. If called with an existing file containing
            a valid tracker URI, we'll resume the upload from this URI; else
            we'll start a new resumable upload (and write the URI to this
            tracker file).

        :type num_retries: int
        :param num_retries: the number of times we'll re-try a resumable upload
            making no progress. (Count resets every time we get progress, so
            upload can span many more than this number of retries.)
        r   N)Útracker_file_nameÚnum_retriesÚserver_has_bytesÚtracker_uriÚ_load_tracker_uri_from_fileÚupload_start_point)Úselfr   r   © r   úD/tmp/pip-unpacked-wheel-dlxw5sjy/boto/gs/resumable_upload_handler.pyÚ__init__@   s    zResumableUploadHandler.__init__c              
   C   s¸   d }z z&t| jdƒ}| ¡  ¡ }|  |¡ W nt tk
rn } z$|jtjkr^t	d| j|j
f ƒ W 5 d }~X Y n4 tk
r  } zt	d|| jf ƒ W 5 d }~X Y nX W 5 |r²|  ¡  X d S )NÚrzHCouldn't read URI tracker file (%s): %s. Restarting upload from scratch.zXInvalid tracker URI (%s) found in URI tracker file (%s). Restarting upload from scratch.)ÚcloseÚopenr   ÚreadlineÚstripÚ_set_tracker_uriÚIOErrorÚerrnoÚENOENTÚprintÚstrerrorr   )r   ÚfÚuriÚer   r   r   r   [   s     
þþz2ResumableUploadHandler._load_tracker_uri_from_filec              
   C   sŠ   | j s
dS d}z<t t | j tjtjB d¡d¡}| | j¡ W 5 Q R X W n: tk
r„ } zt	d| j |j
f tjƒ‚W 5 d}~X Y nX dS )zM
        Saves URI to tracker file if one was passed to constructor.
        Ni€  Úwz¹Couldn't write URI tracker file (%s): %s.
This can happenif you're using an incorrectly configured upload tool
(e.g., gsutil configured to save tracker files to an unwritable directory))r   ÚosÚfdopenr   ÚO_WRONLYÚO_CREATÚwriter   r   r   r    r   ÚABORT)r   r!   r#   r   r   r   Ú_save_tracker_uri_to_files   s&    
 ÿÿ
üúz0ResumableUploadHandler._save_tracker_uri_to_filec                 C   sT   t   |¡}|j ¡ dks|js*td| ƒ‚|| _|j| _d|j|jf | _	d| _
dS )zÒ
        Called when we start a new resumable upload or get a new tracker
        URI for the upload. Saves URI and resets upload state.

        Raises InvalidUriError if URI is syntactically invalid.
        )ÚhttpÚhttpszInvalid tracker URI (%s)z%s?%sr   N)ÚurlparseÚschemeÚlowerÚnetlocr   r   Útracker_uri_hostÚpathÚqueryÚtracker_uri_pathr   )r   r"   Zparse_resultr   r   r   r   ‡   s    
ÿ ÿz'ResumableUploadHandler._set_tracker_uric                 C   s   | j S )zX
        Returns upload tracker URI, or None if the upload has not yet started.
        )r   ©r   r   r   r   Úget_tracker_uri˜   s    z&ResumableUploadHandler.get_tracker_uric                 C   s:   d}| j r2|| j kr2| j | j  |¡t|ƒ d… S dS dS )zt
        Returns the upload ID for the resumable upload, or None if the upload
        has not yet started.
        z?upload_id=N)r   ÚindexÚlen)r   Údelimr   r   r   Úget_upload_idž   s    
z$ResumableUploadHandler.get_upload_idc                 C   s$   | j r tj | j ¡r t | j ¡ d S ©N)r   r%   r3   ÚexistsÚunlinkr6   r   r   r   Ú_remove_tracker_file®   s    ÿz+ResumableUploadHandler._remove_tracker_fileÚ*c                 C   s   d||f S )Nzbytes %s/%sr   )r   Ú
range_specZlength_specr   r   r   Ú_build_content_range_header³   s    z2ResumableUploadHandler._build_content_range_headerc                 C   s8   i }|   d|¡|d< d|d< tj|d| j| j|| jdS )a~  
        Queries server to find out state of given upload.

        Note that this method really just makes special case use of the
        fact that the upload server always returns the current start/end
        state whenever a PUT doesn't complete.

        Returns HTTP response from sending request.

        Raises ResumableUploadException if problem querying server.
        r@   úContent-RangeÚ0úContent-LengthÚPUT©r3   Z	auth_pathÚheadersÚhost)rB   r   Úmake_requestr5   r2   )r   ÚconnÚfile_lengthÚput_headersr   r   r   Ú_query_server_state¶   s    
ÿüz*ResumableUploadHandler._query_server_statec           	      C   sÆ   |   ||¡}|jdkr"d|d fS |jdkr>td|j tjƒ‚d}| d¡}|r‚t d|¡}|rˆt| 	d¡ƒ}t| 	d	¡ƒ}d
}n| j
S |s¤tdt| ¡ ƒ tjƒ‚|jdkr¾td||f ƒ ||fS )a+  
        Queries server to find out what bytes it currently has.

        Returns (server_start, server_end), where the values are inclusive.
        For example, (0, 2) would mean that the server has bytes 0, 1, *and* 2.

        Raises ResumableUploadException if problem querying server.
        éÈ   r   é   i4  z1Got non-308 response (%s) from server state queryFÚrangezbytes=(\d+)-(\d+)é   Tz6Couldn't parse upload server state query response (%s)zServer has: Range: %d - %d.)rN   Ústatusr   r   Ú
START_OVERÚ	getheaderÚreÚsearchÚlongÚgroupÚSERVER_HAS_NOTHINGÚstrÚ
getheadersÚdebugr   )	r   rK   rL   ÚrespZgot_valid_responserA   ÚmÚserver_startÚ
server_endr   r   r   Ú_query_server_posÎ   s:    	

ÿþ

ÿþ
z(ResumableUploadHandler._query_server_posc           	      C   sô   |j j}|jdkrtdƒ d| _i }|D ](}| ¡ dkrDtdtjƒ‚|| ||< q(d||j	j
< | d|j j|j|¡}| ¡ }|jdkrštd	|j tjƒ‚n&|jd
krÀ|jdkrÀtd|j tjƒ‚| d¡}|sÞtd| tjƒ‚|  |¡ |  ¡  dS )zn
        Starts a new resumable upload.

        Raises ResumableUploadException if any errors occur.
        rP   zStarting new resumable upload.r   zcontent-lengthz5Attempt to specify Content-Length header (disallowed)ÚstartÚPOST)éô  é÷  zEGot status %d from attempt to start resumable upload. Will wait/retryrO   éÉ   z>Got status %d from attempt to start resumable upload. AbortingZLocationzINo resumable tracker URI found in resumable initiation POST response (%s)N)ÚbucketÚ
connectionr]   r   r   r0   r   r   r*   ZproviderZresumable_upload_headerrJ   ÚnameÚreadrS   ÚWAIT_BEFORE_RETRYrU   r   r+   )	r   ÚkeyrH   rK   Zpost_headersÚkr^   Úbodyr   r   r   r   Ú_start_new_resumable_upload  sX    
þ   ÿ
ÿýÿý
ÿý
z2ResumableUploadHandler._start_new_resumable_uploadc	                 C   s  |  | j¡}	|rL|dkr,|| j |d  }
n|dk r:d}
nd}
d}|||ƒ |sVi }n| ¡ }|r˜||krx|  d|¡}n|  d||d f |¡}||d< t|| ƒ|d< tj|d	| jd
|| jd}| 	d	|j
¡ |D ]}| ||| ¡ qÔ| ¡  | d¡ |	rt| |	¡ | jD ]}| j|  |	¡ q|t|	ƒ7 }|rf|d7 }||
ksX|
dkrf|||ƒ d}|  | j¡}	qü| |j¡ |r|||ƒ ||kr®td||f tjƒ‚| ¡ }| |j¡ |jdkrê| d¡| d¡| d¡fS |jdkrþtj}ntj}td|j|jf |ƒ‚d
S )zþ
        Makes one attempt to upload file bytes, using an existing resumable
        upload connection.

        Returns (etag, generation, metageneration) from server upon success.

        Raises ResumableUploadException if any problems occur.
        rR   r   r   r@   z%d-%drP   rC   rE   rF   NrG   z<File changed during upload: EOF at %d bytes of %d byte file.rO   Úetagzx-goog-generationzx-goog-metageneration)i˜  re   rf   z1Got response code %d while attempting upload (%s))rk   ÚBUFFER_SIZEÚcopyrB   r[   r   Zbuild_base_http_requestr5   r2   Ú
putrequestr3   Ú	putheaderÚ
endheadersÚset_debuglevelÚsendÚ	digestersÚupdater9   r]   r   r   r*   ÚgetresponserS   rU   rl   Úreason)r   rK   Ú	http_connÚfprL   Útotal_bytes_uploadedÚcbÚnum_cbrH   ÚbufZcb_countÚirM   Zrange_headerÚhttp_requestrn   Úalgr^   Údispositionr   r   r   Ú_upload_file_bytes<  s’    

 ÿþ    þ





ÿýþ
þþz)ResumableUploadHandler._upload_file_bytesc              
   C   sÖ  | j \}}|jj}	| jrüzš|  |	|¡\}}|| _|ržtdƒ | d¡ |d }
|
rž| t	|j
|
ƒ¡}|sttdtjƒ‚| jD ]}| j|  |¡ qz|
t|ƒ8 }
qN|	jdkr°tdƒ W nF tk
rø } z(|	jdkrÜtd|j ƒ |  ||¡ W 5 d}~X Y nX n|  ||¡ | jdkr|| _|d }||k r6| |¡ |jj}	|	 | j|	j|	j¡}| |	j¡ zhz|  |	|||||||¡W W ¢JS  ttjfk
rÂ   |  |	|¡}|jdkr¼td	tj ƒ‚n‚ Y nX W 5 | ¡  X dS )
z¼
        Attempts a resumable upload.

        Returns (etag, generation, metageneration) from server upon success.

        Raises ResumableUploadException if any problems occur.
        z-Catching up hash digest(s) for resumed uploadr   rP   zÒHit end of file during resumable upload hash catchup. This should not happen under
normal circumstances, as it indicates the server has more bytes of this transfer
than the current file size. Restarting upload.zResuming transfer.zUnable to resume transfer (%s).Ni  zîGot 400 response from server state query after failed resumable upload attempt. This can happen for various reasons, including specifying an invalid request (e.g., an invalid canned ACL) or if the file size changed between upload attempts)!rZ   rh   ri   r   rb   r   r   Úseekrk   ÚminZ
BufferSizer   r   rT   ry   rz   r9   r]   Úmessagerp   r   Znew_http_connectionr2   ÚportZ	is_securerw   r   r‡   ÚsocketÚerrorrN   rS   r*   )r   rm   r~   rL   rH   r€   r   r`   ra   rK   Zbytes_to_goÚchunkr…   r#   r   r}   r^   r   r   r   Ú_attempt_resumable_uploadš  sj    	

ÿ
ú


 

ÿ  þ
ûz0ResumableUploadHandler._attempt_resumable_uploadc                 C   sN   |j jjdkrtdƒ |j| d¡krJ| ¡  | ¡  | ¡  t	dt
jƒ‚dS )a;  
        Checks that etag from server agrees with md5 computed before upload.
        This is important, since the upload could have spanned a number of
        hours and multiple processes (e.g., gsutil runs), and the user could
        change some of the file and not realize they have inconsistent data.
        rP   zChecking md5 against etag.z"'z`File changed during upload: md5 signature doesn't match etag (incorrect uploaded object deleted)N)rh   ri   r]   r   r   r   Z	open_readr   Údeleter   r   r*   )r   rm   rq   r   r   r   Ú_check_final_md5ø  s    ýz'ResumableUploadHandler._check_final_md5c                 C   sn   |j tjkr&|dkr"td|j ƒ ‚ nD|j tjkrT|dkrHtd|j ƒ |  ¡  ‚ n|dkrjtd|j ƒ d S )NrP   zWCaught non-retryable ResumableUploadException (%s); aborting but retaining tracker filezVCaught non-retryable ResumableUploadException (%s); aborting and removing tracker filez1Caught ResumableUploadException (%s) - will retry)r†   r   ÚABORT_CUR_PROCESSr   rŠ   r*   r?   )r   r#   r]   r   r   r   Ú!handle_resumable_upload_exception  s"    ÿÿÿz8ResumableUploadHandler.handle_resumable_upload_exceptionTr   c                 C   s~   | j |krd| _n|  jd7  _|r,| j| _| j| jkrDtdtjƒ‚t ¡ d| j  }|dkrpt	d| j|f ƒ t
 |¡ d S )Nr   rP   zaToo many resumable upload attempts failed without progress. You might try this upload again laterrR   zZGot retryable failure (%d progress-less in a row).
Sleeping %3.1f seconds before re-trying)r   Úprogress_less_iterationsÚdigesters_before_attemptry   r   r   r   r’   Úrandomr   ÚtimeÚsleep)r   Úserver_had_bytes_before_attemptZroll_back_md5r]   Zsleep_time_secsr   r   r   Útrack_progress_less_iterations   s     
ýþz5ResumableUploadHandler.track_progress_less_iterationsé
   c              
      sú  |si }d}||kr&|| dkr&||= t |d< t|tƒrD| ¡ j}n | dtj¡ | ¡ }| d¡ |j	j
j}	ˆ dkr~dti‰ t‡ fdd„ˆ pi D ƒƒˆ_ˆjdkr´t dd	d
¡ˆ_dˆ_ˆj}
t‡fdd„ˆjD ƒƒˆ_ztˆ ||||||¡\}ˆ_ˆ_ˆjD ]}ˆj|  ¡ |j|< q ˆ ¡  ˆ ||¡ ˆj|_|	dkrJtdƒ W dS  ˆjk
r¶ } zF|	dkr~td| ¡  ƒ t|tƒr¦|j t j!kr¦|j	j
j
 "¡  W 5 d}~X Y n0 t#k
rä } zˆ $||	¡ W 5 d}~X Y nX ˆ %|
d|	¡ qºdS )a0  
        Upload a file to a key into a bucket on GS, using GS resumable upload
        protocol.

        :type key: :class:`boto.s3.key.Key` or subclass
        :param key: The Key object to which data is to be uploaded

        :type fp: file-like object
        :param fp: The file pointer to upload

        :type headers: dict
        :param headers: The headers to pass along with the PUT request

        :type cb: function
        :param cb: a callback function that will be called to report progress on
            the upload.  The callback should accept two integer parameters, the
            first representing the number of bytes that have been successfully
            transmitted to GS, and the second representing the total number of
            bytes that need to be transmitted.

        :type num_cb: int
        :param num_cb: (optional) If a callback is specified with the cb
            parameter, this parameter determines the granularity of the callback
            by defining the maximum number of times the callback will be called
            during the file transfer. Providing a negative integer will cause
            your callback to be called with each buffer read.

        :type hash_algs: dictionary
        :param hash_algs: (optional) Dictionary mapping hash algorithm
            descriptions to corresponding state-ful hashing objects that
            implement update(), digest(), and copy() (e.g. hashlib.md5()).
            Defaults to {'md5': md5()}.

        Raises ResumableUploadException if a problem occurs during the transfer.
        zContent-TypeNz
User-Agentr   r   c                 3   s   | ]}|ˆ | ƒ fV  qd S r<   r   ©Ú.0r…   )Ú	hash_algsr   r   Ú	<genexpr>x  s    z3ResumableUploadHandler.send_file.<locals>.<genexpr>ZBotor   é   c                 3   s    | ]}|ˆ j |  ¡ fV  qd S r<   )ry   rs   rœ   r6   r   r   rŸ   ƒ  s   ÿrP   zResumable upload complete.zCaught exception (%s)T)&r   Ú
isinstancer	   ZgetkeyÚsizerˆ   r%   ÚSEEK_ENDÚtellrh   ri   r]   r   Údictry   r   r   Úgetintr”   r   r•   r   Z
generationZmetagenerationÚdigestZlocal_hashesr?   r‘   r   ÚRETRYABLE_EXCEPTIONSÚ__repr__r   r   ÚEPIPEr   r   r“   rš   )r   rm   r~   rH   r€   r   rž   ZCTrL   r]   r™   rq   r…   r#   r   )rž   r   r   Ú	send_file;  sd    %


ÿ

þ

  ÿÿ


 ÿz ResumableUploadHandler.send_file)NN)r@   r@   )N)Tr   )Nr›   N)Ú__name__Ú
__module__Ú__qualname__rr   ÚhttplibÚHTTPExceptionr   rŒ   r   Úgaierrorr¨   rZ   r   r   r+   r   r7   r;   r?   rB   rN   rb   rp   r‡   r   r‘   r“   rš   r«   r   r   r   r   r
   6   s0   
ÿ

4
:^^   ÿ
r
   )r   r¯   r%   r–   rV   rŒ   r—   r.   Úhashlibr   Zbotor   r   Zboto.connectionr   Zboto.exceptionr   r   r   Zboto.s3.keyfiler	   Úobjectr
   r   r   r   r   Ú<module>   s   