3
]pe                 @   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	Z	d dl
Z
d dlZd dlmZ d dljZd dlmZ d dlZddlmZ ddlmZ ddlmZ dd	gZdZd
Zd
ZdjeeeZG dd deZ G dd deZ!G dd dZ"dd Z#dS )    N)StringIO)c_int   )utils)ControlThread)downloadSmartDLr      z{}.{}.{}c               @   s(   e Zd ZdZdd Zdd Zdd ZdS )	HashFailedExceptionzRaised when hash check fails.c             C   s   || _ || _|| _d S )N)filenamecalculated_hashneeded_hash)selffnZ	calc_hashr    r   D/home/ghaith/Applications/sentSatProg_v.0.1.3/pySmartDL/pySmartDL.py__init__   s    zHashFailedException.__init__c             C   s   dj | j| j| jS )Nz,HashFailedException({}, got {}, expected {}))formatr   r   r   )r   r   r   r   __str__   s    zHashFailedException.__str__c             C   s   dj | j| j| jS )Nz-<HashFailedException {}, got {}, expected {}>)r   r   r   r   )r   r   r   r   __repr__!   s    zHashFailedException.__repr__N)__name__
__module____qualname____doc__r   r   r   r   r   r   r   r
      s   r
   c               @   s(   e Zd ZdZdd Zdd Zdd ZdS )	CanceledExceptionz Raised when the job is canceled.c             C   s   d S )Nr   )r   r   r   r   r   &   s    zCanceledException.__init__c             C   s   dS )Nr   r   )r   r   r   r   r   (   s    zCanceledException.__str__c             C   s   dS )Nz<CanceledException>r   )r   r   r   r   r   *   s    zCanceledException.__repr__N)r   r   r   r   r   r   r   r   r   r   r   r   $   s   r   c               @   s   e Zd ZdZdJddZd	d
 Zdd Zdd Zdd Zdd Z	dKddZ
dd ZdLddZdMddZdNddZdOddZd d! ZdQd%d&Zd'd( Zd)d* Zd+d, Zd-d. ZdRd/d0Zd1d2 Zd3d4 Zd5d6 Zd7d8 Zd9d: Zd;d< ZdSd=d>ZdTd?d@ZdUdAdBZdWdDdEZ dFdG Z!dHdI Z"dS )Xr   am  
    The main SmartDL class
    
    :param urls: Download url. It is possible to pass unsafe and unicode characters. You can also pass a list of urls, and those will be used as mirrors.
    :type urls: string or list of strings
    :param dest: Destination path. Default is `%TEMP%/pySmartDL/`.
    :type dest: string
    :param progress_bar: If True, prints a progress bar to the `stdout stream <http://docs.python.org/2/library/sys.html#sys.stdout>`_. Default is `True`.
    :type progress_bar: bool
	:param fix_urls: If true, attempts to fix urls with unsafe characters.
	:type fix_urls: bool
	:param threads: Number of threads to use.
	:type threads: int
    :param timeout: Timeout for network operations, in seconds. Default is 5.
	:type timeout: int
    :param logger: An optional logger.
    :type logger: `logging.Logger` instance
    :param connect_default_logger: If true, connects a default logger to the class.
    :type connect_default_logger: bool
    :param request_args: Arguments to be passed to a new urllib.request.Request instance in dictionary form. See `urllib.request docs <https://docs.python.org/3/library/urllib.request.html#urllib.request.Request>`_ for options. 
    :type request_args: dict
    :rtype: `SmartDL` instance
    
    .. NOTE::
            The provided dest may be a folder or a full path name (including filename). The workflow is:
            
            * If the path exists, and it's an existing folder, the file will be downloaded to there with the original filename.
            * If the past does not exist, it will create the folders, if needed, and refer to the last section of the path as the filename.
            * If you want to download to folder that does not exist at the moment, and want the module to fill in the filename, make sure the path ends with `os.sep`.
            * If no path is provided, `%TEMP%/pySmartDL/` will be used.
    NT      Fc             C   s  |r|| _ n|rtj | _ n
