This commit is contained in:
Marcos Pinto 2007-12-22 08:36:15 +00:00
parent 23d13a2cfd
commit 5ccca41ea6
69 changed files with 0 additions and 4418 deletions

View File

@ -1,350 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
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.

View File

@ -1,280 +0,0 @@
# -*- 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
plugin_name = "Web User Interface"
plugin_author = "Martijn Voncken"
plugin_version = "rev."
plugin_description = """A Web based User Interface
Firefox greasemonkey script: http://userscripts.org/scripts/show/12639
Remotely add a file: "curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add"
Advanced template is only tested on firefox and garanteed not to work in IE6
ssl keys are located in WebUi/ssl/
Other contributors:
*somedude : template enhancements.
*markybob : stability : synced with his changes in deluge-svn.
"""
import deluge.common
try:
import deluge.pref
from deluge.dialogs import show_popup_warning
import webserver_common
except ImportError:
print 'WebUi:not imported as a plugin'
try:
from dbus_interface import get_dbus_manager
except:
pass #for unit-test.
import time
import gtk
import os
from subprocess import Popen
from md5 import md5
from threading import Thread
import random
random.seed()
plugin_version += open(os.path.join(os.path.dirname(__file__),'revno')).read()
plugin_description += (
open(os.path.join(os.path.dirname(__file__),'version')).read())
def deluge_init(deluge_path):
global path
path = deluge_path
def enable(core, interface):
global path
return plugin_WebUi(path, core, interface)
class plugin_WebUi(object):
def __init__(self, path, deluge_core, deluge_interface):
self.path = path
self.core = deluge_core
self.interface = deluge_interface
self.proc = None
self.web_server = None
if not deluge.common.windows_check():
import commands
status = commands.getstatusoutput(
'ps x |grep -v grep |grep run_webserver')
if status[0] == 0:
os.kill(int(status[1].split()[0]), 9)
time.sleep(1) #safe time to wait for kill to finish.
self.config_file = os.path.join(deluge.common.CONFIG_DIR, "webui.conf")
self.config = deluge.pref.Preferences(self.config_file, False)
try:
self.config.load()
except IOError:
# File does not exist
pass
if not self.config.get('port'): #ugly way to detect new config file.
#set default values:
self.config.set("port", 8112)
self.config.set("button_style", 2)
self.config.set("auto_refresh", False)
self.config.set("auto_refresh_secs", 4)
self.config.set("template", "deluge")
self.config.save(self.config_file)
if not self.config.get("pwd_salt"):
self.config.set("pwd_salt", "invalid")
self.config.set("pwd_md5", "invalid")
if self.config.get("cache_templates") == None:
self.config.set("cache_templates", True)
if deluge.common.windows_check():
self.config.set("run_in_thread", True)
else:
self.config.set("run_in_thread", False)
if self.config.get("use_https") == None:
self.config.set("use_https", False)
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()
def update(self):
pass
## This will be only called if your plugin is configurable
def configure(self,parent_dialog):
d = ConfigDialog(self.config, self, parent_dialog)
if d.run() == gtk.RESPONSE_OK:
d.save_config()
d.destroy()
def start_server(self):
self.kill_server()
if self.config.get("run_in_thread"):
print 'Start Webui(inside gtk)..'
webserver_common.init_gtk_05() #reload changed config.
from deluge_webserver import WebServer #only import in threaded mode
self.web_server = WebServer()
self.web_server.start_gtk()
else:
print 'Start Webui(in process)..'
server_bin = os.path.join(os.path.dirname(__file__), 'run_webserver')
self.proc = Popen((server_bin,'env=0.5'))
def kill_server(self):
if self.web_server:
print "webserver: stop"
self.web_server.stop_gtk()
self.web_server = None
if self.proc:
print "webserver: kill %i" % self.proc.pid
os.system("kill -9 %i" % self.proc.pid)
time.sleep(1) #safe time to wait for kill to finish.
self.proc = None
def __del__(self):
self.kill_server()
class ConfigDialog(gtk.Dialog):
"""
sorry, can't get used to gui builders.
from what I read glade is better, but i dont want to invest time in them.
"""
def __init__(self, config, plugin, parent):
gtk.Dialog.__init__(self ,parent=parent)
self.config = config
self.plugin = plugin
self.vb = gtk.VBox()
self.set_title(_("WebUi Config"))
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))
and not dirname.startswith('.')]
self.port = self.add_widget(_('Port Number'), gtk.SpinButton())
self.pwd1 = self.add_widget(_('New Password'), gtk.Entry())
self.pwd2 = self.add_widget(_('New Password(confirm)'), gtk.Entry())
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.use_https = self.add_widget(_('https://'),
gtk.CheckButton())
#self.share_downloads = self.add_widget(_('Share Download Directory'),
# gtk.CheckButton())
self.port.set_range(80, 65536)
self.port.set_increments(1, 10)
self.pwd1.set_visibility(False)
self.pwd2.set_visibility(False)
for item in self.templates:
self.template.append_text(item)
if not self.config.get("template") in self.templates:
self.config.set("template","deluge")
for item in [_('Text and image'), _('Image Only'), _('Text Only')]:
self.button_style.append_text(item)
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.cache_templates.set_active(self.config.get("cache_templates"))
self.use_https.set_active(self.config.get("use_https"))
self.vbox.pack_start(self.vb, True, True, 0)
self.vb.show_all()
self.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL
,gtk.STOCK_OK, gtk.RESPONSE_OK)
def add_widget(self,label,w=None):
hb = gtk.HBox()
lbl = gtk.Label(label)
lbl.set_size_request(200,20)
hb.pack_start(lbl,False,False, 0)
hb.pack_start(w,True,True, 0)
self.vb.pack_start(hb,False,False, 0)
return w
self.add_buttons(dgtk.STOCK_CLOSE, dgtk.RESPONSE_CLOSE)
def save_config(self):
if self.pwd1.get_text() > '':
if self.pwd1.get_text() <> self.pwd2.get_text():
show_popup_warning(self,_("Confirmed Password <> New Password\n"
+ "Password was not changed"))
else:
sm = md5()
sm.update(random.getrandbits(5000))
salt = sm.digest()
self.config.set("pwd_salt", salt)
#
m = md5()
m.update(salt)
m.update(unicode(self.pwd1.get_text()))
self.config.set("pwd_md5", m.digest())
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("use_https", self.use_https.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

View File

@ -1,282 +0,0 @@
# -*- coding: utf-8 -*-
# Dbus Ipc for experimental web interface
#
# dbus_interface.py
#
# Copyright (C) Martijn Voncken 2007 <mvoncken@gmail.com>
# Contains copy and pasted code from other parts of deluge,see deluge AUTHORS
#
# 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 gtk
import dbus
import deluge.common as common
from dbus_pythonize import pythonize
import base64
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):
self.core = core
self.interface = interface
self.config = config
self.config_file = config_file
self.bus = dbus.SessionBus()
bus_name = dbus.service.BusName(dbus_interface,bus=self.bus)
dbus.service.Object.__init__(self, bus_name,dbus_service)
#
#todo : add: get_interface_version in=i,get_config_value in=s out=s
#
@dbus.service.method(dbus_interface=dbus_interface,
in_signature="",out_signature="as")
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
"""
torrent_list = [str(key) for key in self.core.unique_IDs]
return torrent_list
@dbus.service.method(dbus_interface=dbus_interface,
in_signature="sas",out_signature="a{sv}")
def get_torrent_status(self, torrent_id, keys):
"""return torrent metadata of a single torrent as a dict
0.6 returns a pickle, this returns a dbus-type.
+added some more values to the dict
"""
torrent_id = int(torrent_id)
# Convert the array of strings to a python list of strings
nkeys = [str(key) for key in keys]
state = self.core.get_torrent_state(torrent_id)
torrent = self.core.unique_IDs[torrent_id]
status = {
"name": state["name"],
"total_size": state["total_size"],
"num_pieces": state["num_pieces"],
"state": state['state'],
"user_paused": self.core.is_user_paused(torrent_id),
"paused":state['is_paused'],
"progress": int(state["progress"] * 100),
"next_announce": state["next_announce"],
"total_payload_download":state["total_payload_download"],
"total_payload_upload": state["total_payload_upload"],
"download_payload_rate": state["download_rate"],
"upload_payload_rate": state["upload_rate"],
"num_peers": state["num_peers"],
"num_seeds": state["num_seeds"],
"total_wanted": state["total_wanted"],
"eta": common.estimate_eta(state),
"ratio": self.interface.manager.calc_ratio(torrent_id,state),
#non 0.6 values follow here:
"tracker_status": state.get("tracker_status","?"),
"uploaded_memory": torrent.uploaded_memory,
}
#more non 0.6 values
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","queue_pos"]:
status[key] = state[key]
#print 'all_keys:',sorted(status.keys())
status_subset = {}
for key in keys:
if key in status:
status_subset[key] = status[key]
else:
print 'mbus error,no key named:', key
return status_subset
@dbus.service.method(dbus_interface=dbus_interface,
in_signature="as",out_signature="")
def pause_torrent(self, torrents):
"""same as 0.6 interface"""
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="as", out_signature="")
def resume_torrent(self, torrents):
"""same as 0.6 interface"""
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="as", out_signature="")
def force_reannounce(self, torrents):
"""same as 0.6 interface"""
for torrent_id in torrents:
torrent_id = int(torrent_id)
self.core.update_tracker(torrent_id)
@dbus.service.method(dbus_interface=dbus_interface,
in_signature="asbb", out_signature="")
def remove_torrent(self, torrent_ids, data_also, torrent_also):
"""remove a torrent,and optionally data and torrent
additions compared to 0.6 interface: (data_also, torrent_also)
"""
for torrent_id in torrent_ids:
torrent_id = int(torrent_id)
self.core.remove_torrent(torrent_id, bool(data_also)
,bool( torrent_also))
#this should not be needed:
gtk.gdk.threads_enter()
try:
self.interface.torrent_model_remove(torrent_id)
except:
pass
@dbus.service.method(dbus_interface=dbus_interface,
in_signature="s", out_signature="b")
def add_torrent_url(self, url):
filename = fetch_url(url)
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):
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):
"""not available in deluge 0.6 interface"""
#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)
filecontent = base64.b64decode(filecontent_b64)
f = open(filename,"wb") #no with statement, that's py 2.5+
f.write(filecontent)
f.close()
print 'write:',filename
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']
@dbus.service.method(dbus_interface=dbus_interface,
in_signature="", out_signature="v")
def get_num_connections(self):
core_state = self.core.get_state()
return core_state['num_connections']
#internal
def _add_torrent(self, filename):
filename = unicode(filename)
target = self.core.config.get("default_download_path")
torrent_id = self.core.add_torrent(filename, target,
self.interface.config.get("use_compact_storage"))
#update gtk-ui This should not be needed!!
gtk.gdk.threads_enter()
try:
self.interface.torrent_model_append(torrent_id)
except:
pass
#finally is 2.5 only!
gtk.gdk.threads_leave()
return True
def fetch_url(url):
import urllib
try:
filename, headers = urllib.urlretrieve(url)
except IOError:
raise Exception( "Network error while trying to fetch torrent from %s"
% url)
else:
if (filename.endswith(".torrent") or
headers["content-type"]=="application/x-bittorrent"):
return filename
else:
raise Exception("URL doesn't appear to be a valid torrent file:%s"
% url)
return None

View File

@ -1,373 +0,0 @@
"""
pretty debug errors
(part of web.py)
adapted from Django <djangoproject.com>
Copyright (c) 2005, the Lawrence Journal-World
Used under the modified BSD license:
http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
"""
__all__ = ["debugerror", "djangoerror"]
import sys, urlparse, pprint
from webpy022.net import websafe
from webpy022.template import Template
import webpy022.webapi as web
import webserver_common as ws
from traceback import format_tb
import os, os.path
whereami = os.path.join(os.getcwd(), __file__)
whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1])
djangoerror_t = """\
$def with (exception_type, exception_value, frames, exception_message, version_info, tback_txt)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="robots" content="NONE,NOARCHIVE" />
<title>$exception_type at $ctx.path</title>
<style type="text/css">
html * { padding:0; margin:0; }
body * { padding:10px 20px; }
body * * { padding:0; }
body { font:small sans-serif; }
body>div { border-bottom:1px solid #ddd; }
h1 { font-weight:normal; }
h2 { margin-bottom:.8em; }
h2 span { font-size:80%; color:#666; font-weight:normal; }
h3 { margin:1em 0 .5em 0; }
h4 { margin:0 0 .5em 0; font-weight: normal; }
table {
border:1px solid #ccc; border-collapse: collapse; background:white; }
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
thead th {
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
font-weight:normal; font-size:11px; border:1px solid #ddd; }
tbody th { text-align:right; color:#666; padding-right:.5em; }
table.vars { margin:5px 0 2px 40px; }
table.vars td, table.req td { font-family:monospace; }
table td.code { width:100%;}
table td.code div { overflow:hidden; }
table.source th { color:#666; }
table.source td {
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
ul.traceback { list-style-type:none; }
ul.traceback li.frame { margin-bottom:1em; }
div.context { margin: 10px 0; }
div.context ol {
padding-left:30px; margin:0 10px; list-style-position: inside; }
div.context ol li {
font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
div.context ol.context-line li { color:black; background-color:#ccc; }
div.context ol.context-line li span { float: right; }
div.commands { margin-left: 40px; }
div.commands a { color:black; text-decoration:none; }
#summary { background: #ffc; }
#summary h2 { font-weight: normal; color: #666; }
#explanation { background:#eee; }
#template, #template-not-exist { background:#f6f6f6; }
#template-not-exist ul { margin: 0 0 0 20px; }
#traceback { background:#eee; }
#requestinfo { background:#f6f6f6; padding-left:120px; }
#summary table { border:none; background:transparent; }
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
#requestinfo h3 { margin-bottom:-1em; }
.error { background: #ffc; }
.specific { color:#cc3300; font-weight:bold; }
</style>
<script type="text/javascript">
//<!--
function getElementsByClassName(oElm, strTagName, strClassName){
// Written by Jonathan Snook, http://www.snook.ca/jon;
// Add-ons by Robert Nyman, http://www.robertnyman.com
var arrElements = (strTagName == "*" && document.all)? document.all :
oElm.getElementsByTagName(strTagName);
var arrReturnElements = new Array();
strClassName = strClassName.replace(/\-/g, "\\-");
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
var oElement;
for(var i=0; i<arrElements.length; i++){
oElement = arrElements[i];
if(oRegExp.test(oElement.className)){
arrReturnElements.push(oElement);
}
}
return (arrReturnElements)
}
function hideAll(elems) {
for (var e = 0; e < elems.length; e++) {
elems[e].style.display = 'none';
}
}
window.onload = function() {
hideAll(getElementsByClassName(document, 'table', 'vars'));
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
}
function toggle() {
for (var i = 0; i < arguments.length; i++) {
var e = document.getElementById(arguments[i]);
if (e) {
e.style.display = e.style.display == 'none' ? 'block' : 'none';
}
}
return false;
}
function varToggle(link, id) {
toggle('v' + id);
var s = link.getElementsByTagName('span')[0];
var uarr = String.fromCharCode(0x25b6);
var darr = String.fromCharCode(0x25bc);
s.innerHTML = s.innerHTML == uarr ? darr : uarr;
return false;
}
//-->
</script>
</head>
<body>
<div id="summary">
<h1>$exception_type : $exception_value</h2>
</div>
<div id="explanation">
<p>
<!--ERROR-MARKER-->
Oops, Deluge Broke :-( , You might have found a bug, or you did something really stupid ;-).
<br />If the error persists :<br />
Read the <a href="http://deluge-torrent.org/faq">Faq</a>.<br />
Try downloading the latest version at
<a href="http://deluge-torrent.org">deluge-torrent.org</a>
<br />Visit the <a href="http://forum.deluge-torrent.org">forum</a>
or the <a href="http://dev.deluge-torrent.org/query">buglist</a> for more info.
</p>
</div>
<div id="summary">
Paste the contents of this text-box when you are asked for a traceback:<br>
<!--
<form action="http://pastebin.ca/index.php" method=POST>
option to paste to a pastebin disabled, need to find out about pastebin etiquette first.
Or ask markybob to make a deluge pastebin.
--->
<textarea cols=80 rows=20 name="content">
--Deluge Error--
$exception_type : $exception_value
path : $ctx.path
file : $frames[0].filename in $frames[0].function, line $frames[0].lineno
--Input--
$web.input()
--Versions--
$version_info
--Traceback--
$tback_txt
</textarea><br />
<font color=red>Use a <a href="http://pastebin.ca/">pastebin</a> on IRC!</font><br>
<!--
<input type=submit name="Submit" value="Click here to paste to http://pastebin.ca">
-->
</form>
</div>
<div id="traceback">
<h2>Traceback <span>(innermost first)</span></h2>
<ul class="traceback">
$for frame in frames:
<li class="frame">
<code>$frame.filename</code> in <code>$frame.function</code>
$if frame.context_line:
<div class="context" id="c$frame.id">
$if frame.pre_context:
<ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">
$for line in frame.pre_context:
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
</ol>
<ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol>
$if frame.post_context:
<ol start='${frame.lineno + 1}' class="post-context" id="post$frame.id">
$for line in frame.post_context:
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
</ol>
</div>
$if frame.vars:
<div class="commands">
<a href='#' onclick="return varToggle(this, '$frame.id')"><span>&#x25b6;</span> Local vars</a>
$# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame))
</div>
$:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id)))
</li>
</ul>
</div>
<div id="requestinfo">
$if ctx.output or ctx.headers:
<h2>Response so far</h2>
<h3>HEADERS</h3>
<p class="req"><code>
$for kv in ctx.headers:
$kv[0]: $kv[1]<br />
$else:
[no headers]
</code></p>
<h3>BODY</h3>
<p class="req" style="padding-bottom: 2em"><code>
$ctx.output
</code></p>
<h2>Request information</h2>
<h3>INPUT</h3>
$:dicttable(web.input())
<h3 id="cookie-info">COOKIES</h3>
$:dicttable(web.cookies())
<h3 id="meta-info">META</h3>
$ newctx = []
$# ) and (k not in ['env', 'output', 'headers', 'environ', 'status', 'db_execute']):
$for k, v in ctx.iteritems():
$if not k.startswith('_') and (k in x):
$newctx.append(kv)
$:dicttable(dict(newctx))
<h3 id="meta-info">ENVIRONMENT</h3>
$:dicttable(ctx.env)
</div>
</body>
</html>
"""
dicttable_t = r"""$def with (d, kls='req', id=None)
$if d:
<table class="$kls"\
$if id: id="$id"\
><thead><tr><th>Variable</th><th>Value</th></tr></thead>
<tbody>
$ temp = d.items()
$temp.sort()
$for kv in temp:
<tr><td>$kv[0]</td><td class="code"><div>$prettify(kv[1])</div></td></tr>
</tbody>
</table>
$else:
<p>No data.</p>
"""
dicttable_r = Template(dicttable_t, filter=websafe)
djangoerror_r = Template(djangoerror_t, filter=websafe)
def djangoerror():
def _get_lines_from_file(filename, lineno, context_lines):
"""
Returns context_lines before and after lineno from file.
Returns (pre_context_lineno, pre_context, context_line, post_context).
"""
try:
source = open(filename).readlines()
lower_bound = max(0, lineno - context_lines)
upper_bound = lineno + context_lines
pre_context = \
[line.strip('\n') for line in source[lower_bound:lineno]]
context_line = source[lineno].strip('\n')
post_context = \
[line.strip('\n') for line in source[lineno + 1:upper_bound]]
return lower_bound, pre_context, context_line, post_context
except (OSError, IOError):
return None, [], None, []
exception_type, exception_value, tback = sys.exc_info()
exception_message = 'Error'
try:
exception_message = exception_value.message
except AttributeError:
exception_message = 'no message'
exception_type = exception_type.__name__
version_info = (
"WebUi : rev." + ws.REVNO
+ "Python : " + str(sys.version)
)
try:
import dbus
version_info += '\ndbus:' + str(dbus.__version__)
except:
pass
tback_txt = ''.join(format_tb(tback))
frames = []
while tback is not None:
filename = tback.tb_frame.f_code.co_filename
function = tback.tb_frame.f_code.co_name
lineno = tback.tb_lineno - 1
pre_context_lineno, pre_context, context_line, post_context = \
_get_lines_from_file(filename, lineno, 7)
frames.append(web.storage({
'tback': tback,
'filename': filename,
'function': function,
'lineno': lineno,
'vars': tback.tb_frame.f_locals,
'id': id(tback),
'pre_context': pre_context,
'context_line': context_line,
'post_context': post_context,
'pre_context_lineno': pre_context_lineno,
}))
tback = tback.tb_next
frames.reverse()
urljoin = urlparse.urljoin
def prettify(x):
try:
out = pprint.pformat(x)
except Exception, e:
out = '[could not display: <' + e.__class__.__name__ + \
': '+str(e)+'>]'
return out
dt = dicttable_r
dt.globals = {'prettify': prettify}
t = djangoerror_r
t.globals = {'ctx': web.ctx, 'web':web, 'dicttable':dt, 'dict':dict, 'str':str}
return t(exception_type, exception_value, frames, exception_message, version_info, tback_txt)
def deluge_debugerror():
"""
A replacement for `internalerror` that presents a nice page with lots
of debug information for the programmer.
(Based on the beautiful 500 page from [Django](http://djangoproject.com/),
designed by [Wilson Miner](http://wilsonminer.com/).)
"""
web.ctx.headers = [
('Content-Type', 'text/html')
]
web.ctx.output = djangoerror()
if __name__ == "__main__":
urls = (
'/', 'index'
)
class index:
def GET(self):
thisdoesnotexist
web.internalerror = web.debugerror
web.run(urls)

View File

@ -1,379 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# deluge_webserver.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.
import webserver_common as ws
from webserver_framework import *
import webpy022 as web
from webpy022.http import seeother, url
import base64
from operator import attrgetter
import os
#routing:
urls = (
"/login", "login",
"/index", "index",
"/torrent/info/(.*)", "torrent_info",
"/torrent/info_inner/(.*)", "torrent_info_inner",
"/torrent/stop/(.*)", "torrent_stop",
"/torrent/start/(.*)", "torrent_start",
"/torrent/reannounce/(.*)", "torrent_reannounce",
"/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",
"/refresh/(.*)", "refresh",
"/config", "config_",
"/home", "home",
"/about", "about",
"/logout", "logout",
#remote-api:
"/remote/torrent/add(.*)", "remote_torrent_add",
#static:
"/static/(.*)", "static",
"/template/static/(.*)", "template_static",
#"/downloads/(.*)","downloads" disabled until it can handle large downloads
#default-pages
"/", "home",
"", "home"
)
#/routing
#pages:
class login:
@deluge_page_noauth
def GET(self, name):
vars = web.input(error = None)
return ws.render.login(vars.error)
def POST(self):
vars = web.input(pwd = None, redir = None)
if check_pwd(vars.pwd):
#start new session
start_session()
do_redirect()
elif vars.redir:
seeother(url('/login', error=1, redir=vars.redir))
else:
seeother('/login?error=1')
class index:
"page containing the torrent list."
@deluge_page
@auto_refreshed
def GET(self, name):
vars = web.input(sort=None, order=None ,filter=None , category=None)
torrent_list = [get_torrent_status(torrent_id)
for torrent_id in ws.proxy.get_session_state()]
all_torrents = torrent_list[:]
#filter-state
if vars.filter:
torrent_list = filter_torrent_state(torrent_list, vars.filter)
setcookie("filter", vars.filter)
else:
setcookie("filter", "")
#filter-cat
if vars.category:
torrent_list = [t for t in torrent_list if t.category == vars.category]
setcookie("category", vars.category)
else:
setcookie("category", "")
#sorting:
if vars.sort:
torrent_list.sort(key=attrgetter(vars.sort))
if vars.order == 'up':
torrent_list = reversed(torrent_list)
setcookie("order", vars.order)
setcookie("sort", vars.sort)
return ws.render.index(torrent_list, all_torrents)
class torrent_info:
@deluge_page
@auto_refreshed
def GET(self, name):
torrent_id = name.split(',')[0]
return ws.render.torrent_info(get_torrent_status(torrent_id))
class torrent_info_inner:
@deluge_page
def GET(self, torrent_ids):
torrent_ids = torrent_ids.split(',')
info = get_torrent_status(torrent_ids[0])
if len(torrent_ids) > 1:
#todo : hmm, lots of manual stuff here :(
pass
return ws.render.torrent_info_inner(info)
class torrent_start:
@check_session
def POST(self, name):
torrent_ids = name.split(',')
ws.proxy.resume_torrent(torrent_ids)
do_redirect()
class torrent_stop:
@check_session
def POST(self, name):
torrent_ids = name.split(',')
ws.proxy.pause_torrent(torrent_ids)
do_redirect()
class torrent_reannounce:
@check_session
def POST(self, torrent_id):
ws.proxy.force_reannounce([torrent_id])
do_redirect()
class torrent_add:
@deluge_page
def GET(self, name):
return ws.render.torrent_add()
@check_session
def POST(self, name):
"""
allows:
*posting of url
*posting file-upload
*posting of data as string(for greasemonkey-private)
"""
vars = web.input(url = None, torrent = {})
torrent_name = None
torrent_data = None
if vars.torrent.filename:
torrent_name = vars.torrent.filename
torrent_data = vars.torrent.file.read()
if vars.url and torrent_name:
error_page(_("Choose an url or a torrent, not both."))
if vars.url:
ws.proxy.add_torrent_url(vars.url)
do_redirect()
elif torrent_name:
data_b64 = base64.b64encode(torrent_data)
#b64 because of strange bug-reports related to binary data
ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64)
do_redirect()
else:
error_page(_("no data."))
class remote_torrent_add:
"""
For use in remote scripts etc.
curl ->POST pwd and torrent as file
greasemonkey: POST pwd torrent_name and data_b64
"""
@remote
def POST(self, name):
vars = web.input(pwd = None, torrent = {},
data_b64 = None , torrent_name= None)
if not check_pwd(vars.pwd):
return 'error:wrong password'
if vars.data_b64: #b64 post (greasemonkey)
data_b64 = unicode(vars.data_b64)
torrent_name = vars.torrent_name
else: #file-post (curl)
data_b64 = base64.b64encode(vars.torrent.file.read())
torrent_name = vars.torrent.filename
ws.proxy.add_torrent_filecontent(torrent_name, data_b64)
return 'ok'
class torrent_delete:
@deluge_page
def GET(self, name):
torrent_ids = name.split(',')
torrent_list = [get_torrent_status(id) for id in torrent_ids]
return ws.render.torrent_delete(name, torrent_list)
@check_session
def POST(self, name):
torrent_ids = name.split(',')
vars = web.input(data_also = None, torrent_also = None)
data_also = bool(vars.data_also)
torrent_also = bool(vars.torrent_also)
ws.proxy.remove_torrent(torrent_ids, data_also, torrent_also)
do_redirect()
class torrent_queue_up:
@check_session
def POST(self, name):
#a bit too verbose..
torrent_ids = name.split(',')
torrents = [get_torrent_status(id) for id in torrent_ids]
torrents.sort(lambda x, y : x.queue_pos - y.queue_pos)
torrent_ids = [t.id for t in torrents]
for torrent_id in torrent_ids:
ws.proxy.queue_up(torrent_id)
do_redirect()
class torrent_queue_down:
@check_session
def POST(self, name):
#a bit too verbose..
torrent_ids = name.split(',')
torrents = [get_torrent_status(id) for id in torrent_ids]
torrents.sort(lambda x, y : x.queue_pos - y.queue_pos)
torrent_ids = [t.id for t in torrents]
for torrent_id in reversed(torrent_ids):
ws.proxy.queue_down(torrent_id)
do_redirect()
class pause_all:
@check_session
def POST(self, name):
ws.proxy.pause_torrent(ws.proxy.get_session_state())
do_redirect()
class resume_all:
@check_session
def POST(self, name):
ws.proxy.resume_torrent(ws.proxy.get_session_state())
do_redirect()
class refresh:
@check_session
def POST(self, name):
auto_refresh = {'off': '0', 'on': '1'}[name]
setcookie('auto_refresh', auto_refresh)
if not getcookie('auto_refresh_secs'):
setcookie('auto_refresh_secs', 10)
do_redirect()
class refresh_set:
@deluge_page
def GET(self, name):
return ws.render.refresh_form()
@check_session
def POST(self, name):
vars = web.input(refresh = 0)
refresh = int(vars.refresh)
if refresh > 0:
setcookie('auto_refresh', '1')
setcookie('auto_refresh_secs', str(refresh))
do_redirect()
else:
error_page(_('refresh must be > 0'))
class config_: #namespace clash?
"""core config
TODO:good validation.
"""
"""
SOMEHOW ONLY BREAKS 0.6 ??
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()
class logout:
@check_session
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())
def run():
server = WebServer()
try:
server.start()
except KeyboardInterrupt:
server.stop()
if __name__ == "__main__":
run()

View File

@ -1,3 +0,0 @@
#!/usr/bin/env python
import deluge_webserver
deluge_webserver.run()

View File

@ -1,10 +0,0 @@
#!/bin/bash
pwd=deluge
url=http://localhost:8112
for arg in "$@"
do
curl -F torrent=@"$arg" -F pwd=$pwd $url/remote/torrent/add
done

View File

@ -1,207 +0,0 @@
// ==UserScript==
// @name Add Torrents To Deluge
// @namespace http://blog.monstuff.com/archives/cat_greasemonkey.html
// @description Let's you add torrents to the deluge WebUi
// @include http://isohunt.com/torrent_details/*
// @include http://thepiratebay.org/details.php?*
// @include http://torrentreactor.net/view.php?*
// @include http://www.mininova.org/*
// @include http://www.torrentspy.com/*
// @include http://ts.searching.com/*
// @include *
// ==/UserScript==
//url-based submit and parsing based on : "Add Torrents To utorrent" by Julien Couvreur
//binary magic,contains from http://mgran.blogspot.com/2006/08/downloading-binary-streams-with.html
//these parameters need to be edited before using the script
// Server address
var host = "localhost";
// Server port
var port = "8112";
//open_page: "_blank" for a new window or "deluge_webui" for window re-use
//(not for private=1)
var open_page = "_blank"
//Private-trackers 0/1
//different behavior, gets torrent-data from (private) site and pops up a message.
var private_submit = 1;
//deluge_password, only needed if private_submit = 1.
var deluge_password = 'deluge';
//========================
if (host == "") { alert('You need to configure the "Add Torrents To Deluge" user script with your WebUI parameters before using it.'); }
function scanLinks() {
var links = getLinks();
for (var i=0; i < links.length; i++){
var link = links[i];
if (match(link.href)) {
if (private_submit) {
makeUTorrentLink_private(link,i);
}
else {
makeUTorrentLink(link);
}
}
}
}
function makeUTorrentLink(link) {
var uTorrentLink = document.createElement('a');
uTorrentLink.setAttribute("href", makeUTorrentUrl(link.href));
uTorrentLink.setAttribute("target", open_page);
uTorrentLink.style.paddingLeft = "5px";
uTorrentLink.innerHTML = "<img src=\"" + image + "\" style='border: 0px' />";
link.parentNode.insertBefore(uTorrentLink, link.nextSibling);
return uTorrentLink
}
function makeUTorrentUrl(url) {
var uTorrentUrl = "http://"+host+":"+port+"/torrent/add?redir_after_login=1";
return uTorrentUrl + "&url=" + escape(url);
}
function makeUTorrentLink_private(link,i) {
var id = 'deluge_link' + i;
var uTorrentLink = document.createElement('a');
uTorrentLink.setAttribute("href", '#');
uTorrentLink.setAttribute("id", id);
uTorrentLink.style.paddingLeft = "5px";
uTorrentLink.innerHTML = "<img src=\"" + image + "\" style='border: 0px' />";
link.parentNode.insertBefore(uTorrentLink, link.nextSibling);
ulink = document.getElementById(id)
ulink.addEventListener("click", evt_private_submit_factory(link.href),false);
return uTorrentLink
}
function evt_private_submit_factory(url) {
//can this be done without magic?
function evt_private_submit(evt) {
GM_xmlhttpRequest({ method: 'GET', url: url,
overrideMimeType: 'text/plain; charset=x-user-defined',
onload: function(xhr) {
var stream = translateToBinaryString(xhr.responseText);
var data_b64 = window.btoa(stream);
post_to_webui(url, data_b64);
},
onerror:function(xhr) {
alert('error fetching torrent file');
}
});
return false;
}
return evt_private_submit;
}
function post_to_webui(url,data_b64){
//alert('here1');
//data contains the content of the .torrent-file.
var POST_data = ('pwd=' + encodeURIComponent(deluge_password) +
'&torrent_name=' + encodeURIComponent(url) + '.torrent' + //+.torrent is a clutch!
'&data_b64=' + encodeURIComponent(data_b64) );
//alert(POST_data);
GM_xmlhttpRequest({ method: 'POST',
url: "http://"+host+":"+port+"/remote/torrent/add",
headers:{'Content-type':'application/x-www-form-urlencoded'},
data: POST_data,
onload: function(xhr) {
if (xhr.responseText == 'ok\n') {
alert('Added torrent to webui : \n' + url);
}
else {
alert('Error adding torrent to webui:\n"' + xhr.responseText + '"');
}
},
onerror:function(xhr) {
alert('error submitting torrent file');
}
});
}
function match(url) {
// isohunt format
if (url.match(/http:\/\/.*isohunt\.com\/download\//i)) {
return true;
}
if (url.match(/\.torrent$/)) {
return true;
}
if (url.match(/http:\/\/.*bt-chat\.com\/download\.php/)) {
return true;
}
// TorrentReactor
if (url.match(/http:\/\/dl\.torrentreactor\.net\/download.php\?/i)) {
return true;
}
// Mininova
if (url.match(/http:\/\/www\.mininova\.org\/get\//i)) {
return true;
}
// Mininova
if (url.match(/http:\/\/www\.mininova\.org\/get\//i)) {
return true;
}
// TorrentSpy
if (url.match(/http:\/\/ts\.searching\.com\/download\.asp\?/i)) {
return true;
}
if (url.match(/http:\/\/www\.torrentspy\.com\/download.asp\?/i)) {
return true;
}
// Seedler
if (url.match(/http:\/\/.*seedler\.org\/download\.x\?/i)) {
return true;
}
return false;
}
function getLinks() {
var doc_links = document.links;
var links = new Array();
for (var i=0; i < doc_links.length; i++){
links.push(doc_links[i]);
}
return links;
}
var image = "data:image/gif;base64,R0lGODlhEAAQAMZyAB1CdihAYx5CdiBEeCJGeSZJfChKfChLfSpPgTBRgThRdDRUgzRVhDVWhDZWhThYhjtbiD1ciD5diT5eiz9eikBeiUFeiT5fjT1gjkBfjERijkdjiUhljkVnlEdolUxokExqkk5qkU9rklBrklFtk1BullFulk5vmlZymFx3nE97rVZ5pUx8sl54nlt5oVl6pE5/tWJ6nVp9qFqArWOEq1uIuW6EpGCItl2Ku26Gp2KKuGuIrF+MvWaLtl+Nv3KJqG+KrGaOu2aQv2SRwnGOs2uQvGqSwICOpoCQqm6Ww3OVvHKWv3iWuoKWsn+XtnacxXaeynifyXigzICewn2gxnqizoqfunujzpWesX6l0IyivYijw4+jvpOiuoOp0puktY2x2I6y2Y+z2pG02pW43Ze42pa43Z/A4qjG56jH56nI6KzJ6a/M67nR67zW8sLa9cff+M/k+P///////////////////////////////////////////////////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgB/ACwAAAAAEAAQAAAHkIB/goOEhYaCX1iHhkdIXU2LgzFARExbkYInCBcvRVSRHgQNEiYoPUmHGAkjO1FSSilBNYYQFTllY2BeSzJChg4iWmhpZ2JXOjgqhBMFH1xvbmtmWUMwM4QZBws/cXBsZFU+LCuFDwIhVm1qYVA8Nx2FEQQDHDZOU09GNIcWDAAGFEC0cBEpwAYNJUgowMQwEAA7";
scanLinks();
/*
binary magic,contains code taken from
http://mgran.blogspot.com/2006/08/downloading-binary-streams-with.html
*/
function translateToBinaryString(text){
var out;
out='';
for(i=0;i<text.length;i++){
//*bugfix* by Marcus Granado 2006 [http://mgran.blogspot.com] adapted by Thomas Belot
out+=String.fromCharCode(text.charCodeAt(i) & 0xff);
}
return out;
}

View File

@ -1,7 +0,0 @@
#!/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 '*.*~'

View File

@ -1 +0,0 @@
curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add

View File

@ -1,29 +0,0 @@
from __future__ import with_statement
import os
import re
template_dirs = ['~/prj/WebUi/templates/deluge',
'~/prj/WebUi/templates/advanced']
template_dirs = [os.path.expanduser(template_dir ) for template_dir in template_dirs]
files = []
for template_dir in template_dirs:
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 )

View File

@ -1,65 +0,0 @@
_('# Of Files')
_('About')
_('Add')
_('Add Torrent')
_('Add torrent')
_('Apply')
_('Auto refresh:')
_('Ava')
_('Availability')
_('Config')
_('Connections')
_('Debug:Data Dump')
_('Delete .torrent file')
_('Delete downloaded files.')
_('Details')
_('Disable')
_('Down')
_('Down Speed')
_('Download')
_('Downloaded')
_('ETA')
_('Enable')
_('Error')
_('Eta')
_('Login')
_('Logout')
_('Name')
_('Next Announce')
_('Off')
_('Password')
_('Password is invalid,try again')
_('Pause')
_('Pause all')
_('Peers')
_('Pieces')
_('Progress')
_('Queue Down')
_('Queue Position')
_('Queue Up')
_('Ratio')
_('Reannounce')
_('Refresh page every:')
_('Remove')
_('Remove torrent')
_('Resume')
_('Resume all')
_('Seeders')
_('Set')
_('Set Timeout')
_('Share Ratio')
_('Size')
_('Speed')
_('Start')
_('Submit')
_('Torrent list')
_('Total Size')
_('Tracker')
_('Tracker Status')
_('Up')
_('Up Speed')
_('Upload')
_('Upload torrent')
_('Uploaded')
_('Url')
_('seconds')

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA1sPXr1O6l2J9NAEvEYQ/JFDSVcJHh9YxP7kPdjsu7k9Ih845
BHMX52A3Ypbe5MHe2bCj/8dRYCixRdF1KUTAKXdzc7mw9prgf3sS3RvmfcRsln6u
x7XRg7YprZJ46hFmcHiUPRgtTFLuFO2YWBnqxu/caTtAxx3PdoK6LDVnuVjHYofC
8uD4A9k6yL/jj3Yrkf8WYQqJ6pJcMAz/2c8ZXlBuiUCb9j5xKTzYoJaiUkKN2YrA
hoxRxfI7Zc7MH2yWw8/fTZJbGXo8nrfek7coSE7yQS1M6ciwkYk5VO2mBVJBJgAT
QUR/jGfLzEqNKXghQ564v9wmuFmUMd99a0tkVwIDAQABAoIBACID6sluLYOEqefu
uBHCLG4IDwheOQ4esrYxDW3gedJs5EP+ObGmuQaAisUmuC7rNeysuYzteMoOJ+Wz
AyeCKB1pOfP+WTT12tDWIWq73InW7ov3jJ89AO4nj/pZ1KTeFKeDsZbrmWEZUXQn
HZX2pOTVYMeaBuyCoDVZBzuxSbhlON4wS6ClMhem+eBOxg351CDTZa2cbq7Ffcos
VP7LY2ORQYNDTQSLguV/dJrFSotB8Eoz2xIpg5XR7msp6lzPzyAd+Aoz/T1lYxCY
IFZCJYKnIpgoYQvmtUlhQrdD8P0J4Kth7I8NgkWvXCKazQjhpUm+wojLKD0G7Kcz
9znIV+ECgYEA+qfp1C8jWbaAn1yAeORUA9aB6aGIURfOpZjnCvtMWM0Nu0nAJYDv
X7L5GRa1ulfKhfUG1Jv/ynMKXYuBUDhyccYLpP7BHpd29Arr7YAgb52KaD1PoKNa
Z45c61dj4sFoCmJEbDoL21UGb0LX3mc4XzPzwWs8AKfLW4aZh1NwCisCgYEA21gJ
Hy3egBgMT9+nVjqsgtIXgJOnzQRhvRwT7IFf392ZyFi8iM+pDUsx1yj0zSG4XNPw
NY8VtZuTBUlG73RKcrrz31jhCMfLCnoRkQeweZv0QWzbLU3V8DleUYdjFc/t0me5
4NBR9lBlwYHgyU3GQ814vum+m0IAH0Ng1UxAVIUCgYAFOHwZTEYLN07kgtO2MOND
FTOtfwzMy5clQdMGGofTjanMjdOvtEjIEH05tYxhbjSsp5bV1M32FIFRw3cVCafw
kLRrYlb5YSQ8HwIc9z81s+1PEH/ZE63tXDy5Nh/BeE/Hb5aHPopCrjmtFZJTcojt
CrL4A1jDlrsYk+wcsnMx8wKBgEhJJQhvd2pDgps4G8+hGoUqc7Bd+OjpzsQh4rcI
k+4U+7847zkvJolJBK3hw3tu53FAL2OXOhJVqQgO9B+p9XcGAaTTh6X7IgDb5bok
DJanPMHq+/hcNGssnNbFhXQEyF2U7X8XaEuCh2ZURR5SUUq7BlX0dmp4P84NyHXC
4Vh5AoGAZYWkXxQUGzVm+H3fPpmETWGRNFDTimzi+6N+/uHkqkiDa3LGSnabmKh+
voKm//DUjEVGlAZ3CGOjO/5SlZc/zjkgh1vg7KOU4x7DqVOuZjom5Tx3ZI4xVVVt
tVtvK0qjzUTVcwAQALN/PNak+gs9534e954rmA9kmc3xBe4ho9M=
-----END RSA PRIVATE KEY-----

View File

@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDlzCCAn+gAwIBAgIJAPnW/GEzRy8xMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNV
BAYTAkFVMRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBX
ZWJ1aTAeFw0wNzExMjQxMDAzNDRaFw0wODExMjMxMDAzNDRaMDsxCzAJBgNVBAYT
AkFVMRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBXZWJ1
aTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANbD169TupdifTQBLxGE
PyRQ0lXCR4fWMT+5D3Y7Lu5PSIfOOQRzF+dgN2KW3uTB3tmwo//HUWAosUXRdSlE
wCl3c3O5sPaa4H97Et0b5n3EbJZ+rse10YO2Ka2SeOoRZnB4lD0YLUxS7hTtmFgZ
6sbv3Gk7QMcdz3aCuiw1Z7lYx2KHwvLg+APZOsi/4492K5H/FmEKieqSXDAM/9nP
GV5QbolAm/Y+cSk82KCWolJCjdmKwIaMUcXyO2XOzB9slsPP302SWxl6PJ633pO3
KEhO8kEtTOnIsJGJOVTtpgVSQSYAE0FEf4xny8xKjSl4IUOeuL/cJrhZlDHffWtL
ZFcCAwEAAaOBnTCBmjAdBgNVHQ4EFgQU1BbX1/4WtAKRKmWI1gqryIoj7BQwawYD
VR0jBGQwYoAU1BbX1/4WtAKRKmWI1gqryIoj7BShP6Q9MDsxCzAJBgNVBAYTAkFV
MRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBXZWJ1aYIJ
APnW/GEzRy8xMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEoiSz5x
hRCplxUG34g3F5yJe0QboqzJ/XmECfO80a980C/WVeivM2Kb1uafsKNp+WK7wD8g
mei+todYXG+fD8WmG41LG87Xi2Xe4SlAcemEpGcC5F1bpCdvqnVAWFnqoF88FOHx
NDlrq5H5lhMH9wVrX9qJvxL+StaDJ0sFk4kMGWEN+bdSYfFdBQzF903nPtm+PlvO
1Uo6gCuRTMYM5J1DC/GpNpo/Fzrkgm8mMf1MYy3rljiNgMt2rnxhtwi6jugwyMui
id6Of6gYAtvhi7kmaUpdI5PHO35dqRK7pHXH+YXaulosCPw/+bSRptFTykeEMrBj
CzotqJ+74MwXZyM=
-----END CERTIFICATE-----

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

View File

@ -1,91 +0,0 @@
/* ----------------------------------------------------------- Theme Name: Simple Theme URI: http://deluge-torrent.org Description: Deluge Theme Version: 1.0 ----------------------------------------------------------- */ BODY { background: #304663 url(images/simple_bg.jpg) repeat-x; font-family: trebuchet ms; font-size: 10pt; margin: 0; } /* GENERIC STYLES */ a img {border: 0px} hr {color: #627082; margin: 15px 0 15px 0;} /* STRUCTURE */ #page { min-width: 800px; margin-left: auto; margin-right: auto; } #main_content { background:url(images/simple_line.jpg) repeat-x; } #simple_logo { background:url(images/simple_logo.jpg) no-repeat; } #main { padding-top: 20px; padding-left: 20px; color: #fff; } #main form table { border: #2a425c 1px solid; } #main form table tr { border: 0px; } #main form table tr th { background: #1f3044; font-size: 16px; border: 0px;
white-space: nowrap; } #main form table tr td{ border: 0px; color: #fff; font-size: 12px; white-space: nowrap; } #main form table tr th a { color: #8fa6c3; font-size: 16px; white-space: nowrap; } #main form table tr th a, a:active, a:visited { color: #8fa6c3; text-decoration: none; } #main form table tr th a:hover {color: #fff; text-decoration: underline;} #main form table tr td a { color: #fff; font-size: 12px; white-space: nowrap; } #main form table tr td a, a:active, a:visited { color: #fff; text-decoration: none;} #main form table tr td a:hover {color: #fff; text-decoration: underline;} #main a { color: #fff; font-size: 12px; } #main a, a:active, a:visited { color: #fff; text-decoration: none;} #main a:hover {color: #fff; text-decoration: underline;} .info { text-align: right; padding: 0 50px 0 0; color: #8fa6c3; font-size: 16px; letter-spacing: 4px; font-weight: bold; } .title { color: #dce4ee; font-size: 32px; padding: 10px 50px 0 0; text-align: right; } .title a, a:active, a:visited { color: #dce4ee; text-decoration: none;} .title a:hover {color: #fff; text-decoration: underline;} #button { border:1px solid #23344b; background: #99acc3; color: #000; font-family:verdana, arial, helvetica, sans-serif; font-size:10px; margin-top:5px; } INPUT{ border:1px solid #23344b; background: #99acc3; color: #000; } TEXTAREA{ border:1px solid #23344b; background: #99acc3; width:480px; } .footertext a { color: #c0c0c0; text-decoration:none;} .footertext a:visited { color: #c0c0c0; text-decoration:none;} .footertext a:active { color: #c0c0c0; text-decoration:none;} .footertext a:hover {color: #fff; text-decoration: underline;} .footertext { text-align: center; padding: 60px 0 0 0; font-size: 8pt; left: -100px; font-family: trebuchet MS; color: #fff; position: relative; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } div.progress_bar{ background-color:#4573a5; /*color:blue;*/ -moz-border-radius:5px; /*ff only setting*/ }
div.progress_bar_outer { /*used in table-view*/
width:150px;
}
td.progress_bar { white-space: nowrap; } td.info_label { font-weight: bold; } td { font-size: 10pt; color: #d1dae5; white-space: nowrap; } tr {
font-size: 10pt;
color: #d1dae5;
}
div.panel {
padding:10px;
width:750px;
background-color: #37506f;
-moz-border-radius:10px; /*ff-only!*/
margin-top:10px;
margin-bottom:10px;
}
/*New styles:*/
div.deluge_button {
display:inline;
}
form.deluge_button {
display:inline;
}
button.deluge_button {
background-color: #37506f;
border:1px solid #68a;
background: #99acc3;
color: #000;
vertical-align:middle;
-moz-border-radius:7px;
}
button.deluge_button:hover {
background-color:#68a;
}
div.error {
background-color:#FFFFFF;
color:#AA0000;
font-weight:bold;
-moz-border-radius:10px;
width:200px;
margin-bottom:20px;
padding:10px;
}
/*tr.torrent_table:hover {
background-color:#68a;
}*/
tr.torrent_table_selected {
background-color:#900;
}
img.button {
margin-bottom:0px;
padding:0px;
position:relative;
top:2px;
}
body.inner {
background:none;
}
form.pause_resume {
margin:0;
padding:0;
border:0;
}
th {
background: #1f3044;
font-size: 14px;
border: 0px;
white-space: nowrap;
}
#torrent_table {
border: #2a425c 1px solid;
}
/* Hides from IE-mac \*/ * html .clearfix {height: 1%;} .clearfix {display: block;} /* End hide from IE-mac */

View File

@ -1,28 +0,0 @@
$def with (title)
<html>
<head>
<title>Deluge:$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" />
<link rel="stylesheet" type="text/css" href="/template/static/advanced.css" />
<script language="javascript" src="/template/static/deluge.js"></script>
<!--<link rel="stylesheet" type="text/css" href="/template/static/scrolling_table.css" />
-->
</head>
<body>
<div id="page">
<a href='/home'>
<div id="simple_logo">
<div class="title">Deluge</div>
<div class="info">$title</div>
</div>
</a>
<div id="main_content">
<div id="main">
<center>

View File

@ -1,146 +0,0 @@
$def with (torrent_list, all_torrents)
$:render.header(_('Torrent list'))
<div class="panel" id="toolbar">
<a class='toolbar_btn' href="#"
onclick='toolbar_get("/torrent/add/",0)'
title='$_("Add")'><img class='toolbar_btn'
src='/static/images/tango/list-add.png'></a>
<a class='toolbar_btn' href="#"
onclick='toolbar_get("/torrent/delete/",2)'><img class='toolbar_btn'
src='/static/images/tango/list-remove.png'
title='$_("Remove")'></a>
<a class='toolbar_btn' href="#"
onclick='toolbar_post("/torrent/stop/",2)'
title='$_("Pause")'><img class='toolbar_btn'
src='/static/images/tango/pause.png'
></a>
<a class='toolbar_btn' href="#"
onclick='toolbar_post("/torrent/start/",2)'
title='$_("Start")'><img class='toolbar_btn'
src='/static/images/tango/start.png'></a>
<a class='toolbar_btn' href="#"
onclick='toolbar_post("/torrent/queue/up/",2)'
title='$_("Up")'><img class='toolbar_btn'
src='/static/images/tango/queue-up.png'></a>
<a class='toolbar_btn' href="#"
onclick='toolbar_post("/torrent/queue/down/",2)'
title='$_("Down")'><img class='toolbar_btn'
src='/static/images/tango/queue-down.png'></a>
<a class='toolbar_btn' href="#"
onclick='toolbar_get("/torrent/info/",1)'
title='$_("Details")'><img class='toolbar_btn'
src='/static/images/tango/details.png'></a>
$:category_tabs(all_torrents)
</div>
<div id="tableContainer" class="tableContainer">
<table class="torrent_list" border=1 id="torrent_list">
<thead class="fixedHeader">
<tr>
$:(sort_head('calc_state_str', 'S'))
$:(sort_head('queue_pos', '#'))
$:(sort_head('name', _('Name')))
$:(sort_head('total_size', _('Size')))
$:(sort_head('progress', _('Progress')))
$if (not get('category')):
$:(sort_head('category', _('Tracker')))
$:(sort_head('num_seeds', _('Seeders')))
$:(sort_head('num_peers', _('Peers')))
$:(sort_head('download_rate', _('Download')))
$:(sort_head('upload_rate', _('Upload')))
$:(sort_head('eta', _('Eta')))
$:(sort_head('distributed_copies', _('Ava')))
$:(sort_head('ratio', _('Ratio')))
</tr>
</thead>
<tbody class="scrollContent">
$#4-space indentation is mandatory for for-loops in templetor!
$for torrent in torrent_list:
<tr class="torrent_table" onclick="on_click_row(event, '$torrent.id')" id="torrent_$torrent.id">
<td>
<form action="/torrent/$torrent.action/$torrent.id" method="POST"
class="pause_resume">
<input type="image"
src="/static/images/$(torrent.calc_state_str)16.png"
name="pauseresume" value="submit" />
</form>
</td>
<td>$torrent.queue_pos</td>
<td style="width:100px; overflow:hidden;white-space: nowrap">
$(crop(torrent.name, 40))</td>
<td>$fsize(torrent.total_size)</td>
<td class="progress_bar">
<div class="progress_bar_outer">
<div class="progress_bar" style="width:$(torrent.progress)%">
$torrent.message
</div>
</div>
</td>
$if (not get('category')):
<td>$torrent.category</td>
<td>$torrent.num_seeds ($torrent.total_seeds)</td>
<td>$torrent.num_peers ($torrent.total_peers)</td>
<td>
$if (torrent.download_rate):
$fspeed(torrent.download_rate)
$else:
&nbsp;
</td>
<td>
$if (torrent.upload_rate):
$fspeed(torrent.upload_rate)
$else:
&nbsp;
</td>
<td>$torrent.eta</td>
<td>$("%.3f" % torrent.distributed_copies)</td>
<td>$("%.3f" % torrent.ratio)</td\>
</tr>
</tbody>
</table>
</div>
$:part_stats()
<div class="panel" id="info_panel_div">
<iframe
style="border-style:hidden;width:740px;height:200px;"
id="torrent_info">
</iframe>
</div>
<br />
<script language='javascript'>
/*on_click_action = open_details;*/
on_click_action = on_click_row_js;
reselect_rows();
var id = state.selected_rows[0];
if (id) {
open_inner_details(id);
}
</script>
<form id='toolbar_form' method="POST" action="changed by javascript">
</form>
$:render.footer()

View File

@ -1,37 +0,0 @@
$def with (filter_tabs, category_tabs)
<form method="GET" id="category_form">
<input type="hidden" name="sort" value="$get('sort')">
<input type="hidden" name="order" value="$get('order')">
<select name='filter' id='filter'
onchange="document.getElementById('category_form').submit()"
title="$_('Filter on state')">
$for tab in filter_tabs:
<option value="$tab.filter"
$if tab.filter == get('filter'):
selected
>
$tab.title
</option>
</select>
<select name='category' id='category'
onchange="document.getElementById('category_form').submit()"
title="$_('Filter on Tracker')">
$for tab in category_tabs:
<option value="$tab.category"
$if tab.category == get('category'):
selected
>
$tab.title
</option>
</select>
<input type="image" id='toolbar_refresh'
src='/static/images/tango/view-refresh.png'
title='$_('Refresh')'
>
</form>

View File

@ -1,36 +0,0 @@
$def with (stats)
<div class="panel" id='refresh_panel'>
$_('Auto refresh:')
$if getcookie('auto_refresh') == '1':
($getcookie('auto_refresh_secs')) $_('seconds') &nbsp;
$:render.part_button('GET', '/refresh/set', _('Set'), 'tango/preferences-system.png')
$:render.part_button('POST', '/refresh/off', _('Disable'), 'tango/process-stop.png')
$else:
$_('Off') &nbsp;
$:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png')
$#end
</div>
<div class="panel" id='stats_panel'>
<!--<a href='/config'>-->
$_('Connections') : $stats.num_connections ($stats.max_num_connections)
$_('Down Speed') : $stats.download_rate ($stats.max_download)
$_('Up Speed') : $stats.upload_rate ($stats.max_upload)
<!--</a>-->
</div>
<div id='about'>
<a href='/about'>$_('About')</a>
</div>

View File

@ -1,35 +0,0 @@
$def with (method, func, title, image='')
<div class="deluge_button">
<form method="$method" action="$url" class="deluge_button">
<input type="hidden" name="redir" value="$self_url()">
$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:
<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>
</form>
</div>
<!--
pause
start
up
down
-->

View File

@ -1,247 +0,0 @@
/* ----------------------------------------------------------- Theme Name: Simple Theme URI: http://deluge-torrent.org Description: Deluge Theme Version: 1.0 ----------------------------------------------------------- */ BODY { background: #304663 url(../../static/images/simple_bg.jpg) repeat-x; font-family: Bitstream Vera,Verdana; font-size: 10pt; margin: 0;
padding:0;
border:0; } /* GENERIC STYLES */ a img {border: 0px} hr {color: #627082; margin: 15px 0 15px 0;}
td {font-family: Bitstream Vera,Verdana;}
tr {font-family: Bitstream Vera,Verdana;}
table {font-family: Bitstream Vera,Verdana;} div {font-family: Bitstream Vera,Verdana;} /* STRUCTURE */ #page { min-width: 800px; margin-left: auto; margin-right: auto;
margin: 0;
padding:0;
font-family: Bitstream Vera,Verdana; } #main_content { background:url(../../static/images/simple_line.jpg) repeat-x;
margin: 0;
padding:0; } #simple_logo { background:url(../../static/images/simple_logo.jpg) no-repeat; } #main {
margin: 0;
padding:0; padding-top: 6px; color: #fff; } #main form table { border: #2a425c 1px solid; } #main form table tr { border: 0px; } #main form table tr th { background: #1f3044; font-size: 16px; border: 0px;
white-space: nowrap; } #main form table tr td{ border: 0px; color: #fff; font-size: 12px; white-space: nowrap;
font-family: Bitstream Vera,Verdana; } #main form table tr th a { color: #8fa6c3; font-size: 16px; white-space: nowrap; } #main form table tr th a, a:active, a:visited { color: #8fa6c3; text-decoration: none; } #main form table tr th a:hover {color: #fff; text-decoration: underline;} #main form table tr td a { color: #fff; font-size: 12px; white-space: nowrap;
font-family: Bitstream Vera,Verdana; } #main form table tr td a, a:active, a:visited { color: #fff; text-decoration: none;} #main form table tr td a:hover {color: #fff; text-decoration: underline;} #main a { color: #fff; font-size: 12px; } #main a, a:active, a:visited { color: #fff; text-decoration: none;} #main a:hover {color: #fff; text-decoration: underline;} .info { text-align: right; padding: 0 50px 0 0; color: #8fa6c3; font-size: 16px; letter-spacing: 4px; font-weight: bold; } .title { color: #dce4ee; font-size: 32px; padding: 10px 50px 0 0; text-align: right; } .title a, a:active, a:visited { color: #dce4ee; text-decoration: none;} .title a:hover {color: #fff; text-decoration: underline;} input{
background-color: #37506f;
border:1px solid #68a;
background: #99acc3;
color: #000;
/*vertical-align:middle;*/
-moz-border-radius:5px;
/*margin-top:5px;*/
}
input:hover {
background-color:#68a;
} TEXTAREA{ border:1px solid #23344b; background: #99acc3; width:480px; } .footertext a { color: #c0c0c0; text-decoration:none;} .footertext a:visited { color: #c0c0c0; text-decoration:none;} .footertext a:active { color: #c0c0c0; text-decoration:none;} .footertext a:hover {color: #fff; text-decoration: underline;} .footertext { text-align: center; padding: 60px 0 0 0; font-size: 8pt; left: -100px; font-family: Bitstream Vera,Verdana; color: #fff; position: relative; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } div.progress_bar{ background-color:#4573a5; /*color:blue;*/ -moz-border-radius:5px; /*ff only setting*/ }
div.progress_bar_outer { /*used in table-view*/
width:150px;
}
td.progress_bar { white-space: nowrap; } td.info_label { font-weight: bold; } td { font-size: 10pt; color: #d1dae5; white-space: nowrap; } tr { font-size: 10pt; color: #d1dae5; }
div.panel {
padding:10px;
width:750px;
background-color: #37506f;
-moz-border-radius:10px; /*ff-only!*/
margin-top:10px;
margin-bottom:10px;
}
/*New styles:*/
div.deluge_button {
display:inline;
}
form.deluge_button {
display:inline;
}
button.deluge_button {
background-color: #37506f;
border:1px solid #68a;
background: #99acc3;
color: #000;
vertical-align:middle;
-moz-border-radius:7px;
}
button.deluge_button:hover {
background-color:#68a;
}
div.error {
background-color:#FFFFFF;
color:#AA0000;
font-weight:bold;
-moz-border-radius:10px;
width:200px;
margin-bottom:20px;
padding:10px;
}
tr.torrent_table:hover {
background-color:#68a;
}
tr.torrent_table_selected {
background-color:#900;
}
th.torrent_table:hover {
background-color:#68a;
}
img.button {
margin-bottom:0px;
padding:0px;
position:relative;
top:2px;
}
body.inner {
background:none;
}
#stats_panel {
-moz-border-radius:0px;
width:100%;
position:fixed;
bottom:0px;
left:0px;
background-color:#304663;
margin: 0;
padding:0;
text-align:left;
height:20px;
background-color:#ddd;
color:#000;
border-style:solid;
border:0;
border-top:1px;
border-color:#000;
}
#about {
position:fixed;
bottom:0px;
right:10px;
}
#info_panel_div2 {
position:fixed;
bottom:10px;
right:0px;
width:100%;
background-color:#304663;
}
#refresh_panel {
-moz-border-radius:0px;
width:350px;
position:fixed;
bottom:0px;
right:0px;
background-color:#304663;
margin: 0;
padding:0;
text-align:right;
height:20px;
background-color:#ddd;
color:#000;
z-index:999;
}
#refresh_panel button {
background-color:#304663;
color:#FFFFFF;
border:0;
position:relative;
top:0px;
height:20px;
background-color:#ddd;
color:#000;
}
#refresh_panel button:hover {
text-decoration: underline;
}
#category_panel {
margin-bottom:0;
padding-bottom:0;
-moz-border-radius-bottomleft:0px;
-moz-border-radius-bottomright:0px;
padding-right:32px;
}
#toolbar {
text-align:left;
margin-top:0;
padding-top:0;
margin-bottom: 30px;
-moz-border-radius-topleft:0px;
-moz-border-radius-topright:0px;
padding-top:5px;
padding-bottom:5px;
margin-bottom: 15px;
padding-left:32px;
height:20px;
}
#toolbar select{
/*border:1px solid #68a;*/
border:0;
background-color: #37506f;
color: #FFF;
}
#toolbar select:hover{
background-color:#68a;
}
a.toolbar_btn {
width:20px;
height:20px;
padding-left:3px;
padding-top:7px;
padding-right:3px;
text-decoration: none;
margin-bottom:3px;
}
a.toolbar_btn:hover {
background-color:#68a;
-moz-border-radius:5px;
text-decoration: none;
}
#toolbar_refresh {
margin:0;
border:0;
background-color:none;
padding-left:2px;
padding-top:2px;
padding-right:2px;
text-decoration: none;
background-color: #37506f;
position:relative;
top:5px;
}
#toolbar_refresh:hover {
background-color:#68a;
-moz-border-radius:5px;
text-decoration: none;
}
#category_form{
display:inline;
position:relative;
top:-3px;
padding-left:20px;
}
form { /*all forms!*/
margin:0;
padding:0;
border:0;
}
#torrent_list {
-moz-border-radius:7px;
}
/* Hides from IE-mac \*/ * html .clearfix {height: 1%;} .clearfix {display: block;} /* End hide from IE-mac */

