fclient-commit Mailing List for fclient (Page 36)
Status: Pre-Alpha
Brought to you by:
jurner
You can subscribe to this list here.
2007 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(23) |
Nov
(54) |
Dec
|
---|---|---|---|---|---|---|---|---|---|---|---|---|
2008 |
Jan
(17) |
Feb
(209) |
Mar
(63) |
Apr
(31) |
May
(7) |
Jun
(39) |
Jul
(390) |
Aug
(122) |
Sep
(6) |
Oct
|
Nov
|
Dec
|
From: <ju...@us...> - 2008-01-30 13:21:04
|
Revision: 87 http://fclient.svn.sourceforge.net/fclient/?rev=87&view=rev Author: jurner Date: 2008-01-30 05:21:04 -0800 (Wed, 30 Jan 2008) Log Message: ----------- it is save now to pass any string as persistent user data Modified Paths: -------------- trunk/sandbox/fcp/fcp2_0_params.py Modified: trunk/sandbox/fcp/fcp2_0_params.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_params.py 2008-01-30 13:19:47 UTC (rev 86) +++ trunk/sandbox/fcp/fcp2_0_params.py 2008-01-30 13:21:04 UTC (rev 87) @@ -1,6 +1,7 @@ """Handling of additional message parameters""" import sys, os +import base64 #--> rel import hack class SysPathHack(object): @@ -65,7 +66,6 @@ ('FcPersistentUserData', validateString), ) - ISubType = 0 IInitTime = 1 IFilenameCollision = 2 @@ -91,19 +91,25 @@ if len(params) != len(FcParams) +1: return None + # validate and drop our magic string + uuid_ = params.pop(0) + result = validateUuid(uuid_) + if result is None: + return None + if result != MAGIC: + return None + for i, (paramName, paramValidator) in enumerate(FcParams): result = paramValidator(params[i]) if result is None: return None params[i] = result - # valiodate and drop our magic string - uuid_ = params.pop() - result = validateUuid(uuid_) - if result is None: + # decode user data + try: + params[IPersistentUserData] = base64.b64decode(params[IPersistentUserData]) + except TypeError: return None - if result != MAGIC: - return None return params @@ -117,7 +123,11 @@ params = [] for paramName, paramValidator in FcParams: params.append( str(msg[paramName]) ) - params.append(MAGIC) + + # encode user data + params[IPersistentUserData] = base64.b64encode(params[IPersistentUserData]) + params.insert(0, MAGIC) + return FcParamsSep.join(params) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ju...@us...> - 2008-01-30 13:19:44
|
Revision: 86 http://fclient.svn.sourceforge.net/fclient/?rev=86&view=rev Author: jurner Date: 2008-01-30 05:19:47 -0800 (Wed, 30 Jan 2008) Log Message: ----------- check keys to more depth Modified Paths: -------------- trunk/sandbox/fcp/fcp2_0_uri.py Modified: trunk/sandbox/fcp/fcp2_0_uri.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_uri.py 2008-01-29 11:28:06 UTC (rev 85) +++ trunk/sandbox/fcp/fcp2_0_uri.py 2008-01-30 13:19:47 UTC (rev 86) @@ -1,82 +1,93 @@ -"""Freennet Client Protocol uri""" +"""Freennet Client Protocol uri and related methods""" +import base64 import re +import urlparse +#************************************************************************************** +# freenet base64 for keys +#************************************************************************************** +def base64UrlsaveDecode(string): + """Decodes a base64 urlsave encoded string as encoded by freenet + @param string: string to decode + @return: decoded string + + @raise TypeError: if the string can not be decoded + @note: this function handles non-standard encoding as used by freenet (see: freenet/support/base64.java) + """ + # freenet uses - for + and ~ for / + altchars = '-~' + + # padding may be ommitted or not + padding = 4 - len(string) % 4 + if padding: + string += '=' * padding + return base64.b64decode(string, altchars) + +#**************************************************************************************** +# freenet keys +# +# KeyType@32 bytes hash, 32 bytes encryption key, 5 bytes extra +# +# all byte components are base64 encoded. Freenet uses base64 without padding +# and uses the following altchars for urlsave encode: - for + and ~ for / +# see: freenet/support/base64.java +# +# so a key as the user gets it to see is: +# KeyType@43 bytes, 43 bytes, 7 bytes ..of [A-Za-z0-9\-~] +# +#*************************************************************************************** +KeyPat = re.compile( +r''' +^(CHK | SSK | SVK | USK) @ +( + [a-z0-9\-~]{43}, + [a-z0-9\-~]{43}, + [a-z0-9\-~]{7} +) +''', re.I | re.X) #TODO: ignorecase? + + +def keyType(uri): + """Returns the ky type of a freenet key or None if the type could not be determined""" + if uri.startswith('KSK@'): + return 'KSK' + result = KeyPat.match(uri) + if result is None: + return None + return result.group(1) + #********************************************************************* # #********************************************************************* - +def stripUri(uri): + """Strips scheme and location parts from an uri""" + result = urlparse.urlsplit(uri)[2] + result = result.lstrip('/') + return result + + class Uri(object): - """Wrapper class for freenet uris""" + KeyTypeSSK = 'SSK' + KeyTypeKSK = 'KSK' + KeyTypeCHK = 'CHK' + KeyTypeUSK = 'USK' + KeyTypeSVK = 'SVK' + KeyTypeInvalid = '' + KeysTypesAll = (KeyTypeSSK, KeyTypeKSK, KeyTypeCHK, KeyTypeUSK, KeyTypeSVK) - KeySSK = 'SSK@' - KeyKSK = 'KSK@' - KeyCHK = 'CHK@' - KeyUSK = 'USK@' - KeySVK = 'SVK@' - KeyUnknown = '' - KeysAll = (KeySSK, KeyKSK, KeyCHK, KeyUSK, KeySVK) - - ReUriPattern = re.compile('(%s.*?)(?= |\Z)' % '.*?|'.join(KeysAll), re.I) - ReKeyPattern = re.compile('(%s)' % '|'.join(KeysAll), re.I) - def __init__(self, uri): - """ - @param uri: uri to wrap - @param cvar ReUriPattern: pattern matching a freenet uri - @param cvar ReKeyPattern: pattern matching the key type of a freenet uri + self.uri = stripUri(uri) - @note: any dfecorations prefixing the freenet part of the uri uri are stripped if possible + result = keyType(self.uri) + self.keyType = self.KeyTypeInvalid if result is None else result + + def __nonzero__(self): + return self.keyType != self.KeyTypeInvalid - >>> uri = FcpUri('freenet:SSK@foo/bar') - >>> str(uri) - 'SSK@foo/bar' - >>> uri.keyType() == FcpUri.KeySSK - True - >>> uri.split() - ('SSK@foo', 'bar') - >>> uri.fileName() - 'bar' - >>> uri = FcpUri('http://SSK@foo/bar') - >>> str(uri) - 'SSK@foo/bar' - - # uris not containing freenet keys are left unchanged - >>> uri = FcpUri('http://foo/bar') - >>> str(uri) - 'http://foo/bar' - >>> uri.keyType() == FcpUri.KeyUnknown - True - >>> uri.split() - ('http://foo/bar', '') - >>> uri.fileName() - 'http://foo/bar' - - """ - self.uri = uri - - result = self.ReUriPattern.search(uri) - if result is not None: - self.uri = result.group(0) - - def __str__(self): - return str(self.uri) - - def __unicode__(self): - return unicode(self.uri) - - def keyType(self): - """Retuns the key type of the uri - @return: one of the Key* consts - """ - result = self.ReKeyPattern.search(self.uri) - if result is not None: - return result.group(0).upper() - return self.KeyUnknown - def split(self): """Splits the uri @return: tuple(freenet-key, file-name) @@ -86,6 +97,7 @@ return head, tail return self.uri, '' + def fileName(self): """Returns the filename part of the uri @return: str @@ -94,4 +106,6 @@ if tail: return tail return self.uri - + + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ju...@us...> - 2008-01-29 11:28:05
|
Revision: 85 http://fclient.svn.sourceforge.net/fclient/?rev=85&view=rev Author: jurner Date: 2008-01-29 03:28:06 -0800 (Tue, 29 Jan 2008) Log Message: ----------- Started implementing python <--> fcp value type mapping Modified Paths: -------------- trunk/sandbox/fcp/fcp2_0_client.py Modified: trunk/sandbox/fcp/fcp2_0_client.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_client.py 2008-01-29 11:27:31 UTC (rev 84) +++ trunk/sandbox/fcp/fcp2_0_client.py 2008-01-29 11:28:06 UTC (rev 85) @@ -46,7 +46,6 @@ import atexit -import base64 import cPickle import logging import os @@ -157,8 +156,6 @@ ConnectReason, DebugVerbosity, DisconnectReason, - FcpTrue, - FcpFalse, FetchError, FilenameCollision, InsertError, @@ -229,7 +226,7 @@ ): """ @param conectionName: name of the connection or None to use an arbitrary connection name - @param debugVerbosity: verbosity level for debugging. Default is L{Verbosity.Warning} + @param debugVerbosity: verbosity level for debugging. Default is L{DebugVerbosity.Warning} @ivar events: events the client supports """ @@ -267,8 +264,8 @@ # persistent params that will go into identifier 'FcSubType': msgSubType, # identifies sub message types - 'FcInitTime': self.fcpTime(initTime), # when was the request started? - 'FcFilenameCollision': filenameCollision, # handle fielanem collisions? + 'FcInitTime': initTime, # when was the request started? + 'FcFilenameCollision': filenameCollision, # handle fielanem collisions? 'FcPersistentUserData': persistentUserData, # any user defined persistent data # non persistent params @@ -349,9 +346,6 @@ if fcParams is None: return None - # have to re-adjust initTime to python time - fcParams[self.FcParams.IInitTime] = self.pythonTime(fcParams[self.FcParams.IInitTime]) - # add additional params to msg msg = self._addFcParamsToRequest( msg, @@ -370,45 +364,8 @@ return msg - - ############################################################### ## - ## Fcp <--> Python mappings - ## - ############################################################### - def fcpBool(self, pythonBool): - """Converts a python bool to a fcp bool - @param pythonBool: (bool) - @return: (str) 'true' or 'false' - """ - return self.FcpTrue if pythonBool else self.FcpFalse - - def fcpTime(self, pythonTime): - """Converts a python time value to a fcp time value - @param fcpTime: (int, str) time to convert - @raise ValueError: if the python time could not be converted - @return: (int) fcp time - """ - return pythonTime * 1000 - - def pythonBool(self, fcpBool): - """Converts a fcp bool to a python bool - @param pythonBool: 'true' or 'false' - @return: (bool) True or False - """ - return fcpBool == self.FcpTrue - - def pythonTime(self, fcpTime): - """Converts a fcp time value to a python time value - @param fcpTime: (int, str) time to convert - @raise ValueError: if the fcp time could not be converted - @return: (int) python time - """ - return int(fcpTime) / 1000 - - ############################################################### - ## ## connection related methods ## ############################################################### @@ -520,7 +477,7 @@ def setDebugVerbosity(self, debugVerbosity): """Sets the verbosity level of the client - @note: see L{Verbosity} + @note: see L{DebugVerbosity} """ self._log.setLevel(debugVerbosity) @@ -604,14 +561,14 @@ elif code == self.ProtocolError.DDADenied: ddaRequestMsg = self.Message(self.Message.MessageTestDDARequest) if initialRequest.name == self.Message.MessageClientGet: - ddaRequestMsg['WantWriteDirectory'] = self.FcpTrue + ddaRequestMsg['WantWriteDirectory'] = True directory = os.path.dirname(initialRequest['Filename']) else: #TODO: determine directory for other cases raise RuntimeError(NotImplemented) - ddaRequestMsg['WantReadDirectory'] = self.FcpTrue + ddaRequestMsg['WantReadDirectory'] = True directory = None ddaRequestMsg['Directory'] = directory @@ -621,7 +578,7 @@ 'Directory': directory, 'Replied': False, 'TmpFile': None, - 'WantWrite': self.pythonBool(ddaRequestMsg.get('WantWriteDirectory', self.FcpFalse)), + 'WantWrite': ddaRequestMsg.get('WantWriteDirectory', False), 'ErrorMsg': msg, } self._ddaTests.append(initialRequest) @@ -729,15 +686,15 @@ # check if test was sucessful testFailed = False if wantWrite: - testFailed = not self.pythonBool(msg.params.get('WriteDirectoryAllowed', self.FcpFalse) ) + testFailed = not msg.params.get('WriteDirectoryAllowed', False) else: - testFailed = not self.pythonBool(msg.params.get('ReadDirectoryAllowed', self.FcpFalse) ) + testFailed = not msg.params.get('ReadDirectoryAllowed', False) if testFailed: #TODO: check if errorMsg gives reasonable feedback - del self._request[initialRequest['Identifier']] + del self._requests[initialRequest['Identifier']] initialRequest['FcStatus'] = self.Message.StatusError | self.Message.StatusRemoved initialRequest['FcErrorMessage'] = initialRequest['FcTestDDA']['ErrorMsg'] self.events.ProtocolError(initialRequest) @@ -801,8 +758,8 @@ if code == self.FetchError.TooBig and initialRequest['FcSubType'] == self.Message.SubTypeGetKeyInfo: initialRequest['FcStatus'] = self.Message.StatusComplete initialRequest['FcMetadataContentType'] = msg.get('ExpectedMetadata.ContentType', '') - initialRequest['FcDataLength'] = msg.get('ExpectedDataLength', '') - initialRequest['FcProgressCompleted'] = self.FcpTrue + initialRequest['FcDataLength'] = msg.get('ExpectedDataLength', -1) + initialRequest['FcProgressCompleted'] = True #TODO: check if Fcp removed the request self.events.RequestCompleted(initialRequest) @@ -955,10 +912,6 @@ return True elif msg.name == self.Message.MessagePeerNote: - note = msg.get('NoteText', '') - if note: - note = base64.decodestring(note) - msg['NoteText'] = note self.events.PeerNote(msg) return True @@ -1068,13 +1021,13 @@ ######################################################### #TODO: WithDefault never returns defaults def getConfig(self, - withCurrent=consts.FcpTrue, - withDefaults=consts.FcpTrue, - withExpertFlag=consts.FcpTrue, - withForceWriteFlag=consts.FcpTrue, - withSortOrder=consts.FcpTrue, - withShortDescription=consts.FcpTrue, - withLongDescription=consts.FcpTrue, + withCurrent=True, + withDefaults=True, + withExpertFlag=True, + withForceWriteFlag=True, + withSortOrder=True, + withShortDescription=True, + withLongDescription=True, ): """ @event: ConfigData(event, msg) @@ -1155,9 +1108,9 @@ uri, allowedMimeTypes=None, - binaryBlob=consts.FcpFalse, - dsOnly=consts.FcpFalse, - ignoreDS=consts.FcpFalse, + binaryBlob=False, + dsOnly=False, + ignoreDS=False, maxRetries=None, maxSize=None, persistence=consts.Persistence.Connection, @@ -1171,9 +1124,9 @@ @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') @param allowedMimeTypes: (str) list of allowed mime types - @param binaryBlob: if FcpTrue, the file is retrieved as binary blob file - @param dsOnly: if FcpTrue, retrieves the file from the local data store only - @param ignoreDs: If FcpTrue, ignores the local data store + @param binaryBlob: if True, the file is retrieved as binary blob file + @param dsOnly: if True, retrieves the file from the local data store only + @param ignoreDs: If True, ignores the local data store @param maxRetries: (int) maximum number of retries or -1 to retry forver or None to leave it to the node to decide @param maxSize: (int) maximum size of the file in bytes or None to set no limited @param persistence: persistence of the request as one of the L{consts.Persistence} constants @@ -1195,7 +1148,7 @@ # Fcp params AllowedMimeTypes = allowedMimeTypes, BinaryBlob=binaryBlob, - Global=self.FcpFalse, + Global=False, DSOnly=dsOnly, Identifier=None, IgnoreDS=ignoreDS, @@ -1214,9 +1167,9 @@ filename, allowedMimeTypes=None, - binaryBlob=consts.FcpFalse, - dsOnly=consts.FcpFalse, - ignoreDS=consts.FcpFalse, + binaryBlob=False, + dsOnly=False, + ignoreDS=False, maxRetries=None, maxSize=None, persistence=consts.Persistence.Connection, @@ -1232,9 +1185,9 @@ @param filename: (full path) filename to store the file to @param allowedMimeTypes: (str) list of allowed mime types - @param binaryBlob: if FcpTrue, the file is retrieved as binary blob file - @param dsOnly: if FcpTrue, retrieves the file from the local data store only - @param ignoreDs: If FcpTrue, ignores the local data store + @param binaryBlob: if True, the file is retrieved as binary blob file + @param dsOnly: if True, retrieves the file from the local data store only + @param ignoreDs: If True, ignores the local data store @param maxRetries: (int) maximum number of retries or -1 to retry forver or None to leave it to the node to decide @param maxSize: (int) maximum size of the file in bytes or None to set no limited @param persistence: persistence of the request as one of the L{consts.Persistence} constants @@ -1258,7 +1211,7 @@ AllowedMimeTypes = allowedMimeTypes, BinaryBlob=binaryBlob, Filename=filename, - Global=self.FcpFalse, + Global=False, DSOnly=dsOnly, Identifier=None, IgnoreDS=ignoreDS, @@ -1275,8 +1228,8 @@ def getKeyInfo(self, uri, - dsOnly=consts.FcpFalse, - ignoreDS=consts.FcpFalse, + dsOnly=False, + ignoreDS=False, maxRetries=None, persistence=consts.Persistence.Connection, priorityClass=consts.Priority.Medium, @@ -1288,8 +1241,8 @@ @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') - @param dsOnly: if FcpTrue, retrieves the file from the local data store only - @param ignoreDs: If FcpTrue, ignores the local data store + @param dsOnly: if True, retrieves the file from the local data store only + @param ignoreDs: If True, ignores the local data store @param maxRetries: (int) maximum number of retries or -1 to retry forver or None to leave it to the node to decide @param persistence: persistence of the request as one of the L{consts.Persistence} constants @param priorityClass: priority of the request as one of the L{consts.Priority} consts @@ -1307,7 +1260,7 @@ consts.FilenameCollision.HandleNever, # Fcp params - Global=self.FcpFalse, + Global=False, DSOnly=dsOnly, Identifier=None, IgnoreDS=ignoreDS, @@ -1382,14 +1335,14 @@ DataLength=len(data), #EarlyEncode='false', #GetCHKOnly='false', - Global=self.FcpFalse, + Global=False, Identifier=None, MaxRetries=maxRetries, DontCompress=dontCompress, Persistence=persistence, TergetFilename=targetFilename, UploadFrom=self.UploadFrom.Direct, - Verbosity=self.Verbosity.ReportProgressAndCompression, + Verbosity=self.Verbosity.ReportProgress | self.Verbosity.ReportCompression, ) def putFile(self): @@ -1447,7 +1400,7 @@ msg = self.Message( self.Message.MessageModifyPersistentRequest, Identifier=initialRequest['Identifier'], - Global=self.FcpFalse, + Global=False, ) if persistentUserData is not None: initialRequest['FcPersistentUserData'] = persistentUserData @@ -1467,7 +1420,7 @@ initialRequest['FcStatus'] = self.Message.StatusRemoved self.sendMessage( self.Message.MessageRemovePersistentRequest, - Global=self.FcpFalse, + Global=False, Identifier=requestIdentifier, ) @@ -1578,6 +1531,7 @@ for nodeHello in c.connect(): pass if nodeHello is not None: + #for i in xrange(5): # c.next() @@ -1589,7 +1543,7 @@ identifier = c.getData( 'CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg', - #binaryBlob=c.FcpTrue, + #binaryBlob=True, ) for i in xrange(50): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ju...@us...> - 2008-01-29 11:27:27
|
Revision: 84 http://fclient.svn.sourceforge.net/fclient/?rev=84&view=rev Author: jurner Date: 2008-01-29 03:27:31 -0800 (Tue, 29 Jan 2008) Log Message: ----------- Started implementing python <--> fcp value type mapping Modified Paths: -------------- trunk/sandbox/fcp/fcp2_0_message.py Modified: trunk/sandbox/fcp/fcp2_0_message.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_message.py 2008-01-29 11:27:08 UTC (rev 83) +++ trunk/sandbox/fcp/fcp2_0_message.py 2008-01-29 11:27:31 UTC (rev 84) @@ -1,6 +1,8 @@ """Freennet Client Protocol message""" import socket + +from fcp2_0_consts import MessageParamTypes #******************************************************************************** # #******************************************************************************** @@ -151,6 +153,7 @@ msg = clss(None) buf = [] + paramTypes = None #TODO: to buffer or not to buffer? while True: @@ -176,6 +179,8 @@ # first line == message name if msg.name is None: msg.name = line + paramTypes = MessageParamTypes.get(line, None) + # get data member elif line == 'Data': @@ -192,6 +197,13 @@ # get next paramater else: head, sep, tail = line.partition('=') + + # covert fcp to python value if necessary + if paramTypes is not None: + paramType = paramTypes.get(head, None) + if paramType is not None: + tail = paramType.fcpToPython(tail) + msg.params[head] = tail # TODO: errorchek params? #if not sep: pass @@ -217,9 +229,18 @@ def pprint(self): """Returns the message as nicely formated human readable string""" out = ['', '>>' + self.name, ] + paramTypes = MessageParamTypes.get(self.name, None) + for param, value in self.params.items(): if param.startswith(self.ParamPrefixPrivate): continue + + # convert python to fcp value if necessary + if paramTypes is not None: + paramType = paramTypes.get(param, None) + if paramType is not None: + value = paramType.pythonToFcp(value) + out.append('>> %s=%s' % (param, value)) out.append('>>EndMessage') return '\n'.join(out) @@ -235,9 +256,18 @@ def toString(self): """Returns the message as formated string ready to be send""" out = [self.name, ] + paramTypes = MessageParamTypes.get(self.name, None) + for param, value in self.params.items(): if param.startswith(self.ParamPrefixPrivate): continue + + # convert python to fcp value if necessary + if paramTypes is not None: + paramType = paramTypes.get(param, None) + if paramType is not None: + value = paramType.pythonToFcp(value) + out.append('%s=%s' % (param, value)) if self.data: assert 'DataLength' in self.params, 'DataLength member required' This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ju...@us...> - 2008-01-29 11:27:05
|
Revision: 83 http://fclient.svn.sourceforge.net/fclient/?rev=83&view=rev Author: jurner Date: 2008-01-29 03:27:08 -0800 (Tue, 29 Jan 2008) Log Message: ----------- Started implementing python <--> fcp value type mapping Modified Paths: -------------- trunk/sandbox/fcp/fcp2_0_consts.py Modified: trunk/sandbox/fcp/fcp2_0_consts.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_consts.py 2008-01-28 11:22:27 UTC (rev 82) +++ trunk/sandbox/fcp/fcp2_0_consts.py 2008-01-29 11:27:08 UTC (rev 83) @@ -1,6 +1,6 @@ -"""Freennet Client Protocol consts""" +"""Freennet Client Protocol consts and type mappings""" - +import base64 import logging #************************************************************************ # @@ -24,34 +24,34 @@ ) def __str__(self): return self.value - MaxArchiveRecursionExceeded = '1' - UnknownSplitfileMetadata = '2' - UnknownMetadata = '3' - InvalidMetadata = '4' - ArchiveFailure = '5' - BlockDecodeError = '6' - MaxMetadataLevelsExceeded = '7' - MaxArchiveRestartsExceeded = '8' - MaxRecursionLevelExceeded = '9' - NotAnArchve = '10' - TooManyMetastrings = '11' - BucketError = '12' - DataNotFound = '13' - RouteNotFound = '14' - RejectedOverload = '15' - TooManyRedirects = '16' - InternalError = '17' - TransferFailed = '18' - SplitfileError = '19' - InvalidUri = '20' + MaxArchiveRecursionExceeded = 1 + UnknownSplitfileMetadata = 2 + UnknownMetadata = 3 + InvalidMetadata = 4 + ArchiveFailure = 5 + BlockDecodeError = 6 + MaxMetadataLevelsExceeded = 7 + MaxArchiveRestartsExceeded = 8 + MaxRecursionLevelExceeded = 9 + NotAnArchve = 10 + TooManyMetastrings = 11 + BucketError = 12 + DataNotFound = 13 + RouteNotFound = 14 + RejectedOverload = 15 + TooManyRedirects = 16 + InternalError = 17 + TransferFailed = 18 + SplitfileError = 19 + InvalidUri = 20 TooBig = '21' - MetadataTooBig = '22' - TooManyBlocks = '23' - NotEnoughMetastrings = '24' - Canceled = '25' - ArchiveRestart = '26' - PermanentRedirect = '27' - NotAllDataFound = '28' + MetadataTooBig = 22 + TooManyBlocks = 23 + NotEnoughMetastrings = 24 + Canceled = 25 + ArchiveRestart = 26 + PermanentRedirect = 27 + NotAllDataFound = 28 class InsertError(Exception): @@ -68,14 +68,14 @@ ) def __str__(self): return self.value - InvalidUri = '1' - BucketError = '2' - InternalError = '3' - RejectedOverload = '4' - RouteNotFound = '5' - FatalErrorInBlocks = '6' - TooManyRetriesInBlock = '7' - RouteReallyNotFound = '8' + InvalidUri = 1 + BucketError = 2 + InternalError = 3 + RejectedOverload = 4 + RouteNotFound = 5 + FatalErrorInBlocks = 6 + TooManyRetriesInBlock = 7 + RouteReallyNotFound = 8 Collision = '9' Canceled = '10' @@ -94,37 +94,37 @@ ) def __str__(self): return self.value - ClientHelloMustBeFirst = '1' - NoLateClientHellos = '2' - MessageParseError = '3' - UriParseError = '4' - MissingField = '5' - ErrorParsingNumber = '6' - InvalidMessage = '7' - InvalidField = '8' - FileNotFound = '9' - DiskTargetExists = '10' # handled: - SameDirectoryExpected = '11' - CouldNotCreateFile = '12' - CouldNotWriteFile = '13' - CouldNotRenameFile = '14' - NoSuchIdentifier = '15' - NotSupported = '16' - InternalError = '17' - ShuttingDown = '18' # handled: - NoSuchNodeIdentifier = '19' # Unused since 995 - UrlParseError = '20' - ReferenceParseError = '21' - FileParseError = '22' - NotAFile = '23' - AccessDenied = '24' - DDADenied = '25' # handled: - CouldNotReadFile = '26' - ReferenceSignature = '27' - CanNotPeerWithSelf = '28' + ClientHelloMustBeFirst = 1 + NoLateClientHellos = 2 + MessageParseError = 3 + UriParseError = 4 + MissingField = 5 + ErrorParsingNumber = 6 + InvalidMessage = 7 + InvalidField = 8 + FileNotFound = 9 + DiskTargetExists = 10 # handled: + SameDirectoryExpected = 11 + CouldNotCreateFile = 12 + CouldNotWriteFile = 13 + CouldNotRenameFile = 14 + NoSuchIdentifier = 15 + NotSupported = 16 + InternalError = 17 + ShuttingDown = 18 # handled: + NoSuchNodeIdentifier = 19 # Unused since 995 + UrlParseError = 20 + ReferenceParseError = 21 + FileParseError = 22 + NotAFile = 23 + AccessDenied = 24 + DDADenied = 25 # handled: + CouldNotReadFile = 26 + ReferenceSignature = 27 + CanNotPeerWithSelf = 28 PeerExists = '29' - OpennetDisabled = '30' - DarknetPeerOnly = '31' + OpennetDisabled = 30 + DarknetPeerOnly = 31 # others @@ -227,15 +227,320 @@ class Verbosity: - ReportCompletion = '0' - ReportProgress = '1' - ReportProgressAndCompression = '513' # 1 | 512 # ClientPut only + ReportCompletion = 0x0 + ReportProgress = 0x1 + ReportCompression = 0x200 +#************************************************************************************* +# python <--> fcp value mappings +#************************************************************************************* +#TODO: reqork Clss.validateFcpValue +FcpTrue = 'true' +FcpFalse = 'false' +class FcpTypeBool(object): + + @classmethod + def pythonToFcp(clss, value): + return FcpTrue if value else FcpFalse + + @classmethod + def fcpToPython(clss, value): + return value == FcpTrue + + @classmethod + def validateFcpValue(clss, value): + if value in ('true', 'false'): + return str(value) + return None +class FcpTypeInt(object): + + @classmethod + def pythonToFcp(clss, value): + return str(value) + + @classmethod + def fcpToPython(clss, value): + return int(value) + + @classmethod + def validateFcpValue(clss, value): + try: + return int(value) + except ValueError: + return None + +# GetFailed sets the ExpectedDataLenght field to '' (empty string). Fix this to show up as -1 +class FcpTypeInt_GetFailed_ExpectedDataLenght(object): + + @classmethod + def pythonToFcp(clss, value): + return str(value) + + @classmethod + def fcpToPython(clss, value): + if value == '': + return -1 + return int(value) + + @classmethod + def validateFcpValue(clss, value): + if value == '': + return -1 + try: + return int(value) + except ValueError: + return None + + +class FcpTypeIntWithBounds(object): + + def __init__(self, lowerBound, upperBound): + self.lowerBound = lowerBound + self.upperBound = upperBound + + def pythonToFcp(self, value): + return str(value) + + def fcpToPython(self, value): + return int(value) + + def validateFcpValue(self, value): + try: + n = int(value) + except ValueError: + return None + + if self.lowerBound is not None: + if n >= self.lowerBound: + return n + if self.upperBound is not None: + if n <= self.upperBound: + return n + + return None + + + +class FcpTypeBase64EncodedString(object): + + @classmethod + def pythonToFcp(clss, value): + return base64.encode(value) + + @classmethod + def fcpToPython(clss, value): + return base64.decode(value) + + @classmethod + def validateFcpValue(clss, value): + pass + #TODO: no idea + + # we add a few private params... + ParamPrefixPrivate = 'Fc' + + + +class FcpTypeTime(object): + + @classmethod + def pythonToFcp(clss, value): + return str(value * 1000) + + @classmethod + def fcpToPython(clss, value): + return int(value) / 1000 + + @classmethod + def validateFcpValue(clss, value): + try: + return self.fcpToPython(value) + except ValueEror: + return none + +#*************************************************************************************** +# +# Mapping from message params to param types +# +# ...being lazy here, only types that are not strings are declared +# +#*************************************************************************************** +MessageParamTypes = { + + # client messages + + 'ListPeers': { + 'WithMetadata': FcpTypeBool, + 'WithVolantile': FcpTypeBool, + }, + + #'AddPeer': {}, # ??? check + + 'ModifyPeer': { + 'AllowLocalAddresses': FcpTypeBool, + 'IsDisabled': FcpTypeBool, + 'ListenOnly': FcpTypeBool, + }, + + 'ModifyPeerNote': { + 'NoteText': FcpTypeBase64EncodedString, + }, + + 'GetNode': { + 'GiveOpennetRef': FcpTypeBool, + 'WithPrivate': FcpTypeBool, + 'WithVolatile': FcpTypeBool, + }, + 'GetConfig': { + 'WithCurrent': FcpTypeBool, + 'WithDefaults': FcpTypeBool, + 'WithSortOrder': FcpTypeBool, + 'WithExpertFlag': FcpTypeBool, + 'WithForceWriteFlag': FcpTypeBool, + 'WithShortDescription': FcpTypeBool, + 'WithLongDescription': FcpTypeBool, + }, + + #'ModifyConfig': # ??? check + + 'TestDDARequest': { + 'WantReadDirectory': FcpTypeBool, + 'WantWriteDirectory': FcpTypeBool, + }, + 'ClientPut': { + 'BinaryBlob': FcpTypeBool, + 'DontCompress': FcpTypeBool, + 'EarlyEncode': FcpTypeBool, + 'GetCHKOnly': FcpTypeBool, + 'Global': FcpTypeBool, + 'MaxRetries': FcpTypeInt, + 'Verbosity': FcpTypeInt, + }, + 'ClientGet': { + 'BinaryBlob': FcpTypeBool, + 'Global': FcpTypeBool, + 'IgnoreDS': FcpTypeBool, + 'DSOnly': FcpTypeBool, + 'MaxSize': FcpTypeInt, + 'MaxTempSize': FcpTypeInt, + 'Verbosity': FcpTypeInt, + }, + 'SubscribeUSK': { + 'DontPoll': FcpTypeBool, + }, + 'WatchGlobal': { + 'Enabled': FcpTypeBool, + 'VerbosityMask': FcpTypeInt, + }, + 'GetRequestStatus': { + 'Global': FcpTypeBool, + 'OnlyData': FcpTypeBool, + }, + 'RemopvePersistentRequest': { + 'Global': FcpTypeBool, + }, + 'ModifyPersistentRequest': { + 'Global': FcpTypeBool, + }, + + + # node messages + + 'NodeHello': { + 'CompressionCodecs': FcpTypeInt, + 'Testnet': FcpTypeBool, + + #TODO: ExtBuild et al. ??? + + }, + + #'Peer': {}, # ??? check + + 'PeerNote': { + 'NoteText': FcpTypeBase64EncodedString, + }, + + + #'NodeData': {}, # ??? check + #'ConfigData': {}, # ??? check + + 'TestDDAComplete': { + 'ReadDirectoryAllowed': FcpTypeBool, + 'WriteDirectoryAllowed': FcpTypeBool, + }, + 'PutFetchable': { + 'Global': FcpTypeBool, + }, + 'DataFound': { + 'Global': FcpTypeBool, + 'DataLength': FcpTypeInt, + }, + 'AllData': { + 'Global': FcpTypeBool, + #NOTE: we ignore startup and completion time here, as long as it is not passed in all messages + + }, + 'FinishedCompression': { + 'OriginalSize': FcpTypeInt, + 'CompressedSize': FcpTypeInt, + }, + 'SimpleProgress': { + 'Total': FcpTypeInt, + 'Required': FcpTypeInt, + 'Failed': FcpTypeInt, + 'FatalyFailed': FcpTypeInt, + 'Succeeded': FcpTypeInt, + 'Finalized': FcpTypeBool, + }, + 'PersistentRequestRemoved': { + 'Global': FcpTypeBool, + }, + 'PersistentRequestModified': { + 'Global': FcpTypeBool, + }, + 'PutFailed': { + 'Global': FcpTypeBool, + 'Code': FcpTypeInt, + }, + 'GetFailed': { + 'Code': FcpTypeInt, + 'ExpectedDataLength': FcpTypeInt_GetFailed_ExpectedDataLenght, + 'Fatal': FcpTypeBool, + 'FinalizedExpected': FcpTypeBool, + 'Global': FcpTypeBool, + }, + 'ProtocolError': { + 'Code': FcpTypeInt, + 'Global': FcpTypeBool, + }, + + 'IdentifierCollision': { + 'Global': FcpTypeBool, + }, + 'SubscribedUSKUpdate': { + 'Edition': FcpTypeInt, + }, + + + + } + +MessageParamTypes['ClientPutDiskDir'] = MessageParamTypes['ClientPut'] +MessageParamTypes['ClientComplexDir'] = MessageParamTypes['ClientPut'] + +# TODO: "Started" param? Think we simply ignore it +MessageParamTypes['PersistentGet'] = MessageParamTypes['ClientGet'] +MessageParamTypes['PersistentPut'] = MessageParamTypes['ClientPut'] + + + + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ju...@us...> - 2008-01-28 11:22:23
|
Revision: 82 http://fclient.svn.sourceforge.net/fclient/?rev=82&view=rev Author: jurner Date: 2008-01-28 03:22:27 -0800 (Mon, 28 Jan 2008) Log Message: ----------- continued working on the client Modified Paths: -------------- trunk/sandbox/fcp/fcp2_0_client.py Modified: trunk/sandbox/fcp/fcp2_0_client.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_client.py 2008-01-28 11:21:45 UTC (rev 81) +++ trunk/sandbox/fcp/fcp2_0_client.py 2008-01-28 11:22:27 UTC (rev 82) @@ -11,6 +11,40 @@ """ + +#Bug reports filed and open: +#-------------------------------------------------------------------------------------------------------------------------------------------- +# [0001931: Send EndListPersistentRequests following client connect] +# +# PendingRequests currently get lost if a.) the node goes down b.) if the client goes down unexpectedly. +# This affects IdentifierCollision + FilenameCollision + ClientPut when a SSK needs to be created first +# +# we can handle this case none short of maintaining a file keeping messages. But still there is the +# problem of knowing when the node has actually registered a request. The node does not send +# an EndListPersistentRequests on connect so it is impossible to tell when or if to restore one of +# the pending requests we stored. +#--------------------------------------------------------------------------------------------------------------------------------------------- +# [0001893: CloseConnectionDuplicateClientName bug or feature?] +# +# CloseConnectionDuplicateClientName +# currently fcp takes down a our connection if another client (...) uses the same connection name. +#---------------------------------------------------------------------------------------------------------------------------------------------- +# [0001888: explicite connection locale] +# +# Many strings are already translated by freenet, but there is no way to tell the node wich language +# to use to talk to a client. Maybe a good idea, maybe not. +#----------------------------------------------------------------------------------------------------------------------------------------------- +# [0001781: unregister directories registered via TestDDARequest] +# +# With current state of the art DDA handling it is not possiblr to unregister directories (may pile +# up in memory over time). +#------------------------------------------------------------------------------------------------------------------------------------------------ +# [0002019: Socket dies if first message is not ClientHello] +# +# minor one +#------------------------------------------------------------------------------------------------------------------------------------------------- + + import atexit import base64 import cPickle @@ -135,6 +169,7 @@ Priority, ProtocolError, ReturnType, + UploadFrom, Verbosity, ) from fcp2_0_message import Message @@ -154,6 +189,7 @@ 'RequestCompleted', # the request is not removed neither from node nor client 'RequestFailed', # the request is already removed from node and client + 'RequestFetchable', 'RequestModified', 'RequestProgress', 'RequestRestored', @@ -174,13 +210,14 @@ 'PeerNote', + # others + 'SSKKeypair', + ############################### 'ProtocolError', - 'PersistentGet', + - # others - 'SSKKeypair', ) @@ -191,15 +228,15 @@ debugVerbosity=None, ): """ - @param conectionName: name of the connection + @param conectionName: name of the connection or None to use an arbitrary connection name @param debugVerbosity: verbosity level for debugging. Default is L{Verbosity.Warning} @ivar events: events the client supports """ - self._connectionName = connectionName - self._ddaTests = [] - self._identifierMapping = {} # mapping from identifiers to FcRequestIdentifiers - self._requests = {} + self._connectionName = self.setConnectionName(connectionName) + self._ddaTests = [] # currently running DDA tests (request0, ... requestN) + self._sskRequests = {} # currently pending ssk requests (sskIdentifier --> request) + self._requests = {} # currently running requests (requestIdentifier --> request) self._log = logging.getLogger(self.__class__.__name__) self._socket = None @@ -208,7 +245,7 @@ self.setDebugVerbosity(self.DebugVerbosity.Warning if debugVerbosity is None else debugVerbosity) atexit.register(self.close) - + ############################################################### ## @@ -253,6 +290,8 @@ # params from PersistentRequestModified 'FcModified': {}, + # params for DDA test + 'FcTestDDA': {}, # params for SimpleProgress 'FcProgressTotal': '0', @@ -284,8 +323,6 @@ @return: (str) uuid @note: the identifier returned is unique to the client but may not be unique to the node """ - - # we store FcRequestIdentifier and additional params in ClientToken identifier = self.FcParams.newUuid() # add additional params to msg @@ -384,12 +421,16 @@ self._socket.close() self._socket = None - # clean left over tmp files + # clean left over DDA test tmp files for initialRequest in self._ddaTests: - if initialRequest['FcTestDDA']['TmpFile'] is not None: + if initialRequest['FcTestDDA'].get('TmpFile', None) is not None: saveRemoveFile(initialRequest['FcTestDDA']['TmpFile']) - - + + self._ddaTests = [] + self._requests = {} + self._sskRequests = {} + + def connect(self, host=DefaultFcpHost, port=DefaultFcpPort, duration=20, timeout=0.5): """Iterator to stablish a connection to a freenet node @param host: (str) host of th node @@ -402,6 +443,10 @@ """ self._log.info(self.LogMessages.Connecting) + # try to Connect socket + if self._socket is not None: + self.close() + # poll untill freenet responds timeElapsed = 0 while timeElapsed <= duration: @@ -425,7 +470,7 @@ # So take it over here. self.sendMessage( self.Message.MessageClientHello, - Name=self._connectionName if self._connectionName is not None else uuid.uuid_time(), + Name=self._connectionName, ExpectedVersion=self.Version, ) while timeElapsed <= duration: @@ -458,6 +503,21 @@ raise StopIteration + def getConnectionName(self): + """Returns the connection name used by the client + @return: (str) connection name + """ + return self._connectionName + + def setConnectionName(self, connectionName=None): + """Sets the connection name to be used by the client + @param connectionName: (str) connection name or None to use an arbitrary connection name + @return: (str) connection name + """ + self._connectionName = uuid.uuid_time() if connectionName is None else connectionName + return self._connectionName + + def setDebugVerbosity(self, debugVerbosity): """Sets the verbosity level of the client @note: see L{Verbosity} @@ -490,14 +550,13 @@ @param msg: (Message) to handle @return: True if the message was handled, False otherwise """ + + CancelPersistentRequests = 0 # for testing... if True, cancels all PersistentRequests + if msg.name == self.Message.MessageClientSocketTimeout: return True self._log.debug(self.LogMessages.MessageReceived + msg.pprint()) - # check if we have a corrosponding initial message - fcRequestIdentifier = None - initialRequest = None - # check if we have an initial request corrosponding to msg requestIdentifier = msg.get('Identifier', None) initialRequest = None if requestIdentifier is None else self._requests.get(requestIdentifier, None) @@ -666,8 +725,6 @@ if initialRequest['FcTestDDA']['TmpFile'] is not None: saveRemoveFile(initialRequest['FcTestDDA']['TmpFile']) wantWrite = initialRequest.params['FcTestDDA']['WantWrite'] - errorMsg = initialRequest['FcTestDDA']['ErrorMsg'] - del initialRequest.params['FcTestDDA'] # check if test was sucessful testFailed = False @@ -678,12 +735,11 @@ if testFailed: - #TODO: check if Fcp removed the request #TODO: check if errorMsg gives reasonable feedback - del self._request[fcRequestIdentifier] + del self._request[initialRequest['Identifier']] initialRequest['FcStatus'] = self.Message.StatusError | self.Message.StatusRemoved - initialRequest['FcErrorMessage'] = errorMsg + initialRequest['FcErrorMessage'] = initialRequest['FcTestDDA']['ErrorMsg'] self.events.ProtocolError(initialRequest) return True @@ -767,8 +823,6 @@ # Fcp does no good job in handling persistent requests and identifiers. Already dropped some # notes and reports regarding this. See freenet-tech mailing list [Fcp notes and issues] - CancelPersistentRequests = 0 # for testing... if True, cancels all persistent requests - # unknown request... try to restore it if initialRequest is None: restoredRequest = self._restorePersistentRequestFromNode(msg) @@ -852,6 +906,37 @@ self.events.RequestProgress(initialRequest) return True + ## put related + + elif msg.name == self.Message.MessageURIGenerated: + if initialRequest is None: # something went wrong + return False + initialRequest['URI'] = msg['URI'] + return True + + + elif msg.name == self.Message.MessagePutFetchable: + if initialRequest is None: + # something went wrong + return False + + self.events.RequestFetchable(initialRequest) + return True + + + elif msg.name == self.Message.MessagePutSuccessful: + if initialRequest is None: + # something went wrong + return False + + # TODO: StartupTime and CompletionTime are passed, but + # as long as no corrosponding params are passed in DataFound + # we ignore them + initialRequest['URI'] = msg['URI'] + self.events.RequestCompleted(initialRequest) + return True + + #################################################### ## ## Peer related messages @@ -892,18 +977,26 @@ #################################################### elif msg.name == self.Message.MessageSSKKeypair: if initialRequest is None: - return False + self.events.SSKKeypair(msg) + return True - #TODO: maybe we need a mapping from SSKKeypair to pending request - initialRequest['Uri'] = msg['InsertUri'] initialRequest['FcRequestUri'] = msg['RequestUri'] - self.sendMessageEx(initialRequest) return True + + elif msg.name == self.Message.MessageCloseConnectionDuplicateClientName: + msg = self.Message( + self.Message.MessageClientDisconnected, + DisconnectReason=DisconnectReason.DuplicateClientName, + ) + self.events.ClientDisconnect(msg) + return True + + # default return False @@ -1018,6 +1111,46 @@ ## ClientGet related methods ## ######################################################## + def clientGet(self, + uri, + messageSubType, + userData, + persistentUserData, + filenameCollision, + **messageParams + ): + """Requests a key from the node + @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') + @param messageSubType: one of the Message.SubType* consts to the desired request + @param userData: any non persistent data to associate to the request or None + @param persistentUserData: any string to associate to the request as persistent data or None + @param filenameCollision: what to do if the disk target alreaady exists. One of the FilenameCollision.* consts + @param messageParams: keyword arguments to pass along with the ClientGet message (uppercase first letter!!). + If the value of a keyword is None, it is ignored. + + @return: (str) identifier of the request + + + """ + uri = self.Uri(uri).uri + msg = self.Message(self.Message.MessageClientGet, URI=uri) + for paramName, value in messageParams.items(): + if value is not None: + msg[paramName] = value + + self._registerRequest( + msg, + userData, + messageSubType, + time.time(), + persistentUserData, + filenameCollision=filenameCollision, + ) + self.sendMessageEx(msg) + return msg['Identifier'] + + + def getData(self, uri, @@ -1052,45 +1185,30 @@ @return: (str) request identifier @note: if a filename collision is handled a RequestFilenameChanged event is triggered """ + return self.clientGet( + uri, + self.Message.SubTypeGetData, + userData, + persistentUserData, + consts.FilenameCollision.HandleNever, - msg = self.Message( - self.Message.MessageClientGet, - - BinaryBlob=binaryBlob, - Global=self.FcpFalse, - DSOnly=dsOnly, - - Identifier=None, - IgnoreDS=ignoreDS, - Persistence=persistence, - PriorityClass=priorityClass, - - ReturnType=self.ReturnType.Direct, - URI=self.Uri(uri).uri, - Verbosity=self.Verbosity.ReportProgress, - - #MaxTempSize=whatever, - #TempFilename=whatever - ) - if allowedMimeTypes is not None: - msg['AllowedMimeTypes'] = allowedMimeTypes - if maxRetries is not None: - msg['MaxRetries'] = maxRetries - if maxSize is not None: - msg['MaxSize'] = maxSize + # Fcp params + AllowedMimeTypes = allowedMimeTypes, + BinaryBlob=binaryBlob, + Global=self.FcpFalse, + DSOnly=dsOnly, + Identifier=None, + IgnoreDS=ignoreDS, + MaxRetries = maxRetries, + MaxSize = maxSize, + Persistence=persistence, + PriorityClass=priorityClass, + ReturnType=self.ReturnType.Direct, + URI=self.Uri(uri).uri, + Verbosity=self.Verbosity.ReportProgress, + ) - self._registerRequest( - msg, - userData, - self.Message.SubTypeGetData, - time.time(), - persistentUserData, - ) - self.sendMessageEx(msg) - return msg['Identifier'] - - def getFile(self, uri, filename, @@ -1129,52 +1247,37 @@ @return: (str) request identifier @note: if a filename collision is handled a RequestFilenameChanged event is triggered """ + return self.clientGet( + uri, + self.Message.SubTypeGetFile, + userData, + persistentUserData, + filenameCollision, - msg = self.Message( - self.Message.MessageClientGet, - - BinaryBlob=binaryBlob, - Filename=filename, - Global=self.FcpFalse, - DSOnly=dsOnly, - - Identifier=None, - IgnoreDS=ignoreDS, - Persistence=persistence, - PriorityClass=priorityClass, - - ReturnType=self.ReturnType.Disk, - URI=self.Uri(uri).uri, - Verbosity=self.Verbosity.ReportProgress, - - #MaxTempSize=whatever, - #TempFilename=whatever - ) - if allowedMimeTypes is not None: - msg['AllowedMimeTypes'] = allowedMimeTypes - if maxRetries is not None: - msg['MaxRetries'] = maxRetries - if maxSize is not None: - msg['MaxSize'] = maxSize + # Fcp params + AllowedMimeTypes = allowedMimeTypes, + BinaryBlob=binaryBlob, + Filename=filename, + Global=self.FcpFalse, + DSOnly=dsOnly, + Identifier=None, + IgnoreDS=ignoreDS, + MaxRetries = maxRetries, + MaxSize = maxSize, + Persistence=persistence, + PriorityClass=priorityClass, + ReturnType=self.ReturnType.Disk, + URI=self.Uri(uri).uri, + Verbosity=self.Verbosity.ReportProgress, + ) - self._registerRequest( - msg, - userData, - self.Message.SubTypeGetFile, - time.time(), - persistentUserData, - filenameCollision, - ) - self.sendMessageEx(msg) - return msg['Identifier'] - - def getKeyInfo(self, uri, dsOnly=consts.FcpFalse, ignoreDS=consts.FcpFalse, + maxRetries=None, persistence=consts.Persistence.Connection, priorityClass=consts.Priority.Medium, @@ -1187,6 +1290,7 @@ @param dsOnly: if FcpTrue, retrieves the file from the local data store only @param ignoreDs: If FcpTrue, ignores the local data store + @param maxRetries: (int) maximum number of retries or -1 to retry forver or None to leave it to the node to decide @param persistence: persistence of the request as one of the L{consts.Persistence} constants @param priorityClass: priority of the request as one of the L{consts.Priority} consts @param userData: any non persistent data to associate to the request @@ -1194,49 +1298,123 @@ @return: (str) request identifier """ - - # how to retrieve meta info about a key? - # ...idea is to provoke a GetFailed (TooBig) - - msg = self.Message( - self.Message.MessageClientGet, + # how to retrieve meta info about a key? ...idea is to provoke a GetFailed (TooBig) + return self.clientGet( + uri, + self.Message.SubTypeGetKeyInfo, + userData, + persistentUserData, + consts.FilenameCollision.HandleNever, + # Fcp params + Global=self.FcpFalse, DSOnly=dsOnly, - Global=self.FcpFalse, Identifier=None, IgnoreDS=ignoreDS, - + MaxRetries = maxRetries, MaxSize=self.MaxSizeKeyInfo, - Persistence=persistence, PriorityClass=priorityClass, ReturnType=self.ReturnType.Nothing, - URI=self.Uri(uri).uri, Verbosity=self.Verbosity.ReportProgress, ) + + + ######################################################## + ## + ## ClientPut related methods + ## + ######################################################## + def clientPut(self, + uri, + messageSubType, + userData, + persistentUserData, + data, + **messageParams): + + msg = self.Message(self.Message.MessageClientPut, URI=uri) + for paramName, value in messageParams.items(): + if value is not None: + msg[paramName] = value + if data is not None: + msg.data = data self._registerRequest( msg, userData, - self.Message.SubTypeGetKeyInfo, + messageSubType, time.time(), persistentUserData, + #filenameCollision=filenameCollision, ) self.sendMessageEx(msg) return msg['Identifier'] - - ######################################################## - ## - ## ClientPut related methods - ## - ######################################################## - def put(self): + + + #TODO: method names + + #CHK + def putData(self, + data, + + contentType=None, + dontCompress=None, + maxRetries=None, + persistence=consts.Persistence.Connection, + priorityClass=consts.Priority.Medium, + targetFilename=None, + + userData=None, + persistentUserData='', + ): + + return self.clientPut( + self.Uri.KeyCHK, + self.Message.SubTypePutData, + userData, + persistentUserData, + data, + + # fcp params + ContentType=contentType, + DataLength=len(data), + #EarlyEncode='false', + #GetCHKOnly='false', + Global=self.FcpFalse, + Identifier=None, + MaxRetries=maxRetries, + DontCompress=dontCompress, + Persistence=persistence, + TergetFilename=targetFilename, + UploadFrom=self.UploadFrom.Direct, + Verbosity=self.Verbosity.ReportProgressAndCompression, + ) + + def putFile(self): pass + + #KSK + def putNamedData(self): + pass + def putReference(self): + pass + #SSK + def putUpdatableData(self): + pass + + + #USK + def putVersionedData(self): + pass + + + ######################################################## ## ## request related methods @@ -1379,7 +1557,7 @@ """ while True: identifier = uuid.uuid_time() - if identifier not in self._requests: + if identifier not in self._sskRequests: break self.sendMessage( self.Message.MessageGenerateSSK, @@ -1427,7 +1605,7 @@ def testGetFile(): filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.jpg') - + print filename identifier = c.getFile( 'CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg', filename, @@ -1438,11 +1616,11 @@ for i in xrange(50): c.next() - #c.removeRequest(identifier) - #for i in xrange(5): - # c.next() + c.removeRequest(identifier) + for i in xrange(5): + c.next() - testGetFile() + #testGetFile() @@ -1457,10 +1635,25 @@ for i in xrange(5): c.next() - #testgetKeyInfo() + #testGetKeyInfo() + def testPutData(): + identifier = c.putData( + 'test123', + ) + + for i in xrange(1000): + c.next() + c.removeRequest(identifier) + for i in xrange(5): + c.next() + + #testPutData() + + + def testConfigData(): from fcp2_0_config import Config @@ -1478,7 +1671,7 @@ #testConfigData() - + def testNodeData(): def cb(event, msg): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ju...@us...> - 2008-01-28 11:21:40
|
Revision: 81 http://fclient.svn.sourceforge.net/fclient/?rev=81&view=rev Author: jurner Date: 2008-01-28 03:21:45 -0800 (Mon, 28 Jan 2008) Log Message: ----------- a few more consts Modified Paths: -------------- trunk/sandbox/fcp/fcp2_0_consts.py Modified: trunk/sandbox/fcp/fcp2_0_consts.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_consts.py 2008-01-28 11:20:59 UTC (rev 80) +++ trunk/sandbox/fcp/fcp2_0_consts.py 2008-01-28 11:21:45 UTC (rev 81) @@ -149,6 +149,7 @@ Shutdown = 1 SocketDied = 2 ConnectingFailed = 3 + DuplicateConnectionName = 4 class FilenameCollision: @@ -172,7 +173,7 @@ MessageSend = 'sending message' MessageReceived = 'received message' - KeyboardInterrupt = 'keyboard interrupt' + KeyboardInterrupt = 'keyboard interrupt' # kick out SocketDied = 'socket died' @@ -219,12 +220,22 @@ Nothing = 'none' +class UploadFrom: + Direct = 'direct' + Disk = 'disk' + Redirect = 'redirect' + + class Verbosity: ReportCompletion = '0' ReportProgress = '1' + ReportProgressAndCompression = '513' # 1 | 512 # ClientPut only + + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ju...@us...> - 2008-01-28 11:20:55
|
Revision: 80 http://fclient.svn.sourceforge.net/fclient/?rev=80&view=rev Author: jurner Date: 2008-01-28 03:20:59 -0800 (Mon, 28 Jan 2008) Log Message: ----------- ... Modified Paths: -------------- trunk/sandbox/fcp/fcp2_0_message.py Modified: trunk/sandbox/fcp/fcp2_0_message.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_message.py 2008-01-27 02:16:50 UTC (rev 79) +++ trunk/sandbox/fcp/fcp2_0_message.py 2008-01-28 11:20:59 UTC (rev 80) @@ -82,10 +82,10 @@ # some additional consts SubTypeNone = 0 - SubTypeGetKeyInfo = 1 + SubTypeGetData = 1 SubTypeGetFile = 2 - SubTypeGetData = 3 - SubTypePutFile = 4 + SubTypeGetKeyInfo = 3 + SubTypePutData = 4 SubTypePutDiskDir = 5 SubTypePutComplexDir = 6 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ju...@us...> - 2008-01-27 02:16:50
|
Revision: 79 http://fclient.svn.sourceforge.net/fclient/?rev=79&view=rev Author: jurner Date: 2008-01-26 18:16:50 -0800 (Sat, 26 Jan 2008) Log Message: ----------- added sanbox with fcp rewrite Added Paths: ----------- trunk/sandbox/ trunk/sandbox/fcp/ trunk/sandbox/fcp/001.py trunk/sandbox/fcp/LICENCE.MIT trunk/sandbox/fcp/README trunk/sandbox/fcp/__init__.py trunk/sandbox/fcp/boards/ trunk/sandbox/fcp/boards/__init__.py trunk/sandbox/fcp/boards/frost.py trunk/sandbox/fcp/fcp2_0_client.py trunk/sandbox/fcp/fcp2_0_config.py trunk/sandbox/fcp/fcp2_0_consts.py trunk/sandbox/fcp/fcp2_0_message.py trunk/sandbox/fcp/fcp2_0_params.py trunk/sandbox/fcp/fcp2_0_uri.py trunk/sandbox/fcp/fcp_lib/ trunk/sandbox/fcp/fcp_lib/__init__.py trunk/sandbox/fcp/fcp_lib/events.py trunk/sandbox/fcp/fcp_lib/namespace.py trunk/sandbox/fcp/fcp_lib/numbers.py trunk/sandbox/fcp/fcp_lib/uuid.py trunk/sandbox/fcp/oo1.py trunk/sandbox/fcp/test_fcp/ trunk/sandbox/fcp/test_fcp/__init__.py trunk/sandbox/fcp/test_fcp/dummy_socket.py trunk/sandbox/fcp/test_fcp/dummy_socket.pyc trunk/sandbox/fcp/test_fcp/test_fcp2_0_client.py trunk/sandbox/fcp/test_fcp/test_fcp2_0_config.py trunk/sandbox/fcp/test_fcp/test_fcp2_0_message.py Added: trunk/sandbox/fcp/001.py =================================================================== --- trunk/sandbox/fcp/001.py (rev 0) +++ trunk/sandbox/fcp/001.py 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1,107 @@ + +import random +import uuid +#********************************************************************************************* +# +#********************************************************************************************* +def validateFcpBool(value): + if value in ('true', 'false'): + return value + return None + +def validateFloat(value): + try: + return float(value) + except ValueError: + return None + +def validateInt(value): + try: + return int(value) + except ValueError: + return None + +def validateUuid(value): + result = uuid.UUID_EXACT_MATCH_PAT.match(value) + if result: + return result.group(0) + return None + +#********************************************************************************************* +# +#********************************************************************************************* +FcParamsSep = '\x01' + +FcParams = ( + ('FcSubType', validateInt), + ('FcInitTime', validateFloat), # can not take it from uuid cos requests may be resend multiple times + ('FcHandleCollisions', validateFcpBool), + ('FcCollisionHandled', validateFcpBool), + ('FcRequestIdentifier', validateUuid), + ('FcRandomBytes', validateInt), + ) + + +ISubType = 0 +IRequestIdentifier = 1 +IInitTime = 2 +IHandleCollisions = 3 +ICollisionHandled = 4 +IRandomBytes = 5 + +def paramsFromRequest(msg): + """ + + >>> params = [1, 123.456, 'false', 'false', uuid.uuid_time(), 123456789] + >>> identifier = FcParamsSep.join( [str(i) for i in params] ) + >>> result = paramsFromRequest({'Identifier': identifier}) + >>> result == params + True + + """ + identifier = msg.get('Identifier', None) + if identifier is None: + return None + + params = identifier.split(FcParamsSep) + if len(params) != len(FcParams): + return None + + for i, (paramName, paramValidator) in enumerate(FcParams): + result = paramValidator(params[i]) + if result is None: + return None + params[i] = result + + return params + +def identifierFromRequest(msg): + """ + + >>> msg = {'FcSubType':1, 'FcInitTime':1.234, 'FcHandleCollisions':'false', 'FcCollisionHandled':'flase', 'FcRequestIdentifier':uuid.uuid_time(), 'FcRandomBytes':1234567879} + >>> identifierFromRequest(msg) + + """ + + + params = [] + for paramName, paramValidator in FcParams: + params.append(msg[paramName]) + + params[-1] = random.getrandbits(32) # add some random bits to be able to + # handle IdentifierCollisions(FcRequestIdentifier + # remains the same, identifier kown to node changes) + + return FcParamsSep.join( [str(i) for i in params] ) + + +s = '1\x011.234\x01false\x01flase\x01{fc0125cc-c76f-11dc-9099-fce464f183f6}\x011234567879' +print len(s) +#********************************************************************************* +# +#********************************************************************************* +if __name__ == '__main__2': + import doctest + doctest.testmod() + + Added: trunk/sandbox/fcp/LICENCE.MIT =================================================================== --- trunk/sandbox/fcp/LICENCE.MIT (rev 0) +++ trunk/sandbox/fcp/LICENCE.MIT 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1,20 @@ + Fcp - a python wraper library for the freenet client protocol. See: [http://www.freenetproject.org] + +Copyright (c) 2008 J\xFCrgen Urner + + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file Added: trunk/sandbox/fcp/README =================================================================== --- trunk/sandbox/fcp/README (rev 0) +++ trunk/sandbox/fcp/README 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1,18 @@ +Fcp - a python wraper library for the freenet client protocol. See: [http://www.freenetproject.org] + + + + +Version history: + +******************************************************************* +0.1.0 +******************************************************************* +news: + + x. + +bugfixes: + + x. + Added: trunk/sandbox/fcp/__init__.py =================================================================== --- trunk/sandbox/fcp/__init__.py (rev 0) +++ trunk/sandbox/fcp/__init__.py 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1,11 @@ +"""Python wrapper for the freenet client protocol + +See: [http://www.freenetproject.org] +""" + + +__author__ = 'Juergen Urner' +__copyright__ = '(c) 2008 - Juergen Urner' +__emeil__ = 'jue...@go...' +__licence__ = 'Mit' +__version__ = '0.1' \ No newline at end of file Added: trunk/sandbox/fcp/boards/__init__.py =================================================================== --- trunk/sandbox/fcp/boards/__init__.py (rev 0) +++ trunk/sandbox/fcp/boards/__init__.py 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1 @@ + Added: trunk/sandbox/fcp/boards/frost.py =================================================================== --- trunk/sandbox/fcp/boards/frost.py (rev 0) +++ trunk/sandbox/fcp/boards/frost.py 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1,99 @@ + +import os, sys, time + +#--> rel import hack +class SysPathHack(object): + def __init__(self, n): + fpath = os.path.abspath(__file__) + for i in xrange(n): fpath = os.path.dirname(fpath) + sys.path.insert(0, fpath) + def __del__(self): sys.path.pop(0) +hack = SysPathHack(2) + +from fcp2_0_client import FcpClient + + +del hack +#<-- rel import hack +#********************************************************************** +# +#********************************************************************** +#c = FcpClient() +#c.connect() + + + +#KSK@frost|message|news|*currentDate*-*boardName*-*slot*.xml + +class FrostDate(object): + + def __init__(self, year, month, day): + self.year = year + self.month = month + self.day = day + + @classmethod + def now(clss): + t = time.gmtime(time.time()) + instance = clss(t.tm_year, t.tm_mon, t.tm_mday) + return instance + + def toString(self): + return '%s.%s.%s' % (self.year, self.month, self.day) + + + + +class FrostBoard(object): + + def __init__(self): + + self.fcpClient = FcpClient(debugVerbosity=FcpClient.DebugVerbosity.Debug) + for nodeHello in self.fcpClient.connect(): + pass + self.fcpClient.events.RequestCompleted += self.handleRequestCompleted + + + def handleRequestCompleted(self, event, request): + + self.fcpClient.removeRequest(request['Identifier']) + print request['FcData'] + + + + def readBoard(self, boardName, section, startDate=None): + + slot = -1 + while True: + slot += 1 + if slot > 30: break + + ksk = 'KSK@frost|message|%s|%s-%s-%s.xml' % (section, startDate.toString(), boardName, slot) + print ksk + + self.fcpClient.getData( + uri=ksk, + maxSize='33000' + ) + + for i in xrange(300): + self.fcpClient.next() + + + + +board = FrostBoard() + + +t = FrostDate.now() +print t.toString() + +board.readBoard('de.freenet', 'news', FrostDate.now()) + + + + + + + + Added: trunk/sandbox/fcp/fcp2_0_client.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_client.py (rev 0) +++ trunk/sandbox/fcp/fcp2_0_client.py 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1,1510 @@ +"""Freenet client protocol 2.0 implementation + +Compatibility: >= Freenet 0.7 Build #1084 + + +@newfield event, events + + +@note: The client implementation never uses or watches the global queue. No (in words N-O) +implementation should ever do so. Global is evil... so avoid it. Already filed a bug report regarding this. + +""" + +import atexit +import base64 +import cPickle +import logging +import os +import random +import socket +import subprocess +import sys +import time + + +#--> rel import hack +class SysPathHack(object): + def __init__(self, n): + fpath = os.path.abspath(__file__) + for i in xrange(n): fpath = os.path.dirname(fpath) + sys.path.insert(0, fpath) + def __del__(self): sys.path.pop(0) +hack = SysPathHack(3) + + +from fcp_lib import events, namespace, uuid + + +del hack +#<-- rel import hack + +import fcp2_0_message +import fcp2_0_consts as consts + + +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +#********************************************************************** +# helpers +#********************************************************************** +def saveReadFile(fpath): + """Reads contents of a file in the savest manner possible + @param fpath: file to write + @return: contents of the file if successful, None otherwise + """ + read = None + try: + fp = open(fpath, 'rb') + except: pass + else: + try: + read = fp.read() + except: pass + fp.close() + return read + +def saveRemoveFile(fpath): + """Savely removes a file + @param fpath: filepath of the file to remove or None + @return: True if the file was removed, False otherwise + """ + if fpath is not None: + if os.path.isfile(fpath): + try: + os.remove(fpath) + except Exception, d: + pass + else: + return True + return False + + +def saveWriteFile(fpath, data): + """Writes data to a file i the savest manner possible + @param fpath: file to write + @param data: data to write to file + @return: True if successful, False otherwise + """ + written = False + try: + fp = open(fpath, 'wb') + except: pass + else: + try: + fp.write(data) + written = True + except: + fp.Close() + else: + fp.close() + return written + +#************************************************************************************************* +# +#************************************************************************************************* +class FcpClient(object): + + DefaultFcpHost = os.environ.get('FCP_HOST', '127.0.0.1').strip() + try: + DefaultFcpPort = int(os.environ.get('FCP_PORT', '').strip()) + except ValueError: + DefaultFcpPort = 9481 + + #TODO: check if required + # suggested by Mathew Toseland to use about 32k for mimeType requests + # basic sizes of keys are: 1k for SSks and 32k for CHKs + # without MaxSize DataFound will have DataLength set to 0 (?!) + MaxSizeKeyInfo = '32000' + SocketTimeout = 0.1 + Version = '2.0' + + from fcp2_0_config import Config + from fcp2_0_consts import ( + ConnectReason, + DebugVerbosity, + DisconnectReason, + FcpTrue, + FcpFalse, + FetchError, + FilenameCollision, + InsertError, + LogMessages, + PeerNodeStatus, + PeerNoteType, + Persistence, + Priority, + ProtocolError, + ReturnType, + Verbosity, + ) + from fcp2_0_message import Message + import fcp2_0_params as FcParams + from fcp2_0_uri import Uri + + + + class Events(events.Events): + """All events the client supports""" + _events_ = ( + + 'Idle', + + 'ClientConnected', + 'ClientDisconnected', + + 'RequestCompleted', # the request is not removed neither from node nor client + 'RequestFailed', # the request is already removed from node and client + 'RequestModified', + 'RequestProgress', + 'RequestRestored', + 'RequestStarted', + + + # config related events + 'ConfigData', + 'NodeData', + + + #Peer related events + 'EndListPeers', + 'Peer', + 'PeerRemoved', + 'UnknownNodeIdentifier', + 'EndListPeerNotes', + 'PeerNote', + + + ############################### + + 'ProtocolError', + 'PersistentGet', + + # others + 'SSKKeypair', + + ) + + + + def __init__(self, + connectionName=None, + debugVerbosity=None, + ): + """ + @param conectionName: name of the connection + @param debugVerbosity: verbosity level for debugging. Default is L{Verbosity.Warning} + + @ivar events: events the client supports + """ + self._connectionName = connectionName + self._ddaTests = [] + self._identifierMapping = {} # mapping from identifiers to FcRequestIdentifiers + self._requests = {} + + self._log = logging.getLogger(self.__class__.__name__) + self._socket = None + + self.events = self.Events() + + self.setDebugVerbosity(self.DebugVerbosity.Warning if debugVerbosity is None else debugVerbosity) + atexit.register(self.close) + + + ############################################################### + ## + ## private methods + ## + ############################################################### + def _addFcParamsToRequest(self, + msg, + userData, + msgSubType, + initTime, + persistentUserData, + filenameCollision, + ): + + + # add additional params to msg + msg.params.update({ + + # persistent params that will go into identifier + 'FcSubType': msgSubType, # identifies sub message types + 'FcInitTime': self.fcpTime(initTime), # when was the request started? + 'FcFilenameCollision': filenameCollision, # handle fielanem collisions? + 'FcPersistentUserData': persistentUserData, # any user defined persistent data + + # non persistent params + 'FcStatus': self.Message.StatusPending, + 'FcErrorMessage': None, # did an error occur? + 'FcUserData': userData, # any user defined runtime data here + + # params for AllData + 'FcData': '', # if data was requested via requestData you will find it here + + # params for SSKKeypair + 'FcInsertUri': None, + 'FcRequestUri': None, + + # params from DataFound + 'FcMetadataContentType': '', # contecnt type + 'FcDataLength': '', # content size + + # params from PersistentRequestModified + 'FcModified': {}, + + + # params for SimpleProgress + 'FcProgressTotal': '0', + 'FcProgressRequired': '0', + 'FcProgressFailed': '0', + 'FcProgressFatalyFailed': '0', + 'FcProgressSucceeeded': '0', + + }) + return msg + + + def _registerRequest(self, + msg, + userData, + msgSubType, + initTime, + persistentUserData, + filenameCollision=consts.FilenameCollision.HandleNever, + ): + """Registers a message + @param msg: message to register for track keeping + @param msgSubType: one of the message sub type consts + @param requestIdentifier: (str) + @param userData: (str) + @param initTime: (python time) + @param handleCollisions: (bool) + + @return: (str) uuid + @note: the identifier returned is unique to the client but may not be unique to the node + """ + + # we store FcRequestIdentifier and additional params in ClientToken + identifier = self.FcParams.newUuid() + + # add additional params to msg + msg = self._addFcParamsToRequest( + msg, + userData, + msgSubType, + initTime, + persistentUserData, + filenameCollision, + ) + + msg['ClientToken'] = self.FcParams.messageToParams(msg) + msg['Identifier'] = identifier + self._requests[identifier] = msg + + + + def _restorePersistentRequestFromNode(self, msg): + """Preps a message from node to be restored + @return: the restored message if everything went well, None otherwise + """ + fcParams = self.FcParams.paramsFromRequest(msg) + if fcParams is None: + return None + + # have to re-adjust initTime to python time + fcParams[self.FcParams.IInitTime] = self.pythonTime(fcParams[self.FcParams.IInitTime]) + + # add additional params to msg + msg = self._addFcParamsToRequest( + msg, + None, # userData, + fcParams[self.FcParams.ISubType], + fcParams[self.FcParams.IInitTime], + fcParams[self.FcParams.IPersistentUserData], + fcParams[self.FcParams.IFilenameCollision], + ) + + # fix some Fcp inconsistencies ClientGet vs. PersistentGet + if msg.name == self.Message.MessagePersistentGet: + del msg.params['Started'] + if 'PersistenceType' in msg.params: + msg['Persistence'] = msg.params.pop('PersistenceType') + + return msg + + + + ############################################################### + ## + ## Fcp <--> Python mappings + ## + ############################################################### + def fcpBool(self, pythonBool): + """Converts a python bool to a fcp bool + @param pythonBool: (bool) + @return: (str) 'true' or 'false' + """ + return self.FcpTrue if pythonBool else self.FcpFalse + + def fcpTime(self, pythonTime): + """Converts a python time value to a fcp time value + @param fcpTime: (int, str) time to convert + @raise ValueError: if the python time could not be converted + @return: (int) fcp time + """ + return pythonTime * 1000 + + def pythonBool(self, fcpBool): + """Converts a fcp bool to a python bool + @param pythonBool: 'true' or 'false' + @return: (bool) True or False + """ + return fcpBool == self.FcpTrue + + def pythonTime(self, fcpTime): + """Converts a fcp time value to a python time value + @param fcpTime: (int, str) time to convert + @raise ValueError: if the fcp time could not be converted + @return: (int) python time + """ + return int(fcpTime) / 1000 + + ############################################################### + ## + ## connection related methods + ## + ############################################################### + def close(self): + """Closes the client + @note: make shure to call close() when done with the client + """ + self._log.info(self.LogMessages.ClientClose) + if self._socket is not None: + self._socket.close() + self._socket = None + + # clean left over tmp files + for initialRequest in self._ddaTests: + if initialRequest['FcTestDDA']['TmpFile'] is not None: + saveRemoveFile(initialRequest['FcTestDDA']['TmpFile']) + + + def connect(self, host=DefaultFcpHost, port=DefaultFcpPort, duration=20, timeout=0.5): + """Iterator to stablish a connection to a freenet node + @param host: (str) host of th node + @param port: (int) port of the node + @param duration: (int) how many seconds try to connect before giving up + @param timeout: (int) how much time to wait before another attempt to connect + @event: Connected(event, params). Triggered as soon as the client is connected. Params + will be the parameters of the NodeHello message. + @return: (Message) NodeHello if successful, None otherwise for the next iteration + """ + self._log.info(self.LogMessages.Connecting) + + # poll untill freenet responds + timeElapsed = 0 + while timeElapsed <= duration: + + # try to Connect socket + if self._socket is not None: + self.close() + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.settimeout(self.SocketTimeout) + try: + self._socket.connect((host, port)) + except socket.error, d: + yield None + else: + self._log.info(self.LogMessages.Connected) + + # send ClientHello and wait for NodeHello + #NOTE: thought I could leave ClientHelloing up to the caller + # but instad of responding with ClientHelloMustBeFirst + # as expected when not doing so, the node disconnects. + # So take it over here. + self.sendMessage( + self.Message.MessageClientHello, + Name=self._connectionName if self._connectionName is not None else uuid.uuid_time(), + ExpectedVersion=self.Version, + ) + while timeElapsed <= duration: + msg = self.next(dispatch=False) + if msg.name == self.Message.MessageClientSocketTimeout: + timeElapsed += self.SocketTimeout + yield None + elif msg.name == self.Message.MessageNodeHello: + self._log.debug(self.LogMessages.MessageReceived + msg.pprint()) + self.events.ClientConnected(msg) + yield msg + raise StopIteration + else: + self._log.debug(self.LogMessages.MessageReceived + msg.pprint()) + break + break + + # continue polling + self._log.info(self.LogMessages.ConnectionRetry) + timeElapsed += timeout + time.sleep(timeout) + + msg = self.Message( + self.Message.MessageClientDisconnected, + DisconnectReason=self.DisconnectReason.ConnectingFailed + ) + self.events.ClientDisconnected(msg) + self._log.info(self.LogMessages.ConnectingFailed) + self.close() + raise StopIteration + + + def setDebugVerbosity(self, debugVerbosity): + """Sets the verbosity level of the client + @note: see L{Verbosity} + """ + self._log.setLevel(debugVerbosity) + + + def startFreenet(self, cmdline): + """Starts freenet + @param cmdline: commandline to start freenet (like '/freenet/run.sh start' or 'c:\freenet\start.bat') + @return: (string) whatever freenet returns + """ + #TODO: on windows it may be necessary to hide the command window + p = subprocess.Popen( + args=cmdline, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout, stderr = p.communicate() + return stdout + + ######################################################### + ## + ## runtime related methods + ## + ######################################################### + def handleMessage(self, msg): + """Handles a message from the freenet node + @param msg: (Message) to handle + @return: True if the message was handled, False otherwise + """ + if msg.name == self.Message.MessageClientSocketTimeout: + return True + self._log.debug(self.LogMessages.MessageReceived + msg.pprint()) + + # check if we have a corrosponding initial message + fcRequestIdentifier = None + initialRequest = None + + # check if we have an initial request corrosponding to msg + requestIdentifier = msg.get('Identifier', None) + initialRequest = None if requestIdentifier is None else self._requests.get(requestIdentifier, None) + #################################################### + ## + ## errors + ## + #################################################### + if msg.name == self.Message.MessageIdentifierCollision: + if initialRequest is not None: + # resend request with new identifier + + newIdentifier = self.FcParams.newUuid(uuids=self._requests) + self._requests[newIdentifier] = initialRequest + del self._requests[requestIdentifier] + initialRequest['Identifier'] = newIdentifier + initialRequest['FcModified'] = {self.Message.ModifiedRequestIdentifier: requestIdentifier} + self.events.RequestModified(initialRequest) + self.sendMessageEx(initialRequest) + return True + + + elif msg.name == self.Message.MessageProtocolError: + code = msg['Code'] + if requestIdentifier is None: + #TODO: check how to handle this + raise self.ProtocolError(msg) + + if initialRequest is None: + #TODO: check how to handle this + raise self.ProtocolError(msg) + + + if code == self.ProtocolError.ShuttingDown: + self.close() + msg = self.Message( + self.Message.MessageClientDisconnected, + DisconnectReason=DisconnectReason.Shutdown, + ) + self.events.ClientDisconnect(msg) + return True + + + # handle DDA errors + elif code == self.ProtocolError.DDADenied: + ddaRequestMsg = self.Message(self.Message.MessageTestDDARequest) + if initialRequest.name == self.Message.MessageClientGet: + ddaRequestMsg['WantWriteDirectory'] = self.FcpTrue + directory = os.path.dirname(initialRequest['Filename']) + else: + + #TODO: determine directory for other cases + raise RuntimeError(NotImplemented) + + ddaRequestMsg['WantReadDirectory'] = self.FcpTrue + directory = None + ddaRequestMsg['Directory'] = directory + + + # add params required for testing + initialRequest['FcTestDDA'] = { + 'Directory': directory, + 'Replied': False, + 'TmpFile': None, + 'WantWrite': self.pythonBool(ddaRequestMsg.get('WantWriteDirectory', self.FcpFalse)), + 'ErrorMsg': msg, + } + self._ddaTests.append(initialRequest) + self.sendMessageEx(ddaRequestMsg) + return True + + + # handle filename collisions + elif code == self.ProtocolError.DiskTargetExists: + handleCollision = initialRequest.get('FcFilenameCollision', self.FilenameCollision.HandleNever) + collisionHandled = bool(handleCollision & self.FilenameCollision.CollisionHandled) + + # rename filename + if handleCollision & self.FilenameCollision.HandleRename: + filename = initialRequest['Filename'] + initialRequest['FcFilenameCollision'] |= self.FilenameCollision.CollisionHandled + newFilename = namespace.unique_filename(filename, extensions=1, ispostfixed=collisionHandled) + initialRequest['Filename'] = newFilename + initialRequest['FcModified'] = {self.Message.ModifiedRequestFilename: filename} + self.sendMessageEx(initialRequest) + self.events.RequestModified(initialRequest) + return True + + # don't handle + else: + initialRequest['FcFilenameCollision'] &= ~self.FilenameCollision.CollisionHandled + + + # only requests should get through to here + + # NOTE: Fcp already removed the request + del self._requests[requestIdentifier] + initialRequest['FcErrorMessage'] = msg + initialRequest['FcStatus'] = self.Message.StatusError | self.Message.StatusRemoved + self.events.RequestFailed(initialRequest) + return True + + + #################################################### + ## + ## TestDDA + ## + ## We assume that a DDATest messages do not get mixed up. 1st TestDDARequest is first to + ## enter TestDDAReply and TestDDAComplete. Hopefuly the freenet devels will rework the + ## TestDDA drill. + ## + #################################################### + elif msg.name == self.Message.MessageTestDDAReply: + directory = msg['Directory'] + + # find message that triggered the call + for initialRequest in self._ddaTests: + if initialRequest['FcTestDDA']['Directory'] == directory: + if not initialRequest['FcTestDDA']['Replied']: + initialRequest['FcTestDDA']['Replied'] = True + break + else: + # fell through + raise ValueError('No inital message found in TestDDAReply') + + # perform read test if necessary + fpathRead = msg.params.get('ReadFilename', None) + readContent = '' + if fpathRead is not None: + readContent = saveReadFile(fpathRead) + if readContent is None: + readContent = '' + + # perform write test if necessary + fpathWrite = msg.params.get('WriteFilename', None) + if fpathWrite is not None: + written = saveWriteFile(fpathWrite, msg['ContentToWrite']) + if not written: + saveRemoveFile(fpathWrite) + else: + initialRequest['FcTestDDA']['TmpFile'] = fpathWrite + + self.sendMessage( + self.Message.MessageTestDDAResponse, + Directory=msg['Directory'], + ReadContent=readContent, + ) + return True + + + elif msg.name == self.Message.MessageTestDDAComplete: + # clean up tmp file + directory = msg['Directory'] + + # find message that triggered the call + for initialRequest in self._ddaTests: + if initialRequest['FcTestDDA']['Directory'] == directory: + if initialRequest['FcTestDDA']['Replied']: + break + else: + # fell through + raise ValueError('No initial message found in TestDDAComplete') + + # remove test and clean tmp data + self._ddaTests.remove(initialRequest) + if initialRequest['FcTestDDA']['TmpFile'] is not None: + saveRemoveFile(initialRequest['FcTestDDA']['TmpFile']) + wantWrite = initialRequest.params['FcTestDDA']['WantWrite'] + errorMsg = initialRequest['FcTestDDA']['ErrorMsg'] + del initialRequest.params['FcTestDDA'] + + # check if test was sucessful + testFailed = False + if wantWrite: + testFailed = not self.pythonBool(msg.params.get('WriteDirectoryAllowed', self.FcpFalse) ) + else: + testFailed = not self.pythonBool(msg.params.get('ReadDirectoryAllowed', self.FcpFalse) ) + + if testFailed: + + #TODO: check if Fcp removed the request + #TODO: check if errorMsg gives reasonable feedback + + del self._request[fcRequestIdentifier] + initialRequest['FcStatus'] = self.Message.StatusError | self.Message.StatusRemoved + initialRequest['FcErrorMessage'] = errorMsg + self.events.ProtocolError(initialRequest) + return True + + + # resend message + self.sendMessageEx(initialRequest) + return True + + + + #################################################### + ## + ## config related + ## + #################################################### + elif msg.name == self.Message.MessageConfigData: + self.events.ConfigData(msg) + return True + + elif msg.name == self.Message.MessageNodeData: + self.events.NodeData(msg) + return True + + + #################################################### + ## + ## get / put related + ## + #################################################### + elif msg.name == self.Message.MessageAllData: + if initialRequest is None: + return False + + initialRequest['FcData'] = msg.data + self.events.RequestCompleted(initialRequest) + return True + + elif msg.name == self.Message.MessageDataFound: + + if initialRequest is None: + # something is going wrong + return False + + initialRequest['FcStatus'] = self.Message.StatusComplete + initialRequest['FcMetadataContentType'] = msg.get('Metadata.ContentType', '') + initialRequest['FcDataLength'] = msg.get('DataLength', '') + self.events.RequestCompleted(initialRequest) + return True + + + + elif msg.name == self.Message.MessageGetFailed: + code = msg['Code'] + if initialRequest is None: + # something is going wrong + return False + + # check if it is one of our requests for key information + if code == self.FetchError.TooBig and initialRequest['FcSubType'] == self.Message.SubTypeGetKeyInfo: + initialRequest['FcStatus'] = self.Message.StatusComplete + initialRequest['FcMetadataContentType'] = msg.get('ExpectedMetadata.ContentType', '') + initialRequest['FcDataLength'] = msg.get('ExpectedDataLength', '') + initialRequest['FcProgressCompleted'] = self.FcpTrue + #TODO: check if Fcp removed the request + + self.events.RequestCompleted(initialRequest) + else: + + #TODO: check if Fcp removed the request + + initialRequest['FcErrorMessage'] = msg + initialRequest['FcStatus'] = self.Message.StatusError + self.events.RequestFailed(initialRequest) + return True + + + + elif msg.name == self.Message.MessagePersistentGet: + + # NOTE: + # Fcp does no good job in handling persistent requests and identifiers. Already dropped some + # notes and reports regarding this. See freenet-tech mailing list [Fcp notes and issues] + + CancelPersistentRequests = 0 # for testing... if True, cancels all persistent requests + + # unknown request... try to restore it + if initialRequest is None: + restoredRequest = self._restorePersistentRequestFromNode(msg) + + # not one of our requests... so cancel it + if restoredRequest is None or CancelPersistentRequests: + self.sendMessage( + self.Message.MessageRemovePersistentRequest, + Identifier=msg['Identifier'], + Global=msg['Global'], + ) + return True + + restoredRequest.name = self.Message.MessageClientGet + self._requests[requestIdentifier] = restoredRequest + restoredRequest['FcStatus'] = self.Message.StatusStarted + self.events.RequestRestored(restoredRequest) + return True + + # known request... filter out multiple PersistentGets + if initialRequest['FcStatus'] == self.Message.StatusPending: + initialRequest['FcStatus'] = self.Message.StatusStarted + + #TODO: update initialRequest with params from PersistentGet? + + self.events.RequestStarted(initialRequest) + return True + + return True + + + elif msg.name == self.Message.MessagePersistentRequestModified: + if initialRequest is None: + return False + + modified = {} + + # check if PersistentUserData has changed + params = self.FcParams.paramsFromRequest(initialRequest) + if params is not None: + clientToken = msg.get('ClientToken', None) + if clientToken is not None: + + #TODO: its more or less a guess that PersistentUserData has changed + # ...as long as no other param is changed at runtime we are ok + # otherwise we would have to set flags to indicate wich member + # of ClientToken changed. See --> modifyRequest() + modified[self.Message.ModifiedRequestPersistentUserData] = None + for i, fcParam in enumerate(self.FcParams.FcParams): + initialRequest[fcParam] = params[i] + + # check if PriorityClass has changed + priorityClass = msg.get('PriorityClass', None) + if priorityClass is not None: + modified[self.Message.ModifiedRequestPriorityClass] = None + initialRequest['PriorityClass'] = priorityClass + + initialRequest['FcModified'] = modified + self.events.RequestModified(initialRequest) + return True + + + elif msg.name == self.Message.MessagePersistentRequestRemoved: + if initialRequest is None: + return False + + del self._requests[requestIdentifier] + return True + + + elif msg.name == self.Message.MessageSimpleProgress: + if initialRequest is None: + # something went wrong + return False + + initialRequest['FcProgressTotal'] = msg['Total'] + initialRequest['FcProgressRequired'] = msg['Required'] + initialRequest['FcProgressFailed'] = msg['Failed'] + initialRequest['FcProgressFatalyFailed'] = msg['FatallyFailed'] + initialRequest['FcProgressSucceeeded'] = msg['Succeeded'] + self.events.RequestProgress(initialRequest) + return True + + #################################################### + ## + ## Peer related messages + ## + #################################################### + elif msg.name == self.Message.MessageEndListPeers: + self.events.EndListPeers(msg) + return True + + elif msg.name == self.Message.MessageEndListPeerNotes: + self.events.EndListPeerNotes(msg.params) + return True + + elif msg.name == self.Message.MessagePeer: + self.events.Peer(msg) + return True + + elif msg.name == self.Message.MessagePeerNote: + note = msg.get('NoteText', '') + if note: + note = base64.decodestring(note) + msg['NoteText'] = note + self.events.PeerNote(msg) + return True + + elif msg.name == self.Message.MessagePeerRemoved: + self.events.PeerRemoved(msg) + return True + + elif msg.name == self.Message.MessageUnknownNodeIdentifier: + self.events.UnknownNodeIdentifier(msg) + return True + + #################################################### + ## + ## others + ## + #################################################### + elif msg.name == self.Message.MessageSSKKeypair: + if initialRequest is None: + return False + + #TODO: maybe we need a mapping from SSKKeypair to pending request + + initialRequest['Uri'] = msg['InsertUri'] + initialRequest['FcRequestUri'] = msg['RequestUri'] + + self.sendMessageEx(initialRequest) + return True + + + + # default + return False + + + def next(self, dispatch=True): + """Pumps the next message waiting + @param dispatch: if True the message is dispatched to L{handleMessage} + @note: use this method instead of run() to run the client step by step + """ + msg = self.Message.fromSocket(self._socket) + if msg.name == self.Message.MessageClientSocketDied: + if dispatch: + msg['DisconnectReason'] = self.DisconnectReason.SocketDied + self.events.ClientDisconnected(msg) + raise socket.error(msg['Details']) + + elif msg.name == self.Message.MessageClientSocketTimeout: + if dispatch: + self.events.Idle(msg) + + else: + if dispatch: + self.handleMessage(msg) + return msg + + + def sendMessage(self, name, data=None, **params): + """Sends a message to freenet + @param name: name of the message to send + @param data: data to atatch to the message + @param params: {para-name: param-calue, ...} of parameters to pass along + with the message (see freenet protocol) + @raise SocketError: if the socket connection to the node dies unexpectedly + If an error handler is passed to the client it is called emidiately before the error + is raised. + """ + return self.sendMessageEx(self.Message(name, data=data, **params)) + + + def sendMessageEx(self, msg): + """Sends a message to freenet + @param msg: (Message) message to send + @return: Message + @raise SocketError: if the socket connection to the node dies unexpectedly. + If an error handler is passed to the client it is called emidiately before the error + is raised. + """ + self._log.debug(self.LogMessages.MessageSend + msg.pprint()) + try: + msg.send(self._socket) + except socket.error, d: + self._log.info(self.LogMessages.SocketDied) + self.close() + + errorMsg = self.Message( + self.Message.MessageClientSocketDied, + DisconnectReason=self.DisconnectReason.SocketDied, + Exception=socket.error, + Details=d + ) + self.events.ClientDisconnected(errorMsg) + raise socket.error(d) + return msg + + ######################################################### + ## + ## config related methods + ## + ######################################################### + #TODO: WithDefault never returns defaults + def getConfig(self, + withCurrent=consts.FcpTrue, + withDefaults=consts.FcpTrue, + withExpertFlag=consts.FcpTrue, + withForceWriteFlag=consts.FcpTrue, + withSortOrder=consts.FcpTrue, + withShortDescription=consts.FcpTrue, + withLongDescription=consts.FcpTrue, + ): + """ + @event: ConfigData(event, msg) + """ + self.sendMessage( + self.Message.MessageGetConfig, + WithSortOrder=withSortOrder, + WithCurrent=withCurrent, + WithDefaults=withDefaults, + WithExpertFlag=withExpertFlag, + WithForceWriteFlag=withForceWriteFlag, + WithShortDescription=withShortDescription, + WithLongDescription=withLongDescription, + ) + + def getNode(self, + withPrivate=True, + withVolatile=True, + giveOpennetRef=True, + ): + """ + @event: NodeData(event, msg) + """ + self.sendMessage( + self.Message.MessageGetNode, + WithPrivate=self.fcpBool(withPrivate), + WithVolatile=self.fcpBool(withVolatile), + GiveOpennetRef=self.fcpBool(giveOpennetRef), + ) + + + ######################################################## + ## + ## ClientGet related methods + ## + ######################################################## + def getData(self, + uri, + + allowedMimeTypes=None, + binaryBlob=consts.FcpFalse, + dsOnly=consts.FcpFalse, + ignoreDS=consts.FcpFalse, + maxRetries=None, + maxSize=None, + persistence=consts.Persistence.Connection, + priorityClass=consts.Priority.Medium, + + userData=None, + persistentUserData='', + ): + """Requests a file from the node + + @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') + + @param allowedMimeTypes: (str) list of allowed mime types + @param binaryBlob: if FcpTrue, the file is retrieved as binary blob file + @param dsOnly: if FcpTrue, retrieves the file from the local data store only + @param ignoreDs: If FcpTrue, ignores the local data store + @param maxRetries: (int) maximum number of retries or -1 to retry forver or None to leave it to the node to decide + @param maxSize: (int) maximum size of the file in bytes or None to set no limited + @param persistence: persistence of the request as one of the L{consts.Persistence} constants + @param priorityClass: priority of the request as one of the L{consts.Priority} consts + + @param userData: any non persistent data to associate to the request + @param persistentUserData: any string to associate to the request as persistent data + + @return: (str) request identifier + @note: if a filename collision is handled a RequestFilenameChanged event is triggered + """ + + msg = self.Message( + self.Message.MessageClientGet, + + BinaryBlob=binaryBlob, + Global=self.FcpFalse, + DSOnly=dsOnly, + + Identifier=None, + IgnoreDS=ignoreDS, + Persistence=persistence, + PriorityClass=priorityClass, + + ReturnType=self.ReturnType.Direct, + URI=self.Uri(uri).uri, + Verbosity=self.Verbosity.ReportProgress, + + #MaxTempSize=whatever, + #TempFilename=whatever + ) + if allowedMimeTypes is not None: + msg['AllowedMimeTypes'] = allowedMimeTypes + if maxRetries is not None: + msg['MaxRetries'] = maxRetries + if maxSize is not None: + msg['MaxSize'] = maxSize + + + self._registerRequest( + msg, + userData, + self.Message.SubTypeGetData, + time.time(), + persistentUserData, + ) + self.sendMessageEx(msg) + return msg['Identifier'] + + + def getFile(self, + uri, + filename, + + allowedMimeTypes=None, + binaryBlob=consts.FcpFalse, + dsOnly=consts.FcpFalse, + ignoreDS=consts.FcpFalse, + maxRetries=None, + maxSize=None, + persistence=consts.Persistence.Connection, + priorityClass=consts.Priority.Medium, + + userData=None, + persistentUserData='', + filenameCollision=consts.FilenameCollision.HandleNever, + ): + """Requests a file from the node + + @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') + @param filename: (full path) filename to store the file to + + @param allowedMimeTypes: (str) list of allowed mime types + @param binaryBlob: if FcpTrue, the file is retrieved as binary blob file + @param dsOnly: if FcpTrue, retrieves the file from the local data store only + @param ignoreDs: If FcpTrue, ignores the local data store + @param maxRetries: (int) maximum number of retries or -1 to retry forver or None to leave it to the node to decide + @param maxSize: (int) maximum size of the file in bytes or None to set no limited + @param persistence: persistence of the request as one of the L{consts.Persistence} constants + @param priorityClass: priority of the request as one of the L{consts.Priority} consts + + @param filenameCollision: what to do if the disk target alreaady exists. One of the FilenameCollision.* consts + @param userData: any non persistent data to associate to the request + @param persistentUserData: any string to associate to the request as persistent data + + @return: (str) request identifier + @note: if a filename collision is handled a RequestFilenameChanged event is triggered + """ + + msg = self.Message( + self.Message.MessageClientGet, + + BinaryBlob=binaryBlob, + Filename=filename, + Global=self.FcpFalse, + DSOnly=dsOnly, + + Identifier=None, + IgnoreDS=ignoreDS, + Persistence=persistence, + PriorityClass=priorityClass, + + ReturnType=self.ReturnType.Disk, + URI=self.Uri(uri).uri, + Verbosity=self.Verbosity.ReportProgress, + + #MaxTempSize=whatever, + #TempFilename=whatever + ) + if allowedMimeTypes is not None: + msg['AllowedMimeTypes'] = allowedMimeTypes + if maxRetries is not None: + msg['MaxRetries'] = maxRetries + if maxSize is not None: + msg['MaxSize'] = maxSize + + + self._registerRequest( + msg, + userData, + self.Message.SubTypeGetFile, + time.time(), + persistentUserData, + filenameCollision, + ) + self.sendMessageEx(msg) + return msg['Identifier'] + + + def getKeyInfo(self, + uri, + + dsOnly=consts.FcpFalse, + ignoreDS=consts.FcpFalse, + persistence=consts.Persistence.Connection, + priorityClass=consts.Priority.Medium, + + userData=None, + persistentUserData='', + ): + """Requests info about a key + + @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') + + @param dsOnly: if FcpTrue, retrieves the file from the local data store only + @param ignoreDs: If FcpTrue, ignores the local data store + @param persistence: persistence of the request as one of the L{consts.Persistence} constants + @param priorityClass: priority of the request as one of the L{consts.Priority} consts + @param userData: any non persistent data to associate to the request + @param persistentUserData: any string to associate to the request as persistent data + + @return: (str) request identifier + """ + + # how to retrieve meta info about a key? + # ...idea is to provoke a GetFailed (TooBig) + + msg = self.Message( + self.Message.MessageClientGet, + + DSOnly=dsOnly, + Global=self.FcpFalse, + Identifier=None, + IgnoreDS=ignoreDS, + + MaxSize=self.MaxSizeKeyInfo, + + Persistence=persistence, + PriorityClass=priorityClass, + ReturnType=self.ReturnType.Nothing, + + URI=self.Uri(uri).uri, + Verbosity=self.Verbosity.ReportProgress, + ) + + self._registerRequest( + msg, + userData, + self.Message.SubTypeGetKeyInfo, + time.time(), + persistentUserData, + ) + self.sendMessageEx(msg) + return msg['Identifier'] + + ######################################################## + ## + ## ClientPut related methods + ## + ######################################################## + def put(self): + pass + + + + + ######################################################## + ## + ## request related methods + ## + ######################################################## + def getRequest(self, identifier): + """Returns a (initial) message, given its identifier + @param identifier: identifier of the message + @return: L{Message} + """ + return self._requests[identifier] + + + def getRequests(self): + """Returns all (initial) messages, currently known to the client + @return: list(messages) + """ + return self._requests + + + def modifyRequest(self, requestIdentifier, persistentUserData=None, priorityClass=None): + """Modifies a request + @param identifier: identifier of the request to modify + @param clientToken: new client token or None + @param priorityClass: new priority or None + + @note: a RequestModified event is triggered as soon as the request has actually been modified + """ + initialRequest = self._requests[requestIdentifier] + msg = self.Message( + self.Message.MessageModifyPersistentRequest, + Identifier=initialRequest['Identifier'], + Global=self.FcpFalse, + ) + if persistentUserData is not None: + initialRequest['FcPersistentUserData'] = persistentUserData + msg['ClientToken'] = self.FcParams.messageToParams(initialRequest) + if priorityClass is not None: + msg['PriorityClass'] = priorityClass + self.sendMessageEx(msg) + + + def removeRequest(self, requestIdentifier): + """Removes a request + @param identifier: (str) identifier of the request to remove + + @note: a RequestRemoved event is triggered as soon as the request has actually been removed + """ + initialRequest = self._requests[requestIdentifier] + initialRequest['FcStatus'] = self.Message.StatusRemoved + self.sendMessage( + self.Message.MessageRemovePersistentRequest, + Global=self.FcpFalse, + Identifier=requestIdentifier, + ) + + #TODO: check how Fcp responds when the identifier is unknwon or something else goes + # werong. Maybe a ProtocolError.NoSuchIdentifier ??? + + ######################################################## + ## + ## Peer related methods + ## + ######################################################## + def listPeer(self, identity): + self.jobClient.sendMessage( + self.Message.MessageListPeer, + NodeIdentifier=identity, + ) + + + def listPeerNotes(self, identity): + """Lists all text notes associated to a peer + @param identifier: peer as returned in a call to L{peerList} + @event: ListPeerNote(event, params) + @event: EndListPeerNotes(event, params) + @note: listPeerNotes() is only available for darknet nodes + """ + self.sendMessage( + self.Message.MessageListPeerNotes, + NodeIdentifier=identity + ) + + + def listPeers(self, withMetaData=True, withVolantile=True): + """Lists all peers of the node + @param withMetaData: include meta data for each peer? + @param withVolantile: include volantile data for each peer? + + @event: Peer(event, peer). + @event: EndListPeers(event, params). + """ + self.sendMessage( + self.Message.MessageListPeers, + WithMetadata=self.fcpBool(withMetaData), + WithVolatile=self.fcpBool(withVolantile), + ) + + + def modifyPeer(self, identity, allowLocalAddresses=None, isDisabled=None, isListenOnly=None): + msg = Message( + self.Message.MessageModifyPeer, + NodeIdentifier=identity, + ) + if allowLocalAddresses is not None: + msg['AllowLocalAddresses'] = self.fcpBool(allowLocalAddresses) + if isDisabled is not None: + msg['isDisabled'] = self.fcpBool(isDisabled) + if isListenOnly is not None: + msg['isListenOnly'] = self.fcpBool(isListenOnly) + self.jobClient.sendMessageEx(msg) + self.sendMessageEx(msg) + + + def modifyPeerNote(self, identity, note): + self.sendMessage( + self.Message.MessageModifyPeerNote, + NodeIdentifier=identity, + #NOTE: currently fcp supports only this one type + PeerNoteType=self.PeerNoteType.Private, + NoteText=note + ) + + + def removePeer(self, identity): + self.sendMessage( + self.Message.MessageRemovePeer, + NodeIdentifier=identity, + ) + + ########################################################## + ## + ## others + ## + ########################################################## + def generateSSK(self): + """ + @event: SSKKeypair(event, params), triggered when the request is complete + @return: identifier of the request + """ + while True: + identifier = uuid.uuid_time() + if identifier not in self._requests: + break + self.sendMessage( + self.Message.MessageGenerateSSK, + Identifier=identifier, + ) + return identifier + + +#***************************************************************************** +# +#***************************************************************************** +if __name__ == '__main__': + c = FcpClient( + connectionName='test', + debugVerbosity=FcpClient.DebugVerbosity.Debug + ) + + for nodeHello in c.connect(): pass + if nodeHello is not None: + + #for i in xrange(5): + # c.next() + + + def testGetData(): + def cb(event, request): + print request['FcData'] + c.events.RequestCompleted += cb + + identifier = c.getData( + 'CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg', + #binaryBlob=c.FcpTrue, + ) + + for i in xrange(50): + c.next() + + c.removeRequest(identifier) + for i in xrange(5): + c.next() + + #testGetData() + + + + def testGetFile(): + filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.jpg') + + identifier = c.getFile( + 'CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg', + filename, + filenameCollision=c.FilenameCollision.HandleRename, + persistence=consts.Persistence.Forever, + ) + + for i in xrange(50): + c.next() + + #c.removeRequest(identifier) + #for i in xrange(5): + # c.next() + + testGetFile() + + + + def testGetKeyInfo(): + identifier = c.getKeyInfo( + 'CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg', + ) + + for i in xrange(50): + c.next() + c.removeRequest(identifier) + for i in xrange(5): + c.next() + + #testgetKeyInfo() + + + + def testConfigData(): + + from fcp2_0_config import Config + + def cb(event, msg): + root=Config(configDataMsg=msg) + for node in root.walk(): + print node.key() + + c.events.ConfigData += cb + c.getConfig() + for i in xrange(10): + c.next() + + #testConfigData() + + + + def testNodeData(): + + def cb(event, msg): + pass + + + c.events.NodeData += cb + c.getNode() + for i in xrange(10): + c.next() + + #testNodeData() + + + def testGenerateSSK(): + + #def cb(event, msg): + # pass + + + #c.events.NodeData += cb + c.generateSSK() + for i in xrange(10): + c.next() + + #testGenerateSSK() + + + \ No newline at end of file Added: trunk/sandbox/fcp/fcp2_0_config.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_config.py (rev 0) +++ trunk/sandbox/fcp/fcp2_0_config.py 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1,335 @@ +"""Sketch for fcp config tree""" + +#**************************************************************************************** +# +#**************************************************************************************** +class ConfigItem(object): + + def __init__(self, parent, name): + self.parent = parent + self.name = name + self.children = {} + self.values = {} + + def key(self): + out = [] + parent = self + while parent is not None: + if parent.name is not None: + out.append(parent.name) + parent = parent.parent + out.reverse() + return '.'.join(out) + + +#**************************************************************************************** +# +#**************************************************************************************** +class Config(object): + + """Class representing fcp config tree + + """ + #{'current': {'valueType': (8, None), 'allowedHosts': 'true'}, 'default': {'valueType': (8, None), 'allowedHosts': 'true'}} + + ValueClassCurrent = 'current' + ValueClassDefault = 'default' + ValueClassExpertFlag = 'expertFlag' + ValueClassForceWriteFlag = 'forceWriteFlag' + ValueClassShortDescription = 'shortDescription' + ValueClassLongDescription = 'longDescription' + ValueClassSortOrder = 'sortOrder' + ValueClassType = 'valueType' + + ValueClassesFcp = ( + ValueClassDefault, + ValueClassExpertFlag, + ValueClassForceWriteFlag, + ValueClassShortDescription, + ValueClassLongDescription, + ) + ValueClassAll = ValueClassesFcp + (ValueClassType, ) + + + ValueTypeUnknown = 0 + ValueTypeBool = 1 + ValueTypeInt = 2 # params: LowerLimit, UpperLimit + ValueTypeBytes = 3 # may have params: LowerLimit, UpperLimit + ValueTypeFilename = 4 + ValueTypeDirname = 5 + ValueTypePort = 6 + ValueTypeIP = 7 + ValueTypeIPList = 8 + ValueTypeChoice = 9 + ValueTypeChoiceDirname = 10 + ValueTypePercent = 11 + ValueTypeString = 12 + ValueTypeUri = 13 + ValueTypeStringList = 14 + + + class ChoiceFProxyCss: + Clean = 'Clean' + Boxed = 'boxed' + GrayAndBlue = 'grayandblue' + Sky = 'sky' + + ChoicesAll = (Clean, Boxed, GrayAndBlue, Sky) + ChoicesAllowMultiple = False + + class ChoiceLoggerPriority: + Error = 'ERROR' + Normal = 'NORMAL' + Minor = 'MINOR' + Debug = 'DEBUG' + + ChoicesAll = (Error, Normal, Minor, Debug) + ChoicesAllowMultiple = False + + class ChoiceNodeDownloadAllowedDirs: + All = 'all' + Downloads = 'downloads' + Nowhere = '' + + ChoicesAll = (All, Downloads, Nowhere) + ChoicesAllowMultiple = False + + class ChoiceNodeUploadAllowedDirs: + All = 'all' + Nowhere = '' + ChoicesAll = (All, Nowhere) + ChoicesAllowMultiple = False + + class ChoicePriorityPolicy: + Hard = 'HARD' + Soft = 'SOFT' + + ChoicesAll = (Hard, Soft) + ChoicesAllowMultiple = False + + + class ChoiceSSLVersion: + Stl = 'STL' + SslV3 = 'SSLV3' + TlsV1 = 'TLSv1' + + ChoicesAll = (Stl, SslV3, TlsV1) + ChoicesAllowMultiple = False + + + KeyConsole = 'console' + KeyFcp = 'fcp' + KeyFproxy = 'fproxy' + KeyLogger = 'logger' + KeyNode = 'node' + KeyNodeLoad = 'node.load' + KeyNodeOpennet = 'node.opennet' + KeyNodeScheduler = 'node.scheduler' + KeyNodeUpdaterer = 'node.updater' + KeyPluginmanager = 'pluginmanager' + KeyPluginmanager2 = 'pluginmanager2' + KeySSL = 'ssl' + KeyToadletSymlinker = 'toadletsymlinker' + + Keys = { + + 'console.allowedHosts': (ValueTypeIPList, None), # host names, single IPs CIDR-maskip IPs likee 192.168.0.0/24 + 'console.bindTo': (ValueTypeIPList, None), + 'console.directEnabled': (ValueTypeBool, None), + 'console.enabled': (ValueTypeBool, None), + 'console.port': (ValueTypePort, None), + 'console.ssl': (ValueTypeBool, None), + + + 'fcp.allowedHosts': (ValueTypeIPList, None), + 'fcp.allowedHostsFullAccess': (ValueTypeIPList, None), + 'fcp.assumeDownloadDDAIsAllowed': (ValueTypeBool, None), + 'fcp.assumeUploadDDAIsAllowed': (ValueTypeBool, None), + 'fcp.bindTo': (ValueTypeIP, None), + 'fcp.enabled': (ValueTypeBool, None), + 'fcp.persistentDownloadsEnabled': (ValueTypeBool, None), + 'fcp.persistentDownloadsFile': (ValueTypeFilename, None), + 'fcp.persistentDownloadsInterval': (ValueTypeInt, (0, None)), + 'fcp.port': (ValueTypePort, None), + 'fcp.ssl': (ValueTypeBool, None), + + + 'fproxy.CSSOverride': (ValueTypeBool, None), + 'fproxy.advancedModeEnabled': (ValueTypeBool, None), + 'fproxy.allowedHosts': (ValueTypeIPList, None), + 'fproxy.allowedHostsFullAccess': (ValueTypeIPList, None), + 'fproxy.bindTo': (ValueTypeIPList, None), + 'fproxy.css': (ValueTypeChoice, ChoiceFProxyCss), + 'fproxy.doRobots': (ValueTypeBool, None), + 'fproxy.enabled': (ValueTypeBool, None), + 'fproxy.javascriptEnabled': (ValueTypeBool, None), + 'fproxy.port': (ValueTypePort, None), + 'fproxy.showPanicButton': (ValueTypeBool, None), + 'fproxy.ssl': (ValueTypeBool, None), + + + 'logger.dirname': (ValueTypeDirname, None), + 'logger.enabled': (ValueTypeBool, None), + 'logger.interval': (ValueTypeUnknown, None), # ??? 1HOUR ?? + 'logger.maxCachedBytes': (ValueTypeBytes, None), + 'logger.maxCachedLines': (ValueTypeInt, (0, None)), # ??? + 'logger.maxZippedLogsSize': (ValueTypeBytes, None), # ??? + 'logger.priority': (ValueTypeChoice, ChoiceLoggerPriority), + 'logger.priorityDetail': (ValueTypeUnknown, None), # ???? Detailed priority thresholds + + + 'node.alwaysAllowLocalAddresses': (ValueTypeBool, None), + 'node.assumeNATed': (ValueTypeBool, None), + 'node.bindTo': (ValueTypeIP, None), + 'nod... [truncated message content] |
From: <jU...@us...> - 2007-11-14 11:03:31
|
Revision: 78 http://fclient.svn.sourceforge.net/fclient/?rev=78&view=rev Author: jUrner Date: 2007-11-14 03:03:35 -0800 (Wed, 14 Nov 2007) Log Message: ----------- typo Modified Paths: -------------- trunk/fclient/fclient_lib/fcp/fcp2_0.py Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py =================================================================== --- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-14 11:03:13 UTC (rev 77) +++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-14 11:03:35 UTC (rev 78) @@ -1286,7 +1286,7 @@ return identifier.startswith(self.IdentifierPrefix.ClientRequestInfo) - def modifyPersistantRequest(self, identifier, global_=False, priorityClass=None): + def modifyPersistentRequest(self, identifier, global_=False, priorityClass=None): """Modifies a request @param identifier: identifier of the request @param global: (bool) required. Is the request global or local? This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-14 11:03:08
|
Revision: 77 http://fclient.svn.sourceforge.net/fclient/?rev=77&view=rev Author: jUrner Date: 2007-11-14 03:03:13 -0800 (Wed, 14 Nov 2007) Log Message: ----------- continued working on menu actions 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-13 21:00:35 UTC (rev 76) +++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-14 11:03:13 UTC (rev 77) @@ -80,9 +80,9 @@ #TODO: implement or not ??? IndexLastProgress = 6 IndexDuration = 7 - - - StatusNone = 0x0 + + #TODO: order as appears in groups context menu... needs adjustment + StatusQueued = 0x0 StatusPending = 0x1 StatusRequestInfo = 0x2 StatusRequestInfoComplete = 0x4 @@ -112,7 +112,7 @@ self._requestName = None self._requestNamePostfix = None self._requestPriority = None - self._requestStatus = self.StatusNone + self._requestStatus = self.StatusQueued self._requestInitTime = time.time() self._uri = uri @@ -292,7 +292,8 @@ ] self.headerSections.sort() - self.itemStatus = { + self.itemStatus = { # DownloadItem.Status* --> shortName + DownloadItem.StatusQueued: self.trUtf8('Queued'), DownloadItem.StatusPending: self.trUtf8('Pending'), DownloadItem.StatusRequestInfo: self.trUtf8('Requesting'), DownloadItem.StatusRequestInfoComplete: self.trUtf8('Found'), @@ -303,14 +304,14 @@ DownloadItem.StatusError: self.trUtf8('Error'), } - self.requestPriorities = { # Priority* --> (shortName, longName) - downloadWidget.PriorityHighest: (self.trUtf8('0'), self.trUtf8('0 Highest')), - downloadWidget.PriorityHigher: (self.trUtf8('1'), self.trUtf8('1 High')), - downloadWidget.PriorityHigh: (self.trUtf8('2'), self.trUtf8('2 Higher')), - downloadWidget.PriorityNormal: (self.trUtf8('3'), self.trUtf8('3 Normal')), - downloadWidget.PriorityLow: (self.trUtf8('4'), self.trUtf8('4 Low')), - downloadWidget.PriorityLower: (self.trUtf8('5'), self.trUtf8('5 Lower')), - downloadWidget.PriorityLowest: (self.trUtf8('6'), self.trUtf8('6 Lowest')), + self.requestPriorities = { # Priority* --> (shortText, longText) + downloadWidget.PriorityHighest: (self.trUtf8('1'), self.trUtf8('1 Highest')), + downloadWidget.PriorityHigher: (self.trUtf8('2'), self.trUtf8('2 High')), + downloadWidget.PriorityHigh: (self.trUtf8('3'), self.trUtf8('3 Higher')), + downloadWidget.PriorityNormal: (self.trUtf8('4'), self.trUtf8('4 Normal')), + downloadWidget.PriorityLow: (self.trUtf8('5'), self.trUtf8('5 Low')), + downloadWidget.PriorityLower: (self.trUtf8('6'), self.trUtf8('6 Lower')), + downloadWidget.PriorityLowest: (self.trUtf8('7'), self.trUtf8('7 Lowest')), } self.unknown = self.trUtf8('???') @@ -323,16 +324,16 @@ # track by other means. Uh.. how? class DownloadWidget(QtGui.QTreeWidget): - PriorityHighest = 0 - PriorityHigher = 1 - PriorityHigh = 2 - PriorityNormal = 3 - PriorityLow = 4 - PriorityLower = 5 - PriorityLowest = 6 + PriorityHighest = 1 + PriorityHigher = 2 + PriorityHigh = 3 + PriorityNormal = 4 + PriorityLow = 5 + PriorityLower = 6 + PriorityLowest = 7 - # menu action names - ActAdjustPrioritiy = 'DownloadWidgetActAdjustPriority' + # menu and action names for priorities + MenuNameAdjustPrioritiy = 'DownloadWidgetMenuAdjustPriority' ActNamePriorityHighest = 'DownloadWidgetActPriorityHighest' ActNamePriorityHigher = 'DownloadWidgetActPriorityHigh' @@ -342,7 +343,7 @@ ActNamePriorityLower = 'DownloadWidgetActPriorityLower' ActNamePriorityLowest = 'DownloadWidgetActPriorityLowest' - ActNameMapping = { + ActNamePriorityMapping = { PriorityHighest: ActNamePriorityHighest, PriorityHigher: ActNamePriorityHigher, PriorityHigh: ActNamePriorityHigh, @@ -352,7 +353,33 @@ PriorityLowest: ActNamePriorityLowest, } + # menu and action names for groups + MenuNameClearGroup = 'DownloadWidgetMenuClearGroup' + ActNameStatusQueued = 'DownloadWidgetActStatusQueued' + ActNameStatusPending = 'DownloadWidgetActStatusPending' + ActNameStatusRequestInfo = 'DownloadWidgetActStatusRequestInfo' + ActNameStatusRequestInfoComplete = 'DownloadWidgetActStatusRequestInfoComplete' + ActNameStatusDownloading = 'DownloadWidgetActStatusDownloading' + ActNameStatusDownloadComplete = 'DownloadWidgetActStatusDownloadComplete' + + #ActNameStatusStopped = 'DownloadWidgetActStatusStopped' + ActNameStatusError = 'DownloadWidgetActStatusError' + + ActNameStatusMapping = { + DownloadItem.StatusQueued: ActNameStatusQueued, + DownloadItem.StatusPending: ActNameStatusPending, + DownloadItem.StatusRequestInfo: ActNameStatusRequestInfo, + DownloadItem.StatusRequestInfoComplete: ActNameStatusRequestInfoComplete, + DownloadItem.StatusDownloading: ActNameStatusDownloading, + DownloadItem.StatusDownloadComplete: ActNameStatusDownloadComplete, + + #DownloadItem.StatusStopped: ActNameStatusStopped, + DownloadItem.StatusError: ActNameStatusError, + } + + + def __init__(self, parent, connectionName='', @@ -365,6 +392,10 @@ @param directory: (str) directory to sownload items to or None to use default directory @param connectionName: name of the connection to the node @param cfg: (configConfig) instance or None + + @signal: 'hasCurrentItem(bool)' emitted when selection or current item changes. Param is true if there is a + current item or a selection + """ QtGui.QTreeWidget.__init__(self, parent) @@ -395,12 +426,27 @@ self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.connect( - self, - QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), - self.handleCustomContextMenu - ) + self, + QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), + self.handleCustomContextMenu + ) + # used to inform acts about has current item / selection available + self.connect( + self, + QtCore.SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), + self.handleCurrentItemChanged + ) + self.connect( + self, + QtCore.SIGNAL('itemSelectionChanged()'), + self.handleCurrentItemChanged + ) + + + + self.retranslate() @@ -420,7 +466,7 @@ if not self._isCreated: self._isCreated = True self._fcpClient = self._cfg.fcpClientManager.newClient(self._connectionName, self.handleFcpClientConnected) - self._fcpClient.setVerbosity(self._fcpClient.Verbosity.Debug) + #self._fcpClient.setVerbosity(self._fcpClient.Verbosity.Debug) # map Fcp priorities to our priorities, in case priority defines change # if self._fcpClient.Version == blah: (...) @@ -441,31 +487,16 @@ ############################################################# def handleCustomContextMenu(self, pt): m = QtGui.QMenu(self) + m.setTearOffEnabled(True) self.populateMenu(m) pt = self.viewport().mapToGlobal(pt) act = m.exec_(pt) return - - def handleAdjustPriority(self, priority): - """Slot called when a priority change of all currently selected items is triggered - @param priority: one of the Priority* consts - """ - items = self.selectedItems() - if not items: - item = self.currentItem() - if item is not None: - items = (item, ) - - priorityName = self._strings.requestPriorities[priority][0] - for item in items: - item.setRequestPriority(priority, priorityName) - - #TODO: more or less a guess that the item is still in the queue. We don't know - # exactly on context menu for example. Check if the node complains - if item.requestStatus() & DownloadItem.StatusMaskInNodeQueue: - self.adjustPriority(item.requestIdentifier(), priority) - + + def handleCurrentItemChanged(self, *params): + self.emit(QtCore.SIGNAL('hasCurrentItem(bool)'), self.hasCurrentItem()) + ############################################################# ## ## handlers for Fcp events @@ -573,12 +604,24 @@ #raise self._fcpClient.FetchError(params) - #TODO: not yet handled + #TODO: not tested!! def handleFcpClientIdentifierCollision(self, event, params): - pass - - - #TODO: handle priorityClass in clientGetFile() and clientRequestInfo() and sortf() + identifier = params['Identifier'] + item = self._requests['Downloads'].get(identifier, None) + if item is not None: + del self._requests['Downloads'][identifier] + + if self._fcpClient.isClientGetFile(identifier): + self._startDownload(item) + + elif isClientRequestInfo(identifier): + self._startRequestInfo(item) + + else: + #TODO: more here + pass + + def handleFcpClientIdle(self, event, params): # check if there are downloads queued @@ -622,7 +665,10 @@ def sortf(item1, item2): """Sort function to sort items in queue by priority / time""" - return cmp(item1.requestInitTime(), item2.requestInitTime()) + result = cmp(item1.requestPriority(), item2.requestPriority()) + if not result: + result = cmp(item1.requestInitTime(), item2.requestInitTime()) + return result # start downloads with info requested complete if downloadsToBeStarted > 0: @@ -632,21 +678,10 @@ downloadsToBeStarted -= 1 item = downloadsRequestInfoComplete.pop(0) - filename = os.path.join(self.downloadDirectoryFromItem(item), item.requestName()) - identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientGetFile) - priorityClass = item.requestPriority() - priorityClass = priorityClass if priorityClass else None # play it save - uri = item.requestUri() - - # tell node to remove completed RequestInfo + # tell node to remove info complete self._fcpClient.removePersistentRequest(item.requestIdentifier()) - - item.setRequestIdentifier(identifier) - self._adjustItemStatus(item, DownloadItem.StatusDownloading) - self._fcpClient.clientGetFile(uri, filename, identifier=identifier, priorityClass=priorityClass) - - self._requests['Downloads'][identifier] = item - + self._startDownload(item) + # start pending downloads if downloadsToBeStarted > 0: downloadsPending = items[DownloadItem.StatusPending] @@ -654,33 +689,18 @@ while downloadsPending and downloadsToBeStarted > 0: downloadsToBeStarted -= 1 - identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientRequestInfo) item = downloadsPending.pop(0) - priorityClass = item.requestPriority() - priorityClass = priorityClass if priorityClass else None # play it save + pendings may not have a priority - uri = item.requestUri() - - # 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, priorityClass=priorityClass) - - self._requests['Downloads'][identifier] = item - + self._startRequestInfo(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 + # uncommment for testing... removes all requests the node passes on connect + ##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 @@ -812,46 +832,110 @@ ## methods ## ####################################################### - def adjustPriority(self, identifier, priority): - """Adjusts the priority of a request - @param identifier: identifier of the request + def adjustPriority(self, priority): + """Adjusts the priority of all currently selected items or if no items are selected the current item @param priority: one of the Priority* consts @return: always None """ - keys, values = self._priorityMapping.keys(), self._priorityMapping.values() - n = values.index(priority) - fcpPriority = keys[n] - self._fcpClient.modifyPersistantRequest(identifier, priorityClass=fcpPriority) + items = self.selectedItems() + if not items: + item = self.currentItem() + if item is not None: + items = (item, ) + priorityName = self._strings.requestPriorities[priority][0] + for item in items: + item.setRequestPriority(priority, priorityName) + + #TODO: more or less a guess that the item is still in the queue. We don't know + # exactly on context menu for example. Check if the node complains + if item.requestStatus() & DownloadItem.StatusMaskInNodeQueue: + self._fcpClient.modifyPersistentRequest( + item.requestIdentifier(), + self._prorityToFcpPriority(priority) + ) + + + + #TODO: not yet implemented + #TODO: check if we don't run into race conditions when called from actions + def clearGroup(self, status): + """Removes all items with the specified status + @param status: DownloadItem.Status* + """ + + def download(self, uri, parent=None): self._requests['DownloadQueue'].append( (parent, uri) ) + + def hasCurrentItem(self): + """Checks if there is a current item or a selection + @return: (bool) + """ + hasCurrentItem = True + if self.currentItem() is None: + if not self.selectedItems(): + hasCurrentItem = False + return hasCurrentItem - #TODO: how do we get to know when to adjust actions (enable / disable)? + def populateMenu(self, menu): + """Populates a menu with all available actions + @return: (list) containing actions and emidiate submenus added to the menu + """ + acts = [] # add priorities submenu # - m = QtGui.QMenu(self.trUtf8('Adjust Priority'), menu) + hasCurrentItem = self.hasCurrentItem() + m = QtGui.QMenu(self.trUtf8('Adjust priority'), menu) + m.setObjectName(self.MenuNameAdjustPrioritiy) + m.setTearOffEnabled(True) + m.setEnabled(hasCurrentItem) + menu.addMenu(m) acts.append(m) + m.connect(self, QtCore.SIGNAL('hasCurrentItem(bool)'), m.setEnabled) priorities = self._priorityMapping.values() priorities.sort() for priority in priorities: - actName = self.ActNameMapping[priority] + actName = self.ActNamePriorityMapping[priority] actText = self._strings.requestPriorities[priority][1] act = QtGui.QAction(actText, menu) - cb = lambda method=self.handleAdjustPriority, arg=priority: method(arg) - print act.connect(act, QtCore.SIGNAL('triggered()'), cb) - act._cb = cb - + act.setObjectName(actName) + act.setEnabled(hasCurrentItem) + cb = lambda method=self.adjustPriority, arg=priority: method(arg) + act.connect(act, QtCore.SIGNAL('triggered()'), cb) + act._downloadWidgetCb = cb + act.connect(self, QtCore.SIGNAL('hasCurrentItem(bool)'), act.setEnabled) m.addAction(act) + # add clear groups menu + # + m = QtGui.QMenu(self.trUtf8('Clear group'), menu) + m.setObjectName(self.MenuNameClearGroup) + m.setTearOffEnabled(True) + + menu.addMenu(m) + acts.append(m) + + groups = self.ActNameStatusMapping.items() + groups.sort() + for status, actName in groups: + actText = self._strings.itemStatus[status] + act = QtGui.QAction(actText, menu) + cb = lambda method=self.clearGroup, arg=status: method(arg) + act.connect(act, QtCore.SIGNAL('triggered()'), cb) + act._downloadWidgetCb = cb + m.addAction(act) + + return acts @@ -945,6 +1029,55 @@ return item + def _prorityToFcpPriority(self, priority): + keys, values = self._priorityMapping.keys(), self._priorityMapping.values() + n = values.index(priority) + return keys[n] + + + def _startDownload(self, item): + """Starts downloading an item + @param item: (DownloadItem) + @note: no check is done to enshure the item is not already being downloaded + """ + filename = os.path.join(self.downloadDirectoryFromItem(item), item.requestName()) + identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientGetFile) + priorityClass = item.requestPriority() + priorityClass = priorityClass if priorityClass else None # play it save + uri = item.requestUri() + + item.setRequestIdentifier(identifier) + self._adjustItemStatus(item, DownloadItem.StatusDownloading) + self._fcpClient.clientGetFile(uri, filename, identifier=identifier, priorityClass=priorityClass) + + self._requests['Downloads'][identifier] = item + + + def _startRequestInfo(self, item): + """Starts requesting info for an item + @param item: (DownloadItem) + @note: no check is done to enshure the item is not already being requested + """ + identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientRequestInfo) + priorityClass = item.requestPriority() + priorityClass = priorityClass if priorityClass else None # play it save + pendings may not have a priority + uri = item.requestUri() + + # 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, priorityClass=priorityClass) + + self._requests['Downloads'][identifier] = item + + + def _uniqueDownloadsIdentifier(self, identifierPrefix): """Creates a new identifier that is unique to the internal sownloads dict @param identifierPrefix: FcpClient.IdentifierPrefix.* This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-13 21:00:47
|
Revision: 76 http://fclient.svn.sourceforge.net/fclient/?rev=76&view=rev Author: jUrner Date: 2007-11-13 13:00:35 -0800 (Tue, 13 Nov 2007) Log Message: ----------- some more words Modified Paths: -------------- trunk/TODO.txt Modified: trunk/TODO.txt =================================================================== --- trunk/TODO.txt 2007-11-13 20:59:47 UTC (rev 75) +++ trunk/TODO.txt 2007-11-13 21:00:35 UTC (rev 76) @@ -1,5 +1,33 @@ -Major todos +Term efinitions ******************************************************************************************** +Fcp == Freenet Client Protocol + + +Compatibility +******************************************************************************************** + +python >= 2.5 +Qt >= 4.3 (Qt and pyQt are still moving targets and compatibility to older versions nearly mpossible) + + + +Code +******************************************************************************************** + +indentation: Tabs. That is no spaces allowed for indentation. +classes, consts: camel case +method names: mixed case (Qt sets the standard here) +docstrings: an undocumentd class, method, (...) is no class (...) at all. +docstring markup: plain epydoc. No reSt allowed. +tests: as many unittests as possible. Pretty hard to do unittests for Fcp. Anyone any ideas? + + +Yet one or the other part of the code has to be adjusted to follow these rules. + + + +Major todos and thoughts +******************************************************************************************** x. fist widgets to implement: up / downloads, browser, message board Multipurpose up / downnload widget should be ready to go in relatively short time. @@ -18,7 +46,7 @@ ..and maybe more. -x. quite a lot of consts are undocumented. Consts used in config settings for example. +x. quite a lot of Fcp consts are undocumented. Consts used in config settings for example. Or the string representations of consts describing the status of peers. The node says hapily this peer is "CONNECTED". But couldn't find any info on the const, except tracking it down to the PEER_NODE_STATUS_CONNECTED const as defined in @@ -27,8 +55,10 @@ x. a widget that can be used to visualize freenet index files. This thingy should give some header information and allow the user to select any number of uris from an index for download. Already implemented a half - way working prototype. But quite unclear how to handle _massive_ indices reliably. Intersting - thing would be to allow for automized creation of index files from a bunch of files ready for + way working prototype. But quite unclear how to handle _massive_ indices reliably. Problem is index files + are Xml... nightmare to parse (hi, I am index and I am blowing up your machine on the next 3Gig CDATA string). + + Interesting thing would be to allow for automized creation of index files from a bunch of files ready for insertion (wizzard?). @@ -41,7 +71,12 @@ [https://frogpie.svn.sourceforge.net/svnroot/frogpie/trunk/sandbox/chm]. Shouldn't be too hard to polish this package and add the desired functionality. Basically its about adjusting the package to support output writers. Alternative - is to flesh "chm_lib/epydoc_fs.py" out and base a standalone module on it. + is to flesh out "chm_lib/epydoc_fs.py" and base a standalone module on it. + + + + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-13 20:59:42
|
Revision: 75 http://fclient.svn.sourceforge.net/fclient/?rev=75&view=rev Author: jUrner Date: 2007-11-13 12:59:47 -0800 (Tue, 13 Nov 2007) Log Message: ----------- started implementing priority handling + first glimpse of a context menu 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-13 20:57:40 UTC (rev 74) +++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-13 20:59:47 UTC (rev 75) @@ -27,7 +27,6 @@ from fclient_lib.qt4ex.ctrls import treewidgetwrap - sys.path.pop(0) del parentdir #<-- rel import hack @@ -58,7 +57,7 @@ ('MaxSimultaniousDownloads', ['toInt', 2]), ('MaxNewDownloadsPerHop', ['toInt', 100]), - + ('ClearCompletedDownloads', ['toBool', False]), ('ItemTipsShow', ['toBool', True]), ('ItemTipsDelay', ['toInt', areatips.DEFAULT_SHOW_DELAY]) @@ -90,12 +89,15 @@ StatusDownloading = 0x8 StatusDownloadComplete = 0x10 - StatusStopped = 0x1000 + #StatusStopped = 0x1000 # ??? StatusError = 0x20000 + # currently busy loading StatusMaskBusy = StatusRequestInfo | StatusDownloading + # the node knows about our request + StatusMaskInNodeQueue = StatusMaskBusy | StatusRequestInfoComplete | StatusDownloadComplete + - def __init__(self, parent, requestIdentifier, uri): """ @param parent: parent of the item @@ -109,6 +111,7 @@ self._requestIdentifier = requestIdentifier self._requestName = None self._requestNamePostfix = None + self._requestPriority = None self._requestStatus = self.StatusNone self._requestInitTime = time.time() self._uri = uri @@ -195,17 +198,19 @@ return self._requestName - def setRequestPriority(self, priority): + def setRequestPriority(self, priority, text): """Sets the request priority of the item - @param prority: (FcpClient.Priority) + @param prority: (DownloadWidget.Priority*) + @param text: text to display """ - self.setText(self.IndexPriority, priority) + self._requestPriority = priority + self.setText(self.IndexPriority, text) def requestPriority(self): """Returns the request priority of the item - @return: (unicode) request priority + @return: (DownloadWidget.Priority*) request priority """ - return unicode(self.text(self.IndexPriority)) + return self._requestPriority def setProgressWidget(self, tree, widget): """Associates a progress widget to the item @@ -254,11 +259,16 @@ """Returns the request uri of the item""" return self._uri - def setDDATested(self, flag): + """Sets wether the item is dda tested r not + @param flag: True if already tested, False otherwise + """ self._ddaTested = flag def ddaTested(self): + """Checks if the item was already dda tested + @return: (bool) + """ return self._ddaTested #***************************************************************************** @@ -267,10 +277,9 @@ class DownloadWidgetStrings(QtCore.QObject): """Strings for the download widget""" - def __init__(self, parent): - QtCore.QObject.__init__(self, parent) - - + def __init__(self, downloadWidget): + QtCore.QObject.__init__(self, downloadWidget) + self.headerSections = [ (DownloadItem.IndexName, self.trUtf8('Name')), (DownloadItem.IndexStatus, self.trUtf8('Status')), @@ -290,20 +299,60 @@ DownloadItem.StatusDownloading: self.trUtf8('Loading'), DownloadItem.StatusDownloadComplete: self.trUtf8('Complete'), - DownloadItem.StatusStopped: self.trUtf8('Stopped'), + #DownloadItem.StatusStopped: self.trUtf8('Stopped'), DownloadItem.StatusError: self.trUtf8('Error'), } - - self.unknown = self.trUtf8('Unknown') + self.requestPriorities = { # Priority* --> (shortName, longName) + downloadWidget.PriorityHighest: (self.trUtf8('0'), self.trUtf8('0 Highest')), + downloadWidget.PriorityHigher: (self.trUtf8('1'), self.trUtf8('1 High')), + downloadWidget.PriorityHigh: (self.trUtf8('2'), self.trUtf8('2 Higher')), + downloadWidget.PriorityNormal: (self.trUtf8('3'), self.trUtf8('3 Normal')), + downloadWidget.PriorityLow: (self.trUtf8('4'), self.trUtf8('4 Low')), + downloadWidget.PriorityLower: (self.trUtf8('5'), self.trUtf8('5 Lower')), + downloadWidget.PriorityLowest: (self.trUtf8('6'), self.trUtf8('6 Lowest')), + } - + self.unknown = self.trUtf8('???') + #**************************************************************************** # #**************************************************************************** -#TODO: when closing we have to empty queues by sending ClientGet requests +#TODO: when closing we have to send ClientGet for all pending requests by, so they don't get lost. +#HINT: might take a while, so take care to handle gracefully (user panic). Alternative is to keep +# track by other means. Uh.. how? class DownloadWidget(QtGui.QTreeWidget): + + PriorityHighest = 0 + PriorityHigher = 1 + PriorityHigh = 2 + PriorityNormal = 3 + PriorityLow = 4 + PriorityLower = 5 + PriorityLowest = 6 + + # menu action names + ActAdjustPrioritiy = 'DownloadWidgetActAdjustPriority' + ActNamePriorityHighest = 'DownloadWidgetActPriorityHighest' + ActNamePriorityHigher = 'DownloadWidgetActPriorityHigh' + ActNamePriorityHigh = 'DownloadWidgetActPriorityHigh' + ActNamePriorityNormal = 'DownloadWidgetActPriorityNormal' + ActNamePriorityLow = 'DownloadWidgetActPriorityLow' + ActNamePriorityLower = 'DownloadWidgetActPriorityLower' + ActNamePriorityLowest = 'DownloadWidgetActPriorityLowest' + + ActNameMapping = { + PriorityHighest: ActNamePriorityHighest, + PriorityHigher: ActNamePriorityHigher, + PriorityHigh: ActNamePriorityHigh, + PriorityNormal: ActNamePriorityNormal, + PriorityLow: ActNamePriorityLow, + PriorityLower: ActNamePriorityLower, + PriorityLowest: ActNamePriorityLowest, + } + + def __init__(self, parent, connectionName='', @@ -317,7 +366,6 @@ @param connectionName: name of the connection to the node @param cfg: (configConfig) instance or None """ - QtGui.QTreeWidget.__init__(self, parent) self._connectionName = connectionName @@ -331,10 +379,10 @@ self._fcpClient = None self._isCreated = False self._itemTips = areatips.ItemTips(self) + self._priorityMapping = {} self._strings = None self._userSettings = UserSettings() - - + # setup item tips self._itemTips.setEnabled(self._userSettings['ItemTipsShow']) self._itemTips.setShowDelay(self._userSettings['ItemTipsDelay']) @@ -364,23 +412,60 @@ #self._requests['DownloadDirectories'][self._directory] = (self, uris) + + #TODO: "PriorityLowest" might be a bit misleading in priorityMapping + # ... according to docs it equals "will never complete". If true, implement + # DownloadItem.StatusStopped and leave out FcpClient.Priority.Minimum 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) - - + + # map Fcp priorities to our priorities, in case priority defines change + # if self._fcpClient.Version == blah: (...) + self._priorityMapping = { + self._fcpClient.Priority.Maximum: self.PriorityHighest, + self._fcpClient.Priority.Interactive: self.PriorityHigher, + self._fcpClient.Priority.SemiInteractive: self.PriorityHigh, + self._fcpClient.Priority.Updatable: self.PriorityNormal, + self._fcpClient.Priority.Bulk: self.PriorityLow, + self._fcpClient.Priority.Prefetch: self.PriorityLower, + self._fcpClient.Priority.Minimum: self.PriorityLowest, + } + ############################################################# ## ## handlers for Qt events ## ############################################################# - def handleCustomContextMenu(self): - pass + def handleCustomContextMenu(self, pt): + m = QtGui.QMenu(self) + self.populateMenu(m) + pt = self.viewport().mapToGlobal(pt) + act = m.exec_(pt) + return - - + + def handleAdjustPriority(self, priority): + """Slot called when a priority change of all currently selected items is triggered + @param priority: one of the Priority* consts + """ + items = self.selectedItems() + if not items: + item = self.currentItem() + if item is not None: + items = (item, ) + + priorityName = self._strings.requestPriorities[priority][0] + for item in items: + item.setRequestPriority(priority, priorityName) + + #TODO: more or less a guess that the item is still in the queue. We don't know + # exactly on context menu for example. Check if the node complains + if item.requestStatus() & DownloadItem.StatusMaskInNodeQueue: + self.adjustPriority(item.requestIdentifier(), priority) + ############################################################# ## ## handlers for Fcp events @@ -398,6 +483,7 @@ (self._fcpClient.events.SimpleProgress, self.handleFcpClientSimpleProgress), (self._fcpClient.events.DataFound, self.handleFcpClientDataFound), (self._fcpClient.events.PersistentGet, self.handleFcpClientPersistentGet), + (self._fcpClient.events.PersistentRequestModified, self.handleFcpClientPersistentRequestModified), (self._fcpClient.events.GetFailed, self.handleFcpClientGetFailed), (self._fcpClient.events.IdentifierCollision, self.handleFcpClientIdentifierCollision), @@ -492,9 +578,10 @@ pass + #TODO: handle priorityClass in clientGetFile() and clientRequestInfo() and sortf() def handleFcpClientIdle(self, event, params): - # check if there are sownloads queued + # check if there are downloads queued n = 0 maxNewDls = self._userSettings['MaxNewDownloadsPerHop'] while self._requests['DownloadQueue'] and n < maxNewDls: @@ -533,9 +620,8 @@ downloadsToBeStarted = self._userSettings['MaxSimultaniousDownloads'] - itemsBusy - - #TODO: sort by priority def sortf(item1, item2): + """Sort function to sort items in queue by priority / time""" return cmp(item1.requestInitTime(), item2.requestInitTime()) # start downloads with info requested complete @@ -548,6 +634,8 @@ item = downloadsRequestInfoComplete.pop(0) filename = os.path.join(self.downloadDirectoryFromItem(item), item.requestName()) identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientGetFile) + priorityClass = item.requestPriority() + priorityClass = priorityClass if priorityClass else None # play it save uri = item.requestUri() # tell node to remove completed RequestInfo @@ -555,7 +643,7 @@ item.setRequestIdentifier(identifier) self._adjustItemStatus(item, DownloadItem.StatusDownloading) - self._fcpClient.clientGetFile(uri, filename, identifier=identifier) + self._fcpClient.clientGetFile(uri, filename, identifier=identifier, priorityClass=priorityClass) self._requests['Downloads'][identifier] = item @@ -568,6 +656,8 @@ identifier = self._uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientRequestInfo) item = downloadsPending.pop(0) + priorityClass = item.requestPriority() + priorityClass = priorityClass if priorityClass else None # play it save + pendings may not have a priority uri = item.requestUri() # ClientToken will be set to item parent identifier @@ -579,7 +669,7 @@ item.setRequestIdentifier(identifier) self._adjustItemStatus(item, DownloadItem.StatusRequestInfo) - self._fcpClient.clientRequestInfo(uri, identifier=identifier, clientToken=clientToken) + self._fcpClient.clientRequestInfo(uri, identifier=identifier, clientToken=clientToken, priorityClass=priorityClass) self._requests['Downloads'][identifier] = item @@ -614,7 +704,26 @@ status = DownloadItem.StatusRequestInfo self._adjustItemStatus(item, status) - + + # + fcpPriority = params['PriorityClass'] + priority = self._priorityMapping[fcpPriority] + priorityText = self._strings.requestPriorities[priority][0] + item.setRequestPriority(priority, priorityText) + + + def handleFcpClientPersistentRequestModified(self, event, params): + identifier = params['Identifier'] + item = self._requests['Downloads'].get(identifier, None) + if item is not None: + + fcpPriority = params.get('PriorityClass', None) + if fcpPriority is not None: + priority = self._priorityMapping[fcpPriority] + priorityText = self._strings.requestPriorities[priority][0] + tem.setRequestPriority(priority, priorityText) + + def handleFcpClientProtocolError(self, event, params): identifier = params.get('Identifier', None) @@ -703,10 +812,48 @@ ## methods ## ####################################################### + def adjustPriority(self, identifier, priority): + """Adjusts the priority of a request + @param identifier: identifier of the request + @param priority: one of the Priority* consts + @return: always None + """ + keys, values = self._priorityMapping.keys(), self._priorityMapping.values() + n = values.index(priority) + fcpPriority = keys[n] + self._fcpClient.modifyPersistantRequest(identifier, priorityClass=fcpPriority) + + def download(self, uri, parent=None): self._requests['DownloadQueue'].append( (parent, uri) ) - #TODO: adjust priority? + + + #TODO: how do we get to know when to adjust actions (enable / disable)? + def populateMenu(self, menu): + acts = [] + + # add priorities submenu + # + m = QtGui.QMenu(self.trUtf8('Adjust Priority'), menu) + menu.addMenu(m) + acts.append(m) + + priorities = self._priorityMapping.values() + priorities.sort() + for priority in priorities: + actName = self.ActNameMapping[priority] + actText = self._strings.requestPriorities[priority][1] + act = QtGui.QAction(actText, menu) + cb = lambda method=self.handleAdjustPriority, arg=priority: method(arg) + print act.connect(act, QtCore.SIGNAL('triggered()'), cb) + act._cb = cb + + m.addAction(act) + + + return acts + def retranslate(self): self._strings = DownloadWidgetStrings(self) @@ -808,6 +955,7 @@ if identifier not in self._requests['Downloads']: return identifier + #*************************************************************************************************** # #*************************************************************************************************** @@ -838,7 +986,7 @@ w.setCentralWidget(peers) #m = w.menuBar() - #m1 = m.addMenu('Peers') + #m1 = m.addMenu('Requests') #peers.populateMenu(m1) w.show() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-13 20:57:36
|
Revision: 74 http://fclient.svn.sourceforge.net/fclient/?rev=74&view=rev Author: jUrner Date: 2007-11-13 12:57:40 -0800 (Tue, 13 Nov 2007) Log Message: ----------- some more docs Modified Paths: -------------- trunk/fclient/fclient_ui/fcp_client_manager.py Modified: trunk/fclient/fclient_ui/fcp_client_manager.py =================================================================== --- trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-13 20:57:13 UTC (rev 73) +++ trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-13 20:57:40 UTC (rev 74) @@ -261,7 +261,9 @@ def stop(self): - """Stops polling the FcpClient""" + """stops trying to establish a connection to the node stops polling the FcpClient for new messages + @note: it is not guaranteed that connecting and / or polling is stopped emidiately + """ self.connectTimer.stop() self.pollTimer.stop() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-13 20:57:12
|
Revision: 73 http://fclient.svn.sourceforge.net/fclient/?rev=73&view=rev Author: jUrner Date: 2007-11-13 12:57:13 -0800 (Tue, 13 Nov 2007) Log Message: ----------- more methods, more flags Modified Paths: -------------- trunk/fclient/fclient_lib/fcp/fcp2_0.py Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py =================================================================== --- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-12 11:33:36 UTC (rev 72) +++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-13 20:57:13 UTC (rev 73) @@ -613,7 +613,7 @@ """All known peer note types""" Private = '1' - class Priorities: + class Priority: """All priorities supported by the client""" Maximum = '0' Interactive = '1' @@ -623,9 +623,7 @@ Prefetch = '5' Minimum = '6' - PriorityMin = Minimum - PriorityDefault = Bulk - + class ProtocolError(Exception): """All protocol errors supported by the client""" @@ -1200,7 +1198,7 @@ ## ########################################################## #TODO: not complete yet - def clientGetFile(self, uri, filename, identifier=None, clientToken=None): + def clientGetFile(self, uri, filename, identifier=None, clientToken=None, priorityClass=None): """ """ if identifier is None: @@ -1225,14 +1223,17 @@ #BinaryBlob='false', Filename=filename, ) - if clientToken is not None: + if clientToken is not None: msg['ClientToken'] = clientToken + if priorityClass is not None: + msg['PriorityClass'] = priorityClass + self.sendMessageEx(msg) return identifier - def clientRequestInfo(self, uri, identifier=None, clientToken=None, **params): + def clientRequestInfo(self, uri, identifier=None, clientToken=None, priorityClass=None, **params): """Requests info about a file @param uri: uri of the file to request info about @param identifier: request identifier or None to let the method create one. If an identifier is passed, it has to be @@ -1262,10 +1263,11 @@ Verbosity='1', **params ) - if clientToken is not None: msg['ClientToken'] = clientToken - + if priorityClass is not None: + msg['PriorityClass'] = priorityClass + self.sendMessageEx(msg) return identifier @@ -1284,6 +1286,24 @@ return identifier.startswith(self.IdentifierPrefix.ClientRequestInfo) + def modifyPersistantRequest(self, identifier, global_=False, priorityClass=None): + """Modifies a request + @param identifier: identifier of the request + @param global: (bool) required. Is the request global or local? + @param clientToken: new client token or None + @param priorityClass: new priority or None + """ + msg = self.Message( + self.Message.ModifyPersistentRequest, + Identifier=identifier, + Global=self.fcpBool(global_), + ) + if priorityClass is not None: + msg['PriorityClass'] = priorityClass + self.sendMessageEx(msg) + + + def removePersistentRequest(self, identifier, global_=False): """Removes a request @param identifier: (str) identifier of the request to remove This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-12 11:33:32
|
Revision: 72 http://fclient.svn.sourceforge.net/fclient/?rev=72&view=rev Author: jUrner Date: 2007-11-12 03:33:36 -0800 (Mon, 12 Nov 2007) Log Message: ----------- added project management script Added Paths: ----------- trunk/update_project.py Added: trunk/update_project.py =================================================================== --- trunk/update_project.py (rev 0) +++ trunk/update_project.py 2007-11-12 11:33:36 UTC (rev 72) @@ -0,0 +1,59 @@ +"""Project management for fclient + +dependencies: + 1. epydoc if developer documentation should be created +""" + + +import shutil +import sys +import os + +from fclient.fclient_lib.qt4ex.scripts import qtpro, manifest, pylupdate +#******************************************************************************* +# +#******************************************************************************* +DirSelf = os.path.dirname(os.path.abspath(__file__)) + +DirDoc = os.path.join(DirSelf, 'fclient/doc') +DirDocDeveloper = os.path.join(DirDoc, 'developer') + +# locale names of translations we currently support +Locales = ( + 'de' + 'en', + ) + +#******************************************************************************** +# +#******************************************************************************** +def createDeveloperDocs(): + from epydoc import cli + + shutil.rmtree(DirDocDeveloper) + os.mkdir(DirDocDeveloper) + sys.argv = ['--html', '-o', DirDocDeveloper, 'fclient'] + cli.cli() + +#********************************************************************************* +# +#********************************************************************************* +if __name__ == '__main__': + + # create manifest, update *.pro and translations + d = os.path.dirname(__file__) + manifest = manifest.createManifest(d) + d = os.path.join(d, 'fclient') + pro = qtpro.createProjectFile(d, locales=Locales, prefix='fclient_') + pylupdate.main(pro) + + # create a fresh set of developer docs (huge!!) + ##createDeveloperDocs() + + + + + + + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-12 10:41:54
|
Revision: 71 http://fclient.svn.sourceforge.net/fclient/?rev=71&view=rev Author: jUrner Date: 2007-11-12 02:41:59 -0800 (Mon, 12 Nov 2007) Log Message: ----------- ... 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:37:52 UTC (rev 70) +++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-12 10:41:59 UTC (rev 71) @@ -473,7 +473,8 @@ self._requests['DownloadDirectories'][directory].append(item) else: self._requests['DownloadDirectories'][directory] = [item, ] - #NOTE: have to take care to not send neted TestDDAs + #NOTE: have to take care to not send nested TestDDAs, + # if so, node will (may) throw a ProtocolError self._fcpClient.testDDA(directory, wantWriteDirectory=True) self._requests['Downloads'][item.requestIdentifier()] = item This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
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. |
From: <jU...@us...> - 2007-11-12 10:36:04
|
Revision: 69 http://fclient.svn.sourceforge.net/fclient/?rev=69&view=rev Author: jUrner Date: 2007-11-12 02:36:01 -0800 (Mon, 12 Nov 2007) Log Message: ----------- ... Modified Paths: -------------- trunk/fclient/fclient_ui/fcp_client_manager.py Modified: trunk/fclient/fclient_ui/fcp_client_manager.py =================================================================== --- trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-12 10:35:40 UTC (rev 68) +++ trunk/fclient/fclient_ui/fcp_client_manager.py 2007-11-12 10:36:01 UTC (rev 69) @@ -142,7 +142,6 @@ from fclient_lib.fcp.fcp2_0 import FcpClient fcpClient = FcpClient( connectionName=name, - #verbosity=FcpClient.Verbosity.Debug, ) fcpClient.events.ClientConnected += eventConnectedHandler handler = FcpClientHandler( This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-12 10:35:42
|
Revision: 68 http://fclient.svn.sourceforge.net/fclient/?rev=68&view=rev Author: jUrner Date: 2007-11-12 02:35:40 -0800 (Mon, 12 Nov 2007) Log Message: ----------- ... Modified Paths: -------------- trunk/fclient/fclient_lib/pyex/namespace.py Modified: trunk/fclient/fclient_lib/pyex/namespace.py =================================================================== --- trunk/fclient/fclient_lib/pyex/namespace.py 2007-11-12 10:35:09 UTC (rev 67) +++ trunk/fclient/fclient_lib/pyex/namespace.py 2007-11-12 10:35:40 UTC (rev 68) @@ -136,7 +136,6 @@ #********************************************************************* # #********************************************************************* - if __name__ == '__main__': import doctest doctest.testmod() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-12 10:35:06
|
Revision: 67 http://fclient.svn.sourceforge.net/fclient/?rev=67&view=rev Author: jUrner Date: 2007-11-12 02:35:09 -0800 (Mon, 12 Nov 2007) Log Message: ----------- bit of this and that Modified Paths: -------------- trunk/fclient/fclient_lib/fcp/fcp2_0.py Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py =================================================================== --- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-10 11:30:27 UTC (rev 66) +++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-12 10:35:09 UTC (rev 67) @@ -168,6 +168,7 @@ 'ClientRequestInfo', + 'PersistentGet', 'DataFound', 'GetFailed', 'SimpleProgress', @@ -318,6 +319,7 @@ + class IdentifierPrefix: """Special purpose identifier prefixes""" ClientGetFile = 'ClientGetFile::' @@ -691,7 +693,7 @@ """ self._connectionName = connectionName - self._ddaTmpFiles = [] + self._ddaTmpFiles = {} self._log = logging.getLogger(name) self._socket = None @@ -813,6 +815,7 @@ ## #################################################### elif msg.name == self.Message.TestDDAReply: + directory = msg['Directory'] fpathWrite = msg.params.get('WriteFilename', None) fpathRead = msg.params.get('ReadFilename', None) readContent = '' @@ -821,8 +824,13 @@ if not written: saveRemoveFile(fpathWrite) else: - self._ddaTmpFiles.append(fpathWrite) - + # ...hope the node will keep request order so the correct + # tmp file is removed on completion + if directory in self._ddaTmpFiles: + self._ddaTmpFiles[directory].append(fpathWrite) + else: + self._ddaTmpFiles[directory] = [fpathWrite, ] + if fpathRead is not None: readContent = saveReadFile(fpathRead) if readContent is None: @@ -835,12 +843,11 @@ ) return True - #TODO: unconditionally clean up all tmp files? Looks like trouble.. elif msg.name == self.Message.TestDDAComplete: - # clean tmp files - for fpath in self._ddaTmpFiles: - saveRemoveFile(fpath) - self._ddaTmpFiles = [] + # clean up tmp file + directory = msg['Directory'] + tmp_file = self._ddaTmpFiles[directory].pop(0) + saveRemoveFile(tmp_file) self.events.TestDDAComplete(msg.params) return True @@ -926,6 +933,10 @@ self.events.IdentifierCollision(msg.params) return True + elif msg.name == self.Message.PersistentGet: + self.events.PersistentGet(msg.params) + return True + elif msg.name == self.Message.PersistentRequestModified: self.events.PersistentRequestModified(msg.params) return True @@ -1189,13 +1200,13 @@ ## ########################################################## #TODO: not complete yet - def clientGetFile(self, uri, filename, identifier=None): + def clientGetFile(self, uri, filename, identifier=None, clientToken=None): """ """ if identifier is None: identifier = self.IdentifierPrefix.ClientGetFile + self.newIdentifier() else: - assert identifier.startswith(self.IdentifierPrefix.ClientGetFile), 'Wrong prefix' + assert self.isClientGetFile(identifier), 'Wrong prefix' msg = self.Message( self.Message.ClientGet, IgnoreDS='false', @@ -1214,12 +1225,14 @@ #BinaryBlob='false', Filename=filename, ) + if clientToken is not None: + msg['ClientToken'] = clientToken self.sendMessageEx(msg) return identifier - def clientRequestInfo(self, uri, identifier=None, **params): + def clientRequestInfo(self, uri, identifier=None, clientToken=None, **params): """Requests info about a file @param uri: uri of the file to request info about @param identifier: request identifier or None to let the method create one. If an identifier is passed, it has to be @@ -1234,13 +1247,13 @@ if identifier is None: identifier = self.IdentifierPrefix.ClientRequestInfo + self.newIdentifier() else: - assert identifier.startswith(self.IdentifierPrefix.ClientRequestInfo), 'Wrong prefix' - self.sendMessage( + assert self.isClientRequestInfo(identifier), 'Wrong prefix' + msg = self.Message( self.Message.ClientGet, Identifier=identifier, URI=uri, #TODO: persistance??? - #Persistence='forever', + Persistence='forever', # suggested by Mathew Toseland to use about 32k for mimeType requests # basic sizes of keys are: 1k for SSks and 32k for CHKs @@ -1249,9 +1262,41 @@ Verbosity='1', **params ) + + if clientToken is not None: + msg['ClientToken'] = clientToken + + self.sendMessageEx(msg) return identifier + + def isClientGetFile(self, identifier): + """Checks if an identifier is a ClientGetFile identifier + @return: (bool) + """ + return identifier.startswith(self.IdentifierPrefix.ClientGetFile) + + def isClientRequestInfo(self, identifier): + """Checks if an identifier is a RequestFileInfo identifier + @return: (bool) + """ + return identifier.startswith(self.IdentifierPrefix.ClientRequestInfo) + + + def removePersistentRequest(self, identifier, global_=False): + """Removes a request + @param identifier: (str) identifier of the request to remove + @param global_: (bool) has to be set to True if the request was a global request + """ + self.sendMessage( + self.Message.RemovePersistentRequest, + Global=self.fcpBool(global_), + Identifier=identifier + ) + + + #TODO: check if node passes directory unchanged to testDDAComplete (trailing slash) def testDDA(self, directory, wantReadDirectory=None, wantWriteDirectory=None): """Tests a directory for read / write access @param directory: directory to test @@ -1292,6 +1337,7 @@ msg['WantWriteDirectory'] = self.fcpBool(wantWriteDirectory) self.sendMessageEx(msg) + ########################################################## ## ## others This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-10 11:30:23
|
Revision: 66 http://fclient.svn.sourceforge.net/fclient/?rev=66&view=rev Author: jUrner Date: 2007-11-10 03:30:27 -0800 (Sat, 10 Nov 2007) Log Message: ----------- implemented actual download of uris 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-10 11:28:52 UTC (rev 65) +++ trunk/fclient/fclient_widgets/download_widget.py 2007-11-10 11:30:27 UTC (rev 66) @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + """Sketch for a download widget """ @@ -105,6 +107,8 @@ self._requestError = None self._requestIdentifier = requestIdentifier + self._requestName = None + self._requestNamePostfix = None self._requestStatus = self.StatusNone self._requestInitTime = time.time() self._uri = uri @@ -144,20 +148,50 @@ self.setText(self.IndexMimeType, mimeType) def requestMimeType(self): - """Returns the mime type of the item""" - return self.text(self.IndexMimeType) + """Returns the mime type of the item + @return: (unicode) request mime type + """ + return unicode(self.text(self.IndexMimeType)) + def setRequestName(self, name): """Sets the request name of the item @param name: (str) name + @return: (unicode) name (including filename collision handling postfix) + @note: the name should not include the filename collision handling postfix """ + self._requestName = name + if self._requestNamePostfix is not None: + name, exts = namespace.split_extensions(name) + name = '%s (%s)%s' % (name, self._requestNamePostfix, ''.join(exts)) self.setText(self.IndexName, name) + return name - def requestName(self): + + def increaseRequestNamePostfix(self): + """Increases the filename collision handling postfix by one + @return: (unicode) new request name + @note: use this to compose one of these 'filename (1).txt' filenames in case of + filename collisons + """ + if self._requestNamePostfix is None: + self._requestNamePostfix = 1 + else: + self._requestNamePostfix += 1 + return self.setRequestName(self.requestName(includePostfix=False)) + + + def requestName(self, includePostfix=True): """Returns the request name of the item + @param includePostfix: if True, returns the request name including file collision handling postfi, + if False the request name is returned without postfix + @return: (unicode) request name """ - return self.text(self.IndexName) + if includePostfix: + return unicode(self.text(self.IndexName)) + return self._requestName + def setRequestPriority(self, priority): """Sets the request priority of the item @param prority: (FcpClient.Priority) @@ -165,8 +199,10 @@ self.setText(self.IndexPriority, priority) def requestPriority(self): - """Returns the request priority of the item""" - return self.text(self.IndexPriority) + """Returns the request priority of the item + @return: (unicode) request priority + """ + return unicode(self.text(self.IndexPriority)) def setProgressWidget(self, tree, widget): """Associates a progress widget to the item @@ -200,8 +236,10 @@ self.setText(self.IndexSize, size) def requestSize(self): - """Returns the request size of the item""" - return self.text(self.IndexSize) + """Returns the request size of the item + @return: (unicode) request size + """ + return unicode(self.text(self.IndexSize)) def setRequestUri(self, uri): """Sets the request uri of the item @@ -253,6 +291,9 @@ #**************************************************************************** # #**************************************************************************** + +#TODO: no idea if the node removes requests when complete or on error or +# if RemovePersistentRequest has to be send explicitely class DownloadWidget(QtGui.QTreeWidget): def __init__(self, @@ -341,9 +382,11 @@ (self._fcpClient.events.TestDDAComplete, self.handleFcpClientTestDDAComplete), (self._fcpClient.events.ClientRequestInfo, self.handleFcpClientRequestInfo), - (self._fcpClient.events.ClientRequestInfoProgress, self.handleFcpClientRequestInfoProgress), + (self._fcpClient.events.SimpleProgress, self.handleFcpClientSimpleProgress), + (self._fcpClient.events.DataFound, self.handleFcpClientDataFound), (self._fcpClient.events.GetFailed, self.handleFcpClientGetFailed), + (self._fcpClient.events.IdentifierCollision, self.handleFcpClientIdentifierCollision), (self._fcpClient.events.ProtocolError, self.handleFcpClientProtocolError), ) # take care not to connect twice @@ -356,17 +399,52 @@ self._fcpClient.testDDA(directory, wantWriteDirectory=True) + #TODO: not yet handled def handleFcpClientDisconnected(self, event, params): """ """ + def handleFcpClientDataFound(self, event, params): + """ + """ + identifier = params['Identifier'] + item = self._downloads['Downloads'].get(identifier, None) + if item is not None: + del self._downloads['Downloads'][identifier] + + progress = item.progressWidget(self) + status = DownloadItem.StatusDownloadComplete + + 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? def handleFcpClientGetFailed(self, event, params): identifier = params['Identifier'] item = self._downloads['Downloads'].get(identifier, None) if item is not None: del self._downloads['Downloads'][identifier] + code = params['Code'] + # handle file name collision + 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) + uri = item.requestUri() + + self._fcpClient.clientGetFile(uri, filename, identifier=identifier) + self._downloads['Downloads'][identifier] = item + return + + # handle error progress = item.progressWidget(self) status = DownloadItem.StatusError @@ -381,8 +459,14 @@ colorBar=self._userSettings['ColorProgressBarError'], colorBg=self._userSettings['ColorProgressBgError'] ) + #raise self._fcpClient.FetchError(params) + #TODO: not yet handled + def handleFcpClientIdentifierCollision(self, vent, params): + pass + + def handleFcpClientIdle(self, event, params): # check if there are sownloads queued @@ -423,31 +507,49 @@ elif status & item.StatusRequestInfoComplete: items[status].append(item) - # start items with info requested - itemsToBeStarted = self._userSettings['MaxSimultaniousDownloads'] - itemsBusy - if itemsToBeStarted > 0: - def sortf(item1, item2): - return cmp(item1.requestInitTime(), item2.requestInitTime()) - - infoCompleteItems = items[DownloadItem.StatusRequestInfoComplete] - infoCompleteItems.sort(cmp=sortf) - while infoCompleteItems: - - #TODO: not yet implemented - break - itemsToBeStarted -= 1 - + downloadsToBeStarted = self._userSettings['MaxSimultaniousDownloads'] - itemsBusy - # start pending items - if itemsToBeStarted > 0: - pendingItems = items[DownloadItem.StatusPending] - pendingItems.sort(cmp=sortf) - while pendingItems: - itemsToBeStarted -= 1 - if itemsToBeStarted < 0: + #TODO: sort by priority + def sortf(item1, item2): + return cmp(item1.requestInitTime(), item2.requestInitTime()) + + # start downloads with info requested complete + if downloadsToBeStarted > 0: + downloadsRequestInfoComplete = items[DownloadItem.StatusRequestInfoComplete] + downloadsRequestInfoComplete.sort(cmp=sortf) + while downloadsRequestInfoComplete: + downloadsToBeStarted -= 1 + if downloadsToBeStarted < 0: break - item = pendingItems.pop(0) + 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 + 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]) + self._fcpClient.clientGetFile(uri, filename, identifier=identifier) + self._downloads['Downloads'][identifier] = item + + # start pending downloads + if downloadsToBeStarted > 0: + downloadsPending = items[DownloadItem.StatusPending] + downloadsPending.sort(cmp=sortf) + while downloadsPending: + downloadsToBeStarted -= 1 + if downloadsToBeStarted < 0: + break + + identifier = self.uniqueDownloadsIdentifier(self._fcpClient.IdentifierPrefix.ClientRequestInfo) + item = downloadsPending.pop(0) progress = progressbarwrap.ProgressBarEx(self) status = DownloadItem.StatusRequestInfo uri = item.requestUri() @@ -460,11 +562,9 @@ ) item.setProgressWidget(self, progress) item.setRequestStatus(status, self._strings.itemStatus[status]) - - identifier = self._fcpClient.clientRequestInfo(uri) + self._fcpClient.clientRequestInfo(uri, identifier=identifier) self._downloads['Downloads'][identifier] = item - - + def handleFcpClientProtocolError(self, event, params): identifier = params.get('Identifier', None) @@ -505,7 +605,7 @@ item.setRequestSize(dataLength) - def handleFcpClientRequestInfoProgress(self, event, params): + def handleFcpClientSimpleProgress(self, event, params): identifier = params['Identifier'] item = self._downloads['Downloads'].get(identifier, None) @@ -558,6 +658,33 @@ #TODO: retranslate item status + def downloadDirectoryFromItem(self, item): + """Returns the dirctory an item should be downloaded to + @param item: (QTreeWidgetItem) + @return: (str) directory + """ + out = [] + parent = item.parent() + while parent is not None: + out.append(parent.requestName()) + parent = parent.parent() + + out.append(self._directory) + out.reverse() + return os.path.join(*out) + + + 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']: + return identifier + + #*************************************************************************************************** # #*************************************************************************************************** This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-10 11:28:50
|
Revision: 65 http://fclient.svn.sourceforge.net/fclient/?rev=65&view=rev Author: jUrner Date: 2007-11-10 03:28:52 -0800 (Sat, 10 Nov 2007) Log Message: ----------- request identifiers can now be set explicitely + EventFileInfoProgress has gone + a bit of this and that Modified Paths: -------------- trunk/fclient/fclient_lib/fcp/fcp2_0.py Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py =================================================================== --- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-10 11:27:11 UTC (rev 64) +++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-10 11:28:52 UTC (rev 65) @@ -167,8 +167,7 @@ 'IdentifierCollision', 'ClientRequestInfo', - 'ClientRequestInfoProgress', - + 'DataFound', 'GetFailed', 'SimpleProgress', @@ -321,6 +320,7 @@ class IdentifierPrefix: """Special purpose identifier prefixes""" + ClientGetFile = 'ClientGetFile::' ClientRequestInfo = 'ClientRequestInfo::' class InsertError(Exception): @@ -919,10 +919,7 @@ return True elif msg.name == self.Message.SimpleProgress: - if msg['Identifier'].startswith(self.IdentifierPrefix.ClientRequestInfo): - self.events.ClientRequestInfoProgress(msg.params) - else: - self.events.SimpleProgress(msg.params) + self.events.SimpleProgress(msg.params) return True elif msg.name == self.Message.IdentifierCollision: @@ -1192,10 +1189,13 @@ ## ########################################################## #TODO: not complete yet - def clientGetFile(self, uri, filename): + def clientGetFile(self, uri, filename, identifier=None): """ """ - identifier = self.new_identifier() + if identifier is None: + identifier = self.IdentifierPrefix.ClientGetFile + self.newIdentifier() + else: + assert identifier.startswith(self.IdentifierPrefix.ClientGetFile), 'Wrong prefix' msg = self.Message( self.Message.ClientGet, IgnoreDS='false', @@ -1219,20 +1219,29 @@ return identifier - def clientRequestInfo(self, uri, **params): + def clientRequestInfo(self, uri, identifier=None, **params): """Requests info about a file @param uri: uri of the file to request info about + @param identifier: request identifier or None to let the method create one. If an identifier is passed, it has to be + be prefixed with L{IdentifierPrefix.ClientRequestInfo} @event: clientGetInfo(event, params). If success, params will contain a key 'Metadata.ContentType' and 'DataLength'. Both may be '' (empty string) @event: GetInfoProgress(event, params). Triggered instead ofSimpleProgress @note: for other events see: L{clientGet} @return: (str) request identifier + @note: the request identifier returned is very likely to be unique but uniqueness is not guaranteed """ - identifier = self.IdentifierPrefix.ClientRequestInfo + self.newIdentifier() + if identifier is None: + identifier = self.IdentifierPrefix.ClientRequestInfo + self.newIdentifier() + else: + assert identifier.startswith(self.IdentifierPrefix.ClientRequestInfo), 'Wrong prefix' self.sendMessage( self.Message.ClientGet, Identifier=identifier, URI=uri, + #TODO: persistance??? + #Persistence='forever', + # suggested by Mathew Toseland to use about 32k for mimeType requests # basic sizes of keys are: 1k for SSks and 32k for CHKs MaxSize='32000', This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-10 11:27:06
|
Revision: 64 http://fclient.svn.sourceforge.net/fclient/?rev=64&view=rev Author: jUrner Date: 2007-11-10 03:27:11 -0800 (Sat, 10 Nov 2007) Log Message: ----------- added new function Modified Paths: -------------- trunk/fclient/fclient_lib/pyex/namespace.py Modified: trunk/fclient/fclient_lib/pyex/namespace.py =================================================================== --- trunk/fclient/fclient_lib/pyex/namespace.py 2007-11-10 11:26:10 UTC (rev 63) +++ trunk/fclient/fclient_lib/pyex/namespace.py 2007-11-10 11:27:11 UTC (rev 64) @@ -99,13 +99,48 @@ #********************************************************************* # #********************************************************************* +def split_extensions(fpath): + """Splits all extensions from a filepath + @param fpath: filepath to split extensions from + @return: (tuple) filepath, [extension1, extension(N) ...] + + >>> split_extensions('foo.aaa.bbb') + ('foo', ['.aaa', '.bbb']) + + >>> split_extensions('foo') + ('foo', []) + + >>> split_extensions('.foo.aaa') + ('.foo', ['.aaa']) + + >>> split_extensions('') + ('', []) + + """ + directory, name = os.path.split(fpath) + tmp_name = name + exts = [] + while True: + tmp_name, ext = os.path.splitext(tmp_name) + if not tmp_name: + name = ext + break + if not ext: + name = tmp_name + break + exts.append(ext) + + exts.reverse() + return os.path.join(directory, name), exts + +#********************************************************************* +# +#********************************************************************* + if __name__ == '__main__': import doctest doctest.testmod() -#TODO: handle fpath='myFolder/foo (1).txt' ? -#a = 'foo (123)' -#p = re.compile('\A(.*)\(([0-9]+)\)\Z') @@ -113,3 +148,4 @@ + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jU...@us...> - 2007-11-10 11:26:07
|
Revision: 63 http://fclient.svn.sourceforge.net/fclient/?rev=63&view=rev Author: jUrner Date: 2007-11-10 03:26:10 -0800 (Sat, 10 Nov 2007) Log Message: ----------- ignore everything Property Changed: ---------------- trunk/fclient/download/ Property changes on: trunk/fclient/download ___________________________________________________________________ Name: svn:ignore + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |