[Checkins] SVN: zopyx.smartprintng.server/trunk/ holiday work

Andreas Jung andreas at andreas-jung.com
Sat Aug 1 16:07:30 EDT 2009


Log message for revision 102414:
  holiday work
  

Changed:
  U   zopyx.smartprintng.server/trunk/README.txt
  U   zopyx.smartprintng.server/trunk/docs/HISTORY.txt
  U   zopyx.smartprintng.server/trunk/setup.py
  D   zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/base.py
  U   zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/models.py
  U   zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/run.py
  U   zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/templates/index.pt
  U   zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/templates/static/default.css
  U   zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/tests.py
  U   zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/views.py

-=-
Modified: zopyx.smartprintng.server/trunk/README.txt
===================================================================
--- zopyx.smartprintng.server/trunk/README.txt	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/README.txt	2009-08-01 20:07:30 UTC (rev 102414)
@@ -14,10 +14,15 @@
 Installation
 ============
 
-- create an virtualenv environment (Python 2,4, 2.5 or 2.6)::
+- create an virtualenv environment (Python 2,4, 2.5 or 2.6) - either within your
+  current (empty) directory or by letting virtualenv create one for you::
 
     virtualenv --no-site-packages .
 
+  or:: 
+
+    virtualenv --no-site-packages smartprintng
+
 - install ``repoze.bfg`` (by installing ``repoze.bfg.xmlrpc`` having ``repoze.bfg``
   as a dependency) ::
 

Modified: zopyx.smartprintng.server/trunk/docs/HISTORY.txt
===================================================================
--- zopyx.smartprintng.server/trunk/docs/HISTORY.txt	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/docs/HISTORY.txt	2009-08-01 20:07:30 UTC (rev 102414)
@@ -5,6 +5,11 @@
 ------------------
 
 * now requires Python 2.6
+* added convertZIPandRedirect() method
+* added deliver() method
+* moved base.ServerCore to models.py
+* delivered files must be younger than 'delivery_max_age' seconds
+* more tests
 
 
 0.4.3 (2009/07/22)

Modified: zopyx.smartprintng.server/trunk/setup.py
===================================================================
--- zopyx.smartprintng.server/trunk/setup.py	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/setup.py	2009-08-01 20:07:30 UTC (rev 102414)
@@ -1,3 +1,7 @@
+##########################################################################
+# zopyx.smartprintng.server
+# (C) 2008, 2009, ZOPYX Ltd & Co. KG, Tuebingen, Germany
+##########################################################################
 
 import sys
 import os
@@ -21,7 +25,7 @@
       keywords='SmartPrintNG Conversion repoze.bfg',
       author='Andreas Jung',
       author_email='info at zopyx.com',
-      url='',
+      url='http://www.zopyx.com/projects/smartprintng',
       license='ZPL',
       packages=find_packages(exclude=['ez_setup']),
       namespace_packages=['zopyx', 'zopyx.smartprintng'],

