This repository has been archived on 2024-12-07. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2024-12-07/src/bitmessagemain.py
2023-01-07 02:11:32 +01:00

396 lines
14 KiB
Python
Executable File

#!/usr/bin/env python
"""
The PyBitmessage startup script
"""
# Copyright (c) 2012-2016 Jonathan Warren
# Copyright (c) 2012-2023 The Bitmessage developers
# Distributed under the MIT/X11 software license. See the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Right now, PyBitmessage only support connecting to stream 1. It doesn't
# yet contain logic to expand into further streams.
import os
import sys
try:
import pathmagic
except ImportError:
from pybitmessage import pathmagic
app_dir = pathmagic.setup()
import depends
depends.check_dependencies()
import getopt
import multiprocessing
# Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully.
import signal
import threading
import time
import traceback
import defaults
# Network subsystem
import network
import shared
import shutdown
import state
from testmode_init import populate_api_test_data
from bmconfigparser import config
from debug import logger # this should go before any threads
from helper_startup import (
adjustHalfOpenConnectionsLimit, fixSocket, start_proxyconfig)
from inventory import Inventory
from singleinstance import singleinstance
# Synchronous threads
from threads import (
set_thread_name, printLock,
addressGenerator, objectProcessor, singleCleaner, singleWorker, sqlThread)
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 state.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.')
class Main(object):
"""Main PyBitmessage class"""
def start(self):
"""Start main application"""
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
fixSocket()
adjustHalfOpenConnectionsLimit()
daemon = config.safeGetBoolean('bitmessagesettings', 'daemon')
try:
opts, _ = getopt.getopt(
sys.argv[1:], "hcdt",
["help", "curses", "daemon", "test"])
except getopt.GetoptError:
self.usage()
sys.exit(2)
for opt, _ in opts:
if opt in ("-h", "--help"):
self.usage()
sys.exit()
elif opt in ("-d", "--daemon"):
daemon = True
elif opt in ("-c", "--curses"):
state.curses = True
elif opt in ("-t", "--test"):
state.testmode = True
if os.path.isfile(os.path.join(
state.appdata, 'unittest.lock')):
daemon = True
state.enableGUI = False # run without a UI
# Fallback: in case when no api command was issued
state.last_api_response = time.time()
# Apply special settings
config.set(
'bitmessagesettings', 'apienabled', 'true')
config.set(
'bitmessagesettings', 'apiusername', 'username')
config.set(
'bitmessagesettings', 'apipassword', 'password')
config.set(
'bitmessagesettings', 'apivariant', 'legacy')
config.set(
'bitmessagesettings', 'apinotifypath',
os.path.join(app_dir, 'tests', 'apinotify_handler.py')
)
if daemon:
state.enableGUI = False # run without a UI
if state.enableGUI and not state.curses and not depends.check_pyqt():
sys.exit(
'PyBitmessage requires PyQt unless you want'
' to run it as a daemon and interact with it'
' using the API. You can download PyQt from '
'http://www.riverbankcomputing.com/software/pyqt/download'
' or by searching Google for \'PyQt Download\'.'
' If you want to run in daemon mode, see '
'https://bitmessage.org/wiki/Daemon\n'
'You can also run PyBitmessage with'
' the new curses interface by providing'
' \'-c\' as a commandline argument.'
)
# is the application already running? If yes then exit.
state.thisapp = singleinstance("", daemon)
if daemon:
with printLock:
print('Running as a daemon. Send TERM signal to end.')
self.daemonize()
self.setSignalHandler()
set_thread_name("PyBitmessage")
state.dandelion = config.safeGetInt('network', 'dandelion')
# dandelion requires outbound connections, without them,
# stem objects will get stuck forever
if state.dandelion and not config.safeGetBoolean(
'bitmessagesettings', 'sendoutgoingconnections'):
state.dandelion = 0
if state.testmode or config.safeGetBoolean(
'bitmessagesettings', 'extralowdifficulty'):
defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100)
defaults.networkDefaultPayloadLengthExtraBytes = int(
defaults.networkDefaultPayloadLengthExtraBytes / 100)
# Start the SQL thread
sqlLookup = sqlThread()
# DON'T close the main program even if there are threads left.
# The closeEvent should command this thread to exit gracefully.
sqlLookup.daemon = False
sqlLookup.start()
Inventory() # init
if state.enableObjProc: # Not needed if objproc is disabled
shared.reloadMyAddressHashes()
shared.reloadBroadcastSendersForWhichImWatching()
# Start the address generation thread
addressGeneratorThread = addressGenerator()
# close the main program even if there are threads left
addressGeneratorThread.daemon = True
addressGeneratorThread.start()
# Start the thread that calculates POWs
singleWorkerThread = singleWorker()
# close the main program even if there are threads left
singleWorkerThread.daemon = True
singleWorkerThread.start()
# Start the object processing thread
objectProcessorThread = objectProcessor()
# DON'T close the main program even if the thread remains.
# This thread checks the shutdown variable after processing
# each object.
objectProcessorThread.daemon = False
objectProcessorThread.start()
# SMTP delivery thread
if daemon and config.safeGet(
'bitmessagesettings', 'smtpdeliver', '') != '':
from class_smtpDeliver import smtpDeliver
smtpDeliveryThread = smtpDeliver()
smtpDeliveryThread.start()
# SMTP daemon thread
if daemon and config.safeGetBoolean(
'bitmessagesettings', 'smtpd'):
from class_smtpServer import smtpServer
smtpServerThread = smtpServer()
smtpServerThread.start()
# API is also objproc dependent
if config.safeGetBoolean('bitmessagesettings', 'apienabled'):
import api # pylint: disable=relative-import
singleAPIThread = api.singleAPI()
# close the main program even if there are threads left
singleAPIThread.daemon = True
singleAPIThread.start()
# Start the cleanerThread
singleCleanerThread = singleCleaner()
# close the main program even if there are threads left
singleCleanerThread.daemon = True
singleCleanerThread.start()
# start network components if networking is enabled
if state.enableNetwork:
start_proxyconfig()
network.start(config, state)
if config.safeGetBoolean('bitmessagesettings', 'upnp'):
import upnp
upnpThread = upnp.uPnPThread()
upnpThread.start()
else:
# Populate with hardcoded value (same as connectToStream above)
state.streamsInWhichIAmParticipating.append(1)
if not daemon and state.enableGUI:
if state.curses:
if not depends.check_curses():
sys.exit()
print('Running with curses')
import bitmessagecurses
bitmessagecurses.runwrapper()
else:
import bitmessageqt
bitmessageqt.run()
else:
config.remove_option('bitmessagesettings', 'dontconnect')
if state.testmode:
populate_api_test_data()
if daemon:
while state.shutdown == 0:
time.sleep(1)
if (
state.testmode
and time.time() - state.last_api_response >= 30
):
self.stop()
elif not state.enableGUI:
state.enableGUI = True
try:
# pylint: disable=relative-import
from tests import core as test_core
except ImportError:
try:
from pybitmessage.tests import core as test_core
except ImportError:
self.stop()
return
test_core_result = test_core.run()
self.stop()
test_core.cleanup()
sys.exit(not test_core_result.wasSuccessful())
@staticmethod
def daemonize():
"""Running as a daemon. Send signal in end."""
grandfatherPid = os.getpid()
parentPid = None
try:
if os.fork():
# unlock
state.thisapp.cleanup()
# wait until grandchild ready
while True:
time.sleep(1)
os._exit(0) # pylint: disable=protected-access
except AttributeError:
# fork not implemented
pass
else:
parentPid = os.getpid()
state.thisapp.lock() # relock
os.umask(0)
try:
os.setsid()
except AttributeError:
# setsid not implemented
pass
try:
if os.fork():
# unlock
state.thisapp.cleanup()
# wait until child ready
while True:
time.sleep(1)
os._exit(0) # pylint: disable=protected-access
except AttributeError:
# fork not implemented
pass
else:
state.thisapp.lock() # relock
state.thisapp.lockPid = None # indicate we're the final child
sys.stdout.flush()
sys.stderr.flush()
if not sys.platform.startswith('win'):
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
if parentPid:
# signal ready
os.kill(parentPid, signal.SIGTERM)
os.kill(grandfatherPid, signal.SIGTERM)
@staticmethod
def setSignalHandler():
"""Setting the Signal Handler"""
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# signal.signal(signal.SIGINT, signal.SIG_DFL)
@staticmethod
def usage():
"""Displaying the usages"""
print('Usage: ' + sys.argv[0] + ' [OPTIONS]')
print('''
Options:
-h, --help show this help message and exit
-c, --curses use curses (text mode) interface
-d, --daemon run in daemon (background) mode
-t, --test dryrun, make testing
All parameters are optional.
''')
@staticmethod
def stop():
"""Stop main application"""
with printLock:
print('Stopping Bitmessage Deamon.')
shutdown.doCleanShutdown()
# .. todo:: nice function but no one is using this
@staticmethod
def getApiAddress():
"""This function returns API address and port"""
if not config.safeGetBoolean(
'bitmessagesettings', 'apienabled'):
return None
address = config.get('bitmessagesettings', 'apiinterface')
port = config.getint('bitmessagesettings', 'apiport')
return {'address': address, 'port': port}
def main():
"""Triggers main module"""
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