Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / lib / hashfiler / context_file.py @ c9b996b7

History | View | Annotate | Download (6.6 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from os import SEEK_CUR, SEEK_SET, fsync, makedirs, rename
35
from os.path import dirname
36
from errno import ENOENT, ESPIPE
37

    
38

    
39
_zeros = ''
40
fsync  # get used
41

    
42

    
43
def zeros(nr):
44
    global _zeros
45
    size = len(_zeros)
46
    if nr == size:
47
        return _zeros
48

    
49
    if nr > size:
50
        _zeros += '\0' * (nr - size)
51
        return _zeros
52

    
53
    if nr < size:
54
        _zeros = _zeros[:nr]
55
        return _zeros
56

    
57

    
58
def file_sync_write_chunks(openfile, chunksize, offset, chunks, size=None):
59
    """Write given chunks to the given buffered file object.
60
       Writes never span across chunk boundaries.
61
       If size is given stop after or pad until size bytes have been written.
62
    """
63
    fwrite = openfile.write
64
    seek = openfile.seek
65
    padding = 0
66

    
67
    try:
68
        seek(offset * chunksize)
69
    except IOError as e:
70
        if e.errno != ESPIPE:
71
            raise
72

    
73
        # substitute seeking in a non-seekable file (e.g. pipe)
74
        seek = None
75
        for x in xrange(offset):
76
            fwrite(zeros(chunksize))
77

    
78
    cursize = offset * chunksize
79

    
80
    for chunk in chunks:
81
        if padding:
82
            if seek:
83
                seek(padding - 1, SEEK_CUR)
84
                fwrite("\x00")
85
            else:
86
                fwrite(buffer(zeros(chunksize), 0, padding))
87
        if size is not None and cursize + chunksize >= size:
88
            chunk = chunk[:chunksize - (cursize - size)]
89
            fwrite(chunk)
90
            cursize += len(chunk)
91
            break
92
        fwrite(chunk)
93
        padding = chunksize - len(chunk)
94

    
95
    padding = size - cursize if size is not None else 0
96
    if padding <= 0:
97
        return
98

    
99
    q, r = divmod(padding, chunksize)
100
    for x in xrange(q):
101
        fwrite(zeros(chunksize))
102
    fwrite(buffer(zeros(chunksize), 0, r))
103

    
104

    
105
def file_sync_read_chunks(openfile, chunksize, nr, offset=0):
106
    """Read and yield groups of chunks from a buffered file object at offset.
107
       Reads never span accros chunksize boundaries.
108
    """
109
    fread = openfile.read
110
    remains = offset * chunksize
111
    seek = openfile.seek
112
    try:
113
        seek(remains)
114
    except IOError as e:
115
        if e.errno != ESPIPE:
116
            raise
117

    
118
        # substitute seeking in a non-seekable file (e.g. pipe)
119
        seek = None
120
        while 1:
121
            s = fread(remains)
122
            remains -= len(s)
123
            if remains <= 0:
124
                break
125

    
126
    while nr:
127
        remains = chunksize
128
        chunk = ''
129
        while 1:
130
            s = fread(remains)
131
            if not s:
132
                if chunk:
133
                    yield chunk
134
                return
135
            chunk += s
136
            remains -= len(s)
137
            if remains <= 0:
138
                break
139
        yield chunk
140
        nr -= 1
141

    
142

    
143
class ContextFile(object):
144

    
145
    def __init__(self, name, create=True, write=False, temp=''):
146
        self.name = name
147
        self.fdesc = None
148
        self._create = create
149
        self._write = write
150
        self._mode = 'rb+' if write else 'rb'
151
        self._temp = temp
152
        self._rename = False
153
        #self.dirty = 0
154

    
155
    def __enter__(self):
156
        name = self.name
157
        try:
158
            fdesc = open(name, self._mode)
159
        except IOError, e:
160
            if not (self._create and e.errno == ENOENT):
161
                raise
162

    
163
            # File does not exist. Create it,
164
            # optionally under a temporary filename.
165
            temp = self._temp
166
            self._rename = False
167
            if temp:
168
                name += '_' + temp
169
                self._rename = name
170

    
171
            try:
172
                fdesc = open(name, 'w+')
173
            except IOError as e:
174
                if e.errno != ENOENT:
175
                    raise
176

    
177
                # ENOENT means parent dirs do not exist.
178
                # Create them.
179
                makedirs(dirname(name))
180
                fdesc = open(name, 'w+')
181

    
182
        self.fdesc = fdesc
183
        return self
184

    
185
    def __exit__(self, exc, arg, trace):
186
        fdesc = self.fdesc
187
        if fdesc is not None:
188
            #if self.dirty:
189
            #    fsync(fdesc.fileno())
190
            fdesc.close()
191

    
192
        tempname = self._rename
193
        if tempname:
194
            # Temporary file needs to be moved into proper place
195
            # This is usually an atomic filesystem operation.
196
            rename(tempname, self.name)
197

    
198
        return False  # propagate exceptions
199

    
200
    def seek(self, offset, whence=SEEK_SET):
201
        return self.fdesc.seek(offset, whence)
202

    
203
    def tell(self):
204
        return self.fdesc.tell()
205

    
206
    def truncate(self, size):
207
        self.fdesc.truncate(size)
208

    
209
    def sync_write(self, data):
210
        #self.dirty = 1
211
        self.fdesc.write(data)
212

    
213
    def sync_write_chunks(self, chunksize, offset, chunks, size=None):
214
        #self.dirty = 1
215
        return file_sync_write_chunks(self.fdesc,
216
                                      chunksize, offset, chunks, size)
217

    
218
    def sync_read(self, size):
219
        read = self.fdesc.read
220
        data = ''
221
        while 1:
222
            s = read(size)
223
            if not s:
224
                break
225
            data += s
226
        return data
227

    
228
    def sync_read_chunks(self, chunksize, nr, offset=0):
229
        return file_sync_read_chunks(self.fdesc, chunksize, nr, offset)