Deleted: zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/base.py
===================================================================
--- zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/base.py	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/base.py	2009-08-01 20:07:30 UTC (rev 102414)
@@ -1,113 +0,0 @@
-##########################################################################
-# zopyx.smartprintng.server
-# (C) 2008, 2009, ZOPYX Ltd & Co. KG, Tuebingen, Germany
-##########################################################################
-
-import base64
-import glob
-import os
-import shutil
-import tempfile
-from datetime import datetime
-import time
-import uuid
-import zipfile
-from logger import LOG
-import mail_util
-from zopyx.convert2.convert import Converter
-
-temp_directory = os.path.join(tempfile.gettempdir(), 
-                              'zopyx.smartprintng.server')
-if not os.path.exists(temp_directory):
-    os.makedirs(temp_directory)
-
-
-class ServerCore(object):
-    """ SmartPrintNG Server Core Implementation """
-
-    def _inject_base_tag(self, html_filename):
-        """ All input HTML files contain relative urls (relative
-            to the path of the main HTML file (the "working dir").
-            So we must inject a BASE tag in order to call the external
-            converters properly with the full path of the html input file
-            since we do not want to change the process working dir (not
-            acceptable in a multi-threaded environment).
-            ATT: this should perhaps handled within zopyx.convert2
-        """
-        html = file(html_filename).read()
-        pos = html.lower().find('<head>')
-        html = html[:pos] + '<head><base href="%s"/>' % html_filename + html[pos+6:]
-        file(html_filename, 'wb').write(html)
-
-    def _convert(self, html_filename, converter_name='pdf-prince'):
-        """ Process a single HTML file """
-        return Converter(html_filename)(converter_name)
-
-    def _processZIP(self, zip_archive, converter_name):
-
-        # temp direcotry handling 
-        now = datetime.now().strftime('%Y%m%d%Z%H%M%S')
-        ident = '%s-%s' % (now, uuid.uuid4())
-        tempdir = os.path.join(temp_directory, ident)
-        os.makedirs(tempdir)
-
-        # store zip archive first
-        zip_temp = os.path.join(tempdir, 'input.zip')
-        file(zip_temp, 'wb').write(base64.decodestring(zip_archive))
-        ZF = zipfile.ZipFile(zip_temp, 'r')
-        for name in ZF.namelist():
-            destfile = os.path.join(tempdir, name)
-            if not os.path.exists(os.path.dirname(destfile)):
-                os.makedirs(os.path.dirname(destfile))
-            file(destfile, 'wb').write(ZF.read(name))
-        ZF.close()
-
-        # find HTML file
-        html_files = glob.glob(os.path.join(tempdir, '*.htm*'))
-        if not html_files:
-            raise IOError('Archive does not contain any html files')
-        if len(html_files) > 1:
-            raise RuntimeError('Archive contains more than one html file')
-        html_filename = html_files[0]
-        # inject BASE tag containing the full local path (required by PrinceXML)
-        self._inject_base_tag(html_filename)
-        result = self._convert(html_filename, 
-                               converter_name=converter_name)
-        basename, ext = os.path.splitext(os.path.basename(result))
-
-        # Generate result ZIP archive with base64-encoded result
-        zip_out = os.path.join(tempdir, 'output.zip')
-        ZF = zipfile.ZipFile(zip_out, 'w')
-        ZF.writestr('output%s' % ext, file(result, 'rb').read())
-        ZF.close()
-        return zip_out, result
-
-    def convertZIP(self, zip_archive, converter_name='pdf-prince'):
-        """ Process html-file + images within a ZIP archive """
-
-        LOG.info('Incoming request (%s, %d bytes)' % (converter_name, len(zip_archive)))
-        ts = time.time()
-        zip_out, output_filename = self._processZIP(zip_archive, converter_name)
-        encoded_result = base64.encodestring(file(zip_out, 'rb').read())
-        shutil.rmtree(os.path.dirname(zip_out))
-        LOG.info('Request end (%3.2lf seconds)' % (time.time() - ts))
-        return encoded_result
-
-    def convertZIPEmail(self, zip_archive, converter_name='pdf-prince', sender=None, recipient=None, subject=None, body=None):
-        """ Process zip archive and send conversion result as mail """
-
-        LOG.info('Incoming request (%s, %d bytes)' % (converter_name, len(zip_archive)))
-        ts = time.time()
-        zip_out, output_filename = self._processZIP(zip_archive, converter_name)
-        mail_util.send_email(sender, recipient, subject, body, [output_filename])
-        shutil.rmtree(os.path.dirname(zip_out))
-        LOG.info('Request end (%3.2lf seconds)' % (time.time() - ts))
-        return True
-
-    def availableConverters(self):
-        from zopyx.convert2.registry import availableConverters
-        return availableConverters()
-
-if __name__ == '__main__':
-    s = ServerCore()
-    print s.availableConverters() 

Modified: zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/models.py
===================================================================
--- zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/models.py	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/models.py	2009-08-01 20:07:30 UTC (rev 102414)
@@ -3,11 +3,172 @@
 # (C) 2008, 2009, ZOPYX Ltd & Co. KG, Tuebingen, Germany
 ##########################################################################
 
