#!/usr/bin/python
# Copyright 2009 Canonical Ltd.
#
# This file is part of desktopcouch.
#
#  desktopcouch is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# desktopcouch 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with desktopcouch.  If not, see <http://www.gnu.org/licenses/>.
#
# Authors: Stuart Langridge <stuart.langridge@canonical.com>
#          Tim Cole <tim.cole@canonical.com>

"""CouchDB port advertiser.

A command-line utility which exports a
desktopCouch.getPort method on the bus which returns
that port, so other apps (specifically, the contacts API) can work out
where CouchDB is running so it can be talked to.

Calculates the port number by looking in the CouchDB log.

If CouchDB is not running, then run the script to start it and then
start advertising the port.

This file should be started by D-Bus activation.

"""

import os
import time
import logging
import logging.handlers
import signal

from twisted.internet import glib2reactor
glib2reactor.install()
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)

from twisted.internet import reactor as mainloop
import dbus.service

import desktopcouch
from desktopcouch import local_files
from desktopcouch import replication
from desktopcouch import stop_local_couchdb


class PortAdvertiser(dbus.service.Object):
    "Advertise the discovered port number on the D-Bus Session bus"
    def __init__(self, death):
        self.conn = dbus.SessionBus()
        self.death = death
        super(PortAdvertiser, self).__init__(object_path="/", conn=self.conn)

        # Here we commit to being ready to answer function calls.
        self.bus_name = dbus.service.BusName("org.desktopcouch.CouchDB",
                bus=self.conn)

    @dbus.service.method(dbus_interface='org.desktopcouch.CouchDB',
                         in_signature='', out_signature='i')
    def getPort(self):
        "Exported method to return the port"
        port = int(desktopcouch._direct_access_find_port())
        return port

    @dbus.service.method(dbus_interface='org.desktopcouch.CouchDB',
                         in_signature='', out_signature='')
    def quit(self):
        "Exported method to quit the program"
        self.death()


def set_up_logging(name):
    """Set logging preferences for this process."""
    import xdg.BaseDirectory
    log_directory = os.path.join(xdg.BaseDirectory.xdg_cache_home,
            "desktop-couch/log")
    try:
        os.makedirs(log_directory)
    except:
        pass

    rotating_log = logging.handlers.TimedRotatingFileHandler(
            os.path.join(log_directory, "desktop-couch-%s.log" % (name,)),
            "midnight", 1, 14)
    rotating_log.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
    rotating_log.setFormatter(formatter)
    logging.getLogger('').addHandler(rotating_log)
    console_log = logging.StreamHandler()
    console_log.setLevel(logging.WARNING)
    console_log.setFormatter(logging.Formatter(
            "%s %%(asctime)s - %%(message)s" % (name,)))
    logging.getLogger('').addHandler(console_log)
    logging.getLogger('').setLevel(logging.DEBUG)


def replicator_main(couchdb_port, ctx=local_files.DEFAULT_CONTEXT):
    replication_runtime = replication.set_up(lambda: couchdb_port)
    try:
        logging.debug("starting replicator main loop")
        mainloop.run()
    finally:
        logging.debug("ending replicator main loop")
        if replication_runtime:
            replication.tear_down(*replication_runtime)
        

def dbus_server_main(ctx=local_files.DEFAULT_CONTEXT):
    portAdvertiser = PortAdvertiser(mainloop.stop)
    logging.debug("starting dbus main loop")
    try:
        mainloop.run()
    finally:
        logging.debug("ending dbus main loop")


def main(ctx=local_files.DEFAULT_CONTEXT):
    should_shut_down_couchdb = False
    couchdb_pid = desktopcouch.find_pid(start_if_not_running=False, ctx=ctx)
    if couchdb_pid is None:
        logging.warn("Starting up personal couchdb.")
        couchdb_pid = desktopcouch.find_pid(start_if_not_running=True, ctx=ctx)
        should_shut_down_couchdb = True
    else:
        logging.warn("Personal couchdb is already running at PID#%d.",
                couchdb_pid)

    couchdb_port = desktopcouch._direct_access_find_port(pid=couchdb_pid,
            ctx=ctx)
    child_pid = os.fork()  # Split!
    if child_pid == 0:
        # Let's be the replicator!
        set_up_logging("replication")
        os.nice(10)
        replicator_main(couchdb_port)
        return
    else:
        assert child_pid > 0
        # Let's be the dbus server!  This is the parent process.  When we exit,
        # we kill children.
        try:
            set_up_logging("dbus")
            dbus_server_main()
        except:
            logging.exception("uncaught exception makes us shut down.")
        finally:
            logging.info("exiting.")
            if should_shut_down_couchdb:
                logging.warn("shutting down personal couchdb.")
                stop_local_couchdb.stop_couchdb(ctx=ctx)
                
            try:
                os.kill(child_pid, signal.SIGTERM)
                time.sleep(1)
                os.kill(child_pid, signal.SIGKILL)
            except OSError:
                pass  

            return


if __name__ == "__main__":
    import gobject
    gobject.set_application_name("desktopcouch service")

    main() 
