1
# Copyright 2009 Canonical Ltd.
3
# This file is part of desktopcouch.
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.
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.
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/>.
17
# Authors: Eric Casteleijn <eric.casteleijn@canonical.com>
18
# Mark G. Saye <mark.saye@canonical.com>
19
# Stuart Langridge <stuart.langridge@canonical.com>
21
"""The Desktop Couch Records API."""
23
from couchdb import Server
24
from couchdb.client import ResourceNotFound
26
from record import Record
28
class NoSuchDatabase(Exception):
29
"Exception for trying to use a non-existent database"
31
def __init__(self, dbname):
32
self.database = dbname
33
super(NoSuchDatabase, self).__init__()
36
return ("Database %s does not exist on this server. (Create it by "
37
"passing create=True)") % self.database
40
class CouchDatabase(object):
41
"""An small records specific abstraction over a couch db database."""
43
def __init__(self, database, uri=None, record_factory=None, create=False):
45
port = self._find_couchdb_port()
46
self.server_uri = "http://localhost:%s" % port
49
self._server = Server(self.server_uri)
50
if database not in self._server:
52
self._server.create(database)
54
raise NoSuchDatabase(database)
55
self.db = self._server[database]
56
self.record_factory = record_factory or Record
58
def _find_couchdb_port(self):
59
"""Look for a running Desktop CouchDB. If there isn't one, fall back
60
to the system CouchDB on 5984."""
61
# Currently using horrible lsof command; this will eventually use D-Bus
62
# pylint: disable-msg=W0702
65
from desktopcouch import local_files
66
lsofcmd = 'lsof -i4TCP@localhost | grep $(couchdb -C %s ' + \
67
'-p %s -s | cut -d" " -f7 | cut -d "," -f1) 2>/dev/null | ' + \
68
'grep "TCP " | grep LISTEN | ' + \
69
'awk \'{print $8}\' | cut -d":" -f2'
70
actual_lsof_cmd = lsofcmd % (local_files.FILE_INI,
72
stdout, stderr = subprocess.Popen(actual_lsof_cmd, shell=True,
73
stdout=subprocess.PIPE).communicate()
81
def query(self, map_fun, reduce_fun=None, language='javascript',
82
wrapper=None, **options):
83
"""Pass-through to CouchDB query"""
84
return self.db.query(map_fun, reduce_fun, language,
87
def get_record(self, record_id):
88
"""Get a record from back end storage."""
90
couch_record = self.db[record_id]
91
except ResourceNotFound:
94
data.update(couch_record)
95
record = self.record_factory(data=data)
96
record.record_id = record_id
99
def put_record(self, record):
100
"""Put a record in back end storage."""
101
record_id = record.record_id or record._data.get('_id', '')
102
record_data = record._data
104
self.db[record_id] = record_data
106
record_id = self._add_record(record_data)
109
def _add_record(self, data):
110
"""Add a new record to the storage backend."""
111
return self.db.create(data)
114
def delete_record(self, record_id):
115
"""Delete record with given id"""
116
record = self.db[record_id]
117
record.setdefault('application_annotations', {}).setdefault(
118
'Ubuntu One', {}).setdefault('private_application_annotations', {})[
120
self.db[record_id] = record
123
def record_exists(self, record_id):
124
"""Check if record with given id exists"""
125
if record_id not in self.db:
127
record = self.db[record_id]
128
return 'deleted' not in record.get('application_annotations', {}).get(
129
'Ubuntu One', {}).get('private_application_annotations', {})