source: deluge/ui/web/server.py@ 9a43ec

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

add a config resource that outputs the uis current configuration
update this in Deluge.js

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