View File

@ -1,145 +0,0 @@
/*
all javascript is optional, everything should work web 1.0
but javascript may/will enhance the experience.
i'm not a full time web-dev so don't expect beautifull patterns.
There's so much crap out there,i can't find good examples.
so i'd rather start from scratch,
Probably broken in an unexpected way , but worksforme.
*/
state = {
'row_js_continue':true
,'selected_rows': new Array()
};
function $(el_id){
return document.getElementById(el_id)
}
function get_row(id){
return $('torrent_' + id);
}
function on_click_row(e,id) {
/*filter out web 1.0 events for detail-link and pause*/
if (state.row_js_continue) {
on_click_action(e,id);
}
state.row_js_continue = true;
}
function on_click_row_js(e, id) {
/*real onClick event*/
if (!e.ctrlKey) {
deselect_all_rows();
select_row(id);
open_inner_details(id);
}
else if (state.selected_rows.indexOf(id) != -1) {
deselect_row(id);
}
else{
select_row(id);
open_inner_details(id);
}
}
function select_row(id){
var row = get_row(id);
if (row) {
row.className = 'torrent_table_selected';
state.selected_rows[state.selected_rows.length] = id;
setCookie('selected_rows',state.selected_rows);
}
}
function deselect_row(id){
var row = get_row(id);
if (row) {
row.className = 'torrent_table'
/*remove from state.selected_rows*/
var idx = state.selected_rows.indexOf(id);
state.selected_rows.splice(idx,1);
setCookie('selected_rows',state.selected_rows);
}
}
function deselect_all_rows(){
/*unbind state.selected_rows from for..in:
there must be a better way to do this*/
var a = new Array()
for (i in state.selected_rows) {
a[a.length] = state.selected_rows[i];
}
for (i in a){
deselect_row(a[i]);
}
}
function reselect_rows(){
var selected_rows = getCookie('selected_rows').split(',');
for (i in getCookie('selected_rows')) {
select_row(selected_rows[i]);
}
}
function open_details(e, id){
alert(id);
window.location.href = '/torrent/info/' + id;
}
function open_inner_details(id){
/*probably broken for IE, use FF!*/
$('torrent_info').src = '/torrent/info_inner/' + id;
}
function on_click_do_nothing(e, id){
}
on_click_action = on_click_do_nothing;
/*toobar buttons, */
function toolbar_post(url, selected) {
if ((!selected) || (state.selected_rows.length > 0)) {
var ids = state.selected_rows.join(',');
var form = $('toolbar_form');
form.action = url +ids;
form.submit();
}
return false;
}
function toolbar_get(url , selected) {
if (!selected) {
window.location.href = url
}
else if (state.selected_rows.length > 0) {
var ids = state.selected_rows.join(',');
window.location.href = url +ids;
}
return false;
}
/*stuff copied from various places:*/
/*http://www.w3schools.com/js/js_cookies.asp*/
function setCookie(c_name,value,expiredays)
{
var exdate=new Date()
exdate.setDate(exdate.getDate()+expiredays)
document.cookie=c_name+ "=" +escape(value)+
((expiredays==null) ? "" : ";expires="+exdate.toGMTString())
}
function getCookie(c_name)
{
if (document.cookie.length>0)
{
c_start=document.cookie.indexOf(c_name + "=")
if (c_start!=-1)
{
c_start=c_start + c_name.length+1
c_end=document.cookie.indexOf(";",c_start)
if (c_end==-1) c_end=document.cookie.length
return unescape(document.cookie.substring(c_start,c_end))
}
}
return ""
}

