
?F[c           @   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 d	 e f d
     YZ d S(   iN(   t   md5(   t   configt	   UserAgent(   t   AWSAuthConnection(   t   InvalidUriError(   t   ResumableTransferDisposition(   t   ResumableUploadException(   t   KeyFilet   ResumableUploadHandlerc           B   s   e  Z d  Z e j e e j e j f Z	 d Z
 d d d  Z d   Z d   Z d   Z d   Z d   Z d	   Z d
 d
 d  Z d   Z d   Z d d  Z d   Z d   Z d   Z d   Z e d d  Z d d d d  Z RS(   i    i    ic         C   sD   | |  _  | |  _ d |  _ d |  _ | r7 |  j   n  d |  _ d S(   s
  
        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.)
        i    N(   t   tracker_file_namet   num_retriest   server_has_bytest   Nonet   tracker_urit   _load_tracker_uri_from_filet   upload_start_point(   t   selfR	   R
   (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   __init__@   s    				c         C   s   d  } z y5 t |  j d  } | j   j   } |  j |  Wna t k
 r} } | j t j k r d |  j | j	 f GHq n% t
 k
 r } d | |  j f GHn XWd  | r | j   n  Xd  S(   Nt   rsH   Couldn't read URI tracker file (%s): %s. Restarting upload from scratch.sX   Invalid tracker URI (%s) found in URI tracker file (%s). Restarting upload from scratch.(   R   t   openR	   t   readlinet   stript   _set_tracker_urit   IOErrort   errnot   ENOENTt   strerrorR   t   close(   R   t   ft   urit   e(    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyR   [   s     c         C   s   |  j  s d Sd } yK t j t j |  j  t j t j Bd  d   } | j |  j  Wd QXWn5 t	 k
 r } t
 d |  j  | j f t j   n Xd S(   sM   
        Saves URI to tracker file if one was passed to constructor.
        Ni  t   ws   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	   R   t   ost   fdopenR   t   O_WRONLYt   O_CREATt   writeR   R   R   R   R   t   ABORT(   R   R   R   (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _save_tracker_uri_to_files   s    	c         C   s|   t  j  |  } | j j   d k s. | j rA t d |   n  | |  _ | j |  _ d | j | j f |  _	 d |  _
 d S(   s   
        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.
        t   httpt   httpss   Invalid tracker URI (%s)s   %s?%si    N(   R'   R(   (   t   urlparset   schemet   lowert   netlocR   R   t   tracker_uri_hostt   patht   queryt   tracker_uri_pathR   (   R   R   t   parse_result(    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyR      s    
	c         C   s   |  j  S(   sX   
        Returns upload tracker URI, or None if the upload has not yet started.
        (   R   (   R   (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   get_tracker_uri   s    c         C   sG   d } |  j  r? | |  j  k r? |  j  |  j  j |  t |  Sd Sd S(   st   
        Returns the upload ID for the resumable upload, or None if the upload
        has not yet started.
        s   ?upload_id=N(   R   t   indext   lenR   (   R   t   delim(    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   get_upload_id   s    
!c         C   s5   |  j  r1 t j j |  j   r1 t j |  j   n  d  S(   N(   R	   R    R.   t   existst   unlink(   R   (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _remove_tracker_file   s    	t   *c         C   s   d | | f S(   Ns   bytes %s/%s(    (   R   t
   range_spect   length_spec(    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _build_content_range_header   s    c         C   sW   i  } |  j  d |  | d <d | d <t j | d d |  j d |  j d | d	 |  j S(
   s~  
        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:   s   Content-Ranget   0s   Content-Lengtht   PUTR.   t	   auth_patht   headerst   host(   R=   R   t   make_requestR0   R-   (   R   t   connt   file_lengtht   put_headers(    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _query_server_state   s    
		c   	      C   s   |  j  | |  } | j d k r/ d | d f S| j d k rZ t d | j t j   n  t } | j d  } | r t j d |  } | r t	 | j
 d   } t	 | j
 d   } t } q n |  j S| s t d	 t | j    t j   n  | j d k rd
 | | f GHn  | | f S(   s+  
        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.
        i   i    i   i4  s1   Got non-308 response (%s) from server state queryt   ranges   bytes=(\d+)-(\d+)i   s6   Couldn't parse upload server state query response (%s)s   Server has: Range: %d - %d.(   RG   t   statusR   R   t
   START_OVERt   Falset	   getheadert   ret   searcht   longt   groupt   Truet   SERVER_HAS_NOTHINGt   strt
   getheaderst   debug(	   R   RD   RE   t   respt   got_valid_responseR;   t   mt   server_startt
   server_end(    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _query_server_pos   s.    	c   	      C   sc  | j  j } | j d k r# d GHn  d |  _ i  } xC | D]; } | j   d k rf t d t j   n  | | | | <q9 Wd | | j j	 <| j
 d | j  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   n  | j d  } | sHt d | t j   n  |  j |  |  j   d S(   sn   
        Starts a new resumable upload.

        Raises ResumableUploadException if any errors occur.
        i   s   Starting new resumable upload.i    s   content-lengths5   Attempt to specify Content-Length header (disallowed)t   startt   POSTi  i  sE   Got status %d from attempt to start resumable upload. Will wait/retryi   i   s>   Got status %d from attempt to start resumable upload. Abortingt   LocationsI   No resumable tracker URI found in resumable initiation POST response (%s)N(   i  i  (   t   buckett
   connectionRU   R   R+   R   R   R%   t   providert   resumable_upload_headerRC   t   namet   readRI   t   WAIT_BEFORE_RETRYRL   R   R&   (	   R   t   keyRA   RD   t   post_headerst   kRV   t   bodyR   (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _start_new_resumable_upload  sB    	c	         C   s  | j  |  j  }	 | rm | d k r< | |  j | d }
 n | d k  rQ d }
 n d }
 d } | | |  n  | s| i  } n | j   } | r | | k r |  j d |  } n  |  j d | | d f |  } | | d <n  t | |  | d <t j | d	 d
 |  j d d d | d |  j	 } | j
 d	 | j  x" | D] } | j | | |  q:W| j   | j d  x |	 r| j |	  x% |  j D] } |  j | j |	  qW| t |	  7} | r| d 7} | |
 k s|
 d k r| | |  d } qn  | j  |  j  }	 qrW| j | j  | r:| | |  n  | | k ret d | | f t j   n  | j   } | j | j  | j d k r| j d  | j d  | j d  f S| j d k rt j } n	 t j } t d | j | j f |   d S(   s   
        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.
        i   i    iR:   s   %d-%di   s   Content-Ranges   Content-LengthR?   R.   R@   RA   RB   s<   File changed during upload: EOF at %d bytes of %d byte file.i   t   etags   x-goog-generations   x-goog-metagenerationi  i  i  s1   Got response code %d while attempting upload (%s)N(   i  i  i  (   Rd   t   BUFFER_SIZEt   copyR=   RS   R   t   build_base_http_requestR0   R   R-   t
   putrequestR.   t	   putheadert
   endheaderst   set_debuglevelt   sendt	   digesterst   updateR4   RU   R   R   R%   t   getresponseRI   RL   Re   t   reason(   R   RD   t	   http_connt   fpRE   t   total_bytes_uploadedt   cbt   num_cbRA   t   buft   cb_countt   iRF   t   range_headert   http_requestRh   t   algRV   t   disposition(    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _upload_file_bytes<  sv    
			
	

	c      	   C   sd  |  j  \ } } | j j }	 |  j rGy |  j |	 |  \ } } | |  _ | r d GH| j d  | d }
 x{ |
 r | j t | j	 |
   } | s t
 d t j   n  x% |  j D] } |  j | j |  q W|
 t |  8}
 qm Wn  |	 j d k r d GHn  WqWt
 k
 rC} |	 j d k r0d | j GHn  |  j | |  qWXn |  j | |  |  j d	 k rr| |  _ n  | d } | | k  r| j |  n  | j j }	 |	 j |  j |	 j |	 j  } | j |	 j  z y& |  j |	 | | | | | | |  SWnS t
 t j f k
 rP|  j |	 |  } | j d k rJt
 d t j   qQ  n XWd	 | j    Xd	 S(
   s   
        Attempts a resumable upload.

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

        Raises ResumableUploadException if any problems occur.
        s-   Catching up hash digest(s) for resumed uploadi    i   s   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.s   Resuming transfer.s   Unable to resume transfer (%s).i  s   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 attemptsN(!   RR   R_   R`   R   R[   R   t   seekRd   t   mint
   BufferSizeR   R   RJ   Rt   Ru   R4   RU   t   messageRj   R   R   t   new_http_connectionR-   t   portt	   is_secureRr   R   t   sockett   errorRG   RI   R%   R   (   R   Rf   Ry   RE   RA   R{   R|   RY   RZ   RD   t   bytes_to_got   chunkR   R   Rz   Rx   RV   (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _attempt_resumable_upload  s^    			
	
 	c         C   sl   | j  j j d k r d GHn  | j | j d  k rh | j   | j   | j   t d t	 j
   n  d S(   s;  
        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.
        i   s   Checking md5 against etag.s   "'s`   File changed during upload: md5 signature doesn't match etag (incorrect uploaded object deleted)N(   R_   R`   RU   R    R   t	   open_readR   t   deleteR   R   R%   (   R   Rf   Rk   (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   _check_final_md5  s    


c         C   s   | j  t j k r3 | d k r- d | j GHn    nX | j  t j k rp | d k r` d | j GHn  |  j     n | d k r d | j GHn  d  S(   Ni   sW   Caught non-retryable ResumableUploadException (%s); aborting but retaining tracker filesV   Caught non-retryable ResumableUploadException (%s); aborting and removing tracker files1   Caught ResumableUploadException (%s) - will retry(   R   R   t   ABORT_CUR_PROCESSR   R%   R9   (   R   R   RU   (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt!   handle_resumable_upload_exception  s    
c         C   s   |  j  | k r d |  _ n$ |  j d 7_ | r? |  j |  _ n  |  j |  j k rf t d t j   n  t j   d |  j } | d k r d |  j | f GHn  t	 j
 |  d  S(   Ni    i   sa   Too many resumable upload attempts failed without progress. You might try this upload again lateri   sZ   Got retryable failure (%d progress-less in a row).
Sleeping %3.1f seconds before re-trying(   R   t   progress_less_iterationst   digesters_before_attemptRt   R
   R   R   R   t   randomt   timet   sleep(   R   t   server_had_bytes_before_attemptt   roll_back_md5RU   t   sleep_time_secs(    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   track_progress_less_iterations   s    i
   c            s  | s i  } n  d } | | k r; | | d k r; | | =n  t | d <t | t  rf | j   j } n, | j d t j  | j	   } | j d  | j
 j j }	   d k r i t d 6  n  t   f d     p i  D   _  j d k rt j d d d   _ n  d  _ xut r j }
 t  f d	    j D   _ y  j | | | | | |  \ }  _  _ x+  j D]  }  j | j   | j | <qW j    j | |   j | _ |	 d
 k rd GHn  d SWn  j k
 rS} |	 d
 k rd | j   GHn  t | t   rv| j! t! j" k rv| j
 j j j#   qvn# t$ k
 ru}  j% | |	  n X j& |
 t |	  qWd S(   s0  
        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.
        s   Content-Types
   User-Agenti    R    c         3   s"   |  ] } |   |   f Vq d  S(   N(    (   t   .0R   (   t	   hash_algs(    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pys	   <genexpr>y  s    t   BotoR
   i   c         3   s(   |  ] } |   j  | j   f Vq d  S(   N(   Rt   Rm   (   R   R   (   R   (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pys	   <genexpr>  s   i   s   Resumable upload complete.Ns   Caught exception (%s)('   R   R   t
   isinstanceR   t   getkeyt   sizeR   R    t   SEEK_ENDt   tellR_   R`   RU   R    t   dictRt   R
   R   t   getintR   RQ   R   R   R   t
   generationt   metagenerationt   digestt   local_hashesR9   R   t   RETRYABLE_EXCEPTIONSt   __repr__R   R   t   EPIPER   R   R   R   (   R   Rf   Ry   RA   R{   R|   R   t   CTRE   RU   R   Rk   R   R   (    (   R   R   s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt	   send_file;  sX    %	

"			
!	(   i    iN(   t   __name__t
   __module__Rl   t   httplibt   HTTPExceptionR   R   R   t   gaierrorR   RR   R   R   R   R&   R   R2   R6   R9   R=   RG   R[   Rj   R   R   R   R   RQ   R   R   (    (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyR   6   s*   								4:	^	^		(   R   R   R    R   RM   R   R   R)   t   hashlibR    t   botoR   R   t   boto.connectionR   t   boto.exceptionR   R   R   t   boto.s3.keyfileR   t   objectR   (    (    (    s?   lib/python2.7/site-packages/boto/gs/resumable_upload_handler.pyt   <module>   s   