webui rev-80
This commit is contained in:
parent
278c6c9f3e
commit
bf8727f7bf
|
@ -115,7 +115,7 @@ class DbusManager(dbus.service.Object):
|
|||
for key in ["total_seeds", "total_peers","is_seed", "total_done",
|
||||
"total_download", "total_upload", "download_rate",
|
||||
"upload_rate", "num_files", "piece_length", "distributed_copies"
|
||||
,"next_announce","tracker"]:
|
||||
,"next_announce","tracker","queue_pos"]:
|
||||
status[key] = state[key]
|
||||
|
||||
#print 'all_keys:',sorted(status.keys())
|
||||
|
@ -162,6 +162,19 @@ class DbusManager(dbus.service.Object):
|
|||
self._add_torrent(filename)
|
||||
return True
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="s", out_signature="b")
|
||||
def queue_up(self, torrent_id):
|
||||
print 'UP!'
|
||||
self.core.queue_up(int(torrent_id))
|
||||
return True
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="s", out_signature="b")
|
||||
def queue_down(self, torrent_id):
|
||||
self.core.queue_down(int(torrent_id))
|
||||
return True
|
||||
|
||||
@dbus.service.method(dbus_interface=dbus_interface,
|
||||
in_signature="ss", out_signature="b")
|
||||
def add_torrent_filecontent(self, name, filecontent_b64):
|
||||
|
|
|
@ -102,6 +102,8 @@ urls = (
|
|||
"/torrent/pause(.*)", "torrent_pause",
|
||||
"/torrent/add(.*)", "torrent_add",
|
||||
"/torrent/delete/(.*)", "torrent_delete",
|
||||
"/torrent/queue/up/(.*)", "torrent_queue_up",
|
||||
"/torrent/queue/down/(.*)", "torrent_queue_down",
|
||||
"/pause_all(.*)", "pause_all",
|
||||
"/resume_all(.*)", "resume_all",
|
||||
"/refresh/set(.*)", "refresh_set",
|
||||
|
@ -234,6 +236,21 @@ class torrent_delete:
|
|||
ws.proxy.remove_torrent(torrent_id, data_also, torrent_also)
|
||||
do_redirect()
|
||||
|
||||
|
||||
class torrent_queue_up:
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
torrent_id = name
|
||||
ws.proxy.queue_up(torrent_id)
|
||||
do_redirect()
|
||||
|
||||
class torrent_queue_down:
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
torrent_id = name
|
||||
ws.proxy.queue_down(torrent_id)
|
||||
do_redirect()
|
||||
|
||||
class pause_all:
|
||||
@check_session
|
||||
def POST(self, name):
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
cd ~/prj/WebUi
|
||||
bzr revno > revno
|
||||
bzr version-info > version
|
||||
rm ~/prj/WebUi/WebUi.tgz
|
||||
cd ~/prj
|
||||
tar -zcvf ~/prj/WebUi/WebUi.tgz WebUi/ --exclude '.*' --exclude '*.pyc' --exclude '*.tgz' --exclude 'attic' --exclude 'xul' --exclude '*.sh' --exclude '*.*~'
|
|
@ -0,0 +1,24 @@
|
|||
from __future__ import with_statement
|
||||
import os
|
||||
import re
|
||||
template_dir = '~/prj/WebUi/templates/deluge'
|
||||
template_dir = os.path.expanduser(template_dir )
|
||||
|
||||
|
||||
files = [os.path.join(template_dir,fname)
|
||||
for fname in os.listdir(template_dir)
|
||||
if fname.endswith('.html')]
|
||||
|
||||
all_strings = []
|
||||
for filename in files:
|
||||
with open(filename,'r') as f:
|
||||
content = f.read()
|
||||
all_strings += re.findall("\$\_\(\'(.*)\'\)",content)
|
||||
all_strings += re.findall("\$\_\(\"(.*)\"\)",content)
|
||||
|
||||
all_strings = sorted(set(all_strings))
|
||||
|
||||
with open ('./template_strings.py','w') as f:
|
||||
for value in all_strings:
|
||||
f.write("_('%s')\n" % value )
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
_('# Of Files')
|
||||
_('About')
|
||||
_('Auto refresh:')
|
||||
_('Availability')
|
||||
_('Debug:Data Dump')
|
||||
_('Delete .torrent file')
|
||||
_('Delete downloaded files.')
|
||||
_('Details')
|
||||
_('Downloaded')
|
||||
_('ETA')
|
||||
_('Next Announce')
|
||||
_('Off')
|
||||
_('Password')
|
||||
_('Password is invalid,try again')
|
||||
_('Peers')
|
||||
_('Pieces')
|
||||
_('Refresh page every:')
|
||||
_('Remove')
|
||||
_('Seeders')
|
||||
_('Set')
|
||||
_('Share Ratio')
|
||||
_('Speed')
|
||||
_('Submit')
|
||||
_('Total Size')
|
||||
_('Tracker')
|
||||
_('Tracker Status')
|
||||
_('Upload torrent')
|
||||
_('Uploaded')
|
||||
_('Url')
|
||||
_('seconds')
|
|
@ -5,7 +5,7 @@ $:render.header(_('Torrent list'))
|
|||
<table class="torrent_list" border=1>
|
||||
<tr>
|
||||
$:(sort_head('calc_state_str', 'S'))
|
||||
$:(sort_head('id', '#'))
|
||||
$:(sort_head('queue_pos', '#'))
|
||||
$:(sort_head('name', _('Name')))
|
||||
$:(sort_head('total_size', _('Size')))
|
||||
$:(sort_head('progress', _('Progress')))
|
||||
|
@ -24,7 +24,7 @@ $for torrent in torrent_list:
|
|||
src="/static/images/$(torrent.calc_state_str)16.png"
|
||||
name="$torrent.action" value="$torrent.id">
|
||||
</td>
|
||||
<td>$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>$fsize(torrent.total_size)</td>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$def with (method, url, title, image='')
|
||||
<div class="deluge_button">
|
||||
<form method="$method" action="$url" class="deluge_button">
|
||||
<button type="submit" class="deluge_button">
|
||||
<input type="hidden" name="redir" value="$self_url()">
|
||||
<button type="submit" class="deluge_button">
|
||||
$if (get_config('button_style') == 0):
|
||||
$title
|
||||
$if image:
|
||||
|
|
|
@ -82,6 +82,7 @@ class="deluge_button">
|
|||
|
||||
$:render.part_button('GET', '/torrent/delete/' + str(torrent.id), _('Remove'), 'tango/user-trash.png')
|
||||
|
||||
|
||||
<br>
|
||||
[<a onclick="javascript:toggle_dump()">$_('Debug:Data Dump')</a>]
|
||||
|
||||
|
@ -103,6 +104,16 @@ function toggle_dump(){
|
|||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class='panel'>
|
||||
Queue pos: $torrent.queue_pos
|
||||
|
||||
$:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up'), 'tango/up.png')
|
||||
|
||||
$:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/down.png')
|
||||
</div>
|
||||
|
||||
$:render.part_refresh()
|
||||
|
||||
$:render.footer()
|
||||
|
|
|
@ -52,11 +52,14 @@ if not hasattr(deluge,'pref'):
|
|||
|
||||
def init():
|
||||
#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('templates/%s/' % config.get('template'),
|
||||
cache=config.get('cache_templates'))
|
||||
globals()['render'] = template.render(os.path.join(path, 'templates/%s/' %
|
||||
config.get('template')), cache=config.get('cache_templates'))
|
||||
|
||||
|
||||
|
||||
|
||||
REVNO = open(os.path.join(os.path.dirname(__file__),'revno')).read()
|
||||
|
@ -69,7 +72,7 @@ TORRENT_KEYS = ['distributed_copies', 'download_payload_rate',
|
|||
'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']
|
||||
'uploaded_memory','tracker','state','queue_pos']
|
||||
|
||||
STATE_MESSAGES = (_("Queued"),
|
||||
_("Checking"),
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#!/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
|
||||
|
@ -29,53 +31,303 @@
|
|||
# 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
|
||||
"""
|
||||
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
|
||||
import pickle
|
||||
from webpy022 import template
|
||||
from operator import attrgetter
|
||||
|
||||
random.seed()
|
||||
from deluge import common
|
||||
from webserver_common import REVNO, VERSION
|
||||
import webserver_common as ws
|
||||
|
||||
config_file = deluge.common.CONFIG_DIR + "/webui.conf"
|
||||
|
||||
#a bit hacky way of detecting i'm in the deluge gui or in a process :(
|
||||
if not hasattr(deluge,'pref'):
|
||||
import dbus
|
||||
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'))
|
||||
|
||||
def init():
|
||||
#appy possibly changed config-vars, only called in when runing inside gtk.
|
||||
from dbus_interface import get_dbus_manager
|
||||
globals()['proxy'] = get_dbus_manager()
|
||||
globals()['config'] = deluge.pref.Preferences(config_file, False)
|
||||
globals()['render'] = template.render('templates/%s/' % config.get('template'),
|
||||
cache=config.get('cache_templates'))
|
||||
#init:
|
||||
web.webapi.internalerror = web.debugerror
|
||||
|
||||
|
||||
REVNO = open(os.path.join(os.path.dirname(__file__),'revno')).read()
|
||||
VERSION = open(os.path.join(os.path.dirname(__file__),'version')).read()
|
||||
#/init
|
||||
|
||||
#methods:
|
||||
SESSIONS = [] #dumb sessions.
|
||||
def start_session():
|
||||
session_id = str(random.random())
|
||||
SESSIONS.append(session_id)
|
||||
setcookie("session_id", session_id)
|
||||
|
||||
if getcookie('auto_refresh_secs') == None:
|
||||
setcookie('auto_refresh_secs','10')
|
||||
|
||||
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 ws.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 ws.render.sort_column_head(id, name, order, active_up, active_down)
|
||||
|
||||
|
||||
def get_config(var):
|
||||
return ws.config.get(var)
|
||||
|
||||
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': get_config,
|
||||
'self_url': self_url,
|
||||
'fspeed': common.fspeed,
|
||||
'fsize': common.fsize,
|
||||
'render': ws.render, #for easy resuse of templates
|
||||
'rev': 'rev.%s' % (REVNO, ),
|
||||
'version': VERSION,
|
||||
'getcookie':getcookie,
|
||||
'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.
|
||||
#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)
|
||||
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','start_session','getcookie'
|
||||
,'create_webserver']
|
||||
|
||||
|
||||
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"))
|
||||
|
|
Loading…
Reference in New Issue