~ubuntu-branches/ubuntu/oneiric/desktopcouch/oneiric

« back to all changes in this revision

Viewing changes to desktopcouch/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): Chad MILLER
  • Date: 2011-01-12 15:08:25 UTC
  • mfrom: (1.5.10 upstream)
  • Revision ID: james.westby@ubuntu.com-20110112150825-bzvn23kzufr0qdyb
Tags: 1.0.5-0ubuntu1
* New upstream release, skipping a few buggy releases.
* Split code into binary packages:
  - desktopcouch, configuration files and dependencies, but no code.
  - python-desktopcouch: transitional package
  - python-desktopcouch-application: local DB startup and discovery
  - python-desktopcouch-records: library for DB access anywhere
  - python-desktopcouch-recordtypes: support specific data structures
  - desktopcouch-ubuntuone, replication and pairing with cloud service
* Drop patch that some maverick apps incorrectly needed.
  patches/0-items-should-expose-private-data-for-now.patch
* Update package compatibility-version, 6 -> 7.
* Use newer debhelper and use python-support instead of python-central.
* Depend on contemporary python-couchdb, instead of ancient version.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2009 Canonical Ltd.
2
 
#
3
 
# This file is part of desktopcouch.
4
 
#
5
 
#  desktopcouch is free software: you can redistribute it and/or modify
6
 
# it under the terms of the GNU Lesser General Public License version 3
7
 
# as published by the Free Software Foundation.
8
 
#
9
 
# desktopcouch is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU Lesser General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU Lesser General Public License
15
 
# along with desktopcouch.  If not, see <http://www.gnu.org/licenses/>.
16
 
"Desktop Couch helper files"
17
 
 
18
 
from __future__ import with_statement
19
 
import os
20
 
import re
21
 
import xdg.BaseDirectory
22
 
import dbus
23
 
import logging
24
 
import platform
25
 
import os
26
 
from desktopcouch import local_files
27
 
 
28
 
 
29
 
def process_is_couchdb_linux(pid):
30
 
    """Find if the process with the given pid is couchdb."""
31
 
    if pid is None:
32
 
        return False
33
 
    pid = int(pid)  # ensure it's a number
34
 
    proc_dir = "/proc/%d" % (pid,)
35
 
    try:
36
 
        # check to make sure it is actually a desktop-couch instance
37
 
        with open(os.path.join(proc_dir, 'cmdline')) as cmd_file:
38
 
            cmd = cmd_file.read()
39
 
        if 'beam' not in cmd:
40
 
            return False
41
 
 
42
 
        # make sure it's our process.
43
 
        if not os.access(os.path.join(proc_dir, "mem"), os.W_OK):
44
 
            return False
45
 
 
46
 
    except IOError:
47
 
        return False
48
 
 
49
 
    return True
50
 
 
51
 
os_name = platform.system()
52
 
try:
53
 
    process_is_couchdb = {
54
 
        "Linux": process_is_couchdb_linux
55
 
        } [os_name]
56
 
except KeyError:
57
 
    raise NotImplementedError("os %r is not yet supported" % (os_name,))
58
 
 
59
 
def read_pidfile(ctx=local_files.DEFAULT_CONTEXT):
60
 
    """Read the pid file for the required information."""
61
 
    try:
62
 
        pid_file = ctx.file_pid
63
 
        if not os.path.exists(pid_file):
64
 
            return None
65
 
        with open(pid_file) as fp:
66
 
            try:
67
 
                contents = fp.read()
68
 
                if contents == "\n":
69
 
                    return None  # not yet written to pid file
70
 
                return int(contents)
71
 
            except ValueError:
72
 
                logging.warn("Pid file does not contain int: %r", contents)
73
 
                return None
74
 
    except IOError, e:
75
 
        logging.warn("Reading pid file caused error.  %s", e)
76
 
        return None
77
 
 
78
 
 
79
 
def find_pid(start_if_not_running=True, ctx=local_files.DEFAULT_CONTEXT):
80
 
    """Find the current OS process ID of the running couchdb.  API users
81
 
    should not use this, and instead go straight to find_port() ."""
82
 
    # Work out whether CouchDB is running by looking at its pid file
83
 
    pid = read_pidfile(ctx=ctx)
84
 
    if not process_is_couchdb(pid):
85
 
        if start_if_not_running:
86
 
            # start CouchDB by running the startup script
87
 
            logging.info("Desktop CouchDB is not running; starting it.")
88
 
            from desktopcouch import start_local_couchdb
89
 
            pid = start_local_couchdb.start_couchdb(ctx=ctx)
90
 
            # now load the design documents and pair records updates,
91
 
            # because it's started
92
 
            start_local_couchdb.update_design_documents()
93
 
            start_local_couchdb.update_pairing_service()
94
 
            if not process_is_couchdb(pid):
95
 
                logging.error("CouchDB process did not start up")
96
 
                raise RuntimeError("desktop-couch not started")
97
 
        else:
98
 
            return None
99
 
 
100
 
    return pid
101
 
 
102
 
