reportlab: implement Ralf Schmitt's open files limiter
authorrgbecker
Thu, 21 Aug 2008 13:15:24 +0000
changeset 2954 5ec6485e810a
parent 2953 59f221d6007c
child 2955 cc16265295fb
reportlab: implement Ralf Schmitt's open files limiter
reportlab/lib/utils.py
reportlab/rl_config.py
--- a/reportlab/lib/utils.py	Mon Aug 18 17:35:54 2008 +0000
+++ b/reportlab/lib/utils.py	Thu Aug 21 13:15:24 2008 +0000
@@ -346,17 +346,14 @@
     haveImages = Image is not None
     if haveImages: del Image
 
-__StringIO=None
+try:
+    from cStringIO import StringIO as __StringIO
+except ImportError:
+    from StringIO import StringIO as __StringIO
 def getStringIO(buf=None):
     '''unified StringIO instance interface'''
-    global __StringIO
-    if not __StringIO:
-        try:
-            from cStringIO import StringIO
-        except ImportError:
-            from StringIO import StringIO
-        __StringIO = StringIO
     return buf is not None and __StringIO(buf) or __StringIO()
+_StringIOKlass=__StringIO().__class__
 
 class ArgvDictValue:
     '''A type to allow clients of getArgvDict to specify a conversion function'''
@@ -530,7 +527,7 @@
     except ImportError:
         return 0
 
-class ImageReader:
+class ImageReader(object):
     "Wraps up either PIL or Java to get data from bitmaps"
     _cache={}
     def __init__(self, fileName):
@@ -554,25 +551,32 @@
                 self.fileName = 'PILIMAGE_%d' % id(self)
         else:
             try:
+                from reportlab.rl_config import imageReaderFlags
                 self.fp = open_for_read(fileName,'b')
-                from reportlab.rl_config import internImageFiles
-                if internImageFiles:
+                if isinstance(self.fp,_StringIOKlass):  imageReaderFlags=0 #avoid messing with already internal files
+                if imageReaderFlags>0:  #interning
                     data = self.fp.read()
-                    if internImageFiles&2:
+                    if imageReaderFlags&2:  #autoclose
                         try:
                             self.fp.close()
                         except:
                             pass
-                    self.fp=getStringIO(self._cache.setdefault(md5(data).digest(),data))
+                    if imageReaderFlags&4:  #cache the data
+                        if not self._cache:
+                            from rl_config import register_reset
+                            register_reset(self._cache.clear)
+                        data=self._cache.setdefault(md5(data).digest(),data)
+                    self.fp=getStringIO(data)
+                elif imageReaderFlags==-1 and isinstance(fileName,(str,unicode)):
+                    #try Ralf Schmitt's re-opening technique of avoiding too many open files
+                    self.fp.close()
+                    del self.fp #will become a property in the next statement
+                    self.__class__=LazyImageReader
                 if haveImages:
                     #detect which library we are using and open the image
-                    if sys.platform[0:4] == 'java':
-                        from javax.imageio import ImageIO
-                        self._image = ImageIO.read(self.fp)
-                    else:
-                        import PIL.Image
-                        self._image = PIL.Image.open(self.fp)
-                        if self._image=='JPEG': self.jpeg_fh = self._jpeg_fh
+                    if not self._image:
+                        self._image = self._read_image(self.fp)
+                    if getattr(self._image,'format',None)=='JPEG': self.jpeg_fh = self._jpeg_fh
                 else:
                     from reportlab.pdfbase.pdfutils import readJPEGInfo
                     try:
@@ -592,6 +596,14 @@
                 else:
                     raise
 
+    def _read_image(self,fp):
+        if sys.platform[0:4] == 'java':
+            from javax.imageio import ImageIO
+            return ImageIO.read(fp)
+        else:
+            import PIL.Image
+            return PIL.Image.open(fp)
+
     def _jpeg_fh(self):
         fp = self.fp
         fp.seek(0)
@@ -663,6 +675,15 @@
             else:
                 return None
 
+class LazyImageReader(ImageReader): 
+    @property 
+    def fp(self): 
+        return open_for_read(self.fileName, 'b') 
+
+    @property 
+    def _image(self):
+        return self._read_image(self.fp)
+
 def getImageData(imageFileName):
     "Get width, height and RGB pixels from image file.  Wraps Java/PIL"
     try:
--- a/reportlab/rl_config.py	Mon Aug 18 17:35:54 2008 +0000
+++ b/reportlab/rl_config.py	Thu Aug 21 13:15:24 2008 +0000
@@ -32,8 +32,11 @@
 canvas_basefontname=        'Helvetica'             #this is used to initialize the canvas; if you override to make
                                                     #something else you are responsible for ensuring the font is registered etc etc
 allowShortTableRows=1                               #allows some rows in a table to be short
-internImageFiles=0                                  #attempt to convert images into internal memory files to reduce
-                                                    #the number of open files; try autoclosing as well if value is 2
+imageReaderFlags=0                                  #attempt to convert images into internal memory files to reduce
+                                                    #the number of open files (see lib.utils.ImageReader)
+                                                    #if imageReaderFlags&2 then attempt autoclosing of those files
+                                                    #if imageReaderFlags&4 then cache data 
+                                                    #if imageReaderFlags==-1 then use Ralf Schmitt's re-opening approach
 
 # places to look for T1Font information
 T1SearchPath =  (
@@ -159,7 +162,7 @@
 platypus_link_underline
 canvas_basefontname
 allowShortTableRows
-internImageFiles'''.split()
+imageReaderFlags'''.split()
     import os, sys
     global sys_version, _unset_
     sys_version = sys.version.split()[0]        #strip off the other garbage