SF.net SVN: fclient:[916] trunk/fclient/fclient/impl
Status: Pre-Alpha
Brought to you by:
jurner
From: <jU...@us...> - 2008-08-16 08:13:49
|
Revision: 916 http://fclient.svn.sourceforge.net/fclient/?rev=916&view=rev Author: jUrner Date: 2008-08-16 08:13:58 +0000 (Sat, 16 Aug 2008) Log Message: ----------- fixes for downloads widget Modified Paths: -------------- trunk/fclient/fclient/impl/ViewDownloads/ViewDownloads.py Removed Paths: ------------- trunk/fclient/fclient/impl/BaseRequestsWidget.py Deleted: trunk/fclient/fclient/impl/BaseRequestsWidget.py =================================================================== --- trunk/fclient/fclient/impl/BaseRequestsWidget.py 2008-08-16 08:10:49 UTC (rev 915) +++ trunk/fclient/fclient/impl/BaseRequestsWidget.py 2008-08-16 08:13:58 UTC (rev 916) @@ -1,554 +0,0 @@ - -#************************************************************************************************************** -#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' - StatusCompressing = 'compressing' - ##StatusCompressed = 'compressed' #TODO: no way to distinguish compressed an loading - - 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 - - #TODO: more or less aguess ..have to check this in detail - elif self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Started: - if self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Compressing: - if self.fcpRequest['RequestStatus'] & fcp2.ConstRequestStatus.Compressed: - return self.StatusLoading - else: - return self.compressing - else: - 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.RequestCompressionStarted, self.onFcpClientRequestCompressionStarted), - (config.fcpClient.events.RequestCompressionCompleted, self.onFcpClientRequestCompressionCompleted), - ) - 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 - elif itemStatus == TreeItem.StatusCompressing: - progressBar.setRange(0, 0) - 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 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'), - TreeItem.StatusCompressing: self.trUtf8('Compressing'), - ##TreeItem.StatusCompressed: self.trUtf8('Compressed'), #TODO: no way to distinguish compressed an loading - } - 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 - ######################################### - #TODO: much... - def addFcpRequest(self, fcpRequest): - """ - @note: if you add a newly created request, make shure to set the requests PersistentUserData - to the result of the call to L{persistentFcpRequestData} - """ - 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 - - self.fcpRequests[fcpRequest['Identifier']] = item - self._adjustItemStatus(item) - return item - - - def controlById(idGlobalFeedback, idControl): - return getattr(idGlobalFeedback, idControl) - - # overwrite - def populateMenu(self, menu): - 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, - ) - - def persistentFcpRequestData(self): - return PersistentRequestData(ClientName=unicode(self.objectName())).dump() - - ######################################### - ## 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: - mimeType = fcpRequest['MetadataContentType'] - item.setData( - TreeItem.IndexSize, - QtCore.Qt.DisplayRole, - QtCore.QVariant(numbers.format_num_bytes(fcpRequest['MetadataSize'])) - ) - item.setData( - TreeItem.IndexMimeType, - QtCore.Qt.DisplayRole, - QtCore.QVariant(mimeType) - ) - icon = config.fcResources.getIcon( - config.mimeTypeIconName(mimeType), - config.fcSettings.value('IconSize'), - config.fcSettings.value('IconTheme'), - ) - item.setIcon(0, icon) - self._adjustItemStatus(item) - - def onFcpClientRequestCompressionStarted(self, fcpEvent, fcpRequest): - item = self.fcpRequests.get(fcpRequest['Identifier'], None) - if item is not None: - self._adjustItemStatus(item) - - def onFcpClientRequestCompressionCompleted(self, fcpEvent, fcpRequest): - item = self.fcpRequests.get(fcpRequest['Identifier'], None) - if item is not None: - 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.addFcpRequest(fcpRequest) - else: - item = self.fcpRequests.get(fcpRequest['Identifier'], None) - if item is not None: - self._adjustItemStatus(item) - - -#********************************************************************************** -# -#********************************************************************************** Modified: trunk/fclient/fclient/impl/ViewDownloads/ViewDownloads.py =================================================================== --- trunk/fclient/fclient/impl/ViewDownloads/ViewDownloads.py 2008-08-16 08:10:49 UTC (rev 915) +++ trunk/fclient/fclient/impl/ViewDownloads/ViewDownloads.py 2008-08-16 08:13:58 UTC (rev 916) @@ -16,7 +16,7 @@ from PyQt4 import QtCore, QtGui from .. import config -from ..BaseRequestsWidget import RequestsWidget +from ..BaseRequestsWidget import BaseRequestsWidget #************************************************************************************ # #************************************************************************************ @@ -98,13 +98,13 @@ ) -class ViewDownloadsWidget(RequestsWidget): +class ViewDownloadsWidget(BaseRequestsWidget.RequestsWidget): def __init__(self, parent, idGlobalFeedback=config.IdMainWindow): self.menuRemoveGroup = None - RequestsWidget.__init__(self, parent) + BaseRequestsWidget.RequestsWidget.__init__(self, parent) self.setObjectName(config.IdViewDownloadsWidget) config.ObjectRegistry.register(self) @@ -124,18 +124,18 @@ ################################### def hideEvent(self, event): self.fcGlobalFeedback.setVisible(False) - RequestsWidget.hideEvent(self, event) + BaseRequestsWidget.RequestsWidget.hideEvent(self, event) def showEvent(self, event): self.fcGlobalFeedback.setVisible(True) - RequestsWidget.showEvent(self, event) + BaseRequestsWidget.RequestsWidget.showEvent(self, event) def populateMenu(self, menu): menu.addAction(self.fcActions['ActionDownloadKeyToDisk']) return menu def retranslateUi(self, parent): - RequestsWidget.retranslateUi(self, parent) + BaseRequestsWidget.RequestsWidget.retranslateUi(self, parent) if self.menuRemoveGroup is not None: self.menuRemoveGroup.setTitle(self.trUtf8('Remove group')) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |