#!/usr/bin/env python # Copyright (C) 2012 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 sys import errno import signal import locale import logging from os.path import join, abspath, exists import active_document as ad import sugar_network_webui as webui from sugar_network import toolkit, local, node from sugar_network.toolkit import sugar, sneakernet from sugar_network.toolkit import dbus_thread, mounts_monitor from sugar_network.local import activities, datastore from sugar_network.local.dbus_datastore import Datastore from sugar_network.local.dbus_network import Network from sugar_network.local.mounts import HomeMount, RemoteMount from sugar_network.local.mountset import Mountset from sugar_network.local.ipc_client import Router from sugar_network.node import stats from sugar_network.resources.volume import Volume from active_toolkit.options import Option from active_toolkit import util, printf, application, coroutine, enforce VERSION = '0.4' class NullHandler(logging.Handler): def emit(self, record): pass class Application(application.Application): _ipc_server = None def __init__(self, **kwargs): application.Application.__init__(self, **kwargs) if application.debug.value < 3: for log_name in ( 'requests.packages.urllib3.connectionpool', 'requests.packages.urllib3.poolmanager', 'requests.packages.urllib3.response', 'requests.packages.urllib3', 'inotify', 'netlink', 'sneakernet', 'toolkit', ): logger = logging.getLogger(log_name) logger.propagate = False logger.addHandler(NullHandler()) new_root = (local.local_root.value != local.local_root.default) local.local_root.value = abspath(local.local_root.value) if new_root: application.logdir.value = join(local.local_root.value, 'log') else: application.logdir.value = sugar.profile_path('logs') application.rundir.value = join(local.local_root.value, 'run') if not exists(toolkit.tmpdir.value): os.makedirs(toolkit.tmpdir.value) 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 if not exists(sugar.profile_path('owner.key')): # Command was launched in foreign environment sugar.uid = lambda: 'index' sugar.nickname = lambda: 'index' printf.info('Index database in %r', local.local_root.value) volume = Volume(local.db_path()) try: volume.populate() datastore.populate(volume['artifact']) self._sync(volume) activities.populate(volume['context'], local.activity_dirs.value) finally: volume.close() @application.command( 'start service and log to standard output') def debug(self): self.start_service() @application.command( 'start service and log to files', name='start', keep_stdout=True) def start_service(self): if self.check_for_instance(): printf.info('%s is already run', self.name) exit(1) jobs = coroutine.Pool() toolkit.ensure_dsa_pubkey(sugar.profile_path('owner.key')) volume = Volume(local.db_path(), lazy_open=local.lazy_open.value) mountset = Mountset(volume) mountset['~'] = HomeMount(volume) mountset['/'] = RemoteMount(volume) def delayed_start(event=None): logging.info('Proceed delayed start') mountset.disconnect(delayed_start) self._sync(mountset.volume) logging.info('Listening for IPC requests on %s port', local.ipc_port.value) server = coroutine.WSGIServer(('localhost', local.ipc_port.value), Router(mountset)) jobs.spawn(server.serve_forever) jobs.spawn(activities.monitor, mountset.volume['context'], local.activity_dirs.value) if webui.webui.value: host = (webui.webui_host.value, webui.webui_port.value) logging.info('Start Web server on %s:%s', *host) server = coroutine.WSGIServer(host, webui.get_app(mountset)) jobs.spawn(server.serve_forever) if local.mounts_root.value: mounts_monitor.start(abspath(local.mounts_root.value)) if local.delayed_start.value: mountset.connect(delayed_start, event='delayed-start') else: delayed_start() dbus_thread.spawn_service(Datastore) dbus_thread.spawn_service(Network) pid_path = self.new_instance() try: mountset.open() dbus_thread.start(mountset) except KeyboardInterrupt: util.exception('%s interrupted', self.name) except Exception: util.exception('%s aborted', self.name) finally: mounts_monitor.stop() jobs.kill() mountset.close() os.unlink(pid_path) 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 _sync(self, volume): contexts = volume['context'] docs, __ = contexts.find(limit=ad.MAX_LIMIT, keep_impl=[1, 2]) for context in docs: if not activities.ensure_checkins(context.guid): contexts.update(context.guid, keep_impl=0) locale.setlocale(locale.LC_ALL, '') # New defaults application.debug.value = sugar.logger_level() # It seems to be that most of users (on XO at least) don't have recent SSH node.trust_users.value = True # If tmpfs is mounted to /tmp, `os.fstat()` will return 0 free space # and will brake offline synchronization logic toolkit.tmpdir.value = sugar.profile_path('tmp') Option.seek('main', [application.debug]) Option.seek('webui', webui) Option.seek('local', local) Option.seek('local', [sugar.keyfile, toolkit.tmpdir]) Option.seek('node', [node.port, node.sync_dirs]) Option.seek('stats', stats) Option.seek('active-document', ad) app = Application( name='sugar-network', description='Sugar Network service.', epilog='See http://wiki.sugarlabs.org/go/Sugar_Network ' \ 'for details.', version=VERSION, config_files=[ '/etc/sweets.conf', '~/.config/sweets/config', sugar.profile_path('sweets.conf'), ]) app.start()