# familysvr.py: the family intranet web server.

# 2/14/23
# After spending a good share of the last two months learning Node.js and 
# porting code from Python, I have found Python Twisted to be superior.
# One problem I have had is when I try to serve several songs on the same 
# web page, the browser (FireFox) will request six of them, then hang, like 
# it thinks it requested more but never looses track of what it has asked for 
# and never really gets them. The Chrome browser has no problem. It asks for 
# six, them asks for the next six and so on until it has them all. So it seems 
# like a browser bug. Only thing is, when I use Twisted/Python, FireFox just 
# gets them all. I tried many things, and for a long time, to find a NodeJS 
# fix, but the bottom line just seems to be that Twisted works and NodeJS 
# doesn't. Twisted just seems faster or something. Anyway, I'm out of time 
# and patience. I like NodeJS, but Twisted works. I don't have any problem 
# with Twisted or Python relative to NodeJS. I like NodeJS syntax better, but 
# that is not a reason to choose it. If Twisted/Python works, I need to just 
# use it. So, back to Python... This is not my first such experience with 
# Twisted. Twisted must have a really sharp developer team. When will I learn.
# I guess I will archive the NodeJS code for future reference.
#
# From here, we need to implement HTTP 2 w/secure sockets so that all resources 
# will be requested over one socket, a big improvement, but pain in the butt.

# Twisted web server is used:
#   Several useful Twisted code examples exist at this web site:
#   https://twistedmatrix.com/documents/current/web/howto/web-in-60
from twisted.web.server import Site
from twisted.web.static import File
from twisted.internet import reactor, endpoints
#from twisted.web.resource import NoResource
from twisted.web.tap import Options

from os import path

import TranClasses as TranClasses
#from dullpencil import DpTranslator


BASEDIR = '/home/wayne'

isRealTime = True



