view mercurial/appendfile.py @ 4260:bdbfc2193524

appendfile: handle only changelog.i file
author Matt Mackall <mpm@selenic.com>
date Thu, 22 Mar 2007 20:10:46 -0500
parents eb081ba21eff
children
line wrap: on
line source

# appendfile.py - special classes to make repo updates atomic
#
# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

import cStringIO, changelog, errno, manifest, os, tempfile, util

# writes to metadata files are ordered.  reads: changelog, manifest,
# normal files.  writes: normal files, manifest, changelog.

# manifest contains pointers to offsets in normal files.  changelog
# contains pointers to offsets in manifest.  if reader reads old
# changelog while manifest or normal files are written, it has no
# pointers into new parts of those files that are maybe not consistent
# yet, so will not read them.

# localrepo.addchangegroup thinks it writes changelog first, then
# manifest, then normal files (this is order they are available, and
# needed for computing linkrev fields), but uses appendfile to hide
# updates from readers.  data not written to manifest or changelog
# until all normal files updated.  write manifest first, then
# changelog.

# with this write ordering, readers cannot see inconsistent view of
# repo during update.

class appendfile(object):
    '''implement enough of file protocol to append to revlog file.
    appended data is written to temp file.  reads and seeks span real
    file and temp file.  readers cannot see appended data until
    writedata called.'''

    def __init__(self, fp, tmpname):
        if tmpname:
            self.tmpname = tmpname
            self.tmpfp = util.posixfile(self.tmpname, 'ab+')
        else:
            fd, self.tmpname = tempfile.mkstemp(prefix="hg-appendfile-")
            os.close(fd)
            self.tmpfp = util.posixfile(self.tmpname, 'ab+')
        self.realfp = fp
        self.offset = fp.tell()
        # real file is not written by anyone else. cache its size so
        # seek and read can be fast.
        self.realsize = util.fstat(fp).st_size
        self.name = fp.name

    def end(self):
        self.tmpfp.flush() # make sure the stat is correct
        return self.realsize + util.fstat(self.tmpfp).st_size

    def tell(self):
        return self.offset

    def flush(self):
        self.tmpfp.flush()

    def close(self):
        self.realfp.close()
        self.tmpfp.close()

    def seek(self, offset, whence=0):
        '''virtual file offset spans real file and temp file.'''
        if whence == 0:
            self.offset = offset
        elif whence == 1:
            self.offset += offset
        elif whence == 2:
            self.offset = self.end() + offset

        if self.offset < self.realsize:
            self.realfp.seek(self.offset)
        else:
            self.tmpfp.seek(self.offset - self.realsize)

    def read(self, count=-1):
        '''only trick here is reads that span real file and temp file.'''
        fp = cStringIO.StringIO()
        old_offset = self.offset
        if self.offset < self.realsize:
            s = self.realfp.read(count)
            fp.write(s)
            self.offset += len(s)
            if count > 0:
                count -= len(s)
        if count != 0:
            if old_offset != self.offset:
                self.tmpfp.seek(self.offset - self.realsize)
            s = self.tmpfp.read(count)
            fp.write(s)
            self.offset += len(s)
        return fp.getvalue()

    def write(self, s):
        '''append to temp file.'''
        self.tmpfp.seek(0, 2)
        self.tmpfp.write(s)
        # all writes are appends, so offset must go to end of file.
        self.offset = self.realsize + self.tmpfp.tell()

class appendopener(object):
    '''special opener for files that only read or append.'''

    def __init__(self, opener):
        self.realopener = opener
        self.tmpname = None

    def __call__(self, name, mode='r'):
        '''open file.'''
        # only handle .i file
        if not name.endswith("."):
            return self.realopener(name, mode)
        assert mode in 'ra+'
        try:
            realfp = self.realopener(name, 'r')
        except IOError, err:
            if err.errno != errno.ENOENT: raise
            self.realfp = self.realopener(name, 'w+')
        fp = appendfile(realfp, self.tmpname)
        if tmpname is None:
            self.tmpname = fp.tmpname
            self.name = name
        return fp

    def writedata(self):
        '''copy data from temp files to real files.'''
        if not self.tmpname:
            return
        ifp = open(self.tmpname, 'rb')
        ofp = self.realopener(self.name, 'a')
        for chunk in util.filechunkiter(ifp):
            ofp.write(chunk)
        ifp.close()
        os.unlink(self.tmpname)
        ofp.close()

    def cleanup(self):
        '''delete temp files (this discards unwritten data!)'''
        if self.tmpname:
            os.unlink(self.tmpname)

# files for changelog and manifest are in different appendopeners, so
# not mixed up together.

class appendchangelog(changelog.changelog, appendopener):
    def __init__(self, opener):
        appendopener.__init__(self, opener)
        changelog.changelog.__init__(self, self)
    def checkinlinesize(self, fp, tr):
        return