SF.net SVN: fclient:[902] trunk/fclient/fclient/impl
Status: Pre-Alpha
Brought to you by:
jurner
From: <jU...@us...> - 2008-08-11 18:14:31
|
Revision: 902 http://fclient.svn.sourceforge.net/fclient/?rev=902&view=rev Author: jUrner Date: 2008-08-11 18:14:30 +0000 (Mon, 11 Aug 2008) Log Message: ----------- isolating requests widget pt2 Modified Paths: -------------- trunk/fclient/fclient/impl/ViewDownloads.py Added Paths: ----------- trunk/fclient/fclient/impl/BaseRequestsWidget.py Added: trunk/fclient/fclient/impl/BaseRequestsWidget.py =================================================================== --- trunk/fclient/fclient/impl/BaseRequestsWidget.py (rev 0) +++ trunk/fclient/fclient/impl/BaseRequestsWidget.py 2008-08-11 18:14:30 UTC (rev 902) @@ -0,0 +1,523 @@ + +#************************************************************************************************************** +#TODO: +# +# x. prtty tricky to get dls right when the node or client may disconnect unexpectedly +# problem: dls that we send and that have not reached the node +# problem: what to do when the user wants to quit and we still have dls to push to the node +# +# solution would require keeping track of all requests +# a. keep identifiers of requests that reached the node (have to do it anyways) +# b. keep track of requests ahead of sending them to the node (feels not too good doubeling downloads.dat.gz) +# +# best idea seems to be to ignore the problem +# 1. wait till (if ever) freenet devels fdrop node keeping track of client requests +# +# 2. a box thrown to the user (x. do not show this message again) to inform him about pendings +# should be enough. maybe along with a separate widget or some separate color code for pendings +# +# x. performance +# start with a standard model and improve it over time. no need to set any hard +# limits. the user will find out by himself how many dls his machine can handle. +# have to be carefrul though when adding dls, like from a *.frdx to provide appropriate +# warnings +# x. it may take a while untill the final DataFound message arrives when a request is % completed. feedback would be nice +# x. DataFound for a request the file has been removed by the user. no idea what happens. have to test this +# x. when the node is about to start up, looks like persistents may arrive or not. check +# x. how to get early information about mimetype/size? maybe use FcpClient.getFileInfo() +# x. show/hide header izems +# x. sort by header +# x. indicate over all time / dl speed +# x. indicate status / remove items by status +# x. item properties +# x. how to handle inserting huge number of dls? +# idea: insert with lowest priority to get the node to know them, increase priority when a slot in +# MaxSimultaneousDls (if set) is free. atatch progressBar no sooner as priority > MinDlPriority +# x. how to handle huge numbers of dls. the node will flood us on startup with persistents. looks +# like the only way to control the flood is to have one connection/dl. maybe wait for freenet devels +# to realize that this is a serious problem... +# x. byte amount postfixes must be transllated ++ use Kib or Kb or let the user decide? +# x. sometimes groups of dls get not removed +#************************************************************************************************************** +from __future__ import absolute_import +if __name__ == '__main__': # see --> http://bugs.python.org/issue1510172 . works only current dir and below + import os; __path__ = [os.path.dirname(__file__)] + +import mimetypes +import os +from PyQt4 import QtCore, QtGui + +from . import config +from .lib import fcp2 +from .lib.fcp2.lib import pmstruct +from .lib.qt4ex import treewidgetwrap +from .lib import numbers + +from . import DlgDownloadKeyToDisk + +from .tpls.Ui_ViewRequestsWidgetTpl import Ui_ViewRequestsWidget +#********************************************************************************** +# +#********************************************************************************** +BLOCK_SIZE = 32768 # from CHKBlock.java + +#********************************************************************************** +# +#********************************************************************************** +class PersistentRequestData(pmstruct.PMStruct): + _fields_ = ( + ('ClientName', pmstruct.STRING), + ) + +#********************************************************************************** +# +#********************************************************************************** +class TreeItem(QtGui.QTreeWidgetItem): + + IndexName = 0 + IndexSize = 1 + IndexMimeType = 2 + IndexStatus = 3 + IndexProgress = 4 + IndexPriority = 5 + IndexElapsed = 6 + + ProgressBarName = 'downloadKey' + + StatusPending = 'pending' + StatusLoading = 'loading' + StatusComplete = 'complete' + StatusError = 'error' + StatusRemoved = 'removed' + + def __init__(self, fcpRequest, *params): + QtGui.QTreeWidgetItem.__init__(self, *params) + self.fcpRequest = fcpRequest + self.fcOldStatus = self.StatusPending + + def status(self): + if self.fcpRequest is None: + return self.StatusRemoved + elif self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Success: + return self.StatusComplete + elif self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Error: + return self.StatusError + elif self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Started: + return self.StatusLoading + else: + return self.StatusPending + +# exposes status property for stylesheets +class ProgressBar(QtGui.QProgressBar): + + def __init__(self, parent, item): + QtGui.QProgressBar.__init__(self, parent) + self.item = item + + def _get_status(self): + return self.item.status() + status= QtCore.pyqtProperty("QString", _get_status) + + +class RequestsWidgetActions(config.ActionsBase): + + def __init__(self, parent): + config.ActionsBase.__init__(self, parent) + + self.action( + name='ActionDownloadKeyToDisk', + text=self.trUtf8('Download &key...'), + trigger=parent.onDlgDownloadKey, + ) + self.action( + name='ActionRemoveSelectedDownloads', + text=self.trUtf8('Remove download'), + trigger=parent.onRemoveSelectedDownloads, + isEnabled=False, + ) + self.action( + name='ActionRestartSelectedDownloads', + text=self.trUtf8('Restart download'), + trigger=parent.onRestartSelectedDownloads, + isEnabled=False, + ) + + #TODO: enable/disable if items of that type are available? + group = self.group( + name='GroupRemoveGroup', + trigger=parent.onRemoveGroup, + ) + self.action( + name='ActionRemoveFailed', + group=group, + text=self.trUtf8('Failed'), + isEnabled=False, + ) + self.action( + name='ActionRemoveCompleted', + group=group, + text=self.trUtf8('Completed'), + isEnabled=False, + ) + +#********************************************************************************** +# +#********************************************************************************** +class RequestsWidget(QtGui.QWidget, Ui_ViewRequestsWidget): + + IdTree = 'tree' + + def __init__(self, parent, idGlobalFeedback=config.IdMainWindow): + QtGui.QWidget.__init__(self, parent) + self._isCreated = False + self.fcHeaderLabels = {} # fcpIdentifier --> treeItem + self.fcActions = RequestsWidgetActions(self) + self.fcpRequests = {} + self.fcRequestStatusNames = {} + self.menuRemoveGroup = QtGui.QMenu(self) + + self.setupUi(self) + self.fcpClientEvents = ( + (config.fcpClient.events.ClientConnected, self.onFcpClientConnected), + (config.fcpClient.events.ClientDisconnected, self.onFcpClientDisconnected), + (config.fcpClient.events.ConfigData, self.onFcpConfigData), + (config.fcpClient.events.RequestCompleted, self.onFcpClientRequestCompleted), + (config.fcpClient.events.RequestFailed, self.onFcpClientRequestFailed), + (config.fcpClient.events.RequestModified, self.onFcpClientRequestModified), + (config.fcpClient.events.RequestProgress, self.onFcpClientRequestProgress), + (config.fcpClient.events.RequestStarted, self.onFcpClientRequestStarted), + (config.fcpClient.events.RequestRemoved, self.onFcpClientRequestRemoved), + ) + config.fcpClient.events += self.fcpClientEvents + + + ############################ + ## private methods + ############################ + def _adjustItemStatus(self, item): + # to take Css styling into account we have to set a new statusBar for each state change + tree = self.controlById(self.IdTree) + oldProgressBar = progressBar = self.tree.itemWidget(item, TreeItem.IndexProgress) + itemStatus = item.status() + itemStatusChanged = itemStatus != item.fcOldStatus + if itemStatusChanged: + progressBar = ProgressBar(self.tree, item) + + # adjust statusBar and set a new one if necessary + # ..bit much work here, but necessary, cos Fcp might come up with + # ..a completed message without any prior progress notifications + if itemStatus == TreeItem.StatusPending: + progressBar.setRange(0, 0) + elif itemStatus == TreeItem.StatusLoading: + progressBar.setRange(0, item.fcpRequest['ProgressRequired']) + progressBar.setValue(item.fcpRequest['ProgressSucceeded']) + elif itemStatus == TreeItem.StatusComplete: + progressBar.setRange(0, 1) + progressBar.setValue(progressBar.maximum()) + elif itemStatus == TreeItem.StatusError: + progressBar.setMinimum(oldProgressBar.minimum()) + progressBar.setMaximum(oldProgressBar.maximum()) + progressBar.setValue(oldProgressBar.value()) + elif itemStatus == TreeItem.StatusRemoved: + pass + else: + raise ValueError('Unknown status: %r' % itemStatus) + if itemStatusChanged: + progressBar.setObjectName(TreeItem.ProgressBarName) + tree.setItemWidget(item, TreeItem.IndexProgress, progressBar) + item.setData( + TreeItem.IndexStatus, + QtCore.Qt.DisplayRole, + QtCore.QVariant(self.fcRequestStatusNames[itemStatus]), + ) + item.fcOldStatus = itemStatus + + def _createItemFromFcpRequest(self, fcpRequest): + tree = self.controlById(self.IdTree) + root = tree.invisibleRootItem() + item= TreeItem(fcpRequest, root) + progressBar = ProgressBar(self.tree, item) + + progressBar.setObjectName(TreeItem.ProgressBarName) + tree.setItemWidget(item, TreeItem.IndexProgress, progressBar) + fileName = fcpRequest['Filename'] + mimeType = mimetypes.guess_type(fileName)[0] + icon = config.fcResources.getIcon( + config.mimeTypeIconName(mimeType), + config.fcSettings.value('IconSize'), + config.fcSettings.value('IconTheme'), + ) + item.setIcon(0, icon) + item.setData( + TreeItem.IndexName, + QtCore.Qt.DisplayRole, + QtCore.QVariant(os.path.basename(fcpRequest['Filename'])) + ) + + #TODO: take a wild guess at the size. no other way to do it currently + estimatedSize = int((BLOCK_SIZE * fcpRequest['ProgressRequired']) + 1) + item.setData( + TreeItem.IndexSize, + QtCore.Qt.DisplayRole, + QtCore.QVariant(self.trUtf8('< ') + numbers.format_num_bytes(estimatedSize)), + ) + + self.fcpRequests[fcpRequest['Identifier']] = item + self._adjustItemStatus(item) + return item + + ############################ + ## + ############################ + def retranslateUi(self, parent): + Ui_ViewRequestsWidget.retranslateUi(self, parent) + tree = self.controlById(self.IdTree) + root = tree.invisibleRootItem() + + # adjust header labels + self.fcHeaderLabels = { + TreeItem.IndexName: self.trUtf8('Name'), + TreeItem.IndexSize: self.trUtf8('Size'), + TreeItem.IndexMimeType: self.trUtf8('MimeType'), + TreeItem.IndexStatus: self.trUtf8('Status'), + TreeItem.IndexPriority: self.trUtf8('Priority'), + TreeItem.IndexProgress: self.trUtf8('Progress'), + TreeItem.IndexElapsed: self.trUtf8('Elapsed'), + } + tree.setHeaderLabels([i[1] for i in sorted(self.fcHeaderLabels.items())]) + + # adjust status names and retranslate all items + self.fcRequestStatusNames = { + TreeItem.StatusPending: self.trUtf8('Pending'), + TreeItem.StatusLoading: self.trUtf8('Loading'), + TreeItem.StatusComplete: self.trUtf8('Complete'), + TreeItem.StatusError: self.trUtf8('Error'), + TreeItem.StatusRemoved: self.trUtf8('Removed'), + } + for item in treewidgetwrap.walkItem(root): + fcpRequest = getattr(item, 'fcpRequest', None) + if hasattr(item, 'fcpRequest'): + item.setText(TreeItem.IndexStatus, self.fcRequestStatusNames[item.status()]) + + # others + self.menuRemoveGroup.setTitle(self.trUtf8('Remove group')) + + def closeEvent(self): + self.viewClose() + + def hideEvent(self, event): + self.fcGlobalFeedback.setVisible(False) + + def showEvent(self, event): + self.fcGlobalFeedback.setVisible(True) + if not self._isCreated: + self._isCreated = True + + # setup tree + tree = self.controlById(self.IdTree) + tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + tree.setRootIsDecorated(False) + tree.setSelectionMode(tree.ExtendedSelection) + tree.setUniformRowHeights(True) + self.connect(tree, QtCore.SIGNAL('customContextMenuRequested(const QPoint &)'), self.onTreeCustomContextMenuRequested) + self.connect(tree, QtCore.SIGNAL('itemSelectionChanged() '), self.onTreeItemSelectionChanged) + + def viewClose(self): + config.fcpClient.events -= self.fcpClientEvents + + ######################################### + ## methods + ######################################### + def controlById(idGlobalFeedback, idControl): + return getattr(idGlobalFeedback, idControl) + + def downloadFile(self, fcpKey, fileName, **kws): + """""" + fileName = unicode(fileName) + fcpRequest = config.fcpClient.getFile( + fcpKey, + fileName, + persistentUserData=PersistentRequestData(ClientName=unicode(self.objectName())).dump(), + #TODO: browser sets this. ok or not? + #handleFilenameCollision=True, + handlePermanentRedirect=True, + **kws + ) + item = self._createItemFromFcpRequest(fcpRequest) + + def populateMenu(self, menu): + menu.addAction(self.fcActions['ActionDownloadKeyToDisk']) + return menu + + + def execDlgDownloadKey(self, fcpKey=None): + """pops up the dialog to allow the user to download a key to disk + @param fcpKey: key to initialize the key with or None + """ + dlg = DlgDownloadKeyToDisk.DlgDownloadKeyToDisk(self, fcpKey=fcpKey) + if dlg.exec_() == dlg.Accepted: + self.downloadFile( + dlg.fcpKey(), + dlg.fileName(), + persistence=fcp2.ConstPersistence.Forever, + handleFilenameCollision=True, + ) + + ######################################### + ## event handlers + ######################################### + def onDlgDownloadKey(self, action): + self.execDlgDownloadKey(fcpKey=None) + + def onRemoveSelectedDownloads(self, action): + tree = self.controlById(self.IdTree) + selectedItems = tree.selectedItems() + + # remove items + for item in selectedItems: + parent = item.parent() + if parent is None: + parent = tree.invisibleRootItem() + parent.removeChild(item) + + # cancel all requests + for item in selectedItems: + for tmp_item in treewidgetwrap.walkItem(item): + if tmp_item.fcpRequest is not None: # we may come across the same item multiple times + if tmp_item.fcpRequest['Identifier'] in self.fcpRequests: #TODO: should never be False?! check + del self.fcpRequests[tmp_item.fcpRequest['Identifier']] + config.fcpClient.removeRequest(tmp_item.fcpRequest) + tmp_item.fcpRequest = None + + def onRestartSelectedDownloads(self, action): + tree = self.controlById(self.IdTree) + for item in tree.selectedItems(): + if item.fcpRequest is None: + raise RuntimeError('fcpRequest is None. should not happen') + del self.fcpRequests[item.fcpRequest['Identifier']] + item.fcpRequest = config.fcpClient.resendRequest(item.fcpRequest) + self.fcpRequests[item.fcpRequest['Identifier']] = item + self._adjustItemStatus(item) + + def onRemoveGroup(self, action): + tree = self.controlById(self.IdTree) + root = tree.invisibleRootItem() + + if action == self.fcActions['ActionRemoveCompleted']: + flag = fcp2.ConstRequestStatus.Success + elif action == self.fcActions['ActionRemoveFailed']: + flag = fcp2.ConstRequestStatus.Error + else: + raise ValueError('Not implemented') + + for item in treewidgetwrap.walkItem(root, topdown=False): + fcpRequest = getattr(item, 'fcpRequest', None) + if fcpRequest is not None: + if fcpRequest['Identifier'] in self.fcpRequests: #TODO: should never be False?! check + del self.fcpRequests[fcpRequest['Identifier']] + + if fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Removed: + pass + elif not fcpRequest['RequestStatus'] & flag: + continue + else: + item.fcpRequest = None + config.fcpClient.removeRequest(fcpRequest) + parent = item.parent() + if parent is None: + parent = root + parent.removeChild(item) + + + # overwrite + def onTreeCustomContextMenuRequested(self, pt): + pass + + # overwrite + def onTreeItemSelectionChanged(self): + pass + + ######################################### + ## fcp event handlers + ######################################### + def onFcpClientConnected(self, event, msg): + for action in self.fcActions: + action.setEnabled(True) + + + def onFcpClientDisconnected(self, event, msg): + for action in self.fcActions: + action.setEnabled(False) + + def onFcpConfigData(self, fcpEvent, fcpRequest): + pass + + + def onFcpClientRequestCompleted(self, fcpEvent, fcpRequest): + item = self.fcpRequests.get(fcpRequest['Identifier'], None) + if item is not None: + item.setData( + TreeItem.IndexSize, + QtCore.Qt.DisplayRole, + QtCore.QVariant(numbers.format_num_bytes(fcpRequest['MetadataSize'])) + ) + item.setData( + TreeItem.IndexMimeType, + QtCore.Qt.DisplayRole, + QtCore.QVariant(fcpRequest['MetadataContentType']) + ) + self._adjustItemStatus(item) + + def onFcpClientRequestFailed(self, fcpEvent, fcpRequest): + item = self.fcpRequests.get(fcpRequest['Identifier'], None) + if item is not None: + self._adjustItemStatus(item) + + #TODO: not tested + def onFcpClientRequestModified(self, fcpEvent, fcpRequest): + + requestIdentifier = fcpRequest['Modified'].get(fcp2.ConstRequestModified.Identifier, None) + if requestIdentifier is None: + requestIdentifier = fcpRequest['Identifier'] + item = self.fcpRequests.get(requestIdentifier, None) + + if item is not None: + if fcp2.ConstRequestModified.Identifier in fcpRequest['Modified']: + newFcpIdentifier = fcpRequest['Identifier'] + del self.fcpRequests[requestIdentifier] + self.fcpRequests[newFcpIdentifier] = item + + if fcp2.ConstRequestModified.Filename in fcpRequest['Modified']: + item.setData( + TreeItem.IndexName, + QtCore.Qt.DisplayRole, + QtCore.QVariant(os.path.basename(fcpRequest['Filename'])) + ) + + + def onFcpClientRequestProgress(self, fcpEvent, fcpRequest): + item = self.fcpRequests.get(fcpRequest['Identifier'], None) + if item is not None: + self._adjustItemStatus(item) + + def onFcpClientRequestRemoved(self, fcpEvent, fcpRequest): + pass + + def onFcpClientRequestStarted(self, fcpEvent, fcpRequest): + if fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Restored: + try: + requestData = PersistentRequestData.load(fcpRequest['PersistentUserData']) + except pmstruct.PMStructError: + pass + else: + if requestData.get('ClientName', None) == self.objectName(): + item = self._createItemFromFcpRequest(fcpRequest) + else: + item = self.fcpRequests.get(fcpRequest['Identifier'], None) + if item is not None: + self._adjustItemStatus(item) + + +#********************************************************************************** +# +#********************************************************************************** Modified: trunk/fclient/fclient/impl/ViewDownloads.py =================================================================== --- trunk/fclient/fclient/impl/ViewDownloads.py 2008-08-11 17:59:02 UTC (rev 901) +++ trunk/fclient/fclient/impl/ViewDownloads.py 2008-08-11 18:14:30 UTC (rev 902) @@ -1,527 +1,17 @@ """""" -#************************************************************************************************************** -#TODO: -# -# x. prtty tricky to get dls right when the node or client may disconnect unexpectedly -# problem: dls that we send and that have not reached the node -# problem: what to do when the user wants to quit and we still have dls to push to the node -# -# solution would require keeping track of all requests -# a. keep identifiers of requests that reached the node (have to do it anyways) -# b. keep track of requests ahead of sending them to the node (feels not too good doubeling downloads.dat.gz) -# -# best idea seems to be to ignore the problem -# 1. wait till (if ever) freenet devels fdrop node keeping track of client requests -# -# 2. a box thrown to the user (x. do not show this message again) to inform him about pendings -# should be enough. maybe along with a separate widget or some separate color code for pendings -# -# x. performance -# start with a standard model and improve it over time. no need to set any hard -# limits. the user will find out by himself how many dls his machine can handle. -# have to be carefrul though when adding dls, like from a *.frdx to provide appropriate -# warnings -# x. it may take a while untill the final DataFound message arrives when a request is % completed. feedback would be nice -# x. DataFound for a request the file has been removed by the user. no idea what happens. have to test this -# x. when the node is about to start up, looks like persistents may arrive or not. check -# x. how to get early information about mimetype/size? maybe use FcpClient.getFileInfo() -# x. show/hide header izems -# x. sort by header -# x. indicate over all time / dl speed -# x. indicate status / remove items by status -# x. item properties -# x. how to handle inserting huge number of dls? -# idea: insert with lowest priority to get the node to know them, increase priority when a slot in -# MaxSimultaneousDls (if set) is free. atatch progressBar no sooner as priority > MinDlPriority -# x. how to handle huge numbers of dls. the node will flood us on startup with persistents. looks -# like the only way to control the flood is to have one connection/dl. maybe wait for freenet devels -# to realize that this is a serious problem... -# x. byte amount postfixes must be transllated ++ use Kib or Kb or let the user decide? -# x. sometimes groups of dls get not removed -#************************************************************************************************************** + from __future__ import absolute_import if __name__ == '__main__': # see --> http://bugs.python.org/issue1510172 . works only current dir and below import os; __path__ = [os.path.dirname(__file__)] + -import mimetypes -import os from PyQt4 import QtCore, QtGui -from . import config -from .lib import fcp2 -from .lib.fcp2.lib import pmstruct -from .lib.qt4ex import treewidgetwrap -from .lib import numbers - -from . import DlgDownloadKeyToDisk - -from .tpls.Ui_ViewRequestsWidgetTpl import Ui_ViewRequestsWidget -#********************************************************************************** +from .BaseRequestsWidget import RequestsWidget +#************************************************************************************ # -#********************************************************************************** -BLOCK_SIZE = 32768 # from CHKBlock.java - -#********************************************************************************** -# -#********************************************************************************** -class PersistentRequestData(pmstruct.PMStruct): - _fields_ = ( - ('ClientName', pmstruct.STRING), - ) - -#********************************************************************************** -# -#********************************************************************************** -class TreeItem(QtGui.QTreeWidgetItem): - - IndexName = 0 - IndexSize = 1 - IndexMimeType = 2 - IndexStatus = 3 - IndexProgress = 4 - IndexPriority = 5 - IndexElapsed = 6 - - ProgressBarName = 'downloadKey' - - StatusPending = 'pending' - StatusLoading = 'loading' - StatusComplete = 'complete' - StatusError = 'error' - StatusRemoved = 'removed' - - def __init__(self, fcpRequest, *params): - QtGui.QTreeWidgetItem.__init__(self, *params) - self.fcpRequest = fcpRequest - self.fcOldStatus = self.StatusPending - - def status(self): - if self.fcpRequest is None: - return self.StatusRemoved - elif self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Success: - return self.StatusComplete - elif self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Error: - return self.StatusError - elif self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Started: - return self.StatusLoading - else: - return self.StatusPending - -# exposes status property for stylesheets -class ProgressBar(QtGui.QProgressBar): - - def __init__(self, parent, item): - QtGui.QProgressBar.__init__(self, parent) - self.item = item - - def _get_status(self): - return self.item.status() - status= QtCore.pyqtProperty("QString", _get_status) - - -class RequestsWidgetActions(config.ActionsBase): - - def __init__(self, parent): - config.ActionsBase.__init__(self, parent) - - self.action( - name='ActionDownloadKeyToDisk', - text=self.trUtf8('Download &key...'), - trigger=parent.onDlgDownloadKey, - ) - self.action( - name='ActionRemoveSelectedDownloads', - text=self.trUtf8('Remove download'), - trigger=parent.onRemoveSelectedDownloads, - isEnabled=False, - ) - self.action( - name='ActionRestartSelectedDownloads', - text=self.trUtf8('Restart download'), - trigger=parent.onRestartSelectedDownloads, - isEnabled=False, - ) - - #TODO: enable/disable if items of that type are available? - group = self.group( - name='GroupRemoveGroup', - trigger=parent.onRemoveGroup, - ) - self.action( - name='ActionRemoveFailed', - group=group, - text=self.trUtf8('Failed'), - isEnabled=False, - ) - self.action( - name='ActionRemoveCompleted', - group=group, - text=self.trUtf8('Completed'), - isEnabled=False, - ) - -#********************************************************************************** -# -#********************************************************************************** -class RequestsWidget(QtGui.QWidget, Ui_ViewRequestsWidget): - - IdTree = 'tree' - - def __init__(self, parent, idGlobalFeedback=config.IdMainWindow): - QtGui.QWidget.__init__(self, parent) - self._isCreated = False - self.fcHeaderLabels = {} # fcpIdentifier --> treeItem - self.fcActions = RequestsWidgetActions(self) - self.fcpRequests = {} - self.fcRequestStatusNames = {} - self.menuRemoveGroup = QtGui.QMenu(self) - - self.setupUi(self) - self.fcpClientEvents = ( - (config.fcpClient.events.ClientConnected, self.onFcpClientConnected), - (config.fcpClient.events.ClientDisconnected, self.onFcpClientDisconnected), - (config.fcpClient.events.ConfigData, self.onFcpConfigData), - (config.fcpClient.events.RequestCompleted, self.onFcpClientRequestCompleted), - (config.fcpClient.events.RequestFailed, self.onFcpClientRequestFailed), - (config.fcpClient.events.RequestModified, self.onFcpClientRequestModified), - (config.fcpClient.events.RequestProgress, self.onFcpClientRequestProgress), - (config.fcpClient.events.RequestStarted, self.onFcpClientRequestStarted), - (config.fcpClient.events.RequestRemoved, self.onFcpClientRequestRemoved), - ) - config.fcpClient.events += self.fcpClientEvents - - - ############################ - ## private methods - ############################ - def _adjustItemStatus(self, item): - # to take Css styling into account we have to set a new statusBar for each state change - tree = self.controlById(self.IdTree) - oldProgressBar = progressBar = self.tree.itemWidget(item, TreeItem.IndexProgress) - itemStatus = item.status() - itemStatusChanged = itemStatus != item.fcOldStatus - if itemStatusChanged: - progressBar = ProgressBar(self.tree, item) - - # adjust statusBar and set a new one if necessary - # ..bit much work here, but necessary, cos Fcp might come up with - # ..a completed message without any prior progress notifications - if itemStatus == TreeItem.StatusPending: - progressBar.setRange(0, 0) - elif itemStatus == TreeItem.StatusLoading: - progressBar.setRange(0, item.fcpRequest['ProgressRequired']) - progressBar.setValue(item.fcpRequest['ProgressSucceeded']) - elif itemStatus == TreeItem.StatusComplete: - progressBar.setRange(0, 1) - progressBar.setValue(progressBar.maximum()) - elif itemStatus == TreeItem.StatusError: - progressBar.setMinimum(oldProgressBar.minimum()) - progressBar.setMaximum(oldProgressBar.maximum()) - progressBar.setValue(oldProgressBar.value()) - elif itemStatus == TreeItem.StatusRemoved: - pass - else: - raise ValueError('Unknown status: %r' % itemStatus) - if itemStatusChanged: - progressBar.setObjectName(TreeItem.ProgressBarName) - tree.setItemWidget(item, TreeItem.IndexProgress, progressBar) - item.setData( - TreeItem.IndexStatus, - QtCore.Qt.DisplayRole, - QtCore.QVariant(self.fcRequestStatusNames[itemStatus]), - ) - item.fcOldStatus = itemStatus - - def _createItemFromFcpRequest(self, fcpRequest): - tree = self.controlById(self.IdTree) - root = tree.invisibleRootItem() - item= TreeItem(fcpRequest, root) - progressBar = ProgressBar(self.tree, item) - - progressBar.setObjectName(TreeItem.ProgressBarName) - tree.setItemWidget(item, TreeItem.IndexProgress, progressBar) - fileName = fcpRequest['Filename'] - mimeType = mimetypes.guess_type(fileName)[0] - icon = config.fcResources.getIcon( - config.mimeTypeIconName(mimeType), - config.fcSettings.value('IconSize'), - config.fcSettings.value('IconTheme'), - ) - item.setIcon(0, icon) - item.setData( - TreeItem.IndexName, - QtCore.Qt.DisplayRole, - QtCore.QVariant(os.path.basename(fcpRequest['Filename'])) - ) - - #TODO: take a wild guess at the size. no other way to do it currently - estimatedSize = int((BLOCK_SIZE * fcpRequest['ProgressRequired']) + 1) - item.setData( - TreeItem.IndexSize, - QtCore.Qt.DisplayRole, - QtCore.QVariant(self.trUtf8('< ') + numbers.format_num_bytes(estimatedSize)), - ) - - self.fcpRequests[fcpRequest['Identifier']] = item - self._adjustItemStatus(item) - return item - - ############################ - ## - ############################ - def retranslateUi(self, parent): - Ui_ViewRequestsWidget.retranslateUi(self, parent) - tree = self.controlById(self.IdTree) - root = tree.invisibleRootItem() - - # adjust header labels - self.fcHeaderLabels = { - TreeItem.IndexName: self.trUtf8('Name'), - TreeItem.IndexSize: self.trUtf8('Size'), - TreeItem.IndexMimeType: self.trUtf8('MimeType'), - TreeItem.IndexStatus: self.trUtf8('Status'), - TreeItem.IndexPriority: self.trUtf8('Priority'), - TreeItem.IndexProgress: self.trUtf8('Progress'), - TreeItem.IndexElapsed: self.trUtf8('Elapsed'), - } - tree.setHeaderLabels([i[1] for i in sorted(self.fcHeaderLabels.items())]) - - # adjust status names and retranslate all items - self.fcRequestStatusNames = { - TreeItem.StatusPending: self.trUtf8('Pending'), - TreeItem.StatusLoading: self.trUtf8('Loading'), - TreeItem.StatusComplete: self.trUtf8('Complete'), - TreeItem.StatusError: self.trUtf8('Error'), - TreeItem.StatusRemoved: self.trUtf8('Removed'), - } - for item in treewidgetwrap.walkItem(root): - fcpRequest = getattr(item, 'fcpRequest', None) - if hasattr(item, 'fcpRequest'): - item.setText(TreeItem.IndexStatus, self.fcRequestStatusNames[item.status()]) - - # others - self.menuRemoveGroup.setTitle(self.trUtf8('Remove group')) - - def closeEvent(self): - self.viewClose() - - def hideEvent(self, event): - self.fcGlobalFeedback.setVisible(False) - - def showEvent(self, event): - self.fcGlobalFeedback.setVisible(True) - if not self._isCreated: - self._isCreated = True - - # setup tree - tree = self.controlById(self.IdTree) - tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - tree.setRootIsDecorated(False) - tree.setSelectionMode(tree.ExtendedSelection) - tree.setUniformRowHeights(True) - self.connect(tree, QtCore.SIGNAL('customContextMenuRequested(const QPoint &)'), self.onTreeCustomContextMenuRequested) - self.connect(tree, QtCore.SIGNAL('itemSelectionChanged() '), self.onTreeItemSelectionChanged) - - def viewClose(self): - config.fcpClient.events -= self.fcpClientEvents - - ######################################### - ## methods - ######################################### - def controlById(idGlobalFeedback, idControl): - return getattr(idGlobalFeedback, idControl) - - def downloadFile(self, fcpKey, fileName, **kws): - """""" - fileName = unicode(fileName) - fcpRequest = config.fcpClient.getFile( - fcpKey, - fileName, - persistentUserData=PersistentRequestData(ClientName=unicode(self.objectName())).dump(), - #TODO: browser sets this. ok or not? - #handleFilenameCollision=True, - handlePermanentRedirect=True, - **kws - ) - item = self._createItemFromFcpRequest(fcpRequest) - - def populateMenu(self, menu): - menu.addAction(self.fcActions['ActionDownloadKeyToDisk']) - return menu - - - def execDlgDownloadKey(self, fcpKey=None): - """pops up the dialog to allow the user to download a key to disk - @param fcpKey: key to initialize the key with or None - """ - dlg = DlgDownloadKeyToDisk.DlgDownloadKeyToDisk(self, fcpKey=fcpKey) - if dlg.exec_() == dlg.Accepted: - self.downloadFile( - dlg.fcpKey(), - dlg.fileName(), - persistence=fcp2.ConstPersistence.Forever, - handleFilenameCollision=True, - ) - - ######################################### - ## event handlers - ######################################### - def onDlgDownloadKey(self, action): - self.execDlgDownloadKey(fcpKey=None) - - def onRemoveSelectedDownloads(self, action): - tree = self.controlById(self.IdTree) - selectedItems = tree.selectedItems() - - # remove items - for item in selectedItems: - parent = item.parent() - if parent is None: - parent = tree.invisibleRootItem() - parent.removeChild(item) - - # cancel all requests - for item in selectedItems: - for tmp_item in treewidgetwrap.walkItem(item): - if tmp_item.fcpRequest is not None: # we may come across the same item multiple times - if tmp_item.fcpRequest['Identifier'] in self.fcpRequests: #TODO: should never be False?! check - del self.fcpRequests[tmp_item.fcpRequest['Identifier']] - config.fcpClient.removeRequest(tmp_item.fcpRequest) - tmp_item.fcpRequest = None - - def onRestartSelectedDownloads(self, action): - tree = self.controlById(self.IdTree) - for item in tree.selectedItems(): - if item.fcpRequest is None: - raise RuntimeError('fcpRequest is None. should not happen') - del self.fcpRequests[item.fcpRequest['Identifier']] - item.fcpRequest = config.fcpClient.resendRequest(item.fcpRequest) - self.fcpRequests[item.fcpRequest['Identifier']] = item - self._adjustItemStatus(item) - - def onRemoveGroup(self, action): - tree = self.controlById(self.IdTree) - root = tree.invisibleRootItem() - - if action == self.fcActions['ActionRemoveCompleted']: - flag = fcp2.ConstRequestStatus.Success - elif action == self.fcActions['ActionRemoveFailed']: - flag = fcp2.ConstRequestStatus.Error - else: - raise ValueError('Not implemented') - - for item in treewidgetwrap.walkItem(root, topdown=False): - fcpRequest = getattr(item, 'fcpRequest', None) - if fcpRequest is not None: - if fcpRequest['Identifier'] in self.fcpRequests: #TODO: should never be False?! check - del self.fcpRequests[fcpRequest['Identifier']] - - if fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Removed: - pass - elif not fcpRequest['RequestStatus'] & flag: - continue - else: - item.fcpRequest = None - config.fcpClient.removeRequest(fcpRequest) - parent = item.parent() - if parent is None: - parent = root - parent.removeChild(item) - - - # overwrite - def onTreeCustomContextMenuRequested(self, pt): - pass - - # overwrite - def onTreeItemSelectionChanged(self): - pass - - ######################################### - ## fcp event handlers - ######################################### - def onFcpClientConnected(self, event, msg): - for action in self.fcActions: - action.setEnabled(True) - - - def onFcpClientDisconnected(self, event, msg): - for action in self.fcActions: - action.setEnabled(False) - - def onFcpConfigData(self, fcpEvent, fcpRequest): - pass - - - def onFcpClientRequestCompleted(self, fcpEvent, fcpRequest): - item = self.fcpRequests.get(fcpRequest['Identifier'], None) - if item is not None: - item.setData( - TreeItem.IndexSize, - QtCore.Qt.DisplayRole, - QtCore.QVariant(numbers.format_num_bytes(fcpRequest['MetadataSize'])) - ) - item.setData( - TreeItem.IndexMimeType, - QtCore.Qt.DisplayRole, - QtCore.QVariant(fcpRequest['MetadataContentType']) - ) - self._adjustItemStatus(item) - - def onFcpClientRequestFailed(self, fcpEvent, fcpRequest): - item = self.fcpRequests.get(fcpRequest['Identifier'], None) - if item is not None: - self._adjustItemStatus(item) - - #TODO: not tested - def onFcpClientRequestModified(self, fcpEvent, fcpRequest): - - requestIdentifier = fcpRequest['Modified'].get(fcp2.ConstRequestModified.Identifier, None) - if requestIdentifier is None: - requestIdentifier = fcpRequest['Identifier'] - item = self.fcpRequests.get(requestIdentifier, None) - - if item is not None: - if fcp2.ConstRequestModified.Identifier in fcpRequest['Modified']: - newFcpIdentifier = fcpRequest['Identifier'] - del self.fcpRequests[requestIdentifier] - self.fcpRequests[newFcpIdentifier] = item - - if fcp2.ConstRequestModified.Filename in fcpRequest['Modified']: - item.setData( - TreeItem.IndexName, - QtCore.Qt.DisplayRole, - QtCore.QVariant(os.path.basename(fcpRequest['Filename'])) - ) - - - def onFcpClientRequestProgress(self, fcpEvent, fcpRequest): - item = self.fcpRequests.get(fcpRequest['Identifier'], None) - if item is not None: - self._adjustItemStatus(item) - - def onFcpClientRequestRemoved(self, fcpEvent, fcpRequest): - pass - - def onFcpClientRequestStarted(self, fcpEvent, fcpRequest): - if fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Restored: - try: - requestData = PersistentRequestData.load(fcpRequest['PersistentUserData']) - except pmstruct.PMStructError: - pass - else: - if requestData.get('ClientName', None) == self.objectName(): - item = self._createItemFromFcpRequest(fcpRequest) - else: - item = self.fcpRequests.get(fcpRequest['Identifier'], None) - if item is not None: - self._adjustItemStatus(item) - - -#********************************************************************************** -# -#********************************************************************************** +#************************************************************************************ class DownloadsWidgetGlobalFeedback(config.GlobalFeedbackBase): """wrapper for global statusbar widgets, menus""" This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |