rework wsgi.py
authorTero Marttila <terom@fixme.fi>
Mon, 25 Jan 2010 04:59:38 +0200
changeset 89 02e5b9b08881
parent 88 5cc2d044d368
child 90 1c317e0628a7
rework wsgi.py
pngtile/wsgi.py
--- a/pngtile/wsgi.py	Mon Jan 25 04:34:02 2010 +0200
+++ b/pngtile/wsgi.py	Mon Jan 25 04:59:38 2010 +0200
@@ -8,22 +8,70 @@
 
 import pypngtile as pt
 
+## Settings
+# path to images
 DATA_ROOT = os.environ.get("PNGTILE_DATA_PATH") or os.path.abspath('data/')
 
+# only open each image once
 IMAGE_CACHE = {}
 
+# width of a tile
 TILE_WIDTH = 256
 TILE_HEIGHT = 256
 
 # max. output resolution to allow
 MAX_PIXELS = 1920 * 1200
 
-def dir_view (req, name, path) :
+def dir_url (prefix, name, item) :
+    """
+        Join together an absolute URL prefix, an optional directory name, and a directory item
+    """
+
+    url = prefix
+
+    if name :
+        url = os.path.join(url, name)
+
+    url = os.path.join(url, item)
+
+    return url
+
+def dir_list (dir_path) :
+    """
+        Yield a series of directory items to show for the given dir
+    """
+
+    # link to parent
+    yield '..'
+
+    for item in os.listdir(dir_path) :
+        path = os.path.join(dir_path, item)
+
+        # skip dotfiles
+        if item.startswith('.') :
+            continue
+        
+        # show dirs
+        if os.path.isdir(path) :
+            yield item
+
+        # examine ext
+        base, ext = os.path.splitext(path)
+
+        # show .png files with a .cache file
+        if ext == '.png' and os.path.exists(base + '.cache') :
+            yield item
+
+### Render HTML data
+def render_dir (req, name, path) :
+    """
+        Directory index
+    """
+
     prefix = os.path.dirname(req.script_root).rstrip('/')
     script_prefix = req.script_root
     name = name.rstrip('/')
 
-
     return """\
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
     <head>
@@ -39,20 +87,28 @@
     </body>
 </html>""" % dict(
         prefix          = prefix,
-        dir             = name,
+        dir             = '/' + name,
         
         listing         = "\n".join(
+            # <li> link
             """<li><a href="%(url)s">%(name)s</a></li>""" % dict(
-                url         = '/'.join((script_prefix, name, item)),
+                # URL to dir
+                url         = dir_url(script_prefix, name, item),
+
+                # item name
                 name        = item,
-            ) for item in ['..'] + [i for i in os.listdir(path) if i.endswith('.png') or os.path.isdir(os.path.join(path, i))]
+            ) for item in dir_list(path)
         ),
     )
 
-def image_view (req, image_path, image) :
-    image_name = os.path.basename(image_path)
-
-    img_width, img_height = image.info()
+def render_img_viewport (req, name, image) :
+    """
+        HTML for image
+    """
+    
+    # a little slow, but not so bad - two stats(), heh
+    info = image.info()
+    img_width, img_height = info['img_width'], info['img_height']
 
     return """\
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
@@ -87,7 +143,7 @@
         </script>
     </body>
 </html>""" % dict(
-        title           = image_name,
+        title           = name,
         prefix          = os.path.dirname(req.script_root).rstrip('/'),
         tile_url        = req.url,
 
@@ -99,6 +155,10 @@
     )
 
 def scale_by_zoom (val, zoom) :
+    """
+        Scale coordinates by zoom factor
+    """
+
     if zoom > 0 :
         return val << zoom
 
@@ -108,14 +168,23 @@
     else :
         return val
 
-def render_tile (image, x, y, zoom, width=TILE_WIDTH, height=TILE_HEIGHT) :
+### Render PNG Data
+def render_img_tile (image, x, y, zoom, width=TILE_WIDTH, height=TILE_HEIGHT) :
+    """
+        Render given tile, returning PNG data
+    """
+
     return image.tile_mem(
         width, height, 
         scale_by_zoom(x, -zoom), scale_by_zoom(y, -zoom), 
         zoom
     )
 
-def render_image (image, cx, cy, zoom, width, height) :
+def render_img_region (image, cx, cy, zoom, width, height) :
+    """
+        Render arbitrary tile, returning PNG data
+    """
+
     x = scale_by_zoom(cx - width / 2, -zoom)
     y = scale_by_zoom(cy - height / 2, -zoom)
 
@@ -129,80 +198,164 @@
         zoom
     )
 
-def handle_main (req) :
+
+### Manipulate request data
+def get_req_path (req) :
+    """
+        Returns the name and path requested
+    """
+
     # path to image
     image_name = req.path.lstrip('/')
     
     # build absolute path
     image_path = os.path.abspath(os.path.join(DATA_ROOT, image_name))
 
