"""
flot.es Server:

  This server is an extension of the Apache 2 web server using mod_python.
  refer to /shared/docs/conventions for coding conventions.

"""

import re
from time import time as server_time
T=server_time()
from sys import path as syspath
from os import path,popen
from mod_python import apache,util,Session,Cookie
syspath.append('/share/code/py/pkg')
import traceback
from pyquery import PyQuery

FHOST='flot.es'
## this is always referenced remotely, so include http://
FHOST_STATIC='http://static.flot.es'

# python data structure method wrappers,
# with the intention of making python less of a pain in the ass.

# safe dict or list lookup
def C_lookup(_map,_key):
  try:
   return _map[_key]
  except (KeyError,IndexError):
   return None

# end of aforementioned wrappers.

## some aliases to help prevent excessive clutters of dots.
dir = path.join
exists = path.exists

MAX_FILENAME_LENGTH=64
MAX_SITE_LENGTH=32

MD5KEY='8Cl6PefmE8bDlHP2'
FROOT=dir('/var','www',FHOST)
ROOT=dir(FROOT,'sites')

# thread id. used in logging.
TID=eval(open(dir(FROOT,'tid.txt'),'r').read())
RID=0
open(dir(FROOT,'tid.txt'),'w').write('%s'%(TID+1))

## use LOG=None to turn off logging.
LOG = dir(FROOT,"flot.es.log")
## approximate forever as 1 billion seconds (around 3 decades)
FOREVER=1E9

AUTH_SITE_ACTIONS=['upload','save']
AUTH_ANY_ACTIONS=['login','update','message']

GHDR="<html><head></head><body>"
GFTR="</body></html>"
HDR0=r'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

  <head profile="http://www.w3.org/2005/10/profile">

    <meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
    <meta name="robots" content="index,follow" />
    <meta name="revisit-after" content="7 days" />
    <meta name="siteinfo" content="'''+FHOST_STATIC+'''/robots.txt" />
    <meta name="googlebot" content="noarchive" />

    <link rel="stylesheet" type="text/css" href="'''+FHOST_STATIC+'''/inc/flt.css" />
    <link rel="icon" href="favicon.ico">
    <title>'''
HDR1=r'''</title>

<!--[if lt IE 7]>
<style>
/* hide broken shadows in ie6 */
.shadow{visibility:hidden}
#flt-hdr-handle img{visibility:hidden}
</style>
<![endif]-->

<script>
//<!--
var new_page=false;
//-->
 </script>
</head>
<body>
 <iframe style="visibility:hidden;width:0;height:0;display:none;border-width:0" id="upload_target" name="upload_target"></iframe>
'''
FTR="""
 <div id="flt-header" style="visibility:hidden" class="ui ui-hbar">
   <div class="shadow"></div>
 </div>
 <div id="flt-hdr-handle" class="ui">
  <a href="""+FHOST+'''no-javascript.html">flot.es</a>
  <div class="shadow"></div>
 </div>
 <div id="flt-ui"></div>
 <script>
//<!--

