|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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>▶</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)
|
|
@ -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()
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
import deluge_webserver
|
||||
deluge_webserver.run()
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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 '*.*~'
|
|
@ -1 +0,0 @@
|
|||
curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add
|
|
@ -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 )
|
||||
|
|
@ -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')
|
|
@ -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-----
|
|
@ -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-----
|
Before Width: | Height: | Size: 588 B |
Before Width: | Height: | Size: 662 B |
Before Width: | Height: | Size: 588 B |
Before Width: | Height: | Size: 612 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 498 B |
Before Width: | Height: | Size: 627 B |
Before Width: | Height: | Size: 323 B |
Before Width: | Height: | Size: 247 B |
Before Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 660 B |
Before Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 611 B |
Before Width: | Height: | Size: 820 B |
Before Width: | Height: | Size: 683 B |
Before Width: | Height: | Size: 652 B |
Before Width: | Height: | Size: 660 B |
Before Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 799 B |
Before Width: | Height: | Size: 592 B |
Before Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 912 B |
|
@ -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 */
|
|
@ -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>
|
|
@ -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:
|
||||
|
||||
</td>
|
||||
<td>
|
||||
$if (torrent.upload_rate):
|
||||
$fspeed(torrent.upload_rate)
|
||||
$else:
|
||||
|
||||
</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()
|
||||
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
|
@ -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')
|
||||
$:render.part_button('GET', '/refresh/set', _('Set'), 'tango/preferences-system.png')
|
||||
$:render.part_button('POST', '/refresh/off', _('Disable'), 'tango/process-stop.png')
|
||||
$else:
|
||||
$_('Off')
|
||||
$:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png')
|
||||
$#end
|
||||
</div>
|
||||
|
||||
<div class="panel" id='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>
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
-->
|
|
@ -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 */
|
||||
|
||||
|
|
@ -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 ""
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
|
@ -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()
|
|
@ -1,5 +0,0 @@
|
|||
-first layout taken from deluge website
|
||||
improved by:
|
||||
-mvoncken
|
||||
-somedude
|
||||
|
|
@ -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()
|
|
@ -1,6 +0,0 @@
|
|||
$def with (error_msg)
|
||||
$:render.header(_('Error'))
|
||||
<pre class="error">
|
||||
$error_msg
|
||||
</pre>
|
||||
$:render.footer()
|
|
@ -1,6 +0,0 @@
|
|||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -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>
|
|
@ -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()
|
||||
|
|
@ -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()
|
|
@ -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>
|
|
@ -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')
|
||||
$:render.part_button('GET', '/refresh/set', _('Set'), 'tango/preferences-system.png')
|
||||
$:render.part_button('POST', '/refresh/off', _('Disable'), 'tango/process-stop.png')
|
||||
$else:
|
||||
$_('Off')
|
||||
$:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png')
|
||||
$#end
|
||||
</div>
|
||||
|
||||
<div class="panel" id='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>
|
||||
|
||||
|
|
@ -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()
|
|
@ -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>
|
||||
|
||||
|
|
@ -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"> </td>
|
||||
<td class="info_value"> </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"> </td>
|
||||
<td class="info_value"> </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>
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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 = []
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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']
|