Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / file.py @ 959

History | View | Annotate | Download (5.93 KB)

1
# file.py -- Safe access to git files
2
# Copyright (C) 2010 Google, Inc.
3
#
4
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
5
# General Public License as public by the Free Software Foundation; version 2.0
6
# or (at your option) any later version. You can redistribute it and/or
7
# modify it under the terms of either of these two licenses.
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
#
15
# You should have received a copy of the licenses; if not, see
16
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
17
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
18
# License, Version 2.0.
19
#
20

    
21
"""Safe access to git files."""
22

    
23
import errno
24
import io
25
import os
26
import sys
27
import tempfile
28

    
29
def ensure_dir_exists(dirname):
30
    """Ensure a directory exists, creating if necessary."""
31
    try:
32
        os.makedirs(dirname)
33
    except OSError as e:
34
        if e.errno != errno.EEXIST:
35
            raise
36

    
37

    
38
def _fancy_rename(oldname, newname):
39
    """Rename file with temporary backup file to rollback if rename fails"""
40
    if not os.path.exists(newname):
41
        try:
42
            os.rename(oldname, newname)
43
        except OSError:
44
            raise
45
        return
46

    
47
    # destination file exists
48
    try:
49
        (fd, tmpfile) = tempfile.mkstemp(".tmp", prefix=oldname+".", dir=".")
50
        os.close(fd)
51
        os.remove(tmpfile)
52
    except OSError:
53
        # either file could not be created (e.g. permission problem)
54
        # or could not be deleted (e.g. rude virus scanner)
55
        raise
56
    try:
57
        os.rename(newname, tmpfile)
58
    except OSError:
59
        raise   # no rename occurred
60
    try:
61
        os.rename(oldname, newname)
62
    except OSError:
63
        os.rename(tmpfile, newname)
64
        raise
65
    os.remove(tmpfile)
66

    
67

    
68
def GitFile(filename, mode='rb', bufsize=-1):
69
    """Create a file object that obeys the git file locking protocol.
70

71
    :return: a builtin file object or a _GitFile object
72

73
    :note: See _GitFile for a description of the file locking protocol.
74

75
    Only read-only and write-only (binary) modes are supported; r+, w+, and a
76
    are not.  To read and write from the same file, you can take advantage of
77
    the fact that opening a file for write does not actually open the file you
78
    request.
79
    """
80
    if 'a' in mode:
81
        raise IOError('append mode not supported for Git files')
82
    if '+' in mode:
83
        raise IOError('read/write mode not supported for Git files')
84
    if 'b' not in mode:
85
        raise IOError('text mode not supported for Git files')
86
    if 'w' in mode:
87
        return _GitFile(filename, mode, bufsize)
88
    else:
89
        return io.open(filename, mode, bufsize)
90

    
91

    
92
class _GitFile(object):
93
    """File that follows the git locking protocol for writes.
94

95
    All writes to a file foo will be written into foo.lock in the same
96
    directory, and the lockfile will be renamed to overwrite the original file
97
    on close.
98

99
    :note: You *must* call close() or abort() on a _GitFile for the lock to be
100
        released. Typically this will happen in a finally block.
101
    """
102

    
103
    PROXY_PROPERTIES = set(['closed', 'encoding', 'errors', 'mode', 'name',
104
                            'newlines', 'softspace'])
105
    PROXY_METHODS = ('__iter__', 'flush', 'fileno', 'isatty', 'read',
106
                     'readline', 'readlines', 'seek', 'tell',
107
                     'truncate', 'write', 'writelines')
108
    def __init__(self, filename, mode, bufsize):
109
        self._filename = filename
110
        self._lockfilename = '%s.lock' % self._filename
111
        fd = os.open(self._lockfilename,
112
            os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0))
113
        self._file = os.fdopen(fd, mode, bufsize)
114
        self._closed = False
115

    
116
        for method in self.PROXY_METHODS:
117
            setattr(self, method, getattr(self._file, method))
118

    
119
    def abort(self):
120
        """Close and discard the lockfile without overwriting the target.
121

122
        If the file is already closed, this is a no-op.
123
        """
124
        if self._closed:
125
            return
126
        self._file.close()
127
        try:
128
            os.remove(self._lockfilename)
129
            self._closed = True
130
        except OSError as e:
131
            # The file may have been removed already, which is ok.
132
            if e.errno != errno.ENOENT:
133
                raise
134
            self._closed = True
135

    
136
    def close(self):
137
        """Close this file, saving the lockfile over the original.
138

139
        :note: If this method fails, it will attempt to delete the lockfile.
140
            However, it is not guaranteed to do so (e.g. if a filesystem becomes
141
            suddenly read-only), which will prevent future writes to this file
142
            until the lockfile is removed manually.
143
        :raises OSError: if the original file could not be overwritten. The lock
144
            file is still closed, so further attempts to write to the same file
145
            object will raise ValueError.
146
        """
147
        if self._closed:
148
            return
149
        self._file.close()
150
        try:
151
            try:
152
                os.rename(self._lockfilename, self._filename)
153
            except OSError as e:
154
                if sys.platform == 'win32' and e.errno == errno.EEXIST:
155
                    # Windows versions prior to Vista don't support atomic renames
156
                    _fancy_rename(self._lockfilename, self._filename)
157
                else:
158
                    raise
159
        finally:
160
            self.abort()
161

    
162
    def __enter__(self):
163
        return self
164

    
165
    def __exit__(self, exc_type, exc_val, exc_tb):
166
        self.close()
167

    
168
    def __getattr__(self, name):
169
        """Proxy property calls to the underlying file."""
170
        if name in self.PROXY_PROPERTIES:
171
            return getattr(self._file, name)
172
        raise AttributeError(name)