View File

@ -1,106 +0,0 @@
/*Taken from:
http://www.imaputz.com/cssStuff/bigFourVersion.html
*/
/* define height and width of scrollable area. Add 16px to width for scrollbar */
div.tableContainer {
clear: both;
/*border: 1px solid #963;*/
height: 285px;
overflow: auto;
width: 756px;
}
/* Reset overflow value to hidden for all non-IE browsers. */
html>body div.tableContainer {
overflow: hidden;
width: 756px
}
/* define width of table. IE browsers only */
div.tableContainer table {
float: left;
width: 740px;
}
/* define width of table. Add 16px to width for scrollbar. */
/* All other non-IE browsers. */
html>body div.tableContainer table {
width: 756px
}
/* set table header to a fixed position. WinIE 6.x only */
/* In WinIE 6.x, any element with a position property set to relative and is a child of */
/* an element that has an overflow property set, the relative value translates into fixed. */
/* Ex: parent element DIV with a class of tableContainer has an overflow property set to auto */
thead.fixedHeader tr {
position: relative
}
/* set THEAD element to have block level attributes. All other non-IE browsers */
/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */
html>body thead.fixedHeader tr {
display: block
}
/* define the table content to be scrollable */
/* set TBODY element to have block level attributes. All other non-IE browsers */
/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */
/* induced side effect is that child TDs no longer accept width: auto */
html>body tbody.scrollContent {
display: block;
height: 262px;
overflow: auto;
width: 100%
}
/* make TD elements pretty. Provide alternating classes for striping the table */
/* http://www.alistapart.com/articles/zebratables/ */
tbody.scrollContent td, tbody.scrollContent tr.normalRow td {
/*background: #FFF;*/
border-bottom: none;
border-left: none;
/*border-right: 1px solid #CCC;
border-top: 1px solid #DDD;*/
padding: 2px 3px 3px 4px
}
tbody.scrollContent tr.alternateRow td {
/*background: #EEE;*/
border-bottom: none;
border-left: none;
/*border-right: 1px solid #CCC;
border-top: 1px solid #DDD;*/
padding: 2px 3px 3px 4px
}
/* define width of TH elements: 1st, 2nd, and 3rd respectively. */
/* Add 16px to last TH for scrollbar padding. All other non-IE browsers. */
/* http://www.w3.org/TR/REC-CSS2/selector.html#adjacent-selectors */
html>body thead.fixedHeader th {
width: 200px
}
html>body thead.fixedHeader th + th {
width: 240px
}
html>body thead.fixedHeader th + th + th {
width: 316px
}
/* define width of TD elements: 1st, 2nd, and 3rd respectively. */
/* All other non-IE browsers. */
/* http://www.w3.org/TR/REC-CSS2/selector.html#adjacent-selectors */
html>body tbody.scrollContent td {
width: 200px
}
html>body tbody.scrollContent td + td {
width: 240px
}
html>body tbody.scrollContent td + td + td {
width: 300px
}

