"""

    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()

