~vds/desktopcouch/mergeable_list_not_default_anymore

« back to all changes in this revision

Viewing changes to desktopcouch/service.py

  • Committer: Tarmac
  • Author(s): Manuel de la Pena
  • Date: 2010-11-01 22:47:48 UTC
  • mfrom: (184.2.1 refactor_service)
  • Revision ID: tarmac-20101101224748-2k29so5rv8in1o1k
Refactors how the service of desktopcouch so that is more testable and portable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# Copyright 2009 Canonical Ltd.
 
3
#
 
4
# This file is part of desktopcouch.
 
5
#
 
6
#  desktopcouch is free software: you can redistribute it and/or modify
 
7
# it under the terms of the GNU Lesser General Public License version 3
 
8
# as published by the Free Software Foundation.
 
9
#
 
10
# desktopcouch is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU Lesser General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU Lesser General Public License
 
16
# along with desktopcouch.  If not, see <http://www.gnu.org/licenses/>.
 
17
#
 
18
# Authors: Stuart Langridge <stuart.langridge@canonical.com>
 
19
#          Tim Cole <tim.cole@canonical.com>
 
20
 
 
21
"""CouchDB port advertiser.
 
22
 
 
23
A command-line utility which exports a
 
24
desktopCouch.getPort method on the bus which returns
 
25
that port, so other apps (specifically, the contacts API) can work out
 
26
where CouchDB is running so it can be talked to.
 
27
 
 
28
Calculates the port number by looking in the CouchDB log.
 
29
 
 
30
If CouchDB is not running, then run the script to start it and then
 
31
start advertising the port.
 
32
 
 
33
This file should be started by D-Bus activation.
 
34
 
 
35
"""
 
36
 
 
37
import os
 
38
import time
 
39
import logging
 
40
import logging.handlers
 
41
import signal
 
42
 
 
43
from twisted.internet import reactor as mainloop
 
44
import dbus.service
 
45
 
 
46
import desktopcouch
 
47
from desktopcouch import local_files
 
48
from desktopcouch import replication
 
49
from desktopcouch import stop_local_couchdb
 
50
 
 
51
def set_up_logging(name):
 
52
    """Set logging preferences for this process."""
 
53
    import xdg.BaseDirectory
 
54
    log_directory = os.path.join(xdg.BaseDirectory.xdg_cache_home,
 
55
            "desktop-couch/log")
 
56
    try:
 
57
        os.makedirs(log_directory)
 
58
    except:
 
59
        pass
 
60
 
 
61
    rotating_log = logging.handlers.TimedRotatingFileHandler(
 
62
            os.path.join(log_directory, "desktop-couch-%s.log" % (name,)),
 
63
            "midnight", 1, 14)
 
64
    rotating_log.setLevel(logging.DEBUG)
 
65
    formatter = logging.Formatter(
 
66
        '%(asctime)s %(levelname)-8s %(message)s')
 
67
    rotating_log.setFormatter(formatter)
 
68
    logging.getLogger('').addHandler(rotating_log)
 
69
    console_log = logging.StreamHandler()
 
70
    console_log.setLevel(logging.WARNING)
 
71
    console_log.setFormatter(logging.Formatter(
 
72
            "%s %%(asctime)s - %%(message)s" % (name,)))
 
73
    logging.getLogger('').addHandler(console_log)
 
74
    logging.getLogger('').setLevel(logging.DEBUG)
 
75
 
 
76
class PortAdvertiser(dbus.service.Object):
 
77
    "Advertise the discovered port number on the D-Bus Session bus"
 
78
    def __init__(self, death):
 
79
        self.conn = dbus.SessionBus()
 
80
        self.death = death
 
81
        super(PortAdvertiser, self).__init__(object_path="/", 
 
82
            conn=self.conn)
 
83
 
 
84
        # Here we commit to being ready to answer function calls.
 
85
        self.bus_name = dbus.service.BusName("org.desktopcouch.CouchDB",
 
86
                bus=self.conn)
 
87
 
 
88
    @dbus.service.method(dbus_interface='org.desktopcouch.CouchDB',
 
89
                         in_signature='', out_signature='i')
 
90
    def getPort(self):
 
91
        "Exported method to return the port"
 
92
        port = int(desktopcouch._direct_access_find_port())
 