View File

@ -1,15 +0,0 @@
$def with (torrent)
<!--for iframe in javascript mode.-->
<html><head>
<html>
<head>
<title>Deluge:$torrent.name</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" />
<link rel="stylesheet" type="text/css" href="/static/simple_site_style.css" />
</head>
<body class="inner">
<!--[Tab :Details] [Tab : Files] [Tab : Peers]-->
$:render.tab_meta(torrent)
$:render.footer()

View File

@ -1,49 +0,0 @@
$:render.header(_('About'))
<div class="panel" style="text-align:left">
<h2>Version</h2>
<pre>$version </pre>
<h2>Links</h2>
<ul>
<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>
</ul>
<h2>Authors</h2>
<ul>
<h3>WebUi</h3>
<ul>
<li>Martijn Voncken</li>
</ul>
<h3>Template</h3>
<ul>
<li>Martijn Voncken</li>
<li>somedude</li>
</ul>
<h3>Deluge</h3>
<ul>
<li>Zach Tibbitts</li>
<li>Alon Zakai</li>
<li>Alon Zakai</li>
<li>Marcos Pinto</li>
<li>Andrew Resch</li>
<li>Alex Dedul</li>
</ul>
<h3>Windows Port</h3>
<ul>
<li>Slurdge</li>
</ul>
</ul>
*and all other authors/helpers/contributors I forgot to mention.
</div>
$:render.footer()

