23
22
__copyright__ = "Copyright (C) 2005 Canonical Ltd."
24
23
__author__ = "Martin Pool <mbp@canonical.com>"
26
import os, tempfile, types, osutils
25
import os, tempfile, types, osutils, gzip, errno
26
from stat import ST_SIZE
27
27
from StringIO import StringIO
28
28
from trace import mutter
31
30
######################################################################
58
57
>>> st['123123'].read()
61
:todo: Atomic add by writing to a temporary file and renaming.
60
TODO: Atomic add by writing to a temporary file and renaming.
63
:todo: Perhaps automatically transform to/from XML in a method?
62
TODO: Perhaps automatically transform to/from XML in a method?
64
63
Would just need to tell the constructor what class to
67
:todo: Even within a simple disk store like this, we could
66
TODO: Even within a simple disk store like this, we could
68
67
gzip the files. But since many are less than one disk
69
68
block, that might not help a lot.
75
74
self._basedir = basedir
77
76
def _path(self, id):
78
78
return os.path.join(self._basedir, id)
80
80
def __repr__(self):
81
81
return "%s(%r)" % (self.__class__.__name__, self._basedir)
83
def add(self, f, fileid):
83
def add(self, f, fileid, compressed=True):
84
84
"""Add contents of a file into the store.
86
:param f: An open file, or file-like object."""
86
f -- An open file, or file-like object."""
87
87
# FIXME: Only works on smallish files
88
88
# TODO: Can be optimized by copying at the same time as
89
89
# computing the sum.
95
if fileid not in self:
96
filename = self._path(fileid)
97
f = file(filename, 'wb')
102
osutils.make_readonly(filename)
96
p = self._path(fileid)
97
if os.access(p, os.F_OK) or os.access(p + '.gz', os.F_OK):
98
bailout("store %r already contains id %r" % (self._basedir, fileid))
101
f = gzip.GzipFile(p + '.gz', 'wb')
102
os.chmod(p + '.gz', 0444)
111
def copy_multi(self, other, ids):
112
"""Copy texts for ids from other into self.
114
If an id is present in self, it is skipped. A count of copied
115
ids is returned, which may be less than len(ids).
117
from bzrlib.progress import ProgressBar
119
pb.update('preparing to copy')
120
to_copy = [id for id in ids if id not in self]
124
pb.update('copy', count, len(to_copy))
125
self.add(other[id], id)
126
assert count == len(to_copy)
105
131
def __contains__(self, fileid):
107
return os.access(self._path(fileid), os.R_OK)
133
p = self._path(fileid)
134
return (os.access(p, os.R_OK)
135
or os.access(p + '.gz', os.R_OK))
137
# TODO: Guard against the same thing being stored twice, compressed and uncompresse
110
139
def __iter__(self):
111
return iter(os.listdir(self._basedir))
140
for f in os.listdir(self._basedir):
142
# TODO: case-insensitive?
148
return len(os.listdir(self._basedir))
113
150
def __getitem__(self, fileid):
114
151
"""Returns a file reading from a particular entry."""
115
return file(self._path(fileid), 'rb')
117
def delete_all(self):
121
def delete(self, fileid):
122
"""Remove nominated store entry.
124
Most stores will be add-only."""
125
filename = self._path(fileid)
126
## osutils.make_writable(filename)
130
"""Remove store; only allowed if it is empty."""
131
os.rmdir(self._basedir)
132
mutter("%r destroyed" % self)
152
p = self._path(fileid)
154
return gzip.GzipFile(p + '.gz', 'rb')
156
if e.errno == errno.ENOENT:
161
def total_size(self):
162
"""Return (count, bytes)
164
This is the (compressed) size stored on disk, not the size of
172
total += os.stat(p)[ST_SIZE]
174
total += os.stat(p + '.gz')[ST_SIZE]
143
188
ImmutableStore.__init__(self, tempfile.mkdtemp())
145
190
def __del__(self):
191
for f in os.listdir(self._basedir):
192
fpath = os.path.join(self._basedir, f)
193
# needed on windows, and maybe some other filesystems
194
os.chmod(fpath, 0600)
196
os.rmdir(self._basedir)
197
mutter("%r destroyed" % self)