Merge branch 'v0.6' into v0.6

This commit is contained in:
Marius Kjærstad 2019-03-23 15:14:56 +01:00 committed by GitHub
commit e0efb7fd2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 238 additions and 194 deletions

49
Dockerfile Normal file
View File

@ -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"]

View File

@ -1,3 +1,4 @@
include COPYING include COPYING
include README.md include README.md
include requirements.txt
recursive-include desktop * recursive-include desktop *

View File

@ -5,23 +5,11 @@ src/addresses.py
""" """
# pylint: disable=redefined-outer-name,inconsistent-return-statements # pylint: disable=redefined-outer-name,inconsistent-return-statements
from __future__ import print_function
import hashlib import hashlib
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from struct import pack, unpack from struct import pack, unpack
from debug import logger 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" ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
@ -288,7 +276,10 @@ def addBMIfNotPresent(address):
return address if address[:3] == 'BM-' else 'BM-' + address return address if address[:3] == 'BM-' else 'BM-' + address
# TODO: make test case
if __name__ == "__main__": if __name__ == "__main__":
from pyelliptic import arithmetic
print( print(
'\nLet us make an address from scratch. Suppose we generate two' '\nLet us make an address from scratch. Suppose we generate two'
' random 32 byte values and call the first one the signing key' ' random 32 byte values and call the first one the signing key'

View File

@ -661,8 +661,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
BMConfigParser().remove_section(address) BMConfigParser().remove_section(address)
with open(state.appdata + 'keys.dat', 'wb') as configfile: with open(state.appdata + 'keys.dat', 'wb') as configfile:
BMConfigParser().write(configfile) BMConfigParser().write(configfile)
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) queues.UISignalQueue.put(('writeNewAddressToTable', ('', '', '')))
queues.UISignalQueue.put(('rerenderMessagelistToLabels', ''))
shared.reloadMyAddressHashes() shared.reloadMyAddressHashes()
return 'success' return 'success'

View File

@ -22,10 +22,13 @@ depends.check_dependencies()
import ctypes import ctypes
import getopt import getopt
import multiprocessing
# Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully.
import signal import signal
import socket import socket
import threading
import time import time
import traceback
from struct import pack from struct import pack
from helper_startup import ( from helper_startup import (
@ -38,7 +41,7 @@ import shared
import knownnodes import knownnodes
import state import state
import shutdown import shutdown
import threading from debug import logger
# Classes # Classes
from class_sqlThread import sqlThread from class_sqlThread import sqlThread
@ -61,9 +64,9 @@ from network.downloadthread import DownloadThread
from network.uploadthread import UploadThread from network.uploadthread import UploadThread
# Helper Functions # Helper Functions
import helper_generic
import helper_threading import helper_threading
def connectToStream(streamNumber): def connectToStream(streamNumber):
state.streamsInWhichIAmParticipating.append(streamNumber) state.streamsInWhichIAmParticipating.append(streamNumber)
selfInitiatedConnections[streamNumber] = {} selfInitiatedConnections[streamNumber] = {}
@ -150,6 +153,37 @@ def _fixSocket():
socket.IPV6_V6ONLY = 27 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) # This is a list of current connections (the thread pointers at least)
selfInitiatedConnections = {} selfInitiatedConnections = {}
@ -437,8 +471,8 @@ class Main:
os.kill(grandfatherPid, signal.SIGTERM) os.kill(grandfatherPid, signal.SIGTERM)
def setSignalHandler(self): def setSignalHandler(self):
signal.signal(signal.SIGINT, helper_generic.signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, helper_generic.signal_handler) signal.signal(signal.SIGTERM, signal_handler)
# signal.signal(signal.SIGINT, signal.SIG_DFL) # signal.signal(signal.SIGINT, signal.SIG_DFL)
def usage(self): def usage(self):

View File

@ -9,6 +9,7 @@ import random
import string import string
import sys import sys
import textwrap import textwrap
import threading
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from sqlite3 import register_adapter from sqlite3 import register_adapter
@ -44,7 +45,6 @@ from account import (
getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount, getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount,
GatewayAccount, MailchuckAccount, AccountColor) GatewayAccount, MailchuckAccount, AccountColor)
import dialogs import dialogs
from helper_generic import powQueueSize
from network.stats import pendingDownload, pendingUpload from network.stats import pendingDownload, pendingUpload
from uisignaler import UISignaler from uisignaler import UISignaler
import knownnodes import knownnodes
@ -107,6 +107,19 @@ def change_translation(newlocale):
logger.error("Failed to set locale to %s", lang, exc_info=True) 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): class MyForm(settingsmixin.SMainWindow):
# the last time that a message arrival sound was played # the last time that a message arrival sound was played
@ -117,6 +130,7 @@ class MyForm(settingsmixin.SMainWindow):
REPLY_TYPE_SENDER = 0 REPLY_TYPE_SENDER = 0
REPLY_TYPE_CHAN = 1 REPLY_TYPE_CHAN = 1
REPLY_TYPE_UPD = 2
def init_file_menu(self): def init_file_menu(self):
QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL( QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL(
@ -368,8 +382,6 @@ class MyForm(settingsmixin.SMainWindow):
self.on_context_menuSubscriptions) self.on_context_menuSubscriptions)
def init_sent_popup_menu(self, connectSignal=True): def init_sent_popup_menu(self, connectSignal=True):
# Popup menu for the Sent page
self.ui.sentContextMenuToolbar = QtGui.QToolBar()
# Actions # Actions
self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction( self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction(
_translate( _translate(
@ -381,6 +393,9 @@ class MyForm(settingsmixin.SMainWindow):
self.actionForceSend = self.ui.sentContextMenuToolbar.addAction( self.actionForceSend = self.ui.sentContextMenuToolbar.addAction(
_translate( _translate(
"MainWindow", "Force send"), self.on_action_ForceSend) "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 = QtGui.QMenu( self )
# self.popMenuSent.addAction( self.actionSentClipboard ) # self.popMenuSent.addAction( self.actionSentClipboard )
# self.popMenuSent.addAction( self.actionTrashSentMessage ) # self.popMenuSent.addAction( self.actionTrashSentMessage )
@ -2656,10 +2671,8 @@ class MyForm(settingsmixin.SMainWindow):
tableWidget.item(i, 3).setFont(font) tableWidget.item(i, 3).setFont(font)
markread = sqlExecuteChunked( markread = sqlExecuteChunked(
"UPDATE %s SET read = 1 WHERE %s IN({0}) AND read=0" % ( "UPDATE inbox SET read = 1 WHERE msgid IN({0}) AND read=0",
('sent', 'ackdata') if self.getCurrentFolder() == 'sent' idCount, *msgids
else ('inbox', 'msgid')
), idCount, *msgids
) )
if markread > 0: if markread > 0:
@ -2999,47 +3012,72 @@ class MyForm(settingsmixin.SMainWindow):
def on_action_InboxReplyChan(self): def on_action_InboxReplyChan(self):
self.on_action_InboxReply(self.REPLY_TYPE_CHAN) 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() tableWidget = self.getCurrentMessagelist()
if not tableWidget: if not tableWidget:
return return
if replyType is None: if reply_type is None:
replyType = self.REPLY_TYPE_SENDER reply_type = self.REPLY_TYPE_SENDER
# save this to return back after reply is done # save this to return back after reply is done
self.replyFromTab = self.ui.tabWidget.currentIndex() 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() currentInboxRow = tableWidget.currentRow()
toAddressAtCurrentInboxRow = tableWidget.item( toAddressAtCurrentInboxRow = tableWidget.item(
currentInboxRow, 0).address currentInboxRow, column_to).address
acct = accountClass(toAddressAtCurrentInboxRow) acct = accountClass(toAddressAtCurrentInboxRow)
fromAddressAtCurrentInboxRow = tableWidget.item( fromAddressAtCurrentInboxRow = tableWidget.item(
currentInboxRow, 1).address currentInboxRow, column_from).address
msgid = str(tableWidget.item( msgid = str(tableWidget.item(
currentInboxRow, 3).data(QtCore.Qt.UserRole).toPyObject()) currentInboxRow, 3).data(QtCore.Qt.UserRole).toPyObject())
queryreturn = sqlQuery( 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 != []: if queryreturn != []:
for row in queryreturn: for row in queryreturn:
messageAtCurrentInboxRow, = row messageAtCurrentInboxRow, = row
acct.parseMessage(toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow, tableWidget.item(currentInboxRow, 2).subject, messageAtCurrentInboxRow) acct.parseMessage(
toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow,
tableWidget.item(currentInboxRow, 2).subject,
messageAtCurrentInboxRow)
widget = { widget = {
'subject': self.ui.lineEditSubject, 'subject': self.ui.lineEditSubject,
'from': self.ui.comboBoxSendFrom, 'from': self.ui.comboBoxSendFrom,
'message': self.ui.textEditMessage 'message': self.ui.textEditMessage
} }
if toAddressAtCurrentInboxRow == str_broadcast_subscribers: if toAddressAtCurrentInboxRow == str_broadcast_subscribers:
self.ui.tabWidgetSend.setCurrentIndex( self.ui.tabWidgetSend.setCurrentIndex(
self.ui.tabWidgetSend.indexOf(self.ui.sendDirect) self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)
) )
# toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow # toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow
elif not BMConfigParser().has_section(toAddressAtCurrentInboxRow): elif not BMConfigParser().has_section(toAddressAtCurrentInboxRow):
QtGui.QMessageBox.information(self, _translate("MainWindow", "Address is gone"), _translate( QtGui.QMessageBox.information(
"MainWindow", "Bitmessage cannot find your address %1. Perhaps you removed it?").arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok) self, _translate("MainWindow", "Address is gone"),
elif not BMConfigParser().getboolean(toAddressAtCurrentInboxRow, 'enabled'): _translate(
QtGui.QMessageBox.information(self, _translate("MainWindow", "Address disabled"), _translate( "MainWindow",
"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) "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: else:
self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow) self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow)
broadcast_tab_index = self.ui.tabWidgetSend.indexOf( broadcast_tab_index = self.ui.tabWidgetSend.indexOf(
@ -3053,28 +3091,44 @@ class MyForm(settingsmixin.SMainWindow):
} }
self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index) self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index)
toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow
if fromAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 1).label or ( if fromAddressAtCurrentInboxRow == \
isinstance(acct, GatewayAccount) and fromAddressAtCurrentInboxRow == acct.relayAddress): tableWidget.item(currentInboxRow, column_from).label or (
isinstance(acct, GatewayAccount) and
fromAddressAtCurrentInboxRow == acct.relayAddress):
self.ui.lineEditTo.setText(str(acct.fromAddress)) self.ui.lineEditTo.setText(str(acct.fromAddress))
else: else:
self.ui.lineEditTo.setText(tableWidget.item(currentInboxRow, 1).label + " <" + str(acct.fromAddress) + ">") 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 the previous message was to a chan then we should send our
if acct.type == AccountMixin.CHAN and replyType == self.REPLY_TYPE_CHAN: # reply to the chan rather than to the particular person who sent
logger.debug('original sent to a chan. Setting the to address in the reply to the chan address.') # the message.
if toAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 0).label: 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)) self.ui.lineEditTo.setText(str(toAddressAtCurrentInboxRow))
else: 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) 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) widget['message'].setPlainText(quotedText)
if acct.subject[0:3] in ['Re:', 'RE:']: 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: 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.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send) self.ui.tabWidget.indexOf(self.ui.send)
) )
@ -3459,7 +3513,10 @@ class MyForm(settingsmixin.SMainWindow):
for plugin in self.menu_plugins['address']: for plugin in self.menu_plugins['address']:
self.popMenuSubscriptions.addAction(plugin) self.popMenuSubscriptions.addAction(plugin)
self.popMenuSubscriptions.addSeparator() self.popMenuSubscriptions.addSeparator()
if self.getCurrentFolder() != 'sent':
self.popMenuSubscriptions.addAction(self.actionMarkAllRead) self.popMenuSubscriptions.addAction(self.actionMarkAllRead)
if self.popMenuSubscriptions.isEmpty():
return
self.popMenuSubscriptions.exec_( self.popMenuSubscriptions.exec_(
self.ui.treeWidgetSubscriptions.mapToGlobal(point)) self.ui.treeWidgetSubscriptions.mapToGlobal(point))
@ -3874,8 +3931,10 @@ class MyForm(settingsmixin.SMainWindow):
for plugin in self.menu_plugins['address']: for plugin in self.menu_plugins['address']:
self.popMenuYourIdentities.addAction(plugin) self.popMenuYourIdentities.addAction(plugin)
self.popMenuYourIdentities.addSeparator() self.popMenuYourIdentities.addSeparator()
if self.getCurrentFolder() != 'sent':
self.popMenuYourIdentities.addAction(self.actionMarkAllRead) self.popMenuYourIdentities.addAction(self.actionMarkAllRead)
if self.popMenuYourIdentities.isEmpty():
return
self.popMenuYourIdentities.exec_( self.popMenuYourIdentities.exec_(
self.ui.treeWidgetYourIdentities.mapToGlobal(point)) self.ui.treeWidgetYourIdentities.mapToGlobal(point))
@ -3899,7 +3958,10 @@ class MyForm(settingsmixin.SMainWindow):
for plugin in self.menu_plugins['address']: for plugin in self.menu_plugins['address']:
self.popMenu.addAction(plugin) self.popMenu.addAction(plugin)
self.popMenu.addSeparator() self.popMenu.addSeparator()
if self.getCurrentFolder() != 'sent':
self.popMenu.addAction(self.actionMarkAllRead) self.popMenu.addAction(self.actionMarkAllRead)
if self.popMenu.isEmpty():
return
self.popMenu.exec_( self.popMenu.exec_(
self.ui.treeWidgetChans.mapToGlobal(point)) self.ui.treeWidgetChans.mapToGlobal(point))
@ -3943,6 +4005,7 @@ class MyForm(settingsmixin.SMainWindow):
self.popMenuSent = QtGui.QMenu(self) self.popMenuSent = QtGui.QMenu(self)
self.popMenuSent.addAction(self.actionSentClipboard) self.popMenuSent.addAction(self.actionSentClipboard)
self.popMenuSent.addAction(self.actionTrashSentMessage) self.popMenuSent.addAction(self.actionTrashSentMessage)
self.popMenuSent.addAction(self.actionSentReply)
# Check to see if this item is toodifficult and display an additional # Check to see if this item is toodifficult and display an additional
# menu option (Force Send) if it is. # menu option (Force Send) if it is.

