From ae5415a26b2133ac4efda6e082a9ad7c7c562c09 Mon Sep 17 00:00:00 2001 From: coffeedogs Date: Wed, 13 Jun 2018 18:50:20 +0100 Subject: [PATCH] Fixed: Style and lint violations for ten more of the worst violating files * Also, closes #1270 --- build/scripts-2.7/pybitmessage | 15 + desktop/icons/24x24/pybitmessage.png | Bin 0 -> 1852 bytes desktop/icons/scalable/pybitmessage.svg | 149 ++++++++++ setup.cfg | 4 +- src/api.py | 310 ++++++++++++++------- src/bitmessagemain.py | 108 +++++--- src/bitmessageqt/__init__.py | 126 +++++---- src/bitmessageqt/bitmessageui.py | 163 ++++++++--- src/bitmessageqt/newaddresswizard.py | 354 ------------------------ src/network/asyncore_pollchoose.py | 253 ++++++++++++----- src/network/bmproto.py | 229 +++++++++------ src/network/tcp.py | 154 ++++++----- src/proofofwork.py | 139 +++++++--- src/socks/__init__.py | 173 +++++++----- src/upnp.py | 166 ++++++----- 15 files changed, 1361 insertions(+), 982 deletions(-) create mode 100755 build/scripts-2.7/pybitmessage create mode 100644 desktop/icons/24x24/pybitmessage.png create mode 100644 desktop/icons/scalable/pybitmessage.svg delete mode 100644 src/bitmessageqt/newaddresswizard.py diff --git a/build/scripts-2.7/pybitmessage b/build/scripts-2.7/pybitmessage new file mode 100755 index 00000000..7f9e2e37 --- /dev/null +++ b/build/scripts-2.7/pybitmessage @@ -0,0 +1,15 @@ +#!/home/coffeedogs/.virtualenvs/pybitmessage-devops/bin/python + +import os +import sys + +import pkg_resources + +import pybitmessage.pathmagic + +dist = pkg_resources.get_distribution('pybitmessage') +script_file = os.path.join(dist.location, dist.key, 'bitmessagemain.py') +new_globals = globals() +new_globals.update(__file__=script_file) + +execfile(script_file, new_globals) diff --git a/desktop/icons/24x24/pybitmessage.png b/desktop/icons/24x24/pybitmessage.png new file mode 100644 index 0000000000000000000000000000000000000000..30f7313eb378b69c4e2f6cb66264c2ae678059e1 GIT binary patch literal 1852 zcmV-C2gCS@P)M^!E?E!Z`=4*Xy`ht_>fhjLl+eV_hy>=-Ek(A(EP8wh@6>5>i{Jah=UrfpOJpyyEE@4x%__we9`O^8Gz zY+YB^0D>Su5CjlH;N;0u?*~Bu&N&bS0ppzGa8EB4rY4DVPDBKSF-HCU16P-HEJ7j{ zNB_V8#>OVLvurL0*LBHtT@Xo}Jb4<4WQ?)(U)V^qv$OC6A8)<&J1DB6+N!HAqgS40M1-^F1`!DUX8C&gnRGe@ zugOKk3q#X23}v$zA1@%4ZpGc13}Zxu2iAXy3X>BMLSomhm+{n7Pk?idful#^*Ezyr z?^5Ynsk5uAtC7-^+n#tfol2v%r3J6O`Z`vx?m{NB3ZCb|_3ZsV_dC<+bR%^l!h!ug z@VU>%#>bmZoH+5fD_6!g$w1(jJD&Yfx-GS5)rys1j5Ug#pWXb-IOmw2o?hS6(|2Sf zpMOr0oWEy8bh}FcG{zVdMbZCv^7OGpGT!ZY?yDjYR4SFe34j4u5I6t=RaNovCkHF@ zoiaipD2jq)GP(ESrQv7p6jU1RJ^w~D8qrNF1lzV@nHGW|z_EWH$MA4|HvlM%F)+q3 zSF1lXI(qr%k!S>tSt5A%%qNy7fY#@OJ=EhAw1( z_N}dtZ{57*k%%G? zyxTA|2q7B+k`g<9wZry251!{C8jT8 z-p;x=cW;j;6M^e_Sar{;s;=ulO|_=rc^-)9w)un*3;rOYd80A9^_8lsC>CeFcKY<0 z4{lZ}Ry-a%wttgbW>)Wx=%_7={5wQBW?I>Bfy4Z=_PG=NJe~Q9lP@w~J_0!8!Mi z9zFhP-S^kT<8fHF3CFQv7zRw!gk#&#H626*-}f;&HHG}>D5{mY;+HpXy5F+Q%Xe-M z0U*X0G)+UPbnTIkKRHlVHEm6MdpjbL2t3b)X_;^w2d-^HS2Yk3s?{on^CK7@$wLGZ zp64ZohO$2d01W`9rl;?qQ0VgL*oRY7S08R~Z--+$Fw77_rVcY?z%ng_LLsP%0zrgw zxr|)yA}WL9CMO3t=b?@zi(yy>Of!T~C+VOB*94LhM2&|}VG20s7@sU4HnuD?>4oOphsFb>PI zZUf%`%U?g-yLaC=7-Oi_YVf%apL0|y71Zl>Y~1)DcJ6!;YuDTZB0_HPA}(DTUI=X2 zCW^)4PlABI9SVie+1UxpvTpCezI`9Hzx&R+{}~^jXqkryAs~c+loA9&&G%88o5S~i zunk+bJl6RAN16Z-N~LQLgR%WmN-SO4fpAk3q?Bk*BpZN(hk5|U(0jP|^`65=ej%iQ zs;ZDuEGZ;^06r_>FlqtwW!jai!%a;Br5)J=X qQ5iHQ#>b literal 0 HcmV?d00001 diff --git a/desktop/icons/scalable/pybitmessage.svg b/desktop/icons/scalable/pybitmessage.svg new file mode 100644 index 00000000..7c854e34 --- /dev/null +++ b/desktop/icons/scalable/pybitmessage.svg @@ -0,0 +1,149 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup.cfg b/setup.cfg index 93efd2d5..d6d2afc5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,10 +3,12 @@ [pycodestyle] max-line-length = 119 +ignore = E722,E402 [flake8] max-line-length = 119 -ignore = E722,F841 +ignore = E402,E722,F841 +# E402: pylint is preferred for wrong-import-position # E722: pylint is preferred for bare-except # F841: pylint is preferred for unused-variable diff --git a/src/api.py b/src/api.py index a32519e4..d88bbc87 100644 --- a/src/api.py +++ b/src/api.py @@ -1,19 +1,24 @@ +# pylint: disable=too-many-locals,too-many-lines,no-self-use,too-many-public-methods,too-many-branches +# pylint: disable=too-many-statements +""" # Copyright (c) 2012-2016 Jonathan Warren # Copyright (c) 2012-2018 The Bitmessage developers -""" This is not what you run to run the Bitmessage API. Instead, enable the API ( https://bitmessage.org/wiki/API ) and optionally enable daemon mode ( https://bitmessage.org/wiki/Daemon ) then run bitmessagemain.py. """ +from __future__ import absolute_import + import base64 import hashlib import json +from struct import pack import time from binascii import hexlify, unhexlify + from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer -from struct import pack import shared from addresses import ( @@ -43,6 +48,8 @@ str_chan = '[chan]' class APIError(Exception): + """APIError exception class""" + def __init__(self, error_number, error_message): super(APIError, self).__init__() self.error_number = error_number @@ -53,26 +60,34 @@ class APIError(Exception): class StoppableXMLRPCServer(SimpleXMLRPCServer): + """A SimpleXMLRPCServer that honours state.shutdown""" allow_reuse_address = True def serve_forever(self): + """Start the SimpleXMLRPCServer""" + # pylint: disable=arguments-differ while state.shutdown == 0: self.handle_request() -# This is one of several classes that constitute the API -# This class was written by Vaibhav Bhatia. -# Modified by Jonathan Warren (Atheros). -# http://code.activestate.com/recipes/501148-xmlrpc-serverclient-which-does-cookie-handling-and/ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): + """ + This is one of several classes that constitute the API + + This class was written by Vaibhav Bhatia. Modified by Jonathan Warren (Atheros). + http://code.activestate.com/recipes/501148-xmlrpc-serverclient-which-does-cookie-handling-and/ + """ def do_POST(self): - # Handles the HTTP POST request. - # Attempts to interpret all HTTP POST requests as XML-RPC calls, - # which are forwarded to the server's _dispatch method for handling. + """ + Handles the HTTP POST request. - # Note: this method is the same as in SimpleXMLRPCRequestHandler, - # just hacked to handle cookies + Attempts to interpret all HTTP POST requests as XML-RPC calls, + which are forwarded to the server's _dispatch method for handling. + + Note: this method is the same as in SimpleXMLRPCRequestHandler, + just hacked to handle cookies + """ # Check that the path is legal if not self.is_rpc_path_valid(): @@ -98,7 +113,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): # SimpleXMLRPCDispatcher. To maintain backwards compatibility, # check to see if a subclass implements _dispatch and dispatch # using that method if present. - response = self.server._marshaled_dispatch( + response = self.server._marshaled_dispatch( # pylint: disable=protected-access data, getattr(self, '_dispatch', None) ) except: # This should only happen if the module is buggy @@ -125,22 +140,21 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): self.connection.shutdown(1) def APIAuthenticateClient(self): + """Predicate to check for valid API credentials in the request header""" + if 'Authorization' in self.headers: # handle Basic authentication - enctype, encstr = self.headers.get('Authorization').split() + _, encstr = self.headers.get('Authorization').split() emailid, password = encstr.decode('base64').split(':') return ( - emailid == - BMConfigParser().get('bitmessagesettings', 'apiusername') - and password == - BMConfigParser().get('bitmessagesettings', 'apipassword') + emailid == BMConfigParser().get('bitmessagesettings', 'apiusername') and + password == BMConfigParser().get('bitmessagesettings', 'apipassword') ) else: logger.warning( 'Authentication failed because header lacks' ' Authentication field') time.sleep(2) - return False return False @@ -155,6 +169,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 22, "Decode error - %s. Had trouble while decoding string: %r" % (e, text) ) + return None def _verifyAddress(self, address): status, addressVersionNumber, streamNumber, ripe = \ @@ -170,15 +185,10 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if status == 'invalidcharacters': raise APIError(9, 'Invalid characters in address: ' + address) if status == 'versiontoohigh': - raise APIError( - 10, - 'Address version number too high (or zero) in address: ' - + address - ) + raise APIError(10, 'Address version number too high (or zero) in address: ' + address) if status == 'varintmalformed': raise APIError(26, 'Malformed varint in address: ' + address) - raise APIError( - 7, 'Could not decode address: %s : %s' % (address, status)) + raise APIError(7, 'Could not decode address: %s : %s' % (address, status)) if addressVersionNumber < 2 or addressVersionNumber > 4: raise APIError( 11, 'The address version number currently must be 2, 3 or 4.' @@ -195,9 +205,11 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): # Request Handlers def HandleListAddresses(self, method): + """Handle a request to list addresses""" + data = '{"addresses":[' for addressInKeysFile in BMConfigParser().addresses(): - status, addressVersionNumber, streamNumber, hash01 = decodeAddress( + status, addressVersionNumber, streamNumber, hash01 = decodeAddress( # pylint: disable=unused-variable addressInKeysFile) if len(data) > 20: data += ',' @@ -215,11 +227,13 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 'enabled': BMConfigParser().getboolean(addressInKeysFile, 'enabled'), 'chan': chan - }, indent=4, separators=(',', ': ')) + }, indent=4, separators=(',', ': ')) data += ']}' return data def HandleListAddressBookEntries(self, params): + """Handle a request to list address book entries""" + if len(params) == 1: label, = params label = self._decode(label, "base64") @@ -243,6 +257,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return data def HandleAddAddressBookEntry(self, params): + """Handle a request to add an address book entry""" + if len(params) != 2: raise APIError(0, "I need label and address") address, label = params @@ -262,6 +278,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return "Added address %s to address book" % address def HandleDeleteAddressBookEntry(self, params): + """Handle a request to delete an address book entry""" + if len(params) != 1: raise APIError(0, "I need an address") address, = params @@ -274,8 +292,11 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return "Deleted address book entry for %s if it existed" % address def HandleCreateRandomAddress(self, params): - if len(params) == 0: + """Handle a request to create a random address""" + + if not params: raise APIError(0, 'I need parameters!') + elif len(params) == 1: label, = params eighteenByteRipe = False @@ -292,19 +313,16 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): elif len(params) == 3: label, eighteenByteRipe, totalDifficulty = params nonceTrialsPerByte = int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) + defaults.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) payloadLengthExtraBytes = BMConfigParser().get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') elif len(params) == 4: label, eighteenByteRipe, totalDifficulty, \ smallMessageDifficulty = params nonceTrialsPerByte = int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) + defaults.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) payloadLengthExtraBytes = int( - defaults.networkDefaultPayloadLengthExtraBytes - * smallMessageDifficulty) + defaults.networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty) else: raise APIError(0, 'Too many parameters!') label = self._decode(label, "base64") @@ -321,8 +339,11 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return queues.apiAddressGeneratorReturnQueue.get() def HandleCreateDeterministicAddresses(self, params): - if len(params) == 0: + """Handle a request to create a deterministic address""" + + if not params: raise APIError(0, 'I need parameters!') + elif len(params) == 1: passphrase, = params numberOfAddresses = 1 @@ -333,6 +354,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 'bitmessagesettings', 'defaultnoncetrialsperbyte') payloadLengthExtraBytes = BMConfigParser().get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 2: passphrase, numberOfAddresses = params addressVersionNumber = 0 @@ -342,6 +364,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 'bitmessagesettings', 'defaultnoncetrialsperbyte') payloadLengthExtraBytes = BMConfigParser().get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 3: passphrase, numberOfAddresses, addressVersionNumber = params streamNumber = 0 @@ -350,6 +373,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 'bitmessagesettings', 'defaultnoncetrialsperbyte') payloadLengthExtraBytes = BMConfigParser().get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 4: passphrase, numberOfAddresses, addressVersionNumber, \ streamNumber = params @@ -358,6 +382,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 'bitmessagesettings', 'defaultnoncetrialsperbyte') payloadLengthExtraBytes = BMConfigParser().get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 5: passphrase, numberOfAddresses, addressVersionNumber, \ streamNumber, eighteenByteRipe = params @@ -365,27 +390,26 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 'bitmessagesettings', 'defaultnoncetrialsperbyte') payloadLengthExtraBytes = BMConfigParser().get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 6: passphrase, numberOfAddresses, addressVersionNumber, \ streamNumber, eighteenByteRipe, totalDifficulty = params nonceTrialsPerByte = int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) + defaults.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) payloadLengthExtraBytes = BMConfigParser().get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 7: passphrase, numberOfAddresses, addressVersionNumber, \ streamNumber, eighteenByteRipe, totalDifficulty, \ smallMessageDifficulty = params nonceTrialsPerByte = int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) + defaults.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) payloadLengthExtraBytes = int( - defaults.networkDefaultPayloadLengthExtraBytes - * smallMessageDifficulty) + defaults.networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty) else: raise APIError(0, 'Too many parameters!') - if len(passphrase) == 0: + if not passphrase: raise APIError(1, 'The specified passphrase is blank.') if not isinstance(eighteenByteRipe, bool): raise APIError( @@ -436,12 +460,14 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return data def HandleGetDeterministicAddress(self, params): + """Handle a request to get a deterministic address""" + if len(params) != 3: raise APIError(0, 'I need exactly 3 parameters.') passphrase, addressVersionNumber, streamNumber = params numberOfAddresses = 1 eighteenByteRipe = False - if len(passphrase) == 0: + if not passphrase: raise APIError(1, 'The specified passphrase is blank.') passphrase = self._decode(passphrase, "base64") if addressVersionNumber != 3 and addressVersionNumber != 4: @@ -463,12 +489,16 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return queues.apiAddressGeneratorReturnQueue.get() def HandleCreateChan(self, params): - if len(params) == 0: + """Handle a request to create a chan""" + + if not params: raise APIError(0, 'I need parameters.') + elif len(params) == 1: passphrase, = params passphrase = self._decode(passphrase, "base64") - if len(passphrase) == 0: + + if not passphrase: raise APIError(1, 'The specified passphrase is blank.') # It would be nice to make the label the passphrase but it is # possible that the passphrase contains non-utf-8 characters. @@ -488,18 +518,20 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): passphrase, True )) queueReturn = queues.apiAddressGeneratorReturnQueue.get() - if len(queueReturn) == 0: + if not queueReturn: raise APIError(24, 'Chan address is already present.') address = queueReturn[0] return address def HandleJoinChan(self, params): + """Handle a request to join a chan""" + if len(params) < 2: raise APIError(0, 'I need two parameters.') elif len(params) == 2: passphrase, suppliedAddress = params passphrase = self._decode(passphrase, "base64") - if len(passphrase) == 0: + if not passphrase: raise APIError(1, 'The specified passphrase is blank.') # It would be nice to make the label the passphrase but it is # possible that the passphrase contains non-utf-8 characters. @@ -509,8 +541,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): except: label = str_chan + ' ' + repr(passphrase) - status, addressVersionNumber, streamNumber, toRipe = \ - self._verifyAddress(suppliedAddress) + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress( # pylint: disable=unused-variable + suppliedAddress) suppliedAddress = addBMIfNotPresent(suppliedAddress) queues.apiAddressGeneratorReturnQueue.queue.clear() queues.addressGeneratorQueue.put(( @@ -522,20 +554,19 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if addressGeneratorReturnValue[0] == \ 'chan name does not match address': raise APIError(18, 'Chan name does not match address.') - if len(addressGeneratorReturnValue) == 0: + if not addressGeneratorReturnValue: raise APIError(24, 'Chan address is already present.') - # TODO: this variable is not used to anything - # in case we ever want it for anything. - # createdAddress = addressGeneratorReturnValue[0] return "success" def HandleLeaveChan(self, params): - if len(params) == 0: + """Handle a request to leave a chan""" + + if not params: raise APIError(0, 'I need parameters.') elif len(params) == 1: address, = params - status, addressVersionNumber, streamNumber, toRipe = \ - self._verifyAddress(address) + # pylint: disable=unused-variable + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(address) address = addBMIfNotPresent(address) if not BMConfigParser().has_section(address): raise APIError( @@ -550,12 +581,14 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return 'success' def HandleDeleteAddress(self, params): - if len(params) == 0: + """Handle a request to delete an address""" + + if not params: raise APIError(0, 'I need parameters.') elif len(params) == 1: address, = params - status, addressVersionNumber, streamNumber, toRipe = \ - self._verifyAddress(address) + # pylint: disable=unused-variable + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(address) address = addBMIfNotPresent(address) if not BMConfigParser().has_section(address): raise APIError( @@ -568,7 +601,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): shared.reloadMyAddressHashes() return 'success' - def HandleGetAllInboxMessages(self, params): + def HandleGetAllInboxMessages(self, params): # pylint: disable=unused-argument + """Handle a request to get all inbox messages""" + queryreturn = sqlQuery( "SELECT msgid, toaddress, fromaddress, subject, received, message," " encodingtype, read FROM inbox where folder='inbox'" @@ -594,7 +629,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): data += ']}' return data - def HandleGetAllInboxMessageIds(self, params): + def HandleGetAllInboxMessageIds(self, params): # pylint: disable=unused-argument + """Handle a request to get all inbox message IDs""" + queryreturn = sqlQuery( "SELECT msgid FROM inbox where folder='inbox' ORDER BY received") data = '{"inboxMessageIds":[' @@ -608,7 +645,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return data def HandleGetInboxMessageById(self, params): - if len(params) == 0: + """Handle a request to get an inbox messsage by ID""" + + if not params: raise APIError(0, 'I need parameters!') elif len(params) == 1: msgid = self._decode(params[0], "hex") @@ -649,7 +688,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): data += ']}' return data - def HandleGetAllSentMessages(self, params): + def HandleGetAllSentMessages(self, params): # pylint: disable=unused-argument + """Handle a request to get all sent messages""" + queryreturn = sqlQuery( "SELECT msgid, toaddress, fromaddress, subject, lastactiontime," " message, encodingtype, status, ackdata FROM sent" @@ -676,7 +717,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): data += ']}' return data - def HandleGetAllSentMessageIds(self, params): + def HandleGetAllSentMessageIds(self, params): # pylint: disable=unused-argument + """Handle a request to get all sent message IDs""" + queryreturn = sqlQuery( "SELECT msgid FROM sent where folder='sent'" " ORDER BY lastactiontime" @@ -692,7 +735,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return data def HandleInboxMessagesByReceiver(self, params): - if len(params) == 0: + """Handle a request to get inbox messages by receiver""" + + if not params: raise APIError(0, 'I need parameters!') toAddress = params[0] queryreturn = sqlQuery( @@ -719,7 +764,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return data def HandleGetSentMessageById(self, params): - if len(params) == 0: + """Handle a request to get a sent message by ID""" + + if not params: raise APIError(0, 'I need parameters!') msgid = self._decode(params[0], "hex") queryreturn = sqlQuery( @@ -747,7 +794,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return data def HandleGetSentMessagesByAddress(self, params): - if len(params) == 0: + """Handle a request to get sent messages by address""" + + if not params: raise APIError(0, 'I need parameters!') fromAddress = params[0] queryreturn = sqlQuery( @@ -759,7 +808,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): data = '{"sentMessages":[' for row in queryreturn: msgid, toAddress, fromAddress, subject, lastactiontime, message, \ - encodingtype, status, ackdata = row + encodingtype, status, ackdata = row # pylint: disable=unused-variable subject = shared.fixPotentiallyInvalidUTF8Data(subject) message = shared.fixPotentiallyInvalidUTF8Data(message) if len(data) > 25: @@ -778,7 +827,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return data def HandleGetSentMessagesByAckData(self, params): - if len(params) == 0: + """Handle a request to get sent messages by ack data""" + + if not params: raise APIError(0, 'I need parameters!') ackData = self._decode(params[0], "hex") queryreturn = sqlQuery( @@ -806,7 +857,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return data def HandleTrashMessage(self, params): - if len(params) == 0: + """Handle a request to trash a message by ID""" + + if not params: raise APIError(0, 'I need parameters!') msgid = self._decode(params[0], "hex") @@ -817,32 +870,42 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return 'Trashed message (assuming message existed).' def HandleTrashInboxMessage(self, params): - if len(params) == 0: + """Handle a request to trash an inbox message by ID""" + + if not params: raise APIError(0, 'I need parameters!') msgid = self._decode(params[0], "hex") helper_inbox.trash(msgid) return 'Trashed inbox message (assuming message existed).' def HandleTrashSentMessage(self, params): - if len(params) == 0: + """Handle a request to trash a sent message by ID""" + + if not params: raise APIError(0, 'I need parameters!') msgid = self._decode(params[0], "hex") sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid) return 'Trashed sent message (assuming message existed).' def HandleSendMessage(self, params): - if len(params) == 0: + """Handle a request to send a message""" + + if not params: raise APIError(0, 'I need parameters!') + elif len(params) == 4: toAddress, fromAddress, subject, message = params encodingType = 2 TTL = 4 * 24 * 60 * 60 + elif len(params) == 5: toAddress, fromAddress, subject, message, encodingType = params TTL = 4 * 24 * 60 * 60 + elif len(params) == 6: toAddress, fromAddress, subject, message, encodingType, TTL = \ params + if encodingType not in [2, 3]: raise APIError(6, 'The encoding type must be 2 or 3.') subject = self._decode(subject, "base64") @@ -855,6 +918,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): TTL = 28 * 24 * 60 * 60 toAddress = addBMIfNotPresent(toAddress) fromAddress = addBMIfNotPresent(fromAddress) + # pylint: disable=unused-variable status, addressVersionNumber, streamNumber, toRipe = \ self._verifyAddress(toAddress) self._verifyAddress(fromAddress) @@ -894,7 +958,6 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if queryreturn != []: for row in queryreturn: toLabel, = row - # apiSignalQueue.put(('displayNewSentMessage',(toAddress,toLabel,fromAddress,subject,message,ackdata))) queues.UISignalQueue.put(('displayNewSentMessage', ( toAddress, toLabel, fromAddress, subject, message, ackdata))) @@ -903,19 +966,25 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return hexlify(ackdata) def HandleSendBroadcast(self, params): - if len(params) == 0: + """Handle a request to send a broadcast message""" + + if not params: raise APIError(0, 'I need parameters!') + if len(params) == 3: fromAddress, subject, message = params encodingType = 2 TTL = 4 * 24 * 60 * 60 + elif len(params) == 4: fromAddress, subject, message, encodingType = params TTL = 4 * 24 * 60 * 60 elif len(params) == 5: fromAddress, subject, message, encodingType, TTL = params + if encodingType not in [2, 3]: raise APIError(6, 'The encoding type must be 2 or 3.') + subject = self._decode(subject, "base64") message = self._decode(message, "base64") if len(subject + message) > (2 ** 18 - 500): @@ -961,6 +1030,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return hexlify(ackdata) def HandleGetStatus(self, params): + """Handle a request to get the status of a sent message""" + if len(params) != 1: raise APIError(0, 'I need one parameter!') ackdata, = params @@ -977,7 +1048,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return status def HandleAddSubscription(self, params): - if len(params) == 0: + """Handle a request to add a subscription""" + + if not params: raise APIError(0, 'I need parameters!') if len(params) == 1: address, = params @@ -1007,6 +1080,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return 'Added subscription.' def HandleDeleteSubscription(self, params): + """Handle a request to delete a subscription""" + if len(params) != 1: raise APIError(0, 'I need 1 parameter!') address, = params @@ -1017,7 +1092,10 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): queues.UISignalQueue.put(('rerenderSubscriptions', '')) return 'Deleted subscription if it existed.' - def ListSubscriptions(self, params): + def ListSubscriptions(self, params): # pylint: disable=unused-argument + """Handle a request to list susbcriptions""" + + # pylint: disable=unused-variable queryreturn = sqlQuery( "SELECT label, address, enabled FROM subscriptions") data = {'subscriptions': []} @@ -1032,6 +1110,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return json.dumps(data, indent=4, separators=(',', ': ')) def HandleDisseminatePreEncryptedMsg(self, params): + """Handle a request to disseminate an encrypted message""" + # The device issuing this command to PyBitmessage supplies a msg # object that has already been encrypted but which still needs the POW # to be done. PyBitmessage accepts this msg object and sends it out @@ -1044,17 +1124,29 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): encryptedPayload = self._decode(encryptedPayload, "hex") # Let us do the POW and attach it to the front target = 2**64 / ( - (len(encryptedPayload) + requiredPayloadLengthExtraBytes + 8) - * requiredAverageProofOfWorkNonceTrialsPerByte) + ( + len(encryptedPayload) + requiredPayloadLengthExtraBytes + 8 + ) * requiredAverageProofOfWorkNonceTrialsPerByte + ) with shared.printLock: - print '(For msg message via API) Doing proof of work. Total required difficulty:', float(requiredAverageProofOfWorkNonceTrialsPerByte) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, 'Required small message difficulty:', float(requiredPayloadLengthExtraBytes) / defaults.networkDefaultPayloadLengthExtraBytes + print( + '(For msg message via API) Doing proof of work. Total required difficulty:', + float( + requiredAverageProofOfWorkNonceTrialsPerByte + ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + 'Required small message difficulty:', + float(requiredPayloadLengthExtraBytes) / defaults.networkDefaultPayloadLengthExtraBytes, + ) powStartTime = time.time() initialHash = hashlib.sha512(encryptedPayload).digest() trialValue, nonce = proofofwork.run(target, initialHash) with shared.printLock: print '(For msg message via API) Found proof of work', trialValue, 'Nonce:', nonce try: - print 'POW took', int(time.time() - powStartTime), 'seconds.', nonce / (time.time() - powStartTime), 'nonce trials per second.' + print( + 'POW took', int(time.time() - powStartTime), 'seconds.', + nonce / (time.time() - powStartTime), 'nonce trials per second.', + ) except: pass encryptedPayload = pack('>Q', nonce) + encryptedPayload @@ -1071,14 +1163,18 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): queues.invQueue.put((toStreamNumber, inventoryHash)) def HandleTrashSentMessageByAckDAta(self, params): + """Handle a request to trash a sent message by ackdata""" + # This API method should only be used when msgid is not available - if len(params) == 0: + if not params: raise APIError(0, 'I need parameters!') ackdata = self._decode(params[0], "hex") sqlExecute("UPDATE sent SET folder='trash' WHERE ackdata=?", ackdata) return 'Trashed sent message (assuming message existed).' - def HandleDissimatePubKey(self, params): + def HandleDissimatePubKey(self, params): # pylint: disable=unused-argument + """Handle a request to disseminate a public key""" + # The device issuing this command to PyBitmessage supplies a pubkey # object to be disseminated to the rest of the Bitmessage network. # PyBitmessage accepts this pubkey object and sends it out to the rest @@ -1090,9 +1186,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): payload = self._decode(payload, "hex") # Let us do the POW - target = 2 ** 64 / ( - (len(payload) + defaults.networkDefaultPayloadLengthExtraBytes - + 8) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) + target = 2 ** 64 / (( + len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8 + ) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) print '(For pubkey message via API) Doing proof of work...' initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) @@ -1100,18 +1196,19 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): payload = pack('>Q', nonce) + payload pubkeyReadPosition = 8 # bypass the nonce - if payload[pubkeyReadPosition:pubkeyReadPosition+4] == \ + if payload[pubkeyReadPosition:pubkeyReadPosition + 4] == \ '\x00\x00\x00\x00': # if this pubkey uses 8 byte time pubkeyReadPosition += 8 else: pubkeyReadPosition += 4 + # pylint: disable=unused-variable addressVersion, addressVersionLength = decodeVarint( - payload[pubkeyReadPosition:pubkeyReadPosition+10]) + payload[pubkeyReadPosition:pubkeyReadPosition + 10]) pubkeyReadPosition += addressVersionLength pubkeyStreamNumber = decodeVarint( - payload[pubkeyReadPosition:pubkeyReadPosition+10])[0] + payload[pubkeyReadPosition:pubkeyReadPosition + 10])[0] inventoryHash = calculateInventoryHash(payload) - objectType = 1 # TODO: support v4 pubkeys + objectType = 1 # .. todo::: support v4 pubkeys TTL = 28 * 24 * 60 * 60 Inventory()[inventoryHash] = ( objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, '' @@ -1121,6 +1218,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): queues.invQueue.put((pubkeyStreamNumber, inventoryHash)) def HandleGetMessageDataByDestinationHash(self, params): + """Handle a request to get message data by destination hash""" + # Method will eventually be used by a particular Android app to # select relevant messages. Do not yet add this to the api # doc. @@ -1145,8 +1244,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): readPosition = 16 # Nonce length + time length # Stream Number length readPosition += decodeVarint( - payload[readPosition:readPosition+10])[1] - t = (payload[readPosition:readPosition+32], hash01) + payload[readPosition:readPosition + 10])[1] + t = (payload[readPosition:readPosition + 32], hash01) sql.execute("UPDATE inventory SET tag=? WHERE hash=?", *t) queryreturn = sqlQuery( @@ -1161,10 +1260,12 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): data += ']}' return data - def HandleClientStatus(self, params): - if len(network.stats.connectedHostsList()) == 0: + def HandleClientStatus(self, params): # pylint: disable=unused-argument + """Handle a request to get the status of the client""" + + if network.stats.connectedHostsList(): networkStatus = 'notConnected' - elif len(network.stats.connectedHostsList()) > 0 \ + elif not network.stats.connectedHostsList() \ and not shared.clientHasReceivedIncomingConnections: networkStatus = 'connectedButHaveNotReceivedIncomingConnections' else: @@ -1177,9 +1278,11 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 'networkStatus': networkStatus, 'softwareName': 'PyBitmessage', 'softwareVersion': softwareVersion - }, indent=4, separators=(',', ': ')) + }, indent=4, separators=(',', ': ')) def HandleDecodeAddress(self, params): + """Handle a request to decode an address""" + # Return a meaningful decoding of an address. if len(params) != 1: raise APIError(0, 'I need 1 parameter!') @@ -1190,29 +1293,41 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 'addressVersion': addressVersion, 'streamNumber': streamNumber, 'ripe': base64.b64encode(ripe) - }, indent=4, separators=(',', ': ')) + }, indent=4, separators=(',', ': ')) def HandleHelloWorld(self, params): + """Test two string params""" + a, b = params return a + '-' + b def HandleAdd(self, params): + """Test two numeric params""" + a, b = params return a + b def HandleStatusBar(self, params): + """Handle a request to update the status bar""" + message, = params queues.UISignalQueue.put(('updateStatusBar', message)) def HandleDeleteAndVacuum(self, params): + """Handle a request to run the deleteandvacuum stored procedure""" + if not params: sqlStoredProcedure('deleteandvacuume') return 'done' + return None def HandleShutdown(self, params): + """Handle a request to huutdown the client""" + if not params: shutdown.doCleanShutdown() return 'done' + return None handlers = {} handlers['helloWorld'] = HandleHelloWorld @@ -1279,6 +1394,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): return self.handlers[method](self, params) def _dispatch(self, method, params): + # pylint: disable=attribute-defined-outside-init self.cookies = [] validuser = self.APIAuthenticateClient() diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 0848b0e4..d2749cdd 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -1,37 +1,43 @@ #!/usr/bin/python2.7 -# Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2018 The Bitmessage developers -# Distributed under the MIT/X11 software license. See the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# pylint: disable=no-self-use,too-many-branches,too-many-statements,too-many-locals +""" +bitmessagemain.py +================= -# Right now, PyBitmessage only support connecting to stream 1. It doesn't -# yet contain logic to expand into further streams. +Copyright (c) 2012-2016 Jonathan Warren +Copyright (c) 2012-2018 The Bitmessage developers +Distributed under the MIT/X11 software license. See the accompanying +file COPYING or http://www.opensource.org/licenses/mit-license.php. -# The software version variable is now held in shared.py +Right now, PyBitmessage only support connecting to stream 1. It doesn't +yet contain logic to expand into further streams. +The software version variable is now held in shared.py + +""" + +from __future__ import absolute_import + +import ctypes +import errno +import getopt import os +import signal +import socket import sys +import threading +from random import randint +from struct import pack +from subprocess import call +from time import sleep app_dir = os.path.dirname(os.path.abspath(__file__)) os.chdir(app_dir) sys.path.insert(0, app_dir) - -import depends -depends.check_dependencies() - # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. -import signal # The next 3 are used for the API from singleinstance import singleinstance -import errno -import socket -import ctypes -from struct import pack -from subprocess import call -from time import sleep -from random import randint -import getopt from api import MySimpleXMLRPCRequestHandler, StoppableXMLRPCServer from helper_startup import ( @@ -39,11 +45,11 @@ from helper_startup import ( ) import defaults +import depends import shared import knownnodes import state import shutdown -import threading # Classes from class_sqlThread import sqlThread @@ -69,7 +75,12 @@ import helper_bootstrap import helper_generic import helper_threading +depends.check_dependencies() + + def connectToStream(streamNumber): + """Connect to a stream""" + state.streamsInWhichIAmParticipating.append(streamNumber) selfInitiatedConnections[streamNumber] = {} @@ -90,10 +101,10 @@ def connectToStream(streamNumber): with knownnodes.knownNodesLock: if streamNumber not in knownnodes.knownNodes: knownnodes.knownNodes[streamNumber] = {} - if streamNumber*2 not in knownnodes.knownNodes: - knownnodes.knownNodes[streamNumber*2] = {} - if streamNumber*2+1 not in knownnodes.knownNodes: - knownnodes.knownNodes[streamNumber*2+1] = {} + if streamNumber * 2 not in knownnodes.knownNodes: + knownnodes.knownNodes[streamNumber * 2] = {} + if streamNumber * 2 + 1 not in knownnodes.knownNodes: + knownnodes.knownNodes[streamNumber * 2 + 1] = {} BMConnectionPool().connectToStream(streamNumber) @@ -111,6 +122,8 @@ def _fixSocket(): addressToString = ctypes.windll.ws2_32.WSAAddressToStringA def inet_ntop(family, host): + """Convert IPv4 and IPv6 addresses from binary to text form""" + if family == socket.AF_INET: if len(host) != 4: raise ValueError("invalid IPv4 host") @@ -132,6 +145,8 @@ def _fixSocket(): stringToAddress = ctypes.windll.ws2_32.WSAStringToAddressA def inet_pton(family, host): + """Convert IPv4 and IPv6 addresses from text to binary form""" + buf = "\0" * 28 lengthBuf = pack("I", len(buf)) if stringToAddress(str(host), @@ -155,13 +170,15 @@ def _fixSocket(): socket.IPV6_V6ONLY = 27 -# This thread, of which there is only one, runs the API. class singleAPI(threading.Thread, helper_threading.StoppableThread): + """This thread, of which there is only one, runs the API.""" + def __init__(self): threading.Thread.__init__(self, name="singleAPI") self.initStop() def stopThread(self): + """Stop the API thread""" super(singleAPI, self).stopThread() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: @@ -175,9 +192,10 @@ class singleAPI(threading.Thread, helper_threading.StoppableThread): pass def run(self): + """Run the API thread""" port = BMConfigParser().getint('bitmessagesettings', 'apiport') try: - from errno import WSAEADDRINUSE + from errno import WSAEADDRINUSE # pylint: disable=unused-variable except (ImportError, AttributeError): errno.WSAEADDRINUSE = errno.EADDRINUSE for attempt in range(50): @@ -212,15 +230,18 @@ if shared.useVeryEasyProofOfWorkForTesting: defaults.networkDefaultPayloadLengthExtraBytes / 100) -class Main: +class Main(object): + """The main app""" + def start(self): + """Start the main app""" _fixSocket() daemon = BMConfigParser().safeGetBoolean( 'bitmessagesettings', 'daemon') try: - opts, args = getopt.getopt( + opts, _ = getopt.getopt( sys.argv[1:], "hcdt", ["help", "curses", "daemon", "test"]) @@ -228,7 +249,7 @@ class Main: self.usage() sys.exit(2) - for opt, arg in opts: + for opt, _ in opts: if opt in ("-h", "--help"): self.usage() sys.exit() @@ -259,7 +280,7 @@ class Main: if daemon and not state.testmode: with shared.printLock: - print('Running as a daemon. Send TERM signal to end.') + print 'Running as a daemon. Send TERM signal to end.' self.daemonize() self.setSignalHandler() @@ -415,6 +436,7 @@ class Main: sleep(1) def daemonize(self): + """Daemonise""" grandfatherPid = os.getpid() parentPid = None try: @@ -424,7 +446,7 @@ class Main: # wait until grandchild ready while True: sleep(1) - os._exit(0) + sys.exit(0) except AttributeError: # fork not implemented pass @@ -445,7 +467,7 @@ class Main: # wait until child ready while True: sleep(1) - os._exit(0) + sys.exit(0) except AttributeError: # fork not implemented pass @@ -467,11 +489,13 @@ class Main: os.kill(grandfatherPid, signal.SIGTERM) def setSignalHandler(self): + """Register signal handlers""" signal.signal(signal.SIGINT, helper_generic.signal_handler) signal.signal(signal.SIGTERM, helper_generic.signal_handler) - # signal.signal(signal.SIGINT, signal.SIG_DFL) def usage(self): + """Print usage message""" + print 'Usage: ' + sys.argv[0] + ' [OPTIONS]' print ''' Options: @@ -484,12 +508,18 @@ All parameters are optional. ''' def stop(self): + """Stop the daemon""" with shared.printLock: - print('Stopping Bitmessage Deamon.') + print 'Stopping Bitmessage Daemon.' shutdown.doCleanShutdown() - # TODO: nice function but no one is using this def getApiAddress(self): + """ + Return the address and port the API is configured to use + + .. todo:: nice function but no one is using this + """ + if not BMConfigParser().safeGetBoolean( 'bitmessagesettings', 'apienabled'): return None @@ -499,14 +529,10 @@ All parameters are optional. def main(): + """Create and start the main app""" mainprogram = Main() mainprogram.start() if __name__ == "__main__": main() - - -# So far, the creation of and management of the Bitmessage protocol and this -# client is a one-man operation. Bitcoin tips are quite appreciated. -# 1H5XaDA6fYENLbknwZyjiYXYPQaFjjLX2u diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 5e4c18fd..97a3a420 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -395,13 +395,13 @@ class MyForm(settingsmixin.SMainWindow): treeWidget.header().setSortIndicator( 0, QtCore.Qt.AscendingOrder) # init dictionary - + db = getSortedSubscriptions(True) for address in db: for folder in folders: if not folder in db[address]: db[address][folder] = {} - + if treeWidget.isSortingEnabled(): treeWidget.setSortingEnabled(False) @@ -413,7 +413,7 @@ class MyForm(settingsmixin.SMainWindow): toAddress = widget.address else: toAddress = None - + if not toAddress in db: treeWidget.takeTopLevelItem(i) # no increment @@ -444,7 +444,7 @@ class MyForm(settingsmixin.SMainWindow): widget.setUnreadCount(unread) db.pop(toAddress, None) i += 1 - + i = 0 for toAddress in db: widget = Ui_SubscriptionWidget(treeWidget, i, toAddress, db[toAddress]["inbox"]['count'], db[toAddress]["inbox"]['label'], db[toAddress]["inbox"]['enabled']) @@ -459,7 +459,7 @@ class MyForm(settingsmixin.SMainWindow): j += 1 widget.setUnreadCount(unread) i += 1 - + treeWidget.setSortingEnabled(True) @@ -468,14 +468,14 @@ class MyForm(settingsmixin.SMainWindow): def rerenderTabTreeChans(self): self.rerenderTabTree('chan') - + def rerenderTabTree(self, tab): if tab == 'messages': treeWidget = self.ui.treeWidgetYourIdentities elif tab == 'chan': treeWidget = self.ui.treeWidgetChans folders = Ui_FolderWidget.folderWeight.keys() - + # sort ascending when creating if treeWidget.topLevelItemCount() == 0: treeWidget.header().setSortIndicator( @@ -483,7 +483,7 @@ class MyForm(settingsmixin.SMainWindow): # init dictionary db = {} enabled = {} - + for toAddress in getSortedAccounts(): isEnabled = BMConfigParser().getboolean( toAddress, 'enabled') @@ -502,7 +502,7 @@ class MyForm(settingsmixin.SMainWindow): db[toAddress] = {} for folder in folders: db[toAddress][folder] = 0 - + enabled[toAddress] = isEnabled # get number of (unread) messages @@ -520,10 +520,10 @@ class MyForm(settingsmixin.SMainWindow): db[None]["sent"] = 0 db[None]["trash"] = 0 enabled[None] = True - + if treeWidget.isSortingEnabled(): treeWidget.setSortingEnabled(False) - + widgets = {} i = 0 while i < treeWidget.topLevelItemCount(): @@ -532,7 +532,7 @@ class MyForm(settingsmixin.SMainWindow): toAddress = widget.address else: toAddress = None - + if not toAddress in db: treeWidget.takeTopLevelItem(i) # no increment @@ -565,7 +565,7 @@ class MyForm(settingsmixin.SMainWindow): widget.setUnreadCount(unread) db.pop(toAddress, None) i += 1 - + i = 0 for toAddress in db: widget = Ui_AddressWidget(treeWidget, i, toAddress, db[toAddress]["inbox"], enabled[toAddress]) @@ -580,7 +580,7 @@ class MyForm(settingsmixin.SMainWindow): j += 1 widget.setUnreadCount(unread) i += 1 - + treeWidget.setSortingEnabled(True) def __init__(self, parent=None): @@ -622,13 +622,13 @@ class MyForm(settingsmixin.SMainWindow): # e.g. for editing labels self.recurDepth = 0 - + # switch back to this when replying self.replyFromTab = None # so that quit won't loop self.quitAccepted = False - + self.init_file_menu() self.init_inbox_popup_menu() self.init_identities_popup_menu() @@ -730,7 +730,7 @@ class MyForm(settingsmixin.SMainWindow): self.ui.treeWidgetYourIdentities.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.treeWidgetSubscriptions.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetAddressBook.setIconSize(QtCore.QSize(identicon_size, identicon_size)) - + self.UISignalThread = UISignaler.get() QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) @@ -784,7 +784,7 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderComboBoxSendFrom() self.rerenderComboBoxSendFromBroadcast() - + # Put the TTL slider in the correct spot TTL = BMConfigParser().getint('bitmessagesettings', 'ttl') if TTL < 3600: # an hour @@ -793,7 +793,7 @@ class MyForm(settingsmixin.SMainWindow): TTL = 28*24*60*60 self.ui.horizontalSliderTTL.setSliderPosition((TTL - 3600) ** (1/3.199)) self.updateHumanFriendlyTTLDescription(TTL) - + QtCore.QObject.connect(self.ui.horizontalSliderTTL, QtCore.SIGNAL( "valueChanged(int)"), self.updateTTL) @@ -817,7 +817,7 @@ class MyForm(settingsmixin.SMainWindow): self.updateHumanFriendlyTTLDescription(TTL) BMConfigParser().set('bitmessagesettings', 'ttl', str(TTL)) BMConfigParser().save() - + def updateHumanFriendlyTTLDescription(self, TTL): numberOfHours = int(round(TTL / (60*60))) font = QtGui.QFont() @@ -981,7 +981,7 @@ class MyForm(settingsmixin.SMainWindow): for row in queryReturn: broadcastsUnread[row[0]] = {} broadcastsUnread[row[0]][row[1]] = row[2] - + for treeWidget in widgets: root = treeWidget.invisibleRootItem() for i in range(root.childCount()): @@ -1100,7 +1100,7 @@ class MyForm(settingsmixin.SMainWindow): if acct is None: acct = BMAccount(fromAddress) acct.parseMessage(toAddress, fromAddress, subject, "") - + items = [] #to MessageList_AddressWidget(items, toAddress, unicode(acct.toLabel, 'utf-8'), not read) @@ -1176,7 +1176,7 @@ class MyForm(settingsmixin.SMainWindow): tableWidget.setRowCount(0) queryreturn = helper_search.search_sql(xAddress, account, folder, where, what, unreadOnly) - + for row in queryreturn: msgfolder, msgid, toAddress, fromAddress, subject, received, read = row self.addMessageListItemInbox(tableWidget, msgfolder, msgid, toAddress, fromAddress, subject, received, read) @@ -1934,7 +1934,7 @@ class MyForm(settingsmixin.SMainWindow): subject = acct.subject toAddress = acct.toAddress else: - if QtGui.QMessageBox.question(self, "Sending an email?", _translate("MainWindow", + if QtGui.QMessageBox.question(self, "Sending an email?", _translate("MainWindow", "You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?"), QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) != QtGui.QMessageBox.Yes: continue @@ -2111,17 +2111,17 @@ class MyForm(settingsmixin.SMainWindow): ackdata = genAckPayload(streamNumber, 0) toAddress = str_broadcast_subscribers ripe = '' - t = ('', # msgid. We don't know what this will be until the POW is done. - toAddress, - ripe, - fromAddress, - subject, - message, - ackdata, + t = ('', # msgid. We don't know what this will be until the POW is done. + toAddress, + ripe, + fromAddress, + subject, + message, + ackdata, int(time.time()), # sentTime (this will never change) int(time.time()), # lastActionTime 0, # sleepTill time. This will get set when the POW gets done. - 'broadcastqueued', + 'broadcastqueued', 0, # retryNumber 'sent', # folder encoding, # encoding type @@ -2131,7 +2131,7 @@ class MyForm(settingsmixin.SMainWindow): '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', *t) toLabel = str_broadcast_subscribers - + self.displayNewSentMessage( toAddress, toLabel, fromAddress, subject, message, ackdata) @@ -2250,7 +2250,7 @@ class MyForm(settingsmixin.SMainWindow): continue elif not helper_search.check_match(toAddress, fromAddress, subject, message, self.getCurrentSearchOption(tab), self.getCurrentSearchLine(tab)): continue - + self.addMessageListItemSent(sent, toAddress, fromAddress, subject, "msgqueued", ackdata, time.time()) self.getAccountTextedit(acct).setPlainText(unicode(message, 'utf-8', 'replace')) sent.setCurrentCell(0, 0) @@ -2412,11 +2412,11 @@ class MyForm(settingsmixin.SMainWindow): self.settingsDialogInstance.ui.checkBoxUseIdenticons.isChecked())) BMConfigParser().set('bitmessagesettings', 'replybelow', str( self.settingsDialogInstance.ui.checkBoxReplyBelow.isChecked())) - + lang = str(self.settingsDialogInstance.ui.languageComboBox.itemData(self.settingsDialogInstance.ui.languageComboBox.currentIndex()).toString()) BMConfigParser().set('bitmessagesettings', 'userlocale', lang) change_translation(l10n.getTranslationLanguage()) - + if int(BMConfigParser().get('bitmessagesettings', 'port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()): if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'): QtGui.QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( @@ -2481,7 +2481,7 @@ class MyForm(settingsmixin.SMainWindow): self.settingsDialogInstance.ui.lineEditNamecoinUser.text())) BMConfigParser().set('bitmessagesettings', 'namecoinrpcpassword', str( self.settingsDialogInstance.ui.lineEditNamecoinPassword.text())) - + # Demanded difficulty tab if float(self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) >= 1: BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(int(float( @@ -2495,7 +2495,7 @@ class MyForm(settingsmixin.SMainWindow): queues.workerQueue.put(('resetPoW', '')) acceptableDifficultyChanged = False - + if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) == 0: if BMConfigParser().get('bitmessagesettings','maxacceptablenoncetrialsperbyte') != str(int(float( self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)): @@ -2511,13 +2511,13 @@ class MyForm(settingsmixin.SMainWindow): BMConfigParser().set('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', str(int(float( self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * defaults.networkDefaultPayloadLengthExtraBytes))) if acceptableDifficultyChanged: - # It might now be possible to send msgs which were previously marked as toodifficult. + # It might now be possible to send msgs which were previously marked as toodifficult. # Let us change them to 'msgqueued'. The singleWorker will try to send them and will again # mark them as toodifficult if the receiver's required difficulty is still higher than # we are willing to do. sqlExecute('''UPDATE sent SET status='msgqueued' WHERE status='toodifficult' ''') queues.workerQueue.put(('sendmessage', '')) - + #start:UI setting to stop trying to send messages after X days/months # I'm open to changing this UI to something else if someone has a better idea. if ((self.settingsDialogInstance.ui.lineEditDays.text()=='') and (self.settingsDialogInstance.ui.lineEditMonths.text()=='')):#We need to handle this special case. Bitmessage has its default behavior. The input is blank/blank @@ -2718,7 +2718,7 @@ class MyForm(settingsmixin.SMainWindow): if getPowType() == "python" and (powQueueSize() > 0 or pendingUpload() > 0): reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Proof of work pending"), _translate("MainWindow", "%n object(s) pending proof of work", None, QtCore.QCoreApplication.CodecForTr, powQueueSize()) + ", " + - _translate("MainWindow", "%n object(s) waiting to be distributed", None, QtCore.QCoreApplication.CodecForTr, pendingUpload()) + "\n\n" + + _translate("MainWindow", "%n object(s) waiting to be distributed", None, QtCore.QCoreApplication.CodecForTr, pendingUpload()) + "\n\n" + _translate("MainWindow", "Wait until these tasks finish?"), QtGui.QMessageBox.Yes|QtGui.QMessageBox.No|QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) if reply == QtGui.QMessageBox.No: @@ -2807,7 +2807,7 @@ class MyForm(settingsmixin.SMainWindow): self.updateStatusBar(_translate( "MainWindow", "Waiting for objects to be sent... %1%").arg(50)) maxPendingUpload = max(1, pendingUpload()) - + while pendingUpload() > 1: self.updateStatusBar(_translate( "MainWindow", @@ -2936,7 +2936,7 @@ class MyForm(settingsmixin.SMainWindow): # self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(QtCore.Qt.UserRole), self.getCurrentFolder()) # else: # self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(QtCore.Qt.UserRole), self.getCurrentFolder(), self.getCurrentTreeWidget(), 0) - # tableWidget.selectRow(currentRow + 1) + # tableWidget.selectRow(currentRow + 1) # This doesn't de-select the last message if you try to mark it unread, but that doesn't interfere. Might not be necessary. # We could also select upwards, but then our problem would be with the topmost message. # tableWidget.clearSelection() manages to mark the message as read again. @@ -2980,18 +2980,18 @@ class MyForm(settingsmixin.SMainWindow): def on_action_InboxReplyChan(self): self.on_action_InboxReply(self.REPLY_TYPE_CHAN) - + def on_action_InboxReply(self, replyType = None): tableWidget = self.getCurrentMessagelist() if not tableWidget: return - + if replyType is None: replyType = self.REPLY_TYPE_SENDER - + # save this to return back after reply is done self.replyFromTab = self.ui.tabWidget.currentIndex() - + currentInboxRow = tableWidget.currentRow() toAddressAtCurrentInboxRow = tableWidget.item( currentInboxRow, 0).address @@ -3040,7 +3040,7 @@ class MyForm(settingsmixin.SMainWindow): self.ui.lineEditTo.setText(str(acct.fromAddress)) else: self.ui.lineEditTo.setText(tableWidget.item(currentInboxRow, 1).label + " <" + str(acct.fromAddress) + ">") - + # If the previous message was to a chan then we should send our reply to the chan rather than to the particular person who sent the message. if acct.type == AccountMixin.CHAN and replyType == self.REPLY_TYPE_CHAN: logger.debug('original sent to a chan. Setting the to address in the reply to the chan address.') @@ -3048,9 +3048,9 @@ class MyForm(settingsmixin.SMainWindow): self.ui.lineEditTo.setText(str(toAddressAtCurrentInboxRow)) else: self.ui.lineEditTo.setText(tableWidget.item(currentInboxRow, 0).label + " <" + str(acct.toAddress) + ">") - + self.setSendFromComboBox(toAddressAtCurrentInboxRow) - + quotedText = self.quoted_text(unicode(messageAtCurrentInboxRow, 'utf-8', 'replace')) widget['message'].setPlainText(quotedText) if acct.subject[0:3] in ['Re:', 'RE:']: @@ -3497,7 +3497,7 @@ class MyForm(settingsmixin.SMainWindow): return messagelistList[currentIndex] else: return False - + def getAccountMessagelist(self, account): try: if account.type == AccountMixin.CHAN: @@ -3584,7 +3584,7 @@ class MyForm(settingsmixin.SMainWindow): if currentItem: return currentItem return False - + def getCurrentAccount(self, treeWidget=None): currentItem = self.getCurrentItem(treeWidget) if currentItem: @@ -3711,7 +3711,7 @@ class MyForm(settingsmixin.SMainWindow): def on_action_AddressBookSetAvatar(self): self.on_action_SetAvatar(self.ui.tableWidgetAddressBook) - + def on_action_SetAvatar(self, thisTableWidget): currentRow = thisTableWidget.currentRow() addressAtCurrentRow = thisTableWidget.item( @@ -3765,7 +3765,7 @@ class MyForm(settingsmixin.SMainWindow): self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) else: overwrite = QtGui.QMessageBox.No - + # copy the image file to the appdata folder if (not exists) | (overwrite == QtGui.QMessageBox.Yes): if overwrite == QtGui.QMessageBox.Yes: @@ -3983,7 +3983,11 @@ class MyForm(settingsmixin.SMainWindow): if column != 0: return # only account names of normal addresses (no chans/mailinglists) - if (not isinstance(item, Ui_AddressWidget)) or (not self.getCurrentTreeWidget()) or self.getCurrentTreeWidget().currentItem() is None: + if ( + not isinstance(item, Ui_AddressWidget) or + not self.getCurrentTreeWidget() or + self.getCurrentTreeWidget().currentItem() is None + ): return # not visible if (not self.getCurrentItem()) or (not isinstance (self.getCurrentItem(), Ui_AddressWidget)): @@ -3994,7 +3998,7 @@ class MyForm(settingsmixin.SMainWindow): # "All accounts" can't be renamed if item.type == AccountMixin.ALL: return - + newLabel = unicode(item.text(0), 'utf-8', 'ignore') oldLabel = item.defaultLabel() @@ -4137,7 +4141,7 @@ class settingsDialog(QtGui.QDialog): BMConfigParser().safeGetBoolean('bitmessagesettings', 'useidenticons')) self.ui.checkBoxReplyBelow.setChecked( BMConfigParser().safeGetBoolean('bitmessagesettings', 'replybelow')) - + if state.appdata == paths.lookupExeFolder(): self.ui.checkBoxPortableMode.setChecked(True) else: @@ -4262,8 +4266,8 @@ class settingsDialog(QtGui.QDialog): BMConfigParser().get('bitmessagesettings', 'stopresendingafterxdays'))) self.ui.lineEditMonths.setText(str( BMConfigParser().get('bitmessagesettings', 'stopresendingafterxmonths'))) - - + + #'System' tab removed for now. """try: maxCores = BMConfigParser().getint('bitmessagesettings', 'maxcores') @@ -4314,7 +4318,7 @@ class settingsDialog(QtGui.QDialog): def namecoinTypeChanged(self, checked): nmctype = self.getNamecoinType() assert nmctype == "namecoind" or nmctype == "nmcontrol" - + isNamecoind = (nmctype == "namecoind") self.ui.lineEditNamecoinUser.setEnabled(isNamecoind) self.ui.labelNamecoinUser.setEnabled(isNamecoind) @@ -4438,7 +4442,7 @@ def run(): # myapp.showMigrationWizard(BMConfigParser().get('bitmessagesettings', 'mailchuck')) # except: # myapp.showMigrationWizard(0) - + # only show after wizards and connect dialogs have completed if not BMConfigParser().getboolean('bitmessagesettings', 'startintray'): myapp.show() diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index cb3578c0..7aec31f2 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -1,20 +1,29 @@ # -*- coding: utf-8 -*- +# pylint: disable=too-many-locals +""" +Form implementation generated from reading ui file 'bitmessageui.ui' -# Form implementation generated from reading ui file 'bitmessageui.ui' -# -# Created: Mon Mar 23 22:18:07 2015 -# by: PyQt4 UI code generator 4.10.4 -# -# WARNING! All changes made in this file will be lost! +Created: Mon Mar 23 22:18:07 2015 + by: PyQt4 UI code generator 4.10.4 + +WARNING! All changes made in this file will be lost! +""" + +from __future__ import absolute_import + +import sys from PyQt4 import QtCore, QtGui + from bmconfigparser import BMConfigParser -from foldertree import AddressBookCompleter -from messageview import MessageView -from messagecompose import MessageCompose -import settingsmixin -from networkstatus import NetworkStatus -from blacklist import Blacklist +from . import bitmessage_icons_rc # pylint: disable=unused-import +from . import settingsmixin +from .messageview import MessageView +from .messagecompose import MessageCompose +from .networkstatus import NetworkStatus +from .blacklist import Blacklist +from .foldertree import AddressBookCompleter + try: _fromUtf8 = QtCore.QString.fromUtf8 @@ -24,24 +33,38 @@ except AttributeError: try: _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig, encoding = QtCore.QCoreApplication.CodecForTr, n = None): + + def _translate(context, text, disambig, encoding=QtCore.QCoreApplication.CodecForTr, n=None): + # pylint: disable=unused-argument if n is None: return QtGui.QApplication.translate(context, text, disambig, _encoding) - else: - return QtGui.QApplication.translate(context, text, disambig, _encoding, n) + return QtGui.QApplication.translate(context, text, disambig, _encoding, n) + except AttributeError: - def _translate(context, text, disambig, encoding = QtCore.QCoreApplication.CodecForTr, n = None): + + def _translate(context, text, disambig, encoding=QtCore.QCoreApplication.CodecForTr, n=None): + # pylint: disable=unused-argument if n is None: return QtGui.QApplication.translate(context, text, disambig) - else: - return QtGui.QApplication.translate(context, text, disambig, QtCore.QCoreApplication.CodecForTr, n) + return QtGui.QApplication.translate(context, text, disambig, QtCore.QCoreApplication.CodecForTr, n) + class Ui_MainWindow(object): + """Encapsulate the main UI""" + # pylint: disable=too-many-instance-attributes,too-many-statements + def setupUi(self, MainWindow): + """Set up the UI""" + # pylint: disable=attribute-defined-outside-init + MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.resize(885, 580) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap( + QtGui.QPixmap( + _fromUtf8(":/newPrefix/images/can-icon-24px.png")), + QtGui.QIcon.Normal, + QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) MainWindow.setTabShape(QtGui.QTabWidget.Rounded) self.centralwidget = QtGui.QWidget(MainWindow) @@ -75,7 +98,11 @@ class Ui_MainWindow(object): self.treeWidgetYourIdentities.setObjectName(_fromUtf8("treeWidgetYourIdentities")) self.treeWidgetYourIdentities.resize(200, self.treeWidgetYourIdentities.height()) icon1 = QtGui.QIcon() - icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/identities.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) + icon1.addPixmap( + QtGui.QPixmap( + _fromUtf8(":/newPrefix/images/identities.png")), + QtGui.QIcon.Selected, + QtGui.QIcon.Off) self.treeWidgetYourIdentities.headerItem().setIcon(0, icon1) self.verticalSplitter_12.addWidget(self.treeWidgetYourIdentities) self.pushButtonNewAddress = QtGui.QPushButton(self.inbox) @@ -175,7 +202,11 @@ class Ui_MainWindow(object): self.tableWidgetAddressBook.resize(200, self.tableWidgetAddressBook.height()) item = QtGui.QTableWidgetItem() icon3 = QtGui.QIcon() - icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/addressbook.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) + icon3.addPixmap( + QtGui.QPixmap( + _fromUtf8(":/newPrefix/images/addressbook.png")), + QtGui.QIcon.Selected, + QtGui.QIcon.Off) item.setIcon(icon3) self.tableWidgetAddressBook.setHorizontalHeaderItem(0, item) item = QtGui.QTableWidgetItem() @@ -376,7 +407,11 @@ class Ui_MainWindow(object): self.treeWidgetSubscriptions.setObjectName(_fromUtf8("treeWidgetSubscriptions")) self.treeWidgetSubscriptions.resize(200, self.treeWidgetSubscriptions.height()) icon5 = QtGui.QIcon() - icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) + icon5.addPixmap( + QtGui.QPixmap( + _fromUtf8(":/newPrefix/images/subscriptions.png")), + QtGui.QIcon.Selected, + QtGui.QIcon.Off) self.treeWidgetSubscriptions.headerItem().setIcon(0, icon5) self.verticalSplitter_3.addWidget(self.treeWidgetSubscriptions) self.pushButtonAddSubscription = QtGui.QPushButton(self.subscriptions) @@ -455,7 +490,11 @@ class Ui_MainWindow(object): self.horizontalSplitter_4.setCollapsible(1, False) self.gridLayout_3.addWidget(self.horizontalSplitter_4, 0, 0, 1, 1) icon6 = QtGui.QIcon() - icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon6.addPixmap( + QtGui.QPixmap( + _fromUtf8(":/newPrefix/images/subscriptions.png")), + QtGui.QIcon.Normal, + QtGui.QIcon.Off) self.tabWidget.addTab(self.subscriptions, icon6, _fromUtf8("")) self.chans = QtGui.QWidget() self.chans.setObjectName(_fromUtf8("chans")) @@ -475,7 +514,11 @@ class Ui_MainWindow(object): self.treeWidgetChans.setObjectName(_fromUtf8("treeWidgetChans")) self.treeWidgetChans.resize(200, self.treeWidgetChans.height()) icon7 = QtGui.QIcon() - icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) + icon7.addPixmap( + QtGui.QPixmap( + _fromUtf8(":/newPrefix/images/can-icon-16px.png")), + QtGui.QIcon.Selected, + QtGui.QIcon.Off) self.treeWidgetChans.headerItem().setIcon(0, icon7) self.verticalSplitter_17.addWidget(self.treeWidgetChans) self.pushButtonAddChan = QtGui.QPushButton(self.chans) @@ -554,7 +597,11 @@ class Ui_MainWindow(object): self.horizontalSplitter_7.setCollapsible(1, False) self.gridLayout_4.addWidget(self.horizontalSplitter_7, 0, 0, 1, 1) icon8 = QtGui.QIcon() - icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon8.addPixmap( + QtGui.QPixmap( + _fromUtf8(":/newPrefix/images/can-icon-16px.png")), + QtGui.QIcon.Normal, + QtGui.QIcon.Off) self.tabWidget.addTab(self.chans, icon8, _fromUtf8("")) self.blackwhitelist = Blacklist() self.tabWidget.addTab(self.blackwhitelist, QtGui.QIcon(":/newPrefix/images/blacklist.png"), "") @@ -652,6 +699,8 @@ class Ui_MainWindow(object): MainWindow.setTabOrder(self.textEditMessage, self.pushButtonAddSubscription) def updateNetworkSwitchMenuLabel(self, dontconnect=None): + """Restore last online/offline setting""" + if dontconnect is None: dontconnect = BMConfigParser().safeGetBoolean( 'bitmessagesettings', 'dontconnect') @@ -662,6 +711,8 @@ class Ui_MainWindow(object): ) def retranslateUi(self, MainWindow): + """Re-translate the UI""" + MainWindow.setWindowTitle(_translate("MainWindow", "Bitmessage", None)) self.treeWidgetYourIdentities.headerItem().setText(0, _translate("MainWindow", "Identities", None)) self.pushButtonNewAddress.setText(_translate("MainWindow", "New Identity", None)) @@ -691,19 +742,33 @@ class Ui_MainWindow(object): self.label_3.setText(_translate("MainWindow", "Subject:", None)) self.label_2.setText(_translate("MainWindow", "From:", None)) self.label.setText(_translate("MainWindow", "To:", None)) - #self.textEditMessage.setHtml("") - self.tabWidgetSend.setTabText(self.tabWidgetSend.indexOf(self.sendDirect), _translate("MainWindow", "Send ordinary Message", None)) + # self.textEditMessage.setHtml("") + self.tabWidgetSend.setTabText( + self.tabWidgetSend.indexOf( + self.sendDirect), + _translate( + "MainWindow", "Send ordinary Message", None)) self.label_8.setText(_translate("MainWindow", "From:", None)) self.label_7.setText(_translate("MainWindow", "Subject:", None)) - #self.textEditMessageBroadcast.setHtml("") - self.tabWidgetSend.setTabText(self.tabWidgetSend.indexOf(self.sendBroadcast), _translate("MainWindow", "Send Message to your Subscribers", None)) + # self.textEditMessageBroadcast.setHtml("") + self.tabWidgetSend.setTabText( + self.tabWidgetSend.indexOf( + self.sendBroadcast), + _translate( + "MainWindow", "Send Message to your Subscribers", None)) self.pushButtonTTL.setText(_translate("MainWindow", "TTL:", None)) hours = 48 try: - hours = int(BMConfigParser().getint('bitmessagesettings', 'ttl')/60/60) + hours = int(BMConfigParser().getint('bitmessagesettings', 'ttl') / 60 / 60) except: pass - self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours)) + self.labelHumanFriendlyTTLDescription.setText( + _translate( + "MainWindow", + "%n hour(s)", + None, + QtCore.QCoreApplication.CodecForTr, + hours)) self.pushButtonClear.setText(_translate("MainWindow", "Clear", None)) self.pushButtonSend.setText(_translate("MainWindow", "Send", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), _translate("MainWindow", "Send", None)) @@ -724,7 +789,11 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Subject", None)) item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(3) item.setText(_translate("MainWindow", "Received", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.subscriptions), _translate("MainWindow", "Subscriptions", None)) + self.tabWidget.setTabText( + self.tabWidget.indexOf( + self.subscriptions), + _translate( + "MainWindow", "Subscriptions", None)) self.treeWidgetChans.headerItem().setText(0, _translate("MainWindow", "Chans", None)) self.pushButtonAddChan.setText(_translate("MainWindow", "Add Chan", None)) self.inboxSearchLineEditChans.setPlaceholderText(_translate("MainWindow", "Search", None)) @@ -744,9 +813,17 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Received", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.chans), _translate("MainWindow", "Chans", None)) self.blackwhitelist.retranslateUi() - self.tabWidget.setTabText(self.tabWidget.indexOf(self.blackwhitelist), _translate("blacklist", "Blacklist", None)) + self.tabWidget.setTabText( + self.tabWidget.indexOf( + self.blackwhitelist), + _translate( + "blacklist", "Blacklist", None)) self.networkstatus.retranslateUi() - self.tabWidget.setTabText(self.tabWidget.indexOf(self.networkstatus), _translate("networkstatus", "Network Status", None)) + self.tabWidget.setTabText( + self.tabWidget.indexOf( + self.networkstatus), + _translate( + "networkstatus", "Network Status", None)) self.menuFile.setTitle(_translate("MainWindow", "File", None)) self.menuSettings.setTitle(_translate("MainWindow", "Settings", None)) self.menuHelp.setTitle(_translate("MainWindow", "Help", None)) @@ -759,19 +836,17 @@ class Ui_MainWindow(object): self.actionSupport.setText(_translate("MainWindow", "Contact support", None)) self.actionAbout.setText(_translate("MainWindow", "About", None)) self.actionSettings.setText(_translate("MainWindow", "Settings", None)) - self.actionRegenerateDeterministicAddresses.setText(_translate("MainWindow", "Regenerate deterministic addresses", None)) + self.actionRegenerateDeterministicAddresses.setText( + _translate("MainWindow", "Regenerate deterministic addresses", None)) self.actionDeleteAllTrashedMessages.setText(_translate("MainWindow", "Delete all trashed messages", None)) self.actionJoinChan.setText(_translate("MainWindow", "Join / Create chan", None)) -import bitmessage_icons_rc if __name__ == "__main__": - import sys - - app = QtGui.QApplication(sys.argv) - MainWindow = settingsmixin.SMainWindow() - ui = Ui_MainWindow() - ui.setupUi(MainWindow) - MainWindow.show() - sys.exit(app.exec_()) + app = QtGui.QApplication(sys.argv) + ThisMainWindow = settingsmixin.SMainWindow() + ui = Ui_MainWindow() + ui.setupUi(ThisMainWindow) + ThisMainWindow.show() + sys.exit(app.exec_()) diff --git a/src/bitmessageqt/newaddresswizard.py b/src/bitmessageqt/newaddresswizard.py deleted file mode 100644 index 2311239c..00000000 --- a/src/bitmessageqt/newaddresswizard.py +++ /dev/null @@ -1,354 +0,0 @@ -#!/usr/bin/env python2.7 -from PyQt4 import QtCore, QtGui - -class NewAddressWizardIntroPage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Creating a new address") - - label = QtGui.QLabel("This wizard will help you create as many addresses as you like. Indeed, creating and abandoning addresses is encouraged.\n\n" - "What type of address would you like? Would you like to send emails or not?\n" - "You can still change your mind later, and register/unregister with an email service provider.\n\n") - label.setWordWrap(True) - - self.emailAsWell = QtGui.QRadioButton("Combined email and bitmessage address") - self.onlyBM = QtGui.QRadioButton("Bitmessage-only address (no email)") - self.emailAsWell.setChecked(True) - self.registerField("emailAsWell", self.emailAsWell) - self.registerField("onlyBM", self.onlyBM) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - layout.addWidget(self.emailAsWell) - layout.addWidget(self.onlyBM) - self.setLayout(layout) - - def nextId(self): - if self.emailAsWell.isChecked(): - return 4 - else: - return 1 - - -class NewAddressWizardRngPassphrasePage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Random or Passphrase") - - label = QtGui.QLabel("