View File

@ -1,5 +0,0 @@
-first layout taken from deluge website
improved by:
-mvoncken
-somedude

View File

@ -1,10 +0,0 @@
$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()

View File

@ -1,6 +0,0 @@
$def with (error_msg)
$:render.header(_('Error'))
<pre class="error">
$error_msg
</pre>
$:render.footer()

View File

@ -1,6 +0,0 @@
</center>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,23 +0,0 @@
$def with (title)
<html>
<head>
<title>Deluge:$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" />
<link rel="stylesheet" type="text/css" href="/static/simple_site_style.css" />
</head>
<body>
<div id="page">
<a href='/home'>
<div id="simple_logo">
<div class="title">Deluge</div>
<div class="info">$title</div>
</div>
</a>
<div id="main_content">
<div id="main">
<center>

View File

@ -1,61 +0,0 @@
$def with (torrent_list, all_torrents)
$:render.header(_('Torrent list'))
<table class="torrent_list" border=0 id='torrent_table'>
<tr>
$:(sort_head('calc_state_str', 'S'))
$:(sort_head('queue_pos', '#'))
$:(sort_head('name', _('Name')))
$:(sort_head('total_size', _('Size')))
$:(sort_head('progress', _('Progress')))
$:(sort_head('num_seeds', _('Seeders')))
$:(sort_head('num_peers', _('Peers')))
$:(sort_head('download_rate', _('Download')))
$:(sort_head('upload_rate', _('Upload')))
$:(sort_head('eta', _('Eta')))
$:(sort_head('distributed_copies', _('Ava')))
$:(sort_head('ratio', _('Ratio')))
</tr>
$#4-space indentation is mandatory for for-loops in templetor!
$for torrent in torrent_list:
<tr class="torrent_table" id="torrent_$torrent.id">
<td>
<form action="/torrent/$torrent.action/$torrent.id" method="POST" class="pause_resume">
<input type="image"
src="/static/images/$(torrent.calc_state_str)16.png"
name="pauseresume" value="submit" /></form></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>
<td class="progress_bar">
<div class="progress_bar_outer">
<div class="progress_bar" style="width:$(torrent.progress)%">
$torrent.message
</div>
</div>
</td>
<td>$torrent.num_seeds ($torrent.total_seeds)</td>
<td>$torrent.num_peers ($torrent.total_peers)</td>
<td>$fspeed(torrent.download_rate)</td>
<td>$fspeed(torrent.upload_rate)</td>
<td>$torrent.eta</td>
<td>$("%.3f" % torrent.distributed_copies)</td>
<td>$("%.3f" % torrent.ratio)</td\>
</tr>
</table>
<div class="panel">
$:render.part_button('GET', '/torrent/add', _('Add torrent'), 'tango/list-add.png')
$:render.part_button('POST', '/pause_all', _('Pause all'), 'tango/pause.png')
$:render.part_button('POST', '/resume_all', _('Resume all'), 'tango/start.png')
<!--$:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png')-->
</div>
$:part_stats()
$:render.footer()

