mirror of
https://git.deluge-torrent.org/deluge
synced 2025-04-20 11:35:49 +00:00
sync webui 112 and translate
This commit is contained in:
parent
019ea1f601
commit
cd2f45da92
22 changed files with 810 additions and 332 deletions
|
@ -29,7 +29,7 @@
|
|||
# this exception statement from your version. If you delete this exception
|
||||
|
||||
plugin_name = _("Web User Interface")
|
||||
plugin_author = _("Martijn Voncken")
|
||||
plugin_author = "Martijn Voncken"
|
||||
plugin_version = "rev."
|
||||
plugin_description = _("""A Web based User Interface
|
||||
|
||||
|
@ -41,7 +41,7 @@ There is support for multiple templates, but just one is included.
|
|||
|
||||
Other contributors:
|
||||
*somedude : template enhancements.
|
||||
|
||||
*markybob : stability : synced with his changes in deluge-svn.
|
||||
""")
|
||||
|
||||
import deluge.common
|
||||
|
@ -80,9 +80,10 @@ class plugin_WebUi(object):
|
|||
self.web_server = None
|
||||
if not deluge.common.windows_check():
|
||||
import commands
|
||||
status = commands.getstatusoutput('ps x |grep -v grep |grep run_webserver')
|
||||
status = commands.getstatusoutput(
|
||||
'ps x |grep -v grep |grep run_webserver')
|
||||
if status[0] == 0:
|
||||
os.kill(status[1].split()[0], 9)
|
||||
os.kill(int(status[1].split()[0]), 9)
|
||||
time.sleep(1) #safe time to wait for kill to finish.
|
||||
self.config_file = deluge.common.CONFIG_DIR + "/webui.conf"
|
||||
self.config = deluge.pref.Preferences(self.config_file, False)
|
||||
|
@ -95,9 +96,6 @@ class plugin_WebUi(object):
|
|||
if not self.config.get('port'): #ugly way to detect new config file.
|
||||
#set default values:
|
||||
self.config.set("port", 8112)
|
||||
#future->use deluge-core setting for download_dir (if it is set)
|
||||
self.config.set("download_dir", os.path.expanduser("~"))
|
||||
self.config.set("torrent_dir", os.path.expanduser("~"))
|
||||
self.config.set("button_style", 2)
|
||||
self.config.set("auto_refresh", False)
|
||||
self.config.set("auto_refresh_secs", 4)
|
||||
|
@ -112,11 +110,9 @@ class plugin_WebUi(object):
|
|||
self.config.set("cache_templates", True)
|
||||
|
||||
if deluge.common.windows_check():
|
||||
if self.config.get("run_in_thread") == None:
|
||||
self.config.set("run_in_thread", True)
|
||||
self.config.set("run_in_thread", True)
|
||||
else:
|
||||
if self.config.get("run_in_thread") == None:
|
||||
self.config.set("run_in_thread", False)
|
||||
self.config.set("run_in_thread", False)
|
||||
|
||||
self.dbus_manager = get_dbus_manager(deluge_core, deluge_interface,
|
||||
self.config, self.config_file)
|
||||
|
@ -141,8 +137,8 @@ class plugin_WebUi(object):
|
|||
self.kill_server()
|
||||
|
||||
if self.config.get("run_in_thread"):
|
||||
print 'start Webui(inside gtk)..'
|
||||
webserver_common.init() #reload changed config.
|
||||
print 'Start Webui(inside gtk)..'
|
||||
webserver_common.init_gtk_05() #reload changed config.
|
||||
from deluge_webserver import WebServer #only import in threaded mode
|
||||
|
||||
|
||||
|
@ -150,11 +146,9 @@ class plugin_WebUi(object):
|
|||
self.web_server.start_gtk()
|
||||
|
||||
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)
|
||||
print 'Start Webui(in process)..'
|
||||
server_bin = os.path.dirname(__file__) + '/run_webserver'
|
||||
self.proc = Popen((server_bin,'env=0.5'))
|
||||
|
||||
def kill_server(self):
|
||||
if self.web_server:
|
||||
|
@ -187,7 +181,8 @@ class ConfigDialog(gtk.Dialog):
|
|||
template_path = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
self.templates = [dirname for dirname
|
||||
in os.listdir(template_path)
|
||||
if os.path.isdir(os.path.join(template_path, dirname))]
|
||||
if os.path.isdir(os.path.join(template_path, dirname))
|
||||
and not dirname.startswith('.')]
|
||||
|
||||
self.port = self.add_widget(_('Port Number'), gtk.SpinButton())
|
||||
self.pwd1 = self.add_widget(_('New Password'), gtk.Entry())
|
||||
|
@ -195,16 +190,11 @@ 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.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 inside GTK'), gtk.CheckButton())
|
||||
#self.share_downloads = self.add_widget(_('Share Download Directory'),
|
||||
# gtk.CheckButton())
|
||||
|
||||
self.download_dir.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||
self.torrent_dir.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||
self.port.set_range(80, 65536)
|
||||
self.port.set_increments(1, 10)
|
||||
self.pwd1.set_visibility(False)
|
||||
|
@ -218,23 +208,16 @@ class ConfigDialog(gtk.Dialog):
|
|||
|
||||
for item in [_('Text and image'), _('Image Only'), _('Text Only')]:
|
||||
self.button_style.append_text(item)
|
||||
if not self.config.get("button_style"):
|
||||
if self.config.get("button_style") == None:
|
||||
self.config.set("button_style", 2)
|
||||
|
||||
self.port.set_value(int(self.config.get("port")))
|
||||
self.template.set_active(
|
||||
self.templates.index(self.config.get("template")))
|
||||
self.button_style.set_active(self.config.get("button_style"))
|
||||
#self.share_downloads.set_active(
|
||||
# bool(self.config.get("share_downloads")))
|
||||
|
||||
self.torrent_dir.set_filename(self.config.get("torrent_dir"))
|
||||
self.download_dir.set_filename(self.config.get("download_dir"))
|
||||
|
||||
if deluge.common.windows_check():
|
||||
self.run_in_thread.set_active(True)
|
||||
self.run_in_thread.set_sensitive(False)
|
||||
else:
|
||||
self.run_in_thread.set_active(False)
|
||||
self.run_in_thread.set_sensitive(False)
|
||||
self.cache_templates.set_active(self.config.get("cache_templates"))
|
||||
|
||||
self.vbox.pack_start(self.vb, True, True, 0)
|
||||
|
@ -270,9 +253,7 @@ 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("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.set("share_downloads", self.share_downloads.get_active())
|
||||
self.config.save(self.plugin.config_file)
|
||||
self.plugin.start_server() #restarts server
|
||||
|
|
|
@ -68,7 +68,7 @@ class DbusManager(dbus.service.Object):
|
|||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="",out_signature="as")
|
||||
def get_torrent_state(self):
|
||||
def get_session_state(self):
|
||||
"""Returns a list of torrent_ids in the session.
|
||||
same as 0.6, but returns type "as" instead of a pickle
|
||||
"""
|
||||
|
@ -129,18 +129,20 @@ class DbusManager(dbus.service.Object):
|
|||
return status_subset
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="s",out_signature="")
|
||||
def pause_torrent(self, torrent_id):
|
||||
in_signature="as",out_signature="")
|
||||
def pause_torrent(self, torrents):
|
||||
"""same as 0.6 interface"""
|
||||
torrent_id = int(torrent_id)
|
||||
self.core.set_user_pause(torrent_id,True)
|
||||
for torrent_id in torrents:
|
||||
torrent_id = int(torrent_id)
|
||||
self.core.set_user_pause(torrent_id,True)
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="s", out_signature="")
|
||||
def resume_torrent(self, torrent_id):
|
||||
in_signature="as", out_signature="")
|
||||
def resume_torrent(self, torrents):
|
||||
"""same as 0.6 interface"""
|
||||
torrent_id = int(torrent_id)
|
||||
self.core.set_user_pause(torrent_id,False)
|
||||
for torrent_id in torrents:
|
||||
torrent_id = int(torrent_id)
|
||||
self.core.set_user_pause(torrent_id,False)
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="sbb", out_signature="")
|
||||
|
@ -157,7 +159,6 @@ class DbusManager(dbus.service.Object):
|
|||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="s", out_signature="b")
|
||||
def add_torrent_url(self, url):
|
||||
"""not available in deluge 0.6 interface"""
|
||||
filename = fetch_url(url)
|
||||
self._add_torrent(filename)
|
||||
return True
|
||||
|
@ -182,8 +183,8 @@ class DbusManager(dbus.service.Object):
|
|||
#name = fillename without directory
|
||||
name = name.replace('\\','/')
|
||||
name = 'deluge_' + str(random.random()) + '_' + name.split('/')[-1]
|
||||
filename = os.path.join(self.core.config.get("default_download_path"), name)
|
||||
|
||||
filename = os.path.join(self.config.get("torrent_dir"),name)
|
||||
filecontent = base64.b64decode(filecontent_b64)
|
||||
f = open(filename,"wb") #no with statement, that's py 2.5+
|
||||
f.write(filecontent)
|
||||
|
@ -192,11 +193,42 @@ class DbusManager(dbus.service.Object):
|
|||
self._add_torrent(filename)
|
||||
return True
|
||||
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="", out_signature="a{sv}")
|
||||
def get_config(self):
|
||||
return self.core.config.mapping
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="s", out_signature="v")
|
||||
def get_config_value(self,key):
|
||||
return self.core.config.mapping[pythonize(key)] #ugly!
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="a{sv}", out_signature="")
|
||||
def set_config(self, config):
|
||||
"""Set the config with values from dictionary"""
|
||||
config = deluge.common.pythonize(config)
|
||||
# Load all the values into the configuration
|
||||
for key in self.core.config.keys():
|
||||
self.core.config[key] = config[key]
|
||||
self.core.apply_prefs()
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="", out_signature="v")
|
||||
def get_download_rate(self):
|
||||
return self.core.get_state()['download_rate']
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="", out_signature="v")
|
||||
def get_upload_rate(self):
|
||||
return self.core.get_state()['upload_rate']
|
||||
|
||||
|
||||
#internal
|
||||
def _add_torrent(self, filename):
|
||||
#dbus types break pickle, again.....
|
||||
filename = unicode(filename)
|
||||
target = self.config.get("download_dir")
|
||||
target = self.core.config.get("default_download_path")
|
||||
|
||||
torrent_id = self.core.add_torrent(filename, target,
|
||||
self.interface.config.get("use_compact_storage"))
|
||||
|
|
|
@ -31,67 +31,15 @@
|
|||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
|
||||
|
||||
from webserver_common import TORRENT_KEYS, STATE_MESSAGES
|
||||
import webserver_common as ws
|
||||
from webserver_framework import *
|
||||
|
||||
|
||||
import webpy022 as web
|
||||
from webpy022.http import seeother, url
|
||||
from webpy022.utils import Storage
|
||||
|
||||
from md5 import md5
|
||||
import base64
|
||||
from deluge.common import fsize
|
||||
from operator import attrgetter
|
||||
|
||||
#utils:
|
||||
def check_pwd(pwd):
|
||||
m = md5()
|
||||
m.update(ws.config.get('pwd_salt'))
|
||||
m.update(pwd)
|
||||
return (m.digest() == ws.config.get('pwd_md5'))
|
||||
|
||||
def get_torrent_status(torrent_id):
|
||||
"""
|
||||
helper method.
|
||||
enhance ws.proxy.get_torrent_status with some extra data
|
||||
"""
|
||||
status = ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS)
|
||||
status["id"] = torrent_id
|
||||
|
||||
#for naming the status-images
|
||||
status["calc_state_str"] = "downloading"
|
||||
if status["paused"]:
|
||||
status["calc_state_str"] = "inactive"
|
||||
elif status["is_seed"]:
|
||||
status["calc_state_str"] = "seeding"
|
||||
|
||||
#action for torrent_pause
|
||||
if status["calc_state_str"] == "inactive":
|
||||
status["action"] = "start"
|
||||
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" : (fsize(status["total_done"])
|
||||
+ " (" + fsize(status["total_download"]) + ")"),
|
||||
"calc_total_uploaded": (fsize(status['uploaded_memory']
|
||||
+ status["total_payload_upload"]) + " ("
|
||||
+ fsize(status["total_upload"]) + ")"),
|
||||
})
|
||||
|
||||
return Storage(status) #Storage for easy templating.
|
||||
|
||||
#/utils
|
||||
import os
|
||||
|
||||
#routing:
|
||||
urls = (
|
||||
|
@ -107,15 +55,21 @@ urls = (
|
|||
"/resume_all(.*)", "resume_all",
|
||||
"/refresh/set(.*)", "refresh_set",
|
||||
"/refresh/(.*)", "refresh",
|
||||
"/config(.*)","config",
|
||||
"/home(.*)", "home",
|
||||
"/about(.*)", "about",
|
||||
"/logout(.*)", "logout",
|
||||
#default-pages
|
||||
"/", "login",
|
||||
"", "login",
|
||||
"/", "home",
|
||||
"", "home",
|
||||
#remote-api:
|
||||
"/remote/torrent/add(.*)", "remote_torrent_add"
|
||||
)
|
||||
"/remote/torrent/add(.*)", "remote_torrent_add",
|
||||
#static:
|
||||
"/static/(.*)","static",
|
||||
"/template/static/(.*)","template_static",
|
||||
#"/downloads/(.*)","downloads" disabled until it can handle large downloads.
|
||||
|
||||
)
|
||||
#/routing
|
||||
|
||||
#pages:
|
||||
|
@ -133,15 +87,10 @@ class login:
|
|||
start_session()
|
||||
do_redirect()
|
||||
elif vars.redir:
|
||||
seeother(url('/login',error=1,redir=vars.redir))
|
||||
seeother(url('/login',error=1, redir=vars.redir))
|
||||
else:
|
||||
seeother('/login?error=1')
|
||||
|
||||
class home:
|
||||
@check_session
|
||||
def GET(self, name):
|
||||
do_redirect()
|
||||
|
||||
class index:
|
||||
"page containing the torrent list."
|
||||
@auto_refreshed
|
||||
|
@ -150,7 +99,7 @@ class index:
|
|||
vars = web.input(sort=None, order=None)
|
||||
|
||||
status_rows = [get_torrent_status(torrent_id)
|
||||
for torrent_id in ws.proxy.get_torrent_state()]
|
||||
for torrent_id in ws.proxy.get_session_state()]
|
||||
|
||||
#sorting:
|
||||
if vars.sort:
|
||||
|
@ -164,21 +113,19 @@ class index:
|
|||
return ws.render.index(status_rows)
|
||||
|
||||
class torrent_info:
|
||||
"torrent details"
|
||||
@auto_refreshed
|
||||
@deluge_page
|
||||
def GET(self, torrent_id):
|
||||
return ws.render.torrent_info(get_torrent_status(torrent_id))
|
||||
|
||||
class torrent_pause:
|
||||
"start/stop a torrent"
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
vars = web.input(stop = None, start = None, redir = None)
|
||||
if vars.stop:
|
||||
ws.proxy.pause_torrent(vars.stop)
|
||||
ws.proxy.pause_torrent([vars.stop])
|
||||
elif vars.start:
|
||||
ws.proxy.resume_torrent(vars.start)
|
||||
ws.proxy.resume_torrent([vars.start])
|
||||
|
||||
do_redirect()
|
||||
|
||||
|
@ -194,7 +141,7 @@ class torrent_add:
|
|||
if vars.url and vars.torrent.filename:
|
||||
error_page(_("Choose an url or a torrent, not both."))
|
||||
if vars.url:
|
||||
ws.proxy.add_torrent_url(vars.url)
|
||||
ws.proxy.add_torrent_url(vars.url )
|
||||
do_redirect()
|
||||
elif vars.torrent.filename:
|
||||
data = vars.torrent.file.read()
|
||||
|
@ -227,8 +174,7 @@ class torrent_delete:
|
|||
return ws.render.torrent_delete(get_torrent_status(torrent_id))
|
||||
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
torrent_id = name
|
||||
def POST(self, torrent_id):
|
||||
vars = web.input(data_also = None, torrent_also = None)
|
||||
data_also = bool(vars.data_also)
|
||||
torrent_also = bool(vars.torrent_also)
|
||||
|
@ -238,30 +184,26 @@ class torrent_delete:
|
|||
|
||||
class torrent_queue_up:
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
torrent_id = name
|
||||
def POST(self, torrent_id):
|
||||
ws.proxy.queue_up(torrent_id)
|
||||
do_redirect()
|
||||
|
||||
class torrent_queue_down:
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
torrent_id = name
|
||||
def POST(self, torrent_id):
|
||||
ws.proxy.queue_down(torrent_id)
|
||||
do_redirect()
|
||||
|
||||
class pause_all:
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
for torrent_id in ws.proxy.get_torrent_state():
|
||||
ws.proxy.pause_torrent(torrent_id)
|
||||
ws.proxy.pause_torrent(ws.proxy.get_session_state())
|
||||
do_redirect()
|
||||
|
||||
class resume_all:
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
for torrent_id in ws.proxy.get_torrent_state():
|
||||
ws.proxy.resume_torrent(torrent_id)
|
||||
ws.proxy.resume_torrent(ws.proxy.get_session_state())
|
||||
do_redirect()
|
||||
|
||||
class refresh:
|
||||
|
@ -287,13 +229,61 @@ class refresh_set:
|
|||
else:
|
||||
error_page(_('refresh must be > 0'))
|
||||
|
||||
class config:
|
||||
"""core config
|
||||
TODO:good validation.
|
||||
"""
|
||||
cfg_form = web.form.Form(
|
||||
web.form.Dropdown('max_download', ws.SPEED_VALUES,
|
||||
description=_('Download Speed Limit'),
|
||||
post='%s Kib/sec' % ws.proxy.get_config_value('max_download_speed')
|
||||
)
|
||||
,web.form.Dropdown('max_upload', ws.SPEED_VALUES,
|
||||
description=_('Upload Speed Limit'),
|
||||
post='%s Kib/sec' % ws.proxy.get_config_value('max_upload_speed')
|
||||
)
|
||||
)
|
||||
|
||||
@deluge_page
|
||||
def GET(self, name):
|
||||
return ws.render.config(self.cfg_form())
|
||||
|
||||
def POST(self, name):
|
||||
vars = web.input(max_download=None, max_upload=None)
|
||||
|
||||
#self.config.set("max_download_speed", float(str_bwdown))
|
||||
raise NotImplementedError('todo')
|
||||
|
||||
class home:
|
||||
@check_session
|
||||
def GET(self, name):
|
||||
do_redirect()
|
||||
|
||||
class about:
|
||||
@deluge_page_noauth
|
||||
def GET(self, name):
|
||||
return ws.render.about()
|
||||
|
||||
#/pages
|
||||
class logout:
|
||||
def POST(self, name):
|
||||
end_session()
|
||||
seeother('/login')
|
||||
|
||||
class static(static_handler):
|
||||
base_dir = os.path.join(os.path.dirname(__file__),'static')
|
||||
|
||||
class template_static(static_handler):
|
||||
def get_base_dir(self):
|
||||
return os.path.join(os.path.dirname(__file__),
|
||||
'templates/%s/static' % ws.config.get('template'))
|
||||
|
||||
class downloads(static_handler):
|
||||
def GET(self, name):
|
||||
self.base_dir = ws.proxy.get_config_value('default_download_path')
|
||||
if not ws.config.get('share_downloads'):
|
||||
raise Exception('Access to downloads is forbidden.')
|
||||
return static_handler.GET(self, name)
|
||||
#/pages
|
||||
|
||||
def WebServer():
|
||||
return create_webserver(urls, globals())
|
||||
|
@ -307,4 +297,3 @@ def run():
|
|||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
87
|
||||
112
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env python
|
||||
from deluge_webserver import *
|
||||
web.run(urls, globals())
|
||||
import deluge_webserver
|
||||
deluge_webserver.run()
|
||||
|
|
10
plugins/WebUi/scripts/add_torrent_to_deluge_webui
Executable file
10
plugins/WebUi/scripts/add_torrent_to_deluge_webui
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
pwd=deluge
|
||||
url=http://localhost:8112
|
||||
|
||||
for arg in "$@"
|
||||
do
|
||||
curl -F torrent=@"$arg" -F pwd=$pwd $url/remote/torrent/add
|
||||
done
|
||||
|
BIN
plugins/WebUi/static/images/tango/system-log-out.png
Normal file
BIN
plugins/WebUi/static/images/tango/system-log-out.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 799 B |
136
plugins/WebUi/static_handler.py
Normal file
136
plugins/WebUi/static_handler.py
Normal file
|
@ -0,0 +1,136 @@
|
|||
#!/usr/bin/env python
|
||||
#(c) Martijn Voncken, mvoncken@gmail.com
|
||||
#Same Licence as web.py 0.22 ->Public Domain
|
||||
#
|
||||
"""
|
||||
static fileserving for web.py
|
||||
without the need for wsgi wrapper magic.
|
||||
"""
|
||||
import webpy022 as web
|
||||
from webpy022.http import seeother, url
|
||||
|
||||
import posixpath
|
||||
import urlparse
|
||||
import urllib
|
||||
import mimetypes
|
||||
import os
|
||||
import datetime
|
||||
import cgi
|
||||
from StringIO import StringIO
|
||||
mimetypes.init() # try to read system mime.types
|
||||
|
||||
class static_handler:
|
||||
"""
|
||||
mostly c&p from SimpleHttpServer
|
||||
serves relative from start location
|
||||
"""
|
||||
base_dir = './'
|
||||
extensions_map = mimetypes.types_map
|
||||
|
||||
def get_base_dir(self):
|
||||
#override this if you have a config that changes the base dir at runtime
|
||||
#deluge on windows :(
|
||||
return self.base_dir
|
||||
|
||||
def GET(self, path):
|
||||
path = self.translate_path(path)
|
||||
if os.path.isdir(path):
|
||||
if not path.endswith('/'):
|
||||
path += "/"
|
||||
return self.list_directory(path)
|
||||
|
||||
ctype = self.guess_type(path)
|
||||
|
||||
try:
|
||||
f = open(path, 'rb')
|
||||
except IOError:
|
||||
raise Exception('file not found:%s' % path)
|
||||
#web.header("404", "File not found")
|
||||
#return
|
||||
web.header("Content-type", ctype)
|
||||
fs = os.fstat(f.fileno())
|
||||
web.header("Content-Length", str(fs[6]))
|
||||
web.lastmodified(datetime.datetime.fromtimestamp(fs.st_mtime))
|
||||
print f.read()
|
||||
|
||||
def translate_path(self, path):
|
||||
"""Translate a /-separated PATH to the local filename syntax.
|
||||
|
||||
Components that mean special things to the local file system
|
||||
(e.g. drive or directory names) are ignored. (XXX They should
|
||||
probably be diagnosed.)
|
||||
|
||||
"""
|
||||
# abandon query parameters
|
||||
path = urlparse.urlparse(path)[2]
|
||||
path = posixpath.normpath(urllib.unquote(path))
|
||||
words = path.split('/')
|
||||
words = filter(None, words)
|
||||
path = self.get_base_dir()
|
||||
for word in words:
|
||||
drive, word = os.path.splitdrive(word)
|
||||
head, word = os.path.split(word)
|
||||
if word in (os.curdir, os.pardir): continue
|
||||
path = os.path.join(path, word)
|
||||
return path
|
||||
|
||||
def guess_type(self, path):
|
||||
base, ext = posixpath.splitext(path)
|
||||
if ext in self.extensions_map:
|
||||
return self.extensions_map[ext]
|
||||
ext = ext.lower()
|
||||
if ext in self.extensions_map:
|
||||
return self.extensions_map[ext]
|
||||
else:
|
||||
return 'application/octet-stream'
|
||||
|
||||
|
||||
def list_directory(self, path):
|
||||
"""Helper to produce a directory listing (absent index.html).
|
||||
|
||||
Return value is either a file object, or None (indicating an
|
||||
error). In either case, the headers are sent, making the
|
||||
interface the same as for send_head().
|
||||
#TODO ->use web.py +template!
|
||||
"""
|
||||
try:
|
||||
list = os.listdir(path)
|
||||
except os.error:
|
||||
web.header('404', "No permission to list directory")
|
||||
return None
|
||||
list.sort(key=lambda a: a.lower())
|
||||
f = StringIO()
|
||||
displaypath = cgi.escape(urllib.unquote(path))
|
||||
f.write("<title>Directory listing for %s</title>\n" % displaypath)
|
||||
f.write("<h2>Directory listing for %s</h2>\n" % displaypath)
|
||||
f.write("<hr>\n<ul>\n")
|
||||
for name in list:
|
||||
fullname = os.path.join(path, name)
|
||||
displayname = linkname = name
|
||||
# Append / for directories or @ for symbolic links
|
||||
if os.path.isdir(fullname):
|
||||
displayname = name + "/"
|
||||
linkname = name + "/"
|
||||
if os.path.islink(fullname):
|
||||
displayname = name + "@"
|
||||
# Note: a link to a directory displays with @ and links with /
|
||||
f.write('<li><a href="%s">%s</a>\n'
|
||||
% (urllib.quote(linkname), cgi.escape(displayname)))
|
||||
f.write("</ul>\n<hr>\n")
|
||||
length = f.tell()
|
||||
f.seek(0)
|
||||
|
||||
web.header("Content-type", "text/html")
|
||||
web.header("Content-Length", str(length))
|
||||
print f.read()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#example:
|
||||
class usr_static(static_handler):
|
||||
base_dir = os.path.expanduser('~')
|
||||
|
||||
urls = ('/relative/(.*)','static_handler',
|
||||
'/(.*)','usr_static')
|
||||
|
||||
web.run(urls,globals())
|
|
@ -7,6 +7,7 @@ $:render.header(_('About'))
|
|||
<li><a href="http://deluge-torrent.org">Deluge</a></li>
|
||||
<li><a href="http://forum.deluge-torrent.org/viewtopic.php?f=9&t=425">
|
||||
WebUi forum Thread</a>
|
||||
|
||||
</li>
|
||||
<li><a href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GPL v2
|
||||
</a></li>
|
||||
|
@ -18,6 +19,7 @@ $:render.header(_('About'))
|
|||
<ul>
|
||||
<li>Martijn Voncken</li>
|
||||
</ul>
|
||||
|
||||
<h3>Template</h3>
|
||||
<ul>
|
||||
<li>Martijn Voncken</li>
|
||||
|
@ -27,6 +29,7 @@ $:render.header(_('About'))
|
|||
<ul>
|
||||
<li>Zach Tibbitts</li>
|
||||
<li>Alon Zakai</li>
|
||||
|
||||
<li>Alon Zakai</li>
|
||||
<li>Marcos Pinto</li>
|
||||
<li>Andrew Resch</li>
|
||||
|
@ -36,4 +39,4 @@ $:render.header(_('About'))
|
|||
*and all other authors/helpers/contributors I forgot to mention.
|
||||
</div>
|
||||
|
||||
$:render.footer()
|
||||
$:render.footer()
|
||||
|
|
10
plugins/WebUi/templates/deluge/config.html
Normal file
10
plugins/WebUi/templates/deluge/config.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
$def with (form)
|
||||
$:render.header(_('Config'))
|
||||
|
||||
<div class="error">Not Implemented!</div>
|
||||
<form method="POST">
|
||||
$:form.render()
|
||||
<input type="submit" value="$_('Apply')"/>
|
||||
</form>
|
||||
|
||||
$:render.footer()
|
|
@ -51,8 +51,10 @@ $for torrent in torrent_list:
|
|||
$:render.part_button('GET', '/torrent/add', _('Add torrent'), 'tango/list-add.png')
|
||||
$:render.part_button('POST', '/pause_all', _('Pause all'), 'tango/media-playback-pause.png')
|
||||
$:render.part_button('POST', '/resume_all', _('Resume all'), 'tango/media-playback-start.png')
|
||||
$:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png')
|
||||
</div>
|
||||
|
||||
$:render.part_refresh()
|
||||
$:part_stats()
|
||||
|
||||
$:render.footer()
|
||||
|
||||
|
|
|
@ -2,21 +2,25 @@ $def with (method, url, title, image='')
|
|||
<div class="deluge_button">
|
||||
<form method="$method" action="$url" class="deluge_button">
|
||||
<input type="hidden" name="redir" value="$self_url()">
|
||||
<button type="submit" class="deluge_button">
|
||||
$if (get_config('button_style') == 0):
|
||||
<button type="submit" class="deluge_button" alt="$title">
|
||||
$title
|
||||
$if image:
|
||||
<image src="/static/images/$image" class="button" alt="$title"/>
|
||||
</button>
|
||||
|
||||
$if (get_config('button_style') == 1):
|
||||
$if image:
|
||||
<image src="/static/images/$image" class="button" alt="$title"/>
|
||||
<input type="image" image src="/static/images/$image" class="img_button" alt="$title"/>
|
||||
$else:
|
||||
<button type="submit" class="deluge_button" alt="$title">
|
||||
$title
|
||||
</button>
|
||||
|
||||
$if (get_config('button_style') == 2):
|
||||
<button type="submit" class="deluge_button" alt="$title">
|
||||
$title
|
||||
</button>
|
||||
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
30
plugins/WebUi/templates/deluge/part_stats.html
Normal file
30
plugins/WebUi/templates/deluge/part_stats.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
$def with (stats)
|
||||
|
||||
|
||||
<div class="panel" id='refresh_panel'>
|
||||
|
||||
$_('Auto refresh:')
|
||||
$if getcookie('auto_refresh') == '1':
|
||||
($getcookie('auto_refresh_secs')) $_('seconds')
|
||||
$:render.part_button('GET', '/refresh/set', _('Set'), 'tango/preferences-system.png')
|
||||
$:render.part_button('POST', '/refresh/off', _('Disable'), 'tango/process-stop.png')
|
||||
$else:
|
||||
$_('Off')
|
||||
$:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png')
|
||||
$#end
|
||||
</div>
|
||||
|
||||
<div class="panel" id='refresh_panel'>
|
||||
<a href='/config'>
|
||||
$_('Down Speed') : $stats.download_rate ($stats.max_download)
|
||||
|
||||
$_('Up Speed') : $stats.upload_rate ($stats.max_upload)
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
(<a href='/about'>$_('About')</a>)
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -84,6 +84,7 @@ $:render.part_button('GET', '/torrent/delete/' + str(torrent.id), _('Remove'), '
|
|||
|
||||
|
||||
<br>
|
||||
<!--
|
||||
[<a onclick="javascript:toggle_dump()">$_('Debug:Data Dump')</a>]
|
||||
|
||||
<pre style="background-color:white;color:black;display:none" id="data_dump">
|
||||
|
@ -102,6 +103,8 @@ function toggle_dump(){
|
|||
}
|
||||
}
|
||||
</script>
|
||||
-->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -114,6 +117,6 @@ $:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up
|
|||
$:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/down.png')
|
||||
</div>
|
||||
|
||||
$:render.part_refresh()
|
||||
$:part_stats()
|
||||
|
||||
$:render.footer()
|
||||
|
|
3
plugins/WebUi/templates/example/footer.html
Normal file
3
plugins/WebUi/templates/example/footer.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
</body>
|
||||
</html>
|
12
plugins/WebUi/templates/example/header.html
Normal file
12
plugins/WebUi/templates/example/header.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
$def with (title)
|
||||
<html>
|
||||
<head>
|
||||
<title>Deluge(example) : $title</title>
|
||||
<link rel="icon" href="/static/images/deluge_icon.gif" type="image/gif" />
|
||||
<link rel="shortcut icon" href="/static/images/deluge_icon.gif" type="image/gif" />
|
||||
</head>
|
||||
<body>
|
||||
<img src="/template/static/example.png">
|
||||
<a href=/home>[HOME]</a>
|
||||
|
||||
<h1>$title</h1>
|
42
plugins/WebUi/templates/example/index.html
Normal file
42
plugins/WebUi/templates/example/index.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
$def with (torrent_list)
|
||||
$:render.header(_('Torrent list'))
|
||||
|
||||
<form action="/torrent/pause" method="POST">
|
||||
<table class="torrent_list" border=1>
|
||||
<tr>
|
||||
$:(sort_head('calc_state_str', 'S'))
|
||||
$:(sort_head('queue_pos', '#'))
|
||||
$:(sort_head('name', _('Name')))
|
||||
$:(sort_head('progress', _('Progress')))
|
||||
</tr>
|
||||
$#4-space indentation is mandatory for for-loops in templetor!
|
||||
$for torrent in torrent_list:
|
||||
<tr>
|
||||
<td><input type="image"
|
||||
src="/static/images/$(torrent.calc_state_str)16.png"
|
||||
name="$torrent.action" value="$torrent.id">
|
||||
</td>
|
||||
<td>$torrent.queue_pos</td>
|
||||
<td style="width:100px; overflow:hidden;white-space: nowrap">
|
||||
<a href="/torrent/info/$torrent.id">$(crop(torrent.name, 40))</a></td>
|
||||
<td class="progress_bar">
|
||||
<div class="progress_bar_outer">
|
||||
<div class="progress_bar" style="width:$(torrent.progress)%">
|
||||
$torrent.message
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="panel" bgcolor="5555AA">
|
||||
$:render.part_button('GET', '/torrent/add', _('Add torrent'), 'tango/list-add.png')
|
||||
$:render.part_button('POST', '/pause_all', _('Pause all'), 'tango/media-playback-pause.png')
|
||||
$:render.part_button('POST', '/resume_all', _('Resume all'), 'tango/media-playback-start.png')
|
||||
$:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png')
|
||||
</div>
|
||||
|
||||
$:render.footer()
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
revision-id: mvoncken@gmail.com-20070930083408-sv8mo0mi1rbjnfvk
|
||||
date: 2007-10-23 15:10:08 +0200
|
||||
build-date: 2007-10-23 15:34:50 +0200
|
||||
revno: 87
|
||||
revno: 112
|
||||
branch-nick: WebUi
|
||||
|
|
|
@ -29,40 +29,96 @@
|
|||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
|
||||
"""
|
||||
initializes config,render and proxy.
|
||||
contains all hacks to support running in process0.5 ,run inside-gtk0.5 and
|
||||
run in process0.6
|
||||
"""
|
||||
|
||||
import os
|
||||
import deluge
|
||||
from deluge.common import INSTALL_PREFIX
|
||||
import random
|
||||
import pickle
|
||||
import sys
|
||||
from webpy022 import template
|
||||
|
||||
random.seed()
|
||||
path = os.path.dirname(__file__)
|
||||
|
||||
config_file = deluge.common.CONFIG_DIR + "/webui.conf"
|
||||
try:
|
||||
_('translate something')
|
||||
except:
|
||||
import gettext
|
||||
gettext.install('~/') #no translations :(
|
||||
|
||||
#a bit hacky way of detecting i'm in the deluge gui or in a process :(
|
||||
if not hasattr(deluge,'pref'):
|
||||
try:
|
||||
config_dir = deluge.common.CONFIG_DIR
|
||||
except:
|
||||
config_dir = os.path.expanduser("~/.config/deluge")
|
||||
|
||||
config_file = os.path.join(config_dir,'webui.conf')
|
||||
session_file = os.path.join(config_dir,'webui.sessions')
|
||||
|
||||
|
||||
class subclassed_render(object):
|
||||
"""
|
||||
try to use the html template in configured dir.
|
||||
not available : use template in /deluge/
|
||||
"""
|
||||
def __init__(self, template_dirname, cache=False):
|
||||
self.base_template = template.render(
|
||||
os.path.join(path, 'templates/deluge/'),
|
||||
cache=cache)
|
||||
|
||||
self.sub_template = template.render(
|
||||
os.path.join(path, 'templates/%s/' % template_dirname),
|
||||
cache=cache)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if hasattr(self.sub_template, attr):
|
||||
return getattr(self.sub_template, attr)
|
||||
else:
|
||||
return getattr(self.base_template, attr)
|
||||
|
||||
def init_process():
|
||||
globals()['config'] = pickle.load(open(config_file))
|
||||
globals()['render'] = subclassed_render(config.get('template'),
|
||||
config.get('cache_templates'))
|
||||
|
||||
def init_06():
|
||||
import deluge.ui.client as proxy
|
||||
proxy.set_core_uri('http://localhost:58846') #How to configure this?
|
||||
|
||||
init_process()
|
||||
globals()['proxy'] = proxy
|
||||
|
||||
def init_05():
|
||||
import dbus
|
||||
init_process()
|
||||
bus = dbus.SessionBus()
|
||||
proxy = bus.get_object("org.deluge_torrent.dbusplugin"
|
||||
, "/org/deluge_torrent/DelugeDbusPlugin")
|
||||
config = pickle.load(open(config_file))
|
||||
render = template.render('templates/%s/' % config.get('template'),
|
||||
cache=config.get('cache_templates'))
|
||||
globals()['proxy'] = proxy
|
||||
|
||||
def init():
|
||||
def init_gtk_05():
|
||||
#appy possibly changed config-vars, only called in when runing inside gtk.
|
||||
path = os.path.dirname(__file__)
|
||||
from dbus_interface import get_dbus_manager
|
||||
globals()['proxy'] = get_dbus_manager()
|
||||
globals()['config'] = deluge.pref.Preferences(config_file, False)
|
||||
globals()['render'] = template.render(os.path.join(path, 'templates/%s/' %
|
||||
config.get('template')), cache=config.get('cache_templates'))
|
||||
globals()['render'] = subclassed_render(config.get('template'),
|
||||
config.get('cache_templates'))
|
||||
|
||||
|
||||
#hacks to determine environment, TODO: clean up.
|
||||
if 'env=0.5' in sys.argv:
|
||||
init_05()
|
||||
elif not hasattr(deluge, 'common'):
|
||||
init_06()
|
||||
elif not hasattr(deluge,'pref'):
|
||||
init_05()
|
||||
|
||||
|
||||
REVNO = '0.56.stable.' + open(os.path.join(os.path.dirname(__file__),'revno')).read()
|
||||
#constants
|
||||
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',
|
||||
|
@ -82,3 +138,30 @@ STATE_MESSAGES = (_("Queued"),
|
|||
_("Finished"),
|
||||
_("Seeding"),
|
||||
_("Allocating"))
|
||||
|
||||
SPEED_VALUES = [
|
||||
(-1, 'Unlimited'),
|
||||
(5, '5.0 Kib/sec'),
|
||||
(10, '10.0 Kib/sec'),
|
||||
(15, '15.0 Kib/sec'),
|
||||
(25, '25.0 Kib/sec'),
|
||||
(30, '30.0 Kib/sec'),
|
||||
(50, '50.0 Kib/sec'),
|
||||
(80, '80.0 Kib/sec'),
|
||||
(300, '300.0 Kib/sec'),
|
||||
(500, '500.0 Kib/sec')
|
||||
]
|
||||
|
||||
COOKIE_DEFAULTS = {
|
||||
'auto_refresh_secs':'10'
|
||||
}
|
||||
|
||||
try:
|
||||
SESSIONS = pickle.load(open(session_file))
|
||||
except:
|
||||
SESSIONS = []
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -45,15 +45,21 @@ import webpy022 as web
|
|||
from webpy022.webapi import cookies, setcookie as w_setcookie
|
||||
from webpy022.http import seeother, url
|
||||
from webpy022 import template,changequery as self_url
|
||||
from webpy022.utils import Storage
|
||||
from static_handler import static_handler
|
||||
|
||||
from deluge.common import fsize,fspeed
|
||||
|
||||
import traceback
|
||||
import random
|
||||
from operator import attrgetter
|
||||
import datetime
|
||||
import pickle
|
||||
from md5 import md5
|
||||
|
||||
from deluge import common
|
||||
from webserver_common import REVNO, VERSION
|
||||
from webserver_common import REVNO, VERSION, COOKIE_DEFAULTS
|
||||
import webserver_common as ws
|
||||
|
||||
from debugerror import deluge_debugerror
|
||||
|
||||
#init:
|
||||
|
@ -65,14 +71,22 @@ def setcookie(key, val):
|
|||
"""add 30 days expires header for persistent cookies"""
|
||||
return w_setcookie(key, val , expires=2592000)
|
||||
|
||||
SESSIONS = [] #dumb sessions.
|
||||
#really simple sessions, to bad i had to implement them myself.
|
||||
def start_session():
|
||||
session_id = str(random.random())
|
||||
SESSIONS.append(session_id)
|
||||
ws.SESSIONS.append(session_id)
|
||||
if len(ws.SESSIONS) > 20: #save max 20 sessions?
|
||||
ws.SESSIONS = ws.SESSIONS[-20:]
|
||||
#not thread safe! , but a verry rare bug.
|
||||
pickle.dump(ws.SESSIONS, open(ws.session_file,'wb'))
|
||||
setcookie("session_id", session_id)
|
||||
|
||||
if getcookie('auto_refresh_secs') == None:
|
||||
setcookie('auto_refresh_secs','10')
|
||||
def end_session():
|
||||
session_id = getcookie("session_id")
|
||||
if session_id in ws.SESSIONS:
|
||||
ws.SESSIONS.remove(session_id)
|
||||
#not thread safe! , but a verry rare bug.
|
||||
pickle.dump(ws.SESSIONS, open(ws.session_file,'wb'))
|
||||
|
||||
def do_redirect():
|
||||
"""for redirects after a POST"""
|
||||
|
@ -92,7 +106,6 @@ def error_page(error):
|
|||
print ws.render.error(error)
|
||||
|
||||
def getcookie(key, default=None):
|
||||
COOKIE_DEFAULTS = {'auto_refresh_secs':'10'}
|
||||
key = str(key).strip()
|
||||
ck = cookies()
|
||||
val = ck.get(key, default)
|
||||
|
@ -120,9 +133,8 @@ def check_session(func):
|
|||
"""
|
||||
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:
|
||||
if ck.has_key("session_id") and ck["session_id"] in ws.SESSIONS:
|
||||
return func(self, name) #ok, continue..
|
||||
elif vars.redir_after_login:
|
||||
seeother(url("/login",redir=self_url()))
|
||||
|
@ -154,6 +166,77 @@ def remote(func):
|
|||
print traceback.format_exc()
|
||||
return deco
|
||||
|
||||
#utils:
|
||||
def check_pwd(pwd):
|
||||
m = md5()
|
||||
m.update(ws.config.get('pwd_salt'))
|
||||
m.update(pwd)
|
||||
return (m.digest() == ws.config.get('pwd_md5'))
|
||||
|
||||
def get_stats():
|
||||
stats = Storage({
|
||||
'download_rate':fspeed(ws.proxy.get_download_rate()),
|
||||
'upload_rate':fspeed(ws.proxy.get_upload_rate()),
|
||||
'max_download':ws.proxy.get_config_value('max_download_speed_bps'),
|
||||
'max_upload':ws.proxy.get_config_value('max_upload_speed_bps'),
|
||||
})
|
||||
if stats.max_upload < 0:
|
||||
stats.max_upload = _("Unlimited")
|
||||
else:
|
||||
stats.max_upload = fspeed(stats.max_upload)
|
||||
|
||||
if stats.max_download < 0:
|
||||
stats.max_download = _("Unlimited")
|
||||
else:
|
||||
stats.max_download = fspeed(stats.max_download)
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def get_torrent_status(torrent_id):
|
||||
"""
|
||||
helper method.
|
||||
enhance ws.proxy.get_torrent_status with some extra data
|
||||
"""
|
||||
status = Storage(ws.proxy.get_torrent_status(torrent_id,ws.TORRENT_KEYS))
|
||||
|
||||
#add missing values for deluge 0.6:
|
||||
for key in ws.TORRENT_KEYS:
|
||||
if not key in status:
|
||||
status[key] = 0
|
||||
|
||||
status["id"] = torrent_id
|
||||
|
||||
#for naming the status-images
|
||||
status["calc_state_str"] = "downloading"
|
||||
if status["paused"]:
|
||||
status["calc_state_str"] = "inactive"
|
||||
elif status["is_seed"]:
|
||||
status["calc_state_str"] = "seeding"
|
||||
|
||||
#action for torrent_pause
|
||||
if status["calc_state_str"] == "inactive":
|
||||
status["action"] = "start"
|
||||
else:
|
||||
status["action"] = "stop"
|
||||
|
||||
if status["paused"]:
|
||||
status["message"] = _("Paused %s%%") % status['progress']
|
||||
else:
|
||||
status["message"] = "%s %i%%" % (ws.STATE_MESSAGES[status["state"]]
|
||||
, status['progress'])
|
||||
|
||||
#add some pre-calculated values
|
||||
status.update({
|
||||
"calc_total_downloaded" : (fsize(status["total_done"])
|
||||
+ " (" + fsize(status["total_download"]) + ")"),
|
||||
"calc_total_uploaded": (fsize(status['uploaded_memory']
|
||||
+ status["total_payload_upload"]) + " ("
|
||||
+ fsize(status["total_upload"]) + ")"),
|
||||
})
|
||||
return status
|
||||
#/utils
|
||||
|
||||
#template-defs:
|
||||
def template_crop(text, end):
|
||||
if len(text) > end:
|
||||
|
@ -176,12 +259,15 @@ def template_sort_head(id,name):
|
|||
|
||||
return ws.render.sort_column_head(id, name, order, active_up, active_down)
|
||||
|
||||
def template_part_stats():
|
||||
return ws.render.part_stats(get_stats())
|
||||
|
||||
def get_config(var):
|
||||
return ws.config.get(var)
|
||||
|
||||
template.Template.globals.update({
|
||||
'sort_head': template_sort_head,
|
||||
'part_stats':template_part_stats,
|
||||
'crop': template_crop,
|
||||
'_': _ , #gettext/translations
|
||||
'str': str, #because % in templetor is broken.
|
||||
|
@ -198,145 +284,20 @@ template.Template.globals.update({
|
|||
})
|
||||
#/template-defs
|
||||
|
||||
def create_webserver(urls, methods):
|
||||
from webpy022.request import webpyfunc
|
||||
from webpy022 import webapi
|
||||
from gtk_cherrypy_wsgiserver import CherryPyWSGIServer
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
#Some copy and paste from web.py
|
||||
#mostly caused by /static
|
||||
#TODO : FIX THIS.
|
||||
#static-files serving should be moved to the normal webserver!
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
from gtk_cherrypy_wsgiserver import CherryPyWSGIServer
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
|
||||
from webpy022.request import webpyfunc
|
||||
from webpy022 import webapi
|
||||
import os
|
||||
|
||||
import posixpath
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
class RelativeHandler(SimpleHTTPRequestHandler):
|
||||
def translate_path(self, path):
|
||||
"""Translate a /-separated PATH to the local filename syntax.
|
||||
|
||||
Components that mean special things to the local file system
|
||||
(e.g. drive or directory names) are ignored. (XXX They should
|
||||
probably be diagnosed.)
|
||||
|
||||
"""
|
||||
# abandon query parameters
|
||||
path = urlparse.urlparse(path)[2]
|
||||
path = posixpath.normpath(urllib.unquote(path))
|
||||
words = path.split('/')
|
||||
words = filter(None, words)
|
||||
path = os.path.dirname(__file__)
|
||||
for word in words:
|
||||
drive, word = os.path.splitdrive(word)
|
||||
head, word = os.path.split(word)
|
||||
if word in (os.curdir, os.pardir): continue
|
||||
path = os.path.join(path, word)
|
||||
return path
|
||||
|
||||
class StaticApp(RelativeHandler):
|
||||
"""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):
|
||||
func = webapi.wsgifunc(webpyfunc(urls,methods, False))
|
||||
server_address=("0.0.0.0",ws.config.get('port'))
|
||||
|
||||
func = WSGIWrapper(func)
|
||||
func = webapi.wsgifunc(webpyfunc(urls, methods, False))
|
||||
server_address=("0.0.0.0", int(ws.config.get('port')))
|
||||
server = CherryPyWSGIServer(server_address, func, server_name="localhost")
|
||||
|
||||
|
||||
print "(created) http://%s:%d/" % server_address
|
||||
|
||||
print "http://%s:%d/" % server_address
|
||||
return server
|
||||
|
||||
#------
|
||||
__all__ = ['deluge_page_noauth', 'deluge_page', 'remote',
|
||||
'auto_refreshed', 'check_session',
|
||||
'do_redirect', 'error_page','start_session','getcookie'
|
||||
,'create_webserver','setcookie']
|
||||
|
||||
|
||||
|
||||
,'setcookie','create_webserver','end_session',
|
||||
'get_torrent_status', 'check_pwd','static_handler']
|
||||
|
|
|
@ -54,4 +54,7 @@ plugins/WebSeed/__init__.py
|
|||
plugins/WebSeed/webseed.glade
|
||||
plugins/Scheduler/__init__.py
|
||||
plugins/Scheduler/plugin.py
|
||||
plugins/WebUI/__init__.py
|
||||
plugins/WebUi/__init__.py
|
||||
plugins/WebUi/webserver_common.py
|
||||
plugins/WebUi/deluge_webserver.py
|
||||
plugins/WebUi/scripts/template_strings.py
|
||||
|
|
238
po/deluge.pot
238
po/deluge.pot
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2007-10-25 21:56-0500\n"
|
||||
"POT-Creation-Date: 2007-10-27 20:34-0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -88,7 +88,7 @@ msgstr ""
|
|||
msgid "<b>Torrent Info</b>"
|
||||
msgstr ""
|
||||
|
||||
#: glade/delugegtk.glade:750
|
||||
#: glade/delugegtk.glade:750 plugins/WebUi/scripts/template_strings.py:8
|
||||
msgid "Details"
|
||||
msgstr ""
|
||||
|
||||
|
@ -149,11 +149,13 @@ msgid "Status"
|
|||
msgstr ""
|
||||
|
||||
#: glade/delugegtk.glade:965 src/interface.py:602
|
||||
#: plugins/WebUi/scripts/template_strings.py:19
|
||||
msgid "Seeders"
|
||||
msgstr ""
|
||||
|
||||
#: glade/delugegtk.glade:974 src/interface.py:605
|
||||
#: plugins/TorrentPeers/__init__.py:72
|
||||
#: plugins/WebUi/scripts/template_strings.py:15
|
||||
msgid "Peers"
|
||||
msgstr ""
|
||||
|
||||
|
@ -171,11 +173,11 @@ msgstr ""
|
|||
msgid "Time Remaining"
|
||||
msgstr ""
|
||||
|
||||
#: glade/delugegtk.glade:1010
|
||||
#: glade/delugegtk.glade:1010 plugins/WebUi/scripts/template_strings.py:4
|
||||
msgid "Availability"
|
||||
msgstr ""
|
||||
|
||||
#: glade/delugegtk.glade:1019
|
||||
#: glade/delugegtk.glade:1019 plugins/WebUi/scripts/template_strings.py:21
|
||||
msgid "Share Ratio"
|
||||
msgstr ""
|
||||
|
||||
|
@ -211,7 +213,7 @@ msgstr ""
|
|||
msgid "Remove Torrent"
|
||||
msgstr ""
|
||||
|
||||
#: glade/delugegtk.glade:1157
|
||||
#: glade/delugegtk.glade:1157 plugins/WebUi/scripts/template_strings.py:18
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
|
||||
|
@ -278,7 +280,7 @@ msgstr ""
|
|||
msgid "Delete downloaded files"
|
||||
msgstr ""
|
||||
|
||||
#: glade/dgtkpopups.glade:88
|
||||
#: glade/dgtkpopups.glade:88 plugins/WebUi/scripts/template_strings.py:6
|
||||
msgid "Delete .torrent file"
|
||||
msgstr ""
|
||||
|
||||
|
@ -294,7 +296,7 @@ msgstr ""
|
|||
msgid "Clear Finished"
|
||||
msgstr ""
|
||||
|
||||
#: glade/dgtkpopups.glade:241
|
||||
#: glade/dgtkpopups.glade:241 plugins/WebUi/scripts/template_strings.py:22
|
||||
msgid "Speed"
|
||||
msgstr ""
|
||||
|
||||
|
@ -542,6 +544,7 @@ msgid "<b>Seeding</b>"
|
|||
msgstr ""
|
||||
|
||||
#: glade/preferences_dialog.glade:964 src/core.py:91
|
||||
#: plugins/WebUi/webserver_common.py:139
|
||||
msgid "Seeding"
|
||||
msgstr ""
|
||||
|
||||
|
@ -646,6 +649,7 @@ msgstr ""
|
|||
|
||||
#: glade/preferences_dialog.glade:1478 glade/preferences_dialog.glade:1672
|
||||
#: glade/preferences_dialog.glade:1866 glade/preferences_dialog.glade:2060
|
||||
#: plugins/WebUi/scripts/template_strings.py:13
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1004,7 +1008,7 @@ msgstr ""
|
|||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/interface.py:614
|
||||
#: src/interface.py:614 plugins/WebUi/scripts/template_strings.py:10
|
||||
msgid "ETA"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1088,31 +1092,32 @@ msgstr ""
|
|||
msgid "Are you sure that you want to remove all seeding torrents?"
|
||||
msgstr ""
|
||||
|
||||
#: src/core.py:85
|
||||
#: src/core.py:85 plugins/WebUi/webserver_common.py:133
|
||||
msgid "Queued"
|
||||
msgstr ""
|
||||
|
||||
#: src/core.py:86
|
||||
#: src/core.py:86 plugins/WebUi/webserver_common.py:134
|
||||
msgid "Checking"
|
||||
msgstr ""
|
||||
|
||||
#: src/core.py:87
|
||||
#: src/core.py:87 plugins/WebUi/webserver_common.py:135
|
||||
msgid "Connecting"
|
||||
msgstr ""
|
||||
|
||||
#: src/core.py:88
|
||||
#: src/core.py:88 plugins/WebUi/webserver_common.py:136
|
||||
msgid "Downloading Metadata"
|
||||
msgstr ""
|
||||
|
||||
#: src/core.py:89 plugins/BlocklistImport/ui.py:117
|
||||
#: plugins/WebUi/webserver_common.py:137
|
||||
msgid "Downloading"
|
||||
msgstr ""
|
||||
|
||||
#: src/core.py:90
|
||||
#: src/core.py:90 plugins/WebUi/webserver_common.py:138
|
||||
msgid "Finished"
|
||||
msgstr ""
|
||||
|
||||
#: src/core.py:92
|
||||
#: src/core.py:92 plugins/WebUi/webserver_common.py:140
|
||||
msgid "Allocating"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1390,66 +1395,66 @@ msgstr ""
|
|||
msgid "Torrent Creator"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:103
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:102
|
||||
msgid "This torrent will be made from a single file"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:104
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:103
|
||||
msgid "File:"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:120
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:119
|
||||
msgid "This torrent will be made from a directory"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:121
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:120
|
||||
msgid "Folder:"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:138
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:137
|
||||
msgid "<b>Source</b>"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:184
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:182
|
||||
msgid "Save Torrent File As:"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:237
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:235
|
||||
msgid "Load this torrent into Deluge for seeding"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:238
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:236
|
||||
msgid "Add new torrent to queue"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:257
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:255
|
||||
msgid "<b>Torrent File</b>"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:305
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:302
|
||||
msgid "<b>Trackers</b>"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:352
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:348
|
||||
msgid "<b>Comments</b>"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:389
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:384
|
||||
msgid "<b>Author</b>"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:425
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:423
|
||||
msgid "Set Private Flag"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:441
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:461
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:436
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:457
|
||||
msgid ""
|
||||
"The smaller the piece sizes, the more efficient the transfers will be, but "
|
||||
"the actual \".torrent\" file will be larger"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:442
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:437
|
||||
msgid ""
|
||||
"32 KiB\n"
|
||||
"64 KiB\n"
|
||||
|
@ -1457,13 +1462,14 @@ msgid ""
|
|||
"256 KiB\n"
|
||||
"512 KiB\n"
|
||||
"1024 KiB\n"
|
||||
"2048 KiB\n"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:463
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:459
|
||||
msgid "Piece Size:"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:477
|
||||
#: plugins/TorrentCreator/torrentcreator.glade:473
|
||||
msgid "<b>Advanced</b>"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2102,3 +2108,171 @@ msgid ""
|
|||
"When set to -1 (unlimited), the global limits in Deluge's preferences will "
|
||||
"be obeyed."
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:31
|
||||
msgid "Web User Interface"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:34
|
||||
msgid ""
|
||||
"A Web based User Interface\n"
|
||||
"\n"
|
||||
"Firefox greasemonkey script: http://userscripts.org/scripts/show/12639\n"
|
||||
"\n"
|
||||
"Remotely add a file: \"curl -F torrent=@./test1.torrent -F pwd=deluge http://"
|
||||
"localhost:8112/remote/torrent/add\"\n"
|
||||
"\n"
|
||||
"There is support for multiple templates, but just one is included.\n"
|
||||
"\n"
|
||||
"Other contributors:\n"
|
||||
"*somedude : template enhancements.\n"
|
||||
"*markybob : stability : synced with his changes in deluge-svn.\n"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:179
|
||||
msgid "WebUi Config"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:187
|
||||
msgid "Port Number"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:188
|
||||
msgid "New Password"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:189
|
||||
msgid "New Password(confirm)"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:190
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:191
|
||||
msgid "Button Style"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:193
|
||||
msgid "Cache Templates"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:209
|
||||
msgid "Text and image"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:209
|
||||
msgid "Image Only"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:209
|
||||
msgid "Text Only"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/__init__.py:243
|
||||
msgid "Confirmed Password <> New Password\n"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/webserver_common.py:48
|
||||
msgid "translate something"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/deluge_webserver.py:142
|
||||
msgid "Choose an url or a torrent, not both."
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/deluge_webserver.py:153
|
||||
msgid "no data."
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/deluge_webserver.py:230
|
||||
msgid "refresh must be > 0"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/deluge_webserver.py:238
|
||||
msgid "Download Speed Limit"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/deluge_webserver.py:242
|
||||
msgid "Upload Speed Limit"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:1
|
||||
msgid "# Of Files"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:2
|
||||
msgid "About"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:3
|
||||
msgid "Auto refresh:"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:5
|
||||
msgid "Debug:Data Dump"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:7
|
||||
msgid "Delete downloaded files."
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:9
|
||||
msgid "Downloaded"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:11
|
||||
msgid "Next Announce"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:12
|
||||
msgid "Off"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:14
|
||||
msgid "Password is invalid,try again"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:16
|
||||
msgid "Pieces"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:17
|
||||
msgid "Refresh page every:"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:20
|
||||
msgid "Set"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:23
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:24
|
||||
msgid "Total Size"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:25
|
||||
msgid "Tracker"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:26
|
||||
msgid "Tracker Status"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:27
|
||||
msgid "Upload torrent"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:28
|
||||
msgid "Uploaded"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:29
|
||||
msgid "Url"
|
||||
msgstr ""
|
||||
|
||||
#: plugins/WebUi/scripts/template_strings.py:30
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
|
Loading…
Add table
Reference in a new issue