+import threading
+import base64
+import glob
+import os
+import shutil
+import tempfile
+from stat import ST_CTIME
+import shutil
+from datetime import datetime
+import time
+import uuid
+import zipfile
+from logger import LOG
+import mail_util
+from zopyx.convert2.convert import Converter
 
+
 class Server(object):
-    pass
+    """ SmartPrintNG Server Core Implementation """
 
+    def __init__(self):
+        self.num_requests = 0
+        self.start_time = datetime.now()
+        self.delivery_max_age = 1800   # deliver files only younger than xx seconds
+        self.cleanup_after = 3600
+        self.cleanup_last = time.time()
+        self.keep_files_for = 3600     # keep files for no longer than xx seconds
+        self._lock = threading.Lock()
+        self.temp_directory = os.path.join(tempfile.gettempdir(), 
+                                           'zopyx.smartprintng.server')
+        if not os.path.exists(self.temp_directory):
+            os.makedirs(self.temp_directory)
+
+    def countRequest(self):
+        self._lock.acquire()
+        self.num_requests += 1
+        self._lock.release()
+
+    @property
+    def start_time_as_str(self):
+        return self.start_time.strftime('%d.%m.%Y %H:%M:%S')
+
+    def _cleanup(self):
+        """ Remove old and outdated files from the temporary and
+            spool directory.
+        """
+
+        if time.time() - self.cleanup_last > self.cleanup_after:
+            self._lock.acquire()
+            try:
+                self.__cleanup()
+                self.cleanup_last = time.time()
+            except Exception, e:
+                LOG.error(e, exc_info=True)
+            finally:
+                self._lock.release()
+
+    def __cleanup(self):
+        for dir in os.listdir(self.temp_directory):
+            destdir = os.path.join(self.temp_directory, dir)
+            age = time.time() - os.stat(destdir)[ST_CTIME]
+            if age > self.keep_files_for:
+                shutil.rmtree(destdir)
+
+        for dir in os.listdir(self.spool_directory):
+            destdir = os.path.join(self.spool_directory, dir)
+            age = time.time() - os.stat(destdir)[ST_CTIME]
+            if age > self.keep_files_for:
+                shutil.rmtree(destdir)
+
+    def _inject_base_tag(self, html_filename):
+        """ All input HTML files contain relative urls (relative
+            to the path of the main HTML file (the "working dir").
+            So we must inject a BASE tag in order to call the external
+            converters properly with the full path of the html input file
+            since we do not want to change the process working dir (not
+            acceptable in a multi-threaded environment).
+            ATT: this should perhaps handled within zopyx.convert2
+        """
+        html = file(html_filename).read()
+        pos = html.lower().find('<head>')
+        if pos == -1:
+            raise RuntimeError('HTML does not contain a HEAD tag')
+        html = html[:pos] + '<head><base href="%s"/>' % html_filename + html[pos+6:]
+        file(html_filename, 'wb').write(html)
+
+    def _convert(self, html_filename, converter_name='pdf-prince'):
+        """ Process a single HTML file """
+        self._cleanup()
+        return Converter(html_filename)(converter_name)
+
+    def _processZIP(self, zip_archive, converter_name):
+
+        LOG.info('Incoming request (%s, %d bytes)' % (converter_name, len(zip_archive)))
+        ts = time.time()
+
+        # temp directory handling 
+        now = datetime.now().strftime('%Y%m%d%Z%H%M%S')
+        ident = '%s-%s' % (now, uuid.uuid4())
+        tempdir = os.path.join(self.temp_directory, ident)
+        os.makedirs(tempdir)
+
+        # store zip archive first
+        zip_temp = os.path.join(tempdir, 'input.zip')
+        file(zip_temp, 'wb').write(base64.decodestring(zip_archive))
+        ZF = zipfile.ZipFile(zip_temp, 'r')
+        for name in ZF.namelist():
+            destfile = os.path.join(tempdir, name)
+            if not os.path.exists(os.path.dirname(destfile)):
+                os.makedirs(os.path.dirname(destfile))
+            file(destfile, 'wb').write(ZF.read(name))
+        ZF.close()
+
+        # find HTML file
+        html_files = glob.glob(os.path.join(tempdir, '*.htm*'))
+        if not html_files:
+            raise IOError('Archive does not contain any html files')
+        if len(html_files) > 1:
+            raise RuntimeError('Archive contains more than one html file')
+        html_filename = html_files[0]
+        # inject BASE tag containing the full local path (required by PrinceXML)
+        self._inject_base_tag(html_filename)
+        result = self._convert(html_filename, 
+                               converter_name=converter_name)
+        basename, ext = os.path.splitext(os.path.basename(result))
+
+        # Generate result ZIP archive with base64-encoded result
+        zip_out = os.path.join(tempdir, '%s.zip' % ident)
+        ZF = zipfile.ZipFile(zip_out, 'w')
+        ZF.writestr('output%s' % ext, file(result, 'rb').read())
+        ZF.close()
+
+        LOG.info('Request end (%3.2lf seconds)' % (time.time() - ts))
+        return zip_out, result
+
+    def convertZIP(self, zip_archive, converter_name='pdf-prince'):
+        """ Process html-file + images within a ZIP archive """
+
+        self.countRequest()
+        zip_out, output_filename = self._processZIP(zip_archive, converter_name)
+        encoded_result = base64.encodestring(file(zip_out, 'rb').read())
+        shutil.rmtree(os.path.dirname(zip_out))
+        return encoded_result
+
+    def convertZIPEmail(self, zip_archive, converter_name='pdf-prince', sender=None, recipient=None, subject=None, body=None):
+        """ Process zip archive and send conversion result as mail """
+
+        self.countRequest()
+
+        zip_out, output_filename = self._processZIP(zip_archive, converter_name)
+        mail_util.send_email(sender, recipient, subject, body, [output_filename])
+        shutil.rmtree(os.path.dirname(zip_out))
+        return True
+
+    def availableConverters(self):
+        """ Return a list of available converter names """
+        from zopyx.convert2.registry import availableConverters
+        self.countRequest()
+        return availableConverters()
+
 root = Server()
 
 def get_root(environ):
     return root
