##// END OF EJS Templates
appendfile: handle only changelog.i file
Matt Mackall -
r4260:bdbfc219 default
parent child Browse files
Show More
@@ -1,154 +1,152
1 1 # appendfile.py - special classes to make repo updates atomic
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import cStringIO, changelog, errno, manifest, os, tempfile, util
9 9
10 10 # writes to metadata files are ordered. reads: changelog, manifest,
11 11 # normal files. writes: normal files, manifest, changelog.
12 12
13 13 # manifest contains pointers to offsets in normal files. changelog
14 14 # contains pointers to offsets in manifest. if reader reads old
15 15 # changelog while manifest or normal files are written, it has no
16 16 # pointers into new parts of those files that are maybe not consistent
17 17 # yet, so will not read them.
18 18
19 19 # localrepo.addchangegroup thinks it writes changelog first, then
20 20 # manifest, then normal files (this is order they are available, and
21 21 # needed for computing linkrev fields), but uses appendfile to hide
22 22 # updates from readers. data not written to manifest or changelog
23 23 # until all normal files updated. write manifest first, then
24 24 # changelog.
25 25
26 26 # with this write ordering, readers cannot see inconsistent view of
27 27 # repo during update.
28 28
29 29 class appendfile(object):
30 30 '''implement enough of file protocol to append to revlog file.
31 31 appended data is written to temp file. reads and seeks span real
32 32 file and temp file. readers cannot see appended data until
33 33 writedata called.'''
34 34
35 35 def __init__(self, fp, tmpname):
36 36 if tmpname:
37 37 self.tmpname = tmpname
38 38 self.tmpfp = util.posixfile(self.tmpname, 'ab+')
39 39 else:
40 40 fd, self.tmpname = tempfile.mkstemp(prefix="hg-appendfile-")
41 41 os.close(fd)
42 42 self.tmpfp = util.posixfile(self.tmpname, 'ab+')
43 43 self.realfp = fp
44 44 self.offset = fp.tell()
45 45 # real file is not written by anyone else. cache its size so
46 46 # seek and read can be fast.
47 47 self.realsize = util.fstat(fp).st_size
48 48 self.name = fp.name
49 49
50 50 def end(self):
51 51 self.tmpfp.flush() # make sure the stat is correct
52 52 return self.realsize + util.fstat(self.tmpfp).st_size
53 53
54 54 def tell(self):
55 55 return self.offset
56 56
57 57 def flush(self):
58 58 self.tmpfp.flush()
59 59
60 60 def close(self):
61 61 self.realfp.close()
62 62 self.tmpfp.close()
63 63
64 64 def seek(self, offset, whence=0):
65 65 '''virtual file offset spans real file and temp file.'''
66 66 if whence == 0:
67 67 self.offset = offset
68 68 elif whence == 1:
69 69 self.offset += offset
70 70 elif whence == 2:
71 71 self.offset = self.end() + offset
72 72
73 73 if self.offset < self.realsize:
74 74 self.realfp.seek(self.offset)
75 75 else:
76 76 self.tmpfp.seek(self.offset - self.realsize)
77 77
78 78 def read(self, count=-1):
79 79 '''only trick here is reads that span real file and temp file.'''
80 80 fp = cStringIO.StringIO()
81 81 old_offset = self.offset
82 82 if self.offset < self.realsize:
83 83 s = self.realfp.read(count)
84 84 fp.write(s)
85 85 self.offset += len(s)
86 86 if count > 0:
87 87 count -= len(s)
88 88 if count != 0:
89 89 if old_offset != self.offset:
90 90 self.tmpfp.seek(self.offset - self.realsize)
91 91 s = self.tmpfp.read(count)
92 92 fp.write(s)
93 93 self.offset += len(s)
94 94 return fp.getvalue()
95 95
96 96 def write(self, s):
97 97 '''append to temp file.'''
98 98 self.tmpfp.seek(0, 2)
99 99 self.tmpfp.write(s)
100 100 # all writes are appends, so offset must go to end of file.
101 101 self.offset = self.realsize + self.tmpfp.tell()
102 102
103 103 class appendopener(object):
104 104 '''special opener for files that only read or append.'''
105 105
106 106 def __init__(self, opener):
107 107 self.realopener = opener
108 # key: file name, value: appendfile name
109 self.tmpnames = {}
108 self.tmpname = None
110 109
111 110 def __call__(self, name, mode='r'):
112 111 '''open file.'''
113
112 # only handle .i file
113 if not name.endswith("."):
114 return self.realopener(name, mode)
114 115 assert mode in 'ra+'
115 116 try:
116 117 realfp = self.realopener(name, 'r')
117 118 except IOError, err:
118 119 if err.errno != errno.ENOENT: raise
119 realfp = self.realopener(name, 'w+')
120 tmpname = self.tmpnames.get(name)
121 fp = appendfile(realfp, tmpname)
120 self.realfp = self.realopener(name, 'w+')
121 fp = appendfile(realfp, self.tmpname)
122 122 if tmpname is None:
123 self.tmpnames[name] = fp.tmpname
123 self.tmpname = fp.tmpname
124 self.name = name
124 125 return fp
125 126
126 127 def writedata(self):
127 128 '''copy data from temp files to real files.'''
128 # write .d file before .i file.
129 tmpnames = self.tmpnames.items()
130 tmpnames.sort()
131 for name, tmpname in tmpnames:
132 ifp = open(tmpname, 'rb')
133 ofp = self.realopener(name, 'a')
134 for chunk in util.filechunkiter(ifp):
135 ofp.write(chunk)
136 ifp.close()
137 os.unlink(tmpname)
138 del self.tmpnames[name]
139 ofp.close()
129 if not self.tmpname:
130 return
131 ifp = open(self.tmpname, 'rb')
132 ofp = self.realopener(self.name, 'a')
133 for chunk in util.filechunkiter(ifp):
134 ofp.write(chunk)
135 ifp.close()
136 os.unlink(self.tmpname)
137 ofp.close()
140 138
141 139 def cleanup(self):
142 140 '''delete temp files (this discards unwritten data!)'''
143 for tmpname in self.tmpnames.values():
144 os.unlink(tmpname)
141 if self.tmpname:
142 os.unlink(self.tmpname)
145 143
146 144 # files for changelog and manifest are in different appendopeners, so
147 145 # not mixed up together.
148 146
149 147 class appendchangelog(changelog.changelog, appendopener):
150 148 def __init__(self, opener):
151 149 appendopener.__init__(self, opener)
152 150 changelog.changelog.__init__(self, self)
153 151 def checkinlinesize(self, fp, tr):
154 152 return
General Comments 0
You need to be logged in to leave comments. Login now