"""
    This sample web server demonstrates how to implement a web service for 
    Dynatree requests.
    
    As data source, we choose a folder in the local file system, simply
    because it is hierarchical and has a concept of documents (files) and
    folders (directories) and because it was easy to implement.
    A typical web service would of course read the data from a 'real' source 
    like an SQL database or XML file, but I hope you get the idea ;-)
    
    See: http://dynatree.googlecode.com
    
    Martin Wendt, 2009-2011
    
    Usage:    
      1. Python 2.5 or later is required to run this server.
         For Python 2.5 the simplejson module must also be installed;
         Python 2.6 has built in json support.
      2. Configure the rootPath variable in the main() function at the bottom
         of this module.
      3. Run this module:
         > python dynatree_server.py
         
         Optionally pass a root folder:
         > python dynatree_server.py c:\temp

    This module
      - Is a standalone web server that answers URLs beginning with
            http://127.0.0.1:8001/?...
        with JSON responses that conform to the Dynatree spec.
        However, we don't do error checking or anything else that would be 
        required for production environments.
        
      - Answers requests to initialize a tree using the 'initAjax: {}' option:
            http://127.0.0.1:8001/?mode=baseFolders
        with a list of files/directories in the configured root directory.
        
      - Answers requests to lazy-load node children using 'appendAjax({...})':
            http://127.0.0.1:8001/?key=_25c2b6d6
        with a list of files/directories in the directory that matches this key.
        
      - Supports Dynatree's 'lazy persistence':
            http://127.0.0.1:8001/?mode=baseFolder&expandedKeyList=_41771df2%2C_4230fb68%2C...
        will return not only the base entries, but also all children inside 
        parents that are listed as expanded.       
        
      - Supports &sleep=SECONDS argument for simulating slow responses.

      - Supports the JSONP protocol:
            http://127.0.0.1:8001/?mode=baseFolder&callback=jsonp1241293219729
        will wrap the result like this "jsonp1241293219729(<res>)".
        This is only required, if this web service is not on the same host as 
        the web page that contains the Dynatree widget.
        JSONP can be enabled for jQuery.ajax() by passing dataType: 'jsonp'
        instead of 'json'.
        
      - Dumps the POST body, if the request URL is '/submit_data' 
        

Sample Dynatree options to use this service:
        $("#tree").dynatree({
            ...
            persist: true,
            initAjax: {url: "http://127.0.0.1:8001",
                       dataType: "jsonp", // Enable JSONP, so this sample can be run from the local file system against a localhost server
                       data: {key: "",
//                              sleep: 3,
//                              depth: 2,
                              mode: "baseFolders"
                              },
                       addExpandedKeyList: true // Send list of expanded keys, so the webservice can deliver these children also
                       },
            onLazyRead: function(dtnode){
                dtnode.appendAjax(
                    {url: "http://127.0.0.1:8001", 
                     dataType: "jsonp",
                     data: {key: dtnode.data.key,
                            mode: "branch"
                            }
                });
            }
        });
"""

import cgi 
import os
import sys
import time
from wsgiref.simple_server import WSGIServer, WSGIRequestHandler 
from tempfile import gettempdir

try:
    import json # Available since Python 2.6
except ImportError:
    import simplejson as json

#===============================================================================
# Helper functions
#===============================================================================

def _keyFromString(s):
    """Calculate a unique key for an arbitrary  string.
    
    Example: _keyFromString("c:\temp\wsgidav1\src\DAV") = "_25c2b6d6"
    """
    return "_" + hex(hash(s)) [3:]


def _findFolderByKey(rootPath, key):
    """Search rootPath and all sub folders for a directory that matches the key."""
    for root, dirs, files in os.walk(rootPath):
        for name in dirs:
            fullPath = os.path.join(root, name)
            fileKey = _keyFromString(fullPath) 
            if key == fileKey:
                return fullPath
    return None

    
#===============================================================================
# DynaTreeWsgiApp
#===============================================================================

class DynaTreeWsgiApp(object):
    """This WSGI application serves a file system hierarchy for dynatree."""
    def __init__(self, optionDict):
        self.optionDict = optionDict

    def __call__(self, environ, start_response):
        """Handle one HTTP request."""
        
        # Parse URL query into a list of 2-tuples (name, value) 
        argList = cgi.parse_qsl(environ.get("QUERY_STRING", ""))
        # Convert to dictionary {"name": "value", ... }
        argDict = dict(argList)
        print "Query args: %s" % argDict

        # Support &sleep=SECONDS argument to simulate slow connections for debugging 
        if argDict.get("sleep"):
            print "Sleeping %s seconds..." % argDict.get("sleep")
            time.sleep(int(argDict.get("sleep")))
            
        # Dump POST request data, if http://HOST:PORT/submit_data was requested