+
+if __name__ == '__main__':
+    s = Server()
+    print s.availableConverters() 
+

Modified: zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/run.py
===================================================================
--- zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/run.py	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/run.py	2009-08-01 20:07:30 UTC (rev 102414)
@@ -15,7 +15,10 @@
     # paster app config callback
     from zopyx.smartprintng.server.models import get_root
     import zopyx.smartprintng.server
+    from models import root
     from logger import LOG
+    from views import spool_directory
+
     if 'mail_config' in global_config:
         mail_config = os.path.abspath(global_config['mail_config'])
         os.environ['EMAIL_CONFIG'] = mail_config
@@ -23,5 +26,7 @@
         LOG.info('Using email configuration at %s' % mail_config)
         LOG.info(config)
     LOG.info('SmartPrintNG server started')
+    LOG.info('Temp directory: %s' % root.temp_directory)
+    LOG.info('Spool directory: %s' % spool_directory)
     return make_app(get_root, zopyx.smartprintng.server, options=kw)
 

Modified: zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/templates/index.pt
===================================================================
--- zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/templates/index.pt	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/templates/index.pt	2009-08-01 20:07:30 UTC (rev 102414)
@@ -2,29 +2,29 @@
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:tal="http://xml.zope.org/namespaces/tal">
 <head>
-<meta http-equiv="content-type" content="text/html; charset=utf-8" />
-<title>${project} Application</title>
-<meta name="keywords" content="python web application" />
-<meta name="description" content="repoze.bfg web application" />
-<link href="${request.application_url}/static/default.css" rel="stylesheet" type="text/css" />
+    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+    <title>${project} Application</title>
+    <meta name="keywords" content="python web application" />
+    <meta name="description" content="repoze.bfg web application" />
+    <link href="${request.application_url}/static/default.css" rel="stylesheet" type="text/css" />
 </head>
 <body>
-  <div id="logo">
+  <div id="header">
       <img src="${request.application_url}/static/zopyx_logo.png" /> 
