source: deluge/ui/web/server.py@ e837493

2.0.x develop extjs4-port
Last change on this file since e837493 was e837493, checked in by Damien Churchill <damoc@gmail.com>, 16 years ago

add basic session support

  • Property mode set to 100644
File size: 11.9 KB
Line 
1#
2# deluge/ui/web/webui.py
3#
4# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
5#
6# Deluge is free software.
7#
8# You may redistribute it and/or modify it under the terms of the
9# GNU General Public License, as published by the Free Software
10# Foundation; either version 3 of the License, or (at your option)
11# any later version.
12#
13# deluge is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16# See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with deluge. If not, write to:
20# The Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor
22# Boston, MA 02110-1301, USA.
23#
24
25import os
26import time
27import locale
28import shutil
29import signal
30import urllib
31import gettext
32import hashlib
33import logging
34import tempfile
35import mimetypes
36import pkg_resources
37
38from twisted.application import service, internet
39from twisted.internet import reactor, error
40from twisted.web import http, resource, server, static
41
42from deluge import common, component
43from deluge.configmanager import ConfigManager
44from deluge.log import setupLogger, LOG as _log
45from deluge.ui import common as uicommon
46from deluge.ui.tracker_icons import TrackerIcons
47from deluge.ui.web.common import Template
48from deluge.ui.web.json_api import JSON, WebApi
49from deluge.ui.web.pluginmanager import PluginManager
50log = logging.getLogger(__name__)
51
52# Initialize gettext
53try:
54 locale.setlocale(locale.LC_ALL, "")
55 if hasattr(locale, "bindtextdomain"):
56 locale.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
57 if hasattr(locale, "textdomain"):
58 locale.textdomain("deluge")
59 gettext.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
60 gettext.textdomain("deluge")
61 gettext.install("deluge", pkg_resources.resource_filename("deluge", "i18n"))
62except Exception, e:
63 log.error("Unable to initialize gettext/locale: %s", e)
64
65_ = gettext.gettext
66
67current_dir = os.path.dirname(__file__)
68
69CONFIG_DEFAULTS = {
70 "port": 8112,
71 "theme": "slate",
72 "pwd_salt": "16f65d5c79b7e93278a28b60fed2431e",
73 "pwd_md5": "2c9baa929ca38fb5c9eb5b054474d1ce",
74 "base": "",
75 "sessions": {},
76 "sidebar_show_zero": False,
77 "sidebar_show_trackers": False,
78 "show_keyword_search": False,
79 "show_sidebar": True,
80 "https": False
81}
82
83def rpath(path):
84 """Convert a relative path into an absolute path relative to the location
85 of this script.
86 """
87 return os.path.join(current_dir, path)
88
89class Config(resource.Resource):
90 """
91 Writes out a javascript file that contains the WebUI configuration
92 available as Deluge.Config.
93 """
94
95 def render(self, request):
96 return """Deluge = {
97 author: 'Damien Churchill <damoxc@gmail.com>',
98 version: '1.2-dev',
99 config: %s
100}""" % common.json.dumps(component.get("DelugeWeb").config.config)
101
102class GetText(resource.Resource):
103 def render(self, request):
104 request.setHeader("content-type", "text/javascript; encoding=utf-8")
105 template = Template(filename=rpath("gettext.js"))
106 return template.render()
107
108class Upload(resource.Resource):
109 """
110 Twisted Web resource to handle file uploads
111 """
112
113 def render(self, request):
114 """
115 Saves all uploaded files to the disk and returns a list of filenames,
116 each on a new line.
117 """
118
119 # Block all other HTTP methods.
120 if request.method != "POST":
121 request.setResponseCode(http.NOT_ALLOWED)
122 return ""
123
124 if "file" not in request.args:
125 request.setResponseCode(http.OK)
126 return ""
127
128 tempdir = os.path.join(tempfile.gettempdir(), "delugeweb")
129 if not os.path.isdir(tempdir):
130 os.mkdir(tempdir)
131
132 filenames = []
133 for upload in request.args.get("file"):
134 fd, fn = tempfile.mkstemp('.torrent', dir=tempdir)
135 os.write(fd, upload)
136 os.close(fd)
137 filenames.append(fn)
138 request.setHeader("content-type", "text/plain")
139 request.setResponseCode(http.OK)
140 return "\n".join(filenames)
141
142class Render(resource.Resource):
143
144 def getChild(self, path, request):
145 request.render_file = path
146 return self
147
148 def render(self, request):
149 if not hasattr(request, "render_file"):
150 request.setResponseCode(http.INTERNAL_SERVER_ERROR)
151 return ""
152
153 filename = os.path.join("render", request.render_file)
154 template = Template(filename=rpath(filename))
155 request.setHeader("content-type", "text/html")
156 request.setResponseCode(http.OK)
157 return template.render()
158
159class Tracker(resource.Resource):
160 tracker_icons = TrackerIcons()
161
162 def getChild(self, path, request):
163 request.tracker_name = path
164 return self
165
166 def render(self, request):
167 headers = {}
168 filename = self.tracker_icons.get(request.tracker_name)
169 if filename:
170 request.setHeader("cache-control",
171 "public, must-revalidate, max-age=86400")
172 if filename.endswith(".ico"):
173 request.setHeader("content-type", "image/x-icon")
174 elif filename.endwith(".png"):
175 request.setHeader("content-type", "image/png")
176 data = open(filename, "rb")
177 request.setResponseCode(http.OK)
178 return data.read()
179 else:
180 request.setResponseCode(http.NOT_FOUND)
181 return ""
182
183class Flag(resource.Resource):
184 def getChild(self, path, request):
185 request.country = path
186 return self
187
188 def render(self, request):
189 headers = {}
190 path = ("data", "pixmaps", "flags", request.country.lower() + ".png")
191 filename = pkg_resources.resource_filename("deluge",
192 os.path.join(*path))
193 if os.path.exists(filename):
194 request.setHeader("cache-control",
195 "public, must-revalidate, max-age=86400")
196 request.setHeader("content-type", "image/png")
197 data = open(filename, "rb")
198 request.setResponseCode(http.OK)
199 return data.read()
200 else:
201 request.setResponseCode(http.NOT_FOUND)
202 return ""
203
204class LookupResource(resource.Resource, component.Component):
205
206 def __init__(self, name, *directories):
207 resource.Resource.__init__(self)
208 component.Component.__init__(self, name)
209 self.__directories = directories
210
211 @property
212 def directories(self):
213 return self.__directories
214
215 def getChild(self, path, request):
216 request.path = path
217 return self
218
219 def render(self, request):
220 log.debug("Requested path: '%s'", request.path)
221 for lookup in self.directories:
222 if request.path in os.listdir(lookup):
223 path = os.path.join(lookup, request.path)
224 log.debug("Serving path: '%s'", path)
225 mime_type = mimetypes.guess_type(path)
226 request.setHeader("content-type", mime_type[0])
227 return open(path, "rb").read()
228 request.setResponseCode(http.NOT_FOUND)
229 return "<h1>404 - Not Found</h1>"
230
231class TopLevel(resource.Resource):
232 addSlash = True
233
234 __stylesheets = [
235 "/css/ext-all.css",
236 "/css/ext-extensions.css",
237 "/css/deluge.css"
238 ]
239
240 __scripts = [
241 "/js/ext-base.js",
242 "/js/ext-all.js",
243 "/js/ext-extensions.js",
244 "/config.js",
245 "/gettext.js",
246 "/js/deluge-yc.js"
247 ]
248
249 __debug_scripts = [
250 "/js/ext-base.js",
251 "/js/ext-all-debug.js",
252 "/js/ext-extensions-debug.js",
253 "/config.js",
254 "/gettext.js",
255 "/js/Deluge.js",
256 "/js/Deluge.Formatters.js",
257 "/js/Deluge.Menus.js",
258 "/js/Deluge.Events.js",
259 "/js/Deluge.Client.js",
260 "/js/Deluge.ConnectionManager.js",
261 "/js/Deluge.Details.js",
262 "/js/Deluge.Details.Status.js",
263 "/js/Deluge.Details.Details.js",
264 "/js/Deluge.Details.Files.js",
265 "/js/Deluge.Details.Peers.js",
266 "/js/Deluge.Details.Options.js",
267 "/js/Deluge.Keys.js",
268 "/js/Deluge.Login.js",
269 "/js/Deluge.Preferences.js",
270 "/js/Deluge.Preferences.Downloads.js",
271 "/js/Deluge.Preferences.Network.js",
272 "/js/Deluge.Preferences.Bandwidth.js",
273 "/js/Deluge.Preferences.Interface.js",
274 "/js/Deluge.Preferences.Other.js",
275 "/js/Deluge.Preferences.Daemon.js",
276 "/js/Deluge.Preferences.Queue.js",
277 "/js/Deluge.Preferences.Proxy.js",
278 "/js/Deluge.Preferences.Notification.js",
279 "/js/Deluge.Preferences.Plugins.js",
280 "/js/Deluge.Sidebar.js",
281 "/js/Deluge.Statusbar.js",
282 "/js/Deluge.Toolbar.js",
283 "/js/Deluge.Torrents.js",
284 "/js/Deluge.UI.js"
285 ]
286
287 def __init__(self):
288 resource.Resource.__init__(self)
289 self.putChild("config.js", Config())
290 self.putChild("css", LookupResource("Css", rpath("css")))
291 self.putChild("gettext.js", GetText())
292 self.putChild("flag", Flag())
293 self.putChild("icons", LookupResource("Icons", rpath("icons")))
294 self.putChild("images", LookupResource("Images", rpath("images")))
295 self.putChild("js", LookupResource("Javascript", rpath("js")))
296 self.putChild("json", JSON())
297 self.putChild("upload", Upload())
298 self.putChild("render", Render())
299 self.putChild("themes", static.File(rpath("themes")))
300 self.putChild("tracker", Tracker())
301
302 theme = component.get("DelugeWeb").config["theme"]
303 self.__stylesheets.append("/css/xtheme-%s.css" % theme)
304
305 @property
306 def scripts(self):
307 return self.__scripts
308
309 @property
310 def debug_scripts(self):
311 return self.__debug_scripts
312
313 @property
314 def stylesheets(self):
315 return self.__stylesheets
316
317 def getChild(self, path, request):
318 if path == "":
319 return self
320 else:
321 return resource.Resource.getChild(self, path, request)
322
323 def render(self, request):
324 if request.args.get('debug', ['false'])[-1] == 'true':
325 scripts = self.debug_scripts[:]
326 else:
327 scripts = self.scripts[:]
328
329 template = Template(filename=rpath("index.html"))
330 request.setHeader("content-type", "text/html; charset=utf-8")
331 return template.render(scripts=scripts, stylesheets=self.stylesheets)
332
333class DelugeWeb(component.Component):
334
335 def __init__(self):
336 super(DelugeWeb, self).__init__("DelugeWeb")
337 self.config = ConfigManager("web.conf", CONFIG_DEFAULTS)
338
339 self.top_level = TopLevel()
340 self.site = server.Site(self.top_level)
341 self.port = self.config["port"]
342 self.web_api = WebApi()
343
344 # Since twisted assigns itself all the signals may as well make
345 # use of it.
346 reactor.addSystemEventTrigger("after", "shutdown", self.shutdown)
347
348 # Initalize the plugins
349 self.plugins = PluginManager()
350
351 def start(self):
352 log.info("%s %s.", _("Starting server in PID"), os.getpid())
353 reactor.listenTCP(self.port, self.site)
354 log.info("serving on %s:%s view at http://127.0.0.1:%s", "0.0.0.0",
355 self.port, self.port)
356 reactor.run()
357
358 def shutdown(self):
359 log.info("Shutting down webserver")
360 log.debug("Saving configuration file")
361 self.config.save()
362
363if __name__ == "__builtin__":
364 deluge_web = DelugeWeb()
365 application = service.Application("DelugeWeb")
366 sc = service.IServiceCollection(application)
367 i = internet.TCPServer(deluge_web.port, deluge_web.site)
368 i.setServiceParent(sc)
369elif __name__ == "__main__":
370 deluge_web = DelugeWeb()
371 deluge_web.start()
Note: See TracBrowser for help on using the repository browser.