bitmessagemain.py.bak
1 #!/usr/bin/env python 2 """ 3 The PyBitmessage startup script 4 """ 5 # Copyright (c) 2012-2016 Jonathan Warren 6 # Copyright (c) 2012-2022 The Bitmessage developers 7 # Distributed under the MIT/X11 software license. See the accompanying 8 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 9 10 # Right now, PyBitmessage only support connecting to stream 1. It doesn't 11 # yet contain logic to expand into further streams. 12 import os 13 import sys 14 15 16 try: 17 import pathmagic 18 except ImportError: 19 from pybitmessage import pathmagic 20 app_dir = pathmagic.setup() 21 22 import depends 23 depends.check_dependencies() 24 25 import getopt 26 import multiprocessing 27 # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. 28 import signal 29 import threading 30 import time 31 import traceback 32 33 import defaults 34 # Network subsystem 35 import network 36 import shutdown 37 import state 38 39 from testmode_init import populate_api_test_data 40 from bmconfigparser import config 41 from debug import logger # this should go before any threads 42 from helper_startup import ( 43 adjustHalfOpenConnectionsLimit, fixSocket, start_proxyconfig) 44 from inventory import Inventory 45 from singleinstance import singleinstance 46 # Synchronous threads 47 from threads import ( 48 set_thread_name, printLock, 49 addressGenerator, objectProcessor, singleCleaner, singleWorker, sqlThread) 50 51 52 def signal_handler(signum, frame): 53 """Single handler for any signal sent to pybitmessage""" 54 process = multiprocessing.current_process() 55 thread = threading.current_thread() 56 logger.error( 57 'Got signal %i in %s/%s', 58 signum, process.name, thread.name 59 ) 60 if process.name == "RegExParser": 61 # on Windows this isn't triggered, but it's fine, 62 # it has its own process termination thing 63 raise SystemExit 64 if "PoolWorker" in process.name: 65 raise SystemExit 66 if thread.name not in ("PyBitmessage", "MainThread"): 67 return 68 logger.error("Got signal %i", signum) 69 # there are possible non-UI variants to run bitmessage 70 # which should shutdown especially test-mode 71 if state.thisapp.daemon or not state.enableGUI: 72 shutdown.doCleanShutdown() 73 else: 74 print('# Thread: %s(%d)' % (thread.name, thread.ident)) 75 for filename, lineno, name, line in traceback.extract_stack(frame): 76 print('File: "%s", line %d, in %s' % (filename, lineno, name)) 77 if line: 78 print(' %s' % line.strip()) 79 print('Unfortunately you cannot use Ctrl+C when running the UI' 80 ' because the UI captures the signal.') 81 82 83 class Main(object): 84 """Main PyBitmessage class""" 85 def start(self): 86 """Start main application""" 87 # pylint: disable=too-many-statements,too-many-branches,too-many-locals 88 fixSocket() 89 adjustHalfOpenConnectionsLimit() 90 91 daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') 92 93 try: 94 opts, _ = getopt.getopt( 95 sys.argv[1:], "hcdt", 96 ["help", "curses", "daemon", "test"]) 97 98 except getopt.GetoptError: 99 self.usage() 100 sys.exit(2) 101 102 for opt, _ in opts: 103 if opt in ("-h", "--help"): 104 self.usage() 105 sys.exit() 106 elif opt in ("-d", "--daemon"): 107 daemon = True 108 elif opt in ("-c", "--curses"): 109 state.curses = True 110 elif opt in ("-t", "--test"): 111 state.testmode = True 112 if os.path.isfile(os.path.join( 113 state.appdata, 'unittest.lock')): 114 daemon = True 115 state.enableGUI = False # run without a UI 116 # Fallback: in case when no api command was issued 117 state.last_api_response = time.time() 118 # Apply special settings 119 config.set( 120 'bitmessagesettings', 'apienabled', 'true') 121 config.set( 122 'bitmessagesettings', 'apiusername', 'username') 123 config.set( 124 'bitmessagesettings', 'apipassword', 'password') 125 config.set( 126 'bitmessagesettings', 'apivariant', 'legacy') 127 config.set( 128 'bitmessagesettings', 'apinotifypath', 129 os.path.join(app_dir, 'tests', 'apinotify_handler.py') 130 ) 131 132 if daemon: 133 state.enableGUI = False # run without a UI 134 135 if state.enableGUI and not state.curses and not depends.check_pyqt(): 136 sys.exit( 137 'PyBitmessage requires PyQt unless you want' 138 ' to run it as a daemon and interact with it' 139 ' using the API. You can download PyQt from ' 140 'http://www.riverbankcomputing.com/software/pyqt/download' 141 ' or by searching Google for \'PyQt Download\'.' 142 ' If you want to run in daemon mode, see ' 143 'https://bitmessage.org/wiki/Daemon\n' 144 'You can also run PyBitmessage with' 145 ' the new curses interface by providing' 146 ' \'-c\' as a commandline argument.' 147 ) 148 # is the application already running? If yes then exit. 149 state.thisapp = singleinstance("", daemon) 150 151 if daemon: 152 with printLock: 153 print('Running as a daemon. Send TERM signal to end.') 154 self.daemonize() 155 156 self.setSignalHandler() 157 158 set_thread_name("PyBitmessage") 159 160 if state.testmode or config.safeGetBoolean( 161 'bitmessagesettings', 'extralowdifficulty'): 162 defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( 163 defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100) 164 defaults.networkDefaultPayloadLengthExtraBytes = int( 165 defaults.networkDefaultPayloadLengthExtraBytes / 100) 166 167 # Start the SQL thread 168 sqlLookup = sqlThread() 169 # DON'T close the main program even if there are threads left. 170 # The closeEvent should command this thread to exit gracefully. 171 sqlLookup.daemon = False 172 sqlLookup.start() 173 state.Inventory = Inventory() # init 174 175 if state.enableObjProc: # Not needed if objproc is disabled 176 # Start the address generation thread 177 addressGeneratorThread = addressGenerator() 178 # close the main program even if there are threads left 179 addressGeneratorThread.daemon = True 180 addressGeneratorThread.start() 181 182 # Start the thread that calculates POWs 183 singleWorkerThread = singleWorker() 184 # close the main program even if there are threads left 185 singleWorkerThread.daemon = True 186 singleWorkerThread.start() 187 188 # Start the object processing thread 189 objectProcessorThread = objectProcessor() 190 # DON'T close the main program even if the thread remains. 191 # This thread checks the shutdown variable after processing 192 # each object. 193 objectProcessorThread.daemon = False 194 objectProcessorThread.start() 195 196 # SMTP delivery thread 197 if daemon and config.safeGet( 198 'bitmessagesettings', 'smtpdeliver', '') != '': 199 from class_smtpDeliver import smtpDeliver 200 smtpDeliveryThread = smtpDeliver() 201 smtpDeliveryThread.start() 202 203 # SMTP daemon thread 204 if daemon and config.safeGetBoolean( 205 'bitmessagesettings', 'smtpd'): 206 from class_smtpServer import smtpServer 207 smtpServerThread = smtpServer() 208 smtpServerThread.start() 209 210 # API is also objproc dependent 211 if config.safeGetBoolean('bitmessagesettings', 'apienabled'): 212 import api # pylint: disable=relative-import 213 singleAPIThread = api.singleAPI() 214 # close the main program even if there are threads left 215 singleAPIThread.daemon = True 216 singleAPIThread.start() 217 218 # Start the cleanerThread 219 singleCleanerThread = singleCleaner() 220 # close the main program even if there are threads left 221 singleCleanerThread.daemon = True 222 singleCleanerThread.start() 223 224 # start network components if networking is enabled 225 if state.enableNetwork: 226 start_proxyconfig() 227 network.start(config, state) 228 229 if config.safeGetBoolean('bitmessagesettings', 'upnp'): 230 import upnp 231 upnpThread = upnp.uPnPThread() 232 upnpThread.start() 233 else: 234 network.connectionpool.pool.connectToStream(1) 235 236 if not daemon and state.enableGUI: 237 if state.curses: 238 if not depends.check_curses(): 239 sys.exit() 240 print('Running with curses') 241 import bitmessagecurses 242 bitmessagecurses.runwrapper() 243 else: 244 import bitmessageqt 245 bitmessageqt.run() 246 else: 247 config.remove_option('bitmessagesettings', 'dontconnect') 248 249 if state.testmode: 250 populate_api_test_data() 251 252 if daemon: 253 while state.shutdown == 0: 254 time.sleep(1) 255 if ( 256 state.testmode 257 and time.time() - state.last_api_response >= 30 258 ): 259 self.stop() 260 elif not state.enableGUI: 261 state.enableGUI = True 262 try: 263 # pylint: disable=relative-import 264 from tests import core as test_core 265 except ImportError: 266 try: 267 from pybitmessage.tests import core as test_core 268 except ImportError: 269 self.stop() 270 return 271 272 test_core_result = test_core.run() 273 self.stop() 274 test_core.cleanup() 275 sys.exit(not test_core_result.wasSuccessful()) 276 277 @staticmethod 278 def daemonize(): 279 """Running as a daemon. Send signal in end.""" 280 grandfatherPid = os.getpid() 281 parentPid = None 282 try: 283 if os.fork(): 284 # unlock 285 state.thisapp.cleanup() 286 # wait until grandchild ready 287 while True: 288 time.sleep(1) 289 os._exit(0) # pylint: disable=protected-access 290 except AttributeError: 291 # fork not implemented 292 pass 293 else: 294 parentPid = os.getpid() 295 state.thisapp.lock() # relock 296 297 os.umask(0) 298 try: 299 os.setsid() 300 except AttributeError: 301 # setsid not implemented 302 pass 303 try: 304 if os.fork(): 305 # unlock 306 state.thisapp.cleanup() 307 # wait until child ready 308 while True: 309 time.sleep(1) 310 os._exit(0) # pylint: disable=protected-access 311 except AttributeError: 312 # fork not implemented 313 pass 314 else: 315 state.thisapp.lock() # relock 316 state.thisapp.lockPid = None # indicate we're the final child 317 sys.stdout.flush() 318 sys.stderr.flush() 319 if not sys.platform.startswith('win'): 320 si = open(os.devnull, 'r') 321 so = open(os.devnull, 'a+') 322 se = open(os.devnull, 'a+', 0) 323 os.dup2(si.fileno(), sys.stdin.fileno()) 324 os.dup2(so.fileno(), sys.stdout.fileno()) 325 os.dup2(se.fileno(), sys.stderr.fileno()) 326 if parentPid: 327 # signal ready 328 os.kill(parentPid, signal.SIGTERM) 329 os.kill(grandfatherPid, signal.SIGTERM) 330 331 @staticmethod 332 def setSignalHandler(): 333 """Setting the Signal Handler""" 334 signal.signal(signal.SIGINT, signal_handler) 335 signal.signal(signal.SIGTERM, signal_handler) 336 # signal.signal(signal.SIGINT, signal.SIG_DFL) 337 338 @staticmethod 339 def usage(): 340 """Displaying the usages""" 341 print('Usage: ' + sys.argv[0] + ' [OPTIONS]') 342 print(''' 343 Options: 344 -h, --help show this help message and exit 345 -c, --curses use curses (text mode) interface 346 -d, --daemon run in daemon (background) mode 347 -t, --test dryrun, make testing 348 349 All parameters are optional. 350 ''') 351 352 @staticmethod 353 def stop(): 354 """Stop main application""" 355 with printLock: 356 print('Stopping Bitmessage Deamon.') 357 shutdown.doCleanShutdown() 358 359 # .. todo:: nice function but no one is using this 360 @staticmethod 361 def getApiAddress(): 362 """This function returns API address and port""" 363 if not config.safeGetBoolean( 364 'bitmessagesettings', 'apienabled'): 365 return None 366 address = config.get('bitmessagesettings', 'apiinterface') 367 port = config.getint('bitmessagesettings', 'apiport') 368 return {'address': address, 'port': port} 369 370 371 def main(): 372 """Triggers main module""" 373 mainprogram = Main() 374 mainprogram.start() 375 376 377 if __name__ == "__main__": 378 main() 379 380 381 # So far, the creation of and management of the Bitmessage protocol and this 382 # client is a one-man operation. Bitcoin tips are quite appreciated. 383 # 1H5XaDA6fYENLbknwZyjiYXYPQaFjjLX2u