-      <h2>ZOPYX SmartPrintNG Server</h2>
-      <div id="version">Version: ${version}</div>
+      <h2>ZOPYX SmartPrintNG Server - version ${version} </h2>
   </div>
   <br/>
   <div>
-  Available converters: 
-  <ul>
-    <li tal:repeat="c converters" tal:content="c" />
-  </ul>
+      <span class="label">Start time:</span> ${context.start_time_as_str}
+      <br/>
+      <span class="label"># requests:</span> ${context.num_requests}
+      <br/>
+      <span class="label">Available converters:</span> 
+      <span tal:content="python: ', '.join(converters)" />
   </div>
   <div id="footer">
       (C) 2009, ZOPYX Ltd. &amp; Co. KG, <a href="http://www.zopyx.com">www.zopyx.com</a>, <a href="mailto:info at zopyx.com">info at zopyx.com</a>
   </div>
 
-
 </body>
 </html>

Modified: zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/templates/static/default.css
===================================================================
--- zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/templates/static/default.css	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/templates/static/default.css	2009-08-01 20:07:30 UTC (rev 102414)
@@ -2,12 +2,10 @@
     font-family: "Arial";
 }
 
-#logo h2 {
+.label {
+    font-weight: bold;
 }
 
-#logo img {
-}
-
 #footer {
     clear: both;
     margin-top: 1em;
@@ -20,9 +18,6 @@
     font-weight: bold;
 }
 