-    
     # ensure the path points inside the data root
     if not image_path.startswith(DATA_ROOT) :
         raise exceptions.NotFound(image_name)
 
+    return image_name, image_path
 
-    if os.path.isdir(image_path) :
-        return Response(dir_view(req, image_name, image_path), content_type="text/html")
-    
-    elif not os.path.exists(image_path) :
-        raise exceptions.NotFound(image_name)
-
-    elif not image_name or not image_name.endswith('.png') :
-        raise exceptions.BadRequest("Not a PNG file")
-    
+def get_image (name, path) :
+    """
+        Gets an Image object from the cache, ensuring that it is cached
+    """
 
     # get Image object
-    if image_path in IMAGE_CACHE :
+    if path in IMAGE_CACHE :
         # get from cache
-        image = IMAGE_CACHE[image_path]
+        image = IMAGE_CACHE[path]
 
     else :
-        # ensure exists
-        if not os.path.exists(image_path) :
-            raise exceptions.NotFound(image_name)
+        # open
+        image = pt.Image(path)
+
+        # check
+        if image.status() not in (pt.CACHE_FRESH, pt.CACHE_STALE) :
+            raise exceptions.InternalServerError("Image cache not available: %s" % name)
+
+        # load
+        image.open()
 
         # cache
-        image = IMAGE_CACHE[image_path] = pt.Image(image_path)
+        IMAGE_CACHE[path] = image
     
-    if image.status() == pt.CACHE_NONE :
-        raise exceptions.InternalServerError("Image not cached: " + image_name)
+    return image
+
+
+
+### Handle request
+def handle_dir (req, name, path) :
+    """
+        Handle request for a directory
+    """
+
+    return Response(render_dir(req, name, path), content_type="text/html")
+
+
+
+def handle_img_viewport (req, image, name) :
+    """
+        Handle request for image viewport
+    """
+
+    # viewport
+    return Response(render_img_viewport(req, name, image), content_type="text/html")
+
+
+def handle_img_region (req, image) :
+    """
+        Handle request for an image region
+    """
+
+    # specific image
+    width = int(req.args['w'])
+    height = int(req.args['h'])
+    cx = int(req.args['cx'])
+    cy = int(req.args['cy'])
+    zoom = int(req.args.get('zl', "0"))
+
+    # yay full render
+    return Response(render_img_region(image, cx, cy, zoom, width, height), content_type="image/png")
+
+
+def handle_img_tile (req, image) :
+    """
+        Handle request for image tile
+    """
+
+    # tile
+    x = int(req.args['x'])
+    y = int(req.args['y'])
+    zoom = int(req.args.get('zl', "0"))
+        
+    # yay render
+    return Response(render_img_tile(image, x, y, zoom), content_type="image/png")
+
+## Dispatch req to handle_img_*
+def handle_img (req, name, path) :
+    """
+        Handle request for an image
+    """
+
+    # get image object
+    image = get_image(name, path)
 
     # what view?
     if not req.args :
-        # viewport
-        return Response(image_view(req, image_path, image), content_type="text/html")
+        return handle_img_viewport(req, image, name)
 
     elif 'w' in req.args and 'h' in req.args and 'cx' in req.args and 'cy' in req.args :
-        # specific image
-        width = int(req.args['w'])
-        height = int(req.args['h'])
-        cx = int(req.args['cx'])
-        cy = int(req.args['cy'])
-        zoom = int(req.args.get('zl', "0"))
-
-        # yay full render
-        return Response(render_image(image, cx, cy, zoom, width, height), content_type="image/png")
+        return handle_img_region(req, image)
 
     elif 'x' in req.args and 'y' in req.args :
-        # tile
-        x = int(req.args['x'])
-        y = int(req.args['y'])
-        zoom = int(req.args.get('zl', "0"))
-        
-        # yay render
-        return Response(render_tile(image, x, y, zoom), content_type="image/png")
-   
+        return handle_img_tile(req, image)
+
     else :
         raise exceptions.BadRequest("Unknown args")
-   
+
+
+
+## Dispatch request to handle_*
+def handle_req (req) :
+    """
+        Main request handler
+    """
+    
+    # decode req
+    name, path = get_req_path(req)
+
+    # determine dir/image
+    if os.path.isdir(path) :
+        # directory
+        return handle_dir(req, name, path)
+    
+    elif not os.path.exists(path) :
+        # no such file
+        raise exceptions.NotFound(name)
+
+    elif not name or not name.endswith('.png') :
+        # invalid file
+        raise exceptions.BadRequest("Not a PNG file")
+    
+    else :
+        # image
+        return handle_img(req, name, path)
+
+
+
 
 @responder
 def application (env, start_response) :
+    """
+        Main WSGI entry point
+    """
+
     req = Request(env, start_response)
     
     try :
-        return handle_main(req)
+        return handle_req(req)
 
     except exceptions.HTTPException, e :
         return e