|
"""Module with a simple buffer implementation using the memory manager""" |
|
import sys |
|
|
|
__all__ = ["SlidingWindowMapBuffer"] |
|
|
|
|
|
class SlidingWindowMapBuffer: |
|
|
|
"""A buffer like object which allows direct byte-wise object and slicing into |
|
memory of a mapped file. The mapping is controlled by the provided cursor. |
|
|
|
The buffer is relative, that is if you map an offset, index 0 will map to the |
|
first byte at the offset you used during initialization or begin_access |
|
|
|
**Note:** Although this type effectively hides the fact that there are mapped windows |
|
underneath, it can unfortunately not be used in any non-pure python method which |
|
needs a buffer or string""" |
|
__slots__ = ( |
|
'_c', |
|
'_size', |
|
) |
|
|
|
def __init__(self, cursor=None, offset=0, size=sys.maxsize, flags=0): |
|
"""Initialize the instance to operate on the given cursor. |
|
:param cursor: if not None, the associated cursor to the file you want to access |
|
If None, you have call begin_access before using the buffer and provide a cursor |
|
:param offset: absolute offset in bytes |
|
:param size: the total size of the mapping. Defaults to the maximum possible size |
|
From that point on, the __len__ of the buffer will be the given size or the file size. |
|
If the size is larger than the mappable area, you can only access the actually available |
|
area, although the length of the buffer is reported to be your given size. |
|
Hence it is in your own interest to provide a proper size ! |
|
:param flags: Additional flags to be passed to os.open |
|
:raise ValueError: if the buffer could not achieve a valid state""" |
|
self._c = cursor |
|
if cursor and not self.begin_access(cursor, offset, size, flags): |
|
raise ValueError("Failed to allocate the buffer - probably the given offset is out of bounds") |
|
|
|
|
|
def __del__(self): |
|
self.end_access() |
|
|
|
def __enter__(self): |
|
return self |
|
|
|
def __exit__(self, exc_type, exc_value, traceback): |
|
self.end_access() |
|
|
|
def __len__(self): |
|
return self._size |
|
|
|
def __getitem__(self, i): |
|
if isinstance(i, slice): |
|
return self.__getslice__(i.start or 0, i.stop or self._size) |
|
c = self._c |
|
assert c.is_valid() |
|
if i < 0: |
|
i = self._size + i |
|
if not c.includes_ofs(i): |
|
c.use_region(i, 1) |
|
|
|
return c.buffer()[i - c.ofs_begin()] |
|
|
|
def __getslice__(self, i, j): |
|
c = self._c |
|
|
|
|
|
assert c.is_valid() |
|
if i < 0: |
|
i = self._size + i |
|
if j == sys.maxsize: |
|
j = self._size |
|
if j < 0: |
|
j = self._size + j |
|
if (c.ofs_begin() <= i) and (j < c.ofs_end()): |
|
b = c.ofs_begin() |
|
return c.buffer()[i - b:j - b] |
|
else: |
|
l = j - i |
|
ofs = i |
|
|
|
|
|
md = list() |
|
while l: |
|
c.use_region(ofs, l) |
|
assert c.is_valid() |
|
d = c.buffer()[:l] |
|
ofs += len(d) |
|
l -= len(d) |
|
|
|
|
|
if hasattr(d, 'tobytes'): |
|
d = d.tobytes() |
|
md.append(d) |
|
|
|
return b''.join(md) |
|
|
|
|
|
|
|
def begin_access(self, cursor=None, offset=0, size=sys.maxsize, flags=0): |
|
"""Call this before the first use of this instance. The method was already |
|
called by the constructor in case sufficient information was provided. |
|
|
|
For more information no the parameters, see the __init__ method |
|
:param path: if cursor is None the existing one will be used. |
|
:return: True if the buffer can be used""" |
|
if cursor: |
|
self._c = cursor |
|
|
|
|
|
|
|
if self._c is not None and self._c.is_associated(): |
|
res = self._c.use_region(offset, size, flags).is_valid() |
|
if res: |
|
|
|
|
|
|
|
|
|
if size > self._c.file_size(): |
|
size = self._c.file_size() - offset |
|
|
|
self._size = size |
|
|
|
return res |
|
|
|
return False |
|
|
|
def end_access(self): |
|
"""Call this method once you are done using the instance. It is automatically |
|
called on destruction, and should be called just in time to allow system |
|
resources to be freed. |
|
|
|
Once you called end_access, you must call begin access before reusing this instance!""" |
|
self._size = 0 |
|
if self._c is not None: |
|
self._c.unuse_region() |
|
|
|
|
|
def cursor(self): |
|
""":return: the currently set cursor which provides access to the data""" |
|
return self._c |
|
|
|
|
|
|