TransRead: implement local caching
authorArtem Bityutskiy <artem.bityutskiy@intel.com>
Mon, 6 May 2013 12:45:04 +0000 (15:45 +0300)
committerArtem Bityutskiy <artem.bityutskiy@intel.com>
Mon, 6 May 2013 14:58:51 +0000 (17:58 +0300)
Teach TransRead to cache remote and compressed files with local uncompressed
files, which makes it possible to use operations like mmap and so on. This
patch adds a 'local' parameter to the TransRead constructor which is 'False' by
default, and when it is 'True', TransRead creates a local copy of the back-end
file.

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

index 3b59073..f9c3318 100755 (executable)
--- a/bmaptool
+++ b/bmaptool
@@ -139,7 +139,9 @@ def copy_command_open_all(args, log):
     bmap_obj = None
     if bmap_path:
         try:
-            bmap_obj = TransRead.TransRead(bmap_path)
+            # The current implementation of BmapCopy requires a local file for
+            # the bmap file.
+            bmap_obj = TransRead.TransRead(bmap_path, local = True)
         except TransRead.Error as err:
             log.error("cannot open bmap file '%s': %s" % (bmap_path, str(err)))
             raise SystemExit(1)
index fd184da..c824adb 100644 (file)
@@ -243,9 +243,42 @@ class TransRead:
             raise Error("cannot open URL '%s': server responds with an HTTP " \
                         "status code that we don't understand" % url)
 
-    def __init__(self, filepath):
+    def _create_local_copy(self):
+        """ Create a local copy of a remote or compressed file. """
+
+        import tempfile
+
+        try:
+            tmp_file_obj = tempfile.NamedTemporaryFile("w+")
+        except IOError as err:
+            raise Error("cannot create a temporary file: %s" % err)
+
+        while True:
+            chunk = self.read(1024 * 1024)
+            if not chunk:
+                break
+
+            tmp_file_obj.write(chunk)
+
+        tmp_file_obj.flush()
+        self.close()
+        self.is_compressed = False
+        self.is_url = False
+        self._file_obj = tmp_file_obj
+
+        try:
+            self._transfile_obj = open(tmp_file_obj.name, "rb")
+        except IOError as err:
+            raise Error("cannot open own temporary file '%s': %s" \
+                        % (tmp_file_obj.name, err))
+
+    def __init__(self, filepath, local = False):
         """ Class constructor. The 'filepath' argument is the full path to the
-        file to read transparently. """
+        file to read transparently. If 'local' is True, then the file-like
+        object is guaranteed to be backed by a local file. This means that if
+        the source file is compressed or an URL, then it will first be copied
+        to a temporary local file, and then all the subsequent operations will
+        be done with the local copy. """
 
         self.name = filepath
         self.size = None
@@ -266,6 +299,9 @@ class TransRead:
 
         self._open_compressed_file()
 
+        if local:
+            self._create_local_copy()
+
     def read(self, size = -1):
         """ Read the data from the file or URL and and uncompress it on-the-fly
         if necessary. """
@@ -308,3 +344,11 @@ class TransRead:
 
         self.__del__()
 
+    def __getattr__(self, name):
+        """ If we are backed by a local uncompressed file, then fall-back to
+        using its operations. """
+
+        if not self.is_compressed and not self.is_url:
+            return getattr(self._transfile_obj, name)
+        else:
+            raise AttributeError