View File

@ -1,25 +0,0 @@
$def with (error)
$:render.header(_('Login'))
<div class="panel">
$if error > 0:
<div class="error">$_("Password is invalid,try again")</div>
<form method="POST" id="loginform" action='/login'>
<input type="hidden" name="redir" value="$get('redir')">
<div id="loginpanel">
<div class="form_row">
<span class="form_label">$_('Password')</span>
<input type="password" name="pwd" id="pwd" class="form_input">
</div>
<div class="form_row">
<span class="form_label"></span>
<input type="submit" name="submit"
id="submit" value="Submit" class="form_input">
</div>
<br />
<a href="/about">$_('About')</a>
</div>
</form>
</div>
$:render.footer()

View File

@ -1,26 +0,0 @@
$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()">
$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:
<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>
</form>
</div>

View File

@ -1,35 +0,0 @@
$def with (stats)
<div class="panel" id='refresh_panel'>
$_('Auto refresh:')
$if getcookie('auto_refresh') == '1':
($getcookie('auto_refresh_secs')) $_('seconds') &nbsp;
$:render.part_button('GET', '/refresh/set', _('Set'), 'tango/preferences-system.png')
$:render.part_button('POST', '/refresh/off', _('Disable'), 'tango/process-stop.png')
$else:
$_('Off') &nbsp;
$:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png')
$#end
</div>
<div class="panel" id='stats_panel'>
<!--<a href='/config'>-->
$_('Connections') : $stats.num_connections ($stats.max_num_connections)
$_('Down Speed') : $stats.download_rate ($stats.max_download)
$_('Up Speed') : $stats.upload_rate ($stats.max_upload)
<!--</a>-->
<span id=#about>
(<a href='/about'>$_('About')</a>)
</span>
</div>

View File

@ -1,11 +0,0 @@
$:render.header(_('Set Timeout'))
<div class="panel">
<form action="/refresh/set" method="POST">
$_('Refresh page every:')
<input type="text" name="refresh" value="$getcookie('auto_refresh_secs')"
size="3">
$_('seconds')
<input type="submit" value="$_('Set')">
</form>
</div>
$:render.footer()

View File

@ -1,12 +0,0 @@
$def with (column_id, column_name, order, active_up, active_down)
<th class="torrent_table">
<a href="/index?sort=$column_id&order=$order&filter=$get('filter')&category=$get('category')">
$column_name\
$if active_up:
<img src="/static/images/tango/up.png" />
$if active_down:
<img src="/static/images/tango/down.png" />
</a>
</th>

View File