-a {
-}
-
 a,
 a:active, 
 a:visited {

Modified: zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/tests.py
===================================================================
--- zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/tests.py	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/tests.py	2009-08-01 20:07:30 UTC (rev 102414)
@@ -10,6 +10,7 @@
 import zipfile
 import tempfile
 from repoze.bfg import testing
+from models import Server
 
 xml = """<?xml version="1.0"?>
 <methodCall>
@@ -23,6 +24,13 @@
 </methodCall>
 """
 
+xml3 = """<?xml version="1.0"?>
+<methodCall>
+   <methodName>convertZIPandRedirect</methodName>
+    %s
+</methodCall>
+"""
+
 class ViewTests(unittest.TestCase):
 
     """ These tests are unit tests for the view.  They test the
@@ -44,7 +52,7 @@
 
     def test_index(self):
         from zopyx.smartprintng.server.views import index
-        context = testing.DummyModel()
+        context = Server()
         request = testing.DummyRequest()
         renderer = testing.registerDummyRenderer('templates/index.pt')
         response = index(context, request)
@@ -64,6 +72,7 @@
     provided by bfg and only the registrations you need, as in the
     above ViewTests.
     """
+
     def setUp(self):
         """ This sets up the application registry with the
         registrations your application declares in its configure.zcml
@@ -81,7 +90,7 @@
 
     def test_index(self):
         from zopyx.smartprintng.server.views import index
-        context = testing.DummyModel()
+        context = Server()
         request = testing.DummyRequest()
         result = index(context, request)
         self.assertEqual(result.status, '200 OK')
@@ -94,7 +103,7 @@
 
     def test_xmlrpc_ping(self):
         from zopyx.smartprintng.server.views import ping
-        context = testing.DummyModel()
+        context = Server()
         headers = dict()
         headers['content-type'] = 'text/xml'
         request = testing.DummyRequest(headers=headers, post=True)
@@ -107,7 +116,7 @@
 
     def test_xmlrpc_convertZIP(self):
         from zopyx.smartprintng.server.views import convertZIP
-        context = testing.DummyModel()
+        context = Server()
         headers = dict()
         headers['content-type'] = 'text/xml'
         request = testing.DummyRequest(headers=headers, post=True)
@@ -125,3 +134,47 @@
         ZIP = zipfile.ZipFile(output_zip_filename, 'r')
         self.assertEqual('output.pdf' in ZIP.namelist(), True)
 
+    def test_xmlrpc_convertZIPandRedirect(self):
+        from zopyx.smartprintng.server.views import convertZIPandRedirect
+        context = Server()
+        headers = dict()
+        headers['content-type'] = 'text/xml'
+        request = testing.DummyRequest(headers=headers, post=True)
+        zip_archive = os.path.join(os.path.dirname(__file__), 'test_data', 'test.zip')
+        zip_data = file(zip_archive, 'rb').read()
+        params = xmlrpclib.dumps((base64.encodestring(zip_data), 'pdf-prince'))
+        request.body = xml3 % params
+        result = convertZIPandRedirect(context, request)
+        self.assertEqual(result.status, '200 OK')
+        body = result.app_iter[0]
+        params, methodname = xmlrpclib.loads(result.body)
+        location = params[0]
+        self.assertEqual('deliver?' in location, True)
+
+    def test_deliver_non_existing_filename(self):
+        from zopyx.smartprintng.server.views import deliver
+        context = Server()
+        request = testing.DummyRequest(params=dict(filename='does-not-exist.pdf'))
+        result = deliver(context, request)
+        self.assertEqual(result.status, '404 Not Found')
+
+    def test_deliver_existing_filename(self):
+        from zopyx.smartprintng.server.views import deliver
+        from views import spool_directory
+        file(os.path.join(spool_directory, 'foo.pdf'), 'wb').write('foo')
+        context = Server()
+        request = testing.DummyRequest(params=dict(filename='foo.pdf'))
+        result = deliver(context, request)
+        self.assertEqual(result.status, '200 OK')
+        self.assertEqual(('content-type', 'application/pdf') in result.headerlist, True)
+        self.assertEqual(('content-disposition', 'attachment; filename=foo.pdf') in result.headerlist, True)
+
+    def test_deliver_existing_filename_with_prefix(self):
+        from zopyx.smartprintng.server.views import deliver
+        from views import spool_directory
+        file(os.path.join(spool_directory, 'foo.pdf'), 'wb').write('foo')
+        context = Server()
+        request = testing.DummyRequest(params=dict(filename='foo.pdf', prefix='bar'))
+        result = deliver(context, request)
+        self.assertEqual(result.status, '200 OK')
+        self.assertEqual(('content-disposition', 'attachment; filename=bar.pdf') in result.headerlist, True)

Modified: zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/views.py
===================================================================
--- zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/views.py	2009-08-01 20:07:18 UTC (rev 102413)
+++ zopyx.smartprintng.server/trunk/zopyx/smartprintng/server/views.py	2009-08-01 20:07:30 UTC (rev 102414)
@@ -3,49 +3,92 @@
 # (C) 2008, 2009, ZOPYX Ltd & Co. KG, Tuebingen, Germany
 ##########################################################################
 
+import os
+import time
+import tempfile
+import shutil
+import mimetypes
+import xmlrpclib
 import pkg_resources
+from stat import ST_CTIME
 from repoze.bfg.chameleon_zpt import render_template_to_response
 from repoze.bfg.view import static
 from repoze.bfg.view import bfg_view
-from zopyx.smartprintng.server.base import ServerCore
+from repoze.bfg.xmlrpc import xmlrpc_view
+from webob import Response
 from models import Server
 from logger import LOG
 
 static_view = static('templates/static')
 
+spool_directory = os.path.join(tempfile.gettempdir(), 
+                              'zopyx.smartprintng.server-spool')
+if not os.path.exists(spool_directory):
+    os.makedirs(spool_directory)
+
 ##################
 # HTTP views
 ##################
 
 @bfg_view(for_=Server, request_type='GET', permission='read')
 class index(object):
+    """ The default view providing some system information """
 
     def __init__(self, context, request):
         self.context = context
         self.request = request
 
     def __call__(self):
-        converters = ServerCore().availableConverters()
+        converters = self.context.availableConverters()
         version = pkg_resources.require('zopyx.smartprintng.server')[0].version 
         return render_template_to_response('templates/index.pt',
+                                           context=self.context,
                                            converters=converters,
                                            request=self.request,
                                            version=version,
                                            project='zopyx.smartprintng.server')
 
+
+ at bfg_view(for_=Server, name='deliver')
+def deliver(context, request):
+    """ Send out a generated output file """
+
+    filename = request.params['filename']
+    prefix = request.params.get('prefix')
+    dest_filename = os.path.abspath(os.path.join(spool_directory, filename))
+
+    # various (security) checks
+    if not os.path.exists(dest_filename):
+        return Response(status=404)
+
+    if not dest_filename.startswith(spool_directory):
+        return Response(status=404)
+
+    if time.time() - os.stat(dest_filename)[ST_CTIME] >= context.delivery_max_age:
+        return Response(status=404)
+
+    ct, dummy = mimetypes.guess_type(dest_filename)
+    filename = os.path.basename(filename)
+    if prefix:
+        filename = prefix + os.path.splitext(filename)[1]
+    headers = [('content-disposition','attachment; filename=%s' % filename),
+               ('content-type', ct)]
+    return Response(body=file(dest_filename, 'rb').read(),
+                    content_type=ct,
+                    headerlist=headers
+                    )
+
+
 ##################
 # XMLRPC views
 ##################
 
-from repoze.bfg.xmlrpc import xmlrpc_view
-import xmlrpclib
 
 @bfg_view(name='convertZIP', for_=Server)
 @xmlrpc_view
 def convertZIP(context, zip_archive, converter_name='pdf-prince'):
-    core = ServerCore()
     try:
-        return core.convertZIP(zip_archive, converter_name)
+        return context.convertZIP(zip_archive, converter_name)
     except Exception, e:
         msg = 'Conversion failed (%s)' % e
         LOG.error(msg, exc_info=True)
@@ -55,19 +98,53 @@
 @bfg_view(name='convertZIPEmail', for_=Server)
 @xmlrpc_view
 def convertZIPEmail(context, zip_archive, converter_name='pdf-prince', sender=None, recipient=None, subject=None, body=None):
-    core = ServerCore()
     try:
-        return core.convertZIPEmail(zip_archive, converter_name, sender, recipient, subject, body)
+        return context.convertZIPEmail(zip_archive, converter_name, sender, recipient, subject, body)
     except Exception, e:
         msg = 'Conversion failed (%s)' % e
         LOG.error(msg, exc_info=True)
         return xmlrpclib.Fault(123, msg)
 
 
+ at bfg_view(name='convertZIPandRedirect',  for_=Server)
+ at xmlrpc_view
+def convertZIPandRedirect(context, zip_archive, converter_name='prince-pdf', prefix=None):
+    """ This view appects a ZIP archive through a POST request containing all
+        relevant information (similar to the XMLRPC API). However the converted
+        output file is not returned to the caller but delivered "directly" through
+        the SmartPrintNG server (through an URL redirection). The 'prefix'
+        parameter can be used to override the basename of filename used within the
+        content-disposition header.
+        (This class is only a base class for the related http_ and xmlrpc_
+         view (in order to avoid redudant code).)
+    """
+
+    try:
+        output_archivename, output_filename = context._processZIP(zip_archive, converter_name)
+        output_ext = os.path.splitext(output_filename)[1]
+
+        # take ident from archive name
+        ident = os.path.splitext(os.path.basename(output_archivename))[0]
+
+        # move output file to spool directory
+        dest_filename = os.path.join(spool_directory, '%s%s' % (ident, output_ext))
+        rel_output_filename = dest_filename.replace(spool_directory + os.sep, '')
+        shutil.move(output_filename, dest_filename)
+        host = 'localhost'
+        port = 6543
+        prefix = prefix or ''
+        location = 'http://%s:%s/deliver?filename=%s&prefix=%s' % (host, port, rel_output_filename, prefix)
+        return location
+    except Exception, e:
+        msg = 'Conversion failed (%s)' % e
+        LOG.error(msg, exc_info=True)
+        return xmlrpclib.Fault(123, msg)
+
+
 @bfg_view(name='availableConverters', for_=Server)
 @xmlrpc_view
 def availableConverters(context):
-    return ServerCore().availableConverters()
+    return context.availableConverters()
 
 
 @bfg_view(name='ping', for_=Server)



More information about the Checkins mailing list