var FHOST = "'''+FHOST+'''",
    FHOST_STATIC = "'''+FHOST_STATIC+'''",
    HOME = (window.location+'/').match(/(http:\/\/.*?)\//)[1],
    SITE = HOME.match(/http:\/\/(.*)$/)[1].replace(".'''+FHOST+'''",""),
    PAGE = (window.location+'/').match(/http:\/\/[^\/]+\/(.+?\.html)/);

if (PAGE) PAGE=PAGE[1]; else PAGE='index';

function loadScript(filename) {
 var fileref=document.createElement('script');
 fileref.setAttribute("type","text/javascript");
 fileref.setAttribute("src", FHOST_STATIC+"/inc/"+filename);
 if (typeof fileref!="undefined")
  document.getElementsByTagName("head")[0].appendChild(fileref);
}

var handle=document.getElementById("flt-hdr-handle");
function loader() {
 document.getElementById("flt-hdr-icon").src=FHOST_STATIC+"/img/loading.gif";
 if(handle.removeEventListener){ // Mozilla, Netscape, Firefox
  handle.removeEventListener('click',loader,false);
 } else { // IE
  handle.detachEvent('click',loader);
 }
 loadScript("jquery-1.2.6.min.js");
 tryLoadFlotes();
}

function tryLoadFlotes() {
  if(typeof $ == "undefined") {
    setTimeout("tryLoadFlotes();",250);
  } else {
    loadScript("flt.max.js");
  }
}

handle.innerHTML=' \
  flot.es<img src="'+FHOST_STATIC+'/img/bar_down.png" id="flt-hdr-icon"/> \
  <div class="shadow"></div> ';

if(new_page || document.cookie.match("user="+SITE)) { loader() } else {
 if(window.addEventListener){ // Mozilla, Netscape, Firefox
  handle.addEventListener('click',loader,false);
 } else { // IE
  handle.attachEvent('click',loader);
 }
}

//-->
 </script>
 
<!--[if lt IE 8]>
  <script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/IE8.js" type="text/javascript"></script>
<![endif]-->

</body>
</html>
'''
MID = 0
RID = 0
def log(msg):
      global MID,TID,RID
      if LOG:
        MID += 1
        open(LOG,"a").write("T:%s R:%s M:%s - %s\n"%(TID,RID,MID,msg))

class DoneException:
  pass

class Responder:

    info_dirty=[] ## List of sites with info file needing saved.
    session_dirty=False ## True if we need to save the session.
    
    out=''
    script=''
    
    S=None ## session (session data private to server)
    I={} ## info (persistent website information). indexed by site.
    C=None ## cookie (session data shared with client)
    F=None ## form (temporary data sent from client in request)
    
    def __init__(self,req):
      global RID
      RID=eval(open(dir(FROOT,'rid.txt'),'r').read())+1
      open(dir(FROOT,'rid.txt'),'w').write('%s'%(RID))
      log('NEW RESPONSE id=%s'%RID)
      
      self.req = req
      self.req.add_common_vars()
      self.host = self.req.subprocess_env['HTTP_HOST']
      self.path = (req.subprocess_env['SCRIPT_NAME'] +
        (C_lookup(self.req.subprocess_env,'PATH_INFO') or '')).strip('/')
      #log("HOST=%s PATH=%s"%(self.host,self.path))
      
      self.act = self.form('act')
      self.uri = self.form('uri')
      self.site = self.form('site')
      
      log('act=%s uri=%s site=%s'%(self.act,self.uri,self.site))
      
    def respond(self):
      
      ## Validate the request.
      
      if self.act == 'js':
        self.out = popen('cat %s/inc/flt.max.%s.js'%(ROOT,MD5KEY,ROOT)).read()
        raise DoneException
      
      if not self.act or not self.site:
        self.error('Invalid argument (act="%s") site="%s" uri="%s"'%(self.act,self.site,self.uri))
      if self.uri and ('/' in self.uri or '\\' in self.uri or '..' in self.uri):
        self.error('Illegal URI=%s'%self.uri)
      if len(self.site) > MAX_SITE_LENGTH:
        self.error('Site name exceeds %s characters.'%(MAX_SITE_LENGTH,len(self.site)))
      if self.uri and len(self.uri) > MAX_FILENAME_LENGTH:
        self.error('Filename exceeds %s characters (%s).'%(MAX_FILENAME_LENGTH,len(self.uri)))

      self.site_fs = dir(ROOT,self.site)
      self.uri_fs = dir(self.site_fs,self.uri or '')
      
      if self.act=='new':
        self.new_page()

      # if the user doesn't identify otherwise, assume the user is the site.
      self.user = self.form('user') or self.site;

      log('User claims to be %s'%self.user)

      if self.act in AUTH_SITE_ACTIONS:
        verdict = self.unauth(self.site)
        log('verdict is %s'%verdict)
        if verdict:
          self.error('Site login failed. %s'%verdict)
      elif self.act in AUTH_ANY_ACTIONS:
        verdict = self.unauth(self.user)
        log('verdict is %s'%verdict)
        if verdict:
          self.error('Login failed. %s'%verdict)
      if self.act == 'data':

        if self.unauth(self.site): # send public info for the current site.

          if not self.info(self.site,'password'):
            self.script += 'new_site=true;\n'
          self.send_shared_info(self.site)
        else: # send private info for the current site
          self.send_auth_info(self.site)
          
        #if self.user and self.site != self.user:
        #  if self.unauth(self.user):
        #    self.send_auth_info(self.user)
        #  else:
        #    self.send_shared_info(self.site)
        raise DoneException

      if self.act == 'upload':
        tgt=self.F['file'].filename
        log('Uploading %s.'%tgt)
        if tgt:
          if eval(popen('du -ms %s | grep -P -o "\d+"'%self.site_fs).read()) > 100:
            self.error('100 meg upload limit exceeded.')
          if exists(dir(self.site_fs,tgt)):
            self.ok(GHDR+'File exists. Creating link.\n'+GFTR)
          elif 'flt.' not in tgt and tgt != 'site.info':
            open(dir(self.site_fs,tgt), 'wb').write(self.F['file'].file.read())
            self.ok(GHDR+'Upload succeeded.'+GFTR)
        else:
         self.error(GHDR+"No filename found."+GFTR)

      elif self.act == 'save':
        tgt = dir(self.site_fs,self.uri)
        if self.form('title') and self.form('html'):
          
          # save changes to file.
          open(tgt,'w').write(HDR0+self.form('title')+HDR1+self.form('html')+FTR)
          save_msg = '<b>Saved %s</b><br/><br/>'%self.uri
          
          # get a list of files using the same template.
          template = self.get_template_string()
          if template:
            cmd = 'ls %s | grep %s.html'%(self.site_fs,template)
            up_docs = popen(cmd).read().strip().split('\n')
            up_docs.remove(self.uri)
            j_tpl = PyQuery('<div></div>').append(PyQuery(self.form('html')));
            
            # propagate template changes to other documents in same template group.
            for doc in up_docs:
              if doc:
                j_doc = PyQuery(open(dir(self.site_fs,doc)).read()) # get html
                page_content = j_doc.find('div.template-box').html() or ''
                j_tpl.find('div.template-box').html(page_content)
                # get full html including parent tag.
                html = j_tpl.html()
                open(dir(self.site_fs,doc),'w').write(HDR0+self.form('title')+HDR1+html+FTR)
                log('saving page %s'%doc)
            
            if up_docs: save_msg += "<i>Applied template to:</i><br/>&nbsp;%s<br/>"%(
              '<br/>&nbsp;'.join(up_docs))

          self.ok(save_msg)
        else:
          self.error('Invalid Arguments: title and/or html is missing.')
          
      elif self.act == 'logout':
        self.load_session()
        try:
          del self.S['user']
        except KeyError:
          self.out='Not logged in.'
          raise DoneException
        self.session_dirty=True
        self.out='Logged out.'
        raise DoneException
      
      elif self.act == 'login':
        self.ok('Logged in.')
      
      elif self.act == 'update':
        if self.form('links'):
          self.info(self.site,'links',eval(self.form('links')))
          if self.site not in self.info_dirty: self.info_dirty.append(self.site)
          self.ok('Update completed.')
      
      elif self.act == 'delete':
        popen('rm %s'%dir(self.site_fs,self.uri))
        self.ok('%s deleted.'%self.uri)
        
      self.error("Action '%s' not handled"%self.act)
    
    def unauth(self,user):
      log('Authenticating user %s. (user was %s)'%(user,self.session('user')))
      
      if not user: return "No user to authenticate."
      if user == self.session('user'):
        log('Already authenticated.')
        return False
      elif not self.form('password'):
        return 'No password received.'
      else:
        ipw = self.info(user,'password')
        import md5crypt
        fpw = md5crypt.unix_md5_crypt(self.form('password'),MD5KEY)
        if ipw:
          if fpw == ipw:
            # first login. send info to client.
            self.send_auth_info(user)
          else:
            return 'Password incorrect.'
        else:
          captcha_file=dir(self.site_fs,'captcha.gif')
          if not self.form('email'): return "Email required for first login."
          if exists(captcha_file):
            if not self.info(self.site,'captcha'):
              self.error("Not verified, but no captcha string saved.")
            if self.form('captcha') != self.info(self.site,'captcha'):
              return "Captcha phrase incorrect."
            #log(popen('rm -f %s'%captcha_file).read())
            self.info(user,'password',fpw)
            self.info(user,'email',self.form('email'))
            popen('rm %s'%captcha_file)
            log('New password created.')
          else:
            self.error('No password exists, but no captcha found!')
      self.session("user","%s"%user)
      return False
    
    def cookie(self,key,val=None):
      self.load_cookies()
      if val==None:
        try:
          return self.C[key].value
        except:
          return None
      else:
        self.C[key]=val
        cookie = Cookie.Cookie(key,val)
        cookie.path = '/%s'%self.site
        cookie.expires = server_time() + FOREVER
        Cookie.add_cookie(self.req,cookie)
        log('set cookie %s=%s'%(key,val))
    
    def info(self,user,key,val=None):

      #log('user=%s key=%s'%(user,key))
      if not self.I.has_key(user):
        self.load_info(user)
      if val==None:
        try:
          return self.I[user][key]
        except KeyError:
          return None
      else:
        log('set info %s=%s'%(key,val))
        self.I[user][key]=val
        if user not in self.info_dirty: self.info_dirty.append(user)
    
    def session(self,key,val=None):
      self.load_session()
      if val==None:
        try:
          return self.S[key]
        except KeyError:
          return None
      else:
        self.S[key]=val
        log('set session %s=%s'%(key,val))
        self.session_dirty=True
    
    def form(self,key):
      if not self.F:
        self.F = util.FieldStorage(self.req)
      field = C_lookup(self.F,key)
      if hasattr(field,'value'):
        return field.value
      else:
        return field
      
    def load_cookies(self):
      if self.C: return
      self.C = Cookie.get_cookies(self.req)
      
    def load_session(self):
      if self.S is not None: return
      self.S = Session.Session(self.req, timeout=7200)
      log("SESSION LOADED IS: %s"%self.S)
      #log('loaded session id=%s'%self.S.id())
      if self.S.is_new():
        self.info(self.site,'hits',self.info(self.site,'hits') + 1)
    
    def load_info(self,user):
      if C_lookup(self.I,user): return
      info_file = dir(dir(ROOT,user),'flt.info')
      if exists(info_file):
        stuff = open(info_file).read()
        log('loaded this from info file: %s'%stuff)
        self.I[user]=eval(stuff)
      else:
        log ("Clearing info file.")
        self.I[user]={'hits':0,'links':[],'messages':[]}
      #log('INFO LOADED IS: %s'%self.I)
    
    def send_shared_info(self,user):
      self.send_info(user,['links'])
    
    def send_auth_info(self,user):
      self.send_info(user,['links','hits','messages'])
      self.script += 'INFO["%s"]["ls"]=["'%self.site + \
        '","'.join(popen('ls %s | grep -v flt.info'%self.site_fs).read().strip().split('\n')) + \
        '"];\n'
      
    def send_info(self,user,keys):
      self.script += 'INFO.%s={\n'%user
      self.script += ','.join(['%s:%s\n'%(k,repr(self.info(user,k))) for k in keys])
      self.script += '};\n'

    def cleanup(self):
      
      if self.act in ['upload', 'new']:
        self.req.content_type="text/html"
        self.req.write(self.out)
      elif self.act == 'js':
        self.req.content_type="text/javascript"
        self.req.write(self.out)
      else:
        self.req.content_type="text/plain"
        self.req.write('''<div class='rsp-data'><div class='rsp-msg'>%s</div><script>\n%s\n</script></div>'''%(self.out,self.script))
      

      if self.S is not None:
        if self.session_dirty:
          log('saving session')
          self.S.save()
        log('self.S is now: %s'%self.S)
        self.S.unlock()

      for user in self.info_dirty:
        if exists(dir(ROOT,user)):
          log('saving user %s info: %s'%(user,self.I[user]))
          open(dir(ROOT,user,'flt.info'),'w').write('%s'%self.I[user])
        else:
          log("Can't save info. user %s doesn't exist!"%user)
      
      log('ok, processing took %s\n\n'%(server_time()-T))

    def mkdir(self):
      if not exists(self.site_fs):
        log('creating '+self.site_fs)
        popen('mkdir -p %s'%self.site_fs)

    def new_page(self):
      log('New page.')
      
      if not exists(self.site_fs):
        import captcha
        self.mkdir()
        self.info(self.site,'captcha',captcha.gen_random_captcha(
          dir(self.site_fs,'captcha.gif')))
        
      template = self.get_template_string()
      tpl_file = dir(self.site_fs,template+('.html'))
      title = self.site + \
        (self.uri != 'index.html' and \
        (' : '+self.uri.replace('.html','')) or '')
      self.out = HDR0+title+HDR1
      if '.'+template in self.uri and exists(tpl_file):
        # create a new page based on a flot.es template.
        j_tpl = PyQuery(open(tpl_file).read()).find('#flt-content') # get html from template
        j_tpl.find('div.template-box').html("(\"%s\" page content area)"%self.uri.replace('.'+template+'.html',''))
        self.out += j_tpl.html()
        
      else:
        # create a new page from scratch (with flot.es welcome messages.)
        self.out += '''
<div id="flt-content" class="no-transform no-rte">
  <div class='default' style='left:60px;top:30px;font-size:36px;color:#808080;font-weight:bold;font-family:sans-serif'>&nbsp;welcome to %s</div>
  <div class='default template-box' style='left:120px;width:500px;top:100px;font-size:14px;border:1px solid #000000;background-color:#ffffe0;font-family:sans-serif'>
  <b>You have just created this website!</b><br/>
  <ol>
  <li>To start making changes just click on this box. Try moving and resizing it.</li>
  <li>Click the pencil icon above the box to edit the text inside it.</li>
  <li>Add new objects from the net, your computer, or from scratch using the toolbar above.</li>
  </ol>
  </div>
</div>'''%( title.capitalize() )
      self.out += '''
  <script class='remove-me'>
  new_page=true;
  </script>
  '''+FTR
      raise DoneException
    
    def get_template_string(self):
      "Get the appropriate template string for this page."
      template_match = re.search(r"([^\.]+)\.html",self.uri)
      if template_match: return template_match.group(1)
      return None
      
    def error(self,msg=''):
      self.out += "(ERR) %s"%msg
      log("(ERR) %s"%msg)
      raise DoneException
    
    def ok(self,msg=''):
      self.out += '(OK) %s'%msg
      raise DoneException
    
def handler(req):
    """flot.es handler routine.
        Interrupts Apache handlers in order to provide custom flot.es behaviour.
        ie) lazy creation of non-existent html files instead of 404 errors."""
    R=Responder(req)
    try:
      R.respond()
      assert(false)
    except DoneException:
      R.cleanup()     
      
    return apache.OK

def authenhandler(req):
    #req.add_common_vars()
    #ENV = req.subprocess_env
    #host = ENV['HTTP_HOST']
    #path = ENV['SCRIPT_NAME']
    #path += C_lookup(ENV,'PATH_INFO') or ''
    
    #log(req.subprocess_env.get('SERVER_SOFTWARE'))

    #log("AUTH for %s on %s"%(path,host))
    
    if req.get_basic_auth_pw() == "proto":
      return apache.OK
    return apache.HTTP_UNAUTHORIZED

