mercurial/appendfile.py
changeset 2089 cb99c711c59f
parent 2082 856f0ba200bc
child 2102 c62112815801
--- a/mercurial/appendfile.py
+++ b/mercurial/appendfile.py
@@ -6,7 +6,7 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from demandload import *
-demandload(globals(), "cStringIO changelog manifest os tempfile")
+demandload(globals(), "cStringIO changelog errno manifest os tempfile")
 
 # writes to metadata files are ordered.  reads: changelog, manifest,
 # normal files.  writes: normal files, manifest, changelog.
@@ -33,18 +33,32 @@ class appendfile(object):
     file and temp file.  readers cannot see appended data until
     writedata called.'''
 
-    def __init__(self, fp):
-        fd, self.tmpname = tempfile.mkstemp()
-        self.tmpfp = os.fdopen(fd, 'ab+')
+    def __init__(self, fp, tmpname):
+        if tmpname:
+            self.tmpname = tmpname
+            self.tmpfp = open(self.tmpname, 'ab+')
+        else:
+            fd, self.tmpname = tempfile.mkstemp()
+            self.tmpfp = os.fdopen(fd, '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.fpsize = os.fstat(fp.fileno()).st_size
+        self.realsize = os.fstat(fp.fileno()).st_size
 
     def end(self):
         self.tmpfp.flush() # make sure the stat is correct
-        return self.fpsize + os.fstat(self.tmpfp.fileno()).st_size
+        return self.realsize + os.fstat(self.tmpfp.fileno()).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.'''
@@ -55,16 +69,16 @@ class appendfile(object):
         elif whence == 2:
             self.offset = self.end() + offset
 
-        if self.offset < self.fpsize:
+        if self.offset < self.realsize:
             self.realfp.seek(self.offset)
         else:
-            self.tmpfp.seek(self.offset - self.fpsize)
+            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.fpsize:
+        if self.offset < self.realsize:
             s = self.realfp.read(count)
             fp.write(s)
             self.offset += len(s)
@@ -72,7 +86,7 @@ class appendfile(object):
                 count -= len(s)
         if count != 0:
             if old_offset != self.offset:
-                self.tmpfp.seek(self.offset - self.fpsize)
+                self.tmpfp.seek(self.offset - self.realsize)
             s = self.tmpfp.read(count)
             fp.write(s)
             self.offset += len(s)
@@ -83,98 +97,47 @@ class appendfile(object):
         self.tmpfp.seek(0, 2)
         self.tmpfp.write(s)
         # all writes are appends, so offset must go to end of file.
-        self.offset = self.fpsize + self.tmpfp.tell()
-
-    def writedata(self):
-        '''copy data from temp file to real file.'''
-        self.tmpfp.seek(0)
-        s = self.tmpfp.read()
-        self.tmpfp.close()
-        self.realfp.seek(0, 2)
-        # small race here.  we write all new data in one call, but
-        # reader can see partial update due to python or os. file
-        # locking no help: slow, not portable, not reliable over nfs.
-        # only safe thing is write to temp file every time and rename,
-        # but performance bad when manifest or changelog gets big.
-        self.realfp.write(s)
-        self.realfp.close()
-
-    def __del__(self):
-        '''delete temp file even if exception raised.'''
-        try: os.unlink(self.tmpname)
-        except: pass
-
-class sharedfile(object):
-    '''let file objects share a single appendfile safely.  each
-    sharedfile has own offset, syncs up with appendfile offset before
-    read and after read and write.'''
-
-    def __init__(self, fp):
-        self.fp = fp
-        self.offset = 0
-
-    def tell(self):
-        return self.offset
-
-    def seek(self, offset, whence=0):
-        if whence == 0:
-            self.offset = offset
-        elif whence == 1:
-            self.offset += offset
-        elif whence == 2:
-            self.offset = self.fp.end() + offset
-
-    def read(self, count=-1):
-        try:
-            if self.offset != self.fp.offset:
-                self.fp.seek(self.offset)
-            return self.fp.read(count)
-        finally:
-            self.offset = self.fp.offset
-
-    def write(self, s):
-        try:
-            return self.fp.write(s)
-        finally:
-            self.offset = self.fp.offset
-
-    def close(self):
-        # revlog wants this.
-        pass
-
-    def flush(self):
-        # revlog wants this.
-        pass
-
-    def writedata(self):
-        self.fp.writedata()
+        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
-        # key: file name, value: appendfile object
-        self.fps = {}
+        # key: file name, value: appendfile name
+        self.tmpnames = {}
 
     def __call__(self, name, mode='r'):
-        '''open file.  return same cached appendfile object for every
-        later call.'''
+        '''open file.'''
 
         assert mode in 'ra+'
-        fp = self.fps.get(name)
-        if fp is None:
-            fp = appendfile(self.realopener(name, 'a+'))
-            self.fps[name] = fp
-        return sharedfile(fp)
+        try:
+            realfp = self.realopener(name, 'r')
+        except IOError, err:
+            if err.errno != errno.ENOENT: raise
+            realfp = self.realopener(name, 'w+')
+        tmpname = self.tmpnames.get(name)
+        fp = appendfile(realfp, tmpname)
+        if tmpname is None:
+            self.tmpnames[name] = fp.tmpname
+        return fp
 
     def writedata(self):
         '''copy data from temp files to real files.'''
         # write .d file before .i file.
-        fps = self.fps.items()
-        fps.sort()
-        for name, fp in fps:
-            fp.writedata()
+        tmpnames = self.tmpnames.items()
+        tmpnames.sort()
+        for name, tmpname in tmpnames:
+            fp = open(tmpname, 'rb')
+            s = fp.read()
+            fp.close()
+            fp = self.realopener(name, 'a')
+            fp.write(s)
+            fp.close()
+
+    def __del__(self):
+        for tmpname in self.tmpnames.itervalues():
+            os.unlink(tmpname)
 
 # files for changelog and manifest are in different appendopeners, so
 # not mixed up together.