#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
**downloadManager.py**
**Platform:**
Windows, Linux, Mac Os X.
**Description:**
This module defines the :class:`DownloadManager` class.
**Others:**
"""
#**********************************************************************************************************************
#*** External imports.
#**********************************************************************************************************************
import os
from PyQt4.QtCore import QFile
from PyQt4.QtCore import QIODevice
from PyQt4.QtCore import QUrl
from PyQt4.QtCore import pyqtSignal
from PyQt4.QtGui import QPixmap
from PyQt4.QtNetwork import QNetworkRequest
#**********************************************************************************************************************
#*** Internal imports.
#**********************************************************************************************************************
import foundations.common
import foundations.exceptions
import foundations.strings
import foundations.ui.common
import foundations.verbose
import umbra.ui.common
#**********************************************************************************************************************
#*** Module attributes.
#**********************************************************************************************************************
__author__ = "Thomas Mansencal"
__copyright__ = "Copyright (C) 2008 - 2012 - Thomas Mansencal"
__license__ = "GPL V3.0 - http://www.gnu.org/licenses/"
__maintainer__ = "Thomas Mansencal"
__email__ = "thomas.mansencal@gmail.com"
__status__ = "Production"
__all__ = ["LOGGER", "UI_FILE", "DownloadManager"]
LOGGER = foundations.verbose.installLogger()
UI_FILE = os.path.join(os.path.dirname(__file__), "ui", "Download_Manager.ui")
#**********************************************************************************************************************
#*** Module classes and definitions.
#**********************************************************************************************************************
[docs]class DownloadManager(foundations.ui.common.QWidgetFactory(uiFile=UI_FILE)):
"""
| This class defines the Application download manager.
| Once initialized with a `QNetworkAccessManager <http://doc.qt.nokia.com/qnetworkaccessmanager.html>`_ instance,
a download directory and a list of requests ( List of online resources / files ),
this class can proceed of the download of those requests using the :meth:`DownloadManager.startDownload` method.
"""
# Custom signals definitions.
downloadFinished = pyqtSignal()
"""
This signal is emited by the :class:`DownloadManager` class when a download is finished. ( pyqtSignal )
"""
def __init__(self, parent, networkAccessManager, downloadDirectory, requests=None, *args, **kwargs):
"""
.. Sphinx: Statements updated for auto-documentation purpose.
:param parent: Object parent. ( QObject )
:param networkAccessManager: Network access manager. ( QNetworkAccessManager )
:param downloadDirectory: Download directory. ( String )
:param requests: Download requests. ( List )
:param \*args: Arguments. ( \* )
:param \*\*kwargs: Keywords arguments. ( \*\* )
"""
LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
super(DownloadManager, self).__init__(parent, *args, **kwargs)
# --- Setting class attributes. ---
self.__container = parent
self.__networkAccessManager = networkAccessManager
self.__downloadDirectory = downloadDirectory
self.__uiResourcesDirectory = "resources/"
self.__uiResourcesDirectory = os.path.join(os.path.dirname(__file__), self.__uiResourcesDirectory)
self.__uiLogoImage = "sIBL_GUI_Small_Logo.png"
self.__requests = None
self.requests = requests
self.__downloads = {}
self.__currentRequest = None
self.__currentFile = None
self.__currentFilePath = None
# Helper attribute for QNetwork reply crash.
self.__downloadStatus = None
DownloadManager.__initializeUi(self)
#******************************************************************************************************************
#*** Attributes properties.
#******************************************************************************************************************
@property
def container(self):
"""
This method is the property for **self.__container** attribute.
:return: self.__container. ( QObject )
"""
return self.__container
@container.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def container(self, value):
"""
This method is the setter method for **self.__container** attribute.
:param value: Attribute value. ( QObject )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "container"))
@container.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def container(self):
"""
This method is the deleter method for **self.__container** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "container"))
@property
def networkAccessManager(self):
"""
This method is the property for **self.__networkAccessManager** attribute.
:return: self.__networkAccessManager. ( QNetworkAccessManager )
"""
return self.__networkAccessManager
@networkAccessManager.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def networkAccessManager(self, value):
"""
This method is the setter method for **self.__networkAccessManager** attribute.
:param value: Attribute value. ( QNetworkAccessManager )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "networkAccessManager"))
@networkAccessManager.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def networkAccessManager(self):
"""
This method is the deleter method for **self.__networkAccessManager** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "networkAccessManager"))
@property
def downloadDirectory(self):
"""
This method is the property for **self.__downloadDirectory** attribute.
:return: self.__downloadDirectory. ( String )
"""
return self.__downloadDirectory
@downloadDirectory.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def downloadDirectory(self, value):
"""
This method is the setter method for **self.__downloadDirectory** attribute.
:param value: Attribute value. ( String )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "downloadDirectory"))
@downloadDirectory.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def downloadDirectory(self):
"""
This method is the deleter method for **self.__downloadDirectory** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "downloadDirectory"))
@property
def uiResourcesDirectory(self):
"""
This method is the property for **self.__uiResourcesDirectory** attribute.
:return: self.__uiResourcesDirectory. ( String )
"""
return self.__uiResourcesDirectory
@uiResourcesDirectory.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def uiResourcesDirectory(self, value):
"""
This method is the setter method for **self.__uiResourcesDirectory** attribute.
:param value: Attribute value. ( String )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiResourcesDirectory"))
@uiResourcesDirectory.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def uiResourcesDirectory(self):
"""
This method is the deleter method for **self.__uiResourcesDirectory** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiResourcesDirectory"))
@property
def uiLogoImage(self):
"""
This method is the property for **self.__uiLogoImage** attribute.
:return: self.__uiLogoImage. ( String )
"""
return self.__uiLogoImage
@uiLogoImage.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def uiLogoImage(self, value):
"""
This method is the setter method for **self.__uiLogoImage** attribute.
:param value: Attribute value. ( String )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiLogoImage"))
@uiLogoImage.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def uiLogoImage(self):
"""
This method is the deleter method for **self.__uiLogoImage** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiLogoImage"))
@property
def requests(self):
"""
This method is the property for **self.__requests** attribute.
:return: self.__requests. ( List )
"""
return self.__requests
@requests.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(AssertionError)
def requests(self, value):
"""
This method is the setter method for **self.__requests** attribute.
:param value: Attribute value. ( List )
"""
if value is not None:
assert type(value) is list, "'{0}' attribute: '{1}' type is not 'list'!".format("requests", value)
for element in value:
assert type(element) in (str, unicode), "'{0}' attribute: '{1}' type is not 'str' or 'unicode'!".format(
"requests", element)
self.__requests = value
@requests.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def requests(self):
"""
This method is the deleter method for **self.__requests** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "requests"))
@property
def downloads(self):
"""
This method is the property for **self.__downloads** attribute.
:return: self.__downloads. ( Dictionary )
"""
return self.__downloads
@downloads.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def downloads(self, value):
"""
This method is the setter method for **self.__downloads** attribute.
:param value: Attribute value. ( Dictionary )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "downloads"))
@downloads.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def downloads(self):
"""
This method is the deleter method for **self.__downloads** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "downloads"))
@property
def currentRequest(self):
"""
This method is the property for **self.__currentRequest** attribute.
:return: self.__currentRequest. ( QNetworkReply )
"""
return self.__currentRequest
@currentRequest.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def currentRequest(self, value):
"""
This method is the setter method for **self.__currentRequest** attribute.
:param value: Attribute value. ( QNetworkReply )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "currentRequest"))
@currentRequest.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def currentRequest(self):
"""
This method is the deleter method for **self.__currentRequest** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "currentRequest"))
@property
def currentFile(self):
"""
This method is the property for **self.__currentFile** attribute.
:return: self.__currentFile. ( QFile )
"""
return self.__currentFile
@currentFile.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def currentFile(self, value):
"""
This method is the setter method for **self.__currentFile** attribute.
:param value: Attribute value. ( QFile )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "currentFile"))
@currentFile.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def currentFile(self):
"""
This method is the deleter method for **self.__currentFile** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "currentFile"))
@property
def currentFilePath(self):
"""
This method is the property for **self.__currentFilePath** attribute.
:return: self.__currentFilePath. ( String )
"""
return self.__currentFilePath
@currentFilePath.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def currentFilePath(self, value):
"""
This method is the setter method for **self.__currentFilePath** attribute.
:param value: Attribute value. ( String )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "currentFilePath"))
@currentFilePath.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def currentFilePath(self):
"""
This method is the deleter method for **self.__currentFilePath** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "currentFilePath"))
@property
def downloadStatus(self):
"""
This method is the property for **self.__downloadStatus** attribute.
:return: self.__downloadStatus. ( QObject )
"""
return self.__downloadStatus
@downloadStatus.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def downloadStatus(self, value):
"""
This method is the setter method for **self.__downloadStatus** attribute.
:param value: Attribute value. ( QObject )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "downloadStatus"))
@downloadStatus.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def downloadStatus(self):
"""
This method is the deleter method for **self.__downloadStatus** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "downloadStatus"))
#******************************************************************************************************************
#*** Class methods.
#******************************************************************************************************************
[docs] def closeEvent(self, event):
"""
This method reimplements the :meth:`QWidget.closeEvent` method.
:param event: QEvent. ( QEvent )
"""
self.__downloadStatus or self.abortDownload()
super(DownloadManager, self).closeEvent(event)
def __initializeUi(self):
"""
This method initializes the Widget ui.
"""
umbra.ui.common.setWindowDefaultIcon(self)
self.Download_progressBar.setValue(0)
self.Download_progressBar.hide()
self.Logo_label.setPixmap(QPixmap(os.path.join(self.__uiResourcesDirectory, self.__uiLogoImage)))
# Signals / Slots.
self.Cancel_Close_pushButton.clicked.connect(self.__Cancel_Close_pushButton__clicked)
def __Cancel_Close_pushButton__clicked(self, checked):
"""
This method is triggered when **Cancel_Close_pushButton** Widget is clicked.
:param checked: Checked state. ( Boolean )
"""
self.close()
def __downloadNext(self):
"""
This method downloads the next request.
"""
if not self.__requests:
return
self.Download_progressBar.show()
self.__currentRequest = self.__networkAccessManager.get(QNetworkRequest(QUrl(self.__requests.pop())))
self.__currentFilePath = os.path.join(self.__downloadDirectory,
os.path.basename(foundations.strings.encode(self.__currentRequest.url().path())))
if foundations.common.pathExists(self.__currentFilePath):
LOGGER.info("{0} | Removing '{1}' local file from previous online update!".format(
self.__class__.__name__, os.path.basename(self.__currentFilePath)))
os.remove(self.__currentFilePath)
self.__currentFile = QFile(self.__currentFilePath)
if not self.__currentFile.open(QIODevice.WriteOnly):
LOGGER.warning("!> Error occured while writing '{0}' file to disk, proceeding to next download!".format(
self.__currentFilePath))
self.__downloadNext()
return
# Signals / Slots.
self.__currentRequest.downloadProgress.connect(self.__downloadProgress)
self.__currentRequest.finished.connect(self.__downloadComplete)
self.__currentRequest.readyRead.connect(self.__requestReady)
def __downloadProgress(self, bytesReceived, bytesTotal):
"""
This method updates the download progress.
:param bytesReceived: Bytes received. ( Integer )
:param bytesTotal: Bytes total. ( Integer )
"""
LOGGER.debug("> Updating download progress: '{0}' bytes received, '{1}' bytes total.".format(bytesReceived,
bytesTotal))
self.Current_File_label.setText("Downloading: '{0}'.".format(
os.path.basename(foundations.strings.encode(self.__currentRequest.url().path()))))
self.Download_progressBar.setRange(0, bytesTotal)
self.Download_progressBar.setValue(bytesReceived)
def __requestReady(self):
"""
This method is triggered when the request is ready to write.
"""
LOGGER.debug("> Updating '{0}' file content.".format(self.__currentFile))
self.__currentFile.write(self.__currentRequest.readAll())
def __downloadComplete(self):
"""
This method is triggered when the request download is complete.
"""
LOGGER.debug("> '{0}' download complete.".format(self.__currentFile))
self.__currentFile.close()
self.__downloads[self.__currentFilePath] = (self.__currentRequest.error(), self.__currentRequest.url().toString())
self.Current_File_label.setText("'{0}' downloading done!".format(os.path.basename(self.__currentFilePath)))
self.Download_progressBar.hide()
self.__currentRequest.deleteLater()
if self.__requests:
LOGGER.debug("> Proceeding to next download request.")
self.__downloadNext()
else:
self.__downloadStatus = True
self.Current_File_label.setText("Downloads complete!")
self.Cancel_Close_pushButton.setText("Close")
self.downloadFinished.emit()
[docs] def startDownload(self):
"""
This method triggers the download.
:return: Method success. ( Boolean )
"""
self.__downloadStatus = False
self.__downloadNext()
return True
[docs] def abortDownload(self):
"""
This method aborts the current download.
:return: Method success. ( Boolean )
"""
self.__currentRequest.abort()
self.__currentRequest.deleteLater()
return True