tj | _ |	rDd|	kr<t |	d< |	| _ndt i| _d| jd krld| jd d< |r|r| j|| t|tr|gn|| _	|rdd | j	D | _	| j	j
d| _| j jdj| j tjjtjjtjj| jj}|ptjjtj d|| _| jd tjkrntjj| jd d r`tjj| jd d r`tj| jd d  |  j|7  _tjj| jrtjj| j|| _|| _|| _|| _ d	| _!d
| _"d| _#d| _$t%j&t'd| _(i | _)d| _*d| _+d| _,d| _-d| _.g | _/|| _0|
| _1d | _2d | _3tjjtjj4| jsR| j jdjtjj4| j tj5tjj4| j tj6| j| j| j ds|| j j7d d	| _tjj| jr| j j7dj| j tjjtjj4| js| j j7djtjj4| j tj5tjj4| j | j jdj| j tj8| j| _9d S )Nheadersz
User-AgentzNMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0c             S   s   g | ]}t j|qS r   )r   Zurl_fix).0xr   r   r   
<listcomp>c   s    z$SmartDL.__init__.<locals>.<listcomp>r   zUsing url "{}"	pySmartDLr      i   r   readyFTz'Folder "{}" does not exist. Creating...)r   timeoutz=Server does not support HTTPRange. threads_count is set to 1.z?Destination "{}" already exists. Existing file will be removed.z-Directory "{}" does not exist. Creating it...z&Creating a ThreadPool of {} thread(s).r%   r%   r%   i   i    ):loggerr   Zcreate_debugging_loggerZDummyLoggerdictrequestArgsadd_basic_authentication
isinstancestrmirrorspopurlinfor   urllibparseunquoteospathbasenameurlparsejointempfile
gettempdirdestsepexistsisfileunlinkisdirprogress_barthreads_countr$   current_attempattemps_limitminChunkFilefilesizemultiprocessingValuer   
shared_varthread_shared_cmdsstatusverify_hash_killed_failed_start_func_blockingerrorshash_algorithm	hash_codepost_threadpool_threadcontrol_threaddirnamemakedirsZauto_detect_HTTPRange_supportwarningZManagedThreadPoolExecutorpool)r   Zurlsr:   r@   Zfix_urlsthreadsr$   r&   Zconnect_default_loggerZrequest_argsZhash_checksumZ	hash_algousernamepasswordr   r   r   r   r   N   sv    

 0zSmartDL.__init__c             C   s   dj | j| jS )NzSmartDL(r"{}", dest=r"{}"))r   r.   r:   )r   r   r   r   r      s    zSmartDL.__str__c             C   s   dj | jS )Nz<SmartDL {}>)r   r.   )r   r   r   r   r      s    zSmartDL.__repr__c             C   s2   dj ||}tj|jd}d| | jd d< dS )z
        Uses HTTP Basic Access authentication for the connection.
        
        :param username: Username.
        :type username: string
        :param password: Password.
        :type password: string
        z{}:{}zutf-8s   Basic r   AuthorizationN)r   base64standard_b64encodeencoder(   )r   rY   rZ   Zauth_stringZbase64stringr   r   r   r)      s    	z SmartDL.add_basic_authenticationc             C   s   d| _ || _|| _dS )a  
        Adds hash verification to the download.
        
        If hash is not correct, will try different mirrors. If all mirrors aren't
        passing hash verification, `HashFailedException` Exception will be raised.
        
        .. NOTE::
            If downloaded file already exist on the destination, and hash matches, pySmartDL will not download it again.
            
        .. WARNING::
            The hashing algorithm must be supported on your system, as documented at `hashlib documentation page <http://docs.python.org/3/library/hashlib.html>`_.
        
        :param algorithm: Hashing algorithm.
        :type algorithm: string
        :param hash: Hash code.
        :type hash: string
        TN)rK   rP   rQ   )r   	algorithmhashr   r   r   add_hash_verification   s    zSmartDL.add_hash_verificationc             C   s   dddg}t jj| j}t jj| j}| jjd x|D ]}yd||f }tjj	|f| j
}tjj|}|j jd}|j  xT|D ]L}	|j |	j kr| jjd|  |jd}
|	jd	d
 }| j|
| dS qW W q8 tjjk
r   w8Y q8X q8W dS )a4  
        Will attempt to fetch UNIX hash sums files (`SHA256SUMS`, `SHA1SUMS` or `MD5SUMS` files in
        the same url directory).
        
        Calls `self.add_hash_verification` if successful. Returns if a matching hash was found.
        
        :rtype: bool
        
        *New in 1.2.1*
        Z
