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.
|