#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
**downloadManager.py**
**Platform:**
Windows, Linux, Mac Os X.
**Description:**
Defines the :class:`DownloadManager` class.
**Others:**
"""
#**********************************************************************************************************************
#*** Future imports.
#**********************************************************************************************************************
from __future__ import unicode_literals
#**********************************************************************************************************************
#*** 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 - 2014 - 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)):
"""
| 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):
"""
Initializes the class.
:param parent: Object parent.
:type parent: QObject
:param networkAccessManager: Network access manager.
:type networkAccessManager: QNetworkAccessManager
:param downloadDirectory: Download directory.
:type downloadDirectory: unicode
:param requests: Download requests.
:type requests: list
:param \*args: Arguments.
:type \*args: \*
:param \*\*kwargs: Keywords arguments.
:type \*\*kwargs: \*\*
"""
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):
"""
Property for **self.__container** attribute.
:return: self.__container.
:rtype: QObject
"""
return self.__container
@container.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def container(self, value):
"""
Setter for **self.__container** attribute.
:param value: Attribute value.
:type value: QObject
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "container"))
@container.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def container(self):
"""
Deleter for **self.__container** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "container"))
@property
def networkAccessManager(self):
"""
Property for **self.__networkAccessManager** attribute.
:return: self.__networkAccessManager.
:rtype: QNetworkAccessManager
"""
return self.__networkAccessManager
@networkAccessManager.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def networkAccessManager(self, value):
"""
Setter for **self.__networkAccessManager** attribute.
:param value: Attribute value.
:type value: QNetworkAccessManager
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "networkAccessManager"))
@networkAccessManager.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def networkAccessManager(self):
"""
Deleter for **self.__networkAccessManager** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "networkAccessManager"))
@property
def downloadDirectory(self):
"""
Property for **self.__downloadDirectory** attribute.
:return: self.__downloadDirectory.
:rtype: unicode
"""
return self.__downloadDirectory
@downloadDirectory.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def downloadDirectory(self, value):
"""
Setter for **self.__downloadDirectory** attribute.
:param value: Attribute value.
:type value: unicode
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "downloadDirectory"))
@downloadDirectory.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def downloadDirectory(self):
"""
Deleter for **self.__downloadDirectory** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "downloadDirectory"))
@property
def uiResourcesDirectory(self):
"""
Property for **self.__uiResourcesDirectory** attribute.
:return: self.__uiResourcesDirectory.
:rtype: unicode
"""
return self.__uiResourcesDirectory
@uiResourcesDirectory.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def uiResourcesDirectory(self, value):
"""
Setter for **self.__uiResourcesDirectory** attribute.
:param value: Attribute value.
:type value: unicode
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiResourcesDirectory"))
@uiResourcesDirectory.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def uiResourcesDirectory(self):
"""
Deleter for **self.__uiResourcesDirectory** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiResourcesDirectory"))
@property
def uiLogoImage(self):
"""
Property for **self.__uiLogoImage** attribute.
:return: self.__uiLogoImage.
:rtype: unicode
"""
return self.__uiLogoImage
@uiLogoImage.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def uiLogoImage(self, value):
"""
Setter for **self.__uiLogoImage** attribute.
:param value: Attribute value.
:type value: unicode
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiLogoImage"))
@uiLogoImage.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def uiLogoImage(self):
"""
Deleter for **self.__uiLogoImage** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiLogoImage"))
@property
def requests(self):
"""
Property for **self.__requests** attribute.
:return: self.__requests.
:rtype: list
"""
return self.__requests
@requests.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(AssertionError)
def requests(self, value):
"""
Setter for **self.__requests** attribute.
:param value: Attribute value.
:type 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) is unicode, "'{0}' attribute: '{1}' type is not 'unicode'!".format(
"requests", element)
self.__requests = value
@requests.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def requests(self):
"""
Deleter for **self.__requests** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "requests"))
@property
def downloads(self):
"""
Property for **self.__downloads** attribute.
:return: self.__downloads.
:rtype: dict
"""
return self.__downloads
@downloads.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def downloads(self, value):
"""
Setter for **self.__downloads** attribute.
:param value: Attribute value.
:type value: dict
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "downloads"))
@downloads.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def downloads(self):
"""
Deleter for **self.__downloads** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "downloads"))
@property
def currentRequest(self):
"""
Property for **self.__currentRequest** attribute.
:return: self.__currentRequest.
:rtype: QNetworkReply
"""
return self.__currentRequest
@currentRequest.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def currentRequest(self, value):
"""
Setter for **self.__currentRequest** attribute.
:param value: Attribute value.
:type value: QNetworkReply
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "currentRequest"))
@currentRequest.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def currentRequest(self):
"""
Deleter for **self.__currentRequest** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "currentRequest"))
@property
def currentFile(self):
"""
Property for **self.__currentFile** attribute.
:return: self.__currentFile.
:rtype: QFile
"""
return self.__currentFile
@currentFile.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def currentFile(self, value):
"""
Setter for **self.__currentFile** attribute.
:param value: Attribute value.
:type value: QFile
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "currentFile"))
@currentFile.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def currentFile(self):
"""
Deleter for **self.__currentFile** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "currentFile"))
@property
def currentFilePath(self):
"""
Property for **self.__currentFilePath** attribute.
:return: self.__currentFilePath.
:rtype: unicode
"""
return self.__currentFilePath
@currentFilePath.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def currentFilePath(self, value):
"""
Setter for **self.__currentFilePath** attribute.
:param value: Attribute value.
:type value: unicode
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "currentFilePath"))
@currentFilePath.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def currentFilePath(self):
"""
Deleter for **self.__currentFilePath** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "currentFilePath"))
@property
def downloadStatus(self):
"""
Property for **self.__downloadStatus** attribute.
:return: self.__downloadStatus.
:rtype: QObject
"""
return self.__downloadStatus
@downloadStatus.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def downloadStatus(self, value):
"""
Setter for **self.__downloadStatus** attribute.
:param value: Attribute value.
:type value: QObject
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "downloadStatus"))
@downloadStatus.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def downloadStatus(self):
"""
Deleter 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):
"""
Reimplements the :meth:`QWidget.closeEvent` method.
:param event: QEvent.
:type event: QEvent
"""
self.__downloadStatus or self.abortDownload()
super(DownloadManager, self).closeEvent(event)
def __initializeUi(self):
"""
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):
"""
Defines the slot triggered by **Cancel_Close_pushButton** Widget when clicked.
:param checked: Checked state.
:type checked: bool
"""
self.close()
def __downloadNext(self):
"""
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.toString(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):
"""
Updates the download progress.
:param bytesReceived: Bytes received.
:type bytesReceived: int
:param bytesTotal: Bytes total.
:type bytesTotal: int
"""
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.toString(self.__currentRequest.url().path()))))
self.Download_progressBar.setRange(0, bytesTotal)
self.Download_progressBar.setValue(bytesReceived)
def __requestReady(self):
"""
Defines the slot triggered by the request when ready.
"""
LOGGER.debug("> Updating '{0}' file content.".format(self.__currentFile))
self.__currentFile.write(self.__currentRequest.readAll())
def __downloadComplete(self):
"""
Defines the slot triggered by the request when download 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):
"""
Triggers the download.
:return: Method success.
:rtype: bool
"""
self.__downloadStatus = False
self.__downloadNext()
return True
[docs] def abortDownload(self):
"""
Aborts the current download.
:return: Method success.
:rtype: bool
"""
self.__currentRequest.abort()
self.__currentRequest.deleteLater()
return True