diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..918f737d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# A container for PyBitmessage daemon + +FROM ubuntu:xenial + +RUN apt-get update + +# Install dependencies +RUN apt-get install -yq --no-install-suggests --no-install-recommends \ + python-msgpack dh-python python-all-dev build-essential libssl-dev \ + python-stdeb fakeroot python-pip libcap-dev + +RUN pip install --upgrade pip + +EXPOSE 8444 8442 + +ENV HOME /home/bitmessage +ENV BITMESSAGE_HOME ${HOME} + +ENV VER 0.6.3.2 + +WORKDIR ${HOME} +ADD . ${HOME} + +# Install tests dependencies +RUN pip install -r requirements.txt + +# Build and install deb +RUN python2 setup.py sdist \ + && py2dsc-deb dist/pybitmessage-${VER}.tar.gz \ + && dpkg -i deb_dist/python-pybitmessage_${VER}-1_amd64.deb + +# Create a user +RUN useradd bitmessage && chown -R bitmessage ${HOME} + +USER bitmessage + +# Generate default config +RUN src/bitmessagemain.py -t && mv keys.dat /tmp + +# Clean HOME +RUN rm -rf ${HOME}/* + +# Setup environment +RUN mv /tmp/keys.dat . \ + && APIPASS=$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo) \ + && echo "\napiusername: api\napipassword: $APIPASS" \ + && echo "apienabled = true\napiinterface = 0.0.0.0\napiusername = api\napipassword = $APIPASS" >> keys.dat + +CMD ["pybitmessage", "-d"] diff --git a/MANIFEST.in b/MANIFEST.in index 2d29971b..3cbc72cd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include COPYING include README.md +include requirements.txt recursive-include desktop * diff --git a/src/addresses.py b/src/addresses.py index 510b0ce6..533ec169 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -5,23 +5,11 @@ src/addresses.py """ # pylint: disable=redefined-outer-name,inconsistent-return-statements -from __future__ import print_function import hashlib from binascii import hexlify, unhexlify from struct import pack, unpack from debug import logger -from pyelliptic import arithmetic - - -def convertIntToString(n): - """.. todo:: There is another copy of this function in Bitmessagemain.py""" - a = __builtins__.hex(n) - if a[-1:] == 'L': - a = a[:-1] - if len(a) % 2 == 0: - return unhexlify(a[2:]) - return unhexlify('0' + a[2:]) ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" @@ -288,7 +276,10 @@ def addBMIfNotPresent(address): return address if address[:3] == 'BM-' else 'BM-' + address +# TODO: make test case if __name__ == "__main__": + from pyelliptic import arithmetic + print( '\nLet us make an address from scratch. Suppose we generate two' ' random 32 byte values and call the first one the signing key' diff --git a/src/api.py b/src/api.py index 4399ecbc..1e485209 100644 --- a/src/api.py +++ b/src/api.py @@ -661,8 +661,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): BMConfigParser().remove_section(address) with open(state.appdata + 'keys.dat', 'wb') as configfile: BMConfigParser().write(configfile) - queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) - queues.UISignalQueue.put(('rerenderMessagelistToLabels', '')) + queues.UISignalQueue.put(('writeNewAddressToTable', ('', '', ''))) shared.reloadMyAddressHashes() return 'success' diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 8f4c7b23..d86e04b9 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -22,10 +22,13 @@ depends.check_dependencies() import ctypes import getopt +import multiprocessing # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. import signal import socket +import threading import time +import traceback from struct import pack from helper_startup import ( @@ -38,7 +41,7 @@ import shared import knownnodes import state import shutdown -import threading +from debug import logger # Classes from class_sqlThread import sqlThread @@ -61,9 +64,9 @@ from network.downloadthread import DownloadThread from network.uploadthread import UploadThread # Helper Functions -import helper_generic import helper_threading + def connectToStream(streamNumber): state.streamsInWhichIAmParticipating.append(streamNumber) selfInitiatedConnections[streamNumber] = {} @@ -150,6 +153,37 @@ def _fixSocket(): socket.IPV6_V6ONLY = 27 +def signal_handler(signum, frame): + """Single handler for any signal sent to pybitmessage""" + process = multiprocessing.current_process() + thread = threading.current_thread() + logger.error( + 'Got signal %i in %s/%s', + signum, process.name, thread.name + ) + if process.name == "RegExParser": + # on Windows this isn't triggered, but it's fine, + # it has its own process termination thing + raise SystemExit + if "PoolWorker" in process.name: + raise SystemExit + if thread.name not in ("PyBitmessage", "MainThread"): + return + logger.error("Got signal %i", signum) + # there are possible non-UI variants to run bitmessage which should shutdown + # especially test-mode + if shared.thisapp.daemon or not state.enableGUI: + shutdown.doCleanShutdown() + else: + print('# Thread: %s(%d)' % (thread.name, thread.ident)) + for filename, lineno, name, line in traceback.extract_stack(frame): + print('File: "%s", line %d, in %s' % (filename, lineno, name)) + if line: + print(' %s' % line.strip()) + print('Unfortunately you cannot use Ctrl+C when running the UI' + ' because the UI captures the signal.') + + # This is a list of current connections (the thread pointers at least) selfInitiatedConnections = {} @@ -437,8 +471,8 @@ class Main: os.kill(grandfatherPid, signal.SIGTERM) def setSignalHandler(self): - signal.signal(signal.SIGINT, helper_generic.signal_handler) - signal.signal(signal.SIGTERM, helper_generic.signal_handler) + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) # signal.signal(signal.SIGINT, signal.SIG_DFL) def usage(self): diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 06082696..0d4cebb3 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -9,6 +9,7 @@ import random import string import sys import textwrap +import threading import time from datetime import datetime, timedelta from sqlite3 import register_adapter @@ -44,7 +45,6 @@ from account import ( getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount, GatewayAccount, MailchuckAccount, AccountColor) import dialogs -from helper_generic import powQueueSize from network.stats import pendingDownload, pendingUpload from uisignaler import UISignaler import knownnodes @@ -107,6 +107,19 @@ def change_translation(newlocale): logger.error("Failed to set locale to %s", lang, exc_info=True) +# TODO: rewrite +def powQueueSize(): + """Returns the size of queues.workerQueue including current unfinished work""" + queue_len = queues.workerQueue.qsize() + for thread in threading.enumerate(): + try: + if thread.name == "singleWorker": + queue_len += thread.busy + except Exception as err: + logger.info('Thread error %s', err) + return queue_len + + class MyForm(settingsmixin.SMainWindow): # the last time that a message arrival sound was played @@ -117,6 +130,7 @@ class MyForm(settingsmixin.SMainWindow): REPLY_TYPE_SENDER = 0 REPLY_TYPE_CHAN = 1 + REPLY_TYPE_UPD = 2 def init_file_menu(self): QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL( @@ -368,8 +382,6 @@ class MyForm(settingsmixin.SMainWindow): self.on_context_menuSubscriptions) def init_sent_popup_menu(self, connectSignal=True): - # Popup menu for the Sent page - self.ui.sentContextMenuToolbar = QtGui.QToolBar() # Actions self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction( _translate( @@ -381,6 +393,9 @@ class MyForm(settingsmixin.SMainWindow): self.actionForceSend = self.ui.sentContextMenuToolbar.addAction( _translate( "MainWindow", "Force send"), self.on_action_ForceSend) + self.actionSentReply = self.ui.sentContextMenuToolbar.addAction( + _translate("MainWindow", "Send update"), + self.on_action_SentReply) # self.popMenuSent = QtGui.QMenu( self ) # self.popMenuSent.addAction( self.actionSentClipboard ) # self.popMenuSent.addAction( self.actionTrashSentMessage ) @@ -2656,15 +2671,13 @@ class MyForm(settingsmixin.SMainWindow): tableWidget.item(i, 3).setFont(font) markread = sqlExecuteChunked( - "UPDATE %s SET read = 1 WHERE %s IN({0}) AND read=0" % ( - ('sent', 'ackdata') if self.getCurrentFolder() == 'sent' - else ('inbox', 'msgid') - ), idCount, *msgids + "UPDATE inbox SET read = 1 WHERE msgid IN({0}) AND read=0", + idCount, *msgids ) if markread > 0: self.propagateUnreadCount() - # addressAtCurrentRow, self.getCurrentFolder(), None, 0) + # addressAtCurrentRow, self.getCurrentFolder(), None, 0) def click_NewAddressDialog(self): dialogs.NewAddressDialog(self) @@ -2998,48 +3011,73 @@ class MyForm(settingsmixin.SMainWindow): def on_action_InboxReplyChan(self): self.on_action_InboxReply(self.REPLY_TYPE_CHAN) - - def on_action_InboxReply(self, replyType = None): + + def on_action_SentReply(self): + self.on_action_InboxReply(self.REPLY_TYPE_UPD) + + def on_action_InboxReply(self, reply_type=None): + """Handle any reply action depending on reply_type""" + # pylint: disable=too-many-locals tableWidget = self.getCurrentMessagelist() if not tableWidget: return - - if replyType is None: - replyType = self.REPLY_TYPE_SENDER - + + if reply_type is None: + reply_type = self.REPLY_TYPE_SENDER + # save this to return back after reply is done self.replyFromTab = self.ui.tabWidget.currentIndex() - + + column_to = 1 if reply_type == self.REPLY_TYPE_UPD else 0 + column_from = 0 if reply_type == self.REPLY_TYPE_UPD else 1 + currentInboxRow = tableWidget.currentRow() toAddressAtCurrentInboxRow = tableWidget.item( - currentInboxRow, 0).address + currentInboxRow, column_to).address acct = accountClass(toAddressAtCurrentInboxRow) fromAddressAtCurrentInboxRow = tableWidget.item( - currentInboxRow, 1).address + currentInboxRow, column_from).address msgid = str(tableWidget.item( currentInboxRow, 3).data(QtCore.Qt.UserRole).toPyObject()) queryreturn = sqlQuery( - '''select message from inbox where msgid=?''', msgid) + "SELECT message FROM inbox WHERE msgid=?", msgid + ) or sqlQuery("SELECT message FROM sent WHERE ackdata=?", msgid) if queryreturn != []: for row in queryreturn: messageAtCurrentInboxRow, = row - acct.parseMessage(toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow, tableWidget.item(currentInboxRow, 2).subject, messageAtCurrentInboxRow) + acct.parseMessage( + toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow, + tableWidget.item(currentInboxRow, 2).subject, + messageAtCurrentInboxRow) widget = { 'subject': self.ui.lineEditSubject, 'from': self.ui.comboBoxSendFrom, 'message': self.ui.textEditMessage } + if toAddressAtCurrentInboxRow == str_broadcast_subscribers: self.ui.tabWidgetSend.setCurrentIndex( self.ui.tabWidgetSend.indexOf(self.ui.sendDirect) ) # toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow elif not BMConfigParser().has_section(toAddressAtCurrentInboxRow): - QtGui.QMessageBox.information(self, _translate("MainWindow", "Address is gone"), _translate( - "MainWindow", "Bitmessage cannot find your address %1. Perhaps you removed it?").arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok) - elif not BMConfigParser().getboolean(toAddressAtCurrentInboxRow, 'enabled'): - QtGui.QMessageBox.information(self, _translate("MainWindow", "Address disabled"), _translate( - "MainWindow", "Error: The address from which you are trying to send is disabled. You\'ll have to enable it on the \'Your Identities\' tab before using it."), QtGui.QMessageBox.Ok) + QtGui.QMessageBox.information( + self, _translate("MainWindow", "Address is gone"), + _translate( + "MainWindow", + "Bitmessage cannot find your address %1. Perhaps you" + " removed it?" + ).arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok) + elif not BMConfigParser().getboolean( + toAddressAtCurrentInboxRow, 'enabled'): + QtGui.QMessageBox.information( + self, _translate("MainWindow", "Address disabled"), + _translate( + "MainWindow", + "Error: The address from which you are trying to send" + " is disabled. You\'ll have to enable it on the" + " \'Your Identities\' tab before using it." + ), QtGui.QMessageBox.Ok) else: self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow) broadcast_tab_index = self.ui.tabWidgetSend.indexOf( @@ -3053,28 +3091,44 @@ class MyForm(settingsmixin.SMainWindow): } self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index) toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow - if fromAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 1).label or ( - isinstance(acct, GatewayAccount) and fromAddressAtCurrentInboxRow == acct.relayAddress): + if fromAddressAtCurrentInboxRow == \ + tableWidget.item(currentInboxRow, column_from).label or ( + isinstance(acct, GatewayAccount) and + fromAddressAtCurrentInboxRow == acct.relayAddress): 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.') - if toAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 0).label: + self.ui.lineEditTo.setText( + tableWidget.item(currentInboxRow, column_from).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 reply_type == self.REPLY_TYPE_CHAN: + logger.debug( + 'Original sent to a chan. Setting the to address in the' + ' reply to the chan address.') + if toAddressAtCurrentInboxRow == \ + tableWidget.item(currentInboxRow, column_to).label: self.ui.lineEditTo.setText(str(toAddressAtCurrentInboxRow)) else: - self.ui.lineEditTo.setText(tableWidget.item(currentInboxRow, 0).label + " <" + str(acct.toAddress) + ">") - + self.ui.lineEditTo.setText( + tableWidget.item(currentInboxRow, column_to).label + + " <" + str(acct.toAddress) + ">" + ) + self.setSendFromComboBox(toAddressAtCurrentInboxRow) - - quotedText = self.quoted_text(unicode(messageAtCurrentInboxRow, 'utf-8', 'replace')) + + quotedText = self.quoted_text( + unicode(messageAtCurrentInboxRow, 'utf-8', 'replace')) widget['message'].setPlainText(quotedText) if acct.subject[0:3] in ['Re:', 'RE:']: - widget['subject'].setText(tableWidget.item(currentInboxRow, 2).label) + widget['subject'].setText( + tableWidget.item(currentInboxRow, 2).label) else: - widget['subject'].setText('Re: ' + tableWidget.item(currentInboxRow, 2).label) + widget['subject'].setText( + 'Re: ' + tableWidget.item(currentInboxRow, 2).label) self.ui.tabWidget.setCurrentIndex( self.ui.tabWidget.indexOf(self.ui.send) ) @@ -3459,11 +3513,14 @@ class MyForm(settingsmixin.SMainWindow): for plugin in self.menu_plugins['address']: self.popMenuSubscriptions.addAction(plugin) self.popMenuSubscriptions.addSeparator() - self.popMenuSubscriptions.addAction(self.actionMarkAllRead) + if self.getCurrentFolder() != 'sent': + self.popMenuSubscriptions.addAction(self.actionMarkAllRead) + if self.popMenuSubscriptions.isEmpty(): + return self.popMenuSubscriptions.exec_( self.ui.treeWidgetSubscriptions.mapToGlobal(point)) - def widgetConvert (self, widget): + def widgetConvert(self, widget): if widget == self.ui.tableWidgetInbox: return self.ui.treeWidgetYourIdentities elif widget == self.ui.tableWidgetInboxSubscriptions: @@ -3874,8 +3931,10 @@ class MyForm(settingsmixin.SMainWindow): for plugin in self.menu_plugins['address']: self.popMenuYourIdentities.addAction(plugin) self.popMenuYourIdentities.addSeparator() - self.popMenuYourIdentities.addAction(self.actionMarkAllRead) - + if self.getCurrentFolder() != 'sent': + self.popMenuYourIdentities.addAction(self.actionMarkAllRead) + if self.popMenuYourIdentities.isEmpty(): + return self.popMenuYourIdentities.exec_( self.ui.treeWidgetYourIdentities.mapToGlobal(point)) @@ -3899,7 +3958,10 @@ class MyForm(settingsmixin.SMainWindow): for plugin in self.menu_plugins['address']: self.popMenu.addAction(plugin) self.popMenu.addSeparator() - self.popMenu.addAction(self.actionMarkAllRead) + if self.getCurrentFolder() != 'sent': + self.popMenu.addAction(self.actionMarkAllRead) + if self.popMenu.isEmpty(): + return self.popMenu.exec_( self.ui.treeWidgetChans.mapToGlobal(point)) @@ -3943,6 +4005,7 @@ class MyForm(settingsmixin.SMainWindow): self.popMenuSent = QtGui.QMenu(self) self.popMenuSent.addAction(self.actionSentClipboard) self.popMenuSent.addAction(self.actionTrashSentMessage) + self.popMenuSent.addAction(self.actionSentReply) # Check to see if this item is toodifficult and display an additional # menu option (Force Send) if it is. diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index cb3578c0..06c9bc6b 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -651,6 +651,10 @@ class Ui_MainWindow(object): MainWindow.setTabOrder(self.lineEditSubject, self.textEditMessage) MainWindow.setTabOrder(self.textEditMessage, self.pushButtonAddSubscription) + # Popup menu actions container for the Sent page + # pylint: disable=attribute-defined-outside-init + self.sentContextMenuToolbar = QtGui.QToolBar() + def updateNetworkSwitchMenuLabel(self, dontconnect=None): if dontconnect is None: dontconnect = BMConfigParser().safeGetBoolean( diff --git a/src/helper_generic.py b/src/helper_generic.py deleted file mode 100644 index ce56a292..00000000 --- a/src/helper_generic.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -Helper Generic perform generic operations for threading. - -Also perform some conversion operations. -""" - - -import socket -import sys -import threading -import traceback -import multiprocessing -from binascii import hexlify, unhexlify - -import shared -import state -import queues -import shutdown -from debug import logger - - -def powQueueSize(): - curWorkerQueue = queues.workerQueue.qsize() - for thread in threading.enumerate(): - try: - if thread.name == "singleWorker": - curWorkerQueue += thread.busy - except Exception as err: - logger.info('Thread error %s', err) - return curWorkerQueue - - -def convertIntToString(n): - a = __builtins__.hex(n) - if a[-1:] == 'L': - a = a[:-1] - if (len(a) % 2) == 0: - return unhexlify(a[2:]) - else: - return unhexlify('0' + a[2:]) - - -def convertStringToInt(s): - return int(hexlify(s), 16) - - -def allThreadTraceback(frame): - id2name = dict([(th.ident, th.name) for th in threading.enumerate()]) - code = [] - for threadId, stack in sys._current_frames().items(): - code.append( - '\n# Thread: %s(%d)' % (id2name.get(threadId, ''), threadId)) - for filename, lineno, name, line in traceback.extract_stack(stack): - code.append( - 'File: "%s", line %d, in %s' % (filename, lineno, name)) - if line: - code.append(' %s' % (line.strip())) - print('\n'.join(code)) - - -def signal_handler(signal, frame): - process = multiprocessing.current_process() - logger.error( - 'Got signal %i in %s/%s', - signal, process.name, threading.current_thread().name - ) - if process.name == "RegExParser": - # on Windows this isn't triggered, but it's fine, - # it has its own process termination thing - raise SystemExit - if "PoolWorker" in process.name: - raise SystemExit - if threading.current_thread().name not in ("PyBitmessage", "MainThread"): - return - logger.error("Got signal %i", signal) - if shared.thisapp.daemon or not state.enableGUI: # FIXME redundant? - shutdown.doCleanShutdown() - else: - allThreadTraceback(frame) - print('Unfortunately you cannot use Ctrl+C when running the UI' - ' because the UI captures the signal.') - - -def isHostInPrivateIPRange(host): - if ":" in host: # IPv6 - hostAddr = socket.inet_pton(socket.AF_INET6, host) - if hostAddr == ('\x00' * 15) + '\x01': - return False - if hostAddr[0] == '\xFE' and (ord(hostAddr[1]) & 0xc0) == 0x80: - return False - if (ord(hostAddr[0]) & 0xfe) == 0xfc: - return False - elif ".onion" not in host: - if host[:3] == '10.': - return True - if host[:4] == '172.': - if host[6] == '.': - if int(host[4:6]) >= 16 and int(host[4:6]) <= 31: - return True - if host[:8] == '192.168.': - return True - # Multicast - if host[:3] >= 224 and host[:3] <= 239 and host[4] == '.': - return True - return False - - -def addDataPadding(data, desiredMsgLength=12, paddingChar='\x00'): - return data + paddingChar * (desiredMsgLength - len(data)) diff --git a/src/network/tcp.py b/src/network/tcp.py index a4d0aa1f..0463d322 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -130,36 +130,30 @@ class TCPConnection(BMProto, TLSDispatcher): # pylint: disable=too-many-instanc # 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) + maxAddrCount = BMConfigParser().safeGetInt( + "bitmessagesettings", "maxaddrperstreamsend", 500) - # init templist = [] addrs = {} for stream in self.streams: with knownnodes.knownNodesLock: - if knownnodes.knownNodes[stream]: - filtered = {k: v for k, v in knownnodes.knownNodes[stream].items() - if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} - elemCount = len(filtered) - if elemCount > maxAddrCount: - elemCount = maxAddrCount + for n, s in enumerate((stream, stream * 2, stream * 2 + 1)): + nodes = knownnodes.knownNodes.get(s) + if not nodes: + continue # 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 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 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) + # and having positive or neutral rating + filtered = [ + (k, v) for k, v in nodes.iteritems() + if v["lastseen"] > int(time.time()) - + shared.maximumAgeOfNodesThatIAdvertiseToOthers and + v["rating"] >= 0 + ] + # sent 250 only if the remote isn't interested in it + elemCount = min( + len(filtered), + maxAddrCount / 2 if n else maxAddrCount) + addrs[s] = helper_random.randomsample(filtered, elemCount) for substream in addrs: for peer, params in addrs[substream]: templist.append((substream, peer, params["lastseen"])) diff --git a/src/tests/test_process.py b/src/tests/test_process.py index 5033045e..c03754a9 100644 --- a/src/tests/test_process.py +++ b/src/tests/test_process.py @@ -89,6 +89,24 @@ class TestProcessProto(unittest.TestCase): len(self.process.threads()), self._threads_count) +class TestProcessShutdown(TestProcessProto): + """Separate test case for SIGTERM""" + def test_shutdown(self): + """Send to pybitmessage SIGTERM and ensure it stopped""" + self.process.send_signal(signal.SIGTERM) + try: + # longer wait time because it's not a benchmark + self.process.wait(10) + except psutil.TimeoutExpired: + self.fail( + '%s has not stopped in 10 sec' % ' '.join(self._process_cmd)) + + @classmethod + def tearDownClass(cls): + """Special teardown because pybitmessage is already stopped""" + cls._cleanup_files() + + class TestProcess(TestProcessProto): """A test case for pybitmessage process""" def test_process_name(self):