SHA256SUMSZSHA1SUMSZMD5SUMSzLooking for SUMS files...z%s/%s
zFound a matching hash in %sZSUMS r   N)r3   r4   rT   r.   r5   r&   r/   r0   requestRequestr(   urlopenreadsplitcloselowerrstripra   error	HTTPError)r   Zdefault_sums_filenamesZfolderZorig_basenamer   Zsums_urlZsumsRequestobjdatalineZalgor`   r   r   r   fetch_hash_sums   s(    



zSmartDL.fetch_hash_sumsc       	   $      sV   j dkstdj j  jjd |dkr6 j}n| _ jr\ jjdjt j n jjd  jdk	rt	j
j jrtj j jj  jj kr jjd j  d _ dS  jjd	j j j tjj jf j}ytjj| jd
}W n tjjtjjtjfk
r } z jj|  jrz jjdjt|  jjd _ jjdj j  j | dS  jj!t|  jj| d _"d _  W Y dd}~X nX y2t#|j$d  _% jjdj j%tj& j% W n. t't(t)fk
r    jj!d d _%Y nX tj* j% j+ j,}|d d |d d  d }t|dkrv jjdjt|tj&| n jjdjtj&| d _ xVt-|D ]J\}} j.j/t0 j jd|   j|d |d  jd j1 j2 jd}qW t3j4t5 j. fddt6t|D  jg j% fd _7d j7_8 j7j   t9  _:|rR j;dd dS )a,  
        Starts the download task. Will raise `RuntimeError` if it's the object's already downloading.
        
        .. warning::
            If you're using the non-blocking mode, Exceptions won't be raised. In that case, call
            `isSuccessful()` after the task is finished, to make sure the download succeeded. Call
            `get_errors()` to get the the exceptions.
        
        :param blocking: If true, calling this function will block the thread until the download finished. Default is *True*.
        :type blocking: bool
        r#   z#cannot start (current status is {})z!Starting a new SmartDL operation.Nz"One URL and {} mirrors are loaded.zOne URL is loaded.zKDestination '%s' already exists, and the hash matches. No need to download.finishedzDownloading '{}' to '{}'...)r$   z{} Trying next mirror...r   zUsing url "{}"TzContent-LengthzContent-Length is {} ({}).z8Server did not send Content-Length. Filesize is unknown.r   z+Launching {} threads (downloads {}/thread).z"Launching 1 thread (downloads {}).downloadingz.%.3dFc                s   g | ]} j d |  qS )z.%.3d)r:   )r   i)r   r   r   r    8  s    z!SmartDL.start.<locals>.<listcomp>)targetargs)raise_exceptions)<rJ   RuntimeErrorr   r&   r/   rN   r,   lenrQ   r3   r4   r<   r:   r   get_file_hashrP   rj   r.   r0   rd   re   r(   rf   r$   rl   rm   URLErrorsocketrO   appendr+   r-   startrV   rM   intr   rE   sizeof_human
IndexErrorKeyError	TypeErrorZcalc_chunk_sizerA   rD   	enumeraterW   Zsubmitr   rH   rI   	threadingThreadpost_threadpool_actionsrangerR   daemonr   rS   wait)	r   blockingreqZurlObjerv   Zbytes_per_threadrt   argr   )r   r   r~      s    
"
" 

zSmartDL.startc             C   s$   | j j|d  | jj|d  d S )Nr   r   )rO   r}   r&   	exception)r   r   r   r   r   r   _exc_callbackE  s    zSmartDL._exc_callback c             C   sx   | j | jk r8|  j d7  _ d| _d| j_i | _| j  n<d}|rN|dj|7 }| jj	t
jj| jd|i t  d| _d S )Nr   r#   r   z"The maximum retry attempts reachedz ({})0T)rB   rC   rJ   rH   valuerI   r~   r   rO   r}   r0   rl   rm   r.   r   rM   )r   ZeStrsr   r   r   retryI  s    
 zSmartDL.retryc             C   sf   | j rP|r| jj| d| _d| j_| j jd| _| jj	dj
| j | j  nd| _| jj| d S )Nr#   r   zUsing url "{}"T)r,   rO   r}   rJ   rH   r   r-   r.   r&   r/   r   r~   rM   )r   r   r   r   r   try_next_mirrorX  s    
zSmartDL.try_next_mirrorc             C   s*   |r t j| jj }|r|S dS | jj S )a  
        Get estimated time of download completion, in seconds. Returns `0` if there is
        no enough data to calculate the estimated time (this will happen on the approx.
        first 5 seconds of each download).
        
        :param human: If true, returns a human-readable formatted string. Else, returns an int type number
        :type human: bool
        :rtype: int/string
        ZTBD)r   
time_humanrS   get_eta)r   humanr   r   r   r   r   e  s    
zSmartDL.get_etac             C   s$   |rdj tj| jj S | jj S )z
        Get current transfer speed in bytes per second.
        
        :param human: If true, returns a human-readable formatted string. Else, returns an int type number
        :type human: bool
        :rtype: int/string
        z{}/s)r   r   r   rS   	get_speed)r   r   r   r   r   r   t  s    zSmartDL.get_speedc             C   s2   | j s
dS | jj | j kr.d| jj  | j  S dS )z~
        Returns the current progress of the download, as a float between `0` and `1`.
        
        :rtype: float
        r   g      ?)rE   rS   get_dl_size)r   r   r   r   get_progress  s
    zSmartDL.get_progress-#   c             C   s   t j| j ||S )a[  
        Returns the current progress of the download as a string containing a progress bar.
        
        .. NOTE::
            That's an alias for pySmartDL.utils.progress_bar(obj.get_progress()).
        
        :param length: The length of the progress bar in chars. Default is 20.
        :type length: int
        :rtype: string
        )r   r@   r   )r   Z	look_feellengthr   r   r   get_progress_bar  s    zSmartDL.get_progress_barc             C   s(   | j dkrdS | j dkrdS | jj  S )zP
        Returns if the task is finished.
        
        :rtype: bool
        r#   Frr   T)rJ   rR   is_alive)r   r   r   r   
isFinished  s
    

zSmartDL.isFinishedc             C   sP   | j r
dS d}x8| jdkrF|d7 }tjd |dkrtdj| jqW | j S )a  
        Returns if the download is successfull. It may fail in the following scenarios:
        
        - Hash check is enabled and fails.
        - All mirrors are down.
        - Any local I/O problems (such as `no disk space available`).
        
        .. NOTE::
            Call `get_errors()` to get the exceptions, if any.
        
        Will raise `RuntimeError` if it's called when the download task is not finished yet.
        
        :rtype: bool
        Fr   rr   r   g?   z]The download task must be finished in order to see if it's successful. (current status is {}))rL   rJ   timesleeprx   r   rM   )r   nr   r   r   isSuccessful  s    
zSmartDL.isSuccessfulc             C   s   | j S )zo
        Get errors happened while downloading.
        
        :rtype: list of `Exception` instances
        )rO   )r   r   r   r   
get_errors  s    zSmartDL.get_errorsc             C   s   | j S )z
        Returns the current status of the task. Possible values: *ready*,
        *downloading*, *paused*, *combining*, *finished*.
        
        :rtype: string
        )rJ   )r   r   r   r   
get_status  s    zSmartDL.get_statusc             C   sR   | j dkrdS x| j s$tjd qW | jj  | jj  | jrN|rN| jd dS )z
        Blocks until the download is finished.
        
        :param raise_exceptions: If true, this function will raise exceptions. Default is *False*.
        :type raise_exceptions: bool
        r#   rr   Ng?r   )r#   rr   r%   )	rJ   r   r   r   rR   r7   rS   rM   rO   )r   rw   r   r   r   r     s    




zSmartDL.waitc             C   s   | j dkrd| jd< d| _dS )z%
        Stops the download.
        rs   r   stopTN)rJ   rI   rL   )r   r   r   r   r     s    

zSmartDL.stopc             C   s   | j dkrd| _ d| jd< dS )z&
        Pauses the download.
        rs   pausedr   pauseN)rJ   rI   )r   r   r   r   r     s    
zSmartDL.pausec             C   s   | j   dS )z<
        Continues the download. same as unpause().
        N)unpause)r   r   r   r   resume  s    zSmartDL.resumec             C   s&   | j dkr"d| jkr"d| _ | jd= dS )z;
        Continues the download. same as resume().
        r   r   rs   N)rJ   rI   )r   r   r   r   r     s    zSmartDL.unpausec             C   sT   | j dkr$|dkr| j  n| j  |dkr>|| j | jd< nd| jkrP| jd= dS )z
        Limits the download transfer speed.
        
        :param speed: Speed in bytes per download per second. Negative values will not limit the speed. Default is `-1`.
        :type speed: int
        rs   r   limitN)rJ   r   r   rA   rI   )r   Zspeedr   r   r   limit_speed  s    


zSmartDL.limit_speedc             C   s   | j S )z
        Get the destination path of the downloaded file. Needed when no
        destination is provided to the class, and exists on a temp folder.
        
        :rtype: string
        )r:   )r   r   r   r   get_dest  s    zSmartDL.get_destc             C   s(   | j s
dS |rtj| j j S | j j S )a*  
        Returns how much time did the download take, in seconds. Returns
        `-1` if the download task is not finished yet.

        :param human: If true, returns a human-readable formatted string. Else, returns an int type number
        :type human: bool
        :rtype: int/string
        r   )rS   r   r   get_dl_time)r   r   r   r   r   r     s
    	zSmartDL.get_dl_timec             C   s(   | j s
dS |rtj| j j S | j j S )z
        Get downloaded bytes counter in bytes.
        
        :param human: If true, returns a human-readable formatted string. Else, returns an int type number
        :type human: bool
        :rtype: int/string
        r   )rS   r   r   r   )r   r   r   r   r   r   +  s
    zSmartDL.get_dl_sizec             C   s(   | j s
dS |rtj| j j S | j j S )z
        Get total download size in bytes.
        
        :param human: If true, returns a human-readable formatted string. Else, returns an int type number
        :type human: bool
        :rtype: int/string
        r   )rS   r   r   get_final_filesize)r   r   r   r   r   r   9  s
    zSmartDL.get_final_filesizer   c          	   C   s\   | j dkrtd| j  |r dnd}t| j | }|dkrF|j|n|j }W dQ R X |S )a  
        Returns the downloaded data. Will raise `RuntimeError` if it's
        called when the download task is not finished yet.
        
        :param binary: If true, will read the data as binary. Else, will read it as text.
        :type binary: bool
        :param bytes: Number of bytes to read. Negative values will read until EOF. Default is `-1`.
        :type bytes: int
        :rtype: string
        rr   zTThe download task must be finished in order to read the data. (current status is %s)rbrr   N)rJ   rx   openr   rg   )r   binarybytesflagsfro   r   r   r   get_dataH  s    
$zSmartDL.get_datac             C   s   t j|| jddj S )a  
        Returns the downloaded data's hash. Will raise `RuntimeError` if it's
        called when the download task is not finished yet.
        
        :param algorithm: Hashing algorithm.
        :type algorithm: bool
        :rtype: string
        
        .. WARNING::
            The hashing algorithm must be supported on your system, as documented at `hashlib documentation page <http://docs.python.org/3/library/hashlib.html>`_.
        T)r   )hashlibnewr   	hexdigest)r   r_   r   r   r   get_data_hash[  s    zSmartDL.get_data_hashc             C   s   | j  }tj|S )a  
        Returns the JSON in the downloaded data. Will raise `RuntimeError` if it's
        called when the download task is not finished yet. Will raise `json.decoder.JSONDecodeError`
        if the downloaded data is not valid JSON.
        
        :rtype: dict
        )r   jsonloads)r   ro   r   r   r   get_jsoni  s    zSmartDL.get_json)NTTr   r   NFNNNNN)N)r   )N)F)Fr   r   )r   r   )F)F)F)Fr%   )Fr%   )#r   r   r   r   r   r   r   r)   ra   rq   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   -   s@   