You may generate addresses by using either random numbers or by using a passphrase. " - "If you use a passphrase, the address is called a "deterministic" address. " - "The \'Random Number\' option is selected by default but deterministic addresses have several pros and cons:

" - "" - "" - "
Pros:Cons:
You can recreate your addresses on any computer from memory. " - "You need-not worry about backing up your keys.dat file as long as you can remember your passphrase.You must remember (or write down) your passphrase if you expect to be able " - "to recreate your keys if they are lost. " -# "You must remember the address version number and the stream number along with your passphrase. " - "If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you." - "

") - label.setWordWrap(True) - - self.randomAddress = QtGui.QRadioButton("Use a random number generator to make an address") - self.deterministicAddress = QtGui.QRadioButton("Use a passphrase to make an address") - self.randomAddress.setChecked(True) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - layout.addWidget(self.randomAddress) - layout.addWidget(self.deterministicAddress) - self.setLayout(layout) - - def nextId(self): - if self.randomAddress.isChecked(): - return 2 - else: - return 3 - -class NewAddressWizardRandomPage(QtGui.QWizardPage): - def __init__(self, addresses): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Random") - - label = QtGui.QLabel("Random address.") - label.setWordWrap(True) - - labelLabel = QtGui.QLabel("Label (not shown to anyone except you):") - self.labelLineEdit = QtGui.QLineEdit() - - self.radioButtonMostAvailable = QtGui.QRadioButton("Use the most available stream\n" - "(best if this is the first of many addresses you will create)") - self.radioButtonExisting = QtGui.QRadioButton("Use the same stream as an existing address\n" - "(saves you some bandwidth and processing power)") - self.radioButtonMostAvailable.setChecked(True) - self.comboBoxExisting = QtGui.QComboBox() - self.comboBoxExisting.setEnabled(False) - self.comboBoxExisting.setEditable(True) - - for address in addresses: - self.comboBoxExisting.addItem(address) - -# self.comboBoxExisting.setObjectName(_fromUtf8("comboBoxExisting")) - self.checkBoxEighteenByteRipe = QtGui.QCheckBox("Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter") - - layout = QtGui.QGridLayout() - layout.addWidget(label, 0, 0) - layout.addWidget(labelLabel, 1, 0) - layout.addWidget(self.labelLineEdit, 2, 0) - layout.addWidget(self.radioButtonMostAvailable, 3, 0) - layout.addWidget(self.radioButtonExisting, 4, 0) - layout.addWidget(self.comboBoxExisting, 5, 0) - layout.addWidget(self.checkBoxEighteenByteRipe, 6, 0) - self.setLayout(layout) - - QtCore.QObject.connect(self.radioButtonExisting, QtCore.SIGNAL("toggled(bool)"), self.comboBoxExisting.setEnabled) - - self.registerField("label", self.labelLineEdit) - self.registerField("radioButtonMostAvailable", self.radioButtonMostAvailable) - self.registerField("radioButtonExisting", self.radioButtonExisting) - self.registerField("comboBoxExisting", self.comboBoxExisting) - -# self.emailAsWell = QtGui.QRadioButton("Combined email and bitmessage account") -# self.onlyBM = QtGui.QRadioButton("Bitmessage-only account (no email)") -# self.emailAsWell.setChecked(True) - - def nextId(self): - return 6 - - -class NewAddressWizardPassphrasePage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Passphrase") - - label = QtGui.QLabel("Deterministric address.") - label.setWordWrap(True) - - passphraseLabel = QtGui.QLabel("Passphrase") - self.lineEditPassphrase = QtGui.QLineEdit() - self.lineEditPassphrase.setEchoMode(QtGui.QLineEdit.Password) - self.lineEditPassphrase.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText) - retypePassphraseLabel = QtGui.QLabel("Retype passphrase") - self.lineEditPassphraseAgain = QtGui.QLineEdit() - self.lineEditPassphraseAgain.setEchoMode(QtGui.QLineEdit.Password) - - numberLabel = QtGui.QLabel("Number of addresses to make based on your passphrase:") - self.spinBoxNumberOfAddressesToMake = QtGui.QSpinBox() - self.spinBoxNumberOfAddressesToMake.setMinimum(1) - self.spinBoxNumberOfAddressesToMake.setProperty("value", 8) -# self.spinBoxNumberOfAddressesToMake.setObjectName(_fromUtf8("spinBoxNumberOfAddressesToMake")) - label2 = QtGui.QLabel("In addition to your passphrase, you must remember these numbers:") - label3 = QtGui.QLabel("Address version number: 4") - label4 = QtGui.QLabel("Stream number: 1") - - layout = QtGui.QGridLayout() - layout.addWidget(label, 0, 0, 1, 4) - layout.addWidget(passphraseLabel, 1, 0, 1, 4) - layout.addWidget(self.lineEditPassphrase, 2, 0, 1, 4) - layout.addWidget(retypePassphraseLabel, 3, 0, 1, 4) - layout.addWidget(self.lineEditPassphraseAgain, 4, 0, 1, 4) - layout.addWidget(numberLabel, 5, 0, 1, 3) - layout.addWidget(self.spinBoxNumberOfAddressesToMake, 5, 3) - layout.setColumnMinimumWidth(3, 1) - layout.addWidget(label2, 6, 0, 1, 4) - layout.addWidget(label3, 7, 0, 1, 2) - layout.addWidget(label4, 7, 2, 1, 2) - self.setLayout(layout) - - def nextId(self): - return 6 - - -class NewAddressWizardEmailProviderPage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Choose email provider") - - label = QtGui.QLabel("Currently only Mailchuck email gateway is available " - "(@mailchuck.com email address). In the future, maybe other gateways will be available. " - "Press Next.") - label.setWordWrap(True) - -# self.mailchuck = QtGui.QRadioButton("Mailchuck email gateway (@mailchuck.com)") -# self.mailchuck.setChecked(True) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) -# layout.addWidget(self.mailchuck) - self.setLayout(layout) - - def nextId(self): - return 5 - - -class NewAddressWizardEmailAddressPage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Email address") - - label = QtGui.QLabel("Choosing an email address. Address must end with @mailchuck.com") - label.setWordWrap(True) - - self.specificEmail = QtGui.QRadioButton("Pick your own email address:") - self.specificEmail.setChecked(True) - self.emailLineEdit = QtGui.QLineEdit() - self.randomEmail = QtGui.QRadioButton("Generate a random email address") - - QtCore.QObject.connect(self.specificEmail, QtCore.SIGNAL("toggled(bool)"), self.emailLineEdit.setEnabled) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - layout.addWidget(self.specificEmail) - layout.addWidget(self.emailLineEdit) - layout.addWidget(self.randomEmail) - self.setLayout(layout) - - def nextId(self): - return 6 - - -class NewAddressWizardWaitPage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("Wait") - - self.label = QtGui.QLabel("Wait!") - self.label.setWordWrap(True) - self.progressBar = QtGui.QProgressBar() - self.progressBar.setMinimum(0) - self.progressBar.setMaximum(100) - self.progressBar.setValue(0) - -# self.emailAsWell = QtGui.QRadioButton("Combined email and bitmessage account") -# self.onlyBM = QtGui.QRadioButton("Bitmessage-only account (no email)") -# self.emailAsWell.setChecked(True) - - layout = QtGui.QVBoxLayout() - layout.addWidget(self.label) - layout.addWidget(self.progressBar) -# layout.addWidget(self.emailAsWell) -# layout.addWidget(self.onlyBM) - self.setLayout(layout) - - def update(self, i): - if i == 101 and self.wizard().currentId() == 6: - self.wizard().button(QtGui.QWizard.NextButton).click() - return - elif i == 101: - print "haha" - return - self.progressBar.setValue(i) - if i == 50: - self.emit(QtCore.SIGNAL('completeChanged()')) - - def isComplete(self): -# print "val = " + str(self.progressBar.value()) - if self.progressBar.value() >= 50: - return True - else: - return False - - def initializePage(self): - if self.field("emailAsWell").toBool(): - val = "yes/" - else: - val = "no/" - if self.field("onlyBM").toBool(): - val += "yes" - else: - val += "no" - - self.label.setText("Wait! " + val) -# self.wizard().button(QtGui.QWizard.NextButton).setEnabled(False) - self.progressBar.setValue(0) - self.thread = NewAddressThread() - self.connect(self.thread, self.thread.signal, self.update) - self.thread.start() - - def nextId(self): - return 10 - - -class NewAddressWizardConclusionPage(QtGui.QWizardPage): - def __init__(self): - super(QtGui.QWizardPage, self).__init__() - self.setTitle("All done!") - - label = QtGui.QLabel("You successfully created a new address.") - label.setWordWrap(True) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - self.setLayout(layout) - -class Ui_NewAddressWizard(QtGui.QWizard): - def __init__(self, addresses): - super(QtGui.QWizard, self).__init__() - - self.pages = {} - - page = NewAddressWizardIntroPage() - self.setPage(0, page) - self.setStartId(0) - page = NewAddressWizardRngPassphrasePage() - self.setPage(1, page) - page = NewAddressWizardRandomPage(addresses) - self.setPage(2, page) - page = NewAddressWizardPassphrasePage() - self.setPage(3, page) - page = NewAddressWizardEmailProviderPage() - self.setPage(4, page) - page = NewAddressWizardEmailAddressPage() - self.setPage(5, page) - page = NewAddressWizardWaitPage() - self.setPage(6, page) - page = NewAddressWizardConclusionPage() - self.setPage(10, page) - - self.setWindowTitle("New address wizard") - self.adjustSize() - self.show() - -class NewAddressThread(QtCore.QThread): - def __init__(self): - QtCore.QThread.__init__(self) - self.signal = QtCore.SIGNAL("signal") - - def __del__(self): - self.wait() - - def createDeterministic(self): - pass - - def createPassphrase(self): - pass - - def broadcastAddress(self): - pass - - def registerMailchuck(self): - pass - - def waitRegistration(self): - pass - - def run(self): - import time - for i in range(1, 101): - time.sleep(0.1) # artificial time delay - self.emit(self.signal, i) - self.emit(self.signal, 101) -# self.terminate() - -if __name__ == '__main__': - - import sys - - app = QtGui.QApplication(sys.argv) - - wizard = Ui_NewAddressWizard(["a", "b", "c", "d"]) - if (wizard.exec_()): - print "Email: " + ("yes" if wizard.field("emailAsWell").toBool() else "no") - print "BM: " + ("yes" if wizard.field("onlyBM").toBool() else "no") - else: - print "Wizard cancelled" - sys.exit() diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py index e50be61b..233a49b1 100644 --- a/src/network/asyncore_pollchoose.py +++ b/src/network/asyncore_pollchoose.py @@ -1,7 +1,9 @@ # -*- Mode: Python -*- # Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp # Author: Sam Rushing - +# pylint: disable=too-many-statements,too-many-branches,no-self-use,too-many-lines,attribute-defined-outside-init +# pylint: disable=global-statement +""" # ====================================================================== # Copyright 1996 by Sam Rushing # @@ -25,7 +27,7 @@ # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # ====================================================================== -"""Basic infrastructure for asynchronous socket service clients and servers. +Basic infrastructure for asynchronous socket service clients and servers. There are only two ways to have a program on a single processor do "more than one thing at a time". Multi-threaded programming is the simplest and @@ -46,22 +48,18 @@ many of the difficult problems for you, making the task of building sophisticated high-performance network servers and clients a snap. """ -# randomise object order for bandwidth balancing -import random +import os import select import socket import sys import time from threading import current_thread import warnings - -import os -import helper_random from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, EINVAL, \ - ENOTCONN, ESHUTDOWN, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \ - ECONNREFUSED, EHOSTUNREACH, ENETUNREACH, ENOTSOCK, EINTR, ETIMEDOUT, \ - EADDRINUSE, \ - errorcode + ENOTCONN, ESHUTDOWN, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \ + ECONNREFUSED, EHOSTUNREACH, ENETUNREACH, ENOTSOCK, EINTR, ETIMEDOUT, \ + EADDRINUSE, \ + errorcode try: from errno import WSAEWOULDBLOCK except (ImportError, AttributeError): @@ -75,13 +73,16 @@ try: except (ImportError, AttributeError): WSAECONNRESET = ECONNRESET try: - from errno import WSAEADDRINUSE + # side-effects on Windows or cruft? + from errno import WSAEADDRINUSE # pylint: disable=unused-import except (ImportError, AttributeError): WSAEADDRINUSE = EADDRINUSE +import helper_random -_DISCONNECTED = frozenset((ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, - EBADF, ECONNREFUSED, EHOSTUNREACH, ENETUNREACH, ETIMEDOUT, - WSAECONNRESET)) + +_DISCONNECTED = frozenset(( + ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, EBADF, ECONNREFUSED, + EHOSTUNREACH, ENETUNREACH, ETIMEDOUT, WSAECONNRESET)) OP_READ = 1 OP_WRITE = 2 @@ -91,17 +92,21 @@ try: except NameError: socket_map = {} + def _strerror(err): try: return os.strerror(err) except (ValueError, OverflowError, NameError): if err in errorcode: return errorcode[err] - return "Unknown error %s" %err + return "Unknown error %s" % err + class ExitNow(Exception): + """We don't use directly but may be necessary as we replace asyncore due to some library raising or expecting it""" pass + _reraised_exceptions = (ExitNow, KeyboardInterrupt, SystemExit) maxDownloadRate = 0 @@ -113,7 +118,10 @@ uploadTimestamp = 0 uploadBucket = 0 sentBytes = 0 + def read(obj): + """Read an event from the object""" + if not can_receive(): return try: @@ -123,7 +131,10 @@ def read(obj): except: obj.handle_error() + def write(obj): + """Write an event to the object""" + if not can_send(): return try: @@ -133,8 +144,12 @@ def write(obj): except: obj.handle_error() + def set_rates(download, upload): + """Set throttling rates""" + global maxDownloadRate, maxUploadRate, downloadBucket, uploadBucket, downloadTimestamp, uploadTimestamp + maxDownloadRate = float(download) * 1024 maxUploadRate = float(upload) * 1024 downloadBucket = maxDownloadRate @@ -142,14 +157,24 @@ def set_rates(download, upload): downloadTimestamp = time.time() uploadTimestamp = time.time() + def can_receive(): + """Predicate indicating whether the download throttle is in effect""" + return maxDownloadRate == 0 or downloadBucket > 0 + def can_send(): + """Predicate indicating whether the upload throttle is in effect""" + return maxUploadRate == 0 or uploadBucket > 0 + def update_received(download=0): + """Update the receiving throttle""" + global receivedBytes, downloadBucket, downloadTimestamp + currentTimestamp = time.time() receivedBytes += download if maxDownloadRate > 0: @@ -160,8 +185,12 @@ def update_received(download=0): downloadBucket -= download downloadTimestamp = currentTimestamp + def update_sent(upload=0): + """Update the sending throttle""" + global sentBytes, uploadBucket, uploadTimestamp + currentTimestamp = time.time() sentBytes += upload if maxUploadRate > 0: @@ -172,7 +201,10 @@ def update_sent(upload=0): uploadBucket -= upload uploadTimestamp = currentTimestamp + def _exception(obj): + """Handle exceptions as appropriate""" + try: obj.handle_expt_event() except _reraised_exceptions: @@ -180,7 +212,10 @@ def _exception(obj): except: obj.handle_error() + def readwrite(obj, flags): + """Read and write any pending data to/from the object""" + try: if flags & select.POLLIN and can_receive(): obj.handle_read_event() @@ -200,12 +235,17 @@ def readwrite(obj, flags): except: obj.handle_error() + def select_poller(timeout=0.0, map=None): """A poller which uses select(), available on most platforms.""" + # pylint: disable=redefined-builtin + if map is None: map = socket_map if map: - r = []; w = []; e = [] + r = [] + w = [] + e = [] for fd, obj in list(map.items()): is_r = obj.readable() is_w = obj.writable() @@ -251,13 +291,16 @@ def select_poller(timeout=0.0, map=None): else: current_thread().stop.wait(timeout) + def poll_poller(timeout=0.0, map=None): """A poller which uses poll(), available on most UNIXen.""" + # pylint: disable=redefined-builtin + if map is None: map = socket_map if timeout is not None: # timeout is in milliseconds - timeout = int(timeout*1000) + timeout = int(timeout * 1000) try: poll_poller.pollster except AttributeError: @@ -301,12 +344,16 @@ def poll_poller(timeout=0.0, map=None): else: current_thread().stop.wait(timeout) + # Aliases for backward compatibility poll = select_poller poll2 = poll3 = poll_poller + def epoll_poller(timeout=0.0, map=None): """A poller which uses epoll(), supported on Linux 2.5.44 and newer.""" + # pylint: disable=redefined-builtin + if map is None: map = socket_map try: @@ -346,7 +393,7 @@ def epoll_poller(timeout=0.0, map=None): if e.errno != EINTR: raise r = [] - except select.error, err: + except select.error as err: if err.args[0] != EINTR: raise r = [] @@ -354,12 +401,15 @@ def epoll_poller(timeout=0.0, map=None): obj = map.get(fd) if obj is None: continue - readwrite(obj, flags) + readwrite(obj, flags) else: current_thread().stop.wait(timeout) + def kqueue_poller(timeout=0.0, map=None): """A poller which uses kqueue(), BSD specific.""" + # pylint: disable=redefined-builtin,no-member + if map is None: map = socket_map try: @@ -408,7 +458,7 @@ def kqueue_poller(timeout=0.0, map=None): for event in events: fd = event.ident - obj = map.get(fd) + obj = map.get(fd) if obj is None: continue if event.flags & select.KQ_EV_ERROR: @@ -425,13 +475,15 @@ def kqueue_poller(timeout=0.0, map=None): current_thread().stop.wait(timeout) -def loop(timeout=30.0, use_poll=False, map=None, count=None, - poller=None): +def loop(timeout=30.0, use_poll=False, map=None, count=None, poller=None): + """Poll in a loop, forever if count is None""" + # pylint: disable=redefined-builtin + if map is None: map = socket_map if count is None: - count = True - # code which grants backward compatibility with "use_poll" + count = True + # code which grants backward compatibility with "use_poll" # argument which should no longer be used in favor of # "poller" @@ -460,10 +512,13 @@ def loop(timeout=30.0, use_poll=False, map=None, count=None, break # then poll poller(subtimeout, map) - if type(count) is int: + if isinstance(count, int): count = count - 1 + class dispatcher: + """Dispatcher for socket objects""" + # pylint: disable=too-many-public-methods,too-many-instance-attributes,old-style-class debug = False connected = False @@ -478,6 +533,7 @@ class dispatcher: minTx = 1500 def __init__(self, sock=None, map=None): + # pylint: disable=redefined-builtin if map is None: self._map = socket_map else: @@ -510,7 +566,7 @@ class dispatcher: self.socket = None def __repr__(self): - status = [self.__class__.__module__+"."+self.__class__.__name__] + status = [self.__class__.__module__ + "." + self.__class__.__name__] if self.accepting and self.addr: status.append('listening') elif self.connected: @@ -525,7 +581,9 @@ class dispatcher: __str__ = __repr__ def add_channel(self, map=None): - #self.log_info('adding channel %s' % self) + """Add a channel""" + # pylint: disable=redefined-builtin + if map is None: map = self._map map[self._fileno] = self @@ -533,11 +591,13 @@ class dispatcher: self.poller_filter = 0 def del_channel(self, map=None): + """Delete a channel""" + # pylint: disable=redefined-builtin + fd = self._fileno if map is None: map = self._map if fd in map: - #self.log_info('closing channel %d:%s' % (fd, self)) del map[fd] if self._fileno: try: @@ -564,25 +624,29 @@ class dispatcher: self.poller_registered = False def create_socket(self, family=socket.AF_INET, socket_type=socket.SOCK_STREAM): + """Create a socket""" self.family_and_type = family, socket_type sock = socket.socket(family, socket_type) sock.setblocking(0) self.set_socket(sock) def set_socket(self, sock, map=None): + """Set socket""" + # pylint: disable=redefined-builtin + self.socket = sock -## self.__dict__['socket'] = sock self._fileno = sock.fileno() self.add_channel(map) def set_reuse_addr(self): - # try to re-use a server port if possible + """try to re-use a server port if possible""" + try: self.socket.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1 - ) + ) except socket.error: pass @@ -593,11 +657,13 @@ class dispatcher: # ================================================== def readable(self): + """Predicate to indicate download throttle status""" if maxDownloadRate > 0: return downloadBucket > dispatcher.minTx return True def writable(self): + """Predicate to indicate upload throttle status""" if maxUploadRate > 0: return uploadBucket > dispatcher.minTx return True @@ -607,21 +673,24 @@ class dispatcher: # ================================================== def listen(self, num): + """Listen on a port""" self.accepting = True if os.name == 'nt' and num > 5: num = 5 return self.socket.listen(num) def bind(self, addr): + """Bind to an address""" self.addr = addr return self.socket.bind(addr) def connect(self, address): + """Connect to an address""" self.connected = False self.connecting = True err = self.socket.connect_ex(address) if err in (EINPROGRESS, EALREADY, EWOULDBLOCK, WSAEWOULDBLOCK) \ - or err == EINVAL and os.name in ('nt', 'ce'): + or err == EINVAL and os.name in ('nt', 'ce'): self.addr = address return if err in (0, EISCONN): @@ -631,7 +700,11 @@ class dispatcher: raise socket.error(err, errorcode[err]) def accept(self): - # XXX can return either an address pair or None + """ + Accept incoming connections + + .. todo:: FIXME: can return either an address pair or None + """ try: conn, addr = self.socket.accept() except TypeError: @@ -645,6 +718,7 @@ class dispatcher: return conn, addr def send(self, data): + """Send data""" try: result = self.socket.send(data) return result @@ -658,6 +732,7 @@ class dispatcher: raise def recv(self, buffer_size): + """Receive data""" try: data = self.socket.recv(buffer_size) if not data: @@ -665,8 +740,7 @@ class dispatcher: # a read condition, and having recv() return 0. self.handle_close() return b'' - else: - return data + return data except socket.error as why: # winsock sometimes raises ENOTCONN if why.args[0] in (EAGAIN, EWOULDBLOCK, WSAEWOULDBLOCK): @@ -678,6 +752,7 @@ class dispatcher: raise def close(self): + """Close connection""" self.connected = False self.accepting = False self.connecting = False @@ -695,10 +770,10 @@ class dispatcher: retattr = getattr(self.socket, attr) except AttributeError: raise AttributeError("%s instance has no attribute '%s'" - %(self.__class__.__name__, attr)) + % (self.__class__.__name__, attr)) else: msg = "%(me)s.%(attr)s is deprecated; use %(me)s.socket.%(attr)s " \ - "instead" % {'me' : self.__class__.__name__, 'attr' : attr} + "instead" % {'me': self.__class__.__name__, 'attr': attr} warnings.warn(msg, DeprecationWarning, stacklevel=2) return retattr @@ -707,13 +782,16 @@ class dispatcher: # and 'log_info' is for informational, warning and error logging. def log(self, message): + """Log a message to stderr""" sys.stderr.write('log: %s\n' % str(message)) def log_info(self, message, log_type='info'): + """Conditionally print a message""" if log_type not in self.ignore_log_types: - print('%s: %s' % (log_type, message)) + print '%s: %s' % log_type, message def handle_read_event(self): + """Handle a read event""" if self.accepting: # accepting sockets are never connected, they "spawn" new # sockets that are connected @@ -726,6 +804,7 @@ class dispatcher: self.handle_read() def handle_connect_event(self): + """Handle a connection event""" err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if err != 0: raise socket.error(err, _strerror(err)) @@ -734,6 +813,7 @@ class dispatcher: self.connecting = False def handle_write_event(self): + """Handle a write event""" if self.accepting: # Accepting sockets shouldn't get a write event. # We will pretend it didn't happen. @@ -745,6 +825,7 @@ class dispatcher: self.handle_write() def handle_expt_event(self): + """Handle expected exceptions""" # handle_expt_event() is called if there might be an error on the # socket, or if there is OOB data # check for the error condition first @@ -763,7 +844,8 @@ class dispatcher: self.handle_expt() def handle_error(self): - nil, t, v, tbinfo = compact_traceback() + """Handle unexpected exceptions""" + _, t, v, tbinfo = compact_traceback() # sometimes a user repr method will crash. try: @@ -777,89 +859,110 @@ class dispatcher: t, v, tbinfo - ), + ), 'error' - ) + ) self.handle_close() - def handle_expt(self): - self.log_info('unhandled incoming priority event', 'warning') - - def handle_read(self): - self.log_info('unhandled read event', 'warning') - - def handle_write(self): - self.log_info('unhandled write event', 'warning') - - def handle_connect(self): - self.log_info('unhandled connect event', 'warning') - def handle_accept(self): + """Handle an accept event""" pair = self.accept() if pair is not None: self.handle_accepted(*pair) + def handle_expt(self): + """Log that the subclass does not implement handle_expt""" + self.log_info('unhandled incoming priority event', 'warning') + + def handle_read(self): + """Log that the subclass does not implement handle_read""" + self.log_info('unhandled read event', 'warning') + + def handle_write(self): + """Log that the subclass does not implement handle_write""" + self.log_info('unhandled write event', 'warning') + + def handle_connect(self): + """Log that the subclass does not implement handle_connect""" + self.log_info('unhandled connect event', 'warning') + def handle_accepted(self, sock, addr): + """Log that the subclass does not implement handle_accepted""" sock.close() self.log_info('unhandled accepted event on %s' % (addr), 'warning') def handle_close(self): + """Log that the subclass does not implement handle_close""" self.log_info('unhandled close event', 'warning') self.close() -# --------------------------------------------------------------------------- -# adds simple buffered output capability, useful for simple clients. -# [for more sophisticated usage use asynchat.async_chat] -# --------------------------------------------------------------------------- class dispatcher_with_send(dispatcher): + """ + adds simple buffered output capability, useful for simple clients. + [for more sophisticated usage use asynchat.async_chat] + """ + # pylint: disable=redefined-builtin def __init__(self, sock=None, map=None): + # pylint: disable=redefined-builtin + dispatcher.__init__(self, sock, map) self.out_buffer = b'' def initiate_send(self): + """Initiate a send""" num_sent = 0 num_sent = dispatcher.send(self, self.out_buffer[:512]) self.out_buffer = self.out_buffer[num_sent:] def handle_write(self): + """Handle a write event""" self.initiate_send() def writable(self): - return (not self.connected) or len(self.out_buffer) + """Predicate to indicate if the object is writable""" + return not self.connected or len(self.out_buffer) def send(self, data): + """Send data""" if self.debug: self.log_info('sending %s' % repr(data)) self.out_buffer = self.out_buffer + data self.initiate_send() + # --------------------------------------------------------------------------- # used for debugging. # --------------------------------------------------------------------------- + def compact_traceback(): + """Return a compact traceback""" t, v, tb = sys.exc_info() tbinfo = [] - if not tb: # Must have a traceback + if not tb: # Must have a traceback raise AssertionError("traceback does not exist") while tb: tbinfo.append(( tb.tb_frame.f_code.co_filename, tb.tb_frame.f_code.co_name, str(tb.tb_lineno) - )) + )) tb = tb.tb_next # just to be safe del tb - file, function, line = tbinfo[-1] + filename, function, line = tbinfo[-1] info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo]) - return (file, function, line), t, v, info + return (filename, function, line), t, v, info + def close_all(map=None, ignore_all=False): + """Close all connections""" + # pylint: disable=redefined-builtin + if map is None: map = socket_map for x in list(map.values()): @@ -877,6 +980,7 @@ def close_all(map=None, ignore_all=False): raise map.clear() + # Asynchronous File I/O: # # After a little research (reading man pages on various unixen, and @@ -890,27 +994,34 @@ def close_all(map=None, ignore_all=False): # # Regardless, this is useful for pipes, and stdin/stdout... + if os.name == 'posix': import fcntl class file_wrapper: - # Here we override just enough to make a file - # look like a socket for the purposes of asyncore. - # The passed fd is automatically os.dup()'d + """ + Here we override just enough to make a file look like a socket for the purposes of asyncore. + + The passed fd is automatically os.dup()'d + """ + # pylint: disable=old-style-class def __init__(self, fd): self.fd = os.dup(fd) def recv(self, *args): + """Fake recv()""" return os.read(self.fd, *args) def send(self, *args): + """Fake send()""" return os.write(self.fd, *args) def getsockopt(self, level, optname, buflen=None): + """Fake getsockopt()""" if (level == socket.SOL_SOCKET and - optname == socket.SO_ERROR and - not buflen): + optname == socket.SO_ERROR and + not buflen): return 0 raise NotImplementedError("Only asyncore specific behaviour " "implemented.") @@ -919,14 +1030,19 @@ if os.name == 'posix': write = send def close(self): + """Fake close()""" os.close(self.fd) def fileno(self): + """Fake fileno()""" return self.fd class file_dispatcher(dispatcher): + """A dispatcher for file_wrapper objects""" def __init__(self, fd, map=None): + # pylint: disable=redefined-builtin + dispatcher.__init__(self, None, map) self.connected = True try: @@ -940,6 +1056,7 @@ if os.name == 'posix': fcntl.fcntl(fd, fcntl.F_SETFL, flags) def set_file(self, fd): + """Set file""" self.socket = file_wrapper(fd) self._fileno = self.socket.fileno() self.add_channel() diff --git a/src/network/bmproto.py b/src/network/bmproto.py index 1f1c67bd..72639ba6 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -1,6 +1,12 @@ +# pylint: disable=too-many-return-statements,too-many-public-methods,attribute-defined-outside-init,too-many-branches +# pylint: disable=too-many-instance-attributes,too-many-statements +""" +The Bitmessage Protocol +======================= +""" + import base64 import hashlib -import random import socket import struct import time @@ -12,11 +18,11 @@ import knownnodes from network.advanceddispatcher import AdvancedDispatcher from network.dandelion import Dandelion from network.bmobject import BMObject, BMObjectInsufficientPOWError, BMObjectInvalidDataError, \ - BMObjectExpiredError, BMObjectUnwantedStreamError, BMObjectInvalidError, BMObjectAlreadyHaveError + BMObjectExpiredError, BMObjectUnwantedStreamError, BMObjectInvalidError, BMObjectAlreadyHaveError import network.connectionpool from network.node import Node from network.objectracker import ObjectTracker -from network.proxy import Proxy, ProxyError, GeneralProxyError +from network.proxy import ProxyError import addresses from queues import objectProcessorQueue, portCheckerQueue, invQueue, addrQueue @@ -25,19 +31,27 @@ import state import protocol import helper_random + class BMProtoError(ProxyError): + """A Bitmessage Protocol Base Error""" errorCodes = ("Protocol error") class BMProtoInsufficientDataError(BMProtoError): + """A Bitmessage Protocol Insufficient Data Error""" + errorCodes = ("Insufficient data") class BMProtoExcessiveDataError(BMProtoError): + """A Bitmessage Protocol Excessive Data Error""" + errorCodes = ("Too much data") class BMProto(AdvancedDispatcher, ObjectTracker): + """A parser for the Bitmessage Protocol""" + # ~1.6 MB which is the maximum possible size of an inv message. maxMessageSize = 1600100 # 2**18 = 256kB is the maximum size of an object payload @@ -52,12 +66,15 @@ class BMProto(AdvancedDispatcher, ObjectTracker): maxTimeOffset = 3600 def __init__(self, address=None, sock=None): + # pylint: disable=super-init-not-called,unused-argument + AdvancedDispatcher.__init__(self, sock) self.isOutbound = False # packet/connection from a local IP self.local = False def bm_proto_reset(self): + """TBC""" self.magic = None self.command = None self.payloadLength = 0 @@ -69,7 +86,10 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.object = None def state_bm_header(self): - self.magic, self.command, self.payloadLength, self.checksum = protocol.Header.unpack(self.read_buf[:protocol.Header.size]) + """Predicate to indicate the prescence of a header""" + + self.magic, self.command, self.payloadLength, self.checksum = protocol.Header.unpack( + self.read_buf[:protocol.Header.size]) self.command = self.command.rstrip('\x00') if self.magic != 0xE9BEB4D9: # skip 1 byte in order to sync @@ -84,8 +104,9 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.invalid = True self.set_state("bm_command", length=protocol.Header.size, expectBytes=self.payloadLength) return True - + def state_bm_command(self): + """TBC""" self.payload = self.read_buf[:self.payloadLength] if self.checksum != hashlib.sha512(self.payload).digest()[0:4]: logger.debug("Bad checksum, ignoring") @@ -122,7 +143,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): # broken read, ignore pass else: - #print "Skipping command %s due to invalid data" % (self.command) + # print "Skipping command %s due to invalid data" % (self.command) logger.debug("Closing due to invalid command %s", self.command) self.close_reason = "Invalid command %s" % (self.command) self.set_state("close") @@ -134,16 +155,21 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return True def decode_payload_string(self, length): - value = self.payload[self.payloadOffset:self.payloadOffset+length] + """Read and return `length` bytes from payload""" + + value = self.payload[self.payloadOffset:self.payloadOffset + length] self.payloadOffset += length return value def decode_payload_varint(self): + """Decode a varint from the payload""" + value, offset = addresses.decodeVarint(self.payload[self.payloadOffset:]) self.payloadOffset += offset return value def decode_payload_node(self): + """Decode node details from the payload""" services, host, port = self.decode_payload_content("Q16sH") if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF': host = socket.inet_ntop(socket.AF_INET, str(host[12:16])) @@ -153,38 +179,45 @@ class BMProto(AdvancedDispatcher, ObjectTracker): else: host = socket.inet_ntop(socket.AF_INET6, str(host)) if host == "": - # This can happen on Windows systems which are not 64-bit compatible - # so let us drop the IPv6 address. + # This can happen on Windows systems which are not 64-bit compatible + # so let us drop the IPv6 address. host = socket.inet_ntop(socket.AF_INET, str(host[12:16])) return Node(services, host, port) - def decode_payload_content(self, pattern = "v"): - # L = varint indicating the length of the next array - # l = varint indicating the length of the next item - # v = varint (or array) - # H = uint16 - # I = uint32 - # Q = uint64 - # i = net_addr (without time and stream number) - # s = string - # 0-9 = length of the next item - # , = end of array + def decode_payload_content(self, pattern="v"): + """ + Decode the payload + + L = varint indicating the length of the next array + l = varint indicating the length of the next item + v = varint (or array) + H = uint16 + I = uint32 + Q = uint64 + i = net_addr (without time and stream number) + s = string + 0-9 = length of the next item + , = end of array + + """ def decode_simple(self, char="v"): + """Some expected objects can be decoded very straightforwardly""" if char == "v": return self.decode_payload_varint() if char == "i": return self.decode_payload_node() if char == "H": self.payloadOffset += 2 - return struct.unpack(">H", self.payload[self.payloadOffset-2:self.payloadOffset])[0] + return struct.unpack(">H", self.payload[self.payloadOffset - 2:self.payloadOffset])[0] if char == "I": self.payloadOffset += 4 - return struct.unpack(">I", self.payload[self.payloadOffset-4:self.payloadOffset])[0] + return struct.unpack(">I", self.payload[self.payloadOffset - 4:self.payloadOffset])[0] if char == "Q": self.payloadOffset += 8 - return struct.unpack(">Q", self.payload[self.payloadOffset-8:self.payloadOffset])[0] + return struct.unpack(">Q", self.payload[self.payloadOffset - 8:self.payloadOffset])[0] + return None size = None isArray = False @@ -197,27 +230,19 @@ class BMProto(AdvancedDispatcher, ObjectTracker): # retval (array) parserStack = [[1, 1, False, pattern, 0, []]] - #try: - # sys._getframe(200) - # logger.error("Stack depth warning, pattern: %s", pattern) - # return - #except ValueError: - # pass - while True: i = parserStack[-1][3][parserStack[-1][4]] - if i in "0123456789" and (size is None or parserStack[-1][3][parserStack[-1][4]-1] not in "lL"): + if i in "0123456789" and (size is None or parserStack[-1][3][parserStack[-1][4] - 1] not in "lL"): try: size = size * 10 + int(i) except TypeError: size = int(i) isArray = False + elif i in "Ll" and size is None: size = self.decode_payload_varint() - if i == "L": - isArray = True - else: - isArray = False + isArray = bool(i == "L") + elif size is not None: if isArray: parserStack.append([size, size, isArray, parserStack[-1][3][parserStack[-1][4]:], 0, []]) @@ -226,25 +251,26 @@ class BMProto(AdvancedDispatcher, ObjectTracker): for j in range(parserStack[-1][4], len(parserStack[-1][3])): if parserStack[-1][3][j] not in "lL0123456789": break - parserStack.append([size, size, isArray, parserStack[-1][3][parserStack[-1][4]:j+1], 0, []]) + # pylint: disable=undefined-loop-variable + parserStack.append([size, size, isArray, parserStack[-1][3][parserStack[-1][4]:j + 1], 0, []]) parserStack[-2][4] += len(parserStack[-1][3]) - 1 size = None continue + elif i == "s": - #if parserStack[-2][2]: - # parserStack[-1][5].append(self.payload[self.payloadOffset:self.payloadOffset + parserStack[-1][0]]) - #else: parserStack[-1][5] = self.payload[self.payloadOffset:self.payloadOffset + parserStack[-1][0]] self.payloadOffset += parserStack[-1][0] parserStack[-1][1] = 0 parserStack[-1][2] = True - #del parserStack[-1] size = None + elif i in "viHIQ": parserStack[-1][5].append(decode_simple(self, parserStack[-1][3][parserStack[-1][4]])) size = None + else: size = None + for depth in range(len(parserStack) - 1, -1, -1): parserStack[depth][4] += 1 if parserStack[depth][4] >= len(parserStack[depth][3]): @@ -269,16 +295,19 @@ class BMProto(AdvancedDispatcher, ObjectTracker): raise BMProtoInsufficientDataError() def bm_command_error(self): + """TBC""" + # pylint: disable=unused-variable fatalStatus, banTime, inventoryVector, errorText = self.decode_payload_content("vvlsls") logger.error("%s:%i error: %i, %s", self.destination.host, self.destination.port, fatalStatus, errorText) return True def bm_command_getdata(self): + """TBC""" items = self.decode_payload_content("l32s") - # skip? + # ..todo:: skip? if time.time() < self.skipUntil: return True - #TODO make this more asynchronous + # .. todo:: make this more asynchronous helper_random.randomshuffle(items) for i in map(str, items): if Dandelion().hasHash(i) and \ @@ -320,6 +349,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return True def bm_command_inv(self): + """TBC""" return self._command_inv(False) def bm_command_dinv(self): @@ -329,12 +359,16 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return self._command_inv(True) def bm_command_object(self): + """TBC""" objectOffset = self.payloadOffset nonce, expiresTime, objectType, version, streamNumber = self.decode_payload_content("QQIvv") self.object = BMObject(nonce, expiresTime, objectType, version, streamNumber, self.payload, self.payloadOffset) if len(self.payload) - self.payloadOffset > BMProto.maxObjectPayloadSize: - logger.info('The payload length of this object is too large (%d bytes). Ignoring it.' % (len(self.payload) - self.payloadOffset)) + logger.info( + 'The payload length of this object is too large (%d bytes). Ignoring it.', + len(self.payload) - self.payloadOffset + ) raise BMProtoExcessiveDataError() try: @@ -347,7 +381,9 @@ class BMProto(AdvancedDispatcher, ObjectTracker): try: self.object.checkStream() except (BMObjectUnwantedStreamError,) as e: - BMProto.stopDownloadingObject(self.object.inventoryHash, BMConfigParser().get("inventory", "acceptmismatch")) + BMProto.stopDownloadingObject( + self.object.inventoryHash, BMConfigParser().get( + "inventory", "acceptmismatch")) if not BMConfigParser().get("inventory", "acceptmismatch"): raise e @@ -366,7 +402,10 @@ class BMProto(AdvancedDispatcher, ObjectTracker): Dandelion().removeHash(self.object.inventoryHash, "cycle detection") Inventory()[self.object.inventoryHash] = ( - self.object.objectType, self.object.streamNumber, buffer(self.payload[objectOffset:]), self.object.expiresTime, buffer(self.object.tag)) + self.object.objectType, self.object.streamNumber, + buffer(self.payload[objectOffset:]), self.object.expiresTime, + buffer(self.object.tag) + ) self.handleReceivedObject(self.object.streamNumber, self.object.inventoryHash) invQueue.put((self.object.streamNumber, self.object.inventoryHash, self.destination)) return True @@ -375,9 +414,10 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return self.decode_payload_content("LQIQ16sH") def bm_command_addr(self): - addresses = self._decode_addr() - for i in addresses: - seenTime, stream, services, ip, port = i + """TBC""" + these_addresses = self._decode_addr() + for i in these_addresses: + seenTime, stream, _, ip, port = i decodedIP = protocol.checkIPAddress(str(ip)) if stream not in state.streamsInWhichIAmParticipating: continue @@ -402,18 +442,23 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return True def bm_command_portcheck(self): + """TBC""" portCheckerQueue.put(state.Peer(self.destination, self.peerNode.port)) return True def bm_command_ping(self): + """TBC""" self.append_write_buf(protocol.CreatePacket('pong')) return True def bm_command_pong(self): - # nothing really + """noop""" + # pylint: disable=no-self-use return True def bm_command_verack(self): + """TBC""" + self.verackReceived = True if self.verackSent: if self.isSSL: @@ -424,6 +469,8 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return True def bm_command_version(self): + """TBC""" + self.remoteProtocolVersion, self.services, self.timestamp, self.sockNode, self.peerNode, self.nonce, \ self.userAgent, self.streams = self.decode_payload_content("IQQiiQlsLv") self.nonce = struct.pack('>Q', self.nonce) @@ -434,17 +481,20 @@ class BMProto(AdvancedDispatcher, ObjectTracker): logger.debug("my external IP: %s", self.sockNode.host) logger.debug("remote node incoming address: %s:%i", self.destination.host, self.peerNode.port) logger.debug("user agent: %s", self.userAgent) - logger.debug("streams: [%s]", ",".join(map(str,self.streams))) + logger.debug("streams: [%s]", ",".join(map(str, self.streams))) if not self.peerValidityChecks(): - # TODO ABORT + # .. todo:: ABORT return True - #shared.connectedHostsList[self.destination] = self.streams[0] self.append_write_buf(protocol.CreatePacket('verack')) self.verackSent = True if not self.isOutbound: - self.append_write_buf(protocol.assembleVersionMessage(self.destination.host, self.destination.port, \ - network.connectionpool.BMConnectionPool().streams, True, nodeid=self.nodeid)) - #print "%s:%i: Sending version" % (self.destination.host, self.destination.port) + self.append_write_buf( + protocol.assembleVersionMessage( + self.destination.host, + self.destination.port, + network.connectionpool.BMConnectionPool().streams, + True, + nodeid=self.nodeid)) if ((self.services & protocol.NODE_SSL == protocol.NODE_SSL) and protocol.haveSSL(not self.isOutbound)): self.isSSL = True @@ -457,41 +507,45 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return True def peerValidityChecks(self): + """Check the validity of peers""" if self.remoteProtocolVersion < 3: - self.append_write_buf(protocol.assembleErrorMessage(fatal=2, - errorText="Your is using an old protocol. Closing connection.")) - logger.debug ('Closing connection to old protocol version %s, node: %s', - str(self.remoteProtocolVersion), str(self.destination)) + self.append_write_buf(protocol.assembleErrorMessage( + fatal=2, errorText="Your is using an old protocol. Closing connection.")) + logger.debug('Closing connection to old protocol version %s, node: %s', + str(self.remoteProtocolVersion), str(self.destination)) return False if self.timeOffset > BMProto.maxTimeOffset: - self.append_write_buf(protocol.assembleErrorMessage(fatal=2, - errorText="Your time is too far in the future compared to mine. Closing connection.")) + self.append_write_buf( + protocol.assembleErrorMessage( + fatal=2, + errorText="Your time is too far in the future compared to mine. Closing connection.")) logger.info("%s's time is too far in the future (%s seconds). Closing connection to it.", - self.destination, self.timeOffset) + self.destination, self.timeOffset) shared.timeOffsetWrongCount += 1 return False elif self.timeOffset < -BMProto.maxTimeOffset: - self.append_write_buf(protocol.assembleErrorMessage(fatal=2, - errorText="Your time is too far in the past compared to mine. Closing connection.")) + self.append_write_buf(protocol.assembleErrorMessage( + fatal=2, errorText="Your time is too far in the past compared to mine. Closing connection.")) logger.info("%s's time is too far in the past (timeOffset %s seconds). Closing connection to it.", - self.destination, self.timeOffset) + self.destination, self.timeOffset) shared.timeOffsetWrongCount += 1 return False else: shared.timeOffsetWrongCount = 0 if not self.streams: - self.append_write_buf(protocol.assembleErrorMessage(fatal=2, - errorText="We don't have shared stream interests. Closing connection.")) - logger.debug ('Closed connection to %s because there is no overlapping interest in streams.', - str(self.destination)) + self.append_write_buf(protocol.assembleErrorMessage( + fatal=2, errorText="We don't have shared stream interests. Closing connection.")) + logger.debug('Closed connection to %s because there is no overlapping interest in streams.', + str(self.destination)) return False if self.destination in network.connectionpool.BMConnectionPool().inboundConnections: try: if not protocol.checkSocksIP(self.destination.host): - self.append_write_buf(protocol.assembleErrorMessage(fatal=2, - errorText="Too many connections from your IP. Closing connection.")) - logger.debug ('Closed connection to %s because we are already connected to that IP.', - str(self.destination)) + self.append_write_buf( + protocol.assembleErrorMessage( + fatal=2, errorText="Too many connections from your IP. Closing connection.")) + logger.debug('Closed connection to %s because we are already connected to that IP.', + str(self.destination)) return False except: pass @@ -499,27 +553,30 @@ class BMProto(AdvancedDispatcher, ObjectTracker): # incoming from a peer we're connected to as outbound, or server full # report the same error to counter deanonymisation if state.Peer(self.destination.host, self.peerNode.port) in \ - network.connectionpool.BMConnectionPool().inboundConnections or \ - len(network.connectionpool.BMConnectionPool().inboundConnections) + \ - len(network.connectionpool.BMConnectionPool().outboundConnections) > \ - BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections") + \ - BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections"): + network.connectionpool.BMConnectionPool().inboundConnections or \ + len(network.connectionpool.BMConnectionPool().inboundConnections) + \ + len(network.connectionpool.BMConnectionPool().outboundConnections) > \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections") + \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections"): self.append_write_buf(protocol.assembleErrorMessage(fatal=2, - errorText="Server full, please try again later.")) - logger.debug ("Closed connection to %s due to server full or duplicate inbound/outbound.", - str(self.destination)) + errorText="Server full, please try again later.")) + logger.debug("Closed connection to %s due to server full or duplicate inbound/outbound.", + str(self.destination)) return False if network.connectionpool.BMConnectionPool().isAlreadyConnected(self.nonce): - self.append_write_buf(protocol.assembleErrorMessage(fatal=2, - errorText="I'm connected to myself. Closing connection.")) - logger.debug ("Closed connection to %s because I'm connected to myself.", - str(self.destination)) + self.append_write_buf( + protocol.assembleErrorMessage( + fatal=2, + errorText="I'm connected to myself. Closing connection.")) + logger.debug("Closed connection to %s because I'm connected to myself.", + str(self.destination)) return False return True @staticmethod def assembleAddr(peerList): + """iBuild up a packed address""" if isinstance(peerList, state.Peer): peerList = (peerList) if not peerList: @@ -541,6 +598,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): @staticmethod def stopDownloadingObject(hashId, forwardAnyway=False): + """Stop downloading an object""" for connection in network.connectionpool.BMConnectionPool().inboundConnections.values() + \ network.connectionpool.BMConnectionPool().outboundConnections.values(): try: @@ -559,6 +617,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): pass def handle_close(self): + """Handle close""" self.set_state("close") if not (self.accepting or self.connecting or self.connected): # already disconnected diff --git a/src/network/tcp.py b/src/network/tcp.py index 6cba566f..8aa905fe 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -1,41 +1,45 @@ -import base64 -from binascii import hexlify -import hashlib +# pylint: disable=too-many-ancestors +""" +tcp.py +====== +""" + +from __future__ import absolute_import + import math import time -from pprint import pprint import socket -import struct import random -import traceback -from addresses import calculateInventoryHash from debug import logger from helper_random import randomBytes import helper_random from inventory import Inventory import knownnodes from network.advanceddispatcher import AdvancedDispatcher -from network.bmproto import BMProtoError, BMProtoInsufficientDataError, BMProtoExcessiveDataError, BMProto -from network.bmobject import BMObject, BMObjectInsufficientPOWError, BMObjectInvalidDataError, BMObjectExpiredError, BMObjectUnwantedStreamError, BMObjectInvalidError, BMObjectAlreadyHaveError + +from network.bmproto import BMProto import network.connectionpool from network.dandelion import Dandelion -from network.node import Node import network.asyncore_pollchoose as asyncore -from network.proxy import Proxy, ProxyError, GeneralProxyError from network.objectracker import ObjectTracker -from network.socks5 import Socks5Connection, Socks5Resolver, Socks5AuthError, Socks5Error -from network.socks4a import Socks4aConnection, Socks4aResolver, Socks4aError +from network.socks5 import Socks5Connection +from network.socks4a import Socks4aConnection from network.tls import TLSDispatcher - import addresses from bmconfigparser import BMConfigParser -from queues import invQueue, objectProcessorQueue, portCheckerQueue, UISignalQueue, receiveDataQueue +from queues import invQueue, UISignalQueue, receiveDataQueue import shared import state import protocol -class TCPConnection(BMProto, TLSDispatcher): + +class TCPConnection(BMProto, TLSDispatcher): # pylint: disable=too-many-instance-attributes + """ + + .. todo:: Look to understand and/or fix the non-parent-init-called + """ + def __init__(self, address=None, sock=None): BMProto.__init__(self, address=address, sock=sock) self.verackReceived = False @@ -67,18 +71,19 @@ class TCPConnection(BMProto, TLSDispatcher): self.connect(self.destination) logger.debug("Connecting to %s:%i", self.destination.host, self.destination.port) encodedAddr = protocol.encodeHost(self.destination.host) - if protocol.checkIPAddress(encodedAddr, True) and not protocol.checkSocksIP(self.destination.host): - self.local = True - else: - self.local = False - #shared.connectedHostsList[self.destination] = 0 - ObjectTracker.__init__(self) + self.local = all([ + protocol.checkIPAddress(encodedAddr, True), + not protocol.checkSocksIP(self.destination.host) + ]) + ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called self.bm_proto_reset() self.set_state("bm_header", expectBytes=protocol.Header.size) - def antiIntersectionDelay(self, initial = False): + def antiIntersectionDelay(self, initial=False): + """TBC""" # estimated time for a small object to propagate across the whole network - delay = math.ceil(math.log(max(len(knownnodes.knownNodes[x]) for x in knownnodes.knownNodes) + 2, 20)) * (0.2 + invQueue.queueCount/2.0) + max_known_nodes = max(len(knownnodes.knownNodes[x]) for x in knownnodes.knownNodes) + delay = math.ceil(math.log(max_known_nodes + 2, 20)) * (0.2 + invQueue.queueCount / 2.0) # take the stream with maximum amount of nodes # +2 is to avoid problems with log(0) and log(1) # 20 is avg connected nodes count @@ -93,12 +98,14 @@ class TCPConnection(BMProto, TLSDispatcher): self.skipUntil = time.time() + delay def state_connection_fully_established(self): + """TBC""" self.set_connection_fully_established() self.set_state("bm_header") self.bm_proto_reset() return True def set_connection_fully_established(self): + """TBC""" if not self.isOutbound and not self.local: shared.clientHasReceivedIncomingConnections = True UISignalQueue.put(('setStatusIcon', 'green')) @@ -113,50 +120,50 @@ class TCPConnection(BMProto, TLSDispatcher): self.sendBigInv() def sendAddr(self): + """TBC""" # We are going to share a maximum number of 1000 addrs (per overlapping # stream) with our peer. 500 from overlapping streams, 250 from the # left child stream, and 250 from the right child stream. maxAddrCount = BMConfigParser().safeGetInt("bitmessagesettings", "maxaddrperstreamsend", 500) # init - addressCount = 0 - payload = b'' - templist = [] addrs = {} for stream in self.streams: with knownnodes.knownNodesLock: - if len(knownnodes.knownNodes[stream]) > 0: + if knownnodes.knownNodes[stream]: filtered = {k: v for k, v in knownnodes.knownNodes[stream].items() - if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} + if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} elemCount = len(filtered) if elemCount > maxAddrCount: elemCount = maxAddrCount # only if more recent than 3 hours addrs[stream] = helper_random.randomsample(filtered.items(), elemCount) # sent 250 only if the remote isn't interested in it - if len(knownnodes.knownNodes[stream * 2]) > 0 and stream not in self.streams: - filtered = {k: v for k, v in knownnodes.knownNodes[stream*2].items() - if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} + if knownnodes.knownNodes[stream * 2] and stream not in self.streams: + filtered = {k: v for k, v in knownnodes.knownNodes[stream * 2].items() + if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} elemCount = len(filtered) if elemCount > maxAddrCount / 2: elemCount = int(maxAddrCount / 2) addrs[stream * 2] = helper_random.randomsample(filtered.items(), elemCount) - if len(knownnodes.knownNodes[(stream * 2) + 1]) > 0 and stream not in self.streams: - filtered = {k: v for k, v in knownnodes.knownNodes[stream*2+1].items() - if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} + if knownnodes.knownNodes[(stream * 2) + 1] and stream not in self.streams: + filtered = {k: v for k, v in knownnodes.knownNodes[stream * 2 + 1].items() + if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} elemCount = len(filtered) if elemCount > maxAddrCount / 2: elemCount = int(maxAddrCount / 2) addrs[stream * 2 + 1] = helper_random.randomsample(filtered.items(), elemCount) - for substream in addrs.keys(): + for substream in addrs: for peer, params in addrs[substream]: templist.append((substream, peer, params["lastseen"])) - if len(templist) > 0: + if templist: self.append_write_buf(BMProto.assembleAddr(templist)) def sendBigInv(self): + """TBC""" def sendChunk(): + """TBC""" if objectCount == 0: return logger.debug('Sending huge inv message with %i objects to just this one peer', objectCount) @@ -172,13 +179,12 @@ class TCPConnection(BMProto, TLSDispatcher): if Dandelion().hasHash(objHash): continue bigInvList[objHash] = 0 - #self.objectsNewToThem[objHash] = time.time() objectCount = 0 payload = b'' # Now let us start appending all of these hashes together. They will be # sent out in a big inv message to our new peer. - for hash, storedValue in bigInvList.items(): - payload += hash + for obj_hash, _ in bigInvList.items(): + payload += obj_hash objectCount += 1 # Remove -1 below when sufficient time has passed for users to @@ -193,20 +199,26 @@ class TCPConnection(BMProto, TLSDispatcher): sendChunk() def handle_connect(self): + """TBC""" try: AdvancedDispatcher.handle_connect(self) except socket.error as e: - if e.errno in asyncore._DISCONNECTED: - logger.debug("%s:%i: Connection failed: %s" % (self.destination.host, self.destination.port, str(e))) + if e.errno in asyncore._DISCONNECTED: # pylint: disable=protected-access + logger.debug("%s:%i: Connection failed: %s", self.destination.host, self.destination.port, str(e)) return self.nodeid = randomBytes(8) - self.append_write_buf(protocol.assembleVersionMessage(self.destination.host, self.destination.port, \ - network.connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid)) - #print "%s:%i: Sending version" % (self.destination.host, self.destination.port) + self.append_write_buf( + protocol.assembleVersionMessage( + self.destination.host, + self.destination.port, + network.connectionpool.BMConnectionPool().streams, + False, + nodeid=self.nodeid)) self.connectedAt = time.time() receiveDataQueue.put(self.destination) def handle_read(self): + """TBC""" TLSDispatcher.handle_read(self) if self.isOutbound and self.fullyEstablished: for s in self.streams: @@ -218,9 +230,11 @@ class TCPConnection(BMProto, TLSDispatcher): receiveDataQueue.put(self.destination) def handle_write(self): + """TBC""" TLSDispatcher.handle_write(self) def handle_close(self): + """TBC""" if self.isOutbound and not self.fullyEstablished: knownnodes.decreaseRating(self.destination) if self.fullyEstablished: @@ -231,37 +245,55 @@ class TCPConnection(BMProto, TLSDispatcher): class Socks5BMConnection(Socks5Connection, TCPConnection): + """TBC""" + def __init__(self, address): Socks5Connection.__init__(self, address=address) TCPConnection.__init__(self, address=address, sock=self.socket) self.set_state("init") def state_proxy_handshake_done(self): + """TBC""" Socks5Connection.state_proxy_handshake_done(self) self.nodeid = randomBytes(8) - self.append_write_buf(protocol.assembleVersionMessage(self.destination.host, self.destination.port, \ - network.connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid)) + self.append_write_buf( + protocol.assembleVersionMessage( + self.destination.host, + self.destination.port, + network.connectionpool.BMConnectionPool().streams, + False, + nodeid=self.nodeid)) self.set_state("bm_header", expectBytes=protocol.Header.size) return True class Socks4aBMConnection(Socks4aConnection, TCPConnection): + """TBC""" + def __init__(self, address): Socks4aConnection.__init__(self, address=address) TCPConnection.__init__(self, address=address, sock=self.socket) self.set_state("init") def state_proxy_handshake_done(self): + """TBC""" Socks4aConnection.state_proxy_handshake_done(self) self.nodeid = randomBytes(8) - self.append_write_buf(protocol.assembleVersionMessage(self.destination.host, self.destination.port, \ - network.connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid)) + self.append_write_buf( + protocol.assembleVersionMessage( + self.destination.host, + self.destination.port, + network.connectionpool.BMConnectionPool().streams, + False, + nodeid=self.nodeid)) self.set_state("bm_header", expectBytes=protocol.Header.size) return True class TCPServer(AdvancedDispatcher): - def __init__(self, host='127.0.0.1', port=8444): + """TBC""" + + def __init__(self, host='127.0.0.1', port=8444): # pylint: disable=redefined-outer-name if not hasattr(self, '_map'): AdvancedDispatcher.__init__(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) @@ -284,20 +316,22 @@ class TCPServer(AdvancedDispatcher): self.listen(5) def is_bound(self): + """TBC""" try: return self.bound except AttributeError: return False def handle_accept(self): + """TBC""" pair = self.accept() if pair is not None: - sock, addr = pair + sock, _ = pair state.ownAddresses[state.Peer(sock.getsockname()[0], sock.getsockname()[1])] = True if len(network.connectionpool.BMConnectionPool().inboundConnections) + \ - len(network.connectionpool.BMConnectionPool().outboundConnections) > \ - BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections") + \ - BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections") + 10: + len(network.connectionpool.BMConnectionPool().outboundConnections) > \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections") + \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections") + 10: # 10 is a sort of buffer, in between it will go through the version handshake # and return an error to the peer logger.warning("Server full, dropping connection") @@ -314,17 +348,7 @@ if __name__ == "__main__": for host in (("127.0.0.1", 8448),): direct = TCPConnection(host) - while len(asyncore.socket_map) > 0: + while asyncore.socket_map: print "loop, state = %s" % (direct.state) asyncore.loop(timeout=10, count=1) continue - - proxy = Socks5BMConnection(host) - while len(asyncore.socket_map) > 0: -# print "loop, state = %s" % (proxy.state) - asyncore.loop(timeout=10, count=1) - - proxy = Socks4aBMConnection(host) - while len(asyncore.socket_map) > 0: -# print "loop, state = %s" % (proxy.state) - asyncore.loop(timeout=10, count=1) diff --git a/src/proofofwork.py b/src/proofofwork.py index df6ed295..aeb782c8 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -1,60 +1,74 @@ -#import shared -#import time -#from multiprocessing import Pool, cpu_count +# pylint: disable=too-many-branches,too-many-statements,protected-access +""" +proofofwork.py +============== +""" + +from __future__ import absolute_import + +import ctypes import hashlib -from struct import unpack, pack -from subprocess import call +import os import sys import time -from bmconfigparser import BMConfigParser -from debug import logger +from struct import unpack, pack +from subprocess import call + import paths import openclpow import queues import tr -import os -import ctypes - import state +from bmconfigparser import BMConfigParser +from debug import logger + bitmsglib = 'bitmsghash.so' - bmpow = None + def _set_idle(): if 'linux' in sys.platform: os.nice(20) else: try: + # pylint: disable=no-member,import-error sys.getwindowsversion() - import win32api,win32process,win32con # @UnresolvedImport + import win32api + import win32process + import win32con # @UnresolvedImport pid = win32api.GetCurrentProcessId() handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, pid) win32process.SetPriorityClass(handle, win32process.IDLE_PRIORITY_CLASS) except: - #Windows 64-bit + # Windows 64-bit pass + def _pool_worker(nonce, initialHash, target, pool_size): _set_idle() trialValue = float('inf') while trialValue > target: nonce += pool_size - trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) + trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( + pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) return [trialValue, nonce] + def _doSafePoW(target, initialHash): logger.debug("Safe PoW start") nonce = 0 trialValue = float('inf') while trialValue > target and state.shutdown == 0: nonce += 1 - trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) + trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( + pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) if state.shutdown != 0: - raise StopIteration("Interrupted") + raise StopIteration("Interrupted") # pylint: misplaced-bare-raise logger.debug("Safe PoW done") return [trialValue, nonce] + def _doFastPoW(target, initialHash): logger.debug("Fast PoW start") from multiprocessing import Pool, cpu_count @@ -96,7 +110,8 @@ def _doFastPoW(target, initialHash): logger.debug("Fast PoW done") return result[0], result[1] time.sleep(0.2) - + + def _doCPoW(target, initialHash): h = initialHash m = target @@ -104,33 +119,52 @@ def _doCPoW(target, initialHash): out_m = ctypes.c_ulonglong(m) logger.debug("C PoW start") nonce = bmpow(out_h, out_m) - trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) + trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) if state.shutdown != 0: raise StopIteration("Interrupted") logger.debug("C PoW done") return [trialValue, nonce] + def _doGPUPoW(target, initialHash): logger.debug("GPU PoW start") nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target) - trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) - #print "{} - value {} < {}".format(nonce, trialValue, target) + trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) if trialValue > target: deviceNames = ", ".join(gpu.name for gpu in openclpow.enabledGpus) - queues.UISignalQueue.put(('updateStatusBar', (tr._translate("MainWindow",'Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.'), 1))) - logger.error("Your GPUs (%s) did not calculate correctly, disabling OpenCL. Please report to the developers.", deviceNames) + queues.UISignalQueue.put( + ( + 'updateStatusBar', + ( + tr._translate( + "MainWindow", + 'Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.' + ), + 1 + ) + ) + ) + logger.error( + "Your GPUs (%s) did not calculate correctly, disabling OpenCL. Please report to the developers.", + deviceNames) openclpow.enabledGpus = [] raise Exception("GPU did not calculate correctly.") if state.shutdown != 0: raise StopIteration("Interrupted") logger.debug("GPU PoW done") return [trialValue, nonce] - -def estimate(difficulty, format = False): + + +def estimate(difficulty, format=False): # pylint: disable=redefined-builtin + """ + .. todo: fix unused variable + """ ret = difficulty / 10 if ret < 1: ret = 1 + if format: + # pylint: disable=unused-variable out = str(int(ret)) + " seconds" if ret > 60: ret /= 60 @@ -148,25 +182,46 @@ def estimate(difficulty, format = False): if ret > 366: ret /= 366 out = str(int(ret)) + " years" - else: - return ret + ret = None # Ensure legacy behaviour + + return ret + def getPowType(): + """Get the proof of work implementation""" + if openclpow.openclEnabled(): return "OpenCL" if bmpow: return "C" return "python" + def notifyBuild(tried=False): + """TBC""" + if bmpow: - queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "C PoW module built successfully."), 1))) + queues.UISignalQueue.put(('updateStatusBar', (tr._translate( + "proofofwork", "C PoW module built successfully."), 1))) elif tried: - queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "Failed to build C PoW module. Please build it manually."), 1))) + queues.UISignalQueue.put( + ( + 'updateStatusBar', ( + tr._translate( + "proofofwork", + "Failed to build C PoW module. Please build it manually." + ), + 1 + ) + ) + ) else: - queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "C PoW module unavailable. Please build it."), 1))) + queues.UISignalQueue.put(('updateStatusBar', (tr._translate( + "proofofwork", "C PoW module unavailable. Please build it."), 1))) + def buildCPoW(): + """TBC""" if bmpow is not None: return if paths.frozen is not None: @@ -190,29 +245,27 @@ def buildCPoW(): except: notifyBuild(True) + def run(target, initialHash): + """Run the proof of work thread""" + if state.shutdown != 0: raise target = int(target) if openclpow.openclEnabled(): -# trialvalue1, nonce1 = _doGPUPoW(target, initialHash) -# trialvalue, nonce = _doFastPoW(target, initialHash) -# print "GPU: %s, %s" % (trialvalue1, nonce1) -# print "Fast: %s, %s" % (trialvalue, nonce) -# return [trialvalue, nonce] try: return _doGPUPoW(target, initialHash) except StopIteration: raise except: - pass # fallback + pass # fallback if bmpow: try: return _doCPoW(target, initialHash) except StopIteration: raise except: - pass # fallback + pass # fallback if paths.frozen == "macosx_app" or not paths.frozen: # on my (Peter Surda) Windows 10, Windows Defender # does not like this and fights with PyBitmessage @@ -225,24 +278,30 @@ def run(target, initialHash): raise except: logger.error("Fast PoW got exception:", exc_info=True) - pass #fallback try: return _doSafePoW(target, initialHash) except StopIteration: raise except: - pass #fallback + pass # fallback + def resetPoW(): + """TBC""" openclpow.initCL() + # init + + def init(): - global bitmsglib, bso, bmpow + """TBC""" + # pylint: disable=global-statement + global bitmsglib, bmpow openclpow.initCL() - if "win32" == sys.platform: + if sys.platform == "win32": if ctypes.sizeof(ctypes.c_voidp) == 4: bitmsglib = 'bitmsghash32.dll' else: diff --git a/src/socks/__init__.py b/src/socks/__init__.py index 7fd2cba3..e478e9ab 100644 --- a/src/socks/__init__.py +++ b/src/socks/__init__.py @@ -1,4 +1,6 @@ -"""SocksiPy - Python SOCKS module. +# pylint: disable=too-many-arguments,global-statement,too-many-branches +""" +SocksiPy - Python SOCKS module. Version 1.00 Copyright 2006 Dan-Haim. All rights reserved. @@ -29,10 +31,6 @@ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. This module provides a standard socket-like interface for Python for tunneling connections through SOCKS proxies. -""" - -""" - Minor modifications made by Christopher Gilbert (http://motomastyle.com/) for use in PyLoris (http://pyloris.sourceforge.net/) @@ -43,7 +41,7 @@ mainly to merge bug fixes found in Sourceforge import socket import struct -import sys + PROXY_TYPE_SOCKS4 = 1 PROXY_TYPE_SOCKS5 = 2 @@ -52,46 +50,71 @@ PROXY_TYPE_HTTP = 3 _defaultproxy = None _orgsocket = socket.socket -class ProxyError(Exception): pass -class GeneralProxyError(ProxyError): pass -class Socks5AuthError(ProxyError): pass -class Socks5Error(ProxyError): pass -class Socks4Error(ProxyError): pass -class HTTPError(ProxyError): pass + +class ProxyError(Exception): + """Base class for other ProxyErrors""" + pass + + +class GeneralProxyError(ProxyError): + """Handle a general proxy error""" + pass + + +class Socks5AuthError(ProxyError): + """Handle a SOCKS5 auth error""" + pass + + +class Socks5Error(ProxyError): + """Handle a SOCKS5 non-auth error""" + pass + + +class Socks4Error(ProxyError): + """Handle a SOCKS4 error""" + pass + + +class HTTPError(ProxyError): + """Handle a HTTP error""" + pass + _generalerrors = ("success", - "invalid data", - "not connected", - "not available", - "bad proxy type", - "bad input", - "timed out", - "network unreachable", - "connection refused", - "host unreachable") + "invalid data", + "not connected", + "not available", + "bad proxy type", + "bad input", + "timed out", + "network unreachable", + "connection refused", + "host unreachable") _socks5errors = ("succeeded", - "general SOCKS server failure", - "connection not allowed by ruleset", - "Network unreachable", - "Host unreachable", - "Connection refused", - "TTL expired", - "Command not supported", - "Address type not supported", - "Unknown error") + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported", + "Unknown error") _socks5autherrors = ("succeeded", - "authentication is required", - "all offered authentication methods were rejected", - "unknown username or invalid password", - "unknown error") + "authentication is required", + "all offered authentication methods were rejected", + "unknown username or invalid password", + "unknown error") _socks4errors = ("request granted", - "request rejected or failed", - "request rejected because SOCKS server cannot connect to identd on the client", - "request rejected because the client program and identd report different user-ids", - "unknown error") + "request rejected or failed", + "request rejected because SOCKS server cannot connect to identd on the client", + "request rejected because the client program and identd report different user-ids", + "unknown error") + def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) @@ -101,6 +124,7 @@ def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=No global _defaultproxy _defaultproxy = (proxytype, addr, port, rdns, username, password) + def wrapmodule(module): """wrapmodule(module) Attempts to replace a module's socket library with a SOCKS socket. Must set @@ -108,11 +132,12 @@ def wrapmodule(module): This will only work on modules that import socket directly into the namespace; most of the Python Standard Library falls into this category. """ - if _defaultproxy != None: + if _defaultproxy is not None: module.socket.socket = socksocket else: raise GeneralProxyError((4, "no proxy specified")) + class socksocket(socket.socket): """socksocket([family[, type[, proto]]]) -> socket object Open a SOCKS enabled socket. The parameters are the same as @@ -121,8 +146,9 @@ class socksocket(socket.socket): """ def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): + # pylint: disable=redefined-builtin _orgsocket.__init__(self, family, type, proto, _sock) - if _defaultproxy != None: + if _defaultproxy is not None: self.__proxy = _defaultproxy else: self.__proxy = (None, None, None, None, None, None) @@ -139,8 +165,9 @@ class socksocket(socket.socket): except socket.timeout: raise GeneralProxyError((6, "timed out")) while len(data) < count: - d = self.recv(count-len(data)) - if not d: raise GeneralProxyError((0, "connection closed unexpectedly")) + d = self.recv(count - len(data)) + if not d: + raise GeneralProxyError((0, "connection closed unexpectedly")) data = data + d return data @@ -181,7 +208,7 @@ class socksocket(socket.socket): Negotiates a connection through a SOCKS5 server. """ # First we'll send the authentication packages we support. - if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): + if (self.__proxy[4] is not None) and (self.__proxy[5] is not None): # The username/password details were supplied to the # setproxy method so we support the USERNAME/PASSWORD # authentication (in addition to the standard none). @@ -203,7 +230,11 @@ class socksocket(socket.socket): elif chosenauth[1:2] == chr(0x02).encode(): # Okay, we need to perform a basic username/password # authentication. - self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) + self.sendall(chr(0x01).encode() + + chr(len(self.__proxy[4])) + + self.__proxy[4] + + chr(len(self.__proxy[5])) + + self.__proxy[5]) authstat = self.__recvall(2) if authstat[0:1] != chr(0x01).encode(): # Bad response @@ -250,7 +281,7 @@ class socksocket(socket.socket): elif resp[1:2] != chr(0x00).encode(): # Connection failed self.close() - if ord(resp[1:2])<=8: + if ord(resp[1:2]) <= 8: raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) else: raise Socks5Error((9, _socks5errors[9])) @@ -262,10 +293,10 @@ class socksocket(socket.socket): boundaddr = self.__recvall(ord(resp[4:5])) else: self.close() - raise GeneralProxyError((1,_generalerrors[1])) + raise GeneralProxyError((1, _generalerrors[1])) boundport = struct.unpack(">H", self.__recvall(2))[0] self.__proxysockname = (boundaddr, boundport) - if ipaddr != None: + if ipaddr is not None: self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) else: self.__proxypeername = (destaddr, destport) @@ -285,7 +316,7 @@ class socksocket(socket.socket): elif resp[1:2] != chr(0x00).encode(): # Connection failed self.close() - if ord(resp[1:2])<=8: + if ord(resp[1:2]) <= 8: raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) else: raise Socks5Error((9, _socks5errors[9])) @@ -297,8 +328,8 @@ class socksocket(socket.socket): ip = self.__recvall(ord(resp[4:5])) else: self.close() - raise GeneralProxyError((1,_generalerrors[1])) - boundport = struct.unpack(">H", self.__recvall(2))[0] + raise GeneralProxyError((1, _generalerrors[1])) + _ = struct.unpack(">H", self.__recvall(2))[0] return ip def getproxysockname(self): @@ -321,9 +352,10 @@ class socksocket(socket.socket): return self.__proxypeername def getproxytype(self): + """Get the proxy type""" return self.__proxy[0] - def __negotiatesocks4(self,destaddr,destport): + def __negotiatesocks4(self, destaddr, destport): """__negotiatesocks4(self,destaddr,destport) Negotiates a connection through a SOCKS4 server. """ @@ -341,7 +373,7 @@ class socksocket(socket.socket): # Construct the request packet req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr # The username parameter is considered userid for SOCKS4 - if self.__proxy[4] != None: + if self.__proxy[4] is not None: req = req + self.__proxy[4] req = req + chr(0x00).encode() # DNS name if remote resolving is required @@ -355,7 +387,7 @@ class socksocket(socket.socket): if resp[0:1] != chr(0x00).encode(): # Bad data self.close() - raise GeneralProxyError((1,_generalerrors[1])) + raise GeneralProxyError((1, _generalerrors[1])) if resp[1:2] != chr(0x5A).encode(): # Server returned an error self.close() @@ -366,7 +398,7 @@ class socksocket(socket.socket): raise Socks4Error((94, _socks4errors[4])) # Get the bound address/port self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0]) - if rmtrslv != None: + if rmtrslv is not None: self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) else: self.__proxypeername = (destaddr, destport) @@ -380,7 +412,16 @@ class socksocket(socket.socket): addr = socket.gethostbyname(destaddr) else: addr = destaddr - self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode()) + self.sendall(''.join([ + "CONNECT ", + addr, + ":", + str(destport), + " HTTP/1.1\r\n", + "Host: ", + destaddr, + "\r\n\r\n", + ]).encode()) # We read the response until we get the string "\r\n\r\n" resp = self.recv(1) while resp.find("\r\n\r\n".encode()) == -1: @@ -410,10 +451,15 @@ class socksocket(socket.socket): To select the proxy server use setproxy(). """ # Do a minimal input check first - if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int): + if any([ + not isinstance(destpair, (list, tuple)), + len(destpair) < 2, + not isinstance(destpair[0], type('')), + not isinstance(destpair[1], int), + ]): raise GeneralProxyError((5, _generalerrors[5])) if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: + if self.__proxy[2] is not None: portnum = self.__proxy[2] else: portnum = 1080 @@ -433,19 +479,19 @@ class socksocket(socket.socket): self.__negotiatesocks5() self.__connectsocks5(destpair[0], destpair[1]) elif self.__proxy[0] == PROXY_TYPE_SOCKS4: - if self.__proxy[2] != None: + if self.__proxy[2] is not None: portnum = self.__proxy[2] else: portnum = 1080 - _orgsocket.connect(self,(self.__proxy[1], portnum)) + _orgsocket.connect(self, (self.__proxy[1], portnum)) self.__negotiatesocks4(destpair[0], destpair[1]) elif self.__proxy[0] == PROXY_TYPE_HTTP: - if self.__proxy[2] != None: + if self.__proxy[2] is not None: portnum = self.__proxy[2] else: portnum = 8080 try: - _orgsocket.connect(self,(self.__proxy[1], portnum)) + _orgsocket.connect(self, (self.__proxy[1], portnum)) except socket.error as e: # ENETUNREACH, WSAENETUNREACH if e[0] in [101, 10051]: @@ -458,14 +504,15 @@ class socksocket(socket.socket): raise GeneralProxyError((9, _generalerrors[9])) raise self.__negotiatehttp(destpair[0], destpair[1]) - elif self.__proxy[0] == None: + elif self.__proxy[0] is None: _orgsocket.connect(self, (destpair[0], destpair[1])) else: raise GeneralProxyError((4, _generalerrors[4])) def resolve(self, host): + """TBC""" if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: + if self.__proxy[2] is not None: portnum = self.__proxy[2] else: portnum = 1080 diff --git a/src/upnp.py b/src/upnp.py index 46d55956..c89beb96 100644 --- a/src/upnp.py +++ b/src/upnp.py @@ -1,21 +1,33 @@ -# A simple upnp module to forward port for BitMessage -# Reference: http://mattscodecave.com/posts/using-python-and-upnp-to-forward-a-port +# pylint: disable=too-many-statements,too-many-branches,protected-access,no-self-use +""" +A simple upnp module to forward port for BitMessage +Reference: http://mattscodecave.com/posts/using-python-and-upnp-to-forward-a-port +""" + +from __future__ import absolute_import + import httplib from random import randint import socket -from struct import unpack, pack +from struct import unpack import threading import time +import urllib2 +from urlparse import urlparse +from xml.dom.minidom import Document, parseString + from bmconfigparser import BMConfigParser +from debug import logger from network.connectionpool import BMConnectionPool -from helper_threading import * +from helper_threading import StoppableThread import queues import shared import state import tr + def createRequestXML(service, action, arguments=None): - from xml.dom.minidom import Document + """Router UPnP requests are XML formatted""" doc = Document() @@ -63,22 +75,24 @@ def createRequestXML(service, action, arguments=None): # our tree is ready, conver it to a string return doc.toxml() -class UPnPError(Exception): - def __init__(self, message): - self.message -class Router: +class UPnPError(Exception): + """Handle a UPnP error""" + + def __init__(self, message): + super(UPnPError, self).__init__() + logger.error(message) + + +class Router: # pylint: disable=old-style-class + """TBC""" name = "" path = "" address = None routerPath = None extPort = None - + def __init__(self, ssdpResponse, address): - import urllib2 - from xml.dom.minidom import parseString - from urlparse import urlparse - from debug import logger self.address = address @@ -92,9 +106,9 @@ class Router: try: self.routerPath = urlparse(header['location']) if not self.routerPath or not hasattr(self.routerPath, "hostname"): - logger.error ("UPnP: no hostname: %s", header['location']) + logger.error("UPnP: no hostname: %s", header['location']) except KeyError: - logger.error ("UPnP: missing location header") + logger.error("UPnP: missing location header") # get the profile xml file and read it into a variable directory = urllib2.urlopen(header['location']).read() @@ -108,45 +122,58 @@ class Router: for service in service_types: if service.childNodes[0].data.find('WANIPConnection') > 0 or \ - service.childNodes[0].data.find('WANPPPConnection') > 0: + service.childNodes[0].data.find('WANPPPConnection') > 0: self.path = service.parentNode.getElementsByTagName('controlURL')[0].childNodes[0].data self.upnp_schema = service.childNodes[0].data.split(':')[-2] - def AddPortMapping(self, externalPort, internalPort, internalClient, protocol, description, leaseDuration = 0, enabled = 1): - from debug import logger + def AddPortMapping( + self, + externalPort, + internalPort, + internalClient, + protocol, + description, + leaseDuration=0, + enabled=1, + ): # pylint: disable=too-many-arguments + """Add UPnP port mapping""" + resp = self.soapRequest(self.upnp_schema + ':1', 'AddPortMapping', [ - ('NewRemoteHost', ''), - ('NewExternalPort', str(externalPort)), - ('NewProtocol', protocol), - ('NewInternalPort', str(internalPort)), - ('NewInternalClient', internalClient), - ('NewEnabled', str(enabled)), - ('NewPortMappingDescription', str(description)), - ('NewLeaseDuration', str(leaseDuration)) - ]) + ('NewRemoteHost', ''), + ('NewExternalPort', str(externalPort)), + ('NewProtocol', protocol), + ('NewInternalPort', str(internalPort)), + ('NewInternalClient', internalClient), + ('NewEnabled', str(enabled)), + ('NewPortMappingDescription', str(description)), + ('NewLeaseDuration', str(leaseDuration)) + ]) self.extPort = externalPort - logger.info("Successfully established UPnP mapping for %s:%i on external port %i", internalClient, internalPort, externalPort) + logger.info("Successfully established UPnP mapping for %s:%i on external port %i", + internalClient, internalPort, externalPort) return resp def DeletePortMapping(self, externalPort, protocol): - from debug import logger + """Delete UPnP port mapping""" + resp = self.soapRequest(self.upnp_schema + ':1', 'DeletePortMapping', [ - ('NewRemoteHost', ''), - ('NewExternalPort', str(externalPort)), - ('NewProtocol', protocol), - ]) + ('NewRemoteHost', ''), + ('NewExternalPort', str(externalPort)), + ('NewProtocol', protocol), + ]) logger.info("Removed UPnP mapping on external port %i", externalPort) return resp def GetExternalIPAddress(self): - from xml.dom.minidom import parseString + """Get the external address""" + resp = self.soapRequest(self.upnp_schema + ':1', 'GetExternalIPAddress') dom = parseString(resp) return dom.getElementsByTagName('NewExternalIPAddress')[0].childNodes[0].data - + def soapRequest(self, service, action, arguments=None): - from xml.dom.minidom import parseString - from debug import logger + """Make a request to a router""" + conn = httplib.HTTPConnection(self.routerPath.hostname, self.routerPath.port) conn.request( 'POST', @@ -155,8 +182,8 @@ class Router: { 'SOAPAction': '"urn:schemas-upnp-org:service:%s#%s"' % (service, action), 'Content-Type': 'text/xml' - } - ) + } + ) resp = conn.getresponse() conn.close() if resp.status == 500: @@ -164,21 +191,24 @@ class Router: try: dom = parseString(respData) errinfo = dom.getElementsByTagName('errorDescription') - if len(errinfo) > 0: + if errinfo: logger.error("UPnP error: %s", respData) raise UPnPError(errinfo[0].childNodes[0].data) except: - raise UPnPError("Unable to parse SOAP error: %s" %(respData)) + raise UPnPError("Unable to parse SOAP error: %s" % (respData)) return resp + class uPnPThread(threading.Thread, StoppableThread): + """Start a thread to handle UPnP activity""" + SSDP_ADDR = "239.255.255.250" GOOGLE_DNS = "8.8.8.8" SSDP_PORT = 1900 SSDP_MX = 2 SSDP_ST = "urn:schemas-upnp-org:device:InternetGatewayDevice:1" - def __init__ (self): + def __init__(self): threading.Thread.__init__(self, name="uPnPThread") try: self.extPort = BMConfigParser().getint('bitmessagesettings', 'extport') @@ -194,8 +224,8 @@ class uPnPThread(threading.Thread, StoppableThread): self.initStop() def run(self): - from debug import logger - + """Start the thread to manage UPnP activity""" + logger.debug("Starting UPnP thread") logger.debug("Local IP: %s", self.localIP) lastSent = 0 @@ -209,9 +239,11 @@ class uPnPThread(threading.Thread, StoppableThread): if not bound: time.sleep(1) + # pylint: disable=attribute-defined-outside-init self.localPort = BMConfigParser().getint('bitmessagesettings', 'port') + while state.shutdown == 0 and BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'): - if time.time() - lastSent > self.sendSleep and len(self.routers) == 0: + if time.time() - lastSent > self.sendSleep and not self.routers: try: self.sendSearchRouter() except: @@ -219,7 +251,7 @@ class uPnPThread(threading.Thread, StoppableThread): lastSent = time.time() try: while state.shutdown == 0 and BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'): - resp,(ip,port) = self.sock.recvfrom(1000) + resp, (ip, _) = self.sock.recvfrom(1000) if resp is None: continue newRouter = Router(resp, ip) @@ -230,14 +262,16 @@ class uPnPThread(threading.Thread, StoppableThread): logger.debug("Found UPnP router at %s", ip) self.routers.append(newRouter) self.createPortMapping(newRouter) - queues.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow",'UPnP port mapping established on port %1').arg(str(self.extPort)))) + queues.UISignalQueue.put(('updateStatusBar', tr._translate( + "MainWindow", 'UPnP port mapping established on port %1' + ).arg(str(self.extPort)))) # retry connections so that the submitted port is refreshed with shared.alreadyAttemptedConnectionsListLock: shared.alreadyAttemptedConnectionsList.clear() shared.alreadyAttemptedConnectionsListResetTime = int( time.time()) break - except socket.timeout as e: + except socket.timeout: pass except: logger.error("Failure running UPnP router search.", exc_info=True) @@ -259,22 +293,25 @@ class uPnPThread(threading.Thread, StoppableThread): self.deletePortMapping(router) shared.extPort = None if deleted: - queues.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow",'UPnP port mapping removed'))) + queues.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow", 'UPnP port mapping removed'))) logger.debug("UPnP thread done") def getLocalIP(self): + """Get the local IP of the node""" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.connect((uPnPThread.GOOGLE_DNS, 1)) return s.getsockname()[0] def sendSearchRouter(self): - from debug import logger + """Querying for UPnP services""" + ssdpRequest = "M-SEARCH * HTTP/1.1\r\n" + \ - "HOST: %s:%d\r\n" % (uPnPThread.SSDP_ADDR, uPnPThread.SSDP_PORT) + \ - "MAN: \"ssdp:discover\"\r\n" + \ - "MX: %d\r\n" % (uPnPThread.SSDP_MX, ) + \ - "ST: %s\r\n" % (uPnPThread.SSDP_ST, ) + "\r\n" + "HOST: %s:%d\r\n" % (uPnPThread.SSDP_ADDR, uPnPThread.SSDP_PORT) + \ + "MAN: \"ssdp:discover\"\r\n" + \ + "MX: %d\r\n" % (uPnPThread.SSDP_MX, ) + \ + "ST: %s\r\n" % (uPnPThread.SSDP_ST, ) + "\r\n" try: logger.debug("Sending UPnP query") @@ -283,19 +320,24 @@ class uPnPThread(threading.Thread, StoppableThread): logger.exception("UPnP send query failed") def createPortMapping(self, router): - from debug import logger + """Add a port mapping""" for i in range(50): try: - routerIP, = unpack('>I', socket.inet_aton(router.address)) + unpack('>I', socket.inet_aton(router.address)) localIP = self.localIP if i == 0: - extPort = self.localPort # try same port first + extPort = self.localPort # try same port first elif i == 1 and self.extPort: - extPort = self.extPort # try external port from last time next + extPort = self.extPort # try external port from last time next else: extPort = randint(32767, 65535) - logger.debug("Attempt %i, requesting UPnP mapping for %s:%i on external port %i", i, localIP, self.localPort, extPort) + logger.debug( + "Attempt %i, requesting UPnP mapping for %s:%i on external port %i", + i, + localIP, + self.localPort, + extPort) router.AddPortMapping(extPort, self.localPort, localIP, 'TCP', 'BitMessage') shared.extPort = extPort self.extPort = extPort @@ -306,7 +348,5 @@ class uPnPThread(threading.Thread, StoppableThread): logger.debug("UPnP error: ", exc_info=True) def deletePortMapping(self, router): + """Delete a port mapping""" router.DeletePortMapping(router.extPort, 'TCP') - - -