@ -1,85 +0,0 @@
$def with (torrent)
<table width="100%"><tr>
<td colspan=3 style="background-color:#999;-moz-border-radius:5px;">
<div class="progress_bar" style="width:$torrent.progress%;
text-align:center;font-weight:bold;">
$torrent.progress %</div>
</td>
</tr><td width=30%%>
<table>
<tr><td class="info_label">$_('Downloaded'):</td>
<td class="info_value">$torrent.calc_total_downloaded</td></tr>
<tr><td class="info_label">$_('Uploaded'):</td>
<td class="info_value">$torrent.calc_total_uploaded</td>
</tr>
<tr><td class="info_label">$_('Seeders'):</td>
<td class="info_value">$torrent.num_seeds ($torrent.total_seeds )</td></tr>
<tr><td class="info_label">$_('Share Ratio'):</td>
<td class="info_value">$("%.3f" % torrent.ratio)</td></tr>
<tr><td class="info_label">$_('Pieces'):</td>
<td class="info_value">$torrent.num_pieces x $fsize(torrent.piece_length) </td>
</tr>
<tr><td class="info_label">&nbsp;</td>
<td class="info_value">&nbsp; </td>
</table>
</td><td width=30%%>
<table>
<tr><td class="info_label">$_('Speed'):</td><td class="info_value">
$fspeed(torrent.download_rate)</td></td></tr>
<tr><td class="info_label">$_('Speed'):</td>
<td class="info_value">$fspeed(torrent.upload_rate)</td></tr>
<tr><td class="info_label">$_('Peers'):</td>
<td class="info_value">$torrent.num_peers ($torrent.total_peers )</td></tr>
<tr><td class="info_label">$_('ETA'):</td>
<td class="info_value">$torrent.eta </td></tr>
<tr><td class="info_label">$_('Availability'):</td>
<td class="info_value">$("%.3f" % torrent.distributed_copies)</td></td></tr>
<tr><td class="info_label">&nbsp;</td>
<td class="info_value">&nbsp; </td>
</tr>
</table>
</td><td width=30%%>
<table>
<tr><td class="info_label">$_('Total Size'):</td>
<td class="info_value">$fspeed(torrent.total_size)</td></tr>
<tr><td class="info_label">$_('# Of Files'):</td>
<td class="info_value">$torrent.num_files</td></tr>
<tr><td class="info_label">$_('Tracker'):</td>
<td class="info_value" title="$torrent.tracker">$(crop(torrent.tracker, 30))</td></tr>
<tr><td class="info_label">$_('Tracker Status'):</td>
<td class="info_value" title="$torrent.tracker_status">$(crop(torrent.tracker_status, 30)) </td></tr>
<tr><td class="info_label">$_('Next Announce'):</td>
<td class="info_value">$torrent.next_announce </td></tr>
<tr><td class="info_label">$_('Queue Position'):</td>
<td class="info_value">$torrent.queue_pos </td>
</tr>
</table>
</table>

View File

@ -1,22 +0,0 @@
$:render.header(_("Add Torrent"))
<div class="panel">
<form method="POST" action="/torrent/add" ENCTYPE="multipart/form-data">
<div id="torrent_add">
<div class="form_row">
<span class="form_label">$_('Url')</span>
<input type="text" name="url" class="form_input" size=60
value="$get('url')" >
</div>
<div class="form_row">
<span class="form_label">$_('Upload torrent')</span>
<input type="file" name="torrent" class="form_input" size=50>
</div>
<div class="form_row">
<span class="form_label"></span>
<input type="submit" name="submit"
value="$_('Submit')" class="form_input">
</div>
</div>
</form>
</div>
$:render.footer()

View File

@ -1,31 +0,0 @@
$def with (torrent_ids, torrent_list)
$:render.header(_("Remove torrent"))
<div class="panel">
<form method="POST" action='/torrent/delete/$torrent_ids'>
<div id="del_torrent">
<h2>$_("Remove torrent")</h2>
<ul>
$for torrent in torrent_list:
<li>$torrent.name</li>
</ul>
<div class="form_row2">
<span class="form_label2">
<input type="checkbox" name="torrent_also" class="form_input" checked
value="1">$_('Delete .torrent file')</span>
</div>
<div class="form_row2">
<span class="form_label2">
<input type="checkbox" name="data_also" class="form_input"
value="1">$_('Delete downloaded files.')</span>
</div>
<div class="form_row2">
<span class="form_label2"></span>
<input type="submit" name="submit"
value="$_('Remove')" class="form_input">
</div>
</div>
</form>
</div>
$:render.footer()

View File

@ -1,50 +0,0 @@
$def with (torrent)
$:(render.header(torrent.message + '/' + torrent.name))
<div class="panel">
<h3>$_('Details')</h3>
$:render.tab_meta(torrent)
$if (torrent.action == 'start'):
$:render.part_button('POST', '/torrent/start/' + str(torrent.id), _('Resume'), 'tango/start.png')
$else:
$:render.part_button('POST', '/torrent/stop/' + str(torrent.id), _('Pause'), 'tango/pause.png')
$:render.part_button('GET', '/torrent/delete/' + str(torrent.id), _('Remove'), 'tango/list-remove.png')
$:render.part_button('POST', '/torrent/reannounce/' + str(torrent.id), _('Reannounce'), 'tango/view-refresh.png')
$:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up'), 'tango/queue-up.png')
$:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/queue-down.png')
<br>
<!--
[<a onclick="javascript:toggle_dump()">$_('Debug:Data Dump')</a>]
<pre style="background-color:white;color:black;display:none" id="data_dump">
$for key in sorted(torrent):
$key : $torrent[key]
</pre>
<script language="javascript">
function toggle_dump(){
el = document.getElementById("data_dump");
if (el.style.display == "block"){
el.style.display = "none";
}
else{
el.style.display = "block";
}
}
</script>
-->
</div>
$:part_stats()
$:render.footer()

View File

@ -1,39 +0,0 @@
Quickstart:
Just copy and rename an existing template.
-The settings panel will see all directory's in this folder ,and let you choose your new template.
-Clicking Ok in the settings panel will restart the webserver and reload your template.
Limited "Subclassing":
All templates are "subclassed" from the /deluge/ template.
If a html file is not found in the template dir, the file from /deluge/ will be used.
Notes:
Please configure your editor to use 4-space indents instead of tabs.
Or use scite and my config: http://mvoncken.sohosted.com/deluge/SciTEUser.properties.txt
template language: http://webpy.org/templetor
Exposed methods and variables (c&p from webserver_framework.py):
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.
'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 :-(
})
I will update this file if there is interest in making templates.

View File

@ -1,382 +0,0 @@
"""
Testing the REST api, not the units.
unittest the right way feels so unpythonic :(
!! BIG FAT WARNING !!: this test deletes active torrents .
!! BIG FAT WARNING 2!!: this test hammers the tracker that is tested against.
"""
import unittest
import cookielib, urllib2 , urllib
import WebUi.webserver_common as ws
import operator
ws.init_05()
print 'test-env=',ws.ENV
#CONFIG:
BASE_URL = 'http://localhost:8112'
PWD = 'deluge'
def get_status(id):
return ws.proxy.get_torrent_status(id,ws.TORRENT_KEYS)
#BASE:
#303 = see other
#404 = not found
#500 = server error
#200 = OK, page exists.
class TestWebUiBase(unittest.TestCase):
def setUp(self):
#cookie aware-opener that DOES NOT use redirects.
opener = urllib2.OpenerDirector()
self.cj = cookielib.CookieJar()
for handler in [urllib2.HTTPHandler(),urllib2.HTTPDefaultErrorHandler(),
urllib2.FileHandler(),urllib2.HTTPErrorProcessor(),
urllib2.HTTPCookieProcessor(self.cj)]:
opener.add_handler(handler)
#/opener
self.opener = opener
def open_url(self, page, post=None):
url = BASE_URL + page
if post == 1:
post = {'Force_a_post' : 'spam'}
if post:
post = urllib.urlencode(post)
r = self.opener.open(url , data = post)
#BUG: error-page does not return status 500, but status 200
#workaround...
data = r.read()
if '<!--ERROR-MARKER-->' in data:
error = IOError()
error.code = 500
#print data
raise error
if r.code <> 200:
fail('no code 200, error-code=%s' % r.code)
return r
def get_cookies(self):
return dict((c.name,c.value) for c in self.cj)
cookies = property(get_cookies)
def assert_status(self,status, page, post):
try :
r = self.open_url(page, post)
except IOError,e:
self.assertEqual(e.code, status)
else:
self.fail('page was found "%s" (%s)' % (page, r.code ))
def assert_404(self, page, post = None):
self.assert_status(404, page, post)
def assert_500(self, page, post = None):
self.assert_status(500, page, post)
def assert_303(self, page, redirect_to, post=None):
try :
r = self.open_url(page, post)
except IOError,e:
self.assertEqual(e.code, 303)
self.assertEqual(e.headers['Location'], redirect_to)
else:
#print r
self.fail('No 303!')
def assert_exists(self, page, post = None):
try :
r = self.open_url(page, post)
except IOError,e:
self.fail('page was not found "%s" (%s)' % (page, e.code))
else:
pass
first_torrent_id = property(lambda self: ws.proxy.get_session_state()[0])
first_torrent = property(lambda self: get_status(self.first_torrent_id))
class TestNoAuth(TestWebUiBase):
def test303(self):
self.assert_303('/','/login')
self.assert_303('','/login')
self.assert_303('/index','/login')
#self.assert_303('/torrent/pause/','/login')
self.assert_303('/config','/login')
self.assert_303('/torrent/info/','/login')
def test404(self):
self.assert_404('/torrent/info')
self.assert_404('/garbage')
#self.assert_404('/static/garbage')
#self.assert_404('/template/static/garbage')
self.assert_404('/torrent/pause/', post=1)
def testOpen(self):
self.assert_exists('/login')
self.assert_exists('/about')
def testStatic(self):
self.assert_exists('/static/images/simple_line.jpg')
self.assert_exists('/static/images/tango/up.png')
#test 404
#test template-static
class TestSession(TestWebUiBase):
def testLogin(self):
self.assert_303('/home','/login')
#invalid pwd:
self.assert_303('/login','/login?error=1',{'pwd':'invalid'})
#login
self.assert_303('/login','/index',{'pwd':PWD})
#now i'm logged-in!
#there are no sort-coockies yet so the default page is /index.
self.assert_303('/home','/index')
self.assert_exists('/index')
self.assert_exists('/config')
self.assert_exists('/torrent/add')
self.assert_303('/','/index')
self.assert_303('','/index')
#logout
self.assert_303('/logout','/login', post=1)
#really logged out?
self.assert_303('/','/login')
self.assert_303('','/login')
self.assert_303('/index','/login')
self.assert_303('/torrent/add','/login')
self.assert_exists('/about')
def testRefresh(self):
#starting pos
self.assert_303('/login','/index',{'pwd':PWD})
r = self.open_url('/index')
assert not 'auto_refresh' in self.cookies
assert not 'auto_refresh_secs' in self.cookies
assert not r.headers.has_key('Refresh')
#on:
self.assert_303('/refresh/on','/index', post=1)
assert 'auto_refresh' in self.cookies
assert 'auto_refresh_secs' in self.cookies
self.assertEqual(self.cookies['auto_refresh'],'1')
self.assertEqual(self.cookies['auto_refresh_secs'],'10')
r = self.open_url('/index')
assert r.headers['Refresh'] == '10 ; url=/index'
#set:
self.assert_303('/refresh/set','/index',{'refresh':'5'})
self.assertEqual(self.cookies['auto_refresh_secs'],'5')
r = self.open_url('/index')
assert r.headers['Refresh'] == '5 ; url=/index'
self.assert_500('/refresh/set',{'refresh':'a string'})
#off:
self.assert_303('/refresh/off','/index', post=1)
self.assertEqual(self.cookies['auto_refresh'],'0')
self.assertEqual(self.cookies['auto_refresh_secs'],'5')
r = self.open_url('/index')
assert not 'Refresh' in r.headers
class TestIntegration(TestWebUiBase):
initialized = False
def setUp(self):
TestWebUiBase.setUp(self)
self.assert_303('/login','/index',{'pwd':PWD})
self.urls = sorted([
'http://torrents.aelitis.com:88/torrents/azplatform2_1.13.zip.torrent',
'http://torrents.aelitis.com:88/torrents/azplugins_2.1.4.jar.torrent',
'http://torrents.aelitis.com:88/torrents/azautoseeder_0.1.1.jar.torrent'
])
torrent_ids = ws.proxy.get_session_state()
#avoid hammering, investigate current torrent-list and do not re-add.
#correct means : 3 torrent's in list (for now)
if len(torrent_ids) <> 3:
#delete all, nice use case for refactoring delete..
torrent_ids = ws.proxy.get_session_state()
for torrent in torrent_ids:
ws.proxy.remove_torrent([torrent], False, False)
torrent_ids = ws.proxy.get_session_state()
self.assertEqual(torrent_ids, [])
#add 3 using url.
for url in self.urls:
self.assert_303('/torrent/add','/index',{'url':url,'torrent':None})
#added?
self.torrent_ids = ws.proxy.get_session_state()
self.assertEqual(len(self.torrent_ids), 3)
else:
#test correctness of existing-list
#The setup makes 0.6 fail everything, added an else..
for url in self.urls:
if ws.ENV.startswith('0.5'):
self.assert_500('/torrent/add',{'url':url,'torrent':None})
else:
self.assert_303('/torrent/add','/index',{'url':url,'torrent':None})
def testPauseResume(self):
#pause all
self.assert_303('/pause_all','/index', post=1)
#pause worked?
pause_status = [get_status(id)["user_paused"] for id in ws.proxy.get_session_state()]
for paused in pause_status:
self.assertEqual(paused, True)
#resume all
self.assert_303('/resume_all','/index', post=1)
#resume worked?
pause_status = [get_status(id)["user_paused"] for id in ws.proxy.get_session_state()]
for paused in pause_status:
self.assertEqual(paused,False)
#pause again.
self.assert_303('/pause_all','/index', post=1)
torrent_id = self.first_torrent_id
#single resume.
self.assert_303('/torrent/start/%s' % torrent_id ,'/index', post=1)
self.assertEqual(get_status(torrent_id)["user_paused"] ,False)
#single pause
self.assert_303('/torrent/stop/%s' % torrent_id,'/index', post=1)
self.assertEqual(get_status(torrent_id)["user_paused"] , True)
def testQueue(self):
#find last:
torrent_id = [id for id in ws.proxy.get_session_state()
if (get_status(id)['queue_pos'] ==3 )][0]
#queue
torrent = get_status(torrent_id)
self.assertEqual(torrent['queue_pos'], 3)
#up:
self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1)
torrent = get_status(torrent_id)
self.assertEqual(torrent['queue_pos'], 2)
self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1)
torrent = get_status(torrent_id)
self.assertEqual(torrent['queue_pos'], 1)
self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1)
#upper limit
torrent = get_status(torrent_id)
self.assertEqual(torrent['queue_pos'], 1)
#down:
self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1)
torrent = get_status(torrent_id)
self.assertEqual(torrent['queue_pos'], 2)
self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1)
torrent = get_status(torrent_id)
self.assertEqual(torrent['queue_pos'], 3)
self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1)
#down limit
torrent = get_status(torrent_id)
self.assertEqual(torrent['queue_pos'], 3)
def testMeta(self):
#info available?
for torrent_id in ws.proxy.get_session_state():
self.assert_exists('/torrent/info/%s' % torrent_id)
self.assert_exists('/torrent/delete/%s' % torrent_id)
#no info:
self.assert_500('/torrent/info/99999999')
self.assert_500('/torrent/delete/99999999')
def testAddRemove(self):
#add a duplicate:
self.assert_500('/torrent/add', post={'url':self.urls[0],'torrent':None})
#add a 4th using url
#delete
#add torrrent-file
#./test01.torrent
def test_do_redirect(self):
self.assert_303('/home','/index')
#1
self.assert_exists('/index?sort=download_rate&order=down')
self.assert_303('/home','/index?sort=download_rate&order=down')
assert self.cookies['sort'] == 'download_rate'
assert self.cookies['order'] == 'down'
#2
self.assert_exists('/index?sort=progress&order=up')
self.assert_303('/home','/index?sort=progress&order=up')
assert self.cookies['sort'] == 'progress'
assert self.cookies['order'] == 'up'
#redir after pause-POST? in /index.
self.assert_exists('/index?sort=name&order=down')
torrent_id = self.first_torrent_id
self.assert_303('/torrent/stop/%s' % torrent_id,
'/index?sort=name&order=down', post=1)
#redir in details 1
self.assert_303('/torrent/stop/%s?redir=/torrent/info/%s' %(torrent_id,torrent_id)
,'/torrent/info/' + torrent_id, post = 1)
#redir in details 2
self.assert_303('/torrent/stop/%s' % torrent_id
,'/torrent/info/' + torrent_id ,
post={'redir': '/torrent/info/' + torrent_id})
def testRemote(self):
pass
def test_redir_after_login(self):
pass
def testReannounce(self):
torrent_id = self.first_torrent_id
self.assert_303(
'/torrent/reannounce/%(id)s?redir=/torrent/info/%(id)s'
% {'id':torrent_id}
,'/torrent/info/' + torrent_id, post = 1)
def testRecheck(self):
#add test before writing code..
#RELEASE-->disable
"""
torrent_id = self.first_torrent_id
self.assert_303(
'/torrent/recheck/%(id)s?redir=/torrent/info/%(id)s'
% {'id':torrent_id}
,'/torrent/info/' + torrent_id, post = 1)
"""
#
if False:
suiteFew = unittest.TestSuite()
suiteFew.addTest(TestSession("testRefresh"))
unittest.TextTestRunner(verbosity=2).run(suiteFew)
elif False:
suiteFew = unittest.TestSuite()
suiteFew.addTest(TestIntegration("testDoRedirect"))
unittest.TextTestRunner(verbosity=2).run(suiteFew)
else:
unittest.main()