#        print "PI", environ["PATH_INFO"]
        if environ["PATH_INFO"] == "/submit_data":
            print "Got /submit_data request, CONTENT_LENGTH=%r" % environ.get("CONTENT_LENGTH")  
            try:
                length = int(environ["CONTENT_LENGTH"])
                data = environ["wsgi.input"].read(length)
            except:
                print >>sys.stderr, "Couldn't read from wsgi.input! This can happen when using Firefox locally"  
                try:
                    data = environ["wsgi.input"].read()
                except:
                    print 
            print "Data: ", data
            start_response("200 OK", [("Content-Type", "text/html")])
            return [ "Thanks for sending<br><pre><code>%s</code></pre>" % data ]

        # Support &depth=LEVEL argument to read more than one level (1: direct children)
        depth = int(argDict.get("depth", 0))
        if depth > 1:
            print "'depth' mode: loading %s levels" % depth

        # Return empty list when '&returnEmpty' is passed
        returnEmpty = "returnEmpty" in argDict
        
        # Eval 'mode' and 'key' arguments
        rootPath = self.optionDict["rootPath"]
        if argDict.get("mode") == "baseFolders":
            folderPath = rootPath
        elif argDict.get("key"):
            key = argDict.get("key")
            folderPath = _findFolderByKey(rootPath, key)
            if not folderPath:
                raise RuntimeError("Could not find folder for key '%s'." % key)
        else:
            raise RuntimeError("Missing required argument '&mode=baseFolder' or '&key=...'")

        # Get list of child nodes (may be recursive)
        childList = [ ]
        if not returnEmpty:
            self.makeChildList(argDict, folderPath, childList, depth)
        
        # Convert result list to a JSON string
        res = json.dumps(childList, encoding="Latin-1")
        
        # Support for the JSONP protocol.
        if "callback" in argDict:
            res = argDict["callback"] + "(" + res + ")" 

        # Return HTTP response 
        start_response("200 OK", [("Content-Type", "application/json")])
        return [ res ]


    def makeChildList(self, argDict, folderPath, childList, depth):
        print "makeChildList(%s, depth=%s) " % (folderPath, depth)
        expandedKeyList = argDict.get("expandedKeyList", "").split(",")
        filenameList = os.listdir(folderPath)
        for fn in filenameList:
            fullPath = os.path.join(folderPath, fn)
            isFolder = os.path.isdir(fullPath)
            key = _keyFromString(fullPath)
            try:
                size = os.path.getsize(fullPath)
                date = time.ctime(os.path.getmtime(fullPath))
            except:
                # May fail when path contains funny chars (don't care in this sample)
                size = 0
                date = time.ctime()
            # Create a node dictionary and append it to the child list
            node = {"title": fn,
                    "key": key,
                    "isFolder": isFolder,
                    "isLazy": isFolder,
                    "tooltip": "%s, %s bytes, modified: %s" % (fullPath, size, date),
                    }
            childList.append(node)
            # Support lazy persistence:
            # If the current node was requested as 'expanded', load the children too 
            if isFolder and (key in expandedKeyList or depth > 1):
                subNodes = []
                self.makeChildList(argDict, fullPath, subNodes, depth-1)
                node["children"] = subNodes
#                node["isLazy"] = False
#                node["expand"] = True
    

#===============================================================================
# Server
#===============================================================================

# Requires Python >= 2.5

def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
    """Create a new WSGI server listening on 'host' and 'port' for 'app'."""
    server = server_class((host, port), handler_class)
    server.set_app(app)
    return server


def main():  
    # Configure root directory that will be exported:
    rootPath = gettempdir()
    if len(sys.argv) > 1:
        rootPath =sys.argv[1]
    
#    rootPath = "/temp"

    # Configure hostname and port on which the server will listen
#    hostname = "127.0.0.1" # Use empty string for localhost (local access only)
    hostname = "" # Use empty string for 0.0.0.0 (allows remote access)
    port = 8001

    wsgi_app = DynaTreeWsgiApp({"rootPath": rootPath})

    httpd = make_server(hostname, port, wsgi_app)
    
    sa = httpd.socket.getsockname()

    print "Exporting file system at ", rootPath, " for Dynatree."
    print "Serving HTTP on", sa[0], "port", sa[1], "..."
    assert os.path.isdir(rootPath), "Invalid root path: '%s'" % rootPath

    httpd.serve_forever()
     

if __name__ == "__main__":
    main()