def find_port(pid=None, ctx=local_files.DEFAULT_CONTEXT):
103
 
    """Ask the service daemon through DBUS what the port is.  This should start
104
 
    it up if it isn't running."""
105
 
 
106
 
    return _direct_access_find_port(pid=pid, ctx=ctx)
107
 
####
108
 
####
109
 
####
110
 
####  This fails in multithreaded execution in the client, apparenly
111
 
####  in DBus.  Chad's guess is that there is some collision in getting
112
 
####  a unique sequence ID for the asynchronous DBus method-call/
113
 
####  method-response.  Once that is worked out, resume using the
114
 
####  rest of this function instead of the direct access above.
115
 
    # Hrm, we don't use 'pid' or 'ctx' any more, since we go through DBus.
116
 
    if ctx != local_files.DEFAULT_CONTEXT or pid is not None:
117
 
        return _direct_access_find_port(pid=pid, ctx=ctx)
118
 
 
119
 
    bus = dbus.SessionBus()
120
 
    proxy = bus.get_object('org.desktopcouch.CouchDB', '/')
121
 
    return proxy.getPort()
122
 
####
123
 
####  ^^
124
 
 
125
 
 
126
 
def __find_port__linux(pid=None, ctx=local_files.DEFAULT_CONTEXT,
127
 
        retries_left=3):
128
 
    """This returns a valid port or raises a RuntimeError exception.  It never
129
 
    returns anything else."""
130
 
    if pid is None:
131
 
        pid = find_pid(start_if_not_running=True, ctx=ctx)
132
 
 
133
 
    if pid is None:
134
 
        if retries_left:
135
 
            return __find_port__linux(pid, ctx, retries_left - 1)
136
 
        raise RuntimeError("Have no PID to use to look up port.")
137
 
 
138
 
    proc_dir = "/proc/%d" % (pid,)
139
 
 
140
 
    # enumerate the process' file descriptors
141
 
    fd_dir = os.path.join(proc_dir, 'fd')
142
 
    fd_paths = list()
143
 
    try:
144
 
        for dirent in os.listdir(fd_dir):
145
 
            try:
146
 
                dirent_path = os.path.join(fd_dir, dirent)
147
 
                fd_paths.append(os.readlink(dirent_path))
148
 
            except OSError:
149
 
                logging.debug("dirent %r disappeared before " +
150
 
                    "we could read it. ", dirent_path)
151
 
                continue
152
 
    except OSError:
153
 
        if retries_left:
154
 
            return __find_port__linux(pid, ctx, retries_left - 1)
155
 
        logging.exception("Unable to find file descriptors in %s" % proc_dir)
156
 
        raise RuntimeError("Unable to find file descriptors in %s" % proc_dir)
157
 
 
158
 
    # identify socket fds
159
 
    socket_matches = [re.match('socket:\\[([0-9]+)\\]', p) for p in fd_paths]
160
 
    # extract their inode numbers
161
 
    socket_inodes = [m.group(1) for m in socket_matches if m is not None]
162
 
 
163
 
    # construct a subexpression which matches any one of these inodes
164
 
    inode_subexp = "|".join(map(re.escape, socket_inodes))
165
 
    # construct regexp to match /proc/net/tcp entries which are listening
166
 
    # sockets having one of the given inode numbers
167
 
    listening_regexp = re.compile(r'''
168
 
        \s*\d+:\s*                # sl
169
 
        [0-9A-F]{8}:              # local_address part 1
170
 
        ([0-9A-F]{4})\s+          # local_address part 2
171
 
        00000000:0000\s+          # rem_address
172
 
        0A\s+                     # st (0A = listening)
173
 
        [0-9A-F]{8}:              # tx_queue
174
 
        [0-9A-F]{8}\s+            # rx_queue
175
 
        [0-9A-F]{2}:              # tr
176
 
        [0-9A-F]{8}\s+            # tm->when
177
 
        [0-9A-F]{8}\s*            # retrnsmt
178
 
        \d+\s+\d+\s+              # uid, timeout
179
 
        (?:%s)\s+                 # inode
180
 
        ''' % (inode_subexp,), re.VERBOSE)
181
 
 
182
 
    # extract the TCP port from the first matching line in /proc/$pid/net/tcp
183
 
    port = None
184
 
    with open(os.path.join(proc_dir, 'net', 'tcp')) as tcp_file:
185
 
        for line in tcp_file:
186
 
            match = listening_regexp.match(line)
187
 
            if match is not None:
188
 
                port = str(int(match.group(1), 16))
189
 
                break
190
 
 
191
 
    if port is None:
192
 
        if retries_left:
193
 
            return __find_port__linux(pid, ctx, retries_left - 1)
194
 
        raise RuntimeError("Unable to find listening port")
195
 
 
196
 
    return port
197
 
 
198
 
import platform
199
 
os_name = platform.system()
200
 
try:
201
 
    _direct_access_find_port = {
202
 
        "Linux": __find_port__linux
203
 
        } [os_name]
204
 
except KeyError:
205
 
    logging.error("os %r is not yet supported" % (os_name,))
206
 
    raise NotImplementedError("os %r is not yet supported" % (os_name,))
 
1
"""Desktopcouch."""