From 5e0d168db60b448b0e027f23f24de17e97882e6d Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 31 Jan 2019 18:23:43 +0200 Subject: [PATCH 01/12] Do not show context menu on 'sent' folder because the 'sent' table have no column 'read' --- src/bitmessageqt/__init__.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 06082696..101097ed 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -2656,15 +2656,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) @@ -3459,11 +3457,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 +3875,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 +3902,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)) From a6c22a1d9eebd7fcccfbb14bb8687933472c7978 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 21 Jun 2018 12:58:25 +0300 Subject: [PATCH 02/12] Minimal Dockerfile --- Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..2aab542a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +# A container for PyBitmessage daemon + +FROM python:2.7-slim +WORKDIR /bitmessaged +ADD . /bitmessaged +RUN python2 setup.py install +EXPOSE 8444 8442 +ENV BITMESSAGE_HOME /bitmessaged +CMD ["pybitmessage", "-d"] From 84300afaf1fb868f488033b22c9397d4b5d886aa Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 16 Jul 2018 11:55:55 +0300 Subject: [PATCH 03/12] More complex container: - installing from deb - configuring and printing apipassword --- Dockerfile | 45 ++++++++++++++++++++++++++++++++++++++++----- MANIFEST.in | 1 + 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2aab542a..4cb7e1c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,44 @@ # A container for PyBitmessage daemon -FROM python:2.7-slim -WORKDIR /bitmessaged -ADD . /bitmessaged -RUN python2 setup.py install +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 BITMESSAGE_HOME /bitmessaged + +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 + +# Generate default config +RUN src/bitmessagemain.py -t && mv keys.dat .. + +# Clean HOME +RUN rm -rf ${HOME} + +# Setup environment +RUN mv ../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 * From 2d702e96470781be75c664844d412a5253c970d0 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 6 Feb 2019 17:19:09 +0200 Subject: [PATCH 04/12] Create user "bitmessage" to setup and run bitmessaged --- Dockerfile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4cb7e1c1..918f737d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,14 +29,19 @@ 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 .. +RUN src/bitmessagemain.py -t && mv keys.dat /tmp # Clean HOME -RUN rm -rf ${HOME} +RUN rm -rf ${HOME}/* # Setup environment -RUN mv ../keys.dat . \ +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 From e25fb857cb99b84adf208e9238c598e9e6741a56 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Tue, 30 Oct 2018 18:11:59 +0200 Subject: [PATCH 05/12] Slightly reduced TCPConnection.sendAddr() and changed in order to send only nodes with non-negative rating --- src/network/tcp.py | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) 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"])) From 7e1ee815b95f138dc4a5820e7ea72cd5132bc637 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Tue, 5 Mar 2019 18:47:37 +0200 Subject: [PATCH 06/12] Rerender more widgets when deleting address by API --- src/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api.py b/src/api.py index 9d32a5bb..65d364d7 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' From d1c601e7ae67f7f54d26a290d68cc5895b0f240f Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Tue, 19 Feb 2019 16:20:42 +0200 Subject: [PATCH 07/12] Added an option to reply on own messages i.e. send update --- src/bitmessageqt/__init__.py | 104 +++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 30 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 101097ed..49f7d058 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -117,6 +117,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( @@ -381,6 +382,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 ) @@ -2996,48 +3000,71 @@ 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): 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( @@ -3051,28 +3078,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) ) @@ -3949,6 +3992,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. From 87bf2ac1f552b90278d04e32b722c1c59502ef2c Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 4 Mar 2019 12:26:25 +0200 Subject: [PATCH 08/12] Handled pylint warnings (closes #1436) --- src/bitmessageqt/__init__.py | 4 ++-- src/bitmessageqt/bitmessageui.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 49f7d058..6a0ca3cb 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -369,8 +369,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( @@ -3005,6 +3003,8 @@ class MyForm(settingsmixin.SMainWindow): 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 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( From 3ec798bcfbee6bc2f5c467bd5471f5c1bf1dab26 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 6 Mar 2019 18:51:23 +0200 Subject: [PATCH 09/12] helper_generic is obsolete --- src/bitmessagemain.py | 48 +++++++++++++-- src/bitmessageqt/__init__.py | 14 ++++- src/helper_generic.py | 109 ----------------------------------- 3 files changed, 57 insertions(+), 114 deletions(-) delete mode 100644 src/helper_generic.py diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index b3775b60..3279a67b 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,43 @@ def _fixSocket(): socket.IPV6_V6ONLY = 27 +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.') + + # This is a list of current connections (the thread pointers at least) selfInitiatedConnections = {} @@ -437,8 +477,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 6a0ca3cb..8267e1f5 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,18 @@ def change_translation(newlocale): logger.error("Failed to set locale to %s", lang, exc_info=True) +# TODO: rewrite +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 + + class MyForm(settingsmixin.SMainWindow): # the last time that a message arrival sound was played 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)) From f2d3b69bf83f495a9deb7134c0c67a267a1c4062 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 6 Mar 2019 19:04:04 +0200 Subject: [PATCH 10/12] Removed another copy of unused convertIntToString() from addresses --- src/addresses.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) 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' From c38d25038908b6baee42f65795dea23e1b5fed28 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 6 Mar 2019 22:36:34 +0200 Subject: [PATCH 11/12] Resolved pylint warnings and removed allThreadTraceback() entirely --- src/bitmessagemain.py | 34 ++++++++++++++-------------------- src/bitmessageqt/__init__.py | 7 ++++--- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 3279a67b..e7daf583 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -153,25 +153,13 @@ def _fixSocket(): socket.IPV6_V6ONLY = 27 -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): +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', - signal, process.name, threading.current_thread().name + signum, process.name, thread.name ) if process.name == "RegExParser": # on Windows this isn't triggered, but it's fine, @@ -179,13 +167,19 @@ def signal_handler(signal, frame): raise SystemExit if "PoolWorker" in process.name: raise SystemExit - if threading.current_thread().name not in ("PyBitmessage", "MainThread"): + if thread.name not in ("PyBitmessage", "MainThread"): return - logger.error("Got signal %i", signal) - if shared.thisapp.daemon or not state.enableGUI: # FIXME redundant? + 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: - allThreadTraceback(frame) + 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.') diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 8267e1f5..0d4cebb3 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -109,14 +109,15 @@ def change_translation(newlocale): # TODO: rewrite def powQueueSize(): - curWorkerQueue = queues.workerQueue.qsize() + """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": - curWorkerQueue += thread.busy + queue_len += thread.busy except Exception as err: logger.info('Thread error %s', err) - return curWorkerQueue + return queue_len class MyForm(settingsmixin.SMainWindow): From acea6831396271b1fb18c5c807bce1168c0ad2eb Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 7 Mar 2019 12:18:45 +0200 Subject: [PATCH 12/12] Test SIGTERM signal handling --- src/tests/test_process.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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):