View File

@ -651,6 +651,10 @@ class Ui_MainWindow(object):
MainWindow.setTabOrder(self.lineEditSubject, self.textEditMessage) MainWindow.setTabOrder(self.lineEditSubject, self.textEditMessage)
MainWindow.setTabOrder(self.textEditMessage, self.pushButtonAddSubscription) 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): def updateNetworkSwitchMenuLabel(self, dontconnect=None):
if dontconnect is None: if dontconnect is None:
dontconnect = BMConfigParser().safeGetBoolean( dontconnect = BMConfigParser().safeGetBoolean(

View File

@ -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))

View File

@ -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 # We are going to share a maximum number of 1000 addrs (per overlapping
# stream) with our peer. 500 from overlapping streams, 250 from the # stream) with our peer. 500 from overlapping streams, 250 from the
# left child stream, and 250 from the right child stream. # left child stream, and 250 from the right child stream.
maxAddrCount = BMConfigParser().safeGetInt("bitmessagesettings", "maxaddrperstreamsend", 500) maxAddrCount = BMConfigParser().safeGetInt(
"bitmessagesettings", "maxaddrperstreamsend", 500)
# init
templist = [] templist = []
addrs = {} addrs = {}
for stream in self.streams: for stream in self.streams:
with knownnodes.knownNodesLock: with knownnodes.knownNodesLock:
if knownnodes.knownNodes[stream]: for n, s in enumerate((stream, stream * 2, stream * 2 + 1)):
filtered = {k: v for k, v in knownnodes.knownNodes[stream].items() nodes = knownnodes.knownNodes.get(s)
if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} if not nodes:
elemCount = len(filtered) continue
if elemCount > maxAddrCount:
elemCount = maxAddrCount
# only if more recent than 3 hours # only if more recent than 3 hours
addrs[stream] = 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 # sent 250 only if the remote isn't interested in it
if knownnodes.knownNodes[stream * 2] and stream not in self.streams: elemCount = min(
filtered = {k: v for k, v in knownnodes.knownNodes[stream * 2].items() len(filtered),
if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} maxAddrCount / 2 if n else maxAddrCount)
elemCount = len(filtered) addrs[s] = helper_random.randomsample(filtered, elemCount)
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)
for substream in addrs: for substream in addrs:
for peer, params in addrs[substream]: for peer, params in addrs[substream]:
templist.append((substream, peer, params["lastseen"])) templist.append((substream, peer, params["lastseen"]))

View File

@ -89,6 +89,24 @@ class TestProcessProto(unittest.TestCase):
len(self.process.threads()), self._threads_count) 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): class TestProcess(TestProcessProto):
"""A test case for pybitmessage process""" """A test case for pybitmessage process"""
def test_process_name(self): def test_process_name(self):