SF.net SVN: fclient: [70] trunk/fclient/fclient_widgets/download_widget.py
Status: Pre-Alpha
Brought to you by:
jurner
From: <jU...@us...> - 2007-11-12 10:37:49
|
Revision: 70 http://fclient.svn.sourceforge.net/fclient/?rev=70&view=rev Author: jUrner Date: 2007-11-12 02:37:52 -0800 (Mon, 12 Nov 2007) Log Message: ----------- too many a changes.. reworked testDDA, PersistentGet is now handled and God knows whatelse Modified Paths: -------------- trunk/fclient/fclient_widgets/download_widget.py Modified: trunk/fclient/fclient_widgets/download_widget.py =================================================================== --- trunk/fclient/fclient_widgets/download_widget.py 2007-11-12 10:36:01 UTC (rev 69) +++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-12 10:37:52 UTC (rev 70) @@ -112,11 +112,14 @@ self._requestStatus = self.StatusNone self._requestInitTime = time.time() self._uri = uri + + self._ddaTested = False def setRequestError(self, event, params): """If an error occures, sets info about the error - @param event: event that triggered the error + @param event: event that triggered the error (EventFetchError, EventProtocolError, + EventTestDDAComplete) @param params: params passed along with the event """ self._requestError = (event, params) @@ -250,6 +253,13 @@ def requestUri(self): """Returns the request uri of the item""" return self._uri + + + def setDDATested(self, flag): + self._ddaTested = flag + + def ddaTested(self): + return self._ddaTested #***************************************************************************** # @@ -291,9 +301,7 @@ #**************************************************************************** # #**************************************************************************** - -#TODO: no idea if the node removes requests when complete or on error or -# if RemovePersistentRequest has to be send explicitely +#TODO: when closing we have to empty queues by sending ClientGet requests class DownloadWidget(QtGui.QTreeWidget): def __init__(self, @@ -315,7 +323,7 @@ self._connectionName = connectionName self._cfg = cfg if cfg is not None else config.Config(self) self._directory = directory if directory is not None else self._cfg.defaultDownloadDir - self._downloads = { + self._requests = { 'Downloads': {}, # identifier --> item / items currently downloading 'DownloadQueue': [], # (parent-item, uri) / items scheduled for download 'DownloadDirectories': {}, # directory --> (parentItem, uris) / directories scheduled for testDDA @@ -350,14 +358,19 @@ # schedule initial uris for download if uris is not None: - self._downloads['DownloadDirectories'][self._directory] = (self, uris) + for uri in uris: + self.download(uri) + + #self._requests['DownloadDirectories'][self._directory] = (self, uris) def showEvent(self, event): if not self._isCreated: self._isCreated = True self._fcpClient = self._cfg.fcpClientManager.newClient(self._connectionName, self.handleFcpClientConnected) - + self._fcpClient.setVerbosity(self._fcpClient.Verbosity.Debug) + + ############################################################# ## ## handlers for Qt events @@ -384,6 +397,7 @@ (self._fcpClient.events.ClientRequestInfo, self.handleFcpClientRequestInfo), (self._fcpClient.events.SimpleProgress, self.handleFcpClientSimpleProgress), (self._fcpClient.events.DataFound, self.handleFcpClientDataFound), + (self._fcpClient.events.PersistentGet, self.handleFcpClientPersistentGet), (self._fcpClient.events.GetFailed, self.handleFcpClientGetFailed), (self._fcpClient.events.IdentifierCollision, self.handleFcpClientIdentifierCollision), @@ -395,7 +409,7 @@ event += observer # register directories - for directory in self._downloads['DownloadDirectories']: + for directory in self._requests['DownloadDirectories']: self._fcpClient.testDDA(directory, wantWriteDirectory=True) @@ -408,62 +422,72 @@ """ """ identifier = params['Identifier'] - item = self._downloads['Downloads'].get(identifier, None) + item = self._requests['Downloads'].get(identifier, None) if item is not None: - del self._downloads['Downloads'][identifier] + del self._requests['Downloads'][identifier] - progress = item.progressWidget(self) - status = DownloadItem.StatusDownloadComplete + mimeType = params.get('Metadata.ContentType', self._strings.unknown) + dataLength = params.get('DataLength', '') + try: + dataLength = int(dataLength) + except ValueError: + dataLength = self._strings.unknown + else: + dataLength = numbers.format_num_bytes(dataLength) + + self._adjustItemStatus(item, DownloadItem.StatusDownloadComplete) + item.setRequestMimeType(mimeType) + item.setRequestSize(dataLength) - item.setRequestStatus(status, self._strings.itemStatus[status]) - progress.setColors( - colorBar=self._userSettings['ColorProgressBarDownloadComplete'], - colorBg=self._userSettings['ColorProgressBgDownloadComplete'] - ) - #TODO: how to provide extra information to the user? + #TODO: how to provide extra error information to the user? def handleFcpClientGetFailed(self, event, params): identifier = params['Identifier'] - item = self._downloads['Downloads'].get(identifier, None) + item = self._requests['Downloads'].get(identifier, None) if item is not None: - del self._downloads['Downloads'][identifier] + del self._requests['Downloads'][identifier] code = params['Code'] # handle file name collision + # ...always wait for this error and adjust filename if necessary if code == self._fcpClient.ProtocolError.DiskTargetExists: if item.requestStatus() == DownloadItem.StatusDownloading: filename = os.path.join( self.downloadDirectoryFromItem(item), item.increaseRequestNamePostfix() ) - identifier = self.uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientGetFile) + identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientGetFile) uri = item.requestUri() self._fcpClient.clientGetFile(uri, filename, identifier=identifier) - self._downloads['Downloads'][identifier] = item + self._requests['Downloads'][identifier] = item return + # handle testDDA + # ...always wait for this error and reinsert download on TestDDAComplete + elif code == self._fcpClient.ProtocolError.DDADenied: + if not item.ddaTested(): + directory = self.downloadDirectoryFromItem(item) + if directory in self._requests['DownloadDirectories']: + self._requests['DownloadDirectories'][directory].append(item) + else: + self._requests['DownloadDirectories'][directory] = [item, ] + #NOTE: have to take care to not send neted TestDDAs + self._fcpClient.testDDA(directory, wantWriteDirectory=True) + + self._requests['Downloads'][item.requestIdentifier()] = item + return + # handle error - progress = item.progressWidget(self) - status = DownloadItem.StatusError - - item.setRequestStatus(status, self._strings.itemStatus[status]) + self._adjustItemStatus(item, DownloadItem.StatusError) item.setRequestError(event, params) - # get rid of progress pending indicator if necessary - if progress.maximum() == progress.minimum() == 0: - progress.setRange(0, 1) - progress.setValue(0) - progress.setColors( - colorBar=self._userSettings['ColorProgressBarError'], - colorBg=self._userSettings['ColorProgressBgError'] - ) #raise self._fcpClient.FetchError(params) #TODO: not yet handled - def handleFcpClientIdentifierCollision(self, vent, params): + def handleFcpClientIdentifierCollision(self, event, params): pass @@ -472,22 +496,21 @@ # check if there are sownloads queued n = 0 maxNewDls = self._userSettings['MaxNewDownloadsPerHop'] - while self._downloads['DownloadQueue'] and n < maxNewDls: + while self._requests['DownloadQueue'] and n < maxNewDls: n += 1 - parent, uri = self._downloads['DownloadQueue'].pop(0) + parent, uri = self._requests['DownloadQueue'].pop(0) + if parent is None: + parent = self item = DownloadItem(parent, '', uri) fcpUri = self._fcpClient.FcpUri(uri) fileName = fcpUri.fileName() fileName = namespace.unquote_uri(fileName) - status = DownloadItem.StatusPending - + item.setRequestName(fileName) - item.setRequestStatus(status, self._strings.itemStatus[status]) + self._adjustItemStatus(item, DownloadItem.StatusPending) - #... more here - - + # check how many downloads are currently running #TODO: bit sloppy coding here. We know how max downloads to start, @@ -508,6 +531,7 @@ items[status].append(item) downloadsToBeStarted = self._userSettings['MaxSimultaniousDownloads'] - itemsBusy + #TODO: sort by priority def sortf(item1, item2): @@ -517,54 +541,79 @@ if downloadsToBeStarted > 0: downloadsRequestInfoComplete = items[DownloadItem.StatusRequestInfoComplete] downloadsRequestInfoComplete.sort(cmp=sortf) - while downloadsRequestInfoComplete: + while downloadsRequestInfoComplete and downloadsToBeStarted > 0: downloadsToBeStarted -= 1 - if downloadsToBeStarted < 0: - break - + item = downloadsRequestInfoComplete.pop(0) - filename = os.path.join( self.downloadDirectoryFromItem(item), item.requestName()) - identifier = self.uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientGetFile) - progress = item.progressWidget(self) - status = DownloadItem.StatusDownloading + filename = os.path.join(self.downloadDirectoryFromItem(item), item.requestName()) + identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientGetFile) uri = item.requestUri() - progress.setRange(0, 0) - progress.setValue(0) - progress.setColors( - colorBar=self._userSettings['ColorProgressBarDownloading'], - colorBg=self._userSettings['ColorProgressBgDownloading'] - ) - item.setRequestStatus(status, self._strings.itemStatus[status]) + # tell node to remove completed RequestInfo + self._fcpClient.removePersistentRequest(item.requestIdentifier()) + + item.setRequestIdentifier(identifier) + self._adjustItemStatus(item, DownloadItem.StatusDownloading) self._fcpClient.clientGetFile(uri, filename, identifier=identifier) - self._downloads['Downloads'][identifier] = item + + self._requests['Downloads'][identifier] = item # start pending downloads if downloadsToBeStarted > 0: downloadsPending = items[DownloadItem.StatusPending] downloadsPending.sort(cmp=sortf) - while downloadsPending: + while downloadsPending and downloadsToBeStarted > 0: downloadsToBeStarted -= 1 - if downloadsToBeStarted < 0: - break - - identifier = self.uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientRequestInfo) + + identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientRequestInfo) item = downloadsPending.pop(0) - progress = progressbarwrap.ProgressBarEx(self) - status = DownloadItem.StatusRequestInfo uri = item.requestUri() - progress.setRange(0, 0) - progress.setValue(0) - progress.setColors( - colorBar=self._userSettings['ColorProgressBarRequestInfo'], - colorBg=self._userSettings['ColorProgressBgRequestInfo'] - ) - item.setProgressWidget(self, progress) - item.setRequestStatus(status, self._strings.itemStatus[status]) - self._fcpClient.clientRequestInfo(uri, identifier=identifier) - self._downloads['Downloads'][identifier] = item + # ClientToken will be set to item parent identifier + parent = item.parent() + if parent is None: + clientToken = None + else: + clientToken = parent.requestIdentifier() + item.setRequestIdentifier(identifier) + self._adjustItemStatus(item, DownloadItem.StatusRequestInfo) + self._fcpClient.clientRequestInfo(uri, identifier=identifier, clientToken=clientToken) + + self._requests['Downloads'][identifier] = item + + + def handleFcpClientPersistentGet(self, event, params): + identifier = params['Identifier'] + item = self._requests['Downloads'].get(identifier, None) + if item is None: + + #self._fcpClient.removePersistentRequest(identifier) + #return + + #TODO: no idea if the node passes PersistentGet messages in insertion order + # ..if not, we will have to cache items and wait for parent item to arrive + # + clientToken = params.get('ClientToken', None) + parent = self + if clientToken is not None: + parent = self._requests['Downloads'].get(clientToken, None) + uri = params['URI'] + item = DownloadItem(parent, identifier, uri) + fcpUri = self._fcpClient.FcpUri(uri) + fileName = fcpUri.fileName() + fileName = namespace.unquote_uri(fileName) + item.setRequestName(fileName) + + self._requests['Downloads'][identifier] = item + + if self._fcpClient.isClientGetFile(identifier): + status = DownloadItem.StatusDownloading + elif self._fcpClient.isClientRequestInfo(identifier): + status = DownloadItem.StatusRequestInfo + + self._adjustItemStatus(item, status) + def handleFcpClientProtocolError(self, event, params): identifier = params.get('Identifier', None) @@ -572,17 +621,20 @@ #TDO: handle error pass else: - item = self._downloads['Downloads'].get(identifier, None) + item = self._requests['Downloads'].get(identifier, None) if item is not None: self.handleFcpClientGetFailed(event, params) + + #NOTE: ProtocolError seems to jump in before a request is inserted in + # the nodes queue. So we will not get informed about the request on next reconnect. def handleFcpClientRequestInfo(self, event, params): identifier = params['Identifier'] - item = self._downloads['Downloads'].get(identifier, None) + item = self._requests['Downloads'].get(identifier, None) if item is not None: - del self._downloads['Downloads'][identifier] + del self._requests['Downloads'][identifier] status = DownloadItem.StatusRequestInfoComplete mimeType = params.get('Metadata.ContentType', self._strings.unknown) @@ -608,7 +660,7 @@ def handleFcpClientSimpleProgress(self, event, params): identifier = params['Identifier'] - item = self._downloads['Downloads'].get(identifier, None) + item = self._requests['Downloads'].get(identifier, None) if item is not None: progress = item.progressWidget(self) required=int(params['Required']) @@ -627,19 +679,23 @@ writeAllowed = params.get('WriteDirectoryAllowed') # check if there are items to be donloaded for the directory - downloads = self._downloads['DownloadDirectories'].get(directory, None) + downloads = self._requests['DownloadDirectories'].get(directory, None) if downloads is not None: - del self._downloads['DownloadDirectories'][directory] # ??? + del self._requests['DownloadDirectories'][directory] if downloads: if writeAllowed == self._fcpClient.FcpTrue: - parent, uris = downloads - for uri in uris: - self.download(uri, parent=parent) - + for item in downloads: + item.setDDATested(True) + filename = os.path.join(self.downloadDirectoryFromItem(item), item.requestName()) + self._fcpClient.clientGetFile(item.requestUri(), filename, identifier=item.requestIdentifier()) + + #TODO: not tested else: - pass - #TODO: write access denied, error + for item in downloads: + self._adjustItemStatus(item, DownloadItem.StatusError) + item.setRequestError(event, params) + ####################################################### ## @@ -647,7 +703,7 @@ ## ####################################################### def download(self, uri, parent=None): - self._downloads['DownloadQueue'].append( (parent, uri) ) + self._requests['DownloadQueue'].append( (parent, uri) ) #TODO: adjust priority? @@ -658,6 +714,8 @@ #TODO: retranslate item status + #TODO: is not correct for items that are not downloaded to self._downloads. + # self._downloads may change and / or items from global queue may jump in def downloadDirectoryFromItem(self, item): """Returns the dirctory an item should be downloaded to @param item: (QTreeWidgetItem) @@ -674,17 +732,81 @@ return os.path.join(*out) - def uniqueDownloadsIdentifier(self, identifierPrefix): + ####################################################### + ## + ## private methods + ## + ####################################################### + def _adjustItemStatus(self, item, status): + """Adjusts an item to a specifed status + @param item: (L{DownloadItem}) to adjust + @param status: L{DownloadItem}.Status* to adjust the item to + @return: item + """ + if status == DownloadItem.StatusPending: + pass + + elif status == DownloadItem.StatusRequestInfo: + progress = progressbarwrap.ProgressBarEx(self) + item.setProgressWidget(self, progress) + progress.setRange(0, 0) + progress.setValue(0) + progress.setColors( + colorBar=self._userSettings['ColorProgressBarRequestInfo'], + colorBg=self._userSettings['ColorProgressBgRequestInfo'] + ) + + elif status == DownloadItem.StatusDownloading: + progress = item.progressWidget(self) + #NOTE: when we connect to the node PersistenGet may pass GetFile requests, + # ...so make shure progessbar is available + if progress is None: + progress = progressbarwrap.ProgressBarEx(self) + item.setProgressWidget(self, progress) + progress.setRange(0, 0) + progress.setValue(0) + progress.setColors( + colorBar=self._userSettings['ColorProgressBarDownloading'], + colorBg=self._userSettings['ColorProgressBgDownloading'] + ) + + elif status == DownloadItem.StatusDownloadComplete: + progress = item.progressWidget(self) + progress.setRange(0, 1) + progress.setValue(1) + progress.setColors( + colorBar=self._userSettings['ColorProgressBarDownloadComplete'], + colorBg=self._userSettings['ColorProgressBgDownloadComplete'] + ) + + elif status == DownloadItem.StatusError: + progress = item.progressWidget(self) + # get rid of pending progress indicator if necessary + if progress.maximum() == progress.minimum() == 0: + progress.setRange(0, 1) + progress.setValue(0) + progress.setColors( + colorBar=self._userSettings['ColorProgressBarError'], + colorBg=self._userSettings['ColorProgressBgError'] + ) + + else: + raise ValueError('Status not handled') + + item.setRequestStatus(status, self._strings.itemStatus[status]) + return item + + + def _uniqueDownloadsIdentifier(self, identifierPrefix): """Creates a new identifier that is unique to the internal sownloads dict @param identifierPrefix: FcpClient.IdentifierPrefix.* @return: (str) identifier """ while True: identifier = identifierPrefix + self._fcpClient.newIdentifier() - if identifier not in self._downloads['Downloads']: + if identifier not in self._requests['Downloads']: return identifier - #*************************************************************************************************** # #*************************************************************************************************** @@ -708,8 +830,9 @@ w = QtGui.QMainWindow() peers = DownloadWidget(None, connectionName='TestDownloadWidget', - uris=TestUris - + #uris=None, + #uris=TestUris, + #uris=[TestUris[1], ] ) w.setCentralWidget(peers) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |