#!/usr/bin/env python # Copyright (C) 2012-2013 Aleksey Lim # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import errno import signal import locale import gettext import logging from os.path import join, abspath, exists from sugar_network.toolkit import coroutine coroutine.inject() import sugar_network_webui as webui from sugar_network import db, toolkit, client, node from sugar_network.client.routes import ClientRoutes from sugar_network.client.injector import Injector from sugar_network.client.model import Volume from sugar_network.client.auth import BasicCreds, SugarCreds from sugar_network.toolkit.router import Router, Request, Response from sugar_network.toolkit.coroutine import this from sugar_network.toolkit import mountpoints, printf, application from sugar_network.toolkit import Option gettext.textdomain('sugar-network') class Application(application.Daemon): def __init__(self, **kwargs): application.Daemon.__init__(self, **kwargs) self.jobs = coroutine.Pool() new_root = (client.local_root.value != client.local_root.default) client.local_root.value = abspath(client.local_root.value) if new_root: application.logdir.value = join(client.local_root.value, 'log') else: application.logdir.value = client.profile_path('logs') if not exists(toolkit.cachedir.value): os.makedirs(toolkit.cachedir.value) application.rundir.value = join(client.local_root.value, 'run') coroutine.signal(signal.SIGCHLD, self.__SIGCHLD_cb) @application.command( 'index local Sugar Network database') def index(self): if self.check_for_instance(): printf.info('%s already started, no need in index', self.name) return printf.info('Index database in %r', client.local_root.value) volume = Volume(client.path()) try: volume.populate() finally: volume.close() @application.command(hidden=True) def graceful_start(self): """Restart existing process if it was started in different environment. This command makes sense only for Sugar Network activity when it is required avoid restarting existing sugar-network-client. But, restart process if it was started in different Sugar session. """ pid = self.check_for_instance() if pid: run_environ = _read_environ(pid) if os.environ.get('DISPLAY') == run_environ.get('DISPLAY') and \ os.environ.get('DBUS_SESSION_BUS_ADDRESS') == \ run_environ.get('DBUS_SESSION_BUS_ADDRESS'): printf.info('%s already started in current environment', self.name) return application.replace.value = True self.cmd_start() @application.command(hidden=True) def debug(self): printf.info('Use "start --foreground" command instead') application.foreground.value = True self.cmd_start() def run(self): this.injector = Injector(client.path('cache'), client.cache_lifetime.value, client.cache_limit.value, client.cache_limit_percent.value) volume = Volume(client.path()) if client.login.value and client.password.value: creds = BasicCreds(client.login.value, client.password.value) elif client.keyfile.value: creds = SugarCreds(client.keyfile.value) else: raise RuntimeError('No credentials specified') routes = ClientRoutes(volume, creds) router = Router(routes, allow_spawn=True) logging.info('Listening for IPC requests on %s port', client.ipc_port.value) server = coroutine.WSGIServer( ('localhost', client.ipc_port.value), router) self.jobs.spawn(server.serve_forever) coroutine.dispatch() self.accept() def final_start(): volume.populate() if webui.webui.value: host = (webui.webui_host.value, webui.webui_port.value) logging.info('Start Web server on %s:%s', *host) webui_app = webui.get_app( lambda **kwargs: router.call(Request(**kwargs), Response()), 'http://localhost:%s' % client.ipc_port.value) server = coroutine.WSGIServer(host, webui_app) self.jobs.spawn(server.serve_forever) if client.mounts_root.value: mounts_root = abspath(client.mounts_root.value) if not exists(mounts_root): os.makedirs(mounts_root) self.jobs.spawn(mountpoints.monitor, mounts_root) if client.cache_timeout.value: self.jobs.spawn(self._recycle_cache, routes) api_url = None if client.discover_node.value else client.api.value routes.connect(api_url) def delayed_start(event=None): for __ in routes.subscribe(event='delayed-start'): break logging.info('Proceed delayed start') final_start() if client.delayed_start.value: self.jobs.spawn(delayed_start) else: final_start() try: self.jobs.join() except KeyboardInterrupt: toolkit.exception('%s interrupted', self.name) finally: self.jobs.kill() routes.close() def shutdown(self): self.jobs.kill() def _recycle_cache(self, routes): while True: logging.debug('Start cache recycling in %d seconds', client.cache_timeout.value) coroutine.sleep(client.cache_timeout.value) routes.recycle() def __SIGCHLD_cb(self): while True: try: pid, __ = os.waitpid(-1, os.WNOHANG) if pid: continue except OSError, error: if error.errno != errno.ECHILD: raise break def _read_environ(pid): with file('/proc/%s/environ' % pid, 'rb') as f: return dict([i.split('=', 1) for i in f.read().split('\0') if i]) locale.setlocale(locale.LC_ALL, '') # New defaults application.debug.value = client.logger_level() # If tmpfs is mounted to /tmp, `os.fstat()` will return 0 free space # and will brake offline synchronization logic toolkit.cachedir.value = client.profile_path('tmp') Option.seek('main', application) Option.seek('main', [toolkit.cachedir]) Option.seek('webui', webui) Option.seek('client', client) Option.seek('db', db) Option.seek('node', [node.port, node.find_limit]) app = Application( name='sugar-network-client', description='Sugar Network client application.', epilog='See http://wiki.sugarlabs.org/go/Sugar_Network ' 'for details.', config_files=[ '/etc/sweets.d', '/etc/sweets.conf', '~/.config/sweets/config', client.profile_path('sweets.conf'), ]) app.start()