F#
d




	




c             C   sZ  x| j  stjd qW |jr"dS | j rZx| j D ]}|jj| q4W |jt	| j  |j
rp|jjd dS |rt|d }tdd |d D }tj|| }|d| krd	j||||}|jj| |j| dS d
|_|jjd tj|  |jrV|d }	tj|j|	}
|
|jkr.|jjd n(|jjd |jttjj|	t|j dS )z=Run function after thread pool is done. Run this in a thread.g?NzTask had errors. Exiting...r   c             S   s   g | ]}t jj|qS r   )r3   r4   getsize)r   r   r   r   r   r      s    z+post_threadpool_actions.<locals>.<listcomp>r"   i   znDiff between downloaded files and expected filesizes is {}B (filesize: {}, expected_filesize: {}, {} threads).	combiningzCombining downloaded file.r   zHash verification succeeded.zHash verification failed.i   r%   )doner   r   rL   Zget_exceptionZget_exceptionsr&   r   r   r+   rM   rV   ry   summathfabsr   rJ   r/   r   Zcombine_filesrK   rz   rP   rQ   r   r
   r3   r4   r5   r`   )rW   rv   Zexpected_filesizeZ
SmartDLObjexcrX   Ztotal_filesizeZdiffZerrMsgZ	dest_pathZhash_r   r   r   r   t  s<    


r   )$r3   urllib.requestr0   urllib.errorurllib.parser   r   r   r8   r\   r   r|   ior   Zmultiprocessing.dummydummyrF   ctypesr   r   r   r   rS   r   r   __all__Z__version_mjaor__Z__version_minor__Z__version_micro__r   __version__	Exceptionr
   r   r   r   r   r   r   r   <module>   s8   
	    K