diff --git a/rhodecode/rcserver.py b/rhodecode/rcserver.py deleted file mode 100644 --- a/rhodecode/rcserver.py +++ /dev/null @@ -1,1024 +0,0 @@ -# (c) 2005 Ian Bicking and contributors; written for Paste -# (http://pythonpaste.org) Licensed under the MIT license: -# http://www.opensource.org/licenses/mit-license.php -# -# For discussion of daemonizing: -# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 -# -# Code taken also from QP: http://www.mems-exchange.org/software/qp/ From -# lib/site.py - -import atexit -import errno -import fnmatch -import logging -import optparse -import os -import re -import subprocess32 -import sys -import textwrap -import threading -import time -import traceback - -from logging.config import fileConfig -import ConfigParser as configparser -from paste.deploy import loadserver -from paste.deploy import loadapp - -import rhodecode -from rhodecode.lib.compat import kill - - -def make_web_build_callback(filename): - p = subprocess32.Popen('make web-build', shell=True, - stdout=subprocess32.PIPE, - stderr=subprocess32.PIPE, - cwd=os.path.dirname(os.path.dirname(__file__))) - stdout, stderr = p.communicate() - stdout = ''.join(stdout) - stderr = ''.join(stderr) - if stdout: - print(stdout) - if stderr: - print('%s %s %s' % ('-' * 20, 'ERRORS', '-' * 20)) - print(stderr) - - -MAXFD = 1024 -HERE = os.path.dirname(os.path.abspath(__file__)) -SERVER_RUNNING_FILE = None - - -# watch those extra files for changes, server gets restarted if file changes -GLOBAL_EXTRA_FILES = { - 'rhodecode/public/css/*.less': make_web_build_callback, - 'rhodecode/public/js/src/**/*.js': make_web_build_callback, -} - - - -## HOOKS - inspired by gunicorn # - -def when_ready(server): - """ - Called just after the server is started. - """ - - def _remove_server_running_file(): - if os.path.isfile(SERVER_RUNNING_FILE): - os.remove(SERVER_RUNNING_FILE) - - if SERVER_RUNNING_FILE: - with open(SERVER_RUNNING_FILE, 'wb') as f: - f.write(str(os.getpid())) - # register cleanup of that file when server exits - atexit.register(_remove_server_running_file) - - -def setup_logging(config_uri, fileConfig=fileConfig, - configparser=configparser): - """ - Set up logging via the logging module's fileConfig function with the - filename specified via ``config_uri`` (a string in the form - ``filename#sectionname``). - - ConfigParser defaults are specified for the special ``__file__`` - and ``here`` variables, similar to PasteDeploy config loading. - """ - path, _ = _getpathsec(config_uri, None) - parser = configparser.ConfigParser() - parser.read([path]) - if parser.has_section('loggers'): - config_file = os.path.abspath(path) - return fileConfig( - config_file, - {'__file__': config_file, 'here': os.path.dirname(config_file)} - ) - - -def set_rhodecode_is_test(config_uri): - """If is_test is defined in the config file sets rhodecode.is_test.""" - path, _ = _getpathsec(config_uri, None) - parser = configparser.ConfigParser() - parser.read(path) - rhodecode.is_test = ( - parser.has_option('app:main', 'is_test') and - parser.getboolean('app:main', 'is_test')) - - -def _getpathsec(config_uri, name): - if '#' in config_uri: - path, section = config_uri.split('#', 1) - else: - path, section = config_uri, 'main' - if name: - section = name - return path, section - - -def parse_vars(args): - """ - Given variables like ``['a=b', 'c=d']`` turns it into ``{'a': - 'b', 'c': 'd'}`` - """ - result = {} - for arg in args: - if '=' not in arg: - raise ValueError( - 'Variable assignment %r invalid (no "=")' - % arg) - name, value = arg.split('=', 1) - result[name] = value - return result - - -def _match_pattern(filename): - for pattern in GLOBAL_EXTRA_FILES: - if fnmatch.fnmatch(filename, pattern): - return pattern - return False - - -def generate_extra_file_list(): - - extra_list = [] - for root, dirs, files in os.walk(HERE, topdown=True): - for fname in files: - stripped_src = os.path.join( - 'rhodecode', os.path.relpath(os.path.join(root, fname), HERE)) - - if _match_pattern(stripped_src): - extra_list.append(stripped_src) - - return extra_list - - -def run_callback_for_pattern(filename): - pattern = _match_pattern(filename) - if pattern: - _file_callback = GLOBAL_EXTRA_FILES.get(pattern) - if callable(_file_callback): - _file_callback(filename) - - -class DaemonizeException(Exception): - pass - - -class RcServerCommand(object): - - usage = '%prog config_uri [start|stop|restart|status] [var=value]' - description = """\ - This command serves a web application that uses a PasteDeploy - configuration file for the server and application. - - If start/stop/restart is given, then --daemon is implied, and it will - start (normal operation), stop (--stop-daemon), or do both. - - You can also include variable assignments like 'http_port=8080' - and then use %(http_port)s in your config files. - """ - default_verbosity = 1 - - parser = optparse.OptionParser( - usage, - description=textwrap.dedent(description) - ) - parser.add_option( - '-n', '--app-name', - dest='app_name', - metavar='NAME', - help="Load the named application (default main)") - parser.add_option( - '-s', '--server', - dest='server', - metavar='SERVER_TYPE', - help="Use the named server.") - parser.add_option( - '--server-name', - dest='server_name', - metavar='SECTION_NAME', - help=("Use the named server as defined in the configuration file " - "(default: main)")) - parser.add_option( - '--with-vcsserver', - dest='vcs_server', - action='store_true', - help=("Start the vcsserver instance together with the RhodeCode server")) - if hasattr(os, 'fork'): - parser.add_option( - '--daemon', - dest="daemon", - action="store_true", - help="Run in daemon (background) mode") - parser.add_option( - '--pid-file', - dest='pid_file', - metavar='FILENAME', - help=("Save PID to file (default to pyramid.pid if running in " - "daemon mode)")) - parser.add_option( - '--running-file', - dest='running_file', - metavar='RUNNING_FILE', - help="Create a running file after the server is initalized with " - "stored PID of process") - parser.add_option( - '--log-file', - dest='log_file', - metavar='LOG_FILE', - help="Save output to the given log file (redirects stdout)") - parser.add_option( - '--reload', - dest='reload', - action='store_true', - help="Use auto-restart file monitor") - parser.add_option( - '--reload-interval', - dest='reload_interval', - default=1, - help=("Seconds between checking files (low number can cause " - "significant CPU usage)")) - parser.add_option( - '--monitor-restart', - dest='monitor_restart', - action='store_true', - help="Auto-restart server if it dies") - parser.add_option( - '--status', - action='store_true', - dest='show_status', - help="Show the status of the (presumably daemonized) server") - parser.add_option( - '-v', '--verbose', - default=default_verbosity, - dest='verbose', - action='count', - help="Set verbose level (default "+str(default_verbosity)+")") - parser.add_option( - '-q', '--quiet', - action='store_const', - const=0, - dest='verbose', - help="Suppress verbose output") - - if hasattr(os, 'setuid'): - # I don't think these are available on Windows - parser.add_option( - '--user', - dest='set_user', - metavar="USERNAME", - help="Set the user (usually only possible when run as root)") - parser.add_option( - '--group', - dest='set_group', - metavar="GROUP", - help="Set the group (usually only possible when run as root)") - - parser.add_option( - '--stop-daemon', - dest='stop_daemon', - action='store_true', - help=('Stop a daemonized server (given a PID file, or default ' - 'pyramid.pid file)')) - - _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I) - - _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN' - _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN' - - possible_subcommands = ('start', 'stop', 'restart', 'status') - - def __init__(self, argv, quiet=False): - self.options, self.args = self.parser.parse_args(argv[1:]) - if quiet: - self.options.verbose = 0 - - def out(self, msg): # pragma: no cover - if self.options.verbose > 0: - print(msg) - - def get_options(self): - if (len(self.args) > 1 - and self.args[1] in self.possible_subcommands): - restvars = self.args[2:] - else: - restvars = self.args[1:] - - return parse_vars(restvars) - - def run(self): # pragma: no cover - if self.options.stop_daemon: - return self.stop_daemon() - - if not hasattr(self.options, 'set_user'): - # Windows case: - self.options.set_user = self.options.set_group = None - - # @@: Is this the right stage to set the user at? - self.change_user_group( - self.options.set_user, self.options.set_group) - - if not self.args: - self.out('Please provide configuration file as first argument, ' - 'most likely it should be production.ini') - return 2 - app_spec = self.args[0] - - if (len(self.args) > 1 - and self.args[1] in self.possible_subcommands): - cmd = self.args[1] - else: - cmd = None - - if self.options.reload: - if os.environ.get(self._reloader_environ_key): - if self.options.verbose > 1: - self.out('Running reloading file monitor') - - install_reloader(int(self.options.reload_interval), - [app_spec] + generate_extra_file_list()) - # if self.requires_config_file: - # watch_file(self.args[0]) - else: - return self.restart_with_reloader() - - if cmd not in (None, 'start', 'stop', 'restart', 'status'): - self.out( - 'Error: must give start|stop|restart (not %s)' % cmd) - return 2 - - if cmd == 'status' or self.options.show_status: - return self.show_status() - - if cmd == 'restart' or cmd == 'stop': - result = self.stop_daemon() - if result: - if cmd == 'restart': - self.out("Could not stop daemon; aborting") - else: - self.out("Could not stop daemon") - return result - if cmd == 'stop': - return result - self.options.daemon = True - - if cmd == 'start': - self.options.daemon = True - - app_name = self.options.app_name - - vars = self.get_options() - - if self.options.vcs_server: - vars['vcs.start_server'] = 'true' - - if self.options.running_file: - global SERVER_RUNNING_FILE - SERVER_RUNNING_FILE = self.options.running_file - - if not self._scheme_re.search(app_spec): - app_spec = 'config:' + app_spec - server_name = self.options.server_name - if self.options.server: - server_spec = 'egg:pyramid' - assert server_name is None - server_name = self.options.server - else: - server_spec = app_spec - base = os.getcwd() - - if getattr(self.options, 'daemon', False): - if not self.options.pid_file: - self.options.pid_file = 'pyramid.pid' - if not self.options.log_file: - self.options.log_file = 'pyramid.log' - - # Ensure the log file is writeable - if self.options.log_file: - try: - writeable_log_file = open(self.options.log_file, 'a') - except IOError as ioe: - msg = 'Error: Unable to write to log file: %s' % ioe - raise ValueError(msg) - writeable_log_file.close() - - # Ensure the pid file is writeable - if self.options.pid_file: - try: - writeable_pid_file = open(self.options.pid_file, 'a') - except IOError as ioe: - msg = 'Error: Unable to write to pid file: %s' % ioe - raise ValueError(msg) - writeable_pid_file.close() - - - if getattr(self.options, 'daemon', False): - try: - self.daemonize() - except DaemonizeException as ex: - if self.options.verbose > 0: - self.out(str(ex)) - return 2 - - if (self.options.monitor_restart - and not os.environ.get(self._monitor_environ_key)): - return self.restart_with_monitor() - - if self.options.pid_file: - self.record_pid(self.options.pid_file) - - if self.options.log_file: - stdout_log = LazyWriter(self.options.log_file, 'a') - sys.stdout = stdout_log - sys.stderr = stdout_log - logging.basicConfig(stream=stdout_log) - - log_fn = app_spec - if log_fn.startswith('config:'): - log_fn = app_spec[len('config:'):] - elif log_fn.startswith('egg:'): - log_fn = None - if log_fn: - log_fn = os.path.join(base, log_fn) - setup_logging(log_fn) - set_rhodecode_is_test(log_fn) - - server = self.loadserver(server_spec, name=server_name, - relative_to=base, global_conf=vars) - # starting hooks - app = self.loadapp(app_spec, name=app_name, relative_to=base, - global_conf=vars) - - if self.options.verbose > 0: - if hasattr(os, 'getpid'): - msg = 'Starting %s in PID %i.' % (__name__, os.getpid()) - else: - msg = 'Starting %s.' % (__name__,) - self.out(msg) - if SERVER_RUNNING_FILE: - self.out('PID file written as %s' % (SERVER_RUNNING_FILE, )) - elif not self.options.pid_file: - self.out('No PID file written by default.') - - try: - when_ready(server) - server(app) - except (SystemExit, KeyboardInterrupt) as e: - if self.options.verbose > 1: - raise - if str(e): - msg = ' ' + str(e) - else: - msg = '' - self.out('Exiting%s (-v to see traceback)' % msg) - - def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover - return loadapp(app_spec, name=name, relative_to=relative_to, **kw) - - def loadserver(self, server_spec, name, relative_to, **kw): # pragma: no cover - return loadserver( - server_spec, name=name, relative_to=relative_to, **kw) - - def quote_first_command_arg(self, arg): # pragma: no cover - """ - There's a bug in Windows when running an executable that's - located inside a path with a space in it. This method handles - that case, or on non-Windows systems or an executable with no - spaces, it just leaves well enough alone. - """ - if sys.platform != 'win32' or ' ' not in arg: - # Problem does not apply: - return arg - try: - import win32api - except ImportError: - raise ValueError( - "The executable %r contains a space, and in order to " - "handle this issue you must have the win32api module " - "installed" % arg) - arg = win32api.GetShortPathName(arg) - return arg - - def daemonize(self): # pragma: no cover - pid = live_pidfile(self.options.pid_file) - if pid: - raise DaemonizeException( - "Daemon is already running (PID: %s from PID file %s)" - % (pid, self.options.pid_file)) - - if self.options.verbose > 0: - self.out('Entering daemon mode') - pid = os.fork() - if pid: - # The forked process also has a handle on resources, so we - # *don't* want proper termination of the process, we just - # want to exit quick (which os._exit() does) - os._exit(0) - # Make this the session leader - os.setsid() - # Fork again for good measure! - pid = os.fork() - if pid: - os._exit(0) - - # @@: Should we set the umask and cwd now? - - import resource # Resource usage information. - maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] - if maxfd == resource.RLIM_INFINITY: - maxfd = MAXFD - # Iterate through and close all file descriptors. - for fd in range(0, maxfd): - try: - os.close(fd) - except OSError: # ERROR, fd wasn't open to begin with (ignored) - pass - - if hasattr(os, "devnull"): - REDIRECT_TO = os.devnull - else: - REDIRECT_TO = "/dev/null" - os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) - # Duplicate standard input to standard output and standard error. - os.dup2(0, 1) # standard output (1) - os.dup2(0, 2) # standard error (2) - - def _remove_pid_file(self, written_pid, filename, verbosity): - current_pid = os.getpid() - if written_pid != current_pid: - # A forked process must be exiting, not the process that - # wrote the PID file - return - if not os.path.exists(filename): - return - with open(filename) as f: - content = f.read().strip() - try: - pid_in_file = int(content) - except ValueError: - pass - else: - if pid_in_file != current_pid: - msg = "PID file %s contains %s, not expected PID %s" - self.out(msg % (filename, pid_in_file, current_pid)) - return - if verbosity > 0: - self.out("Removing PID file %s" % filename) - try: - os.unlink(filename) - return - except OSError as e: - # Record, but don't give traceback - self.out("Cannot remove PID file: (%s)" % e) - # well, at least lets not leave the invalid PID around... - try: - with open(filename, 'w') as f: - f.write('') - except OSError as e: - self.out('Stale PID left in file: %s (%s)' % (filename, e)) - else: - self.out('Stale PID removed') - - def record_pid(self, pid_file): - pid = os.getpid() - if self.options.verbose > 1: - self.out('Writing PID %s to %s' % (pid, pid_file)) - with open(pid_file, 'w') as f: - f.write(str(pid)) - atexit.register(self._remove_pid_file, pid, pid_file, self.options.verbose) - - def stop_daemon(self): # pragma: no cover - pid_file = self.options.pid_file or 'pyramid.pid' - if not os.path.exists(pid_file): - self.out('No PID file exists in %s' % pid_file) - return 1 - pid = read_pidfile(pid_file) - if not pid: - self.out("Not a valid PID file in %s" % pid_file) - return 1 - pid = live_pidfile(pid_file) - if not pid: - self.out("PID in %s is not valid (deleting)" % pid_file) - try: - os.unlink(pid_file) - except (OSError, IOError) as e: - self.out("Could not delete: %s" % e) - return 2 - return 1 - for j in range(10): - if not live_pidfile(pid_file): - break - import signal - kill(pid, signal.SIGTERM) - time.sleep(1) - else: - self.out("failed to kill web process %s" % pid) - return 3 - if os.path.exists(pid_file): - os.unlink(pid_file) - return 0 - - def show_status(self): # pragma: no cover - pid_file = self.options.pid_file or 'pyramid.pid' - if not os.path.exists(pid_file): - self.out('No PID file %s' % pid_file) - return 1 - pid = read_pidfile(pid_file) - if not pid: - self.out('No PID in file %s' % pid_file) - return 1 - pid = live_pidfile(pid_file) - if not pid: - self.out('PID %s in %s is not running' % (pid, pid_file)) - return 1 - self.out('Server running in PID %s' % pid) - return 0 - - def restart_with_reloader(self): # pragma: no cover - self.restart_with_monitor(reloader=True) - - def restart_with_monitor(self, reloader=False): # pragma: no cover - if self.options.verbose > 0: - if reloader: - self.out('Starting subprocess with file monitor') - else: - self.out('Starting subprocess with monitor parent') - while 1: - args = [self.quote_first_command_arg(sys.executable)] + sys.argv - new_environ = os.environ.copy() - if reloader: - new_environ[self._reloader_environ_key] = 'true' - else: - new_environ[self._monitor_environ_key] = 'true' - proc = None - try: - try: - _turn_sigterm_into_systemexit() - proc = subprocess32.Popen(args, env=new_environ) - exit_code = proc.wait() - proc = None - except KeyboardInterrupt: - self.out('^C caught in monitor process') - if self.options.verbose > 1: - raise - return 1 - finally: - if proc is not None: - import signal - try: - kill(proc.pid, signal.SIGTERM) - except (OSError, IOError): - pass - - if reloader: - # Reloader always exits with code 3; but if we are - # a monitor, any exit code will restart - if exit_code != 3: - return exit_code - if self.options.verbose > 0: - self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20)) - - def change_user_group(self, user, group): # pragma: no cover - if not user and not group: - return - import pwd - import grp - uid = gid = None - if group: - try: - gid = int(group) - group = grp.getgrgid(gid).gr_name - except ValueError: - try: - entry = grp.getgrnam(group) - except KeyError: - raise ValueError( - "Bad group: %r; no such group exists" % group) - gid = entry.gr_gid - try: - uid = int(user) - user = pwd.getpwuid(uid).pw_name - except ValueError: - try: - entry = pwd.getpwnam(user) - except KeyError: - raise ValueError( - "Bad username: %r; no such user exists" % user) - if not gid: - gid = entry.pw_gid - uid = entry.pw_uid - if self.options.verbose > 0: - self.out('Changing user to %s:%s (%s:%s)' % ( - user, group or '(unknown)', uid, gid)) - if gid: - os.setgid(gid) - if uid: - os.setuid(uid) - - -class LazyWriter(object): - - """ - File-like object that opens a file lazily when it is first written - to. - """ - - def __init__(self, filename, mode='w'): - self.filename = filename - self.fileobj = None - self.lock = threading.Lock() - self.mode = mode - - def open(self): - if self.fileobj is None: - with self.lock: - self.fileobj = open(self.filename, self.mode) - return self.fileobj - - def close(self): - fileobj = self.fileobj - if fileobj is not None: - fileobj.close() - - def __del__(self): - self.close() - - def write(self, text): - fileobj = self.open() - fileobj.write(text) - fileobj.flush() - - def writelines(self, text): - fileobj = self.open() - fileobj.writelines(text) - fileobj.flush() - - def flush(self): - self.open().flush() - - -def live_pidfile(pidfile): # pragma: no cover - """ - (pidfile:str) -> int | None - Returns an int found in the named file, if there is one, - and if there is a running process with that process id. - Return None if no such process exists. - """ - pid = read_pidfile(pidfile) - if pid: - try: - kill(int(pid), 0) - return pid - except OSError as e: - if e.errno == errno.EPERM: - return pid - return None - - -def read_pidfile(filename): - if os.path.exists(filename): - try: - with open(filename) as f: - content = f.read() - return int(content.strip()) - except (ValueError, IOError): - return None - else: - return None - - -def ensure_port_cleanup( - bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover - """ - This makes sure any open ports are closed. - - Does this by connecting to them until they give connection - refused. Servers should call like:: - - ensure_port_cleanup([80, 443]) - """ - atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries, - sleeptime=sleeptime) - - -def _cleanup_ports( - bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover - # Wait for the server to bind to the port. - import socket - import errno - for bound_address in bound_addresses: - for attempt in range(maxtries): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect(bound_address) - except socket.error as e: - if e.args[0] != errno.ECONNREFUSED: - raise - break - else: - time.sleep(sleeptime) - else: - raise SystemExit('Timeout waiting for port.') - sock.close() - - -def _turn_sigterm_into_systemexit(): # pragma: no cover - """ - Attempts to turn a SIGTERM exception into a SystemExit exception. - """ - try: - import signal - except ImportError: - return - def handle_term(signo, frame): - raise SystemExit - signal.signal(signal.SIGTERM, handle_term) - - -def install_reloader(poll_interval=1, extra_files=None): # pragma: no cover - """ - Install the reloading monitor. - - On some platforms server threads may not terminate when the main - thread does, causing ports to remain open/locked. The - ``raise_keyboard_interrupt`` option creates a unignorable signal - which causes the whole application to shut-down (rudely). - """ - mon = Monitor(poll_interval=poll_interval) - if extra_files is None: - extra_files = [] - mon.extra_files.extend(extra_files) - t = threading.Thread(target=mon.periodic_reload) - t.setDaemon(True) - t.start() - - -class classinstancemethod(object): - """ - Acts like a class method when called from a class, like an - instance method when called by an instance. The method should - take two arguments, 'self' and 'cls'; one of these will be None - depending on how the method was called. - """ - - def __init__(self, func): - self.func = func - self.__doc__ = func.__doc__ - - def __get__(self, obj, type=None): - return _methodwrapper(self.func, obj=obj, type=type) - - -class _methodwrapper(object): - - def __init__(self, func, obj, type): - self.func = func - self.obj = obj - self.type = type - - def __call__(self, *args, **kw): - assert not 'self' in kw and not 'cls' in kw, ( - "You cannot use 'self' or 'cls' arguments to a " - "classinstancemethod") - return self.func(*((self.obj, self.type) + args), **kw) - - -class Monitor(object): # pragma: no cover - """ - A file monitor and server restarter. - - Use this like: - - ..code-block:: Python - - install_reloader() - - Then make sure your server is installed with a shell script like:: - - err=3 - while test "$err" -eq 3 ; do - python server.py - err="$?" - done - - or is run from this .bat file (if you use Windows):: - - @echo off - :repeat - python server.py - if %errorlevel% == 3 goto repeat - - or run a monitoring process in Python (``pserve --reload`` does - this). - - Use the ``watch_file(filename)`` function to cause a reload/restart for - other non-Python files (e.g., configuration files). If you have - a dynamic set of files that grows over time you can use something like:: - - def watch_config_files(): - return CONFIG_FILE_CACHE.keys() - add_file_callback(watch_config_files) - - Then every time the reloader polls files it will call - ``watch_config_files`` and check all the filenames it returns. - """ - instances = [] - global_extra_files = [] - global_file_callbacks = [] - - def __init__(self, poll_interval): - self.module_mtimes = {} - self.keep_running = True - self.poll_interval = poll_interval - self.extra_files = list(self.global_extra_files) - self.instances.append(self) - self.file_callbacks = list(self.global_file_callbacks) - - def _exit(self): - # use os._exit() here and not sys.exit() since within a - # thread sys.exit() just closes the given thread and - # won't kill the process; note os._exit does not call - # any atexit callbacks, nor does it do finally blocks, - # flush open files, etc. In otherwords, it is rude. - os._exit(3) - - def periodic_reload(self): - while True: - if not self.check_reload(): - self._exit() - break - time.sleep(self.poll_interval) - - def check_reload(self): - filenames = list(self.extra_files) - for file_callback in self.file_callbacks: - try: - filenames.extend(file_callback()) - except: - print( - "Error calling reloader callback %r:" % file_callback) - traceback.print_exc() - for module in list(sys.modules.values()): - try: - filename = module.__file__ - except (AttributeError, ImportError): - continue - if filename is not None: - filenames.append(filename) - - for filename in filenames: - try: - stat = os.stat(filename) - if stat: - mtime = stat.st_mtime - else: - mtime = 0 - except (OSError, IOError): - continue - if filename.endswith('.pyc') and os.path.exists(filename[:-1]): - mtime = max(os.stat(filename[:-1]).st_mtime, mtime) - if not filename in self.module_mtimes: - self.module_mtimes[filename] = mtime - elif self.module_mtimes[filename] < mtime: - print("%s changed; reloading..." % filename) - run_callback_for_pattern(filename) - return False - return True - - def watch_file(self, cls, filename): - """Watch the named file for changes""" - filename = os.path.abspath(filename) - if self is None: - for instance in cls.instances: - instance.watch_file(filename) - cls.global_extra_files.append(filename) - else: - self.extra_files.append(filename) - - watch_file = classinstancemethod(watch_file) - - def add_file_callback(self, cls, callback): - """Add a callback -- a function that takes no parameters -- that will - return a list of filenames to watch for changes.""" - if self is None: - for instance in cls.instances: - instance.add_file_callback(callback) - cls.global_file_callbacks.append(callback) - else: - self.file_callbacks.append(callback) - - add_file_callback = classinstancemethod(add_file_callback) - -watch_file = Monitor.watch_file -add_file_callback = Monitor.add_file_callback - - -def main(argv=sys.argv, quiet=False): - command = RcServerCommand(argv, quiet=quiet) - return command.run() - -if __name__ == '__main__': # pragma: no cover - sys.exit(main() or 0) diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -166,7 +166,6 @@ setup( 'enterprise=rhodecode.tests.plugin', ], 'console_scripts': [ - 'rc-server=rhodecode.rcserver:main', 'rc-setup-app=rhodecode.lib.rc_commands.setup_rc:main', 'rc-upgrade-db=rhodecode.lib.rc_commands.upgrade_db:main', 'rc-ishell=rhodecode.lib.rc_commands.ishell:main',