Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
at main 673 lines 34 kB view raw
1import argparse 2import sys 3import os 4import locale 5import re 6import configparser 7import logging 8import logging.handlers 9import stat 10import time 11 12 13class Config(object): 14 15 def __init__(self, argv): 16 self.version = "0.7.2" 17 self.rev = 4555 18 self.argv = argv 19 self.action = None 20 self.test_parser = None 21 self.pending_changes = {} 22 self.need_restart = False 23 self.keys_api_change_allowed = set([ 24 "tor", "fileserver_port", "language", "tor_use_bridges", "trackers_proxy", "trackers", 25 "trackers_file", "open_browser", "log_level", "fileserver_ip_type", "ip_external", "offline", 26 "threads_fs_read", "threads_fs_write", "threads_crypt", "threads_db" 27 ]) 28 self.keys_restart_need = set([ 29 "tor", "fileserver_port", "fileserver_ip_type", "threads_fs_read", "threads_fs_write", "threads_crypt", "threads_db" 30 ]) 31 self.start_dir = self.getStartDir() 32 33 self.config_file = self.start_dir + "/zeronet.conf" 34 self.data_dir = self.start_dir + "/data" 35 self.log_dir = self.start_dir + "/log" 36 self.openssl_lib_file = None 37 self.openssl_bin_file = None 38 39 self.trackers_file = False 40 self.createParser() 41 self.createArguments() 42 43 def createParser(self): 44 # Create parser 45 self.parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) 46 self.parser.register('type', 'bool', self.strToBool) 47 self.subparsers = self.parser.add_subparsers(title="Action to perform", dest="action") 48 49 def __str__(self): 50 return str(self.arguments).replace("Namespace", "Config") # Using argparse str output 51 52 # Convert string to bool 53 def strToBool(self, v): 54 return v.lower() in ("yes", "true", "t", "1") 55 56 def getStartDir(self): 57 this_file = os.path.abspath(__file__).replace("\\", "/").rstrip("cd") 58 59 if "--start_dir" in self.argv: 60 start_dir = self.argv[self.argv.index("--start_dir") + 1] 61 elif this_file.endswith("/Contents/Resources/core/src/Config.py"): 62 # Running as ZeroNet.app 63 if this_file.startswith("/Application") or this_file.startswith("/private") or this_file.startswith(os.path.expanduser("~/Library")): 64 # Runnig from non-writeable directory, put data to Application Support 65 start_dir = os.path.expanduser("~/Library/Application Support/ZeroNet") 66 else: 67 # Running from writeable directory put data next to .app 68 start_dir = re.sub("/[^/]+/Contents/Resources/core/src/Config.py", "", this_file) 69 elif this_file.endswith("/core/src/Config.py"): 70 # Running as exe or source is at Application Support directory, put var files to outside of core dir 71 start_dir = this_file.replace("/core/src/Config.py", "") 72 elif this_file.endswith("usr/share/zeronet/src/Config.py"): 73 # Running from non-writeable location, e.g., AppImage 74 start_dir = os.path.expanduser("~/ZeroNet") 75 else: 76 start_dir = "." 77 78 return start_dir 79 80 # Create command line arguments 81 def createArguments(self): 82 trackers = [ 83 "zero://boot3rdez4rzn36x.onion:15441", 84 "zero://zero.booth.moe#f36ca555bee6ba216b14d10f38c16f7769ff064e0e37d887603548cc2e64191d:443", # US/NY 85 "udp://tracker.coppersurfer.tk:6969", # DE 86 "udp://104.238.198.186:8000", # US/LA 87 "udp://retracker.akado-ural.ru:80", # RU 88 "http://h4.trakx.nibba.trade:80/announce", # US/VA 89 "http://open.acgnxtracker.com:80/announce", # DE 90 "http://tracker.bt4g.com:2095/announce", # Cloudflare 91 "zero://2602:ffc5::c5b2:5360:26312" # US/ATL 92 ] 93 # Platform specific 94 if sys.platform.startswith("win"): 95 coffeescript = "type %s | tools\\coffee\\coffee.cmd" 96 else: 97 coffeescript = None 98 99 try: 100 language, enc = locale.getdefaultlocale() 101 language = language.lower().replace("_", "-") 102 if language not in ["pt-br", "zh-tw"]: 103 language = language.split("-")[0] 104 except Exception: 105 language = "en" 106 107 use_openssl = True 108 109 if repr(1483108852.565) != "1483108852.565": # Fix for weird Android issue 110 fix_float_decimals = True 111 else: 112 fix_float_decimals = False 113 114 config_file = self.start_dir + "/zeronet.conf" 115 data_dir = self.start_dir + "/data" 116 log_dir = self.start_dir + "/log" 117 118 ip_local = ["127.0.0.1", "::1"] 119 120 # Main 121 action = self.subparsers.add_parser("main", help='Start UiServer and FileServer (default)') 122 123 # SiteCreate 124 action = self.subparsers.add_parser("siteCreate", help='Create a new site') 125 action.register('type', 'bool', self.strToBool) 126 action.add_argument('--use_master_seed', help="Allow created site's private key to be recovered using the master seed in users.json (default: True)", type="bool", choices=[True, False], default=True) 127 128 # SiteNeedFile 129 action = self.subparsers.add_parser("siteNeedFile", help='Get a file from site') 130 action.add_argument('address', help='Site address') 131 action.add_argument('inner_path', help='File inner path') 132 133 # SiteDownload 134 action = self.subparsers.add_parser("siteDownload", help='Download a new site') 135 action.add_argument('address', help='Site address') 136 137 # SiteSign 138 action = self.subparsers.add_parser("siteSign", help='Update and sign content.json: address [privatekey]') 139 action.add_argument('address', help='Site to sign') 140 action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?') 141 action.add_argument('--inner_path', help='File you want to sign (default: content.json)', 142 default="content.json", metavar="inner_path") 143 action.add_argument('--remove_missing_optional', help='Remove optional files that is not present in the directory', action='store_true') 144 action.add_argument('--publish', help='Publish site after the signing', action='store_true') 145 146 # SitePublish 147 action = self.subparsers.add_parser("sitePublish", help='Publish site to other peers: address') 148 action.add_argument('address', help='Site to publish') 149 action.add_argument('peer_ip', help='Peer ip to publish (default: random peers ip from tracker)', 150 default=None, nargs='?') 151 action.add_argument('peer_port', help='Peer port to publish (default: random peer port from tracker)', 152 default=15441, nargs='?') 153 action.add_argument('--inner_path', help='Content.json you want to publish (default: content.json)', 154 default="content.json", metavar="inner_path") 155 156 # SiteVerify 157 action = self.subparsers.add_parser("siteVerify", help='Verify site files using sha512: address') 158 action.add_argument('address', help='Site to verify') 159 160 # SiteCmd 161 action = self.subparsers.add_parser("siteCmd", help='Execute a ZeroFrame API command on a site') 162 action.add_argument('address', help='Site address') 163 action.add_argument('cmd', help='API command name') 164 action.add_argument('parameters', help='Parameters of the command', nargs='?') 165 166 # dbRebuild 167 action = self.subparsers.add_parser("dbRebuild", help='Rebuild site database cache') 168 action.add_argument('address', help='Site to rebuild') 169 170 # dbQuery 171 action = self.subparsers.add_parser("dbQuery", help='Query site sql cache') 172 action.add_argument('address', help='Site to query') 173 action.add_argument('query', help='Sql query') 174 175 # PeerPing 176 action = self.subparsers.add_parser("peerPing", help='Send Ping command to peer') 177 action.add_argument('peer_ip', help='Peer ip') 178 action.add_argument('peer_port', help='Peer port', nargs='?') 179 180 # PeerGetFile 181 action = self.subparsers.add_parser("peerGetFile", help='Request and print a file content from peer') 182 action.add_argument('peer_ip', help='Peer ip') 183 action.add_argument('peer_port', help='Peer port') 184 action.add_argument('site', help='Site address') 185 action.add_argument('filename', help='File name to request') 186 action.add_argument('--benchmark', help='Request file 10x then displays the total time', action='store_true') 187 188 # PeerCmd 189 action = self.subparsers.add_parser("peerCmd", help='Request and print a file content from peer') 190 action.add_argument('peer_ip', help='Peer ip') 191 action.add_argument('peer_port', help='Peer port') 192 action.add_argument('cmd', help='Command to execute') 193 action.add_argument('parameters', help='Parameters to command', nargs='?') 194 195 # CryptSign 196 action = self.subparsers.add_parser("cryptSign", help='Sign message using Bitcoin private key') 197 action.add_argument('message', help='Message to sign') 198 action.add_argument('privatekey', help='Private key') 199 200 # Crypt Verify 201 action = self.subparsers.add_parser("cryptVerify", help='Verify message using Bitcoin public address') 202 action.add_argument('message', help='Message to verify') 203 action.add_argument('sign', help='Signiture for message') 204 action.add_argument('address', help='Signer\'s address') 205 206 # Crypt GetPrivatekey 207 action = self.subparsers.add_parser("cryptGetPrivatekey", help='Generate a privatekey from master seed') 208 action.add_argument('master_seed', help='Source master seed') 209 action.add_argument('site_address_index', help='Site address index', type=int) 210 211 action = self.subparsers.add_parser("getConfig", help='Return json-encoded info') 212 action = self.subparsers.add_parser("testConnection", help='Testing') 213 action = self.subparsers.add_parser("testAnnounce", help='Testing') 214 215 self.test_parser = self.subparsers.add_parser("test", help='Run a test') 216 self.test_parser.add_argument('test_name', help='Test name', nargs="?") 217 # self.test_parser.add_argument('--benchmark', help='Run the tests multiple times to measure the performance', action='store_true') 218 219 # Config parameters 220 self.parser.add_argument('--verbose', help='More detailed logging', action='store_true') 221 self.parser.add_argument('--debug', help='Debug mode', action='store_true') 222 self.parser.add_argument('--silent', help='Only log errors to terminal output', action='store_true') 223 self.parser.add_argument('--debug_socket', help='Debug socket connections', action='store_true') 224 self.parser.add_argument('--merge_media', help='Merge all.js and all.css', action='store_true') 225 226 self.parser.add_argument('--batch', help="Batch mode (No interactive input for commands)", action='store_true') 227 228 self.parser.add_argument('--start_dir', help='Path of working dir for variable content (data, log, .conf)', default=self.start_dir, metavar="path") 229 self.parser.add_argument('--config_file', help='Path of config file', default=config_file, metavar="path") 230 self.parser.add_argument('--data_dir', help='Path of data directory', default=data_dir, metavar="path") 231 232 self.parser.add_argument('--console_log_level', help='Level of logging to console', default="default", choices=["default", "DEBUG", "INFO", "ERROR", "off"]) 233 234 self.parser.add_argument('--log_dir', help='Path of logging directory', default=log_dir, metavar="path") 235 self.parser.add_argument('--log_level', help='Level of logging to file', default="DEBUG", choices=["DEBUG", "INFO", "ERROR", "off"]) 236 self.parser.add_argument('--log_rotate', help='Log rotate interval', default="daily", choices=["hourly", "daily", "weekly", "off"]) 237 self.parser.add_argument('--log_rotate_backup_count', help='Log rotate backup count', default=5, type=int) 238 239 self.parser.add_argument('--language', help='Web interface language', default=language, metavar='language') 240 self.parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='ip') 241 self.parser.add_argument('--ui_port', help='Web interface bind port', default=43110, type=int, metavar='port') 242 self.parser.add_argument('--ui_restrict', help='Restrict web access', default=False, metavar='ip', nargs='*') 243 self.parser.add_argument('--ui_host', help='Allow access using this hosts', metavar='host', nargs='*') 244 self.parser.add_argument('--ui_trans_proxy', help='Allow access using a transparent proxy', action='store_true') 245 246 self.parser.add_argument('--open_browser', help='Open homepage in web browser automatically', 247 nargs='?', const="default_browser", metavar='browser_name') 248 self.parser.add_argument('--homepage', help='Web interface Homepage', default='1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D', 249 metavar='address') 250 self.parser.add_argument('--updatesite', help='Source code update site', default='1uPDaT3uSyWAPdCv1WkMb5hBQjWSNNACf', 251 metavar='address') 252 self.parser.add_argument('--dist_type', help='Type of installed distribution', default='source') 253 254 self.parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, type=int, metavar='limit') 255 self.parser.add_argument('--file_size_limit', help='Maximum per file size limit in MB', default=10, type=int, metavar='limit') 256 self.parser.add_argument('--connected_limit', help='Max connected peer per site', default=8, type=int, metavar='connected_limit') 257 self.parser.add_argument('--global_connected_limit', help='Max connections', default=512, type=int, metavar='global_connected_limit') 258 self.parser.add_argument('--workers', help='Download workers per site', default=5, type=int, metavar='workers') 259 260 self.parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='ip') 261 self.parser.add_argument('--fileserver_port', help='FileServer bind port (0: randomize)', default=0, type=int, metavar='port') 262 self.parser.add_argument('--fileserver_port_range', help='FileServer randomization range', default="10000-40000", metavar='port') 263 self.parser.add_argument('--fileserver_ip_type', help='FileServer ip type', default="dual", choices=["ipv4", "ipv6", "dual"]) 264 self.parser.add_argument('--ip_local', help='My local ips', default=ip_local, type=int, metavar='ip', nargs='*') 265 self.parser.add_argument('--ip_external', help='Set reported external ip (tested on start if None)', metavar='ip', nargs='*') 266 self.parser.add_argument('--offline', help='Disable network communication', action='store_true') 267 268 self.parser.add_argument('--disable_udp', help='Disable UDP connections', action='store_true') 269 self.parser.add_argument('--proxy', help='Socks proxy address', metavar='ip:port') 270 self.parser.add_argument('--bind', help='Bind outgoing sockets to this address', metavar='ip') 271 self.parser.add_argument('--trackers', help='Bootstraping torrent trackers', default=trackers, metavar='protocol://address', nargs='*') 272 self.parser.add_argument('--trackers_file', help='Load torrent trackers dynamically from a file', metavar='path', nargs='*') 273 self.parser.add_argument('--trackers_proxy', help='Force use proxy to connect to trackers (disable, tor, ip:port)', default="disable") 274 self.parser.add_argument('--use_libsecp256k1', help='Use Libsecp256k1 liblary for speedup', type='bool', choices=[True, False], default=True) 275 self.parser.add_argument('--use_openssl', help='Use OpenSSL liblary for speedup', type='bool', choices=[True, False], default=True) 276 self.parser.add_argument('--openssl_lib_file', help='Path for OpenSSL library file (default: detect)', default=argparse.SUPPRESS, metavar="path") 277 self.parser.add_argument('--openssl_bin_file', help='Path for OpenSSL binary file (default: detect)', default=argparse.SUPPRESS, metavar="path") 278 self.parser.add_argument('--disable_db', help='Disable database updating', action='store_true') 279 self.parser.add_argument('--disable_encryption', help='Disable connection encryption', action='store_true') 280 self.parser.add_argument('--force_encryption', help="Enforce encryption to all peer connections", action='store_true') 281 self.parser.add_argument('--disable_sslcompression', help='Disable SSL compression to save memory', 282 type='bool', choices=[True, False], default=True) 283 self.parser.add_argument('--keep_ssl_cert', help='Disable new SSL cert generation on startup', action='store_true') 284 self.parser.add_argument('--max_files_opened', help='Change maximum opened files allowed by OS to this value on startup', 285 default=2048, type=int, metavar='limit') 286 self.parser.add_argument('--stack_size', help='Change thread stack size', default=None, type=int, metavar='thread_stack_size') 287 self.parser.add_argument('--use_tempfiles', help='Use temporary files when downloading (experimental)', 288 type='bool', choices=[True, False], default=False) 289 self.parser.add_argument('--stream_downloads', help='Stream download directly to files (experimental)', 290 type='bool', choices=[True, False], default=False) 291 self.parser.add_argument("--msgpack_purepython", help='Use less memory, but a bit more CPU power', 292 type='bool', choices=[True, False], default=False) 293 self.parser.add_argument("--fix_float_decimals", help='Fix content.json modification date float precision on verification', 294 type='bool', choices=[True, False], default=fix_float_decimals) 295 self.parser.add_argument("--db_mode", choices=["speed", "security"], default="speed") 296 297 self.parser.add_argument('--threads_fs_read', help='Number of threads for file read operations', default=1, type=int) 298 self.parser.add_argument('--threads_fs_write', help='Number of threads for file write operations', default=1, type=int) 299 self.parser.add_argument('--threads_crypt', help='Number of threads for cryptographic operations', default=2, type=int) 300 self.parser.add_argument('--threads_db', help='Number of threads for database operations', default=1, type=int) 301 302 self.parser.add_argument("--download_optional", choices=["manual", "auto"], default="manual") 303 304 self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript, 305 metavar='executable_path') 306 307 self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=["disable", "enable", "always"], default='enable') 308 self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051') 309 self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050') 310 self.parser.add_argument('--tor_password', help='Tor controller password', metavar='password') 311 self.parser.add_argument('--tor_use_bridges', help='Use obfuscated bridge relays to avoid Tor block', action='store_true') 312 self.parser.add_argument('--tor_hs_limit', help='Maximum number of hidden services in Tor always mode', metavar='limit', type=int, default=10) 313 self.parser.add_argument('--tor_hs_port', help='Hidden service port in Tor always mode', metavar='limit', type=int, default=15441) 314 315 self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev)) 316 self.parser.add_argument('--end', help='Stop multi value argument parsing', action='store_true') 317 318 return self.parser 319 320 def loadTrackersFile(self): 321 if not self.trackers_file: 322 return None 323 324 self.trackers = self.arguments.trackers[:] 325 326 for trackers_file in self.trackers_file: 327 try: 328 if trackers_file.startswith("/"): # Absolute 329 trackers_file_path = trackers_file 330 elif trackers_file.startswith("{data_dir}"): # Relative to data_dir 331 trackers_file_path = trackers_file.replace("{data_dir}", self.data_dir) 332 else: # Relative to zeronet.py 333 trackers_file_path = self.start_dir + "/" + trackers_file 334 335 for line in open(trackers_file_path): 336 tracker = line.strip() 337 if "://" in tracker and tracker not in self.trackers: 338 self.trackers.append(tracker) 339 except Exception as err: 340 print("Error loading trackers file: %s" % err) 341 342 # Find arguments specified for current action 343 def getActionArguments(self): 344 back = {} 345 arguments = self.parser._subparsers._group_actions[0].choices[self.action]._actions[1:] # First is --version 346 for argument in arguments: 347 back[argument.dest] = getattr(self, argument.dest) 348 return back 349 350 # Try to find action from argv 351 def getAction(self, argv): 352 actions = [list(action.choices.keys()) for action in self.parser._actions if action.dest == "action"][0] # Valid actions 353 found_action = False 354 for action in actions: # See if any in argv 355 if action in argv: 356 found_action = action 357 break 358 return found_action 359 360 # Move plugin parameters to end of argument list 361 def moveUnknownToEnd(self, argv, default_action): 362 valid_actions = sum([action.option_strings for action in self.parser._actions], []) 363 valid_parameters = [] 364 plugin_parameters = [] 365 plugin = False 366 for arg in argv: 367 if arg.startswith("--"): 368 if arg not in valid_actions: 369 plugin = True 370 else: 371 plugin = False 372 elif arg == default_action: 373 plugin = False 374 375 if plugin: 376 plugin_parameters.append(arg) 377 else: 378 valid_parameters.append(arg) 379 return valid_parameters + plugin_parameters 380 381 def getParser(self, argv): 382 action = self.getAction(argv) 383 if not action: 384 return self.parser 385 else: 386 return self.subparsers.choices[action] 387 388 # Parse arguments from config file and command line 389 def parse(self, silent=False, parse_config=True): 390 argv = self.argv[:] # Copy command line arguments 391 current_parser = self.getParser(argv) 392 if silent: # Don't display messages or quit on unknown parameter 393 original_print_message = self.parser._print_message 394 original_exit = self.parser.exit 395 396 def silencer(parser, function_name): 397 parser.exited = True 398 return None 399 current_parser.exited = False 400 current_parser._print_message = lambda *args, **kwargs: silencer(current_parser, "_print_message") 401 current_parser.exit = lambda *args, **kwargs: silencer(current_parser, "exit") 402 403 self.parseCommandline(argv, silent) # Parse argv 404 self.setAttributes() 405 if parse_config: 406 argv = self.parseConfig(argv) # Add arguments from config file 407 408 self.parseCommandline(argv, silent) # Parse argv 409 self.setAttributes() 410 411 if not silent: 412 if self.fileserver_ip != "*" and self.fileserver_ip not in self.ip_local: 413 self.ip_local.append(self.fileserver_ip) 414 415 if silent: # Restore original functions 416 if current_parser.exited and self.action == "main": # Argument parsing halted, don't start ZeroNet with main action 417 self.action = None 418 current_parser._print_message = original_print_message 419 current_parser.exit = original_exit 420 421 self.loadTrackersFile() 422 423 # Parse command line arguments 424 def parseCommandline(self, argv, silent=False): 425 # Find out if action is specificed on start 426 action = self.getAction(argv) 427 if not action: 428 argv.append("--end") 429 argv.append("main") 430 action = "main" 431 argv = self.moveUnknownToEnd(argv, action) 432 if silent: 433 res = self.parser.parse_known_args(argv[1:]) 434 if res: 435 self.arguments = res[0] 436 else: 437 self.arguments = {} 438 else: 439 self.arguments = self.parser.parse_args(argv[1:]) 440 441 # Parse config file 442 def parseConfig(self, argv): 443 # Find config file path from parameters 444 if "--config_file" in argv: 445 self.config_file = argv[argv.index("--config_file") + 1] 446 # Load config file 447 if os.path.isfile(self.config_file): 448 config = configparser.RawConfigParser(allow_no_value=True, strict=False) 449 config.read(self.config_file) 450 for section in config.sections(): 451 for key, val in config.items(section): 452 if val == "True": 453 val = None 454 if section != "global": # If not global prefix key with section 455 key = section + "_" + key 456 457 if key == "open_browser": # Prefer config file value over cli argument 458 while "--%s" % key in argv: 459 pos = argv.index("--open_browser") 460 del argv[pos:pos + 2] 461 462 argv_extend = ["--%s" % key] 463 if val: 464 for line in val.strip().split("\n"): # Allow multi-line values 465 argv_extend.append(line) 466 if "\n" in val: 467 argv_extend.append("--end") 468 469 argv = argv[:1] + argv_extend + argv[1:] 470 return argv 471 472 # Return command line value of given argument 473 def getCmdlineValue(self, key): 474 if key not in self.argv: 475 return None 476 argv_index = self.argv.index(key) 477 if argv_index == len(self.argv) - 1: # last arg, test not specified 478 return None 479 480 return self.argv[argv_index + 1] 481 482 # Expose arguments as class attributes 483 def setAttributes(self): 484 # Set attributes from arguments 485 if self.arguments: 486 args = vars(self.arguments) 487 for key, val in args.items(): 488 if type(val) is list: 489 val = val[:] 490 if key in ("data_dir", "log_dir", "start_dir", "openssl_bin_file", "openssl_lib_file"): 491 if val: 492 val = val.replace("\\", "/") 493 setattr(self, key, val) 494 495 def loadPlugins(self): 496 from Plugin import PluginManager 497 498 @PluginManager.acceptPlugins 499 class ConfigPlugin(object): 500 def __init__(self, config): 501 self.argv = config.argv 502 self.parser = config.parser 503 self.subparsers = config.subparsers 504 self.test_parser = config.test_parser 505 self.getCmdlineValue = config.getCmdlineValue 506 self.createArguments() 507 508 def createArguments(self): 509 pass 510 511 ConfigPlugin(self) 512 513 def saveValue(self, key, value): 514 if not os.path.isfile(self.config_file): 515 content = "" 516 else: 517 content = open(self.config_file).read() 518 lines = content.splitlines() 519 520 global_line_i = None 521 key_line_i = None 522 i = 0 523 for line in lines: 524 if line.strip() == "[global]": 525 global_line_i = i 526 if line.startswith(key + " =") or line == key: 527 key_line_i = i 528 i += 1 529 530 if key_line_i and len(lines) > key_line_i + 1: 531 while True: # Delete previous multiline values 532 is_value_line = lines[key_line_i + 1].startswith(" ") or lines[key_line_i + 1].startswith("\t") 533 if not is_value_line: 534 break 535 del lines[key_line_i + 1] 536 537 if value is None: # Delete line 538 if key_line_i: 539 del lines[key_line_i] 540 541 else: # Add / update 542 if type(value) is list: 543 value_lines = [""] + [str(line).replace("\n", "").replace("\r", "") for line in value] 544 else: 545 value_lines = [str(value).replace("\n", "").replace("\r", "")] 546 new_line = "%s = %s" % (key, "\n ".join(value_lines)) 547 if key_line_i: # Already in the config, change the line 548 lines[key_line_i] = new_line 549 elif global_line_i is None: # No global section yet, append to end of file 550 lines.append("[global]") 551 lines.append(new_line) 552 else: # Has global section, append the line after it 553 lines.insert(global_line_i + 1, new_line) 554 555 open(self.config_file, "w").write("\n".join(lines)) 556 557 def getServerInfo(self): 558 from Plugin import PluginManager 559 import main 560 561 info = { 562 "platform": sys.platform, 563 "fileserver_ip": self.fileserver_ip, 564 "fileserver_port": self.fileserver_port, 565 "ui_ip": self.ui_ip, 566 "ui_port": self.ui_port, 567 "version": self.version, 568 "rev": self.rev, 569 "language": self.language, 570 "debug": self.debug, 571 "plugins": PluginManager.plugin_manager.plugin_names, 572 573 "log_dir": os.path.abspath(self.log_dir), 574 "data_dir": os.path.abspath(self.data_dir), 575 "src_dir": os.path.dirname(os.path.abspath(__file__)) 576 } 577 578 try: 579 info["ip_external"] = main.file_server.port_opened 580 info["tor_enabled"] = main.file_server.tor_manager.enabled 581 info["tor_status"] = main.file_server.tor_manager.status 582 except Exception: 583 pass 584 585 return info 586 587 def initConsoleLogger(self): 588 if self.action == "main": 589 format = '[%(asctime)s] %(name)s %(message)s' 590 else: 591 format = '%(name)s %(message)s' 592 593 if self.console_log_level == "default": 594 if self.silent: 595 level = logging.ERROR 596 elif self.debug: 597 level = logging.DEBUG 598 else: 599 level = logging.INFO 600 else: 601 level = logging.getLevelName(self.console_log_level) 602 603 console_logger = logging.StreamHandler() 604 console_logger.setFormatter(logging.Formatter(format, "%H:%M:%S")) 605 console_logger.setLevel(level) 606 logging.getLogger('').addHandler(console_logger) 607 608 def initFileLogger(self): 609 if self.action == "main": 610 log_file_path = "%s/debug.log" % self.log_dir 611 else: 612 log_file_path = "%s/cmd.log" % self.log_dir 613 614 if self.log_rotate == "off": 615 file_logger = logging.FileHandler(log_file_path, "w", "utf-8") 616 else: 617 when_names = {"weekly": "w", "daily": "d", "hourly": "h"} 618 file_logger = logging.handlers.TimedRotatingFileHandler( 619 log_file_path, when=when_names[self.log_rotate], interval=1, backupCount=self.log_rotate_backup_count, 620 encoding="utf8" 621 ) 622 623 if os.path.isfile(log_file_path): 624 file_logger.doRollover() # Always start with empty log file 625 file_logger.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)-8s %(name)s %(message)s')) 626 file_logger.setLevel(logging.getLevelName(self.log_level)) 627 logging.getLogger('').setLevel(logging.getLevelName(self.log_level)) 628 logging.getLogger('').addHandler(file_logger) 629 630 def initLogging(self, console_logging=None, file_logging=None): 631 if console_logging == None: 632 console_logging = self.console_log_level != "off" 633 634 if file_logging == None: 635 file_logging = self.log_level != "off" 636 637 # Create necessary files and dirs 638 if not os.path.isdir(self.log_dir): 639 os.mkdir(self.log_dir) 640 try: 641 os.chmod(self.log_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) 642 except Exception as err: 643 print("Can't change permission of %s: %s" % (self.log_dir, err)) 644 645 # Make warning hidden from console 646 logging.WARNING = 15 # Don't display warnings if not in debug mode 647 logging.addLevelName(15, "WARNING") 648 649 logging.getLogger('').name = "-" # Remove root prefix 650 651 self.error_logger = ErrorLogHandler() 652 self.error_logger.setLevel(logging.getLevelName("ERROR")) 653 logging.getLogger('').addHandler(self.error_logger) 654 655 if console_logging: 656 self.initConsoleLogger() 657 if file_logging: 658 self.initFileLogger() 659 660 661class ErrorLogHandler(logging.StreamHandler): 662 def __init__(self): 663 self.lines = [] 664 return super().__init__() 665 666 def emit(self, record): 667 self.lines.append([time.time(), record.levelname, self.format(record)]) 668 669 def onNewRecord(self, record): 670 pass 671 672 673config = Config(sys.argv)