~bzr-pqm/bzr/bzr.dev

409 by Martin Pool
- New AtomicFile class
1
# Copyright (C) 2004, 2005 by Canonical Ltd
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
18
563 by Martin Pool
- AtomicFile emits a warning if it is gc'd without being closed
19
from warnings import warn
20
21
558 by Martin Pool
- All top-level classes inherit from object
22
class AtomicFile(object):
409 by Martin Pool
- New AtomicFile class
23
    """A file that does an atomic-rename to move into place.
24
25
    This also causes hardlinks to break when it's written out.
26
27
    Open this as for a regular file, then use commit() to move into
28
    place or abort() to cancel.
29
431 by Martin Pool
- stat cache is written in utf-8 to accomodate non-ascii
30
    An encoding can be specified; otherwise the default is ascii.
409 by Martin Pool
- New AtomicFile class
31
    """
32
431 by Martin Pool
- stat cache is written in utf-8 to accomodate non-ascii
33
    def __init__(self, filename, mode='wb', encoding=None):
409 by Martin Pool
- New AtomicFile class
34
        if mode != 'wb' and mode != 'wt':
35
            raise ValueError("invalid AtomicFile mode %r" % mode)
36
37
        import os, socket
447 by Martin Pool
- atomicfile temporaries should end in .tmp to make it
38
        self.tmpfilename = '%s.%d.%s.tmp' % (filename, os.getpid(),
409 by Martin Pool
- New AtomicFile class
39
                                             socket.gethostname())
40
        self.realfilename = filename
41
        
431 by Martin Pool
- stat cache is written in utf-8 to accomodate non-ascii
42
        if encoding:
43
            import codecs
563 by Martin Pool
- AtomicFile emits a warning if it is gc'd without being closed
44
            self.f = codecs.open(self.tmpfilename, mode, encoding)
45
        else:
46
            self.f = open(self.tmpfilename, mode)
47
409 by Martin Pool
- New AtomicFile class
48
        self.write = self.f.write
563 by Martin Pool
- AtomicFile emits a warning if it is gc'd without being closed
49
        self.closed = False
50
51
52
    def __repr__(self):
53
        return '%s(%r)' % (self.__class__.__name__,
54
                           self.realfilename)
55
    
409 by Martin Pool
- New AtomicFile class
56
57
    def commit(self):
497 by Martin Pool
- new AtomicFile.close() aborts if appropriate
58
        """Close the file and move to final name."""
410 by Martin Pool
- Fix ignore command and add tests
59
        import sys, os
60
        
563 by Martin Pool
- AtomicFile emits a warning if it is gc'd without being closed
61
        if self.closed:
62
            raise Exception('%r is already closed' % self)
63
565 by Martin Pool
- more invariant checks in AtomicFile
64
        self.closed = True
409 by Martin Pool
- New AtomicFile class
65
        self.f.close()
565 by Martin Pool
- more invariant checks in AtomicFile
66
        self.f = None
67
        
409 by Martin Pool
- New AtomicFile class
68
        if sys.platform == 'win32':
559 by Martin Pool
- AtomicFile must cope without destination file existing on
69
            # windows cannot rename over an existing file
70
            try:
71
                os.remove(self.realfilename)
72
            except OSError, e:
73
                import errno
74
                if e.errno != errno.ENOENT:
75
                    raise
565 by Martin Pool
- more invariant checks in AtomicFile
76
                
409 by Martin Pool
- New AtomicFile class
77
        os.rename(self.tmpfilename, self.realfilename)
78
563 by Martin Pool
- AtomicFile emits a warning if it is gc'd without being closed
79
409 by Martin Pool
- New AtomicFile class
80
    def abort(self):
497 by Martin Pool
- new AtomicFile.close() aborts if appropriate
81
        """Discard temporary file without committing changes."""
410 by Martin Pool
- Fix ignore command and add tests
82
        import os
563 by Martin Pool
- AtomicFile emits a warning if it is gc'd without being closed
83
84
        if self.closed:
85
            raise Exception('%r is already closed' % self)
86
565 by Martin Pool
- more invariant checks in AtomicFile
87
        self.closed = True
409 by Martin Pool
- New AtomicFile class
88
        self.f.close()
565 by Martin Pool
- more invariant checks in AtomicFile
89
        self.f = None
409 by Martin Pool
- New AtomicFile class
90
        os.remove(self.tmpfilename)
497 by Martin Pool
- new AtomicFile.close() aborts if appropriate
91
563 by Martin Pool
- AtomicFile emits a warning if it is gc'd without being closed
92
497 by Martin Pool
- new AtomicFile.close() aborts if appropriate
93
    def close(self):
94
        """Discard the file unless already committed."""
95
        if not self.closed:
96
            self.abort()
563 by Martin Pool
- AtomicFile emits a warning if it is gc'd without being closed
97
98
99
    def __del__(self):
100
        if not self.closed:
101
            warn("%r leaked" % self)
428 by Martin Pool
- Use AtomicFile to update statcache.
102