Logo Search packages:      
Sourcecode: viewcvs version File versions  Download package

standalone.py

#!/usr/bin/env python
# $Id: standalone.py,v 1.34 2004/07/17 01:50:20 jhenstridge Exp $
# 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: 1.34 $"
__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 apache_icons
import compat; compat.for_standalone()


# calculate the mtime of the apache_icons module, for use when serving icons
mtime = os.stat(apache_icons.__file__)[stat.ST_MTIME]
icons_last_modified = rfc822.formatdate(mtime)
del mtime

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
            elif self.path[:7] == "/icons/":
                # XXX icon type should not be hardcoded to GIF:
                self.send_response(200)
                self.send_header("Content-type", "image/gif")
                self.send_header("Last-Modified", icons_last_modified)
                self.end_headers()
                apache_icons.serve_icon(self.path, self.wfile)
            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.close(1) 
                      assert os.dup(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.close(1)
                      assert os.dup(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 icon and docroot locations
        viewcvs.cfg.options.icons = "/icons"
        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)

Generated by  Doxygen 1.6.0   Back to index