viewvc-4intranet/standalone.py

633 lines
25 KiB
Python
Executable File

#!/usr/bin/env python
# $Id$
# vim:sw=4:ts=4:et:nowrap
# [Emacs: -*- python -*-]
#
# Copyright (C) 1999-2002 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# This file: Peter Funk, Oldenburger Str.86, 27777 Ganderkesee, Germany
# ViewCVS project: Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# Note: this module is designed to deploy instantly and run under any
# version of Python from 1.5 and up. That's why some 2.0 features
# (like string methods) are conspicuously avoided.
# XXX Security issues?
"""Run "standalone.py -p <port>" to start an HTTP server on a given port
on the local machine to generate ViewCVS web pages.
"""
__author__ = "Peter Funk <pf@artcom-gmbh.de>"
__date__ = "11 November 2001"
__version__ = "$Revision$"
__credits__ = """Guido van Rossum, for an excellent programming language.
Greg Stein, for writing ViewCVS in the first place.
Ka-Ping Yee, for the GUI code and the framework stolen from pydoc.py.
"""
# INSTALL-TIME CONFIGURATION
#
# This value will be set during the installation process. During
# development, it will remain None.
#
LIBRARY_DIR = None
import sys
import os
import stat
import string
import urllib
import rfc822
import socket
import select
import BaseHTTPServer
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path[:0] = ['lib']
import sapi
import viewcvs
import compat; compat.for_standalone()
if viewcvs.CONF_PATHNAME is None:
viewcvs.g_install_dir = ''
class Options:
port = 7467 # default TCP/IP port used for the server
start_gui = 0 # No GUI unless requested.
repositories = {} # use default repositories specified in config
if sys.platform == 'mac':
host = '127.0.0.1'
else:
host = 'localhost'
# --- web browser interface: ----------------------------------------------
class StandaloneServer(sapi.CgiServer):
def __init__(self, handler):
sapi.CgiServer.__init__(self, inheritableOut = sys.platform != "win32")
self.handler = handler
def header(self, content_type='text/html', status=None):
if not self.headerSent:
self.headerSent = 1
if status is None:
statusCode = 200
statusText = 'OK'
else:
p = string.find(status, ' ')
if p < 0:
statusCode = int(status)
statusText = ''
else:
statusCode = int(status[:p])
statusText = status[p+1:]
self.handler.send_response(statusCode, statusText)
self.handler.send_header("Content-type", content_type)
for (name, value) in self.headers:
self.handler.send_header(name, value)
self.handler.end_headers()
def serve(host, port, callback=None):
"""start a HTTP server on the given port. call 'callback' when the
server is ready to serve"""
class ViewCVS_Handler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
"""Serve a GET request."""
if not self.path or self.path == "/":
self.redirect()
elif self.is_viewcvs():
try:
self.run_viewcvs()
except IOError:
# ignore IOError: [Errno 32] Broken pipe
pass
else:
self.send_error(404)
def do_POST(self):
"""Serve a POST request."""
if self.is_viewcvs():
self.run_viewcvs()
else:
self.send_error(501, "Can only POST to viewcvs")
def is_viewcvs(self):
"""Check whether self.path matches the hardcoded ScriptAlias
/viewcvs"""
if self.path[:8] == "/viewcvs":
return 1
return 0
def redirect(self):
"""redirect the browser to the viewcvs URL"""
self.send_response(301, "moved (redirection follows)")
self.send_header("Content-type", "text/html")
self.send_header("Location", self.server.url + 'viewcvs/')
self.end_headers()
self.wfile.write("""<html>
<head>
<meta http-equiv="refresh" content="1; URL=%s">
</head>
<body>
<h1>Redirection to <a href="%s">ViewCVS</a></h1>
Wait a second. You will be automatically redirected to <b>ViewCVS</b>.
If this doesn't work, please click on the link above.
</body>
</html>
""" % tuple([self.server.url + "viewcvs/"]*2))
def run_viewcvs(self):
"""This is a quick and dirty cut'n'rape from Pythons
standard library module CGIHTTPServer."""
scriptname = "/viewcvs"
assert self.path[:8] == scriptname
viewcvs_url, rest = self.server.url[:-1]+scriptname, self.path[8:]
i = string.rfind(rest, '?')
if i >= 0:
rest, query = rest[:i], rest[i+1:]
else:
query = ''
# sys.stderr.write("Debug: '"+scriptname+"' '"+rest+"' '"+query+"'\n")
env = os.environ
# Since we're going to modify the env in the parent, provide empty
# values to override previously set values
for k in env.keys():
if k[:5] == 'HTTP_':
del env[k]
for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
'HTTP_USER_AGENT', 'HTTP_COOKIE'):
if env.has_key(k):
env[k] = ""
# XXX Much of the following could be prepared ahead of time!
env['SERVER_SOFTWARE'] = self.version_string()
env['SERVER_NAME'] = self.server.server_name
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
env['SERVER_PROTOCOL'] = self.protocol_version
env['SERVER_PORT'] = str(self.server.server_port)
env['REQUEST_METHOD'] = self.command
uqrest = urllib.unquote(rest)
env['PATH_INFO'] = uqrest
env['SCRIPT_NAME'] = scriptname
if query:
env['QUERY_STRING'] = query
host = self.address_string()
if host != self.client_address[0]:
env['REMOTE_HOST'] = host
env['REMOTE_ADDR'] = self.client_address[0]
# AUTH_TYPE
# REMOTE_USER
# REMOTE_IDENT
if self.headers.typeheader is None:
env['CONTENT_TYPE'] = self.headers.type
else:
env['CONTENT_TYPE'] = self.headers.typeheader
length = self.headers.getheader('content-length')
if length:
env['CONTENT_LENGTH'] = length
accept = []
for line in self.headers.getallmatchingheaders('accept'):
if line[:1] in string.whitespace:
accept.append(string.strip(line))
else:
accept = accept + string.split(line[7:], ',')
env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
ua = self.headers.getheader('user-agent')
if ua:
env['HTTP_USER_AGENT'] = ua
modified = self.headers.getheader('if-modified-since')
if modified:
env['HTTP_IF_MODIFIED_SINCE'] = modified
etag = self.headers.getheader('if-none-match')
if etag:
env['HTTP_IF_NONE_MATCH'] = etag
# XXX Other HTTP_* headers
decoded_query = string.replace(query, '+', ' ')
# Preserve state, because we execute script in current process:
save_argv = sys.argv
save_stdin = sys.stdin
save_stdout = sys.stdout
save_stderr = sys.stderr
# For external tools like enscript we also need to redirect
# the real stdout file descriptor. (On windows, reassigning the
# sys.stdout variable is sufficient because pipe_cmds makes it
# the standard output for child processes.)
if sys.platform != "win32": save_realstdout = os.dup(1)
try:
try:
sys.stdout = self.wfile
if sys.platform != "win32":
os.dup2(self.wfile.fileno(), 1)
sys.stdin = self.rfile
viewcvs.main(StandaloneServer(self))
finally:
sys.argv = save_argv
sys.stdin = save_stdin
sys.stdout.flush()
if sys.platform != "win32":
os.dup2(save_realstdout, 1)
os.close(save_realstdout)
sys.stdout = save_stdout
sys.stderr = save_stderr
except SystemExit, status:
self.log_error("ViewCVS exit status %s", str(status))
else:
self.log_error("ViewCVS exited ok")
class ViewCVS_Server(BaseHTTPServer.HTTPServer):
def __init__(self, host, port, callback):
self.address = (host, port)
self.url = 'http://%s:%d/' % (host, port)
self.callback = callback
BaseHTTPServer.HTTPServer.__init__(self, self.address,
self.handler)
def serve_until_quit(self):
self.quit = 0
while not self.quit:
rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
if rd:
self.handle_request()
def server_activate(self):
BaseHTTPServer.HTTPServer.server_activate(self)
if self.callback:
self.callback(self)
def server_bind(self):
# set SO_REUSEADDR (if available on this platform)
if hasattr(socket, 'SOL_SOCKET') \
and hasattr(socket, 'SO_REUSEADDR'):
self.socket.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
BaseHTTPServer.HTTPServer.server_bind(self)
ViewCVS_Server.handler = ViewCVS_Handler
try:
# XXX Move this code out of this function.
# Early loading of configuration here. Used to
# allow tinkering with some configuration settings:
viewcvs.handle_config()
if options.repositories:
viewcvs.cfg.general.default_root = "Development"
viewcvs.cfg.general.cvs_roots.update(options.repositories)
elif viewcvs.cfg.general.cvs_roots.has_key("Development") and \
not os.path.isdir(viewcvs.cfg.general.cvs_roots["Development"]):
sys.stderr.write("*** No repository found. Please use the -r option.\n")
sys.stderr.write(" Use --help for more info.\n")
raise KeyboardInterrupt # Hack!
os.close(0) # To avoid problems with shell job control
# always use default docroot location
viewcvs.cfg.options.docroot = None
# if cvsnt isn't found, fall back to rcs
if (viewcvs.cfg.conf_path is None
and viewcvs.cfg.general.cvsnt_exe_path):
import popen
cvsnt_works = 0
try:
fp = popen.popen(viewcvs.cfg.general.cvsnt_exe_path,
['--version'], 'rt')
try:
while 1:
line = fp.readline()
if not line: break
if string.find(line, "Concurrent Versions System (CVSNT)")>=0:
cvsnt_works = 1
while fp.read(4096):
pass
break
finally:
fp.close()
except:
pass
if not cvsnt_works:
viewcvs.cfg.cvsnt_exe_path = None
ViewCVS_Server(host, port, callback).serve_until_quit()
except (KeyboardInterrupt, select.error):
pass
print 'server stopped'
# --- graphical interface: --------------------------------------------------
def nogui(missing_module):
sys.stderr.write(
"Sorry! Your Python was compiled without the %s module"%missing_module+
" enabled.\nI'm unable to run the GUI part. Please omit the '-g'\n"+
"and '--gui' options or install another Python interpreter.\n")
raise SystemExit, 1
def gui(host, port):
"""Graphical interface (starts web server and pops up a control window)."""
class GUI:
def __init__(self, window, host, port):
self.window = window
self.server = None
self.scanner = None
try:
import Tkinter
except ImportError:
nogui("Tkinter")
self.server_frm = Tkinter.Frame(window)
self.title_lbl = Tkinter.Label(self.server_frm,
text='Starting server...\n ')
self.open_btn = Tkinter.Button(self.server_frm,
text='open browser', command=self.open, state='disabled')
self.quit_btn = Tkinter.Button(self.server_frm,
text='quit serving', command=self.quit, state='disabled')
self.window.title('ViewCVS standalone')
self.window.protocol('WM_DELETE_WINDOW', self.quit)
self.title_lbl.pack(side='top', fill='x')
self.open_btn.pack(side='left', fill='x', expand=1)
self.quit_btn.pack(side='right', fill='x', expand=1)
# Early loading of configuration here. Used to
# allow tinkering with configuration settings through the gui:
viewcvs.handle_config()
if not LIBRARY_DIR:
viewcvs.cfg.options.cvsgraph_conf = "../cgi/cvsgraph.conf.dist"
self.options_frm = Tkinter.Frame(window)
# cvsgraph toggle:
self.cvsgraph_ivar = Tkinter.IntVar()
self.cvsgraph_ivar.set(viewcvs.cfg.options.use_cvsgraph)
self.cvsgraph_toggle = Tkinter.Checkbutton(self.options_frm,
text="enable cvsgraph (needs binary)", var=self.cvsgraph_ivar,
command=self.toggle_use_cvsgraph)
self.cvsgraph_toggle.pack(side='top', anchor='w')
# enscript toggle:
self.enscript_ivar = Tkinter.IntVar()
self.enscript_ivar.set(viewcvs.cfg.options.use_enscript)
self.enscript_toggle = Tkinter.Checkbutton(self.options_frm,
text="enable enscript (needs binary)", var=self.enscript_ivar,
command=self.toggle_use_enscript)
self.enscript_toggle.pack(side='top', anchor='w')
# show_subdir_lastmod toggle:
self.subdirmod_ivar = Tkinter.IntVar()
self.subdirmod_ivar.set(viewcvs.cfg.options.show_subdir_lastmod)
self.subdirmod_toggle = Tkinter.Checkbutton(self.options_frm,
text="show subdir last mod (dir view)", var=self.subdirmod_ivar,
command=self.toggle_subdirmod)
self.subdirmod_toggle.pack(side='top', anchor='w')
# use_re_search toggle:
self.useresearch_ivar = Tkinter.IntVar()
self.useresearch_ivar.set(viewcvs.cfg.options.use_re_search)
self.useresearch_toggle = Tkinter.Checkbutton(self.options_frm,
text="allow regular expr search", var=self.useresearch_ivar,
command=self.toggle_useresearch)
self.useresearch_toggle.pack(side='top', anchor='w')
# use_localtime toggle:
self.use_localtime_ivar = Tkinter.IntVar()
self.use_localtime_ivar.set(viewcvs.cfg.options.use_localtime)
self.use_localtime_toggle = Tkinter.Checkbutton(self.options_frm,
text="use localtime (instead of UTC)",
var=self.use_localtime_ivar,
command=self.toggle_use_localtime)
self.use_localtime_toggle.pack(side='top', anchor='w')
# use_pagesize integer var:
self.usepagesize_lbl = Tkinter.Label(self.options_frm,
text='Paging (number of items per page, 0 disables):')
self.usepagesize_lbl.pack(side='top', anchor='w')
self.use_pagesize_ivar = Tkinter.IntVar()
self.use_pagesize_ivar.set(viewcvs.cfg.options.use_pagesize)
self.use_pagesize_entry = Tkinter.Entry(self.options_frm,
width=10, textvariable=self.use_pagesize_ivar)
self.use_pagesize_entry.bind('<Return>', self.set_use_pagesize)
self.use_pagesize_entry.pack(side='top', anchor='w')
# directory view template:
self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
text='Chooose HTML Template for the Directory pages:')
self.dirtemplate_lbl.pack(side='top', anchor='w')
self.dirtemplate_svar = Tkinter.StringVar()
self.dirtemplate_svar.set(viewcvs.cfg.templates.directory)
self.dirtemplate_entry = Tkinter.Entry(self.options_frm,
width = 40, textvariable=self.dirtemplate_svar)
self.dirtemplate_entry.bind('<Return>', self.set_templates_directory)
self.dirtemplate_entry.pack(side='top', anchor='w')
self.templates_dir = Tkinter.Radiobutton(self.options_frm,
text="directory.ezt", value="templates/directory.ezt",
var=self.dirtemplate_svar, command=self.set_templates_directory)
self.templates_dir.pack(side='top', anchor='w')
self.templates_dir_alt = Tkinter.Radiobutton(self.options_frm,
text="dir_alternate.ezt", value="templates/dir_alternate.ezt",
var=self.dirtemplate_svar, command=self.set_templates_directory)
self.templates_dir_alt.pack(side='top', anchor='w')
# log view template:
self.logtemplate_lbl = Tkinter.Label(self.options_frm,
text='Chooose HTML Template for the Log pages:')
self.logtemplate_lbl.pack(side='top', anchor='w')
self.logtemplate_svar = Tkinter.StringVar()
self.logtemplate_svar.set(viewcvs.cfg.templates.log)
self.logtemplate_entry = Tkinter.Entry(self.options_frm,
width = 40, textvariable=self.logtemplate_svar)
self.logtemplate_entry.bind('<Return>', self.set_templates_log)
self.logtemplate_entry.pack(side='top', anchor='w')
self.templates_log = Tkinter.Radiobutton(self.options_frm,
text="log.ezt", value="templates/log.ezt",
var=self.logtemplate_svar, command=self.set_templates_log)
self.templates_log.pack(side='top', anchor='w')
self.templates_log_table = Tkinter.Radiobutton(self.options_frm,
text="log_table.ezt", value="templates/log_table.ezt",
var=self.logtemplate_svar, command=self.set_templates_log)
self.templates_log_table.pack(side='top', anchor='w')
# query view template:
self.querytemplate_lbl = Tkinter.Label(self.options_frm,
text='Template for the database query page:')
self.querytemplate_lbl.pack(side='top', anchor='w')
self.querytemplate_svar = Tkinter.StringVar()
self.querytemplate_svar.set(viewcvs.cfg.templates.query)
self.querytemplate_entry = Tkinter.Entry(self.options_frm,
width = 40, textvariable=self.querytemplate_svar)
self.querytemplate_entry.bind('<Return>', self.set_templates_query)
self.querytemplate_entry.pack(side='top', anchor='w')
self.templates_query = Tkinter.Radiobutton(self.options_frm,
text="query.ezt", value="templates/query.ezt",
var=self.querytemplate_svar, command=self.set_templates_query)
self.templates_query.pack(side='top', anchor='w')
# pack and set window manager hints:
self.server_frm.pack(side='top', fill='x')
self.options_frm.pack(side='top', fill='x')
self.window.update()
self.minwidth = self.window.winfo_width()
self.minheight = self.window.winfo_height()
self.expanded = 0
self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
self.window.wm_minsize(self.minwidth, self.minheight)
try:
import threading
except ImportError:
nogui("thread")
threading.Thread(target=serve,
args=(host, port, self.ready)).start()
def toggle_use_cvsgraph(self, event=None):
viewcvs.cfg.options.use_cvsgraph = self.cvsgraph_ivar.get()
def toggle_use_enscript(self, event=None):
viewcvs.cfg.options.use_enscript = self.enscript_ivar.get()
def toggle_use_localtime(self, event=None):
viewcvs.cfg.options.use_localtime = self.use_localtime_ivar.get()
def toggle_subdirmod(self, event=None):
viewcvs.cfg.options.show_subdir_lastmod = self.subdirmod_ivar.get()
def toggle_useresearch(self, event=None):
viewcvs.cfg.options.use_re_search = self.useresearch_ivar.get()
def set_use_pagesize(self, event=None):
viewcvs.cfg.options.use_pagesize = self.use_pagesize_ivar.get()
def set_templates_log(self, event=None):
viewcvs.cfg.templates.log = self.logtemplate_svar.get()
def set_templates_directory(self, event=None):
viewcvs.cfg.templates.directory = self.dirtemplate_svar.get()
def set_templates_query(self, event=None):
viewcvs.cfg.templates.query = self.querytemplate_svar.get()
def ready(self, server):
"""used as callback parameter to the serve() function"""
self.server = server
self.title_lbl.config(
text='ViewCVS standalone server at\n' + server.url)
self.open_btn.config(state='normal')
self.quit_btn.config(state='normal')
def open(self, event=None, url=None):
"""opens a browser window on the local machine"""
url = url or self.server.url
try:
import webbrowser
webbrowser.open(url)
except ImportError: # pre-webbrowser.py compatibility
if sys.platform == 'win32':
os.system('start "%s"' % url)
elif sys.platform == 'mac':
try:
import ic
ic.launchurl(url)
except ImportError: pass
else:
rc = os.system('netscape -remote "openURL(%s)" &' % url)
if rc: os.system('netscape "%s" &' % url)
def quit(self, event=None):
if self.server:
self.server.quit = 1
self.window.quit()
import Tkinter
try:
gui = GUI(Tkinter.Tk(), host, port)
Tkinter.mainloop()
except KeyboardInterrupt:
pass
# --- command-line interface: ----------------------------------------------
def cli(argv):
"""Command-line interface (looks at argv to decide what to do)."""
import getopt
class BadUsage(Exception): pass
try:
opts, args = getopt.getopt(argv[1:], 'gp:r:h:',
['gui', 'port=', 'repository='])
for opt, val in opts:
if opt in ('-g', '--gui'):
options.start_gui = 1
elif opt in ('-r', '--repository'):
if options.repositories: # option may be used more than once:
num = len(options.repositories.keys())+1
symbolic_name = "Repository"+str(num)
options.repositories[symbolic_name] = val
else:
options.repositories["Development"] = val
elif opt in ('-p', '--port'):
try:
options.port = int(val)
except ValueError:
raise BadUsage
elif opt in ('-h', '--host'):
options.host = val
if options.start_gui:
gui(options.host, options.port)
return
elif options.port:
def ready(server):
print 'server ready at %s' % server.url
serve(options.host, options.port, ready)
return
raise BadUsage
except (getopt.error, BadUsage):
cmd = sys.argv[0]
port = options.port
host = options.host
print """ViewCVS standalone - a simple standalone HTTP-Server
Usage: %(cmd)s [ <options> ]
Available Options:
-h <host> or --host=<host>
Start the HTTP server listening on <host>.
Defaults to %(host)s. You need to provide the
hostname, if you want to access the standalone server
from remote.
-p <port> or --port=<port>
Start an HTTP server on the given port.
Default port is %(port)d.
-r <path> or --repository=<path>
Specify path for a CVS repository. May be used more than once.
If you don't have a CVS repository at /home/cvsroot you will need to
use this option or you have to install first and edit viewcvs.conf.
-g or --gui
Pop up a graphical interface for serving and testing ViewCVS.
Note: This requires you start %(cmd)s with a valid X11 display
connection on Unix/Linux systems.
""" % locals()
if __name__ == '__main__':
options = Options()
cli(sys.argv)