| 1 |
# twisted_goodies.simpleserver.http: |
|---|
| 2 |
# A virtual hosting twisted.web2 HTTP server that uses subdirectories for |
|---|
| 3 |
# virtual host content. Subdirectories can be python packages providing dynamic |
|---|
| 4 |
# content with a root resource object, or sources of static content. |
|---|
| 5 |
# |
|---|
| 6 |
# Copyright (C) 2006-2007 by Edwin A. Suominen, http://www.eepatents.com |
|---|
| 7 |
# |
|---|
| 8 |
# This program is free software; you can redistribute it and/or modify it under |
|---|
| 9 |
# the terms of the GNU General Public License as published by the Free Software |
|---|
| 10 |
# Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 11 |
# version. |
|---|
| 12 |
# |
|---|
| 13 |
# This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 14 |
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 15 |
# FOR A PARTICULAR PURPOSE. See the file COPYING for more details. |
|---|
| 16 |
# |
|---|
| 17 |
# You should have received a copy of the GNU General Public License along with |
|---|
| 18 |
# this program; if not, write to the Free Software Foundation, Inc., 51 |
|---|
| 19 |
# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
|---|
| 20 |
|
|---|
| 21 |
""" |
|---|
| 22 |
This file is what defines the root resource for U{foss.eepatents.com}, my free |
|---|
| 23 |
& open source projects site. A L{resources.WSGIResource} is used to provide |
|---|
| 24 |
access to Trac via an WSGI gateway. |
|---|
| 25 |
""" |
|---|
| 26 |
|
|---|
| 27 |
import os.path |
|---|
| 28 |
from textwrap import TextWrapper |
|---|
| 29 |
from twisted.web2 import static |
|---|
| 30 |
from twisted.python.log import msg as log |
|---|
| 31 |
|
|---|
| 32 |
from twisted_goodies.simpleserver.http.resources import \ |
|---|
| 33 |
T, StanResource, DownloadResource, APIDocResource, TracResource |
|---|
| 34 |
|
|---|
| 35 |
|
|---|
| 36 |
TRAC_DIR = '/var/trac/foss' |
|---|
| 37 |
PROJECTS = ( |
|---|
| 38 |
("AsynCluster", |
|---|
| 39 |
""" |
|---|
| 40 |
Asynchronous operation of a computing cluster with a Node Display |
|---|
| 41 |
Manager (NDM) that allows regular workstation usage of cluster |
|---|
| 42 |
nodes with computing jobs running behind the scenes. Includes |
|---|
| 43 |
evolutionary computing tools that make use of the asynchronous |
|---|
| 44 |
node processing. |
|---|
| 45 |
"""), |
|---|
| 46 |
|
|---|
| 47 |
("AsynQueue", |
|---|
| 48 |
""" |
|---|
| 49 |
Asynchronous task queueing based on the Twisted framework, with task |
|---|
| 50 |
prioritization and a powerful worker/manager interface. |
|---|
| 51 |
"""), |
|---|
| 52 |
|
|---|
| 53 |
("sAsync", |
|---|
| 54 |
""" |
|---|
| 55 |
SQLAlchemy done asynchronously, with enhancements like persistent |
|---|
| 56 |
dictionaries, arrays, and graphs, all based on an access broker |
|---|
| 57 |
that conveniently manages database access, table setup, and |
|---|
| 58 |
deferred transactions. |
|---|
| 59 |
"""), |
|---|
| 60 |
|
|---|
| 61 |
("Twisted-Goodies", |
|---|
| 62 |
""" |
|---|
| 63 |
Miscellaneous add-ons and improvements to the separately |
|---|
| 64 |
maintained and licensed Twisted framework, including asynchronous |
|---|
| 65 |
task queueing, HTTP fetching, and simple HTTP and POP3 servers. |
|---|
| 66 |
""" |
|---|
| 67 |
), |
|---|
| 68 |
|
|---|
| 69 |
("WinDictator", |
|---|
| 70 |
""" |
|---|
| 71 |
Gives desktop users of the GNU/Linux operating system a |
|---|
| 72 |
convenient way to make use of speech recognition software that |
|---|
| 73 |
runs only under the Microsoft Windows operating system. Your |
|---|
| 74 |
dictation software runs on a separate computer (virtual or real) |
|---|
| 75 |
that is networked to your Linux- based desktop. Keystrokes of |
|---|
| 76 |
dictated text are transmitted via a TCP connection and entered |
|---|
| 77 |
via faked X events. |
|---|
| 78 |
"""), |
|---|
| 79 |
) |
|---|
| 80 |
|
|---|
| 81 |
|
|---|
| 82 |
class DisabledResource(StanResource): |
|---|
| 83 |
addSlash = False |
|---|
| 84 |
|
|---|
| 85 |
def __init__(self, whatDisabled): |
|---|
| 86 |
self.whatDisabled = whatDisabled |
|---|
| 87 |
self.title = "%s Temporarily Disabled" % whatDisabled |
|---|
| 88 |
|
|---|
| 89 |
def render(self, request): |
|---|
| 90 |
return [ |
|---|
| 91 |
T.h2[self.title], |
|---|
| 92 |
T.p["Hopefully this should be fixed soon."]] |
|---|
| 93 |
|
|---|
| 94 |
|
|---|
| 95 |
class Resource(StanResource): |
|---|
| 96 |
""" |
|---|
| 97 |
A home page for all of the trac project names and paths supplied in the |
|---|
| 98 |
I{projects} dict |
|---|
| 99 |
""" |
|---|
| 100 |
addSlash = True |
|---|
| 101 |
wrapper = TextWrapper(break_long_words=False) |
|---|
| 102 |
projectURLProto = "/trac/%s/wiki" |
|---|
| 103 |
projectRepoProto = "/foss/projects/%s/trunk" |
|---|
| 104 |
|
|---|
| 105 |
def __init__(self, vhostPath): |
|---|
| 106 |
self.vhostPath = vhostPath |
|---|
| 107 |
self.apiDocResource = APIDocResource( |
|---|
| 108 |
vhostPath, self.projectURLProto, |
|---|
| 109 |
"file:///var/svn" + self.projectRepoProto) |
|---|
| 110 |
self.downloadResource = DownloadResource( |
|---|
| 111 |
vhostPath, self.projectURLProto, |
|---|
| 112 |
"svn://foss.eepatents.com" + self.projectRepoProto) |
|---|
| 113 |
self.title = "Ed Suominen's Free & Open Source Software Projects" |
|---|
| 114 |
self.style = { |
|---|
| 115 |
"row":\ |
|---|
| 116 |
{'padding':'0.3em', |
|---|
| 117 |
'margin-bottom':'1em', |
|---|
| 118 |
'margin-left':'0.5em', |
|---|
| 119 |
'margin-right':'2em', |
|---|
| 120 |
'background-color':'#FFFFC0'}, |
|---|
| 121 |
|
|---|
| 122 |
"project_link":\ |
|---|
| 123 |
{'font-size':'larger', |
|---|
| 124 |
'font-style':'bold', |
|---|
| 125 |
'margin-bottom':'2px'}, |
|---|
| 126 |
|
|---|
| 127 |
"project_description":{'margin':'2px'}, |
|---|
| 128 |
"header":{'margin':'0.5em'}, |
|---|
| 129 |
"footer":{'font-style':'oblique', 'margin-top':'0.5em'} |
|---|
| 130 |
} |
|---|
| 131 |
env = { |
|---|
| 132 |
'mod_python.subprocess_env':\ |
|---|
| 133 |
{'PYTHON_EGG_CACHE': os.path.join(TRAC_DIR, 'python-eggs')}} |
|---|
| 134 |
self.tr = TracResource(TRAC_DIR, env) |
|---|
| 135 |
|
|---|
| 136 |
def _possibleStatic(self, *pathParts): |
|---|
| 137 |
""" |
|---|
| 138 |
""" |
|---|
| 139 |
staticPath = os.path.join(self.vhostPath, *pathParts) |
|---|
| 140 |
if os.path.exists(staticPath): |
|---|
| 141 |
return static.File(staticPath) |
|---|
| 142 |
|
|---|
| 143 |
def render(self, request): |
|---|
| 144 |
""" |
|---|
| 145 |
Returns the body of my home page HTML. |
|---|
| 146 |
""" |
|---|
| 147 |
body = [T.img(src='/banner.png', alt=self.title), self.header(), T.hr] |
|---|
| 148 |
for projectName, doc in PROJECTS: |
|---|
| 149 |
projectDescription = " ".join( |
|---|
| 150 |
[x.strip() for x in doc.strip().split("\n")]) |
|---|
| 151 |
href = self.projectURLProto % projectName |
|---|
| 152 |
projectDiv = [ |
|---|
| 153 |
T.a(id="project_link", href=href)[projectName]] |
|---|
| 154 |
projectDiv.append( |
|---|
| 155 |
T.p(id="project_description")[projectDescription]) |
|---|
| 156 |
body.append(T.div(id="row")[projectDiv]) |
|---|
| 157 |
body.extend([T.hr, self.footer()]) |
|---|
| 158 |
return body |
|---|
| 159 |
|
|---|
| 160 |
def header(self): |
|---|
| 161 |
contents = [ |
|---|
| 162 |
"Built on the ", |
|---|
| 163 |
T.a(href='http://python.org')["Python"], |
|---|
| 164 |
" object-oriented programming language and the ", |
|---|
| 165 |
T.a(href='http://twistedmatrix.com')["Twisted"], |
|---|
| 166 |
" asynchronous processing framework." |
|---|
| 167 |
] |
|---|
| 168 |
return T.p(id="header")[contents] |
|---|
| 169 |
|
|---|
| 170 |
def footer(self): |
|---|
| 171 |
contents = """ |
|---|
| 172 |
The software featured here is licensed under the <a |
|---|
| 173 |
href="http://www.gnu.org/licenses/gpl.html"> |
|---|
| 174 |
GNU Public License</a>, which permits you to use it freely but requires |
|---|
| 175 |
that you accept the complete lack of <strong>any warranty</strong>, and |
|---|
| 176 |
agree to make whatever software you build with it equally free. If you |
|---|
| 177 |
want to use this software under different terms, <span |
|---|
| 178 |
style="font-style:normal">e.g.</span>, with a commercial license to put |
|---|
| 179 |
it under wraps in a proprietary product, feel free to contact me about |
|---|
| 180 |
it. Commercial licenses are available, for the most part, and in use. |
|---|
| 181 |
""" |
|---|
| 182 |
return T.p(id="footer")[self.html(contents)] |
|---|
| 183 |
|
|---|
| 184 |
def locateChild(self, request, segments): |
|---|
| 185 |
""" |
|---|
| 186 |
Returns either a Trac WSGI resource or a static file in my |
|---|
| 187 |
vhost subdirectory. |
|---|
| 188 |
""" |
|---|
| 189 |
root = segments[0] |
|---|
| 190 |
if not root: |
|---|
| 191 |
return self, () |
|---|
| 192 |
if root == 'trac': |
|---|
| 193 |
return self.tracChild(request, segments[1:]) |
|---|
| 194 |
if root in [x[0] for x in PROJECTS]: |
|---|
| 195 |
return self.tracChild(request, segments) |
|---|
| 196 |
return static.File(os.path.join(self.vhostPath, *segments)), () |
|---|
| 197 |
|
|---|
| 198 |
def tracChild(self, request, segments): |
|---|
| 199 |
""" |
|---|
| 200 |
Returns a resource within a trac project path. If a file is available |
|---|
| 201 |
in a corresponding path from the subdirectory 'trac', a static file |
|---|
| 202 |
resource to it is returned instead of a TracResource. |
|---|
| 203 |
""" |
|---|
| 204 |
def rootState(segmentList): |
|---|
| 205 |
root = segmentList[0] |
|---|
| 206 |
if len(segmentList) == 1: |
|---|
| 207 |
return root, True |
|---|
| 208 |
if len(segmentList) == 2 and segmentList[1] == '': |
|---|
| 209 |
del segmentList[1] |
|---|
| 210 |
return root, True |
|---|
| 211 |
return root, False |
|---|
| 212 |
|
|---|
| 213 |
def listOp(methodName, *args): |
|---|
| 214 |
uriList = request.uri.split("/") |
|---|
| 215 |
k = len(uriList) - len(segments) |
|---|
| 216 |
uriPreList, uriPostList = uriList[:k], uriList[k:] |
|---|
| 217 |
for thisList in (request.postpath, subSegments, uriPostList): |
|---|
| 218 |
getattr(thisList, methodName)(*args) |
|---|
| 219 |
request.uri = "/".join(uriPreList + uriPostList) |
|---|
| 220 |
|
|---|
| 221 |
projectName, subSegments = segments[0], list(segments[1:]) |
|---|
| 222 |
if subSegments and not subSegments[-1]: |
|---|
| 223 |
listOp('pop') |
|---|
| 224 |
if not subSegments: |
|---|
| 225 |
listOp('append', 'wiki') |
|---|
| 226 |
root, isAtRoot = rootState(subSegments) |
|---|
| 227 |
if False and root == 'changeset': |
|---|
| 228 |
# TODO, hopefully won't need to keep trying this for long |
|---|
| 229 |
return DisabledResource("Changeset Listings"), () |
|---|
| 230 |
if root == 'wiki' and len(subSegments) > 1: |
|---|
| 231 |
subRoot = subSegments[1] |
|---|
| 232 |
if subRoot.isalpha() and subRoot.islower(): |
|---|
| 233 |
listOp('remove', 'wiki') |
|---|
| 234 |
root = subRoot |
|---|
| 235 |
if root == 'api': |
|---|
| 236 |
return self.apiDocResource, [projectName] + subSegments[1:] |
|---|
| 237 |
if root == 'download': |
|---|
| 238 |
return self.downloadResource, [projectName] + subSegments[1:] |
|---|
| 239 |
if isAtRoot: |
|---|
| 240 |
if root == 'wiki': |
|---|
| 241 |
listOp('append', "Start_%s" % projectName) |
|---|
| 242 |
elif root == 'browser': |
|---|
| 243 |
listOp('extend', ['projects', projectName, 'trunk']) |
|---|
| 244 |
possibleResult = self._possibleStatic('trac', *subSegments) |
|---|
| 245 |
if possibleResult: |
|---|
| 246 |
return possibleResult, () |
|---|
| 247 |
return self.tr, subSegments |
|---|