93
        return port
 
94
 
 
95
    @dbus.service.method(dbus_interface='org.desktopcouch.CouchDB',
 
96
                         in_signature='', out_signature='')
 
97
    def quit(self):
 
98
        "Exported method to quit the program"
 
99
        self.death()
 
100
 
 
101
class DesktopcouchService():
 
102
    """Host the services."""
 
103
    
 
104
    def __init__(self, main_loop, 
 
105
        pid_finder=desktopcouch.find_pid,
 
106
        port_finder=desktopcouch._direct_access_find_port,
 
107
        ctx=local_files.DEFAULT_CONTEXT,
 
108
        stop_couchdb=stop_local_couchdb.stop_couchdb,
 
109
        replication=replication,
 
110
        advertiser_factory=PortAdvertiser,
 
111
        logging=set_up_logging,
 
112
        fork=os.fork,
 
113
        nice=os.nice,
 
114
        kill=os.kill,
 
115
        sleep=time.sleep):
 
116
        self._mainloop = main_loop
 
117
        self._pid_finder = pid_finder
 
118
        self._port_finder = port_finder
 
119
        self._ctx = ctx
 
120
        self._stop_couchdb = stop_couchdb
 
121
        self._replication = replication
 
122
        self._advertiser_factory = advertiser_factory
 
123
        self._logging = logging
 
124
        self._fork = fork
 
125
        self._nice = nice
 
126
        self._kill = kill
 
127
        self._sleep = sleep
 
128
 
 
129
    def _start_replicator_main(self, couchdb_port):
 
130
        replication_runtime = self._replication.set_up(
 
131
            lambda: couchdb_port)
 
132
        try:
 
133
            logging.debug("starting replicator main loop")
 
134
            self._mainloop.run()
 
135
        finally:
 
136
            logging.debug("ending replicator main loop")
 
137
            if replication_runtime:
 
138
                replication.tear_down(*replication_runtime)
 
139
 
 
140
    def _start_server_main(self):
 
141
        portAdvertiser = self._advertiser_factory(self._mainloop.stop)
 
142
        logging.debug("starting dbus main loop")
 
143
        try:
 
144
            self._mainloop.run()
 
145
        finally:
 
146
            logging.debug("ending dbus main loop")
 
147
 
 
148
    def start(self):
 
149
        """Start the services used by desktopcouch."""
 
150
        should_shut_down_couchdb = False
 
151
        couchdb_pid = self._pid_finder(start_if_not_running=False, 
 
152
            ctx=self._ctx)
 
153
        if couchdb_pid is None:
 
154
            logging.warn("Starting up personal couchdb.")
 
155
            couchdb_pid = self._pid_finder(start_if_not_running=True, 
 
156
                ctx=self._ctx)
 
157
            should_shut_down_couchdb = True
 
158
        else:
 
159
            logging.warn("Personal couchdb is already running at PID#%d.",
 
160
                    couchdb_pid)
 
161
 
 
162
        couchdb_port = self._port_finder(pid=couchdb_pid, ctx=self._ctx)
 
163
        child_pid = self._fork()  # Split!
 
164
        if child_pid == 0:
 
165
            # Let's be the replicator!
 
166
            self._logging("replication")
 
167
            self._nice(10)
 
168
            self._start_replicator_main(couchdb_port)
 
169
            return
 
170
        else:
 
171
            assert child_pid > 0
 
172
            # Let's be the dbus server!  
 
173
            # This is the parent process.  When we exit, we kill children.
 
174
            try:
 
175
                set_up_logging("dbus")
 
176
                # offer the dbus service
 
177
                self._start_server_main()
 
178
            except:
 
179
                logging.exception(
 
180
                    "uncaught exception makes us shut down.")
 
181
            finally:
 
182
                logging.info("exiting.")
 
183
                if should_shut_down_couchdb:
 
184
                    logging.warn("shutting down personal couchdb.")
 
185
                    self._stop_couchdb(ctx=self._ctx)
 
186
                
 
187
                    try:
 
188
                        self._kill(child_pid, signal.SIGTERM)
 
189
                        self._sleep(1)
 
190
                        self._kill(child_pid, signal.SIGKILL)
 
191
                    except OSError:
 
192
                        pass  
 
193
 
 
194
                return
 
195