"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/demo/altdemo.py $
$Id: altdemo.py *** 25635 2004-11-18 00:11:51Z nascheme $

An alternative Quixote demo, showing also a way to do form-based authentication
and page access control. This version is contained in a single module and does 
not use PTL.  

The easiest way to run this demo is to use the simple HTTP server included 
with Quixote.  For example:

    $ server/simple_server.py --factory quixote.demo.altdemo.create_publisher

The server listens on localhost:8080 by default.  Debug and error output
will be sent to the terminal.

If you have installed durus, you can run the same demo, except with
persistent sessions stored in a durus database, by running:

    $ server/simple_server.py --factory quixote.demo.altdemo.create_durus_publisher

"""

from quixote import get_request, get_user, get_session, get_session_manager, \
                    get_field
from quixote.directory import Directory, AccessControlled
from quixote.html import href, htmltext
from quixote.errors import AccessError
from quixote.publish import SessionPublisher
from quixote.session import Session, SessionManager
from quixote.util import dump_request

#

def format_page(title, content):
    request = htmltext(
        '<div style="font-size: smaller;background:#eee">'
        '<h1>Request:</h1>%s</div>') % dump_request()
    return htmltext(
        '<html><head><title>%(title)s</title>'
        '<style type="text/css">\n'
        'body { border: thick solid green; padding: 2em; }\n'
        'h1 { font-size: larger; }\n'
        'th { background: #aaa; text-align:left; font-size: smaller; }\n'
        'td { background: #ccc; font-size: smaller; }\n'
        '.login_form { text-align:right;width:260px; }\n'
        '.restricted { border:2px solid red; padding: 10px; }\n' 
        '</style>'
        '</head><body>%(content)s%(request)s</body></html>') % locals()

def format_common_restricted_content():
    content = htmltext('')
    sessions = get_session_manager().items()
    if sessions:
        sessions.sort()
        content += htmltext('<table class="restricted"><tr>'
                            '<th></th>'
                            '<th>Session</th>'
                            '<th>User</th>'
                            '<th>Number of Requests</th>'
                            '</tr>')
        this_session = get_session()
        for index, (id, session) in enumerate(sessions):
            if session is this_session:
                formatted_id = htmltext(
                    '<span style="font-weight:bold">%s</span>' % id)
            else:
                formatted_id = id
            content += htmltext(
               '<tr><td>%s</td><td>%s</td><td>%s</td><td>%d</td>' % (
                index,
                formatted_id,
                session.user or htmltext("<em>None</em>"),
                session.num_requests))
        content += htmltext('</table>')
    return content 

#

class RootDirectory(AccessControlled, Directory):

    _q_exports = ['', 'restricted', 'login', 'logout']

    def _q_access(self):
	if get_request().get_header('PATH_INFO').find('restricted') > 0: 
            if not get_user():
                raise NotLoggedInError()

    def _q_index(self):
        content = htmltext('')
        content += htmltext('<p>%s</p>' % 'index')
        content += htmltext('<p>%s</p>' % href('restricted', 'restricted'))
        if not get_user():
            content += htmltext('<p>%s</p>' % href('login', 'login'))
        else:
            content += htmltext('<p>%s</p>' % href('logout', 'logout'))
        return format_page("Quixote Session Management Demo", content)

    def restricted(self):
        content = htmltext('')
        content += htmltext('<p>%s</p>' % href('..','index'))
        content += htmltext('<p>%s</p>' % 'restricted')
        content += htmltext('<p>%s</p>' % href('logout', 'logout'))
        content += format_common_restricted_content()
        return format_page("Quixote Session Management Demo", content)

    def login(self):
        content = htmltext('')
        content += htmltext('<p>%s</p>' % href('..','index'))

        def authenticate(name,passwd):
            # customize check
            if not name or not passwd:
                return False 
            return True 

        if not get_user():
            if authenticate(get_field("name"), get_field('passwd')): 
                session = get_session()
                session.set_user(get_field("name")) # This is the important part.

        if get_user():
            # may have already been logged in
            content += htmltext(
                '<p>You are logged in as %s.</p>') % get_user()
            content += htmltext('<p>%s</p>' % href('logout', 'logout'))

            # If referer is _not_ /login itself 
            referer = get_request().get_header('HTTP_REFERER')
            if referer and referer.find('/login') < 0: 
                from quixote import redirect
	        return redirect( get_request().get_header('HTTP_REFERER') or '/' )

        else:
            content += htmltext(
                '<p>Please enter your name here:</p>\n'
                '<form method="POST" action="/login">'
                '<div class="login_form">'
                'name <input type="text" name="name" /><br/>'
                'passwd <input type="password" name="passwd" /><br/>'
                '<input type="submit" />'
                '</div>'
                '</form>')
        return format_page("Quixote Session Demo: Login", content)

    def logout(self):
        content = htmltext('')
        content += htmltext('<p>%s</p>' % href('..','index'))
        if get_user():
            content += htmltext('<p>You are logged out. Goodbye, %s.</p>') % get_user()
        else:
            content += htmltext('<p>Nobody to log out - You are already logged out.</p>')
        content += htmltext('<p>%s</p>' % href('login', 'login'))
        get_session_manager().expire_session() # This is the important part.

        # If referer is _not_ /logout itself
        referer = get_request().get_header('HTTP_REFERER')
        if referer and referer.find('/logout') < 0: 
            from quixote import redirect
	    return redirect( get_request().get_header('HTTP_REFERER') or '/' )

        return format_page("Quixote Session Demo: Logout", content)

#

class DemoSession(Session):

    def __init__(self, id):
        Session.__init__(self, id)
        self.num_requests = 0

    def start_request(self):
        """
        This is called from the main object publishing loop whenever
        we start processing a new request.  Obviously, this is a good
        place to track the number of requests made.  (If we were
        interested in the number of *successful* requests made, then
        we could override finish_request(), which is called by
        the publisher at the end of each successful request.)
        """
        Session.start_request(self)
        self.num_requests += 1

    def has_info(self):
        """
        Overriding has_info() is essential but non-obvious.  The
        session manager uses has_info() to know if it should hang on
        to a session object or not: if a session is "dirty", then it
        must be saved.  This prevents saving sessions that don't need
        to be saved, which is especially important as a defensive
        measure against clients that don't handle cookies: without it,
        we might create and store a new session object for every
        request made by such clients.  With has_info(), we create the
        new session object every time, but throw it away unsaved as
        soon as the request is complete.

        (Of course, if you write your session class such that
        has_info() always returns true after a request has been
        processed, you're back to the original problem -- and in fact,
        this class *has* been written that way, because num_requests
        is incremented on every request, which makes has_info() return
        true, which makes SessionManager always store the session
        object.  In a real application, think carefully before putting
        data in a session object that causes has_info() to return
        true.)
        """
        return (self.num_requests > 0) or Session.has_info(self)

    is_dirty = has_info

#

class NotLoggedInError(AccessError):
    status_code = 401 # Unauthorized 
    title = "Not logged in" 
    description = "You must be logged in to access this resource."

    def __init__(self, public_msg=None, private_msg=None):
        if not public_msg: public_msg = self.description
        if not private_msg: private_msg = self.title
        AccessError.__init__(self, public_msg, private_msg)

#

class DemoSessionPublisher(SessionPublisher):
     def try_publish(self, request):
        try:
            return SessionPublisher.try_publish(self, request)
        except NotLoggedInError, e:
            # just include the login page source, but without 
            # redirecting, to "stay within" current request/url 
            return self.root_directory.login()

def create_publisher():
    return DemoSessionPublisher(RootDirectory(),
                            SessionManager(session_class=DemoSession),
                            display_exceptions='plain')

try:
    # If durus is installed, define a create_durus_publisher() that
    # uses a durus database to store persistent sessions.
    import os, tempfile
    from durus.persistent import Persistent
    from durus.persistent_dict import PersistentDict
    from durus.file_storage import FileStorage
    from durus.connection import Connection
    connection = None # set in create_durus_publisher()

    class PersistentSession(DemoSession, Persistent):
        pass

    class PersistentSessionManager(SessionManager, Persistent):
        def __init__(self):
            sessions = PersistentDict()
            SessionManager.__init__(self,
                                    session_class=PersistentSession,
                                    session_mapping=sessions)
        def forget_changes(self, session):
            print 'abort changes', get_session()
            connection.abort()

        def commit_changes(self, session):
            print 'commit changes', get_session()
            connection.commit()

    def create_durus_publisher():
        global connection
        filename = os.path.join(tempfile.gettempdir(), 'quixote-demo.durus')
        print 'Opening %r as a Durus database.' % filename
        connection = Connection(FileStorage(filename))
        root = connection.get_root()
        session_manager = root.get('session_manager', None)
        if session_manager is None:
            session_manager = PersistentSessionManager()
            connection.get_root()['session_manager'] = session_manager
            connection.commit()
        return DemoSessionPublisher(RootDirectory(),
                                session_mgr=session_manager,
                                display_exceptions='plain')
except ImportError, exc:
    pass # durus not installed.

#
