#@+leo-ver=4-thin
#@+node:EKR.20040517080250.1:@thin mod_http.py
"""
A minimal http plugin for LEO, based on AsyncHttpServer.py.
Based on the asyncore / asynchat framework.
"""
#@@language python
#@@tabwidth -4
# From http://home.pacbell.net/bwmulder/python/Leo/HttpPlugin.leo
# See also, the related script from the Python Cookbook:
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/259148
__version__ = "0.9"
#@<< how to use this plugin >>
#@+node:EKR.20040517080250.2:<< how to use this plugin >>
#@+at
# Use this plugin is as follows:
#
# 1. Start Leo with the plugin enabled. You will see a purple message that
# says something like:
#
# "http serving enabled on port 8080, version 0.9"
#
# 2. Start a web browser, and enter the following url: http://localhost:8080/
#
# You will see a a "top" level page containing one link for every open .leo
# file. Start clicking :-)
#
# You can use the browser's refresh button to update the top-level view in the
# browser after you have opened or closed files.
#@-at
#@nonl
#@-node:EKR.20040517080250.2:<< how to use this plugin >>
#@nl
#@<< imports >>
#@+node:EKR.20040517080250.3:<< imports >>
import leoGlobals as g
import leoPlugins
import asynchat
import asyncore
import cgi
import ConfigParser
import cStringIO
import exceptions
from StringIO import StringIO
import os
import posixpath
import select
import shutil
import SimpleHTTPServer
import socket
import sys
import time
import urllib
import urlparse
#@nonl
#@-node:EKR.20040517080250.3:<< imports >>
#@nl
sockets_to_close = []
#@+others
#@+node:EKR.20040517080250.4:class delayedSocketStream
class delayedSocketStream(asyncore.dispatcher_with_send):
#@ @+others
#@+node:EKR.20040517080250.5:__init__
def __init__(self,sock):
self.socket=sock
self.socket.setblocking(0)
self.closed=1 # compatibility with SocketServer
self.buffer = []
#@-node:EKR.20040517080250.5:__init__
#@+node:EKR.20040517080250.6:write
def write(self,data):
self.buffer.append(data)
#@-node:EKR.20040517080250.6:write
#@+node:EKR.20040517080250.7:initiate_sending
def initiate_sending(self):
self.out_buffer = ''.join(self.buffer)
del self.buffer
self.set_socket(self.socket, None)
self.socket.setblocking(0)
self.connected = 1
try:
self.addr = self.socket.getpeername()
except socket.error:
# The addr isn't crucial
pass
#@-node:EKR.20040517080250.7:initiate_sending
#@+node:EKR.20040517080250.8:handle_read
def handle_read(self):
pass
#@nonl
#@-node:EKR.20040517080250.8:handle_read
#@+node:EKR.20040517080250.9:writable
def writable(self):
result = (not self.connected) or len(self.out_buffer)
if not result:
sockets_to_close.append(self)
return result
#@-node:EKR.20040517080250.9:writable
#@-others
#@-node:EKR.20040517080250.4:class delayedSocketStream
#@+node:EKR.20040517080250.10:class nodeNotFound
class nodeNotFound(Exception):
pass
#@nonl
#@-node:EKR.20040517080250.10:class nodeNotFound
#@+node:EKR.20040517080250.11:class escaped_StringIO
class escaped_StringIO(StringIO):
#@ @+others
#@+node:EKR.20040517080250.12:write_escaped
def write_escaped(self, s):
s = s.replace('&', "&")
s = s.replace('<', "<")
s = s.replace('>', ">")
# is there a more elegant way to do this?
# Replaces blanks with id they are in
# the beginning of the line.
lines = s.split('\n')
result = []
blank = chr(32)
for line in lines:
if line.startswith(blank):
resultchars = []
startline = True
for char in line:
if char == blank:
if startline:
resultchars.append(' ')
else:
resultchars.append(' ')
else:
startline = False
resultchars.append(char)
result.append(''.join(resultchars))
else:
result.append(line)
s = '\n'.join(result)
s = s.replace('\n', '
')
s = s.replace(chr(9), ' ')
StringIO.write(self, s)
#@nonl
#@-node:EKR.20040517080250.12:write_escaped
#@-others
#@nonl
#@-node:EKR.20040517080250.11:class escaped_StringIO
#@+node:EKR.20040517080250.13:class RequestHandler
class RequestHandler(
asynchat.async_chat,
SimpleHTTPServer.SimpleHTTPRequestHandler):
#@ @+others
#@+node:EKR.20040517080250.14:__init__
def __init__(self,conn,addr,server):
asynchat.async_chat.__init__(self,conn)
self.client_address=addr
self.connection=conn
self.server=server
self.wfile = delayedSocketStream(self.socket)
# sets the terminator : when it is received, this means that the
# http request is complete ; control will be passed to
# self.found_terminator
self.set_terminator ('\r\n\r\n')
self.buffer=cStringIO.StringIO()
self.found_terminator=self.handle_request_line
#@-node:EKR.20040517080250.14:__init__
#@+node:EKR.20040517080250.15:copyfile
def copyfile(self, source, outputfile):
"""Copy all data between two file objects.
The SOURCE argument is a file object open for reading
(or anything with a read() method) and the DESTINATION
argument is a file object open for writing (or
anything with a write() method).
The only reason for overriding this would be to change
the block size or perhaps to replace newlines by CRLF
-- note however that this the default server uses this
to copy binary data as well.
"""
shutil.copyfileobj(source, outputfile, length=255)
#@-node:EKR.20040517080250.15:copyfile
#@+node:EKR.20040517080250.16:log_message
def log_message(self, format, *args):
"""Log an arbitrary message.
This is used by all other logging functions. Override
it if you have specific logging wishes.
The first argument, FORMAT, is a format string for the
message to be logged. If the format string contains
any % escapes requiring parameters, they should be
specified as subsequent arguments (it's just like
printf!).
The client host and current date/time are prefixed to
every message.
"""
message = "%s - - [%s] %s\n" % (
self.address_string(),
self.log_date_time_string(),
format%args)
g.es(message)
#@-node:EKR.20040517080250.16:log_message
#@+node:EKR.20040517080250.17:collect_incoming_data
def collect_incoming_data(self,data):
"""Collects the data arriving on the connexion"""
self.buffer.write(data)
#@-node:EKR.20040517080250.17:collect_incoming_data
#@+node:EKR.20040517080250.18:prepare_POST
def prepare_POST(self):
"""Prepare to read the request body"""
bytesToRead = int(self.headers.getheader('content-length'))
# set terminator to length (will read bytesToRead bytes)
self.set_terminator(bytesToRead)
self.buffer=cStringIO.StringIO()
# control will be passed to a new found_terminator
self.found_terminator=self.handle_post_data
#@-node:EKR.20040517080250.18:prepare_POST
#@+node:EKR.20040517080250.19:handle_post_data
def handle_post_data(self):
"""Called when a POST request body has been read"""
self.rfile=cStringIO.StringIO(self.buffer.getvalue())
self.do_POST()
self.finish()
#@-node:EKR.20040517080250.19:handle_post_data
#@+node:EKR.20040517080250.20:Leo specific code
#@+node:EKR.20040517080250.21:add_leo_links
def add_leo_links(self, window, node, f):
"""
Given a node 'node', add links to:
The next sibling, if any.
the next node.
the parent.
The children, if any.
"""
# Collecting the navigational links.
if node is None:
# top level
child = window.c.rootVnode()
children = [child]
next = child.next()
while next:
child = next
children.append(child)
next = child.next()
nodename = window.shortFileName()
else:
nodename = node.headString()
threadNext = node.threadNext()
sibling = node.next()
parent = node.parent()
children = []
firstChild = node.firstChild()
if firstChild:
child = firstChild
while child:
children.append(child)
child = child.next()
if threadNext is not None:
self.create_leo_reference(window, threadNext, "next", f)
f.write("
")
if sibling is not None:
self.create_leo_reference(window, sibling, "next Sibling", f)
f.write("
")
if parent is None:
self.create_href("/", "Top level", f)
else:
self.create_leo_reference(window, parent, "Up", f)
f.write("
")
if children:
f.write("