~loggerhead-team/loggerhead/trunk-rich

47 by Robey Pointer
slowly moving the branch-specific stuff into a common structure...
1
#
2
# Copyright (C) 2006  Robey Pointer <robey@lag.net>
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program 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 General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
467.2.1 by Toshio Kuratomi
Change to the FSF address in license headers
16
# Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335  USA
47 by Robey Pointer
slowly moving the branch-specific stuff into a common structure...
17
#
18
19
"""
312.1.3 by Michael Hudson
docs/clarifications
20
a cache for chewed-up 'file change' data structures, which are basically just
21
a different way of storing a revision delta.  the cache improves lookup times
22
10x over bazaar's xml revision structure, though, so currently still worth
23
doing.
47 by Robey Pointer
slowly moving the branch-specific stuff into a common structure...
24
25
once a revision is committed in bazaar, it never changes, so once we have
26
cached a change, it's good forever.
27
"""
28
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
29
import marshal
47 by Robey Pointer
slowly moving the branch-specific stuff into a common structure...
30
import os
543.2.1 by Jelmer Vernooij
Sort Python import definitions with isort
31
import pickle
312.1.1 by Michael Hudson
a more intelligent (?) approach to concurrency
32
import tempfile
332.2.6 by Michael Hudson
zlib compress the marshalled data -- the data seems to compress by about 4:1
33
import zlib
519.1.2 by Jelmer Vernooij
Drop support for Python < 3 and Breezy < 3.1.
34
from sqlite3 import dbapi2
128.4.11 by Michael Hudson
fiddle things around so you can use the python-sqlite or python-pysqlite
35
312.1.3 by Michael Hudson
docs/clarifications
36
# We take an optimistic approach to concurrency here: we might do work twice
37
# in the case of races, but not crash or corrupt data.
128.4.5 by Michael Hudson
reorganizations, cleanups. still utterly horrid though.
38
332.2.8 by Michael Hudson
docstrings (omg!!)
39
def safe_init_db(filename, init_sql):
40
    # To avoid races around creating the database, we create the db in
41
    # a temporary file and rename it into the ultimate location.
42
    fd, temp_path = tempfile.mkstemp(dir=os.path.dirname(filename))
336 by Matt Nordhoff
Make sure to close mkstemp's file descriptor (bug #370845)
43
    os.close(fd)
332.2.8 by Michael Hudson
docstrings (omg!!)
44
    con = dbapi2.connect(temp_path)
45
    cur = con.cursor()
46
    cur.execute(init_sql)
47
    con.commit()
48
    con.close()
49
    os.rename(temp_path, filename)
50
128.4.5 by Michael Hudson
reorganizations, cleanups. still utterly horrid though.
51
class FakeShelf(object):
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
52
128.4.6 by Michael Hudson
remove one layer of ick
53
    def __init__(self, filename):
54
        create_table = not os.path.exists(filename)
312.1.1 by Michael Hudson
a more intelligent (?) approach to concurrency
55
        if create_table:
332.2.8 by Michael Hudson
docstrings (omg!!)
56
            safe_init_db(
57
                filename, "create table RevisionData "
58
                "(revid binary primary key, data binary)")
