webui rev 64 update
This commit is contained in:
parent
5cda3e71fa
commit
8fa2f73434
|
@ -36,12 +36,13 @@ plugin_description = "A Web based User Interface\n"
|
|||
import deluge.common
|
||||
import deluge.pref
|
||||
from deluge.dialogs import show_popup_warning
|
||||
from dbus_interface import DbusManager
|
||||
from dbus_interface import get_dbus_manager
|
||||
|
||||
import gtk
|
||||
import os
|
||||
from subprocess import Popen
|
||||
from md5 import md5
|
||||
from threading import Thread
|
||||
import random
|
||||
random.seed()
|
||||
|
||||
|
@ -49,10 +50,6 @@ plugin_version += open(os.path.join(os.path.dirname(__file__),'revno')).read()
|
|||
plugin_description += (
|
||||
open(os.path.join(os.path.dirname(__file__),'version')).read())
|
||||
|
||||
#not found a way to stop a dbus manager.
|
||||
#global so it does not get started twice.
|
||||
dbus_manager = None
|
||||
|
||||
def deluge_init(deluge_path):
|
||||
global path
|
||||
path = deluge_path
|
||||
|
@ -61,15 +58,27 @@ def enable(core, interface):
|
|||
global path
|
||||
return plugin_WebUi(path, core, interface)
|
||||
|
||||
class plugin_WebUi:
|
||||
class WebServerThread(Thread):
|
||||
|
||||
def run(self):
|
||||
#must be imported after plugin-load because of dbus:
|
||||
import webserver_common
|
||||
reload(webserver_common) #HACK!!!
|
||||
from deluge_webserver import WebServer #only import in threaded mode
|
||||
self.web_server = WebServer()
|
||||
self.web_server.start()
|
||||
|
||||
def stop(self):
|
||||
print 'WebUi : Stop threaded server'
|
||||
self.web_server.stop()
|
||||
|
||||
class plugin_WebUi(object):
|
||||
def __init__(self, path, deluge_core, deluge_interface):
|
||||
global dbus_manager
|
||||
self.path = path
|
||||
self.core = deluge_core
|
||||
self.interface = deluge_interface
|
||||
self.proc = None
|
||||
|
||||
|
||||
self.web_server_thread = None
|
||||
|
||||
self.config_file = deluge.common.CONFIG_DIR + "/webui.conf"
|
||||
self.config = deluge.pref.Preferences(self.config_file, False)
|
||||
|
@ -98,19 +107,17 @@ class plugin_WebUi:
|
|||
if self.config.get("cache_templates") == None:
|
||||
self.config.set("cache_templates", True)
|
||||
|
||||
if self.config.get("run_in_thread") == None:
|
||||
self.config.set("run_in_thread", True)
|
||||
|
||||
if not dbus_manager:
|
||||
self.dbusManager = DbusManager(deluge_core, deluge_interface
|
||||
, self.config, self.config_file)
|
||||
|
||||
self.dbus_manager = dbus_manager
|
||||
self.dbus_manager = get_dbus_manager(deluge_core, deluge_interface,
|
||||
self.config, self.config_file)
|
||||
|
||||
self.start_server()
|
||||
|
||||
def unload(self):
|
||||
print 'WebUI:unload..'
|
||||
self.kill_server()
|
||||
#self.dbusManager.
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
@ -124,18 +131,30 @@ class plugin_WebUi:
|
|||
|
||||
def start_server(self):
|
||||
self.kill_server()
|
||||
print 'start Webui..'
|
||||
path = os.path.dirname(__file__)
|
||||
server_bin = path + '/run_webserver'
|
||||
port = str(self.config.get('port'))
|
||||
self.proc = Popen((server_bin, port),cwd=path)
|
||||
|
||||
if self.config.get("run_in_thread"):
|
||||
print 'start Webui(in thread)..'
|
||||
self.web_server_thread = WebServerThread()
|
||||
self.web_server_thread.start()
|
||||
else:
|
||||
print 'start Webui(in process)..'
|
||||
path = os.path.dirname(__file__)
|
||||
server_bin = path + '/run_webserver'
|
||||
port = str(self.config.get('port'))
|
||||
self.proc = Popen((server_bin, port),cwd=path)
|
||||
|
||||
def kill_server(self):
|
||||
if self.web_server_thread:
|
||||
self.web_server_thread.stop()
|
||||
if self.proc:
|
||||
print "webserver: kill %i"%self.proc.pid
|
||||
os.system("kill %i"%self.proc.pid)
|
||||
print "webserver: kill %i" % self.proc.pid
|
||||
os.system("kill %i" % self.proc.pid)
|
||||
self.proc = None
|
||||
|
||||
def __del__(self):
|
||||
self.kill_server()
|
||||
|
||||
|
||||
|
||||
class ConfigDialog(gtk.Dialog):
|
||||
"""
|
||||
|
@ -160,12 +179,15 @@ class ConfigDialog(gtk.Dialog):
|
|||
self.template = self.add_widget(_('Template'), gtk.combo_box_new_text())
|
||||
self.button_style = self.add_widget(_('Button Style'),
|
||||
gtk.combo_box_new_text())
|
||||
self.cache_templates = self.add_widget(_('Cache Templates'),
|
||||
gtk.CheckButton())
|
||||
self.download_dir = self.add_widget(_('Download Directory'),
|
||||
gtk.FileChooserButton(_('Download Directory')))
|
||||
self.torrent_dir = self.add_widget(_('Torrent Directory'),
|
||||
gtk.FileChooserButton(_('Torrent Directory')))
|
||||
self.cache_templates = self.add_widget(_('Cache Templates'),
|
||||
gtk.CheckButton())
|
||||
self.run_in_thread = self.add_widget(_('Run in thread'),
|
||||
gtk.CheckButton())
|
||||
|
||||
|
||||
self.download_dir.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||
self.torrent_dir.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||
|
@ -189,10 +211,13 @@ class ConfigDialog(gtk.Dialog):
|
|||
self.template.set_active(
|
||||
self.templates.index(self.config.get("template")))
|
||||
self.button_style.set_active(self.config.get("button_style"))
|
||||
self.cache_templates.set_active(self.config.get("cache_templates"))
|
||||
|
||||
self.torrent_dir.set_filename(self.config.get("torrent_dir"))
|
||||
self.download_dir.set_filename(self.config.get("download_dir"))
|
||||
|
||||
self.run_in_thread.set_active(self.config.get("run_in_thread"))
|
||||
self.cache_templates.set_active(self.config.get("cache_templates"))
|
||||
|
||||
self.vbox.pack_start(self.vb, True, True, 0)
|
||||
self.vb.show_all()
|
||||
|
||||
|
@ -226,17 +251,9 @@ class ConfigDialog(gtk.Dialog):
|
|||
self.config.set("port", int(self.port.get_value()))
|
||||
self.config.set("template", self.template.get_active_text())
|
||||
self.config.set("button_style", self.button_style.get_active())
|
||||
self.config.set("cache_templates", self.cache_templates.get_active())
|
||||
self.config.set("torrent_dir", self.torrent_dir.get_filename())
|
||||
self.config.set("download_dir",self.download_dir.get_filename())
|
||||
self.config.set("cache_templates", self.cache_templates.get_active())
|
||||
self.config.set("run_in_thread", self.run_in_thread.get_active())
|
||||
self.config.save(self.plugin.config_file)
|
||||
self.plugin.start_server() #restarts server
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -32,20 +32,25 @@
|
|||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
|
||||
|
||||
import os
|
||||
import gtk
|
||||
import dbus
|
||||
import deluge.common as common
|
||||
from dbus_pythonize import pythonize
|
||||
import base64
|
||||
from md5 import md5
|
||||
import random
|
||||
random.seed()
|
||||
|
||||
dbus_interface="org.deluge_torrent.dbusplugin"
|
||||
dbus_service="/org/deluge_torrent/DelugeDbusPlugin"
|
||||
|
||||
dbus_manager = None
|
||||
def get_dbus_manager(*args):
|
||||
#another way to make a singleton.
|
||||
global dbus_manager
|
||||
if not dbus_manager:
|
||||
dbus_manager = DbusManager(*args)
|
||||
return dbus_manager
|
||||
|
||||
class DbusManager(dbus.service.Object):
|
||||
def __init__(self, core, interface,config,config_file):
|
||||
|
@ -89,7 +94,7 @@ class DbusManager(dbus.service.Object):
|
|||
"name": state["name"],
|
||||
"total_size": state["total_size"],
|
||||
"num_pieces": state["num_pieces"],
|
||||
#"state": int(status.state), #?
|
||||
"state": state['state'],
|
||||
"paused": self.core.is_user_paused(torrent_id),
|
||||
"progress": int(state["progress"] * 100),
|
||||
"next_announce": state["next_announce"],
|
||||
|
@ -103,7 +108,6 @@ class DbusManager(dbus.service.Object):
|
|||
"eta": common.estimate_eta(state),
|
||||
"ratio": self.interface.manager.calc_ratio(torrent_id,state),
|
||||
#non 0.6 values follow here:
|
||||
"message": self.interface.get_message_from_state(state),
|
||||
"tracker_status": state.get("tracker_status","?"),
|
||||
"uploaded_memory": torrent.uploaded_memory,
|
||||
}
|
||||
|
@ -175,39 +179,6 @@ class DbusManager(dbus.service.Object):
|
|||
self._add_torrent(filename)
|
||||
return True
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="s",out_signature="v")
|
||||
def get_webui_config(self,key):
|
||||
"""
|
||||
return data from wevbui config.
|
||||
not in 0.6
|
||||
"""
|
||||
retval = self.config.get(str(key))
|
||||
#print 'get webui config:', str(key), retval
|
||||
if retval == None:
|
||||
retval = False #dbus does not accept None :(
|
||||
|
||||
return retval
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="sv",out_signature="")
|
||||
def set_webui_config(self, key, value):
|
||||
"""
|
||||
return data from wevbui config.
|
||||
not in 0.6
|
||||
"""
|
||||
#print 'set webui config:', str(key), pythonize(value)
|
||||
self.config.set(str(key), pythonize(value))
|
||||
self.config.save(self.config_file)
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="s",out_signature="b")
|
||||
def check_pwd(self, pwd):
|
||||
m = md5()
|
||||
m.update(self.config.get('pwd_salt'))
|
||||
m.update(pwd)
|
||||
return (m.digest() == self.config.get('pwd_md5'))
|
||||
|
||||
#internal
|
||||
def _add_torrent(self, filename):
|
||||
#dbus types break pickle, again.....
|
||||
|
|
|
@ -31,149 +31,33 @@
|
|||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
|
||||
"""
|
||||
Todo's before stable:
|
||||
-__init__:kill->restart is not waiting for kill to be finished.
|
||||
--later/features:---
|
||||
-alternating rows?
|
||||
-set prio
|
||||
-clear finished?
|
||||
-torrent files.
|
||||
"""
|
||||
|
||||
from webserver_common import proxy, config ,TORRENT_KEYS, STATE_MESSAGES
|
||||
from webserver_framework import *
|
||||
|
||||
|
||||
import webpy022 as web
|
||||
|
||||
from webpy022.webapi import cookies, setcookie
|
||||
from webpy022.http import seeother, url
|
||||
from webpy022.webapi import setcookie
|
||||
from webpy022.utils import Storage
|
||||
from webpy022.net import urlquote
|
||||
from webpy022 import template, changequery as self_url
|
||||
|
||||
import dbus
|
||||
|
||||
import gettext, os, platform, locale, traceback
|
||||
import random
|
||||
import base64
|
||||
from md5 import md5
|
||||
from deluge.common import fsize
|
||||
from operator import attrgetter
|
||||
|
||||
from deluge import common
|
||||
from deluge.common import INSTALL_PREFIX
|
||||
|
||||
|
||||
#init:
|
||||
APP = 'deluge'
|
||||
DIR = os.path.join(INSTALL_PREFIX, 'share', 'locale')
|
||||
if platform.system() != "Windows":
|
||||
locale.setlocale(locale.LC_MESSAGES, '')
|
||||
locale.bindtextdomain(APP, DIR)
|
||||
locale.textdomain(APP)
|
||||
else:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
gettext.bindtextdomain(APP, DIR)
|
||||
gettext.textdomain(APP)
|
||||
gettext.install(APP, DIR)
|
||||
|
||||
random.seed()
|
||||
bus = dbus.SessionBus()
|
||||
proxy = bus.get_object("org.deluge_torrent.dbusplugin"
|
||||
, "/org/deluge_torrent/DelugeDbusPlugin")
|
||||
|
||||
web.webapi.internalerror = web.debugerror
|
||||
|
||||
render = template.render('templates/%s/' % proxy.get_webui_config('template')
|
||||
,cache=proxy.get_webui_config('cache_templates'))
|
||||
#/init
|
||||
|
||||
#framework:
|
||||
|
||||
SESSIONS = {}
|
||||
|
||||
def do_redirect():
|
||||
"""for redirects after a POST"""
|
||||
vars = web.input(redir = None)
|
||||
ck = cookies()
|
||||
|
||||
if vars.redir:
|
||||
seeother(vars.redir)
|
||||
elif ("order" in ck and "sort" in ck):
|
||||
seeother(url("/index", sort=ck['sort'], order=ck['order']))
|
||||
else:
|
||||
seeother(url("/index"))
|
||||
|
||||
def deluge_page_noauth(func):
|
||||
"""
|
||||
add http headers
|
||||
print result of func
|
||||
"""
|
||||
def deco(self, name=None):
|
||||
web.header("Content-Type", "text/html; charset=utf-8")
|
||||
web.header("Cache-Control", "no-cache, must-revalidate")
|
||||
res = func(self, name)
|
||||
print res
|
||||
return deco
|
||||
|
||||
def check_session(func):
|
||||
"""
|
||||
a decorator
|
||||
return func if session is valid, else redirect to login page.
|
||||
"""
|
||||
def deco(self, name):
|
||||
vars = web.input(redir_after_login=None)
|
||||
|
||||
ck = cookies()
|
||||
if ck.has_key("session_id") and ck["session_id"] in SESSIONS:
|
||||
return func(self, name) #ok, continue..
|
||||
elif vars.redir_after_login:
|
||||
seeother("/login?redir=" + urlquote(self_url()))
|
||||
else:
|
||||
seeother("/login") #do not continue, and redirect to login page
|
||||
return deco
|
||||
|
||||
def deluge_page(func):
|
||||
return check_session(deluge_page_noauth(func))
|
||||
|
||||
def auto_refreshed(func):
|
||||
"decorator:adds a refresh header"
|
||||
def deco(self, name):
|
||||
if proxy.get_webui_config('auto_refresh'):
|
||||
web.header("Refresh", "%i ; url=%s" %
|
||||
(proxy.get_webui_config('auto_refresh_secs'),self_url()))
|
||||
return func(self, name)
|
||||
return deco
|
||||
|
||||
def error_page(error):
|
||||
web.header("Content-Type", "text/html; charset=utf-8")
|
||||
web.header("Cache-Control", "no-cache, must-revalidate")
|
||||
print render.error(error)
|
||||
|
||||
def remote(func):
|
||||
"decorator for remote api's"
|
||||
def deco(self, name):
|
||||
try:
|
||||
print func(self, name)
|
||||
except Exception, e:
|
||||
print 'error:' + e.message
|
||||
print '-'*20
|
||||
print traceback.format_exc()
|
||||
return deco
|
||||
|
||||
#/framework
|
||||
|
||||
#utils:
|
||||
torrent_keys = ['distributed_copies', 'download_payload_rate',
|
||||
'download_rate', 'eta', 'is_seed', 'message', 'name', 'next_announce',
|
||||
'num_files', 'num_peers', 'num_pieces', 'num_seeds', 'paused',
|
||||
'piece_length','progress', 'ratio', 'total_done', 'total_download',
|
||||
'total_payload_download', 'total_payload_upload', 'total_peers',
|
||||
'total_seeds', 'total_size', 'total_upload', 'total_wanted',
|
||||
'tracker_status', 'upload_payload_rate', 'upload_rate',
|
||||
'uploaded_memory','tracker']
|
||||
def check_pwd(pwd):
|
||||
m = md5()
|
||||
m.update(config.get('pwd_salt'))
|
||||
m.update(pwd)
|
||||
return (m.digest() == config.get('pwd_md5'))
|
||||
|
||||
def get_torrent_status(torrent_id):
|
||||
"""
|
||||
helper method.
|
||||
enhance proxy.get_torrent_status with some extra data
|
||||
"""
|
||||
status = proxy.get_torrent_status(torrent_id,torrent_keys)
|
||||
status = proxy.get_torrent_status(torrent_id,TORRENT_KEYS)
|
||||
status["id"] = torrent_id
|
||||
|
||||
#for naming the status-images
|
||||
|
@ -189,61 +73,26 @@ def get_torrent_status(torrent_id):
|
|||
else:
|
||||
status["action"] = "stop"
|
||||
|
||||
|
||||
if status["paused"]:
|
||||
status["message"] = _("Paused %s%%") % status['progress']
|
||||
else:
|
||||
status["message"] = "%s %i%%" % (STATE_MESSAGES[status["state"]]
|
||||
, status['progress'])
|
||||
|
||||
#add some pre-calculated values
|
||||
status.update({
|
||||
"calc_total_downloaded" : (common.fsize(status["total_done"])
|
||||
+ " (" + common.fsize(status["total_download"]) + ")"),
|
||||
"calc_total_uploaded": (common.fsize(status['uploaded_memory']
|
||||
"calc_total_downloaded" : (fsize(status["total_done"])
|
||||
+ " (" + fsize(status["total_download"]) + ")"),
|
||||
"calc_total_uploaded": (fsize(status['uploaded_memory']
|
||||
+ status["total_payload_upload"]) + " ("
|
||||
+ common.fsize(status["total_upload"]) + ")"),
|
||||
+ fsize(status["total_upload"]) + ")"),
|
||||
})
|
||||
|
||||
return Storage(status) #Storage for easy templating.
|
||||
|
||||
#/utils
|
||||
|
||||
#template-defs:
|
||||
def template_crop(text, end):
|
||||
if len(text) > end:
|
||||
return text[0:end - 3] + '...'
|
||||
return text
|
||||
|
||||
def template_sort_head(id,name):
|
||||
#got tired of doing these complex things inside templetor..
|
||||
vars = web.input(sort=None, order=None)
|
||||
active_up = False
|
||||
active_down = False
|
||||
order = 'down'
|
||||
|
||||
if vars.sort == id:
|
||||
if vars.order == 'down':
|
||||
order = 'up'
|
||||
active_down = True
|
||||
else:
|
||||
active_up = True
|
||||
|
||||
return render.sort_column_head(id, name, order, active_up, active_down)
|
||||
|
||||
template.Template.globals.update({
|
||||
'sort_head': template_sort_head,
|
||||
'crop': template_crop,
|
||||
'_': _ , #gettext/translations
|
||||
'str': str, #because % in templetor is broken.
|
||||
'sorted': sorted,
|
||||
'get_config': proxy.get_webui_config,
|
||||
'self_url': self_url,
|
||||
'fspeed': common.fspeed,
|
||||
'fsize': common.fsize,
|
||||
'render': render, #for easy resuse of templates
|
||||
'button_style': (proxy.get_webui_config('button_style')),
|
||||
'rev': ('rev.' +
|
||||
open(os.path.join(os.path.dirname(__file__),'revno')).read()),
|
||||
'version': (
|
||||
open(os.path.join(os.path.dirname(__file__),'version')).read()),
|
||||
'get': lambda (var): getattr(web.input(**{var:None}),var) # unreadable :-(
|
||||
})
|
||||
#/template-defs
|
||||
|
||||
#routing:
|
||||
urls = (
|
||||
"/login(.*)", "login",
|
||||
|
@ -277,14 +126,12 @@ class login:
|
|||
def POST(self, name):
|
||||
vars = web.input(pwd = None ,redir = None)
|
||||
|
||||
if proxy.check_pwd(vars.pwd):
|
||||
if check_pwd(vars.pwd):
|
||||
#start new session
|
||||
session_id = str(random.random())
|
||||
SESSIONS[session_id] = {"not":"used"}
|
||||
setcookie("session_id", session_id)
|
||||
start_session()
|
||||
do_redirect()
|
||||
elif vars.redir:
|
||||
seeother('/login?error=1&redir=' + urlquote(vars.redir))
|
||||
seeother(url('/login',error=1,redir=vars.redir))
|
||||
else:
|
||||
seeother('/login?error=1')
|
||||
|
||||
|
@ -360,13 +207,12 @@ class remote_torrent_add:
|
|||
"""
|
||||
For use in remote scripts etc.
|
||||
POST pwd and torrent
|
||||
Example : curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add"
|
||||
"""
|
||||
@remote
|
||||
def POST(self, name):
|
||||
vars = web.input(pwd = None, torrent = {})
|
||||
|
||||
if not proxy.check_pwd(vars.pwd):
|
||||
if not check_pwd(vars.pwd):
|
||||
return 'error:wrong password'
|
||||
|
||||
data_b64 = base64.b64encode(vars.torrent.file.read())
|
||||
|
@ -404,8 +250,8 @@ class resume_all:
|
|||
class refresh:
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
auto_refresh = {'off':False, 'on':True}[name]
|
||||
proxy.set_webui_config('auto_refresh', auto_refresh)
|
||||
auto_refresh = {'off':'0', 'on':'1'}[name]
|
||||
setcookie('auto_refresh',auto_refresh)
|
||||
do_redirect()
|
||||
|
||||
class refresh_set:
|
||||
|
@ -418,8 +264,8 @@ class refresh_set:
|
|||
vars = web.input(refresh = 0)
|
||||
refresh = int(vars.refresh)
|
||||
if refresh > 0:
|
||||
proxy.set_webui_config('refresh', refresh)
|
||||
proxy.set_webui_config('auto_refresh', True)
|
||||
setcookie('auto_refresh','1')
|
||||
setcookie('auto_refresh_secs', str(refresh))
|
||||
do_redirect()
|
||||
else:
|
||||
error_page(_('refresh must be > 0'))
|
||||
|
@ -429,10 +275,19 @@ class about:
|
|||
def GET(self, name):
|
||||
return render.about()
|
||||
|
||||
|
||||
#/pages
|
||||
|
||||
|
||||
def WebServer():
|
||||
return create_webserver(urls, globals())
|
||||
|
||||
def run():
|
||||
server = WebServer()
|
||||
try:
|
||||
server.start()
|
||||
except KeyboardInterrupt:
|
||||
server.stop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
web.run(urls, globals())
|
||||
|
||||
run()
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) Martijn Voncken 2007 <mvoncken@gmail.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
|
||||
import os
|
||||
import deluge
|
||||
from deluge.common import INSTALL_PREFIX
|
||||
import random
|
||||
import pickle
|
||||
random.seed()
|
||||
|
||||
config_file = deluge.common.CONFIG_DIR + "/webui.conf"
|
||||
#config = deluge.pref.Preferences(config_file, False)
|
||||
config = pickle.load(open(config_file))
|
||||
|
||||
if config.get('run_in_thread'):
|
||||
#do not use dbus ipc for threads!
|
||||
from dbus_interface import get_dbus_manager
|
||||
proxy = get_dbus_manager()
|
||||
else:
|
||||
import dbus
|
||||
bus = dbus.SessionBus()
|
||||
proxy = bus.get_object("org.deluge_torrent.dbusplugin"
|
||||
, "/org/deluge_torrent/DelugeDbusPlugin")
|
||||
|
||||
REVNO = open(os.path.join(os.path.dirname(__file__),'revno')).read()
|
||||
VERSION = open(os.path.join(os.path.dirname(__file__),'version')).read()
|
||||
|
||||
TORRENT_KEYS = ['distributed_copies', 'download_payload_rate',
|
||||
'download_rate', 'eta', 'is_seed', 'name', 'next_announce',
|
||||
'num_files', 'num_peers', 'num_pieces', 'num_seeds', 'paused',
|
||||
'piece_length','progress', 'ratio', 'total_done', 'total_download',
|
||||
'total_payload_download', 'total_payload_upload', 'total_peers',
|
||||
'total_seeds', 'total_size', 'total_upload', 'total_wanted',
|
||||
'tracker_status', 'upload_payload_rate', 'upload_rate',
|
||||
'uploaded_memory','tracker','state']
|
||||
|
||||
STATE_MESSAGES = (_("Queued"),
|
||||
_("Checking"),
|
||||
_("Connecting"),
|
||||
_("Downloading Metadata"),
|
||||
_("Downloading"),
|
||||
_("Finished"),
|
||||
_("Seeding"),
|
||||
_("Allocating"))
|
|
@ -0,0 +1,302 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# webserver_framework.py
|
||||
#
|
||||
# Copyright (C) Martijn Voncken 2007 <mvoncken@gmail.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
|
||||
"""
|
||||
Todo's before stable:
|
||||
-__init__:kill->restart is not waiting for kill to be finished.
|
||||
--later/features:---
|
||||
-alternating rows?
|
||||
-set prio
|
||||
-clear finished?
|
||||
-torrent files.
|
||||
"""
|
||||
import webpy022 as web
|
||||
|
||||
from webpy022.webapi import cookies, setcookie
|
||||
from webpy022.http import seeother, url
|
||||
from webpy022 import template, changequery as self_url
|
||||
|
||||
import traceback
|
||||
import random
|
||||
from operator import attrgetter
|
||||
|
||||
from deluge import common
|
||||
from webserver_common import proxy, config , REVNO, VERSION
|
||||
|
||||
#init:
|
||||
web.webapi.internalerror = web.debugerror
|
||||
render = template.render('templates/%s/' % config.get('template'),
|
||||
cache=config.get('cache_templates'))
|
||||
|
||||
#/init
|
||||
|
||||
#methods:
|
||||
SESSIONS = [] #dumb sessions.
|
||||
def start_session():
|
||||
session_id = str(random.random())
|
||||
SESSIONS.append(session_id)
|
||||
setcookie("session_id", session_id)
|
||||
|
||||
def do_redirect():
|
||||
"""for redirects after a POST"""
|
||||
vars = web.input(redir = None)
|
||||
ck = cookies()
|
||||
|
||||
if vars.redir:
|
||||
seeother(vars.redir)
|
||||
elif ("order" in ck and "sort" in ck):
|
||||
seeother(url("/index", sort=ck['sort'], order=ck['order']))
|
||||
else:
|
||||
seeother(url("/index"))
|
||||
|
||||
def error_page(error):
|
||||
web.header("Content-Type", "text/html; charset=utf-8")
|
||||
web.header("Cache-Control", "no-cache, must-revalidate")
|
||||
print render.error(error)
|
||||
|
||||
def getcookie(key, default=None):
|
||||
ck = cookies()
|
||||
return str(ck.get(key, default))
|
||||
|
||||
#deco's:
|
||||
def deluge_page_noauth(func):
|
||||
"""
|
||||
add http headers
|
||||
print result of func
|
||||
"""
|
||||
def deco(self, name=None):
|
||||
web.header("Content-Type", "text/html; charset=utf-8")
|
||||
web.header("Cache-Control", "no-cache, must-revalidate")
|
||||
res = func(self, name)
|
||||
print res
|
||||
return deco
|
||||
|
||||
def check_session(func):
|
||||
"""
|
||||
a decorator
|
||||
return func if session is valid, else redirect to login page.
|
||||
"""
|
||||
def deco(self, name):
|
||||
vars = web.input(redir_after_login=None)
|
||||
|
||||
ck = cookies()
|
||||
if ck.has_key("session_id") and ck["session_id"] in SESSIONS:
|
||||
return func(self, name) #ok, continue..
|
||||
elif vars.redir_after_login:
|
||||
seeother(url("/login",redir=self_url()))
|
||||
else:
|
||||
seeother("/login") #do not continue, and redirect to login page
|
||||
return deco
|
||||
|
||||
def deluge_page(func):
|
||||
return check_session(deluge_page_noauth(func))
|
||||
|
||||
#combi-deco's:
|
||||
def auto_refreshed(func):
|
||||
"decorator:adds a refresh header"
|
||||
def deco(self, name):
|
||||
if getcookie('auto_refresh') == '1':
|
||||
web.header("Refresh", "%i ; url=%s" %
|
||||
(int(getcookie('auto_refresh_secs',10)),self_url()))
|
||||
return func(self, name)
|
||||
return deco
|
||||
|
||||
def remote(func):
|
||||
"decorator for remote api's"
|
||||
def deco(self, name):
|
||||
try:
|
||||
print func(self, name)
|
||||
except Exception, e:
|
||||
print 'error:' + e.message
|
||||
print '-'*20
|
||||
print traceback.format_exc()
|
||||
return deco
|
||||
|
||||
#template-defs:
|
||||
def template_crop(text, end):
|
||||
if len(text) > end:
|
||||
return text[0:end - 3] + '...'
|
||||
return text
|
||||
|
||||
def template_sort_head(id,name):
|
||||
#got tired of doing these complex things inside templetor..
|
||||
vars = web.input(sort=None, order=None)
|
||||
active_up = False
|
||||
active_down = False
|
||||
order = 'down'
|
||||
|
||||
if vars.sort == id:
|
||||
if vars.order == 'down':
|
||||
order = 'up'
|
||||
active_down = True
|
||||
else:
|
||||
active_up = True
|
||||
|
||||
return render.sort_column_head(id, name, order, active_up, active_down)
|
||||
|
||||
template.Template.globals.update({
|
||||
'sort_head': template_sort_head,
|
||||
'crop': template_crop,
|
||||
'_': _ , #gettext/translations
|
||||
'str': str, #because % in templetor is broken.
|
||||
'sorted': sorted,
|
||||
'get_config': config.get,
|
||||
'self_url': self_url,
|
||||
'fspeed': common.fspeed,
|
||||
'fsize': common.fsize,
|
||||
'render': render, #for easy resuse of templates
|
||||
'button_style': (config.get('button_style')),
|
||||
'rev': 'rev.%s' % (REVNO, ),
|
||||
'version': VERSION,
|
||||
'get': lambda (var): getattr(web.input(**{var:None}),var) # unreadable :-(
|
||||
})
|
||||
#/template-defs
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#Some copy and paste from web.py
|
||||
#mostly caused by /static
|
||||
#TODO : FIX THIS.
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
from webpy022.request import webpyfunc
|
||||
from webpy022 import webapi
|
||||
import os
|
||||
|
||||
class StaticApp(SimpleHTTPRequestHandler):
|
||||
"""WSGI application for serving static files."""
|
||||
def __init__(self, environ, start_response):
|
||||
self.headers = []
|
||||
self.environ = environ
|
||||
self.start_response = start_response
|
||||
|
||||
def send_response(self, status, msg=""):
|
||||
self.status = str(status) + " " + msg
|
||||
|
||||
def send_header(self, name, value):
|
||||
self.headers.append((name, value))
|
||||
|
||||
def end_headers(self):
|
||||
pass
|
||||
|
||||
def log_message(*a): pass
|
||||
|
||||
def __iter__(self):
|
||||
environ = self.environ
|
||||
|
||||
self.path = environ.get('PATH_INFO', '')
|
||||
self.client_address = environ.get('REMOTE_ADDR','-'), \
|
||||
environ.get('REMOTE_PORT','-')
|
||||
self.command = environ.get('REQUEST_METHOD', '-')
|
||||
|
||||
from cStringIO import StringIO
|
||||
self.wfile = StringIO() # for capturing error
|
||||
|
||||
f = self.send_head()
|
||||
self.start_response(self.status, self.headers)
|
||||
|
||||
if f:
|
||||
block_size = 16 * 1024
|
||||
while True:
|
||||
buf = f.read(block_size)
|
||||
if not buf:
|
||||
break
|
||||
yield buf
|
||||
f.close()
|
||||
else:
|
||||
value = self.wfile.getvalue()
|
||||
yield value
|
||||
|
||||
class WSGIWrapper(BaseHTTPRequestHandler):
|
||||
"""WSGI wrapper for logging the status and serving static files."""
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.format = '%s - - [%s] "%s %s %s" - %s'
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
def xstart_response(status, response_headers, *args):
|
||||
write = start_response(status, response_headers, *args)
|
||||
self.log(status, environ)
|
||||
return write
|
||||
|
||||
path = environ.get('PATH_INFO', '')
|
||||
if path.startswith('/static/'):
|
||||
return StaticApp(environ, xstart_response)
|
||||
else:
|
||||
return self.app(environ, xstart_response)
|
||||
|
||||
def log(self, status, environ):
|
||||
#mvoncken,no logging..
|
||||
return
|
||||
|
||||
outfile = environ.get('wsgi.errors', web.debug)
|
||||
req = environ.get('PATH_INFO', '_')
|
||||
protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
|
||||
method = environ.get('REQUEST_METHOD', '-')
|
||||
host = "%s:%s" % (environ.get('REMOTE_ADDR','-'),
|
||||
environ.get('REMOTE_PORT','-'))
|
||||
|
||||
#@@ It is really bad to extend from
|
||||
#@@ BaseHTTPRequestHandler just for this method
|
||||
time = self.log_date_time_string()
|
||||
|
||||
print >> outfile, self.format % (host, time, protocol,
|
||||
method, req, status)
|
||||
|
||||
def create_webserver(urls,methods):
|
||||
from webpy022.wsgiserver import CherryPyWSGIServer
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
|
||||
|
||||
os.chdir(os.path.dirname(__file__)) #HACK for /static..
|
||||
|
||||
func = webapi.wsgifunc(webpyfunc(urls,methods, False))
|
||||
server_address=("0.0.0.0",config.get('port'))
|
||||
|
||||
func = WSGIWrapper(func)
|
||||
server = CherryPyWSGIServer(server_address, func, server_name="localhost")
|
||||
|
||||
|
||||
print "(created) http://%s:%d/" % server_address
|
||||
|
||||
return server
|
||||
|
||||
#------
|
||||
__all__ = ['deluge_page_noauth', 'deluge_page', 'remote',
|
||||
'auto_refreshed', 'check_session',
|
||||
'do_redirect', 'error_page', 'render', 'start_session','getcookie'
|
||||
,'create_webserver']
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue