1 # mod_python interface for Roundup Issue Tracker
2 #
3 # This module is free software, you may redistribute it
4 # and/or modify under the same terms as Python.
5 #
6 # This module provides Roundup Web User Interface
7 # using mod_python Apache module. Initially written
8 # with python 2.3.3, mod_python 3.1.3, roundup 0.7.0.
9 #
10 # This module operates with only one tracker
11 # and must be placed in the tracker directory.
12 #
14 import cgi
15 import os
16 import threading
18 from mod_python import apache
20 import roundup.instance
21 from roundup.cgi import TranslationService
23 class Headers(dict):
25 """HTTP headers wrapper"""
27 def __init__(self, headers):
28 """Initialize with `apache.table`"""
29 super(Headers, self).__init__(headers)
30 self.getheader = self.get
32 class Request(object):
34 """`apache.Request` object wrapper providing roundup client interface"""
36 def __init__(self, request):
37 """Initialize with `apache.Request` object"""
38 self._req = request
39 # .headers.getheader()
40 self.headers = Headers(request.headers_in)
41 # .wfile.write()
42 self.wfile = self._req
44 def start_response(self, headers, response):
45 self.send_response(response)
46 for key, value in headers:
47 self.send_header(key, value)
48 self.end_headers()
50 def send_response(self, response_code):
51 """Set HTTP response code"""
52 self._req.status = response_code
54 def send_header(self, name, value):
55 """Set output header"""
56 # value may be an instance of roundup.cgi.exceptions.HTTPException
57 value = str(value)
58 # XXX default content_type is "text/plain",
59 # and ain't overrided by "Content-Type" header
60 if name == "Content-Type":
61 self._req.content_type = value
62 else:
63 self._req.headers_out.add(name, value)
65 def end_headers(self):
66 """NOOP. There aint no such thing as 'end_headers' in mod_python"""
67 pass
70 def sendfile(self, filename, offset = 0, len = -1):
71 """Send 'filename' to the user."""
73 return self._req.sendfile(filename, offset, len)
75 __tracker_cache = {}
76 """A cache of optimized tracker instances.
78 The keys are strings giving the directories containing the trackers.
79 The values are tracker instances."""
81 __tracker_cache_lock = threading.Lock()
82 """A lock used to guard access to the cache."""
85 def handler(req):
86 """HTTP request handler"""
87 _options = req.get_options()
88 _home = _options.get("TrackerHome")
89 _lang = _options.get("TrackerLanguage")
90 _timing = _options.get("TrackerTiming", "no")
91 if _timing.lower() in ("no", "false"):
92 _timing = ""
93 _debug = _options.get("TrackerDebug", "no")
94 _debug = _debug.lower() not in ("no", "false")
96 # We do not need to take a lock here (the fast path) because reads
97 # from dictionaries are atomic.
98 if not _debug and _home in __tracker_cache:
99 _tracker = __tracker_cache[_home]
100 else:
101 if not (_home and os.path.isdir(_home)):
102 apache.log_error(
103 "PythonOption TrackerHome missing or invalid for %(uri)s"
104 % {'uri': req.uri})
105 return apache.HTTP_INTERNAL_SERVER_ERROR
106 if _debug:
107 _tracker = roundup.instance.open(_home, optimize=0)
108 else:
109 __tracker_cache_lock.acquire()
110 try:
111 # The tracker may have been added while we were acquiring
112 # the lock.
113 if _home in __tracker_cache:
114 _tracker = __tracker_cache[home]
115 else:
116 _tracker = roundup.instance.open(_home, optimize=1)
117 __tracker_cache[_home] = _tracker
118 finally:
119 __tracker_cache_lock.release()
120 # create environment
121 # Note: cookies are read from HTTP variables, so we need all HTTP vars
122 req.add_common_vars()
123 _env = dict(req.subprocess_env)
124 # XXX classname must be the first item in PATH_INFO. roundup.cgi does:
125 # path = string.split(os.environ.get('PATH_INFO', '/'), '/')
126 # os.environ['PATH_INFO'] = string.join(path[2:], '/')
127 # we just remove the first character ('/')
128 _env["PATH_INFO"] = req.path_info[1:]
129 if _timing:
130 _env["CGI_SHOW_TIMING"] = _timing
131 _form = cgi.FieldStorage(req, environ=_env)
132 _client = _tracker.Client(_tracker, Request(req), _env, _form,
133 translator=TranslationService.get_translation(_lang,
134 tracker_home=_home))
135 _client.main()
136 return apache.OK
138 # vim: set et sts=4 sw=4 :