128.4.8 by Michael Hudson
remove storm dependency (it'll be back)
59
        self.connection = dbapi2.connect(filename)
128.4.11 by Michael Hudson
fiddle things around so you can use the python-sqlite or python-pysqlite
60
        self.cursor = self.connection.cursor()
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
61
312.1.1 by Michael Hudson
a more intelligent (?) approach to concurrency
62
    def _create_table(self, filename):
63
        con = dbapi2.connect(filename)
64
        cur = con.cursor()
65
        cur.execute(
128.4.6 by Michael Hudson
remove one layer of ick
66
            "create table RevisionData "
128.4.9 by Michael Hudson
gar, fix problem with NULLs
67
            "(revid binary primary key, data binary)")
312.1.1 by Michael Hudson
a more intelligent (?) approach to concurrency
68
        con.commit()
69
        con.close()
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
70
128.4.9 by Michael Hudson
gar, fix problem with NULLs
71
    def _serialize(self, obj):
491.2.32 by Jelmer Vernooij
Fix some more import errors.
72
        return dbapi2.Binary(pickle.dumps(obj, protocol=2))
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
73
128.4.9 by Michael Hudson
gar, fix problem with NULLs
74
    def _unserialize(self, data):
491.2.32 by Jelmer Vernooij
Fix some more import errors.
75
        return pickle.loads(str(data))
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
76
128.4.5 by Michael Hudson
reorganizations, cleanups. still utterly horrid though.
77
    def get(self, revid):
221.1.2 by Michael Hudson
remove pointless parameterization
78
        self.cursor.execute(
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
79
            "select data from revisiondata where revid = ?", (revid, ))
128.4.11 by Michael Hudson
fiddle things around so you can use the python-sqlite or python-pysqlite
80
        filechange = self.cursor.fetchone()
128.4.5 by Michael Hudson
reorganizations, cleanups. still utterly horrid though.
81
        if filechange is None:
82
            return None
83
        else:
128.4.9 by Michael Hudson
gar, fix problem with NULLs
84
            return self._unserialize(filechange[0])
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
85
305.1.6 by Michael Hudson
further simplification
86
    def add(self, revid, object):
312.1.1 by Michael Hudson
a more intelligent (?) approach to concurrency
87
        try:
88
            self.cursor.execute(
89
                "insert into revisiondata (revid, data) values (?, ?)",
90
                (revid, self._serialize(object)))
91
            self.connection.commit()
92
        except dbapi2.IntegrityError:
312.1.3 by Michael Hudson
docs/clarifications
93
            # If another thread or process attempted to set the same key, we
94
            # assume it set it to the same value and carry on with our day.
312.1.1 by Michael Hudson
a more intelligent (?) approach to concurrency
95
            pass
47 by Robey Pointer
slowly moving the branch-specific stuff into a common structure...
96
128.13.20 by Martin Albisetti
Merge from trunk! Yay!
97
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
98
class RevInfoDiskCache(object):
332.2.8 by Michael Hudson
docstrings (omg!!)
99
    """Like `RevInfoMemoryCache` but backed in a sqlite DB."""
332.2.3 by Michael Hudson
fairly tortuous two level caching
100
101
    def __init__(self, cache_path):
102
        if not os.path.exists(cache_path):
103
            os.mkdir(cache_path)
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
104
        filename = os.path.join(cache_path, 'revinfo.sql')
332.2.3 by Michael Hudson
fairly tortuous two level caching
105
        create_table = not os.path.exists(filename)
106
        if create_table:
332.2.8 by Michael Hudson
docstrings (omg!!)
107
            safe_init_db(
108
                filename, "create table Data "
109
                "(key binary primary key, revid binary, data binary)")
332.2.3 by Michael Hudson
fairly tortuous two level caching
110
        self.connection = dbapi2.connect(filename)
111
        self.cursor = self.connection.cursor()
112
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
113
    def get(self, key, revid):
491.2.39 by Jelmer Vernooij
More python3 porting.
114
        if not isinstance(key, bytes):
115
            raise TypeError(key)
116
        if not isinstance(revid, bytes):
117
            raise TypeError(revid)
332.2.3 by Michael Hudson
fairly tortuous two level caching
118
        self.cursor.execute(
332.2.8 by Michael Hudson
docstrings (omg!!)
119
            "select revid, data from data where key = ?", (dbapi2.Binary(key),))
332.2.3 by Michael Hudson
fairly tortuous two level caching
120
        row = self.cursor.fetchone()
121
        if row is None:
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
122
            return None
123
        elif str(row[0]) != revid:
124
            return None
332.2.3 by Michael Hudson
fairly tortuous two level caching
125
        else:
515.3.1 by Colin Watson
Make RevInfoDiskCache use a marshal version supported by Python 2.
126
            try:
127
                return marshal.loads(zlib.decompress(row[1]))
128
            except (EOFError, ValueError, TypeError):
129
                return None
332.2.3 by Michael Hudson
fairly tortuous two level caching
130
131
    def set(self, key, revid, data):
491.2.39 by Jelmer Vernooij
More python3 porting.
132
        if not isinstance(key, bytes):
133
            raise TypeError(key)
134
        if not isinstance(revid, bytes):
135
            raise TypeError(revid)
332.2.3 by Michael Hudson
fairly tortuous two level caching
136
        try:
137
            self.cursor.execute(
138
                'delete from data where key = ?', (dbapi2.Binary(key), ))
518.1.1 by Colin Watson
Fix marshal.dumps call.
139
            blob = zlib.compress(marshal.dumps(data, 2))
332.2.3 by Michael Hudson
fairly tortuous two level caching
140
            self.cursor.execute(
141
                "insert into data (key, revid, data) values (?, ?, ?)",
491.2.39 by Jelmer Vernooij
More python3 porting.
142
                list(map(dbapi2.Binary, [key, revid, blob])))
332.2.3 by Michael Hudson
fairly tortuous two level caching
143
            self.connection.commit()
144
        except dbapi2.IntegrityError:
145
            # If another thread or process attempted to set the same key, we
332.2.6 by Michael Hudson
zlib compress the marshalled data -- the data seems to compress by about 4:1
146
            # don't care too much -- it's only a cache after all!
332.2.3 by Michael Hudson
fairly tortuous two level caching
147
            pass