TransRead: cleanup fake seek
authorArtem Bityutskiy <artem.bityutskiy@intel.com>
Wed, 19 Dec 2012 13:47:37 +0000 (15:47 +0200)
committerArtem Bityutskiy <artem.bityutskiy@intel.com>
Wed, 19 Dec 2012 14:03:38 +0000 (16:03 +0200)
Instead of patching objects run-time, just add seek() and tell() methods
to classes we need to support and use a helper '_fake_seek_forward()' function
from there. This is cleaner.

Change-Id: Ib8ca83eb078322e5468206b4440051451eac70ab
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@intel.com>
bmaptools/TransRead.py

index 5ae9f27..c8484ec 100644 (file)
@@ -11,53 +11,38 @@ import errno
 # A list of supported compression types
 SUPPORTED_COMPRESSION_TYPES = ('bz2', 'gz', 'tar.gz', 'tgz', 'tar.bz2')
 
-def _fake_seek_forward(self, offset, whence = os.SEEK_SET):
-    """ Seek to a specified offset. We only support seeking forward and
-    only relative to the beginning of the file and to the current
-    position. """
+def _fake_seek_forward(file_obj, cur_pos, offset, whence = os.SEEK_SET):
+    """ This function implements the 'seek()' method for file object
+    'file_obj'. Oonly seeking forward and is allowed, and 'whence' may be
+    either 'os.SEEK_SET' or 'os.SEEK_CUR'. """
 
     if whence == os.SEEK_SET:
         new_pos = offset
     elif whence == os.SEEK_CUR:
-        new_pos = self._pos + offset
+        new_pos = cur_pos + offset
     else:
         raise Error("'seek()' method requires the 'whence' argument " \
                     "to be %d or %d, but %d was passed" \
                     % (os.SEEK_SET, os.SEEK_CUR, whence))
 
-    if new_pos < self._pos:
+    if new_pos < cur_pos:
         raise Error("''seek()' method supports only seeking forward, " \
                     "seeking from %d to %d is not allowed" \
-                    % (self._pos, new_pos))
+                    % (cur_pos, new_pos))
 
-    length = new_pos - self._pos
+    length = new_pos - cur_pos
     to_read = length
     while to_read > 0:
-        buf = self.read(to_read)
+        buf = file_obj.read(to_read)
         if not buf:
             break
         to_read -= len(buf)
 
     if to_read < 0:
         raise Error("seeked too far: %d instead of %d" \
-                    % (self._pos, new_pos))
+                    % (new_pos - to_read, new_pos))
 
-def _fake_tell(self):
-    """ Return the current emulated file position """
-
-    return self._pos
-
-def _add_fake_seek(self):
-    """ Add a limitef fake 'seek()' and 'tell()' capability to a file-like
-    object 'self'. """
-
-    assert hasattr(self, "_pos") == False
-    assert hasattr(self, "seek") == False
-    assert hasattr(self, "tell") == False
-
-    self._pos = 0
-    self.seek = types.MethodType(_fake_seek_forward, self)
-    self.tell = types.MethodType(_fake_tell, self)
+    return new_pos
 
 class Error(Exception):
     """ A class for exceptions generated by this module. We currently support
@@ -76,10 +61,21 @@ class _CompressedFile:
 
         self._file_obj = file_obj
         self._decompress_func = decompress_func
+        self._pos = 0
         self._buffer = ''
         self._buffer_pos = 0
         self._eof = False
 
+    def seek(self, offset, whence = os.SEEK_SET):
+        """ The 'seek()' method, similar to the one file objects have. """
+
+        self._pos = _fake_seek_forward(self, self._pos, offset, whence)
+
+    def tell(self):
+        """ The 'tell()' method, similar to the one file objects have. """
+
+        return self._pos
+
     def _read_from_buffer(self, length):
         """ Read from the internal buffer. """
 
@@ -98,6 +94,7 @@ class _CompressedFile:
         """ Read the compressed file, uncompress the data on-the-fly, and
         return 'size' bytes of the uncompressed data. """
 
+        assert self._pos >= 0
         assert self._buffer_pos >= 0
         assert self._buffer_pos <= len(self._buffer)
 
@@ -135,8 +132,7 @@ class _CompressedFile:
 
             size -= len(buf)
 
-        if hasattr(self, "_pos"):
-            self._pos += len(data)
+        self._pos += len(data)
 
         return data
 
@@ -171,13 +167,11 @@ class TransRead:
                 decompressor = zlib.decompressobj(16 + zlib.MAX_WBITS)
                 self._transfile_obj = _CompressedFile(self._file_obj,
                                                       decompressor.decompress)
-                _add_fake_seek(self._transfile_obj)
             elif self.name.endswith('.bz2'):
                 import bz2
 
                 self._transfile_obj = _CompressedFile(self._file_obj,
                                               bz2.BZ2Decompressor().decompress)
-                _add_fake_seek(self._transfile_obj)
             else:
                 self.is_compressed = False
                 self._transfile_obj = self._file_obj
@@ -202,6 +196,7 @@ class TransRead:
         self.is_url = False
         self._file_obj = None
         self._transfile_obj = None
+        self._pos = 0
 
         try:
             self._file_obj = open(self.name, "rb")
@@ -227,7 +222,10 @@ class TransRead:
         """ Read the data from the file or URL and and uncompress it on-the-fly
         if necessary. """
 
-        return self._transfile_obj.read(size)
+        buf = self._transfile_obj.read(size)
+        self._pos += len(buf)
+
+        return buf
 
     def __del__(self):
         """ The class destructor which closes opened files. """
@@ -239,8 +237,17 @@ class TransRead:
 
     def seek(self, offset, whence = os.SEEK_SET):
         """ The 'seek()' method, similar to the one file objects have. """
-        self._transfile_obj.seek(offset, whence)
+
+        if hasattr(self._transfile_obj, "seek"):
+            self._transfile_obj.seek(offset, whence)
+        else:
+            self._pos = _fake_seek_forward(self._transfile_obj, self._pos, \
+                                           offset, whence)
 
     def tell(self):
         """ The 'tell()' method, similar to the one file objects have. """
-        return self._transfile_obj.tell()
+
+        if hasattr(self._transfile_obj, "tell"):
+            return self._transfile_obj.tell()
+        else:
+            return self._pos