# FileTran, sub-class File to manage branches of the site and translate 
# .txt files.
# Sub-classing 'File' comes with baggage. As twisted handles each request, 
# it apparently calls the sub-classes __init__() using the exact same 
# parameters as File.__init__() needs. This makes it so we cannot 
# do an __init__() that is unique to this sub-class, thus limiting what 
# we can do with the sub-class. So we are doing work in __init__() that 
# might have been done using a more graceful sub-classing scheme.
#
# In terms of processing, here is what I understand:
# - We instantiate the first instance of FileTran. Somehow, "Site" uses that 
#   first instance as a template to instantiate more instances as needed.
# - When a request comes in, they sort through what should be the file name,
#   and test to see if that file really exists. They will only instantiate 
#   a new FileTran if the named file or directory really exists on disk. If 
#   the named entity does not exist, they reject it with "file not found".
# - If named entity does exist, they instantiate a FileTran, then call
#   render_GET() with the specified URI, so by the time render_GET() is called 
#   we have a valid file or directory name (self.path) and URI (request.uri).
#   In render_GET(), we want to handle only the "special" requests (.txt files 
#   or forms). Let them do all the default stuff like pushing out big static 
#   files. Somehow, Twisted's handling of big files is great. I have tried 
#   other legitimate frameworks (CherryPy, NodeJS) and they fail when it comes 
#   to handling large files or more than six concurrent requests from the 
#   same client. Twisted's default handling seems to just work.
# - If a request comes in specifying a directory and we pass the request on 
#   to default handling, they look for index files (self.indexNames) in the 
#   directory. If they find any, I believe they trigger a re-direct and the 
#   client then does a request for the index file.
class FileTran(File):

  # __init__(), called to create a new instance of FileTran each time 
  # a resource of this type is handled.
  # The parameters here are copied exactly from File.__init__().
  def __init__(self, path, defaultType="text/html", ignoredExts=(), registry=None, allowExt=0):
    File.__init__(self, path, defaultType, ignoredExts, registry, allowExt)
    
    s = path[len(BASEDIR)+1:]
    a = s.split('/')
    sitename = a[0]
    
    #print( 'FileTran__init__(): ', path, '('+sitename+')' )
    
    if sitename == 'family':
      self.dpTran = TranClasses.TranFamily( BASEDIR, isRealTime )
    elif sitename == 'localgrown':
      self.dpTran = TranClasses.TranLocalGrown( BASEDIR, isRealTime )
    elif sitename == 'moodswingsband':
      self.dpTran = TranClasses.TranMoodSwings( BASEDIR, isRealTime )
    elif sitename == 'bleachedblondes':
      self.dpTran = TranClasses.TranBlondes( BASEDIR, isRealTime )
    elif sitename == 'bleachedblondes-2020':
      self.dpTran = TranClasses.TranBlondes2020( BASEDIR, isRealTime )
    elif sitename == 'bleachedblondes-2021':
      self.dpTran = TranClasses.TranBlondes2021( BASEDIR, isRealTime )
    elif sitename == 'bleachedblondes-2022-23':
      self.dpTran = TranClasses.TranBlondes2023( BASEDIR, isRealTime )
    #elif sitename == 'wiki':
    #  self.dpTran = TranWiki( BASEDIR )      
    else:
      #self.dpTran = None
      self.dpTran = TranClasses.TranWiki( BASEDIR, isRealTime )      
      
    # This will make it look for a 'index.txt' file just like it looks 
    # for an 'index.html' file when a directory is given as the url.
    # If a directory does not contain index.html or index.txt, it displays the directory.
    if self.dpTran != None and 'index.txt' not in self.indexNames:
      #self.indexNames.append( 'index.txt' )
      #print( 'startuf:', self.indexNames )
      self.indexNames = ['index.ww']+['index.txt']+self.indexNames
      #self.indexNames = ['index','index.txt','index.html','index.htm','index.rpy']
      #self.indexNames = ['index','index.html','index.rpy','index.txt','index.html']
    #print( 'startup:', self.indexNames )

  #def getChild( self, name, request ):
  #  print( 'FileTran.getChild(): ', name, request.uri, self.path )
  #  return super(FileTran,self).getChild( name, request )
  
  def render_GET( self, request ):
    rfn = self.path

    #print( 'FileTran_render() ', request.uri, rfn )
    
    # Swim form processing: check for & process swim form
    sa = b'/swim/action.html?'
    #print( 'uri stuff: ', request.uri[:len(sa)] )
    #print( 'sa: ', sa )
    if request.uri[:len(sa)] == sa:
      # We have a swim form
      astr = request.uri.decode('ascii')
      a12 = astr.split('?')
      ap = a12[1].split('&')
      name = []
      value = []
      for i in range(len(ap)):
        nv = ap[i].split('=')
        name.append( nv[0] )
        value.append( nv[1] )
      dfn = "swimform.csv"
      t = not path.isfile( dfn )
      fin = open( dfn, "a" )
      print( 'Swim form:', value )
      if t:
        # Write value names on first line of file
        i = 0
        while True:
          fin.write( name[i] )
          i += 1
          if i >= len(name): break;
          fin.write( ", " )
        fin.write( '\n' )
      i = 0
      # Write values on subsequent lines of file
      while True:
        fin.write( value[i] )
        i += 1
        if i >= len(value): break;
        fin.write( ", " )
      fin.write( '\n' )
      fin.close()
    # - done with swim form

    if self.dpTran != None and path.isfile( rfn ):
      ty = ''
      i = rfn.rfind('.')
      if i != -1:
        ty = rfn[i:].lower()
      if ty == '.txt':
        s = self.dpTran.toHtml( rfn )
        return s.encode()
      elif ty == '.ww':
        s = self.dpTran.wwToHtml( rfn )
        return s.encode()
        
    # Else do default rendering
    return super(FileTran,self).render_GET(request)





rr = FileTran( BASEDIR )
factory = Site(rr)

# This endpoint works locally
#endpoint = endpoints.TCP4ServerEndpoint(reactor, 8888)
# Use this endpoint for local development (works like the previous one)
#endpoint = endpoints.serverFromString(reactor, "tcp:8080")
# Use this endpoint when using systemd to deploy on port 80
# (see /etc/systemd/system/familysvr.socket and familysvr.service)
endpoint = endpoints.serverFromString(reactor, "systemd:domain=INET:index=0")

endpoint.listen(factory)
reactor.run()