View File

@ -1,224 +0,0 @@
#!/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.
"""
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
import random
import pickle
import sys
import base64
from webpy022 import template
random.seed()
webui_path = os.path.dirname(__file__)
ENV = 'UNKNOWN'
config_defaults = {
"port":8112,
"button_style":2,
"auto_refresh":False,
"auto_refresh_secs": 10,
"template":"advanced",
"pwd_salt":"2540626806573060601127357001536142078273646936492343724296134859793541603059837926595027859394922651189016967573954758097008242073480355104215558310954",
"pwd_md5":"\xea\x8d\x90\x98^\x9f\xa9\xe2\x19l\x7f\x1a\xca\x82u%",
"cache_templates":False,
"use_https":False
}
try:
_('translate something')
except:
import gettext
gettext.install('~/')
#log.error('no translations :(')
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(webui_path, 'templates/deluge/'),
cache=cache)
self.sub_template = template.render(
os.path.join(webui_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
from deluge.log import LOG as log
globals()['log'] = log
proxy.set_core_uri('http://localhost:58846') #How to configure this?
def add_torrent_filecontent(name , data_b64):
log.debug('monkeypatched add_torrent_filecontent:%s,len(data:%s))' %
(name , len(data_b64)))
name = name.replace('\\','/')
name = 'deluge06_' + str(random.random()) + '_' + name.split('/')[-1]
filename = os.path.join('/tmp', name)
log.debug('write: %s' % filename)
f = open(filename,"wb")
f.write(base64.b64decode(data_b64))
f.close()
proxy.add_torrent_file([filename])
proxy.add_torrent_filecontent = add_torrent_filecontent
log.debug('cfg-file %s' % config_file)
if not os.path.exists(config_file):
log.debug('create cfg file %s' % config_file)
#load&save defaults.
f = file(config_file,'wb')
pickle.dump(config_defaults,f)
f.close()
init_process()
globals()['proxy'] = proxy
globals()['ENV'] = '0.6'
def init_05():
import dbus
init_process()
bus = dbus.SessionBus()
proxy = bus.get_object("org.deluge_torrent.dbusplugin"
, "/org/deluge_torrent/DelugeDbusPlugin")
globals()['proxy'] = proxy
globals()['ENV'] = '0.5_process'
init_logger()
def init_gtk_05():
#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'] = subclassed_render(config.get('template'),
config.get('cache_templates'))
globals()['ENV'] = '0.5_gtk'
init_logger()
def init_logger():
#only for 0.5..
import logging
logging.basicConfig(level=logging.DEBUG,format="[%(levelname)-8s] %(module)s:%(lineno)d %(message)s")
globals()['log'] = logging
#hacks to determine environment, TODO: clean up.
if 'env=0.5' in sys.argv:
init_05()
elif 'env=0.6' in sys.argv:
init_06()
elif hasattr(deluge, 'ui'):
init_06()
elif not hasattr(deluge,'pref'):
init_05()
#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',
'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','queue_pos','user_paused']
STATE_MESSAGES = (_("Queued"),
_("Checking"),
_("Connecting"),
_("Downloading Metadata"),
_("Downloading"),
_("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')
]
#try:
# SESSIONS = pickle.load(open(session_file))
#except:
SESSIONS = []

View File

@ -1,395 +0,0 @@
#!/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 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 urlparse import urlparse
from deluge import common
from webserver_common import REVNO, VERSION, log
import webserver_common as ws
from debugerror import deluge_debugerror
#init:
web.webapi.internalerror = deluge_debugerror
#/init
#methods:
def setcookie(key, val):
"""add 30 days expires header for persistent cookies"""
return w_setcookie(key, val , expires=2592000)
#really simple sessions, to bad i had to implement them myself.
def start_session():
log.debug('start session')
session_id = str(random.random())
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.
#f = open(ws.session_file,'wb')
#pickle.dump(ws.SESSIONS, f)
#f.close()
setcookie("session_id", session_id)
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.
#f = open(ws.session_file,'wb')
#pickle.dump(ws.SESSIONS, f)
#f.close()
setcookie("session_id","")
def do_redirect():
"""for redirects after a POST"""
vars = web.input(redir = None)
ck = cookies()
url_vars = {}
if vars.redir:
seeother(vars.redir)
return
#todo:cleanup
if ("order" in ck and "sort" in ck):
url_vars.update({'sort':ck['sort'] ,'order':ck['order'] })
if ("filter" in ck) and ck['filter']:
url_vars['filter'] = ck['filter']
if ("category" in ck) and ck['category']:
url_vars['category'] = ck['category']
seeother(url("/index", **url_vars))
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):
key = str(key).strip()
ck = cookies()
return 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
deco.__name__ = func.__name__
return deco
def check_session(func):
"""
a decorator
return func if session is valid, else redirect to login page.
"""
def deco(self, name = None):
log.debug('%s.%s(name=%s)' % (self.__class__.__name__,func.__name__,name))
vars = web.input(redir_after_login = None)
ck = cookies()
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()))
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 = None):
if getcookie('auto_refresh') == '1':
web.header("Refresh", "%i ; url=%s" %
(int(getcookie('auto_refresh_secs',10)),self_url()))
return func(self, name)
deco.__name__ = func.__name__
return deco
def remote(func):
"decorator for remote api's"
def deco(self, name = None):
try:
print func(self, name)
except Exception, e:
print 'error:' + e.message
print '-'*20
print traceback.format_exc()
deco.__name__ = func.__name__
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'),
'num_connections':ws.proxy.get_num_connections(),
'max_num_connections':ws.proxy.get_config_value('max_connections_global')
})
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
url = urlparse(status.tracker)
if hasattr(url,'hostname'):
status.category = url.hostname or 'unknown'
else:
status.category = 'No-tracker'
#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.user_paused:
status.action = "start"
else:
status.action = "stop"
if status.user_paused:
status.message = _("Paused %s%%") % status.progress
elif status.paused:
status.message = _("Queued %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) + ")"),
})
#no non-unicode string may enter the templates.
for k, v in status.iteritems():
if (not isinstance(v, unicode)) and isinstance(v, str):
try:
status[k] = unicode(v)
except:
raise Exception('Non Unicode for key:%s' % (k, ))
return status
def get_categories(torrent_list):
trackers = [(torrent['category'] or 'unknown') for torrent in torrent_list]
categories = {}
for tracker in trackers:
categories[tracker] = categories.get(tracker,0) + 1
return categories
def filter_torrent_state(torrent_list,filter_name):
filters = {
'downloading': lambda t: (not t.paused and not t.is_seed)
,'queued':lambda t: (t.paused and not t.user_paused)
,'paused':lambda t: (t.user_paused)
,'seeding':lambda t:(t.is_seed and not t.paused )
}
filter_func = filters[filter_name]
return [t for t in torrent_list if filter_func(t)]
#/utils
#template-defs:
def category_tabs(torrent_list):
categories = get_categories(torrent_list)
filter_tabs = [Storage(title='All (%s)' % len(torrent_list),
filter=None, category=None)]
#static filters
for title, filter_name in [
(_('Downloading'),'downloading') ,
(_('Queued'),'queued') ,
(_('Paused'),'paused') ,
(_('Seeding'),'seeding')
]:
title += ' (%s)' % (
len(filter_torrent_state(torrent_list, filter_name)), )
filter_tabs.append(Storage(title=title, filter=filter_name))
categories = [x for x in get_categories(torrent_list).iteritems()]
categories.sort()
#trackers:
category_tabs = []
category_tabs.append(
Storage(title=_('Trackers'),category=None))
for title,count in categories:
category = title
title += ' (%s)' % (count, )
category_tabs.append(Storage(title=title, category=category))
return ws.render.part_categories(filter_tabs, category_tabs)
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 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,
'category_tabs':category_tabs,
'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
def create_webserver(urls, methods):
from webpy022.request import webpyfunc
from webpy022 import webapi
from gtk_cherrypy_wsgiserver import CherryPyWSGIServer
import os
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")
if ws.config.get('use_https'):
server.ssl_certificate = os.path.join(ws.webui_path,'ssl/deluge.pem')
server.ssl_private_key = os.path.join(ws.webui_path,'ssl/deluge.key')
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'
,'setcookie','create_webserver','end_session',
'get_torrent_status', 'check_pwd','static_handler','get_categories'
,'template','filter_torrent_state','log']