diff --git a/plugins/WebUi/LICENSE b/plugins/WebUi/LICENSE deleted file mode 100644 index 4856598ea..000000000 --- a/plugins/WebUi/LICENSE +++ /dev/null @@ -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. - - - Copyright (C) - - 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. - - , 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. diff --git a/plugins/WebUi/TODO b/plugins/WebUi/TODO deleted file mode 100644 index e0944ed43..000000000 --- a/plugins/WebUi/TODO +++ /dev/null @@ -1,20 +0,0 @@ -0.5.7 -SSL -torrent/add http-post for private sites -rename reannounce->update-tracker. -queued displays as seeding/downloading - - -0.5.7 advanced layout -fonts -fix auto-refresh-layout -buttons -hide 0.0 kbps like in gtk-ui -update-tracker. - -0.6 -prepare for cat: - filters on status (prepare for cat) - filters on tracker -categories -greasemonkey : private sites. \ No newline at end of file diff --git a/plugins/WebUi/__init__.py b/plugins/WebUi/__init__.py deleted file mode 100644 index 69b1f999d..000000000 --- a/plugins/WebUi/__init__.py +++ /dev/null @@ -1,281 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Copyright (C) Martijn Voncken 2007 -# -# 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: - print 'error importing dbus-interface!' - 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 = 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.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(str(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 diff --git a/plugins/WebUi/dbus_interface.py b/plugins/WebUi/dbus_interface.py deleted file mode 100644 index 4743b77a0..000000000 --- a/plugins/WebUi/dbus_interface.py +++ /dev/null @@ -1,283 +0,0 @@ -# -*- coding: utf-8 -*- -# Dbus Ipc for experimental web interface -# -# dbus_interface.py -# -# Copyright (C) Martijn Voncken 2007 -# 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 lib.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 diff --git a/plugins/WebUi/debugerror.py b/plugins/WebUi/debugerror.py deleted file mode 100644 index 259dcac72..000000000 --- a/plugins/WebUi/debugerror.py +++ /dev/null @@ -1,373 +0,0 @@ -""" -pretty debug errors -(part of web.py) - -adapted from Django -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 lib.webpy022.net import websafe -from lib.webpy022.template import Template -import lib.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) - - - - - - $exception_type at $ctx.path - - - - - -
-

$exception_type : $exception_value

-
-
-

- - Oops, Deluge Broke :-( , You might have found a bug, or you did something really stupid ;-). -
If the error persists :
- Read the Faq.
- Try downloading the latest version at - deluge-torrent.org -
Visit the forum - or the buglist for more info. -

-
- - -
-Paste the contents of this text-box when you are asked for a traceback:
- - -
-Use a pastebin on IRC!
- - -
- - -
-

Traceback (innermost first)

-
    -$for frame in frames: -
  • - $frame.filename in $frame.function - $if frame.context_line: -
    - $if frame.pre_context: -
      - $for line in frame.pre_context: -
    1. $line
    2. -
    -
    1. $frame.context_line ...
    - $if frame.post_context: -
      - $for line in frame.post_context: -
    1. $line
    2. -
    -
    - - $if frame.vars: -
    - Local vars - $# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame)) -
    - $:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id))) -
  • -
-
- -
-$if ctx.output or ctx.headers: -

Response so far

-

HEADERS

-

- $for kv in ctx.headers: - $kv[0]: $kv[1]
- $else: - [no headers] -

- -

BODY

-

- $ctx.output -

- -

Request information

- -

INPUT

-$:dicttable(web.input()) - - -$:dicttable(web.cookies()) - -

META

-$ 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)) - -

ENVIRONMENT

-$:dicttable(ctx.env) -
- - - -""" - -dicttable_t = r"""$def with (d, kls='req', id=None) -$if d: - - - $ temp = d.items() - $temp.sort() - $for kv in temp: - - -
VariableValue
$kv[0]
$prettify(kv[1])
-$else: -

No data.

-""" - -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) diff --git a/plugins/WebUi/deluge_webserver.py b/plugins/WebUi/deluge_webserver.py deleted file mode 100644 index e8fd839b9..000000000 --- a/plugins/WebUi/deluge_webserver.py +++ /dev/null @@ -1,382 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# deluge_webserver.py -# -# Copyright (C) Martijn Voncken 2007 -# -# 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 lib.webpy022 as web -from lib.webpy022.http import seeother, url - -import base64 -from operator import attrgetter -import os - -from json_api import json_api - -#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", - "/json/(.*)","json_api", - #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() diff --git a/plugins/WebUi/json_api.py b/plugins/WebUi/json_api.py deleted file mode 100644 index 720aec91b..000000000 --- a/plugins/WebUi/json_api.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# webserver_framework.py -# -# Copyright (C) Martijn Voncken 2007 -# -# 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. -""" -json api. -only used for XUL and/or external scripts -it would be possible not to incluse the python-json dependency. -""" - -from new import instancemethod -from inspect import getargspec -from webserver_framework import remote,ws,get_torrent_status,log,get_category_choosers, get_stats,log,filter_torrent_state,fsize,fspeed -from operator import attrgetter -import lib.webpy022 as web -proxy = ws.proxy - -def to_json(obj): - from lib.pythonize import pythonize - obj = pythonize(obj) - try: - import json - return json.write(obj) - except ImportError: - raise ImportError("""Install python-json using your package-manager - http://sourceforge.net/projects/json-py/""") - -class json_api: - """ - eperimental json api - generic proxy for all methods onm self. - """ - illegal_methods = ['shutdown', 'socket', 'xmlrpclib','pickle','os', - 'is_localhost','CoreProxy','connect_on_new_core', 'connect_on_no_core', - 'connected','deluge','GET','POST'] - def __init__(self): - self._add_proxy_methods() - - #extra exposed: - get_torrent_status = get_torrent_status - - @remote - def POST(self,name): - import json - if name.startswith('_'): - raise AttributeError('_ methods are illegal.') - if name in self.illegal_methods: - raise AttributeError('Illegal method.') - if not(hasattr(self,name)): - raise AttributeError('No such method') - - method = getattr(self,name) - vars = web.input(kwargs= None) - log.debug('vars=%s' % vars) - if vars.kwargs: - kwargs = json.read(vars.kwargs) - else: - kwargs = {} - - result = method(**kwargs) - - return "(" + to_json(result) + ")" - - - def list_methods(self): - """ - list all json methods - returns a dict of {methodname:{args:[list of kwargs],doc:'string'},..} - """ - methods = [getattr(self,m) for m in dir(self) - if not m.startswith('_') - and (not m in self.illegal_methods) - and callable(getattr(self,m)) - ] - - return dict([(f.__name__, - {'args':getargspec(f)[0],'doc':(f.__doc__ or '').strip()}) - for f in methods]) - - def _add_proxy_methods(self): - methods = [getattr(proxy,m) for m in dir(proxy) - if not m.startswith('_') - and (not m in self.illegal_methods) - and callable(getattr(proxy,m)) - ] - for m in methods: - setattr(self,m.__name__,m) - - #extra's: - def list_torrents(self): - return [get_torrent_status(torrent_id) - for torrent_id in ws.proxy.get_session_state()] - - def simplify_torrent_status(self, torrent): - """smaller subset and preformatted data for the treelist""" - data = { - "id":torrent.id, - "message":torrent.message, - "name":torrent.name, - "total_size":fsize(torrent.total_size), - "progress":torrent.progress, - "category":torrent.category, - "seeds":"", - "peers":"", - "download_rate":"", - "upload_rate":"", - "eta":"", - "distributed_copies":"", - "ratio":"", - "calc_state_str":torrent.calc_state_str, - "queue_pos":torrent.queue_pos - } - if torrent.total_seeds > 0: - data['seeds'] = "%s (%s)" % (torrent.num_seeds, torrent.total_seeds) - if torrent.total_peers > 0: - data['peers'] = "%s (%s)" % (torrent.num_peers, torrent.total_peers) - if torrent.download_rate > 0: - data['download_rate'] = fspeed(torrent.download_rate) - if torrent.upload_rate > 0: - data['upload_rate'] = fspeed(torrent.upload_rate) - if torrent.eta > 0: - data['eta'] = ("%.3f" % torrent.eta) - if torrent.distributed_copies > 0: - data['distributed_copies'] = "%.3f" % torrent.distributed_copies - if torrent.ratio > 0: - data['ratio'] = "%.3f" % torrent.ratio - return data - - def update_ui(self, filter=None, category=None ,sort='name' ,order='down'): - """ - Combines the most important ui calls into 1 composite call. - xmlhttp requests are expensive,max 2 running at the same time. - and performance over the internet is mostly related to the number - of requests (low ping) - returns : - {torrent_list:[{},..],'categories':[],'filters':'','stats':{}} - """ - torrent_list = self.list_torrents(); - filter_tabs, category_tabs = get_category_choosers(torrent_list) - - - #filter-state - if filter: - torrent_list = filter_torrent_state(torrent_list, filter) - - #filter-cat - if category: - torrent_list = [t for t in torrent_list if t.category == category] - - #sorting - if sort: - torrent_list.sort(key=attrgetter(sort)) - if order == 'up': - torrent_list = reversed(torrent_list) - - torrent_list = [self.simplify_torrent_status(t) for t in torrent_list] - - return { - 'torrent_list':torrent_list, - 'categories':category_tabs, - 'filters':filter_tabs, - 'stats':get_stats() - } - - - -if __name__ == '__main__': - from pprint import pprint - #proxy.set_core_uri('http://localhost:58846') #How to configure this? - j = json_api() - if True: - print 'list-methods:' - methods = j.list_methods() - names = methods.keys() - names.sort() - for name in names: - m = methods[name] - print "%s(%s)\n %s\n" % (name , m['args'] , m['doc']) - - #j.GET('list_torrents') - j.POST('list_torrents') - diff --git a/plugins/WebUi/lib/__init__.py b/plugins/WebUi/lib/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/WebUi/lib/gtk_cherrypy_wsgiserver.py b/plugins/WebUi/lib/gtk_cherrypy_wsgiserver.py deleted file mode 100644 index ce55b3fa7..000000000 --- a/plugins/WebUi/lib/gtk_cherrypy_wsgiserver.py +++ /dev/null @@ -1,1077 +0,0 @@ -""" -mvoncken: -Modified this to integrate into the gtk main-loop. -*split start() into start_common(),start,start_gtk(),start() -*add stop_gtk() -*add CherryPy license in comment ----- -Copyright (c) 2004-2007, CherryPy Team (team@cherrypy.org) -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of the CherryPy Team nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -A high-speed, production ready, thread pooled, generic WSGI server. - -Simplest example on how to use this module directly -(without using CherryPy's application machinery): - - from cherrypy import wsgiserver - - def my_crazy_app(environ, start_response): - status = '200 OK' - response_headers = [('Content-type','text/plain')] - start_response(status, response_headers) - return ['Hello world!\n'] - - # Here we set our application to the script_name '/' - wsgi_apps = [('/', my_crazy_app)] - - server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps, - server_name='localhost') - - # Want SSL support? Just set these attributes - # server.ssl_certificate = - # server.ssl_private_key = - - if __name__ == '__main__': - try: - server.start() - except KeyboardInterrupt: - server.stop() - -This won't call the CherryPy engine (application side) at all, only the -WSGI server, which is independant from the rest of CherryPy. Don't -let the name "CherryPyWSGIServer" throw you; the name merely reflects -its origin, not it's coupling. - -The CherryPy WSGI server can serve as many WSGI applications -as you want in one instance: - - wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)] - -""" -import base64 -import Queue -import os -import re -quoted_slash = re.compile("(?i)%2F") -import rfc822 -import socket -try: - import cStringIO as StringIO -except ImportError: - import StringIO -import sys -import threading -import time -import traceback -from urllib import unquote -from urlparse import urlparse -try: - import gobject -except ImportError: - pass - -try: - from OpenSSL import SSL - from OpenSSL import crypto -except ImportError: - SSL = None - -import errno -socket_errors_to_ignore = [] -# Not all of these names will be defined for every platform. -for _ in ("EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET", - "EHOSTDOWN", "EHOSTUNREACH", - "WSAECONNABORTED", "WSAECONNREFUSED", "WSAECONNRESET", - "WSAENETRESET", "WSAETIMEDOUT"): - if _ in dir(errno): - socket_errors_to_ignore.append(getattr(errno, _)) -# de-dupe the list -socket_errors_to_ignore = dict.fromkeys(socket_errors_to_ignore).keys() -socket_errors_to_ignore.append("timed out") - -comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', - 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL', - 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT', - 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE', - 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING', - 'WWW-AUTHENTICATE'] - -class HTTPRequest(object): - """An HTTP Request (and response). - - A single HTTP connection may consist of multiple request/response pairs. - - connection: the HTTP Connection object which spawned this request. - rfile: the 'read' fileobject from the connection's socket - ready: when True, the request has been parsed and is ready to begin - generating the response. When False, signals the calling Connection - that the response should not be generated and the connection should - close. - close_connection: signals the calling Connection that the request - should close. This does not imply an error! The client and/or - server may each request that the connection be closed. - chunked_write: if True, output will be encoded with the "chunked" - transfer-coding. This value is set automatically inside - send_headers. - """ - - def __init__(self, connection): - self.connection = connection - self.rfile = self.connection.rfile - self.sendall = self.connection.sendall - self.environ = connection.environ.copy() - - self.ready = False - self.started_response = False - self.status = "" - self.outheaders = [] - self.sent_headers = False - self.close_connection = False - self.chunked_write = False - - def parse_request(self): - """Parse the next HTTP request start-line and message-headers.""" - # HTTP/1.1 connections are persistent by default. If a client - # requests a page, then idles (leaves the connection open), - # then rfile.readline() will raise socket.error("timed out"). - # Note that it does this based on the value given to settimeout(), - # and doesn't need the client to request or acknowledge the close - # (although your TCP stack might suffer for it: cf Apache's history - # with FIN_WAIT_2). - request_line = self.rfile.readline() - if not request_line: - # Force self.ready = False so the connection will close. - self.ready = False - return - - if request_line == "\r\n": - # RFC 2616 sec 4.1: "...if the server is reading the protocol - # stream at the beginning of a message and receives a CRLF - # first, it should ignore the CRLF." - # But only ignore one leading line! else we enable a DoS. - request_line = self.rfile.readline() - if not request_line: - self.ready = False - return - - server = self.connection.server - environ = self.environ - environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version - - method, path, req_protocol = request_line.strip().split(" ", 2) - environ["REQUEST_METHOD"] = method - - # path may be an abs_path (including "http://host.domain.tld"); - scheme, location, path, params, qs, frag = urlparse(path) - - if frag: - self.simple_response("400 Bad Request", - "Illegal #fragment in Request-URI.") - return - - if scheme: - environ["wsgi.url_scheme"] = scheme - if params: - path = path + ";" + params - - # Unquote the path+params (e.g. "/this%20path" -> "this path"). - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 - # - # But note that "...a URI must be separated into its components - # before the escaped characters within those components can be - # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 - atoms = [unquote(x) for x in quoted_slash.split(path)] - path = "%2F".join(atoms) - - if path == "*": - # This means, of course, that the last wsgi_app (shortest path) - # will always handle a URI of "*". - environ["SCRIPT_NAME"] = "" - environ["PATH_INFO"] = "*" - self.wsgi_app = server.mount_points[-1][1] - else: - for mount_point, wsgi_app in server.mount_points: - # The mount_points list should be sorted by length, descending. - if path.startswith(mount_point + "/") or path == mount_point: - environ["SCRIPT_NAME"] = mount_point - environ["PATH_INFO"] = path[len(mount_point):] - self.wsgi_app = wsgi_app - break - else: - self.simple_response("404 Not Found") - return - - # Note that, like wsgiref and most other WSGI servers, - # we unquote the path but not the query string. - environ["QUERY_STRING"] = qs - - # Compare request and server HTTP protocol versions, in case our - # server does not support the requested protocol. Limit our output - # to min(req, server). We want the following output: - # request server actual written supported response - # protocol protocol response protocol feature set - # a 1.0 1.0 1.0 1.0 - # b 1.0 1.1 1.1 1.0 - # c 1.1 1.0 1.0 1.0 - # d 1.1 1.1 1.1 1.1 - # Notice that, in (b), the response will be "HTTP/1.1" even though - # the client only understands 1.0. RFC 2616 10.5.6 says we should - # only return 505 if the _major_ version is different. - rp = int(req_protocol[5]), int(req_protocol[7]) - sp = int(server.protocol[5]), int(server.protocol[7]) - if sp[0] != rp[0]: - self.simple_response("505 HTTP Version Not Supported") - return - # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. - environ["SERVER_PROTOCOL"] = req_protocol - # set a non-standard environ entry so the WSGI app can know what - # the *real* server protocol is (and what features to support). - # See http://www.faqs.org/rfcs/rfc2145.html. - environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol - self.response_protocol = "HTTP/%s.%s" % min(rp, sp) - - # If the Request-URI was an absoluteURI, use its location atom. - if location: - environ["SERVER_NAME"] = location - - # then all the http headers - try: - self.read_headers() - except ValueError, ex: - self.simple_response("400 Bad Request", repr(ex.args)) - return - - creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1) - environ["AUTH_TYPE"] = creds[0] - if creds[0].lower() == 'basic': - user, pw = base64.decodestring(creds[1]).split(":", 1) - environ["REMOTE_USER"] = user - - # Persistent connection support - if self.response_protocol == "HTTP/1.1": - if environ.get("HTTP_CONNECTION", "") == "close": - self.close_connection = True - else: - # HTTP/1.0 - if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": - self.close_connection = True - - # Transfer-Encoding support - te = None - if self.response_protocol == "HTTP/1.1": - te = environ.get("HTTP_TRANSFER_ENCODING") - if te: - te = [x.strip().lower() for x in te.split(",") if x.strip()] - - read_chunked = False - - if te: - for enc in te: - if enc == "chunked": - read_chunked = True - else: - # Note that, even if we see "chunked", we must reject - # if there is an extension we don't recognize. - self.simple_response("501 Unimplemented") - self.close_connection = True - return - - if read_chunked: - if not self.decode_chunked(): - return - - # From PEP 333: - # "Servers and gateways that implement HTTP 1.1 must provide - # transparent support for HTTP 1.1's "expect/continue" mechanism. - # This may be done in any of several ways: - # 1. Respond to requests containing an Expect: 100-continue request - # with an immediate "100 Continue" response, and proceed normally. - # 2. Proceed with the request normally, but provide the application - # with a wsgi.input stream that will send the "100 Continue" - # response if/when the application first attempts to read from - # the input stream. The read request must then remain blocked - # until the client responds. - # 3. Wait until the client decides that the server does not support - # expect/continue, and sends the request body on its own. - # (This is suboptimal, and is not recommended.) - # - # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, - # but it seems like it would be a big slowdown for such a rare case. - if environ.get("HTTP_EXPECT", "") == "100-continue": - self.simple_response(100) - - self.ready = True - - def read_headers(self): - """Read header lines from the incoming stream.""" - environ = self.environ - - while True: - line = self.rfile.readline() - if not line: - # No more data--illegal end of headers - raise ValueError("Illegal end of headers.") - - if line == '\r\n': - # Normal end of headers - break - - if line[0] in ' \t': - # It's a continuation line. - v = line.strip() - else: - k, v = line.split(":", 1) - k, v = k.strip().upper(), v.strip() - envname = "HTTP_" + k.replace("-", "_") - - if k in comma_separated_headers: - existing = environ.get(envname) - if existing: - v = ", ".join((existing, v)) - environ[envname] = v - - ct = environ.pop("HTTP_CONTENT_TYPE", None) - if ct: - environ["CONTENT_TYPE"] = ct - cl = environ.pop("HTTP_CONTENT_LENGTH", None) - if cl: - environ["CONTENT_LENGTH"] = cl - - def decode_chunked(self): - """Decode the 'chunked' transfer coding.""" - cl = 0 - data = StringIO.StringIO() - while True: - line = self.rfile.readline().strip().split(";", 1) - chunk_size = int(line.pop(0), 16) - if chunk_size <= 0: - break -## if line: chunk_extension = line[0] - cl += chunk_size - data.write(self.rfile.read(chunk_size)) - crlf = self.rfile.read(2) - if crlf != "\r\n": - self.simple_response("400 Bad Request", - "Bad chunked transfer coding " - "(expected '\\r\\n', got %r)" % crlf) - return - - # Grab any trailer headers - self.read_headers() - - data.seek(0) - self.environ["wsgi.input"] = data - self.environ["CONTENT_LENGTH"] = str(cl) or "" - return True - - def respond(self): - """Call the appropriate WSGI app and write its iterable output.""" - response = self.wsgi_app(self.environ, self.start_response) - try: - for chunk in response: - # "The start_response callable must not actually transmit - # the response headers. Instead, it must store them for the - # server or gateway to transmit only after the first - # iteration of the application return value that yields - # a NON-EMPTY string, or upon the application's first - # invocation of the write() callable." (PEP 333) - if chunk: - self.write(chunk) - finally: - if hasattr(response, "close"): - response.close() - if (self.ready and not self.sent_headers - and not self.connection.server.interrupt): - self.sent_headers = True - self.send_headers() - if self.chunked_write: - self.sendall("0\r\n\r\n") - - def simple_response(self, status, msg=""): - """Write a simple response back to the client.""" - status = str(status) - buf = ["%s %s\r\n" % (self.connection.server.protocol, status), - "Content-Length: %s\r\n" % len(msg)] - - if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': - # Request Entity Too Large - self.close_connection = True - buf.append("Connection: close\r\n") - - buf.append("\r\n") - if msg: - buf.append(msg) - self.sendall("".join(buf)) - - def start_response(self, status, headers, exc_info = None): - """WSGI callable to begin the HTTP response.""" - if self.started_response: - if not exc_info: - raise AssertionError("WSGI start_response called a second " - "time with no exc_info.") - else: - try: - raise exc_info[0], exc_info[1], exc_info[2] - finally: - exc_info = None - self.started_response = True - self.status = status - self.outheaders.extend(headers) - return self.write - - def write(self, chunk): - """WSGI callable to write unbuffered data to the client. - - This method is also used internally by start_response (to write - data from the iterable returned by the WSGI application). - """ - if not self.started_response: - raise AssertionError("WSGI write called before start_response.") - - if not self.sent_headers: - self.sent_headers = True - self.send_headers() - - if self.chunked_write and chunk: - buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"] - self.sendall("".join(buf)) - else: - self.sendall(chunk) - - def send_headers(self): - """Assert, process, and send the HTTP response message-headers.""" - hkeys = [key.lower() for key, value in self.outheaders] - status = int(self.status[:3]) - - if status == 413: - # Request Entity Too Large. Close conn to avoid garbage. - self.close_connection = True - elif "content-length" not in hkeys: - # "All 1xx (informational), 204 (no content), - # and 304 (not modified) responses MUST NOT - # include a message-body." So no point chunking. - if status < 200 or status in (204, 205, 304): - pass - else: - if self.response_protocol == 'HTTP/1.1': - # Use the chunked transfer-coding - self.chunked_write = True - self.outheaders.append(("Transfer-Encoding", "chunked")) - else: - # Closing the conn is the only way to determine len. - self.close_connection = True - - if "connection" not in hkeys: - if self.response_protocol == 'HTTP/1.1': - if self.close_connection: - self.outheaders.append(("Connection", "close")) - else: - if not self.close_connection: - self.outheaders.append(("Connection", "Keep-Alive")) - - if "date" not in hkeys: - self.outheaders.append(("Date", rfc822.formatdate())) - - server = self.connection.server - - if "server" not in hkeys: - self.outheaders.append(("Server", server.version)) - - buf = [server.protocol, " ", self.status, "\r\n"] - try: - buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] - except TypeError: - if not isinstance(k, str): - raise TypeError("WSGI response header key %r is not a string.") - if not isinstance(v, str): - raise TypeError("WSGI response header value %r is not a string.") - else: - raise - buf.append("\r\n") - self.sendall("".join(buf)) - - -class NoSSLError(Exception): - """Exception raised when a client speaks HTTP to an HTTPS socket.""" - pass - - -def _ssl_wrap_method(method, is_reader=False): - """Wrap the given method with SSL error-trapping. - - is_reader: if False (the default), EOF errors will be raised. - If True, EOF errors will return "" (to emulate normal sockets). - """ - def ssl_method_wrapper(self, *args, **kwargs): -## print (id(self), method, args, kwargs) - start = time.time() - while True: - try: - return method(self, *args, **kwargs) - except (SSL.WantReadError, SSL.WantWriteError): - # Sleep and try again. This is dangerous, because it means - # the rest of the stack has no way of differentiating - # between a "new handshake" error and "client dropped". - # Note this isn't an endless loop: there's a timeout below. - time.sleep(self.ssl_retry) - except SSL.SysCallError, e: - if is_reader and e.args == (-1, 'Unexpected EOF'): - return "" - - errno = e.args[0] - if is_reader and errno in socket_errors_to_ignore: - return "" - raise socket.error(errno) - except SSL.Error, e: - if is_reader and e.args == (-1, 'Unexpected EOF'): - return "" - - thirdarg = None - try: - thirdarg = e.args[0][0][2] - except IndexError: - pass - - if is_reader and thirdarg == 'ssl handshake failure': - return "" - if thirdarg == 'http request': - # The client is talking HTTP to an HTTPS server. - raise NoSSLError() - raise - if time.time() - start > self.ssl_timeout: - raise socket.timeout("timed out") - return ssl_method_wrapper - -class SSL_fileobject(socket._fileobject): - """Faux file object attached to a socket object.""" - - ssl_timeout = 3 - ssl_retry = .01 - - close = _ssl_wrap_method(socket._fileobject.close) - flush = _ssl_wrap_method(socket._fileobject.flush) - write = _ssl_wrap_method(socket._fileobject.write) - writelines = _ssl_wrap_method(socket._fileobject.writelines) - read = _ssl_wrap_method(socket._fileobject.read, is_reader=True) - readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True) - readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True) - - -class HTTPConnection(object): - """An HTTP connection (active socket). - - socket: the raw socket object (usually TCP) for this connection. - addr: the "bind address" for the remote end of the socket. - For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT). - For UNIX domain sockets, this will be a string. - server: the HTTP Server for this Connection. Usually, the server - object possesses a passive (server) socket which spawns multiple, - active (client) sockets, one for each connection. - - environ: a WSGI environ template. This will be copied for each request. - rfile: a fileobject for reading from the socket. - sendall: a function for writing (+ flush) to the socket. - """ - - rbufsize = -1 - RequestHandlerClass = HTTPRequest - environ = {"wsgi.version": (1, 0), - "wsgi.url_scheme": "http", - "wsgi.multithread": True, - "wsgi.multiprocess": False, - "wsgi.run_once": False, - "wsgi.errors": sys.stderr, - } - - def __init__(self, sock, addr, server): - self.socket = sock - self.addr = addr - self.server = server - - # Copy the class environ into self. - self.environ = self.environ.copy() - - if SSL and isinstance(sock, SSL.ConnectionType): - timeout = sock.gettimeout() - self.rfile = SSL_fileobject(sock, "r", self.rbufsize) - self.rfile.ssl_timeout = timeout - self.sendall = _ssl_wrap_method(sock.sendall) - self.environ["wsgi.url_scheme"] = "https" - self.environ["HTTPS"] = "on" - sslenv = getattr(server, "ssl_environ", None) - if sslenv: - self.environ.update(sslenv) - else: - self.rfile = sock.makefile("rb", self.rbufsize) - self.sendall = sock.sendall - - self.environ.update({"wsgi.input": self.rfile, - "SERVER_NAME": self.server.server_name, - }) - - if isinstance(self.server.bind_addr, basestring): - # AF_UNIX. This isn't really allowed by WSGI, which doesn't - # address unix domain sockets. But it's better than nothing. - self.environ["SERVER_PORT"] = "" - else: - self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) - # optional values - # Until we do DNS lookups, omit REMOTE_HOST - self.environ["REMOTE_ADDR"] = self.addr[0] - self.environ["REMOTE_PORT"] = str(self.addr[1]) - - def communicate(self): - """Read each request and respond appropriately.""" - try: - while True: - # (re)set req to None so that if something goes wrong in - # the RequestHandlerClass constructor, the error doesn't - # get written to the previous request. - req = None - req = self.RequestHandlerClass(self) - # This order of operations should guarantee correct pipelining. - req.parse_request() - if not req.ready: - return - req.respond() - if req.close_connection: - return - except socket.error, e: - errno = e.args[0] - if errno not in socket_errors_to_ignore: - if req: - req.simple_response("500 Internal Server Error", - format_exc()) - return - except (KeyboardInterrupt, SystemExit): - raise - except NoSSLError: - # Unwrap our sendall - req.sendall = self.socket._sock.sendall - req.simple_response("400 Bad Request", - "The client sent a plain HTTP request, but " - "this server only speaks HTTPS on this port.") - except: - if req: - req.simple_response("500 Internal Server Error", format_exc()) - - def close(self): - """Close the socket underlying this connection.""" - self.rfile.close() - self.socket.close() - - -def format_exc(limit=None): - """Like print_exc() but return a string. Backport for Python 2.3.""" - try: - etype, value, tb = sys.exc_info() - return ''.join(traceback.format_exception(etype, value, tb, limit)) - finally: - etype = value = tb = None - - -_SHUTDOWNREQUEST = None - -class WorkerThread(threading.Thread): - """Thread which continuously polls a Queue for Connection objects. - - server: the HTTP Server which spawned this thread, and which owns the - Queue and is placing active connections into it. - ready: a simple flag for the calling server to know when this thread - has begun polling the Queue. - - Due to the timing issues of polling a Queue, a WorkerThread does not - check its own 'ready' flag after it has started. To stop the thread, - it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue - (one for each running WorkerThread). - """ - - def __init__(self, server): - self.ready = False - self.server = server - threading.Thread.__init__(self) - - def run(self): - try: - self.ready = True - while True: - conn = self.server.requests.get() - if conn is _SHUTDOWNREQUEST: - return - - try: - conn.communicate() - finally: - conn.close() - except (KeyboardInterrupt, SystemExit), exc: - self.server.interrupt = exc - - -class SSLConnection: - """A thread-safe wrapper for an SSL.Connection. - - *args: the arguments to create the wrapped SSL.Connection(*args). - """ - - def __init__(self, *args): - self._ssl_conn = SSL.Connection(*args) - self._lock = threading.RLock() - - for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', - 'renegotiate', 'bind', 'listen', 'connect', 'accept', - 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', - 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', - 'makefile', 'get_app_data', 'set_app_data', 'state_string', - 'sock_shutdown', 'get_peer_certificate', 'want_read', - 'want_write', 'set_connect_state', 'set_accept_state', - 'connect_ex', 'sendall', 'settimeout'): - exec """def %s(self, *args): - self._lock.acquire() - try: - return self._ssl_conn.%s(*args) - finally: - self._lock.release() -""" % (f, f) - - -class CherryPyWSGIServer(object): - """An HTTP server for WSGI. - - bind_addr: a (host, port) tuple if TCP sockets are desired; - for UNIX sockets, supply the filename as a string. - wsgi_app: the WSGI 'application callable'; multiple WSGI applications - may be passed as (script_name, callable) pairs. - numthreads: the number of worker threads to create (default 10). - server_name: the string to set for WSGI's SERVER_NAME environ entry. - Defaults to socket.gethostname(). - max: the maximum number of queued requests (defaults to -1 = no limit). - request_queue_size: the 'backlog' argument to socket.listen(); - specifies the maximum number of queued connections (default 5). - timeout: the timeout in seconds for accepted connections (default 10). - - protocol: the version string to write in the Status-Line of all - HTTP responses. For example, "HTTP/1.1" (the default). This - also limits the supported features used in the response. - - - SSL/HTTPS - --------- - The OpenSSL module must be importable for SSL functionality. - You can obtain it from http://pyopenssl.sourceforge.net/ - - ssl_certificate: the filename of the server SSL certificate. - ssl_privatekey: the filename of the server's private key file. - - If either of these is None (both are None by default), this server - will not use SSL. If both are given and are valid, they will be read - on server start and used in the SSL context for the listening socket. - """ - - protocol = "HTTP/1.1" - version = "CherryPy/3.0.2" - ready = False - _interrupt = None - ConnectionClass = HTTPConnection - - # Paths to certificate and private key files - ssl_certificate = None - ssl_private_key = None - - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, - max=-1, request_queue_size=5, timeout=10): - self.requests = Queue.Queue(max) - - if callable(wsgi_app): - # We've been handed a single wsgi_app, in CP-2.1 style. - # Assume it's mounted at "". - self.mount_points = [("", wsgi_app)] - else: - # We've been handed a list of (mount_point, wsgi_app) tuples, - # so that the server can call different wsgi_apps, and also - # correctly set SCRIPT_NAME. - self.mount_points = wsgi_app - self.mount_points.sort() - self.mount_points.reverse() - - self.bind_addr = bind_addr - self.numthreads = numthreads or 1 - if not server_name: - server_name = socket.gethostname() - self.server_name = server_name - self.request_queue_size = request_queue_size - self._workerThreads = [] - self.gtk_idle_id = None - - self.timeout = timeout - - def start_common(self): - """Run the server forever.""" - # We don't have to trap KeyboardInterrupt or SystemExit here, - # because cherrpy.server already does so, calling self.stop() for us. - # If you're using this server with another framework, you should - # trap those exceptions in whatever code block calls start(). - self._interrupt = None - - # Select the appropriate socket - if isinstance(self.bind_addr, basestring): - # AF_UNIX socket - - # So we can reuse the socket... - try: os.unlink(self.bind_addr) - except: pass - - # So everyone can access the socket... - try: os.chmod(self.bind_addr, 0777) - except: pass - - info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] - else: - # AF_INET or AF_INET6 socket - # Get the correct address family for our host (allows IPv6 addresses) - host, port = self.bind_addr - flags = 0 - if host == '': - # Despite the socket module docs, using '' does not - # allow AI_PASSIVE to work. Passing None instead - # returns '0.0.0.0' like we want. In other words: - # host AI_PASSIVE result - # '' Y 192.168.x.y - # '' N 192.168.x.y - # None Y 0.0.0.0 - # None N 127.0.0.1 - host = None - flags = socket.AI_PASSIVE - try: - info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, flags) - except socket.gaierror: - # Probably a DNS issue. Assume IPv4. - info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)] - - self.socket = None - msg = "No socket could be created" - for res in info: - af, socktype, proto, canonname, sa = res - try: - self.bind(af, socktype, proto) - except socket.error, msg: - if self.socket: - self.socket.close() - self.socket = None - continue - break - if not self.socket: - raise socket.error, msg - - # Timeout so KeyboardInterrupt can be caught on Win32 - self.socket.settimeout(1) - self.socket.listen(self.request_queue_size) - - # Create worker threads - for i in xrange(self.numthreads): - self._workerThreads.append(WorkerThread(self)) - for worker in self._workerThreads: - worker.setName("CP WSGIServer " + worker.getName()) - worker.start() - for worker in self._workerThreads: - while not worker.ready: - time.sleep(.1) - self.ready = True - - def start(self): - self.start_common() - while self.ready: - self.tick() - if self.interrupt: - while self.interrupt is True: - # Wait for self.stop() to complete. See _set_interrupt. - time.sleep(0.1) - raise self.interrupt - - def start_gtk(self): - self.start_common() - self.socket.settimeout(0.0001) - self.timeout = 0.3 - self.gtk_idle_id = gobject.idle_add(self.tick) - #self.gtk_idle_id = gobject.timeout_add(100, self.tick) #needs tweaking! - - - def bind(self, family, type, proto=0): - """Create (or recreate) the actual socket object.""" - self.socket = socket.socket(family, type, proto) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -## self.socket.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) - if self.ssl_certificate and self.ssl_private_key: - if SSL is None: - raise ImportError("You must install pyOpenSSL to use HTTPS.") - - # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.use_privatekey_file(self.ssl_private_key) - ctx.use_certificate_file(self.ssl_certificate) - self.socket = SSLConnection(ctx, self.socket) - self.populate_ssl_environ() - self.socket.bind(self.bind_addr) - - def tick(self): - """Accept a new connection and put it on the Queue.""" - #print 'tick!' - try: - s, addr = self.socket.accept() - if not self.ready: - return True - if hasattr(s, 'settimeout'): - s.settimeout(self.timeout) - conn = self.ConnectionClass(s, addr, self) - self.requests.put(conn) - except socket.timeout: - # The only reason for the timeout in start() is so we can - # notice keyboard interrupts on Win32, which don't interrupt - # accept() by default - # mvoncken and i'ts usefull for gtk too. - return True - except socket.error, x: - msg = x.args[1] - if msg in ("Bad file descriptor", "Socket operation on non-socket"): - # Our socket was closed. - return True - if msg == "Resource temporarily unavailable": - # Just try again. See http://www.cherrypy.org/ticket/479. - return True - raise #mvoncken:should it raise here? - return True - - def _get_interrupt(self): - return self._interrupt - def _set_interrupt(self, interrupt): - self._interrupt = True - self.stop() - self._interrupt = interrupt - interrupt = property(_get_interrupt, _set_interrupt, - doc="Set this to an Exception instance to " - "interrupt the server.") - - def stop(self): - """Gracefully shutdown a server that is serving forever.""" - self.ready = False - - sock = getattr(self, "socket", None) - if sock: - if not isinstance(self.bind_addr, basestring): - # Touch our own socket to make accept() return immediately. - try: - host, port = sock.getsockname()[:2] - except socket.error, x: - if x.args[1] != "Bad file descriptor": - raise - else: - # Note that we're explicitly NOT using AI_PASSIVE, - # here, because we want an actual IP to touch. - # localhost won't work if we've bound to a public IP, - # but it would if we bound to INADDR_ANY via host = ''. - for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - s = None - try: - s = socket.socket(af, socktype, proto) - # See http://groups.google.com/group/cherrypy-users/ - # browse_frm/thread/bbfe5eb39c904fe0 - s.settimeout(1.0) - s.connect((host, port)) - s.close() - except socket.error: - if s: - s.close() - if hasattr(sock, "close"): - sock.close() - self.socket = None - - # Must shut down threads here so the code that calls - # this method can know when all threads are stopped. - for worker in self._workerThreads: - self.requests.put(_SHUTDOWNREQUEST) - - # Don't join currentThread (when stop is called inside a request). - current = threading.currentThread() - while self._workerThreads: - worker = self._workerThreads.pop() - if worker is not current and worker.isAlive: - try: - worker.join() - except AssertionError: - pass - - def stop_gtk(self): - self.stop() - if self.gtk_idle_id == None: - raise Exception('gtk_idle_id == None in stop_gtk') - gobject.source_remove(self.gtk_idle_id) - self.gtk_idle_id = None - - - def populate_ssl_environ(self): - """Create WSGI environ entries to be merged into each request.""" - cert = open(self.ssl_certificate).read() - cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert) - self.ssl_environ = { - # pyOpenSSL doesn't provide access to any of these AFAICT -## 'SSL_PROTOCOL': 'SSLv2', -## SSL_CIPHER string The cipher specification name -## SSL_VERSION_INTERFACE string The mod_ssl program version -## SSL_VERSION_LIBRARY string The OpenSSL program version - } - - # Server certificate attributes - self.ssl_environ.update({ - 'SSL_SERVER_M_VERSION': cert.get_version(), - 'SSL_SERVER_M_SERIAL': cert.get_serial_number(), -## 'SSL_SERVER_V_START': Validity of server's certificate (start time), -## 'SSL_SERVER_V_END': Validity of server's certificate (end time), - }) - - for prefix, dn in [("I", cert.get_issuer()), - ("S", cert.get_subject())]: - # X509Name objects don't seem to have a way to get the - # complete DN string. Use str() and slice it instead, - # because str(dn) == "" - dnstr = str(dn)[18:-2] - - wsgikey = 'SSL_SERVER_%s_DN' % prefix - self.ssl_environ[wsgikey] = dnstr - - # The DN should be of the form: /k1=v1/k2=v2, but we must allow - # for any value to contain slashes itself (in a URL). - while dnstr: - pos = dnstr.rfind("=") - dnstr, value = dnstr[:pos], dnstr[pos + 1:] - pos = dnstr.rfind("/") - dnstr, key = dnstr[:pos], dnstr[pos + 1:] - if key and value: - wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) - self.ssl_environ[wsgikey] = value - - diff --git a/plugins/WebUi/lib/pythonize.py b/plugins/WebUi/lib/pythonize.py deleted file mode 100644 index 699c61dac..000000000 --- a/plugins/WebUi/lib/pythonize.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -some dbus to python type conversions --decorator for interface --wrapper class for proxy -""" -def pythonize(var): - """translates dbus types back to basic python types.""" - if isinstance(var, list): - return [pythonize(value) for value in var] - if isinstance(var, tuple): - return tuple([pythonize(value) for value in var]) - if isinstance(var, dict): - return dict( - [(pythonize(key), pythonize(value)) for key, value in var.iteritems()] - ) - - for klass in [unicode, str, bool, int, float, long]: - if isinstance(var,klass): - return klass(var) - return var - -def pythonize_call(func): - def deco(*args,**kwargs): - return pythonize(func(*args, **kwargs)) - return deco - -def pythonize_interface(func): - def deco(*args, **kwargs): - args = pythonize(args) - kwargs = pythonize(kwargs) - return func(*args, **kwargs) - return deco - -class PythonizeProxy(object): - def __init__(self,proxy): - self.proxy = proxy - def __getattr__(self, key): - return pythonize_call(getattr(self.proxy, key)) diff --git a/plugins/WebUi/lib/readme.txt b/plugins/WebUi/lib/readme.txt deleted file mode 100644 index 16c5eee85..000000000 --- a/plugins/WebUi/lib/readme.txt +++ /dev/null @@ -1,8 +0,0 @@ -This folder may only contain general purpose utilities/files/tools. -They should be usable outside of deluge. - -Disclaimer: - -Some may have been adapted to work better with deluge. -But they will import other parts of deluge or Webui. - diff --git a/plugins/WebUi/lib/static_handler.py b/plugins/WebUi/lib/static_handler.py deleted file mode 100644 index d5b706cca..000000000 --- a/plugins/WebUi/lib/static_handler.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python -#(c) Martijn Voncken, mvoncken@gmail.com -#Same Licence as web.py 0.22 ->Public Domain -# -""" -static fileserving for web.py -without the need for wsgi wrapper magic. -""" -import webpy022 as web -from webpy022.http import seeother, url - -import posixpath -import urlparse -import urllib -import mimetypes -import os -import datetime -import cgi -from StringIO import StringIO -mimetypes.init() # try to read system mime.types - -class static_handler: - """ - mostly c&p from SimpleHttpServer - serves relative from start location - """ - base_dir = './' - extensions_map = mimetypes.types_map - - def get_base_dir(self): - #override this if you have a config that changes the base dir at runtime - #deluge on windows :( - return self.base_dir - - def GET(self, path): - path = self.translate_path(path) - if os.path.isdir(path): - if not path.endswith('/'): - path += "/" - return self.list_directory(path) - - ctype = self.guess_type(path) - - try: - f = open(path, 'rb') - except IOError: - raise Exception('file not found:%s' % path) - #web.header("404", "File not found") - #return - web.header("Content-type", ctype) - fs = os.fstat(f.fileno()) - web.header("Content-Length", str(fs[6])) - web.lastmodified(datetime.datetime.fromtimestamp(fs.st_mtime)) - print f.read() - - def translate_path(self, path): - """Translate a /-separated PATH to the local filename syntax. - - Components that mean special things to the local file system - (e.g. drive or directory names) are ignored. (XXX They should - probably be diagnosed.) - - """ - # abandon query parameters - path = urlparse.urlparse(path)[2] - path = posixpath.normpath(urllib.unquote(path)) - words = path.split('/') - words = filter(None, words) - path = self.get_base_dir() - for word in words: - drive, word = os.path.splitdrive(word) - head, word = os.path.split(word) - if word in (os.curdir, os.pardir): continue - path = os.path.join(path, word) - return path - - def guess_type(self, path): - base, ext = posixpath.splitext(path) - if ext in self.extensions_map: - return self.extensions_map[ext] - ext = ext.lower() - if ext in self.extensions_map: - return self.extensions_map[ext] - else: - return 'application/octet-stream' - - - def list_directory(self, path): - """Helper to produce a directory listing (absent index.html). - - Return value is either a file object, or None (indicating an - error). In either case, the headers are sent, making the - interface the same as for send_head(). - #TODO ->use web.py +template! - """ - try: - list = os.listdir(path) - except os.error: - web.header('404', "No permission to list directory") - return None - list.sort(key=lambda a: a.lower()) - f = StringIO() - displaypath = cgi.escape(urllib.unquote(path)) - f.write("Directory listing for %s\n" % displaypath) - f.write("

Directory listing for %s

\n" % displaypath) - f.write("
\n
    \n") - for name in list: - fullname = os.path.join(path, name) - displayname = linkname = name - # Append / for directories or @ for symbolic links - if os.path.isdir(fullname): - displayname = name + "/" - linkname = name + "/" - if os.path.islink(fullname): - displayname = name + "@" - # Note: a link to a directory displays with @ and links with / - f.write('
  • %s\n' - % (urllib.quote(linkname), cgi.escape(displayname))) - f.write("
\n
\n") - length = f.tell() - f.seek(0) - - web.header("Content-type", "text/html") - web.header("Content-Length", str(length)) - print f.read() - - -if __name__ == '__main__': - #example: - class usr_static(static_handler): - base_dir = os.path.expanduser('~') - - urls = ('/relative/(.*)','static_handler', - '/(.*)','usr_static') - - web.run(urls,globals()) diff --git a/plugins/WebUi/lib/webpy022/Dependency-not-really part of webui.txt b/plugins/WebUi/lib/webpy022/Dependency-not-really part of webui.txt deleted file mode 100644 index 54e2330e9..000000000 --- a/plugins/WebUi/lib/webpy022/Dependency-not-really part of webui.txt +++ /dev/null @@ -1 +0,0 @@ - http://webpy.org/ \ No newline at end of file diff --git a/plugins/WebUi/lib/webpy022/__init__.py b/plugins/WebUi/lib/webpy022/__init__.py deleted file mode 100644 index 25e03d137..000000000 --- a/plugins/WebUi/lib/webpy022/__init__.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -from __future__ import generators - -"""web.py: makes web apps (http://webpy.org)""" -__version__ = "0.22" -__revision__ = "$Rev: 183 $" -__author__ = "Aaron Swartz " -__license__ = "public domain" -__contributors__ = "see http://webpy.org/changes" - -# todo: -# - some sort of accounts system - -import utils, db, net, wsgi, http, webapi, request, httpserver, debugerror -import template, form - -from utils import * -from db import * -from net import * -from wsgi import * -from http import * -from webapi import * -from request import * -from httpserver import * -from debugerror import * - -try: - import cheetah - from cheetah import * -except ImportError: - pass - -def main(): - import doctest - - doctest.testmod(utils) - doctest.testmod(db) - doctest.testmod(net) - doctest.testmod(wsgi) - doctest.testmod(http) - doctest.testmod(webapi) - doctest.testmod(request) - - try: - doctest.testmod(cheetah) - except NameError: - pass - - template.test() - - import sys - urls = ('/web.py', 'source') - class source: - def GET(self): - header('Content-Type', 'text/python') - print open(sys.argv[0]).read() - - if listget(sys.argv, 1) != 'test': - run(urls, locals()) - -if __name__ == "__main__": main() - diff --git a/plugins/WebUi/lib/webpy022/changes.txt b/plugins/WebUi/lib/webpy022/changes.txt deleted file mode 100644 index 326e8a177..000000000 --- a/plugins/WebUi/lib/webpy022/changes.txt +++ /dev/null @@ -1,5 +0,0 @@ -1:Commented out some code to enable a relative redirect. -This is not according to HTTP/1.1 Spec -But many deluge users will want to route the webui through firewalls/routers or use apache redirects. - -2:Disabled logging in the builtin http-server. diff --git a/plugins/WebUi/lib/webpy022/cheetah.py b/plugins/WebUi/lib/webpy022/cheetah.py deleted file mode 100644 index db9fbf305..000000000 --- a/plugins/WebUi/lib/webpy022/cheetah.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -Cheetah API -(from web.py) -""" - -__all__ = ["render"] - -import re, urlparse, pprint, traceback, sys -from Cheetah.Compiler import Compiler -from Cheetah.Filters import Filter -from utils import re_compile, memoize, dictadd -from net import htmlquote, websafe -from webapi import ctx, header, output, input, cookies, loadhooks - -def upvars(level=2): - """Guido van Rossum sez: don't use this function.""" - return dictadd( - sys._getframe(level).f_globals, - sys._getframe(level).f_locals) - -r_include = re_compile(r'(?!\\)#include \"(.*?)\"($|#)', re.M) -def __compiletemplate(template, base=None, isString=False): - if isString: - text = template - else: - text = open('templates/'+template).read() - # implement #include at compile-time - def do_include(match): - text = open('templates/'+match.groups()[0]).read() - return text - while r_include.findall(text): - text = r_include.sub(do_include, text) - - execspace = _compiletemplate.bases.copy() - tmpl_compiler = Compiler(source=text, mainClassName='GenTemplate') - tmpl_compiler.addImportedVarNames(execspace.keys()) - exec str(tmpl_compiler) in execspace - if base: - _compiletemplate.bases[base] = execspace['GenTemplate'] - - return execspace['GenTemplate'] - -_compiletemplate = memoize(__compiletemplate) -_compiletemplate.bases = {} - -def render(template, terms=None, asTemplate=False, base=None, - isString=False): - """ - Renders a template, caching where it can. - - `template` is the name of a file containing the a template in - the `templates/` folder, unless `isString`, in which case it's the - template itself. - - `terms` is a dictionary used to fill the template. If it's None, then - the caller's local variables are used instead, plus context, if it's not - already set, is set to `context`. - - If asTemplate is False, it `output`s the template directly. Otherwise, - it returns the template object. - - If the template is a potential base template (that is, something other templates) - can extend, then base should be a string with the name of the template. The - template will be cached and made available for future calls to `render`. - - Requires [Cheetah](http://cheetahtemplate.org/). - """ - # terms=['var1', 'var2'] means grab those variables - if isinstance(terms, list): - new = {} - old = upvars() - for k in terms: - new[k] = old[k] - terms = new - # default: grab all locals - elif terms is None: - terms = {'context': ctx, 'ctx':ctx} - terms.update(sys._getframe(1).f_locals) - # terms=d means use d as the searchList - if not isinstance(terms, tuple): - terms = (terms,) - - if 'headers' in ctx and not isString and template.endswith('.html'): - header('Content-Type','text/html; charset=utf-8', unique=True) - - if loadhooks.has_key('reloader'): - compiled_tmpl = __compiletemplate(template, base=base, isString=isString) - else: - compiled_tmpl = _compiletemplate(template, base=base, isString=isString) - compiled_tmpl = compiled_tmpl(searchList=terms, filter=WebSafe) - if asTemplate: - return compiled_tmpl - else: - return output(str(compiled_tmpl)) - -class WebSafe(Filter): - def filter(self, val, **keywords): - return websafe(val) diff --git a/plugins/WebUi/lib/webpy022/db.py b/plugins/WebUi/lib/webpy022/db.py deleted file mode 100644 index 2438a162f..000000000 --- a/plugins/WebUi/lib/webpy022/db.py +++ /dev/null @@ -1,703 +0,0 @@ -""" -Database API -(part of web.py) -""" - -# todo: -# - test with sqlite -# - a store function? - -__all__ = [ - "UnknownParamstyle", "UnknownDB", - "sqllist", "sqlors", "aparam", "reparam", - "SQLQuery", "sqlquote", - "SQLLiteral", "sqlliteral", - "connect", - "TransactionError", "transaction", "transact", "commit", "rollback", - "query", - "select", "insert", "update", "delete" -] - -import time -try: import datetime -except ImportError: datetime = None - -from utils import storage, iters, iterbetter -import webapi as web - -try: - from DBUtils import PooledDB - web.config._hasPooling = True -except ImportError: - web.config._hasPooling = False - -class _ItplError(ValueError): - def __init__(self, text, pos): - ValueError.__init__(self) - self.text = text - self.pos = pos - def __str__(self): - return "unfinished expression in %s at char %d" % ( - repr(self.text), self.pos) - -def _interpolate(format): - """ - Takes a format string and returns a list of 2-tuples of the form - (boolean, string) where boolean says whether string should be evaled - or not. - - from (public domain, Ka-Ping Yee) - """ - from tokenize import tokenprog - - def matchorfail(text, pos): - match = tokenprog.match(text, pos) - if match is None: - raise _ItplError(text, pos) - return match, match.end() - - namechars = "abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; - chunks = [] - pos = 0 - - while 1: - dollar = format.find("$", pos) - if dollar < 0: - break - nextchar = format[dollar + 1] - - if nextchar == "{": - chunks.append((0, format[pos:dollar])) - pos, level = dollar + 2, 1 - while level: - match, pos = matchorfail(format, pos) - tstart, tend = match.regs[3] - token = format[tstart:tend] - if token == "{": - level = level + 1 - elif token == "}": - level = level - 1 - chunks.append((1, format[dollar + 2:pos - 1])) - - elif nextchar in namechars: - chunks.append((0, format[pos:dollar])) - match, pos = matchorfail(format, dollar + 1) - while pos < len(format): - if format[pos] == "." and \ - pos + 1 < len(format) and format[pos + 1] in namechars: - match, pos = matchorfail(format, pos + 1) - elif format[pos] in "([": - pos, level = pos + 1, 1 - while level: - match, pos = matchorfail(format, pos) - tstart, tend = match.regs[3] - token = format[tstart:tend] - if token[0] in "([": - level = level + 1 - elif token[0] in ")]": - level = level - 1 - else: - break - chunks.append((1, format[dollar + 1:pos])) - - else: - chunks.append((0, format[pos:dollar + 1])) - pos = dollar + 1 + (nextchar == "$") - - if pos < len(format): - chunks.append((0, format[pos:])) - return chunks - -class UnknownParamstyle(Exception): - """ - raised for unsupported db paramstyles - - (currently supported: qmark, numeric, format, pyformat) - """ - pass - -def aparam(): - """ - Returns the appropriate string to be used to interpolate - a value with the current `web.ctx.db_module` or simply %s - if there isn't one. - - >>> aparam() - '%s' - """ - if hasattr(web.ctx, 'db_module'): - style = web.ctx.db_module.paramstyle - else: - style = 'pyformat' - - if style == 'qmark': - return '?' - elif style == 'numeric': - return ':1' - elif style in ['format', 'pyformat']: - return '%s' - raise UnknownParamstyle, style - -def reparam(string_, dictionary): - """ - Takes a string and a dictionary and interpolates the string - using values from the dictionary. Returns an `SQLQuery` for the result. - - >>> reparam("s = $s", dict(s=True)) - - """ - vals = [] - result = [] - for live, chunk in _interpolate(string_): - if live: - result.append(aparam()) - vals.append(eval(chunk, dictionary)) - else: result.append(chunk) - return SQLQuery(''.join(result), vals) - -def sqlify(obj): - """ - converts `obj` to its proper SQL version - - >>> sqlify(None) - 'NULL' - >>> sqlify(True) - "'t'" - >>> sqlify(3) - '3' - """ - - # because `1 == True and hash(1) == hash(True)` - # we have to do this the hard way... - - if obj is None: - return 'NULL' - elif obj is True: - return "'t'" - elif obj is False: - return "'f'" - elif datetime and isinstance(obj, datetime.datetime): - return repr(obj.isoformat()) - else: - return repr(obj) - -class SQLQuery: - """ - You can pass this sort of thing as a clause in any db function. - Otherwise, you can pass a dictionary to the keyword argument `vars` - and the function will call reparam for you. - """ - # tested in sqlquote's docstring - def __init__(self, s='', v=()): - self.s, self.v = str(s), tuple(v) - - def __getitem__(self, key): # for backwards-compatibility - return [self.s, self.v][key] - - def __add__(self, other): - if isinstance(other, str): - self.s += other - elif isinstance(other, SQLQuery): - self.s += other.s - self.v += other.v - return self - - def __radd__(self, other): - if isinstance(other, str): - self.s = other + self.s - return self - else: - return NotImplemented - - def __str__(self): - try: - return self.s % tuple([sqlify(x) for x in self.v]) - except (ValueError, TypeError): - return self.s - - def __repr__(self): - return '' % repr(str(self)) - -class SQLLiteral: - """ - Protects a string from `sqlquote`. - - >>> insert('foo', time=SQLLiteral('NOW()'), _test=True) - - """ - def __init__(self, v): - self.v = v - - def __repr__(self): - return self.v - -sqlliteral = SQLLiteral - -def sqlquote(a): - """ - Ensures `a` is quoted properly for use in a SQL query. - - >>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3) - - """ - return SQLQuery(aparam(), (a,)) - -class UnknownDB(Exception): - """raised for unsupported dbms""" - pass - -def connect(dbn, **keywords): - """ - Connects to the specified database. - - `dbn` currently must be "postgres", "mysql", or "sqlite". - - If DBUtils is installed, connection pooling will be used. - """ - if dbn == "postgres": - try: - import psycopg2 as db - except ImportError: - try: - import psycopg as db - except ImportError: - import pgdb as db - if 'pw' in keywords: - keywords['password'] = keywords['pw'] - del keywords['pw'] - keywords['database'] = keywords['db'] - del keywords['db'] - - elif dbn == "mysql": - import MySQLdb as db - if 'pw' in keywords: - keywords['passwd'] = keywords['pw'] - del keywords['pw'] - db.paramstyle = 'pyformat' # it's both, like psycopg - - elif dbn == "sqlite": - try: - import sqlite3 as db - db.paramstyle = 'qmark' - except ImportError: - try: - from pysqlite2 import dbapi2 as db - db.paramstyle = 'qmark' - except ImportError: - import sqlite as db - web.config._hasPooling = False - keywords['database'] = keywords['db'] - del keywords['db'] - - elif dbn == "firebird": - import kinterbasdb as db - if 'pw' in keywords: - keywords['passwd'] = keywords['pw'] - del keywords['pw'] - keywords['database'] = keywords['db'] - del keywords['db'] - - else: - raise UnknownDB, dbn - - web.ctx.db_name = dbn - web.ctx.db_module = db - web.ctx.db_transaction = 0 - web.ctx.db = keywords - - def _PooledDB(db, keywords): - # In DBUtils 0.9.3, `dbapi` argument is renamed as `creator` - # see Bug#122112 - if PooledDB.__version__.split('.') < '0.9.3'.split('.'): - return PooledDB.PooledDB(dbapi=db, **keywords) - else: - return PooledDB.PooledDB(creator=db, **keywords) - - def db_cursor(): - if isinstance(web.ctx.db, dict): - keywords = web.ctx.db - if web.config._hasPooling: - if 'db' not in globals(): - globals()['db'] = _PooledDB(db, keywords) - web.ctx.db = globals()['db'].connection() - else: - web.ctx.db = db.connect(**keywords) - return web.ctx.db.cursor() - web.ctx.db_cursor = db_cursor - - web.ctx.dbq_count = 0 - - def db_execute(cur, sql_query, dorollback=True): - """executes an sql query""" - - web.ctx.dbq_count += 1 - - try: - a = time.time() - out = cur.execute(sql_query.s, sql_query.v) - b = time.time() - except: - if web.config.get('db_printing'): - print >> web.debug, 'ERR:', str(sql_query) - if dorollback: rollback(care=False) - raise - - if web.config.get('db_printing'): - print >> web.debug, '%s (%s): %s' % (round(b-a, 2), web.ctx.dbq_count, str(sql_query)) - - return out - web.ctx.db_execute = db_execute - return web.ctx.db - -class TransactionError(Exception): pass - -class transaction: - """ - A context that can be used in conjunction with "with" statements - to implement SQL transactions. Starts a transaction on enter, - rolls it back if there's an error; otherwise it commits it at the - end. - """ - def __enter__(self): - transact() - - def __exit__(self, exctype, excvalue, traceback): - if exctype is not None: - rollback() - else: - commit() - -def transact(): - """Start a transaction.""" - if not web.ctx.db_transaction: - # commit everything up to now, so we don't rollback it later - if hasattr(web.ctx.db, 'commit'): - web.ctx.db.commit() - else: - db_cursor = web.ctx.db_cursor() - web.ctx.db_execute(db_cursor, - SQLQuery("SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction)) - web.ctx.db_transaction += 1 - -def commit(): - """Commits a transaction.""" - web.ctx.db_transaction -= 1 - if web.ctx.db_transaction < 0: - raise TransactionError, "not in a transaction" - - if not web.ctx.db_transaction: - if hasattr(web.ctx.db, 'commit'): - web.ctx.db.commit() - else: - db_cursor = web.ctx.db_cursor() - web.ctx.db_execute(db_cursor, - SQLQuery("RELEASE SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction)) - -def rollback(care=True): - """Rolls back a transaction.""" - web.ctx.db_transaction -= 1 - if web.ctx.db_transaction < 0: - web.db_transaction = 0 - if care: - raise TransactionError, "not in a transaction" - else: - return - - if not web.ctx.db_transaction: - if hasattr(web.ctx.db, 'rollback'): - web.ctx.db.rollback() - else: - db_cursor = web.ctx.db_cursor() - web.ctx.db_execute(db_cursor, - SQLQuery("ROLLBACK TO SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction), - dorollback=False) - -def query(sql_query, vars=None, processed=False, _test=False): - """ - Execute SQL query `sql_query` using dictionary `vars` to interpolate it. - If `processed=True`, `vars` is a `reparam`-style list to use - instead of interpolating. - - >>> query("SELECT * FROM foo", _test=True) - - >>> query("SELECT * FROM foo WHERE x = $x", vars=dict(x='f'), _test=True) - - >>> query("SELECT * FROM foo WHERE x = " + sqlquote('f'), _test=True) - - """ - if vars is None: vars = {} - - if not processed and not isinstance(sql_query, SQLQuery): - sql_query = reparam(sql_query, vars) - - if _test: return sql_query - - db_cursor = web.ctx.db_cursor() - web.ctx.db_execute(db_cursor, sql_query) - - if db_cursor.description: - names = [x[0] for x in db_cursor.description] - def iterwrapper(): - row = db_cursor.fetchone() - while row: - yield storage(dict(zip(names, row))) - row = db_cursor.fetchone() - out = iterbetter(iterwrapper()) - if web.ctx.db_name != "sqlite": - out.__len__ = lambda: int(db_cursor.rowcount) - out.list = lambda: [storage(dict(zip(names, x))) \ - for x in db_cursor.fetchall()] - else: - out = db_cursor.rowcount - - if not web.ctx.db_transaction: web.ctx.db.commit() - return out - -def sqllist(lst): - """ - Converts the arguments for use in something like a WHERE clause. - - >>> sqllist(['a', 'b']) - 'a, b' - >>> sqllist('a') - 'a' - - """ - if isinstance(lst, str): - return lst - else: - return ', '.join(lst) - -def sqlors(left, lst): - """ - `left is a SQL clause like `tablename.arg = ` - and `lst` is a list of values. Returns a reparam-style - pair featuring the SQL that ORs together the clause - for each item in the lst. - - >>> sqlors('foo = ', []) - - >>> sqlors('foo = ', [1]) - - >>> sqlors('foo = ', 1) - - >>> sqlors('foo = ', [1,2,3]) - - """ - if isinstance(lst, iters): - lst = list(lst) - ln = len(lst) - if ln == 0: - return SQLQuery("2+2=5", []) - if ln == 1: - lst = lst[0] - - if isinstance(lst, iters): - return SQLQuery('(' + left + - (' OR ' + left).join([aparam() for param in lst]) + ")", lst) - else: - return SQLQuery(left + aparam(), [lst]) - -def sqlwhere(dictionary, grouping=' AND '): - """ - Converts a `dictionary` to an SQL WHERE clause `SQLQuery`. - - >>> sqlwhere({'cust_id': 2, 'order_id':3}) - - >>> sqlwhere({'cust_id': 2, 'order_id':3}, grouping=', ') - - """ - - return SQLQuery(grouping.join([ - '%s = %s' % (k, aparam()) for k in dictionary.keys() - ]), dictionary.values()) - -def select(tables, vars=None, what='*', where=None, order=None, group=None, - limit=None, offset=None, _test=False): - """ - Selects `what` from `tables` with clauses `where`, `order`, - `group`, `limit`, and `offset`. Uses vars to interpolate. - Otherwise, each clause can be a SQLQuery. - - >>> select('foo', _test=True) - - >>> select(['foo', 'bar'], where="foo.bar_id = bar.id", limit=5, _test=True) - - """ - if vars is None: vars = {} - qout = "" - - def gen_clause(sql, val): - if isinstance(val, (int, long)): - if sql == 'WHERE': - nout = 'id = ' + sqlquote(val) - else: - nout = SQLQuery(val) - elif isinstance(val, (list, tuple)) and len(val) == 2: - nout = SQLQuery(val[0], val[1]) # backwards-compatibility - elif isinstance(val, SQLQuery): - nout = val - elif val: - nout = reparam(val, vars) - else: - return "" - - out = "" - if qout: out += " " - out += sql + " " + nout - return out - - if web.ctx.get('db_name') == "firebird": - for (sql, val) in ( - ('FIRST', limit), - ('SKIP', offset) - ): - qout += gen_clause(sql, val) - if qout: - SELECT = 'SELECT ' + qout - else: - SELECT = 'SELECT' - qout = "" - sql_clauses = ( - (SELECT, what), - ('FROM', sqllist(tables)), - ('WHERE', where), - ('GROUP BY', group), - ('ORDER BY', order) - ) - else: - sql_clauses = ( - ('SELECT', what), - ('FROM', sqllist(tables)), - ('WHERE', where), - ('GROUP BY', group), - ('ORDER BY', order), - ('LIMIT', limit), - ('OFFSET', offset) - ) - - for (sql, val) in sql_clauses: - qout += gen_clause(sql, val) - - if _test: return qout - return query(qout, processed=True) - -def insert(tablename, seqname=None, _test=False, **values): - """ - Inserts `values` into `tablename`. Returns current sequence ID. - Set `seqname` to the ID if it's not the default, or to `False` - if there isn't one. - - >>> insert('foo', joe='bob', a=2, _test=True) - - """ - - if values: - sql_query = SQLQuery("INSERT INTO %s (%s) VALUES (%s)" % ( - tablename, - ", ".join(values.keys()), - ', '.join([aparam() for x in values]) - ), values.values()) - else: - sql_query = SQLQuery("INSERT INTO %s DEFAULT VALUES" % tablename) - - if _test: return sql_query - - db_cursor = web.ctx.db_cursor() - if seqname is False: - pass - elif web.ctx.db_name == "postgres": - if seqname is None: - seqname = tablename + "_id_seq" - sql_query += "; SELECT currval('%s')" % seqname - elif web.ctx.db_name == "mysql": - web.ctx.db_execute(db_cursor, sql_query) - sql_query = SQLQuery("SELECT last_insert_id()") - elif web.ctx.db_name == "sqlite": - web.ctx.db_execute(db_cursor, sql_query) - # not really the same... - sql_query = SQLQuery("SELECT last_insert_rowid()") - - web.ctx.db_execute(db_cursor, sql_query) - try: - out = db_cursor.fetchone()[0] - except Exception: - out = None - - if not web.ctx.db_transaction: web.ctx.db.commit() - - return out - -def update(tables, where, vars=None, _test=False, **values): - """ - Update `tables` with clause `where` (interpolated using `vars`) - and setting `values`. - - >>> joe = 'Joseph' - >>> update('foo', where='name = $joe', name='bob', age=5, - ... vars=locals(), _test=True) - - """ - if vars is None: vars = {} - - if isinstance(where, (int, long)): - where = "id = " + sqlquote(where) - elif isinstance(where, (list, tuple)) and len(where) == 2: - where = SQLQuery(where[0], where[1]) - elif isinstance(where, SQLQuery): - pass - else: - where = reparam(where, vars) - - query = ( - "UPDATE " + sqllist(tables) + - " SET " + sqlwhere(values, ', ') + - " WHERE " + where) - - if _test: return query - - db_cursor = web.ctx.db_cursor() - web.ctx.db_execute(db_cursor, query) - - if not web.ctx.db_transaction: web.ctx.db.commit() - return db_cursor.rowcount - -def delete(table, where=None, using=None, vars=None, _test=False): - """ - Deletes from `table` with clauses `where` and `using`. - - >>> name = 'Joe' - >>> delete('foo', where='name = $name', vars=locals(), _test=True) - - """ - if vars is None: vars = {} - - if isinstance(where, (int, long)): - where = "id = " + sqlquote(where) - elif isinstance(where, (list, tuple)) and len(where) == 2: - where = SQLQuery(where[0], where[1]) - elif isinstance(where, SQLQuery): - pass - elif where is None: - pass - else: - where = reparam(where, vars) - - q = 'DELETE FROM ' + table - if where: - q += ' WHERE ' + where - if using and web.ctx.get('db_name') != "firebird": - q += ' USING ' + sqllist(using) - - if _test: return q - - db_cursor = web.ctx.db_cursor() - web.ctx.db_execute(db_cursor, q) - - if not web.ctx.db_transaction: web.ctx.db.commit() - return db_cursor.rowcount - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/plugins/WebUi/lib/webpy022/debugerror.py b/plugins/WebUi/lib/webpy022/debugerror.py deleted file mode 100644 index 1de465a81..000000000 --- a/plugins/WebUi/lib/webpy022/debugerror.py +++ /dev/null @@ -1,316 +0,0 @@ -""" -pretty debug errors -(part of web.py) - -adapted from Django -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 net import websafe -from template import Template -import webapi as web - -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_type at $ctx.path - - - - - -
-

$exception_type at $ctx.path

-

$exception_value

- - - - - - -
Python$frames[0].filename in $frames[0].function, line $frames[0].lineno
Web$ctx.method $ctx.home$ctx.path
-
-
-

Traceback (innermost first)

-
    -$for frame in frames: -
  • - $frame.filename in $frame.function - $if frame.context_line: -
    - $if frame.pre_context: -
      - $for line in frame.pre_context: -
    1. $line
    2. -
    -
    1. $frame.context_line ...
    - $if frame.post_context: -
      - $for line in frame.post_context: -
    1. $line
    2. -
    -
    - - $if frame.vars: -
    - Local vars - $# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame)) -
    - $:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id))) -
  • -
-
- -
-$if ctx.output or ctx.headers: -

Response so far

-

HEADERS

-

- $for kv in ctx.headers: - $kv[0]: $kv[1]
- $else: - [no headers] -

- -

BODY

-

- $ctx.output -

- -

Request information

- -

INPUT

-$:dicttable(web.input()) - - -$:dicttable(web.cookies()) - -

META

-$ 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)) - -

ENVIRONMENT

-$:dicttable(ctx.env) -
- -
-

- You're seeing this error because you have web.internalerror - set to web.debugerror. Change that if you want a different one. -

-
- - - -""" - -dicttable_t = r"""$def with (d, kls='req', id=None) -$if d: - - - $ temp = d.items() - $temp.sort() - $for kv in temp: - - -
VariableValue
$kv[0]
$prettify(kv[1])
-$else: -

No data.

-""" - -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() - 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) - -def 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) \ No newline at end of file diff --git a/plugins/WebUi/lib/webpy022/form.py b/plugins/WebUi/lib/webpy022/form.py deleted file mode 100644 index b1b808e49..000000000 --- a/plugins/WebUi/lib/webpy022/form.py +++ /dev/null @@ -1,215 +0,0 @@ -""" -HTML forms -(part of web.py) -""" - -import copy, re -import webapi as web -import utils, net - -def attrget(obj, attr, value=None): - if hasattr(obj, 'has_key') and obj.has_key(attr): return obj[attr] - if hasattr(obj, attr): return getattr(obj, attr) - return value - -class Form: - def __init__(self, *inputs, **kw): - self.inputs = inputs - self.valid = True - self.note = None - self.validators = kw.pop('validators', []) - - def __call__(self, x=None): - o = copy.deepcopy(self) - if x: o.validates(x) - return o - - def render(self): - out = '' - out += self.rendernote(self.note) - out += '\n' - for i in self.inputs: - out += ' ' % (i.id, i.description) - out += "" - out += '\n' % (i.id, self.rendernote(i.note)) - out += "
"+i.pre+i.render()+i.post+"%s
" - return out - - def rendernote(self, note): - if note: return '%s' % note - else: return "" - - def validates(self, source=None, _validate=True, **kw): - source = source or kw or web.input() - out = True - for i in self.inputs: - v = attrget(source, i.name) - if _validate: - out = i.validate(v) and out - else: - i.value = v - if _validate: - out = out and self._validate(source) - self.valid = out - return out - - def _validate(self, value): - self.value = value - for v in self.validators: - if not v.valid(value): - self.note = v.msg - return False - return True - - def fill(self, source=None, **kw): - return self.validates(source, _validate=False, **kw) - - def __getitem__(self, i): - for x in self.inputs: - if x.name == i: return x - raise KeyError, i - - def _get_d(self): #@@ should really be form.attr, no? - return utils.storage([(i.name, i.value) for i in self.inputs]) - d = property(_get_d) - -class Input(object): - def __init__(self, name, *validators, **attrs): - self.description = attrs.pop('description', name) - self.value = attrs.pop('value', None) - self.pre = attrs.pop('pre', "") - self.post = attrs.pop('post', "") - self.id = attrs.setdefault('id', name) - if 'class_' in attrs: - attrs['class'] = attrs['class_'] - del attrs['class_'] - self.name, self.validators, self.attrs, self.note = name, validators, attrs, None - - def validate(self, value): - self.value = value - for v in self.validators: - if not v.valid(value): - self.note = v.msg - return False - return True - - def render(self): raise NotImplementedError - - def addatts(self): - str = "" - for (n, v) in self.attrs.items(): - str += ' %s="%s"' % (n, net.websafe(v)) - return str - -#@@ quoting - -class Textbox(Input): - def render(self): - x = '' - -class Checkbox(Input): - def render(self): - x = 'moved permanently') - -def found(url): - """A `302 Found` redirect.""" - return redirect(url, '302 Found') - -def seeother(url): - """A `303 See Other` redirect.""" - return redirect(url, '303 See Other') - -def tempredirect(url): - """A `307 Temporary Redirect` redirect.""" - return redirect(url, '307 Temporary Redirect') - -def write(cgi_response): - """ - Converts a standard CGI-style string response into `header` and - `output` calls. - """ - cgi_response = str(cgi_response) - cgi_response.replace('\r\n', '\n') - head, body = cgi_response.split('\n\n', 1) - lines = head.split('\n') - - for line in lines: - if line.isspace(): - continue - hdr, value = line.split(":", 1) - value = value.strip() - if hdr.lower() == "status": - web.ctx.status = value - else: - web.header(hdr, value) - - web.output(body) - -def urlencode(query): - """ - Same as urllib.urlencode, but supports unicode strings. - - >>> urlencode({'text':'foo bar'}) - 'text=foo+bar' - """ - query = dict([(k, utils.utf8(v)) for k, v in query.items()]) - return urllib.urlencode(query) - -def changequery(query=None, **kw): - """ - Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return - `/foo?a=3&b=2` -- the same URL but with the arguments you requested - changed. - """ - if query is None: - query = web.input(_method='get') - for k, v in kw.iteritems(): - if v is None: - query.pop(k, None) - else: - query[k] = v - out = web.ctx.path - if query: - out += '?' + urlencode(query) - return out - -def url(path=None, **kw): - """ - Makes url by concatinating web.ctx.homepath and path and the - query string created using the arguments. - """ - if path is None: - path = web.ctx.path - if path.startswith("/"): - out = web.ctx.homepath + path - else: - out = path - - if kw: - out += '?' + urlencode(kw) - - return out - -def background(func): - """A function decorator to run a long-running function as a background thread.""" - def internal(*a, **kw): - web.data() # cache it - - tmpctx = web._context[threading.currentThread()] - web._context[threading.currentThread()] = utils.storage(web.ctx.copy()) - - def newfunc(): - web._context[threading.currentThread()] = tmpctx - func(*a, **kw) - myctx = web._context[threading.currentThread()] - for k in myctx.keys(): - if k not in ['status', 'headers', 'output']: - try: del myctx[k] - except KeyError: pass - - t = threading.Thread(target=newfunc) - background.threaddb[id(t)] = t - t.start() - web.ctx.headers = [] - return seeother(changequery(_t=id(t))) - return internal -background.threaddb = {} - -def backgrounder(func): - def internal(*a, **kw): - i = web.input(_method='get') - if '_t' in i: - try: - t = background.threaddb[int(i._t)] - except KeyError: - return web.notfound() - web._context[threading.currentThread()] = web._context[t] - return - else: - return func(*a, **kw) - return internal - -class Reloader: - """ - Before every request, checks to see if any loaded modules have changed on - disk and, if so, reloads them. - """ - def __init__(self, func): - self.func = func - self.mtimes = {} - # cheetah: - # b = _compiletemplate.bases - # _compiletemplate = globals()['__compiletemplate'] - # _compiletemplate.bases = b - - web.loadhooks['reloader'] = self.check - # todo: - # - replace relrcheck with a loadhook - #if reloader in middleware: - # relr = reloader(None) - # relrcheck = relr.check - # middleware.remove(reloader) - #else: - # relr = None - # relrcheck = lambda: None - # if relr: - # relr.func = wsgifunc - # return wsgifunc - # - - - def check(self): - for mod in sys.modules.values(): - try: - mtime = os.stat(mod.__file__).st_mtime - except (AttributeError, OSError, IOError): - continue - if mod.__file__.endswith('.pyc') and \ - os.path.exists(mod.__file__[:-1]): - mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime) - if mod not in self.mtimes: - self.mtimes[mod] = mtime - elif self.mtimes[mod] < mtime: - try: - reload(mod) - self.mtimes[mod] = mtime - except ImportError: - pass - return True - - def __call__(self, e, o): - self.check() - return self.func(e, o) - -reloader = Reloader - -def profiler(app): - """Outputs basic profiling information at the bottom of each response.""" - from utils import profile - def profile_internal(e, o): - out, result = profile(app)(e, o) - return out + ['
' + net.websafe(result) + '
'] - return profile_internal - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/plugins/WebUi/lib/webpy022/httpserver.py b/plugins/WebUi/lib/webpy022/httpserver.py deleted file mode 100644 index 6df60b1d8..000000000 --- a/plugins/WebUi/lib/webpy022/httpserver.py +++ /dev/null @@ -1,227 +0,0 @@ -__all__ = ["runsimple"] - -import sys, os -import webapi as web -import net - -def runbasic(func, server_address=("0.0.0.0", 8080)): - """ - Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` - is hosted statically. - - Based on [WsgiServer][ws] from [Colin Stewart][cs]. - - [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html - [cs]: http://www.owlfish.com/ - """ - # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) - # Modified somewhat for simplicity - # Used under the modified BSD license: - # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 - - import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse - import socket, errno - import traceback - - class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): - def run_wsgi_app(self): - protocol, host, path, parameters, query, fragment = \ - urlparse.urlparse('http://dummyhost%s' % self.path) - - # we only use path, query - env = {'wsgi.version': (1, 0) - ,'wsgi.url_scheme': 'http' - ,'wsgi.input': self.rfile - ,'wsgi.errors': sys.stderr - ,'wsgi.multithread': 1 - ,'wsgi.multiprocess': 0 - ,'wsgi.run_once': 0 - ,'REQUEST_METHOD': self.command - ,'REQUEST_URI': self.path - ,'PATH_INFO': path - ,'QUERY_STRING': query - ,'CONTENT_TYPE': self.headers.get('Content-Type', '') - ,'CONTENT_LENGTH': self.headers.get('Content-Length', '') - ,'REMOTE_ADDR': self.client_address[0] - ,'SERVER_NAME': self.server.server_address[0] - ,'SERVER_PORT': str(self.server.server_address[1]) - ,'SERVER_PROTOCOL': self.request_version - } - - for http_header, http_value in self.headers.items(): - env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \ - http_value - - # Setup the state - self.wsgi_sent_headers = 0 - self.wsgi_headers = [] - - try: - # We have there environment, now invoke the application - result = self.server.app(env, self.wsgi_start_response) - try: - try: - for data in result: - if data: - self.wsgi_write_data(data) - finally: - if hasattr(result, 'close'): - result.close() - except socket.error, socket_err: - # Catch common network errors and suppress them - if (socket_err.args[0] in \ - (errno.ECONNABORTED, errno.EPIPE)): - return - except socket.timeout, socket_timeout: - return - except: - print >> web.debug, traceback.format_exc(), - - if (not self.wsgi_sent_headers): - # We must write out something! - self.wsgi_write_data(" ") - return - - do_POST = run_wsgi_app - do_PUT = run_wsgi_app - do_DELETE = run_wsgi_app - - def do_GET(self): - if self.path.startswith('/static/'): - SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) - else: - self.run_wsgi_app() - - def wsgi_start_response(self, response_status, response_headers, - exc_info=None): - if (self.wsgi_sent_headers): - raise Exception \ - ("Headers already sent and start_response called again!") - # Should really take a copy to avoid changes in the application.... - self.wsgi_headers = (response_status, response_headers) - return self.wsgi_write_data - - def wsgi_write_data(self, data): - if (not self.wsgi_sent_headers): - status, headers = self.wsgi_headers - # Need to send header prior to data - status_code = status[:status.find(' ')] - status_msg = status[status.find(' ') + 1:] - self.send_response(int(status_code), status_msg) - for header, value in headers: - self.send_header(header, value) - self.end_headers() - self.wsgi_sent_headers = 1 - # Send the data - self.wfile.write(data) - - class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): - def __init__(self, func, server_address): - BaseHTTPServer.HTTPServer.__init__(self, - server_address, - WSGIHandler) - self.app = func - self.serverShuttingDown = 0 - - print "http://%s:%d/" % server_address - WSGIServer(func, server_address).serve_forever() - -def runsimple(func, server_address=("0.0.0.0", 8080)): - """ - Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. - The directory `static/` is hosted statically. - - [cp]: http://www.cherrypy.org - """ - from wsgiserver import CherryPyWSGIServer - from SimpleHTTPServer import SimpleHTTPRequestHandler - from BaseHTTPServer import BaseHTTPRequestHandler - - class StaticApp(SimpleHTTPRequestHandler): - """WSGI application for serving static files.""" - def __init__(self, environ, start_response): - self.headers = [] - self.environ = environ - self.start_response = start_response - - def send_response(self, status, msg=""): - self.status = str(status) + " " + msg - - def send_header(self, name, value): - self.headers.append((name, value)) - - def end_headers(self): - pass - - def log_message(*a): pass - - def __iter__(self): - environ = self.environ - - self.path = environ.get('PATH_INFO', '') - self.client_address = environ.get('REMOTE_ADDR','-'), \ - environ.get('REMOTE_PORT','-') - self.command = environ.get('REQUEST_METHOD', '-') - - from cStringIO import StringIO - self.wfile = StringIO() # for capturing error - - f = self.send_head() - self.start_response(self.status, self.headers) - - if f: - block_size = 16 * 1024 - while True: - buf = f.read(block_size) - if not buf: - break - yield buf - f.close() - else: - value = self.wfile.getvalue() - yield value - - class WSGIWrapper(BaseHTTPRequestHandler): - """WSGI wrapper for logging the status and serving static files.""" - def __init__(self, app): - self.app = app - self.format = '%s - - [%s] "%s %s %s" - %s' - - def __call__(self, environ, start_response): - def xstart_response(status, response_headers, *args): - write = start_response(status, response_headers, *args) - self.log(status, environ) - return write - - path = environ.get('PATH_INFO', '') - if path.startswith('/static/'): - return StaticApp(environ, xstart_response) - else: - return self.app(environ, xstart_response) - - def log(self, status, environ): - #mvoncken,no logging.. - return - - outfile = environ.get('wsgi.errors', web.debug) - req = environ.get('PATH_INFO', '_') - protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-') - method = environ.get('REQUEST_METHOD', '-') - host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), - environ.get('REMOTE_PORT','-')) - - #@@ It is really bad to extend from - #@@ BaseHTTPRequestHandler just for this method - time = self.log_date_time_string() - - print >> outfile, self.format % (host, time, protocol, - method, req, status) - - func = WSGIWrapper(func) - server = CherryPyWSGIServer(server_address, func, server_name="localhost") - - print "http://%s:%d/" % server_address - try: - server.start() - except KeyboardInterrupt: - server.stop() diff --git a/plugins/WebUi/lib/webpy022/net.py b/plugins/WebUi/lib/webpy022/net.py deleted file mode 100644 index b97d4e155..000000000 --- a/plugins/WebUi/lib/webpy022/net.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -Network Utilities -(from web.py) -""" - -__all__ = [ - "validipaddr", "validipport", "validip", "validaddr", - "urlquote", - "httpdate", "parsehttpdate", - "htmlquote", "websafe", -] - -import urllib, time -try: import datetime -except ImportError: pass - -def validipaddr(address): - """returns True if `address` is a valid IPv4 address""" - try: - octets = address.split('.') - assert len(octets) == 4 - for x in octets: - assert 0 <= int(x) <= 255 - except (AssertionError, ValueError): - return False - return True - -def validipport(port): - """returns True if `port` is a valid IPv4 port""" - try: - assert 0 <= int(port) <= 65535 - except (AssertionError, ValueError): - return False - return True - -def validip(ip, defaultaddr="0.0.0.0", defaultport=8080): - """returns `(ip_address, port)` from string `ip_addr_port`""" - addr = defaultaddr - port = defaultport - - ip = ip.split(":", 1) - if len(ip) == 1: - if not ip[0]: - pass - elif validipaddr(ip[0]): - addr = ip[0] - elif validipport(ip[0]): - port = int(ip[0]) - else: - raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' - elif len(ip) == 2: - addr, port = ip - if not validipaddr(addr) and validipport(port): - raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' - port = int(port) - else: - raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' - return (addr, port) - -def validaddr(string_): - """ - returns either (ip_address, port) or "/path/to/socket" from string_ - - >>> validaddr('/path/to/socket') - '/path/to/socket' - >>> validaddr('8000') - ('0.0.0.0', 8000) - >>> validaddr('127.0.0.1') - ('127.0.0.1', 8080) - >>> validaddr('127.0.0.1:8000') - ('127.0.0.1', 8000) - >>> validaddr('fff') - Traceback (most recent call last): - ... - ValueError: fff is not a valid IP address/port - """ - if '/' in string_: - return string_ - else: - return validip(string_) - -def urlquote(val): - """ - Quotes a string for use in a URL. - - >>> urlquote('://?f=1&j=1') - '%3A//%3Ff%3D1%26j%3D1' - >>> urlquote(None) - '' - >>> urlquote(u'\u203d') - '%E2%80%BD' - """ - if val is None: return '' - if not isinstance(val, unicode): val = str(val) - else: val = val.encode('utf-8') - return urllib.quote(val) - -def httpdate(date_obj): - """ - Formats a datetime object for use in HTTP headers. - - >>> import datetime - >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1)) - 'Thu, 01 Jan 1970 01:01:01 GMT' - """ - return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT") - -def parsehttpdate(string_): - """ - Parses an HTTP date into a datetime object. - - >>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT') - datetime.datetime(1970, 1, 1, 1, 1, 1) - """ - try: - t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z") - except ValueError: - return None - return datetime.datetime(*t[:6]) - -def htmlquote(text): - """ - Encodes `text` for raw use in HTML. - - >>> htmlquote("<'&\\">") - '<'&">' - """ - text = text.replace("&", "&") # Must be done first! - text = text.replace("<", "<") - text = text.replace(">", ">") - text = text.replace("'", "'") - text = text.replace('"', """) - return text - -def websafe(val): - """ - Converts `val` so that it's safe for use in UTF-8 HTML. - - >>> websafe("<'&\\">") - '<'&">' - >>> websafe(None) - '' - >>> websafe(u'\u203d') - '\\xe2\\x80\\xbd' - """ - if val is None: - return '' - if isinstance(val, unicode): - val = val.encode('utf-8') - val = str(val) - return htmlquote(val) - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/plugins/WebUi/lib/webpy022/request.py b/plugins/WebUi/lib/webpy022/request.py deleted file mode 100644 index 0826d822a..000000000 --- a/plugins/WebUi/lib/webpy022/request.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Request Delegation -(from web.py) -""" - -__all__ = ["handle", "nomethod", "autodelegate", "webpyfunc", "run"] - -import sys, re, types, os.path, urllib - -import http, wsgi, utils, webapi -import webapi as web - -def handle(mapping, fvars=None): - """ - Call the appropriate function based on the url to function mapping in `mapping`. - If no module for the function is specified, look up the function in `fvars`. If - `fvars` is empty, using the caller's context. - - `mapping` should be a tuple of paired regular expressions with function name - substitutions. `handle` will import modules as necessary. - """ - for url, ofno in utils.group(mapping, 2): - if isinstance(ofno, tuple): - ofn, fna = ofno[0], list(ofno[1:]) - else: - ofn, fna = ofno, [] - fn, result = utils.re_subm('^' + url + '$', ofn, web.ctx.path) - if result: # it's a match - if fn.split(' ', 1)[0] == "redirect": - url = fn.split(' ', 1)[1] - if web.ctx.method == "GET": - x = web.ctx.env.get('QUERY_STRING', '') - if x: - url += '?' + x - return http.redirect(url) - elif '.' in fn: - x = fn.split('.') - mod, cls = '.'.join(x[:-1]), x[-1] - mod = __import__(mod, globals(), locals(), [""]) - cls = getattr(mod, cls) - else: - cls = fn - mod = fvars - if isinstance(mod, types.ModuleType): - mod = vars(mod) - try: - cls = mod[cls] - except KeyError: - return web.notfound() - - meth = web.ctx.method - if meth == "HEAD": - if not hasattr(cls, meth): - meth = "GET" - if not hasattr(cls, meth): - return nomethod(cls) - tocall = getattr(cls(), meth) - args = list(result.groups()) - for d in re.findall(r'\\(\d+)', ofn): - args.pop(int(d) - 1) - return tocall(*([x and urllib.unquote(x) for x in args] + fna)) - - return web.notfound() - -def nomethod(cls): - """Returns a `405 Method Not Allowed` error for `cls`.""" - web.ctx.status = '405 Method Not Allowed' - web.header('Content-Type', 'text/html') - web.header('Allow', \ - ', '.join([method for method in \ - ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'] \ - if hasattr(cls, method)])) - - # commented out for the same reason redirect is - # return output('method not allowed') - -def autodelegate(prefix=''): - """ - Returns a method that takes one argument and calls the method named prefix+arg, - calling `notfound()` if there isn't one. Example: - - urls = ('/prefs/(.*)', 'prefs') - - class prefs: - GET = autodelegate('GET_') - def GET_password(self): pass - def GET_privacy(self): pass - - `GET_password` would get called for `/prefs/password` while `GET_privacy` for - `GET_privacy` gets called for `/prefs/privacy`. - - If a user visits `/prefs/password/change` then `GET_password(self, '/change')` - is called. - """ - def internal(self, arg): - if '/' in arg: - first, rest = arg.split('/', 1) - func = prefix + first - args = ['/' + rest] - else: - func = prefix + arg - args = [] - - if hasattr(self, func): - try: - return getattr(self, func)(*args) - except TypeError: - return web.notfound() - else: - return web.notfound() - return internal - -def webpyfunc(inp, fvars, autoreload=False): - """If `inp` is a url mapping, returns a function that calls handle.""" - if not hasattr(inp, '__call__'): - if autoreload: - def modname(): - """find name of the module name from fvars.""" - file, name = fvars['__file__'], fvars['__name__'] - if name == '__main__': - # Since the __main__ module can't be reloaded, the module has - # to be imported using its file name. - name = os.path.splitext(os.path.basename(file))[0] - return name - - mod = __import__(modname(), None, None, [""]) - #@@probably should replace this with some inspect magic - name = utils.dictfind(fvars, inp) - func = lambda: handle(getattr(mod, name), mod) - else: - func = lambda: handle(inp, fvars) - else: - func = inp - return func - -def run(inp, fvars, *middleware): - """ - Starts handling requests. If called in a CGI or FastCGI context, it will follow - that protocol. If called from the command line, it will start an HTTP - server on the port named in the first command line argument, or, if there - is no argument, on port 8080. - - `input` is a callable, then it's called with no arguments. - Otherwise, it's a `mapping` object to be passed to `handle(...)`. - - **Caveat:** So that `reloader` will work correctly, input has to be a variable, - it can't be a tuple passed in directly. - - `middleware` is a list of WSGI middleware which is applied to the resulting WSGI - function. - """ - autoreload = http.reloader in middleware - return wsgi.runwsgi(webapi.wsgifunc(webpyfunc(inp, fvars, autoreload), *middleware)) diff --git a/plugins/WebUi/lib/webpy022/template.py b/plugins/WebUi/lib/webpy022/template.py deleted file mode 100644 index b21903401..000000000 --- a/plugins/WebUi/lib/webpy022/template.py +++ /dev/null @@ -1,878 +0,0 @@ -""" -simple, elegant templating -(part of web.py) -""" - -import re, glob, os, os.path -from types import FunctionType as function -from utils import storage, group, utf8 -from net import websafe - -# differences from python: -# - for: has an optional else: that gets called if the loop never runs -# differences to add: -# - you can use the expression inside if, while blocks -# - special for loop attributes, like django? -# - you can check to see if a variable is defined (perhaps w/ get func?) -# all these are probably good ideas for python... - -# todo: -# inline tuple -# relax constraints on spacing -# continue, break, etc. -# tracebacks - -global_globals = {'None':None, 'False':False, 'True': True} -MAX_ITERS = 100000 - -WHAT = 0 -ARGS = 4 -KWARGS = 6 -NAME = 2 -BODY = 4 -CLAUSE = 2 -ELIF = 6 -ELSE = 8 -IN = 6 -NAME = 2 -EXPR = 4 -FILTER = 4 -THING = 2 -ATTR = 4 -ITEM = 4 -NEGATE = 4 -X = 2 -OP = 4 -Y = 6 -LINENO = -1 - -# http://docs.python.org/ref/identifiers.html -r_var = '[a-zA-Z_][a-zA-Z0-9_]*' - -class ParseError(Exception): pass -class Parser: - def __init__(self, text, name=""): - self.t = text - self.p = 0 - self._lock = [False] - self.name = name - - def lock(self): - self._lock[-1] = True - - def curline(self): - return self.t[:self.p].count('\n')+1 - - def csome(self): - return repr(self.t[self.p:self.p+5]+'...') - - def Error(self, x, y=None): - if y is None: y = self.csome() - raise ParseError, "%s: expected %s, got %s (line %s)" % (self.name, x, y, self.curline()) - - def q(self, f): - def internal(*a, **kw): - checkp = self.p - self._lock.append(False) - try: - q = f(*a, **kw) - except ParseError: - if self._lock[-1]: - raise - self.p = checkp - self._lock.pop() - return False - self._lock.pop() - return q or True - return internal - - def tokr(self, t): - text = self.c(len(t)) - if text != t: - self.Error(repr(t), repr(text)) - return t - - def ltokr(self, *l): - for x in l: - o = self.tokq(x) - if o: return o - self.Error('one of '+repr(l)) - - def rer(self, r): - x = re.match(r, self.t[self.p:]) #@@re_compile - if not x: - self.Error('r'+repr(r)) - return self.tokr(x.group()) - - def endr(self): - if self.p != len(self.t): - self.Error('EOF') - - def c(self, n=1): - out = self.t[self.p:self.p+n] - if out == '' and n != 0: - self.Error('character', 'EOF') - self.p += n - return out - - def lookbehind(self, t): - return self.t[self.p-len(t):self.p] == t - - def __getattr__(self, a): - if a.endswith('q'): - return self.q(getattr(self, a[:-1]+'r')) - raise AttributeError, a - -class TemplateParser(Parser): - def __init__(self, *a, **kw): - Parser.__init__(self, *a, **kw) - self.curws = '' - self.curind = '' - - def o(self, *a): - return a+('lineno', self.curline()) - - def go(self): - # maybe try to do some traceback parsing/hacking - return self.gor() - - def gor(self): - header = self.defwithq() - results = self.lines(start=True) - self.endr() - return header, results - - def ws(self): - n = 0 - while self.tokq(" "): n += 1 - return " " * n - - def defwithr(self): - self.tokr('$def with ') - self.lock() - self.tokr('(') - args = [] - kw = [] - x = self.req(r_var) - while x: - if self.tokq('='): - v = self.exprr() - kw.append((x, v)) - else: - args.append(x) - x = self.tokq(', ') and self.req(r_var) - self.tokr(')\n') - return self.o('defwith', 'null', None, 'args', args, 'kwargs', kw) - - def literalr(self): - o = ( - self.req('"[^"]*"') or #@@ no support for escapes - self.req("'[^']*'") - ) - if o is False: - o = self.req('\-?[0-9]+(\.[0-9]*)?') - if o is not False: - if '.' in o: o = float(o) - else: o = int(o) - - if o is False: self.Error('literal') - return self.o('literal', 'thing', o) - - def listr(self): - self.tokr('[') - self.lock() - x = [] - if not self.tokq(']'): - while True: - t = self.exprr() - x.append(t) - if not self.tokq(', '): break - self.tokr(']') - return self.o('list', 'thing', x) - - def dictr(self): - self.tokr('{') - self.lock() - x = {} - if not self.tokq('}'): - while True: - k = self.exprr() - self.tokr(': ') - v = self.exprr() - x[k] = v - if not self.tokq(', '): break - self.tokr('}') - return self.o('dict', 'thing', x) - - def parenr(self): - self.tokr('(') - self.lock() - o = self.exprr() # todo: allow list - self.tokr(')') - return self.o('paren', 'thing', o) - - def atomr(self): - """returns var, literal, paren, dict, or list""" - o = ( - self.varq() or - self.parenq() or - self.dictq() or - self.listq() or - self.literalq() - ) - if o is False: self.Error('atom') - return o - - def primaryr(self): - """returns getattr, call, or getitem""" - n = self.atomr() - while 1: - if self.tokq('.'): - v = self.req(r_var) - if not v: - self.p -= 1 # get rid of the '.' - break - else: - n = self.o('getattr', 'thing', n, 'attr', v) - elif self.tokq('('): - args = [] - kw = [] - - while 1: - # need to see if we're doing a keyword argument - checkp = self.p - k = self.req(r_var) - if k and self.tokq('='): # yup - v = self.exprr() - kw.append((k, v)) - else: - self.p = checkp - x = self.exprq() - if x: # at least it's something - args.append(x) - else: - break - - if not self.tokq(', '): break - self.tokr(')') - n = self.o('call', 'thing', n, 'args', args, 'kwargs', kw) - elif self.tokq('['): - v = self.exprr() - self.tokr(']') - n = self.o('getitem', 'thing', n, 'item', v) - else: - break - - return n - - def exprr(self): - negate = self.tokq('not ') - x = self.primaryr() - if self.tokq(' '): - operator = self.ltokr('not in', 'in', 'is not', 'is', '==', '!=', '>=', '<=', '<', '>', 'and', 'or', '*', '+', '-', '/', '%') - self.tokr(' ') - y = self.exprr() - x = self.o('test', 'x', x, 'op', operator, 'y', y) - - return self.o('expr', 'thing', x, 'negate', negate) - - def varr(self): - return self.o('var', 'name', self.rer(r_var)) - - def liner(self): - out = [] - o = self.curws - while 1: - c = self.c() - self.lock() - if c == '\n': - self.p -= 1 - break - if c == '$': - if self.lookbehind('\\$'): - o = o[:-1] + c - else: - filter = not bool(self.tokq(':')) - - if self.tokq('{'): - out.append(o) - out.append(self.o('itpl', 'name', self.exprr(), 'filter', filter)) - self.tokr('}') - o = '' - else: - g = self.primaryq() - if g: - out.append(o) - out.append(self.o('itpl', 'name', g, 'filter', filter)) - o = '' - else: - o += c - else: - o += c - self.tokr('\n') - if not self.lookbehind('\\\n'): - o += '\n' - else: - o = o[:-1] - out.append(o) - return self.o('line', 'thing', out) - - def varsetr(self): - self.tokr('$var ') - self.lock() - what = self.rer(r_var) - self.tokr(':') - body = self.lines() - return self.o('varset', 'name', what, 'body', body) - - def ifr(self): - self.tokr("$if ") - self.lock() - expr = self.exprr() - self.tokr(":") - ifc = self.lines() - - elifs = [] - while self.tokq(self.curws + self.curind + '$elif '): - v = self.exprr() - self.tokr(':') - c = self.lines() - elifs.append(self.o('elif', 'clause', v, 'body', c)) - - if self.tokq(self.curws + self.curind + "$else:"): - elsec = self.lines() - else: - elsec = None - - return self.o('if', 'clause', expr, 'then', ifc, 'elif', elifs, 'else', elsec) - - def forr(self): - self.tokr("$for ") - self.lock() - v = self.setabler() - self.tokr(" in ") - g = self.exprr() - self.tokr(":") - l = self.lines() - - if self.tokq(self.curws + self.curind + '$else:'): - elsec = self.lines() - else: - elsec = None - - return self.o('for', 'name', v, 'body', l, 'in', g, 'else', elsec) - - def whiler(self): - self.tokr('$while ') - self.lock() - v = self.exprr() - self.tokr(":") - l = self.lines() - - if self.tokq(self.curws + self.curind + '$else:'): - elsec = self.lines() - else: - elsec = None - - return self.o('while', 'clause', v, 'body', l, 'null', None, 'else', elsec) - - def assignr(self): - self.tokr('$ ') - assign = self.rer(r_var) # NOTE: setable - self.tokr(' = ') - expr = self.exprr() - self.tokr('\n') - - return self.o('assign', 'name', assign, 'expr', expr) - - def commentr(self): - self.tokr('$#') - self.lock() - while self.c() != '\n': pass - return self.o('comment') - - def setabler(self): - out = [self.varr()] #@@ not quite right - while self.tokq(', '): - out.append(self.varr()) - return out - - def lines(self, start=False): - """ - This function gets called from two places: - 1. at the start, where it's matching the document itself - 2. after any command, where it matches one line or an indented block - """ - o = [] - if not start: # try to match just one line - singleline = self.tokq(' ') and self.lineq() - if singleline: - return [singleline] - else: - self.rer(' *') #@@slurp space? - self.tokr('\n') - oldind = self.curind - self.curind += ' ' - while 1: - oldws = self.curws - t = self.tokq(oldws + self.curind) - if not t: break - - self.curws += self.ws() - x = t and ( - self.varsetq() or - self.ifq() or - self.forq() or - self.whileq() or - self.assignq() or - self.commentq() or - self.lineq()) - self.curws = oldws - if not x: - break - elif x[WHAT] == 'comment': - pass - else: - o.append(x) - - if not start: self.curind = oldind - return o - -class Stowage(storage): - def __str__(self): return self.get('_str') - #@@ edits in place - def __add__(self, other): - if isinstance(other, (unicode, str)): - self._str += other - return self - else: - raise TypeError, 'cannot add' - def __radd__(self, other): - if isinstance(other, (unicode, str)): - self._str = other + self._str - return self - else: - raise TypeError, 'cannot add' - -class WTF(AssertionError): pass -class SecurityError(Exception): - """The template seems to be trying to do something naughty.""" - pass - - - - -Required = object() -class Template: - globals = {} - content_types = { - '.html' : 'text/html; charset=utf-8', - '.txt' : 'text/plain', - } - - def __init__(self, text, filter=None, filename=""): - self.filter = filter - self.filename = filename - # universal newlines: - text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs() - if not text.endswith('\n'): text += '\n' - header, tree = TemplateParser(text, filename).go() - self.tree = tree - if header: - self.h_defwith(header) - else: - self.args, self.kwargs = (), {} - - def __call__(self, *a, **kw): - d = self.globals.copy() - d.update(self._parseargs(a, kw)) - f = Fill(self.tree, d=d) - if self.filter: f.filter = self.filter - - import webapi as web - if 'headers' in web.ctx and self.filename: - content_type = self.find_content_type() - if content_type: - web.header('Content-Type', content_type, unique=True) - - return f.go() - - def find_content_type(self): - for ext, content_type in self.content_types.iteritems(): - if self.filename.endswith(ext): - return content_type - - def _parseargs(self, inargs, inkwargs): - # difference from Python: - # no error on setting a keyword arg twice - d = {} - for arg in self.args: - d[arg] = Required - for kw, val in self.kwargs: - d[kw] = val - - for n, val in enumerate(inargs): - if n < len(self.args): - d[self.args[n]] = val - elif n < len(self.args)+len(self.kwargs): - kw = self.kwargs[n - len(self.args)][0] - d[kw] = val - - for kw, val in inkwargs.iteritems(): - d[kw] = val - - unset = [] - for k, v in d.iteritems(): - if v is Required: - unset.append(k) - if unset: - raise TypeError, 'values for %s are required' % unset - - return d - - def h_defwith(self, header): - assert header[WHAT] == 'defwith' - f = Fill(self.tree, d={}) - - self.args = header[ARGS] - self.kwargs = [] - for var, valexpr in header[KWARGS]: - self.kwargs.append((var, f.h(valexpr))) - - def __repr__(self): - return "" % self.filename - -class Handle: - def __init__(self, parsetree, **kw): - self._funccache = {} - self.parsetree = parsetree - for (k, v) in kw.iteritems(): setattr(self, k, v) - - def h(self, item): - return getattr(self, 'h_' + item[WHAT])(item) - -class Fill(Handle): - builtins = global_globals - def filter(self, text): - if text is None: return '' - else: return utf8(text) - # often replaced with stuff like net.websafe - - def h_literal(self, i): - item = i[THING] - if isinstance(item, (unicode, str)) and item[0] in ['"', "'"]: - item = item[1:-1] - elif isinstance(item, (float, int)): - pass - return item - - def h_list(self, i): - x = i[THING] - out = [] - for item in x: - out.append(self.h(item)) - return out - - def h_dict(self, i): - x = i[THING] - out = {} - for k, v in x.iteritems(): - out[self.h(k)] = self.h(v) - return out - - def h_paren(self, i): - item = i[THING] - if isinstance(item, list): - raise NotImplementedError, 'tuples' - return self.h(item) - - def h_getattr(self, i): - thing, attr = i[THING], i[ATTR] - thing = self.h(thing) - if attr.startswith('_') or attr.startswith('func_') or attr.startswith('im_'): - raise SecurityError, 'tried to get ' + attr - try: - if thing in self.builtins: - raise SecurityError, 'tried to getattr on ' + repr(thing) - except TypeError: - pass # raised when testing an unhashable object - try: - return getattr(thing, attr) - except AttributeError: - if isinstance(thing, list) and attr == 'join': - return lambda s: s.join(thing) - else: - raise - - def h_call(self, i): - call = self.h(i[THING]) - args = [self.h(x) for x in i[ARGS]] - kw = dict([(x, self.h(y)) for (x, y) in i[KWARGS]]) - return call(*args, **kw) - - def h_getitem(self, i): - thing, item = i[THING], i[ITEM] - thing = self.h(thing) - item = self.h(item) - return thing[item] - - def h_expr(self, i): - item = self.h(i[THING]) - if i[NEGATE]: - item = not item - return item - - def h_test(self, item): - ox, op, oy = item[X], item[OP], item[Y] - # for short-circuiting to work, we can't eval these here - e = self.h - if op == 'is': - return e(ox) is e(oy) - elif op == 'is not': - return e(ox) is not e(oy) - elif op == 'in': - return e(ox) in e(oy) - elif op == 'not in': - return e(ox) not in e(oy) - elif op == '==': - return e(ox) == e(oy) - elif op == '!=': - return e(ox) != e(oy) - elif op == '>': - return e(ox) > e(oy) - elif op == '<': - return e(ox) < e(oy) - elif op == '<=': - return e(ox) <= e(oy) - elif op == '>=': - return e(ox) >= e(oy) - elif op == 'and': - return e(ox) and e(oy) - elif op == 'or': - return e(ox) or e(oy) - elif op == '+': - return e(ox) + e(oy) - elif op == '-': - return e(ox) - e(oy) - elif op == '*': - return e(ox) * e(oy) - elif op == '/': - return e(ox) / e(oy) - elif op == '%': - return e(ox) % e(oy) - else: - raise WTF, 'op ' + op - - def h_var(self, i): - v = i[NAME] - if v in self.d: - return self.d[v] - elif v in self.builtins: - return self.builtins[v] - elif v == 'self': - return self.output - else: - raise NameError, 'could not find %s (line %s)' % (repr(i[NAME]), i[LINENO]) - - def h_line(self, i): - out = [] - for x in i[THING]: - #@@ what if x is unicode - if isinstance(x, str): - out.append(x) - elif x[WHAT] == 'itpl': - o = self.h(x[NAME]) - if x[FILTER]: - o = self.filter(o) - else: - o = (o is not None and utf8(o)) or "" - out.append(o) - else: - raise WTF, x - return ''.join(out) - - def h_varset(self, i): - self.output[i[NAME]] = ''.join(self.h_lines(i[BODY])) - return '' - - def h_if(self, i): - expr = self.h(i[CLAUSE]) - if expr: - do = i[BODY] - else: - for e in i[ELIF]: - expr = self.h(e[CLAUSE]) - if expr: - do = e[BODY] - break - else: - do = i[ELSE] - return ''.join(self.h_lines(do)) - - def h_for(self, i): - out = [] - assert i[IN][WHAT] == 'expr' - invar = self.h(i[IN]) - forvar = i[NAME] - if invar: - for nv in invar: - if len(forvar) == 1: - fv = forvar[0] - assert fv[WHAT] == 'var' - self.d[fv[NAME]] = nv # same (lack of) scoping as Python - else: - for x, y in zip(forvar, nv): - assert x[WHAT] == 'var' - self.d[x[NAME]] = y - - out.extend(self.h_lines(i[BODY])) - else: - if i[ELSE]: - out.extend(self.h_lines(i[ELSE])) - return ''.join(out) - - def h_while(self, i): - out = [] - expr = self.h(i[CLAUSE]) - if not expr: - return ''.join(self.h_lines(i[ELSE])) - c = 0 - while expr: - c += 1 - if c >= MAX_ITERS: - raise RuntimeError, 'too many while-loop iterations (line %s)' % i[LINENO] - out.extend(self.h_lines(i[BODY])) - expr = self.h(i[CLAUSE]) - return ''.join(out) - - def h_assign(self, i): - self.d[i[NAME]] = self.h(i[EXPR]) - return '' - - def h_comment(self, i): pass - - def h_lines(self, lines): - if lines is None: return [] - return map(self.h, lines) - - def go(self): - self.output = Stowage() - self.output._str = ''.join(map(self.h, self.parsetree)) - if self.output.keys() == ['_str']: - self.output = self.output['_str'] - return self.output - -class render: - def __init__(self, loc='templates/', cache=True): - self.loc = loc - if cache: - self.cache = {} - else: - self.cache = False - - def _do(self, name, filter=None): - if self.cache is False or name not in self.cache: - - tmplpath = os.path.join(self.loc, name) - p = [f for f in glob.glob(tmplpath + '.*') if not f.endswith('~')] # skip backup files - if not p and os.path.isdir(tmplpath): - return render(tmplpath, cache=self.cache) - elif not p: - raise AttributeError, 'no template named ' + name - - p = p[0] - c = Template(open(p).read(), filename=p) - if self.cache is not False: self.cache[name] = (p, c) - - if self.cache is not False: p, c = self.cache[name] - - if p.endswith('.html') or p.endswith('.xml'): - if not filter: c.filter = websafe - return c - - def __getattr__(self, p): - return self._do(p) - -def frender(fn, *a, **kw): - return Template(open(fn).read(), *a, **kw) - -def test(): - import sys - verbose = '-v' in sys.argv - def assertEqual(a, b): - if a == b: - if verbose: - sys.stderr.write('.') - sys.stderr.flush() - else: - assert a == b, "\nexpected: %s\ngot: %s" % (repr(b), repr(a)) - - from utils import storage, group - - class t: - def __init__(self, text): - self.text = text - - def __call__(self, *a, **kw): - return TestResult(self.text, Template(self.text)(*a, **kw)) - - class TestResult: - def __init__(self, source, value): - self.source = source - self.value = value - - def __eq__(self, other): - if self.value == other: - if verbose: - sys.stderr.write('.') - else: - print >> sys.stderr, 'FAIL:', repr(self.source), 'expected', repr(other), ', got', repr(self.value) - sys.stderr.flush() - - t('1')() == '1\n' - t('$def with ()\n1')() == '1\n' - t('$def with (a)\n$a')(1) == '1\n' - t('$def with (a=0)\n$a')(1) == '1\n' - t('$def with (a=0)\n$a')(a=1) == '1\n' - t('$if 1: 1')() == '1\n' - t('$if 1:\n 1')() == '1\n' - t('$if 0: 0\n$elif 1: 1')() == '1\n' - t('$if 0: 0\n$elif None: 0\n$else: 1')() == '1\n' - t('$if (0 < 1) and (1 < 2): 1')() == '1\n' - t('$for x in [1, 2, 3]: $x')() == '1\n2\n3\n' - t('$for x in []: 0\n$else: 1')() == '1\n' - t('$def with (a)\n$while a and a.pop(): 1')([1, 2, 3]) == '1\n1\n1\n' - t('$while 0: 0\n$else: 1')() == '1\n' - t('$ a = 1\n$a')() == '1\n' - t('$# 0')() == '' - t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1}) == '1\n' - t('$def with (a)\n$(a)')(1) == '1\n' - t('$def with (a)\n$a')(1) == '1\n' - t('$def with (a)\n$a.b')(storage(b=1)) == '1\n' - t('$def with (a)\n$a[0]')([1]) == '1\n' - t('${0 or 1}')() == '1\n' - t('$ a = [1]\n$a[0]')() == '1\n' - t('$ a = {1: 1}\n$a.keys()[0]')() == '1\n' - t('$ a = []\n$if not a: 1')() == '1\n' - t('$ a = {}\n$if not a: 1')() == '1\n' - t('$ a = -1\n$a')() == '-1\n' - t('$ a = "1"\n$a')() == '1\n' - t('$if 1 is 1: 1')() == '1\n' - t('$if not 0: 1')() == '1\n' - t('$if 1:\n $if 1: 1')() == '1\n' - t('$ a = 1\n$a')() == '1\n' - t('$ a = 1.\n$a')() == '1.0\n' - t('$({1: 1}.keys()[0])')() == '1\n' - t('$for x in [1, 2, 3]:\n\t$x')() == ' 1\n 2\n 3\n' - t('$def with (a)\n$:a')(1) == '1\n' - t('$def with (a)\n$a')(u'\u203d') == '\xe2\x80\xbd\n' - t(u'$def with (f)\n$:f("x")')(lambda x: x) == 'x\n' - - j = Template("$var foo: bar")() - assertEqual(str(j), '') - assertEqual(j.foo, 'bar\n') - if verbose: sys.stderr.write('\n') - - -if __name__ == "__main__": - test() diff --git a/plugins/WebUi/lib/webpy022/utils.py b/plugins/WebUi/lib/webpy022/utils.py deleted file mode 100644 index 5b6187583..000000000 --- a/plugins/WebUi/lib/webpy022/utils.py +++ /dev/null @@ -1,796 +0,0 @@ -""" -General Utilities -(part of web.py) -""" - -__all__ = [ - "Storage", "storage", "storify", - "iters", - "rstrips", "lstrips", "strips", "utf8", - "TimeoutError", "timelimit", - "Memoize", "memoize", - "re_compile", "re_subm", - "group", - "IterBetter", "iterbetter", - "dictreverse", "dictfind", "dictfindall", "dictincr", "dictadd", - "listget", "intget", "datestr", - "numify", "denumify", "dateify", - "CaptureStdout", "capturestdout", "Profile", "profile", - "tryall", - "ThreadedDict", - "autoassign", - "to36", - "safemarkdown" -] - -import re, sys, time, threading -try: import datetime -except ImportError: pass - -class Storage(dict): - """ - A Storage object is like a dictionary except `obj.foo` can be used - in addition to `obj['foo']`. - - >>> o = storage(a=1) - >>> o.a - 1 - >>> o['a'] - 1 - >>> o.a = 2 - >>> o['a'] - 2 - >>> del o.a - >>> o.a - Traceback (most recent call last): - ... - AttributeError: 'a' - - """ - def __getattr__(self, key): - try: - return self[key] - except KeyError, k: - raise AttributeError, k - - def __setattr__(self, key, value): - self[key] = value - - def __delattr__(self, key): - try: - del self[key] - except KeyError, k: - raise AttributeError, k - - def __repr__(self): - return '' - -storage = Storage - -def storify(mapping, *requireds, **defaults): - """ - Creates a `storage` object from dictionary `mapping`, raising `KeyError` if - d doesn't have all of the keys in `requireds` and using the default - values for keys found in `defaults`. - - For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of - `storage({'a':1, 'b':2, 'c':3})`. - - If a `storify` value is a list (e.g. multiple values in a form submission), - `storify` returns the last element of the list, unless the key appears in - `defaults` as a list. Thus: - - >>> storify({'a':[1, 2]}).a - 2 - >>> storify({'a':[1, 2]}, a=[]).a - [1, 2] - >>> storify({'a':1}, a=[]).a - [1] - >>> storify({}, a=[]).a - [] - - Similarly, if the value has a `value` attribute, `storify will return _its_ - value, unless the key appears in `defaults` as a dictionary. - - >>> storify({'a':storage(value=1)}).a - 1 - >>> storify({'a':storage(value=1)}, a={}).a - - >>> storify({}, a={}).a - {} - - """ - def getvalue(x): - if hasattr(x, 'value'): - return x.value - else: - return x - - stor = Storage() - for key in requireds + tuple(mapping.keys()): - value = mapping[key] - if isinstance(value, list): - if isinstance(defaults.get(key), list): - value = [getvalue(x) for x in value] - else: - value = value[-1] - if not isinstance(defaults.get(key), dict): - value = getvalue(value) - if isinstance(defaults.get(key), list) and not isinstance(value, list): - value = [value] - setattr(stor, key, value) - - for (key, value) in defaults.iteritems(): - result = value - if hasattr(stor, key): - result = stor[key] - if value == () and not isinstance(result, tuple): - result = (result,) - setattr(stor, key, result) - - return stor - -iters = [list, tuple] -import __builtin__ -if hasattr(__builtin__, 'set'): - iters.append(set) -try: - from sets import Set - iters.append(Set) -except ImportError: - pass - -class _hack(tuple): pass -iters = _hack(iters) -iters.__doc__ = """ -A list of iterable items (like lists, but not strings). Includes whichever -of lists, tuples, sets, and Sets are available in this version of Python. -""" - -def _strips(direction, text, remove): - if direction == 'l': - if text.startswith(remove): - return text[len(remove):] - elif direction == 'r': - if text.endswith(remove): - return text[:-len(remove)] - else: - raise ValueError, "Direction needs to be r or l." - return text - -def rstrips(text, remove): - """ - removes the string `remove` from the right of `text` - - >>> rstrips("foobar", "bar") - 'foo' - - """ - return _strips('r', text, remove) - -def lstrips(text, remove): - """ - removes the string `remove` from the left of `text` - - >>> lstrips("foobar", "foo") - 'bar' - - """ - return _strips('l', text, remove) - -def strips(text, remove): - """removes the string `remove` from the both sides of `text` - - >>> strips("foobarfoo", "foo") - 'bar' - - """ - return rstrips(lstrips(text, remove), remove) - -def utf8(text): - """Encodes text in utf-8. - - >> utf8(u'\u1234') # doctest doesn't seem to like utf-8 - '\xe1\x88\xb4' - - >>> utf8('hello') - 'hello' - >>> utf8(42) - '42' - """ - if isinstance(text, unicode): - return text.encode('utf-8') - elif isinstance(text, str): - return text - else: - return str(text) - -class TimeoutError(Exception): pass -def timelimit(timeout): - """ - A decorator to limit a function to `timeout` seconds, raising `TimeoutError` - if it takes longer. - - >>> import time - >>> def meaningoflife(): - ... time.sleep(.2) - ... return 42 - >>> - >>> timelimit(.1)(meaningoflife)() - Traceback (most recent call last): - ... - TimeoutError: took too long - >>> timelimit(1)(meaningoflife)() - 42 - - _Caveat:_ The function isn't stopped after `timeout` seconds but continues - executing in a separate thread. (There seems to be no way to kill a thread.) - - inspired by - """ - def _1(function): - def _2(*args, **kw): - class Dispatch(threading.Thread): - def __init__(self): - threading.Thread.__init__(self) - self.result = None - self.error = None - - self.setDaemon(True) - self.start() - - def run(self): - try: - self.result = function(*args, **kw) - except: - self.error = sys.exc_info() - - c = Dispatch() - c.join(timeout) - if c.isAlive(): - raise TimeoutError, 'took too long' - if c.error: - raise c.error[0], c.error[1] - return c.result - return _2 - return _1 - -class Memoize: - """ - 'Memoizes' a function, caching its return values for each input. - - >>> import time - >>> def meaningoflife(): - ... time.sleep(.2) - ... return 42 - >>> fastlife = memoize(meaningoflife) - >>> meaningoflife() - 42 - >>> timelimit(.1)(meaningoflife)() - Traceback (most recent call last): - ... - TimeoutError: took too long - >>> fastlife() - 42 - >>> timelimit(.1)(fastlife)() - 42 - - """ - def __init__(self, func): - self.func = func - self.cache = {} - def __call__(self, *args, **keywords): - key = (args, tuple(keywords.items())) - if key not in self.cache: - self.cache[key] = self.func(*args, **keywords) - return self.cache[key] - -memoize = Memoize - -re_compile = memoize(re.compile) #@@ threadsafe? -re_compile.__doc__ = """ -A memoized version of re.compile. -""" - -class _re_subm_proxy: - def __init__(self): - self.match = None - def __call__(self, match): - self.match = match - return '' - -def re_subm(pat, repl, string): - """ - Like re.sub, but returns the replacement _and_ the match object. - - >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball') - >>> t - 'foooooolish' - >>> m.groups() - ('oooooo',) - """ - compiled_pat = re_compile(pat) - proxy = _re_subm_proxy() - compiled_pat.sub(proxy.__call__, string) - return compiled_pat.sub(repl, string), proxy.match - -def group(seq, size): - """ - Returns an iterator over a series of lists of length size from iterable. - - >>> list(group([1,2,3,4], 2)) - [[1, 2], [3, 4]] - """ - if not hasattr(seq, 'next'): - seq = iter(seq) - while True: - yield [seq.next() for i in xrange(size)] - -class IterBetter: - """ - Returns an object that can be used as an iterator - but can also be used via __getitem__ (although it - cannot go backwards -- that is, you cannot request - `iterbetter[0]` after requesting `iterbetter[1]`). - - >>> import itertools - >>> c = iterbetter(itertools.count()) - >>> c[1] - 1 - >>> c[5] - 5 - >>> c[3] - Traceback (most recent call last): - ... - IndexError: already passed 3 - """ - def __init__(self, iterator): - self.i, self.c = iterator, 0 - def __iter__(self): - while 1: - yield self.i.next() - self.c += 1 - def __getitem__(self, i): - #todo: slices - if i < self.c: - raise IndexError, "already passed "+str(i) - try: - while i > self.c: - self.i.next() - self.c += 1 - # now self.c == i - self.c += 1 - return self.i.next() - except StopIteration: - raise IndexError, str(i) -iterbetter = IterBetter - -def dictreverse(mapping): - """ - >>> dictreverse({1: 2, 3: 4}) - {2: 1, 4: 3} - """ - return dict([(value, key) for (key, value) in mapping.iteritems()]) - -def dictfind(dictionary, element): - """ - Returns a key whose value in `dictionary` is `element` - or, if none exists, None. - - >>> d = {1:2, 3:4} - >>> dictfind(d, 4) - 3 - >>> dictfind(d, 5) - """ - for (key, value) in dictionary.iteritems(): - if element is value: - return key - -def dictfindall(dictionary, element): - """ - Returns the keys whose values in `dictionary` are `element` - or, if none exists, []. - - >>> d = {1:4, 3:4} - >>> dictfindall(d, 4) - [1, 3] - >>> dictfindall(d, 5) - [] - """ - res = [] - for (key, value) in dictionary.iteritems(): - if element is value: - res.append(key) - return res - -def dictincr(dictionary, element): - """ - Increments `element` in `dictionary`, - setting it to one if it doesn't exist. - - >>> d = {1:2, 3:4} - >>> dictincr(d, 1) - 3 - >>> d[1] - 3 - >>> dictincr(d, 5) - 1 - >>> d[5] - 1 - """ - dictionary.setdefault(element, 0) - dictionary[element] += 1 - return dictionary[element] - -def dictadd(*dicts): - """ - Returns a dictionary consisting of the keys in the argument dictionaries. - If they share a key, the value from the last argument is used. - - >>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1}) - {1: 0, 2: 1, 3: 1} - """ - result = {} - for dct in dicts: - result.update(dct) - return result - -def listget(lst, ind, default=None): - """ - Returns `lst[ind]` if it exists, `default` otherwise. - - >>> listget(['a'], 0) - 'a' - >>> listget(['a'], 1) - >>> listget(['a'], 1, 'b') - 'b' - """ - if len(lst)-1 < ind: - return default - return lst[ind] - -def intget(integer, default=None): - """ - Returns `integer` as an int or `default` if it can't. - - >>> intget('3') - 3 - >>> intget('3a') - >>> intget('3a', 0) - 0 - """ - try: - return int(integer) - except (TypeError, ValueError): - return default - -def datestr(then, now=None): - """ - Converts a (UTC) datetime object to a nice string representation. - - >>> from datetime import datetime, timedelta - >>> d = datetime(1970, 5, 1) - >>> datestr(d, now=d) - '0 microseconds ago' - >>> for t, v in { - ... timedelta(microseconds=1): '1 microsecond ago', - ... timedelta(microseconds=2): '2 microseconds ago', - ... -timedelta(microseconds=1): '1 microsecond from now', - ... -timedelta(microseconds=2): '2 microseconds from now', - ... timedelta(microseconds=2000): '2 milliseconds ago', - ... timedelta(seconds=2): '2 seconds ago', - ... timedelta(seconds=2*60): '2 minutes ago', - ... timedelta(seconds=2*60*60): '2 hours ago', - ... timedelta(days=2): '2 days ago', - ... }.iteritems(): - ... assert datestr(d, now=d+t) == v - >>> datestr(datetime(1970, 1, 1), now=d) - 'January 1' - >>> datestr(datetime(1969, 1, 1), now=d) - 'January 1, 1969' - >>> datestr(datetime(1970, 6, 1), now=d) - 'June 1, 1970' - """ - def agohence(n, what, divisor=None): - if divisor: n = n // divisor - - out = str(abs(n)) + ' ' + what # '2 day' - if abs(n) != 1: out += 's' # '2 days' - out += ' ' # '2 days ' - if n < 0: - out += 'from now' - else: - out += 'ago' - return out # '2 days ago' - - oneday = 24 * 60 * 60 - - if not now: now = datetime.datetime.utcnow() - if type(now).__name__ == "DateTime": - now = datetime.datetime.fromtimestamp(now) - if type(then).__name__ == "DateTime": - then = datetime.datetime.fromtimestamp(then) - delta = now - then - deltaseconds = int(delta.days * oneday + delta.seconds + delta.microseconds * 1e-06) - deltadays = abs(deltaseconds) // oneday - if deltaseconds < 0: deltadays *= -1 # fix for oddity of floor - - if deltadays: - if abs(deltadays) < 4: - return agohence(deltadays, 'day') - - out = then.strftime('%B %e') # e.g. 'June 13' - if then.year != now.year or deltadays < 0: - out += ', %s' % then.year - return out - - if int(deltaseconds): - if abs(deltaseconds) > (60 * 60): - return agohence(deltaseconds, 'hour', 60 * 60) - elif abs(deltaseconds) > 60: - return agohence(deltaseconds, 'minute', 60) - else: - return agohence(deltaseconds, 'second') - - deltamicroseconds = delta.microseconds - if delta.days: deltamicroseconds = int(delta.microseconds - 1e6) # datetime oddity - if abs(deltamicroseconds) > 1000: - return agohence(deltamicroseconds, 'millisecond', 1000) - - return agohence(deltamicroseconds, 'microsecond') - -def numify(string): - """ - Removes all non-digit characters from `string`. - - >>> numify('800-555-1212') - '8005551212' - >>> numify('800.555.1212') - '8005551212' - - """ - return ''.join([c for c in str(string) if c.isdigit()]) - -def denumify(string, pattern): - """ - Formats `string` according to `pattern`, where the letter X gets replaced - by characters from `string`. - - >>> denumify("8005551212", "(XXX) XXX-XXXX") - '(800) 555-1212' - - """ - out = [] - for c in pattern: - if c == "X": - out.append(string[0]) - string = string[1:] - else: - out.append(c) - return ''.join(out) - -def dateify(datestring): - """ - Formats a numified `datestring` properly. - """ - return denumify(datestring, "XXXX-XX-XX XX:XX:XX") - -class CaptureStdout: - """ - Captures everything `func` prints to stdout and returns it instead. - - >>> def idiot(): - ... print "foo" - >>> capturestdout(idiot)() - 'foo\\n' - - **WARNING:** Not threadsafe! - """ - def __init__(self, func): - self.func = func - def __call__(self, *args, **keywords): - from cStringIO import StringIO - # Not threadsafe! - out = StringIO() - oldstdout = sys.stdout - sys.stdout = out - try: - self.func(*args, **keywords) - finally: - sys.stdout = oldstdout - return out.getvalue() - -capturestdout = CaptureStdout - -class Profile: - """ - Profiles `func` and returns a tuple containing its output - and a string with human-readable profiling information. - - >>> import time - >>> out, inf = profile(time.sleep)(.001) - >>> out - >>> inf[:10].strip() - 'took 0.0' - """ - def __init__(self, func): - self.func = func - def __call__(self, *args): ##, **kw): kw unused - import hotshot, hotshot.stats, tempfile ##, time already imported - temp = tempfile.NamedTemporaryFile() - prof = hotshot.Profile(temp.name) - - stime = time.time() - result = prof.runcall(self.func, *args) - stime = time.time() - stime - - prof.close() - stats = hotshot.stats.load(temp.name) - stats.strip_dirs() - stats.sort_stats('time', 'calls') - x = '\n\ntook '+ str(stime) + ' seconds\n' - x += capturestdout(stats.print_stats)(40) - x += capturestdout(stats.print_callers)() - return result, x - -profile = Profile - - -import traceback -# hack for compatibility with Python 2.3: -if not hasattr(traceback, 'format_exc'): - from cStringIO import StringIO - def format_exc(limit=None): - strbuf = StringIO() - traceback.print_exc(limit, strbuf) - return strbuf.getvalue() - traceback.format_exc = format_exc - -def tryall(context, prefix=None): - """ - Tries a series of functions and prints their results. - `context` is a dictionary mapping names to values; - the value will only be tried if it's callable. - - >>> tryall(dict(j=lambda: True)) - j: True - ---------------------------------------- - results: - True: 1 - - For example, you might have a file `test/stuff.py` - with a series of functions testing various things in it. - At the bottom, have a line: - - if __name__ == "__main__": tryall(globals()) - - Then you can run `python test/stuff.py` and get the results of - all the tests. - """ - context = context.copy() # vars() would update - results = {} - for (key, value) in context.iteritems(): - if not hasattr(value, '__call__'): - continue - if prefix and not key.startswith(prefix): - continue - print key + ':', - try: - r = value() - dictincr(results, r) - print r - except: - print 'ERROR' - dictincr(results, 'ERROR') - print ' ' + '\n '.join(traceback.format_exc().split('\n')) - - print '-'*40 - print 'results:' - for (key, value) in results.iteritems(): - print ' '*2, str(key)+':', value - -class ThreadedDict: - """ - Takes a dictionary that maps threads to objects. - When a thread tries to get or set an attribute or item - of the threadeddict, it passes it on to the object - for that thread in dictionary. - """ - def __init__(self, dictionary): - self.__dict__['_ThreadedDict__d'] = dictionary - - def __getattr__(self, attr): - return getattr(self.__d[threading.currentThread()], attr) - - def __getitem__(self, item): - return self.__d[threading.currentThread()][item] - - def __setattr__(self, attr, value): - if attr == '__doc__': - self.__dict__[attr] = value - else: - return setattr(self.__d[threading.currentThread()], attr, value) - - def __delattr__(self, item): - try: - del self.__d[threading.currentThread()][item] - except KeyError, k: - raise AttributeError, k - - def __delitem__(self, item): - del self.__d[threading.currentThread()][item] - - def __setitem__(self, item, value): - self.__d[threading.currentThread()][item] = value - - def __hash__(self): - return hash(self.__d[threading.currentThread()]) - -threadeddict = ThreadedDict - -def autoassign(self, locals): - """ - Automatically assigns local variables to `self`. - - >>> self = storage() - >>> autoassign(self, dict(a=1, b=2)) - >>> self - - - Generally used in `__init__` methods, as in: - - def __init__(self, foo, bar, baz=1): autoassign(self, locals()) - """ - for (key, value) in locals.iteritems(): - if key == 'self': - continue - setattr(self, key, value) - -def to36(q): - """ - Converts an integer to base 36 (a useful scheme for human-sayable IDs). - - >>> to36(35) - 'z' - >>> to36(119292) - '2k1o' - >>> int(to36(939387374), 36) - 939387374 - >>> to36(0) - '0' - >>> to36(-393) - Traceback (most recent call last): - ... - ValueError: must supply a positive integer - - """ - if q < 0: raise ValueError, "must supply a positive integer" - letters = "0123456789abcdefghijklmnopqrstuvwxyz" - converted = [] - while q != 0: - q, r = divmod(q, 36) - converted.insert(0, letters[r]) - return "".join(converted) or '0' - - -r_url = re_compile('(?', text) - text = markdown(text) - return text - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/plugins/WebUi/lib/webpy022/webapi.py b/plugins/WebUi/lib/webpy022/webapi.py deleted file mode 100644 index 39d3be873..000000000 --- a/plugins/WebUi/lib/webpy022/webapi.py +++ /dev/null @@ -1,369 +0,0 @@ -""" -Web API (wrapper around WSGI) -(from web.py) -""" - -__all__ = [ - "config", - "badrequest", "notfound", "gone", "internalerror", - "header", "output", "flush", "debug", - "input", "data", - "setcookie", "cookies", - "ctx", - "loadhooks", "load", "unloadhooks", "unload", "_loadhooks", - "wsgifunc" -] - -import sys, os, cgi, threading, Cookie, pprint, traceback -try: import itertools -except ImportError: pass -from utils import storage, storify, threadeddict, dictadd, intget, lstrips, utf8 - -config = storage() -config.__doc__ = """ -A configuration object for various aspects of web.py. - -`db_parameters` - : A dictionary containing the parameters to be passed to `connect` - when `load()` is called. -`db_printing` - : Set to `True` if you would like SQL queries and timings to be - printed to the debug output. - -""" - -def badrequest(): - """Return a `400 Bad Request` error.""" - ctx.status = '400 Bad Request' - header('Content-Type', 'text/html') - return output('bad request') - -def notfound(): - """Returns a `404 Not Found` error.""" - ctx.status = '404 Not Found' - header('Content-Type', 'text/html') - return output('not found') - -def gone(): - """Returns a `410 Gone` error.""" - ctx.status = '410 Gone' - header('Content-Type', 'text/html') - return output("gone") - -def internalerror(): - """Returns a `500 Internal Server` error.""" - ctx.status = "500 Internal Server Error" - ctx.headers = [('Content-Type', 'text/html')] - ctx.output = "internal server error" - -def header(hdr, value, unique=False): - """ - Adds the header `hdr: value` with the response. - - If `unique` is True and a header with that name already exists, - it doesn't add a new one. - """ - hdr, value = utf8(hdr), utf8(value) - # protection against HTTP response splitting attack - if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value: - raise ValueError, 'invalid characters in header' - - if unique is True: - for h, v in ctx.headers: - if h.lower() == hdr.lower(): return - - ctx.headers.append((hdr, value)) - -def output(string_): - """Appends `string_` to the response.""" - if isinstance(string_, unicode): string_ = string_.encode('utf8') - if ctx.get('flush'): - ctx._write(string_) - else: - ctx.output += str(string_) - -def flush(): - ctx.flush = True - return flush - -def input(*requireds, **defaults): - """ - Returns a `storage` object with the GET and POST arguments. - See `storify` for how `requireds` and `defaults` work. - """ - from cStringIO import StringIO - def dictify(fs): return dict([(k, fs[k]) for k in fs.keys()]) - - _method = defaults.pop('_method', 'both') - - e = ctx.env.copy() - a = b = {} - - if _method.lower() in ['both', 'post']: - if e['REQUEST_METHOD'] == 'POST': - a = cgi.FieldStorage(fp = StringIO(data()), environ=e, - keep_blank_values=1) - a = dictify(a) - - if _method.lower() in ['both', 'get']: - e['REQUEST_METHOD'] = 'GET' - b = dictify(cgi.FieldStorage(environ=e, keep_blank_values=1)) - - out = dictadd(b, a) - try: - return storify(out, *requireds, **defaults) - except KeyError: - badrequest() - raise StopIteration - -def data(): - """Returns the data sent with the request.""" - if 'data' not in ctx: - cl = intget(ctx.env.get('CONTENT_LENGTH'), 0) - ctx.data = ctx.env['wsgi.input'].read(cl) - return ctx.data - -def setcookie(name, value, expires="", domain=None): - """Sets a cookie.""" - if expires < 0: - expires = -1000000000 - kargs = {'expires': expires, 'path':'/'} - if domain: - kargs['domain'] = domain - # @@ should we limit cookies to a different path? - cookie = Cookie.SimpleCookie() - cookie[name] = value - for key, val in kargs.iteritems(): - cookie[name][key] = val - header('Set-Cookie', cookie.items()[0][1].OutputString()) - -def cookies(*requireds, **defaults): - """ - Returns a `storage` object with all the cookies in it. - See `storify` for how `requireds` and `defaults` work. - """ - cookie = Cookie.SimpleCookie() - cookie.load(ctx.env.get('HTTP_COOKIE', '')) - try: - return storify(cookie, *requireds, **defaults) - except KeyError: - badrequest() - raise StopIteration - -def debug(*args): - """ - Prints a prettyprinted version of `args` to stderr. - """ - try: - out = ctx.environ['wsgi.errors'] - except: - out = sys.stderr - for arg in args: - print >> out, pprint.pformat(arg) - return '' - -def _debugwrite(x): - try: - out = ctx.environ['wsgi.errors'] - except: - out = sys.stderr - out.write(x) -debug.write = _debugwrite - -class _outputter: - """Wraps `sys.stdout` so that print statements go into the response.""" - def __init__(self, file): self.file = file - def write(self, string_): - if hasattr(ctx, 'output'): - return output(string_) - else: - self.file.write(string_) - def __getattr__(self, attr): return getattr(self.file, attr) - def __getitem__(self, item): return self.file[item] - -def _capturedstdout(): - sysstd = sys.stdout - while hasattr(sysstd, 'file'): - if isinstance(sys.stdout, _outputter): return True - sysstd = sysstd.file - if isinstance(sys.stdout, _outputter): return True - return False - -if not _capturedstdout(): - sys.stdout = _outputter(sys.stdout) - -_context = {threading.currentThread(): storage()} -ctx = context = threadeddict(_context) - -ctx.__doc__ = """ -A `storage` object containing various information about the request: - -`environ` (aka `env`) - : A dictionary containing the standard WSGI environment variables. - -`host` - : The domain (`Host` header) requested by the user. - -`home` - : The base path for the application. - -`ip` - : The IP address of the requester. - -`method` - : The HTTP method used. - -`path` - : The path request. - -`query` - : If there are no query arguments, the empty string. Otherwise, a `?` followed - by the query string. - -`fullpath` - : The full path requested, including query arguments (`== path + query`). - -### Response Data - -`status` (default: "200 OK") - : The status code to be used in the response. - -`headers` - : A list of 2-tuples to be used in the response. - -`output` - : A string to be used as the response. -""" - -loadhooks = {} -_loadhooks = {} - -def load(): - """ - Loads a new context for the thread. - - You can ask for a function to be run at loadtime by - adding it to the dictionary `loadhooks`. - """ - _context[threading.currentThread()] = storage() - ctx.status = '200 OK' - ctx.headers = [] - if config.get('db_parameters'): - import db - db.connect(**config.db_parameters) - - for x in loadhooks.values(): x() - -def _load(env): - load() - ctx.output = '' - ctx.environ = ctx.env = env - ctx.host = env.get('HTTP_HOST') - ctx.homedomain = 'http://' + env.get('HTTP_HOST', '[unknown]') - ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', '')) - ctx.home = ctx.homedomain + ctx.homepath - ctx.ip = env.get('REMOTE_ADDR') - ctx.method = env.get('REQUEST_METHOD') - ctx.path = env.get('PATH_INFO') - # http://trac.lighttpd.net/trac/ticket/406 requires: - if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'): - ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], - os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))) - - if env.get('QUERY_STRING'): - ctx.query = '?' + env.get('QUERY_STRING', '') - else: - ctx.query = '' - - ctx.fullpath = ctx.path + ctx.query - for x in _loadhooks.values(): x() - -unloadhooks = {} - -def unload(): - """ - Unloads the context for the thread. - - You can ask for a function to be run at loadtime by - adding it ot the dictionary `unloadhooks`. - """ - for x in unloadhooks.values(): x() - # ensures db cursors and such are GCed promptly - del _context[threading.currentThread()] - -def _unload(): - unload() - -def wsgifunc(func, *middleware): - """Returns a WSGI-compatible function from a webpy-function.""" - middleware = list(middleware) - - def wsgifunc(env, start_resp): - _load(env) - try: - result = func() - except StopIteration: - result = None - except: - print >> debug, traceback.format_exc() - result = internalerror() - - is_generator = result and hasattr(result, 'next') - if is_generator: - # wsgi requires the headers first - # so we need to do an iteration - # and save the result for later - try: - firstchunk = result.next() - except StopIteration: - firstchunk = '' - - status, headers, output = ctx.status, ctx.headers, ctx.output - ctx._write = start_resp(status, headers) - - # and now, the fun: - - def cleanup(): - # we insert this little generator - # at the end of our itertools.chain - # so that it unloads the request - # when everything else is done - - yield '' # force it to be a generator - _unload() - - # result is the output of calling the webpy function - # it could be a generator... - - if is_generator: - if firstchunk is flush: - # oh, it's just our special flush mode - # ctx._write is set up, so just continue execution - try: - result.next() - except StopIteration: - pass - - _unload() - return [] - else: - return itertools.chain([firstchunk], result, cleanup()) - - # ... but it's usually just None - # - # output is the stuff in ctx.output - # it's usually a string... - if isinstance(output, str): #@@ other stringlikes? - _unload() - return [output] - # it could be a generator... - elif hasattr(output, 'next'): - return itertools.chain(output, cleanup()) - else: - _unload() - raise Exception, "Invalid ctx.output" - - for mw_func in middleware: - wsgifunc = mw_func(wsgifunc) - - return wsgifunc diff --git a/plugins/WebUi/lib/webpy022/wsgi.py b/plugins/WebUi/lib/webpy022/wsgi.py deleted file mode 100644 index 26abf9291..000000000 --- a/plugins/WebUi/lib/webpy022/wsgi.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -WSGI Utilities -(from web.py) -""" - -import os, sys - -import http -import webapi as web -from utils import listget -from net import validaddr, validip -import httpserver - -def runfcgi(func, addr=('localhost', 8000)): - """Runs a WSGI function as a FastCGI server.""" - import flup.server.fcgi as flups - return flups.WSGIServer(func, multiplexed=True, bindAddress=addr).run() - -def runscgi(func, addr=('localhost', 4000)): - """Runs a WSGI function as an SCGI server.""" - import flup.server.scgi as flups - return flups.WSGIServer(func, bindAddress=addr).run() - -def runwsgi(func): - """ - Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server, - as appropriate based on context and `sys.argv`. - """ - - if os.environ.has_key('SERVER_SOFTWARE'): # cgi - os.environ['FCGI_FORCE_CGI'] = 'Y' - - if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi - or os.environ.has_key('SERVER_SOFTWARE')): - return runfcgi(func, None) - - if 'fcgi' in sys.argv or 'fastcgi' in sys.argv: - args = sys.argv[1:] - if 'fastcgi' in args: args.remove('fastcgi') - elif 'fcgi' in args: args.remove('fcgi') - if args: - return runfcgi(func, validaddr(args[0])) - else: - return runfcgi(func, None) - - if 'scgi' in sys.argv: - args = sys.argv[1:] - args.remove('scgi') - if args: - return runscgi(func, validaddr(args[0])) - else: - return runscgi(func) - - return httpserver.runsimple(func, validip(listget(sys.argv, 1, ''))) diff --git a/plugins/WebUi/lib/webpy022/wsgiserver/__init__.py b/plugins/WebUi/lib/webpy022/wsgiserver/__init__.py deleted file mode 100644 index 1fe1c71ec..000000000 --- a/plugins/WebUi/lib/webpy022/wsgiserver/__init__.py +++ /dev/null @@ -1,1019 +0,0 @@ -"""A high-speed, production ready, thread pooled, generic WSGI server. - -Simplest example on how to use this module directly -(without using CherryPy's application machinery): - - from cherrypy import wsgiserver - - def my_crazy_app(environ, start_response): - status = '200 OK' - response_headers = [('Content-type','text/plain')] - start_response(status, response_headers) - return ['Hello world!\n'] - - # Here we set our application to the script_name '/' - wsgi_apps = [('/', my_crazy_app)] - - server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps, - server_name='localhost') - - # Want SSL support? Just set these attributes - # server.ssl_certificate = - # server.ssl_private_key = - - if __name__ == '__main__': - try: - server.start() - except KeyboardInterrupt: - server.stop() - -This won't call the CherryPy engine (application side) at all, only the -WSGI server, which is independant from the rest of CherryPy. Don't -let the name "CherryPyWSGIServer" throw you; the name merely reflects -its origin, not it's coupling. - -The CherryPy WSGI server can serve as many WSGI applications -as you want in one instance: - - wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)] - -""" - -import base64 -import Queue -import os -import re -quoted_slash = re.compile("(?i)%2F") -import rfc822 -import socket -try: - import cStringIO as StringIO -except ImportError: - import StringIO -import sys -import threading -import time -import traceback -from urllib import unquote -from urlparse import urlparse - -try: - from OpenSSL import SSL - from OpenSSL import crypto -except ImportError: - SSL = None - -import errno -socket_errors_to_ignore = [] -# Not all of these names will be defined for every platform. -for _ in ("EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET", - "EHOSTDOWN", "EHOSTUNREACH", - "WSAECONNABORTED", "WSAECONNREFUSED", "WSAECONNRESET", - "WSAENETRESET", "WSAETIMEDOUT"): - if _ in dir(errno): - socket_errors_to_ignore.append(getattr(errno, _)) -# de-dupe the list -socket_errors_to_ignore = dict.fromkeys(socket_errors_to_ignore).keys() -socket_errors_to_ignore.append("timed out") - - -comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', - 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL', - 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT', - 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE', - 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING', - 'WWW-AUTHENTICATE'] - -class HTTPRequest(object): - """An HTTP Request (and response). - - A single HTTP connection may consist of multiple request/response pairs. - - connection: the HTTP Connection object which spawned this request. - rfile: the 'read' fileobject from the connection's socket - ready: when True, the request has been parsed and is ready to begin - generating the response. When False, signals the calling Connection - that the response should not be generated and the connection should - close. - close_connection: signals the calling Connection that the request - should close. This does not imply an error! The client and/or - server may each request that the connection be closed. - chunked_write: if True, output will be encoded with the "chunked" - transfer-coding. This value is set automatically inside - send_headers. - """ - - def __init__(self, connection): - self.connection = connection - self.rfile = self.connection.rfile - self.sendall = self.connection.sendall - self.environ = connection.environ.copy() - - self.ready = False - self.started_response = False - self.status = "" - self.outheaders = [] - self.sent_headers = False - self.close_connection = False - self.chunked_write = False - - def parse_request(self): - """Parse the next HTTP request start-line and message-headers.""" - # HTTP/1.1 connections are persistent by default. If a client - # requests a page, then idles (leaves the connection open), - # then rfile.readline() will raise socket.error("timed out"). - # Note that it does this based on the value given to settimeout(), - # and doesn't need the client to request or acknowledge the close - # (although your TCP stack might suffer for it: cf Apache's history - # with FIN_WAIT_2). - request_line = self.rfile.readline() - if not request_line: - # Force self.ready = False so the connection will close. - self.ready = False - return - - if request_line == "\r\n": - # RFC 2616 sec 4.1: "...if the server is reading the protocol - # stream at the beginning of a message and receives a CRLF - # first, it should ignore the CRLF." - # But only ignore one leading line! else we enable a DoS. - request_line = self.rfile.readline() - if not request_line: - self.ready = False - return - - server = self.connection.server - environ = self.environ - environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version - - method, path, req_protocol = request_line.strip().split(" ", 2) - environ["REQUEST_METHOD"] = method - - # path may be an abs_path (including "http://host.domain.tld"); - scheme, location, path, params, qs, frag = urlparse(path) - - if frag: - self.simple_response("400 Bad Request", - "Illegal #fragment in Request-URI.") - return - - if scheme: - environ["wsgi.url_scheme"] = scheme - if params: - path = path + ";" + params - - # Unquote the path+params (e.g. "/this%20path" -> "this path"). - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 - # - # But note that "...a URI must be separated into its components - # before the escaped characters within those components can be - # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 - atoms = [unquote(x) for x in quoted_slash.split(path)] - path = "%2F".join(atoms) - - if path == "*": - # This means, of course, that the last wsgi_app (shortest path) - # will always handle a URI of "*". - environ["SCRIPT_NAME"] = "" - environ["PATH_INFO"] = "*" - self.wsgi_app = server.mount_points[-1][1] - else: - for mount_point, wsgi_app in server.mount_points: - # The mount_points list should be sorted by length, descending. - if path.startswith(mount_point + "/") or path == mount_point: - environ["SCRIPT_NAME"] = mount_point - environ["PATH_INFO"] = path[len(mount_point):] - self.wsgi_app = wsgi_app - break - else: - self.simple_response("404 Not Found") - return - - # Note that, like wsgiref and most other WSGI servers, - # we unquote the path but not the query string. - environ["QUERY_STRING"] = qs - - # Compare request and server HTTP protocol versions, in case our - # server does not support the requested protocol. Limit our output - # to min(req, server). We want the following output: - # request server actual written supported response - # protocol protocol response protocol feature set - # a 1.0 1.0 1.0 1.0 - # b 1.0 1.1 1.1 1.0 - # c 1.1 1.0 1.0 1.0 - # d 1.1 1.1 1.1 1.1 - # Notice that, in (b), the response will be "HTTP/1.1" even though - # the client only understands 1.0. RFC 2616 10.5.6 says we should - # only return 505 if the _major_ version is different. - rp = int(req_protocol[5]), int(req_protocol[7]) - sp = int(server.protocol[5]), int(server.protocol[7]) - if sp[0] != rp[0]: - self.simple_response("505 HTTP Version Not Supported") - return - # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. - environ["SERVER_PROTOCOL"] = req_protocol - # set a non-standard environ entry so the WSGI app can know what - # the *real* server protocol is (and what features to support). - # See http://www.faqs.org/rfcs/rfc2145.html. - environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol - self.response_protocol = "HTTP/%s.%s" % min(rp, sp) - - # If the Request-URI was an absoluteURI, use its location atom. - if location: - environ["SERVER_NAME"] = location - - # then all the http headers - try: - self.read_headers() - except ValueError, ex: - self.simple_response("400 Bad Request", repr(ex.args)) - return - - creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1) - environ["AUTH_TYPE"] = creds[0] - if creds[0].lower() == 'basic': - user, pw = base64.decodestring(creds[1]).split(":", 1) - environ["REMOTE_USER"] = user - - # Persistent connection support - if self.response_protocol == "HTTP/1.1": - if environ.get("HTTP_CONNECTION", "") == "close": - self.close_connection = True - else: - # HTTP/1.0 - if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": - self.close_connection = True - - # Transfer-Encoding support - te = None - if self.response_protocol == "HTTP/1.1": - te = environ.get("HTTP_TRANSFER_ENCODING") - if te: - te = [x.strip().lower() for x in te.split(",") if x.strip()] - - read_chunked = False - - if te: - for enc in te: - if enc == "chunked": - read_chunked = True - else: - # Note that, even if we see "chunked", we must reject - # if there is an extension we don't recognize. - self.simple_response("501 Unimplemented") - self.close_connection = True - return - - if read_chunked: - if not self.decode_chunked(): - return - - # From PEP 333: - # "Servers and gateways that implement HTTP 1.1 must provide - # transparent support for HTTP 1.1's "expect/continue" mechanism. - # This may be done in any of several ways: - # 1. Respond to requests containing an Expect: 100-continue request - # with an immediate "100 Continue" response, and proceed normally. - # 2. Proceed with the request normally, but provide the application - # with a wsgi.input stream that will send the "100 Continue" - # response if/when the application first attempts to read from - # the input stream. The read request must then remain blocked - # until the client responds. - # 3. Wait until the client decides that the server does not support - # expect/continue, and sends the request body on its own. - # (This is suboptimal, and is not recommended.) - # - # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, - # but it seems like it would be a big slowdown for such a rare case. - if environ.get("HTTP_EXPECT", "") == "100-continue": - self.simple_response(100) - - self.ready = True - - def read_headers(self): - """Read header lines from the incoming stream.""" - environ = self.environ - - while True: - line = self.rfile.readline() - if not line: - # No more data--illegal end of headers - raise ValueError("Illegal end of headers.") - - if line == '\r\n': - # Normal end of headers - break - - if line[0] in ' \t': - # It's a continuation line. - v = line.strip() - else: - k, v = line.split(":", 1) - k, v = k.strip().upper(), v.strip() - envname = "HTTP_" + k.replace("-", "_") - - if k in comma_separated_headers: - existing = environ.get(envname) - if existing: - v = ", ".join((existing, v)) - environ[envname] = v - - ct = environ.pop("HTTP_CONTENT_TYPE", None) - if ct: - environ["CONTENT_TYPE"] = ct - cl = environ.pop("HTTP_CONTENT_LENGTH", None) - if cl: - environ["CONTENT_LENGTH"] = cl - - def decode_chunked(self): - """Decode the 'chunked' transfer coding.""" - cl = 0 - data = StringIO.StringIO() - while True: - line = self.rfile.readline().strip().split(";", 1) - chunk_size = int(line.pop(0), 16) - if chunk_size <= 0: - break -## if line: chunk_extension = line[0] - cl += chunk_size - data.write(self.rfile.read(chunk_size)) - crlf = self.rfile.read(2) - if crlf != "\r\n": - self.simple_response("400 Bad Request", - "Bad chunked transfer coding " - "(expected '\\r\\n', got %r)" % crlf) - return - - # Grab any trailer headers - self.read_headers() - - data.seek(0) - self.environ["wsgi.input"] = data - self.environ["CONTENT_LENGTH"] = str(cl) or "" - return True - - def respond(self): - """Call the appropriate WSGI app and write its iterable output.""" - response = self.wsgi_app(self.environ, self.start_response) - try: - for chunk in response: - # "The start_response callable must not actually transmit - # the response headers. Instead, it must store them for the - # server or gateway to transmit only after the first - # iteration of the application return value that yields - # a NON-EMPTY string, or upon the application's first - # invocation of the write() callable." (PEP 333) - if chunk: - self.write(chunk) - finally: - if hasattr(response, "close"): - response.close() - if (self.ready and not self.sent_headers - and not self.connection.server.interrupt): - self.sent_headers = True - self.send_headers() - if self.chunked_write: - self.sendall("0\r\n\r\n") - - def simple_response(self, status, msg=""): - """Write a simple response back to the client.""" - status = str(status) - buf = ["%s %s\r\n" % (self.connection.server.protocol, status), - "Content-Length: %s\r\n" % len(msg)] - - if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': - # Request Entity Too Large - self.close_connection = True - buf.append("Connection: close\r\n") - - buf.append("\r\n") - if msg: - buf.append(msg) - self.sendall("".join(buf)) - - def start_response(self, status, headers, exc_info = None): - """WSGI callable to begin the HTTP response.""" - if self.started_response: - if not exc_info: - raise AssertionError("WSGI start_response called a second " - "time with no exc_info.") - else: - try: - raise exc_info[0], exc_info[1], exc_info[2] - finally: - exc_info = None - self.started_response = True - self.status = status - self.outheaders.extend(headers) - return self.write - - def write(self, chunk): - """WSGI callable to write unbuffered data to the client. - - This method is also used internally by start_response (to write - data from the iterable returned by the WSGI application). - """ - if not self.started_response: - raise AssertionError("WSGI write called before start_response.") - - if not self.sent_headers: - self.sent_headers = True - self.send_headers() - - if self.chunked_write and chunk: - buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"] - self.sendall("".join(buf)) - else: - self.sendall(chunk) - - def send_headers(self): - """Assert, process, and send the HTTP response message-headers.""" - hkeys = [key.lower() for key, value in self.outheaders] - status = int(self.status[:3]) - - if status == 413: - # Request Entity Too Large. Close conn to avoid garbage. - self.close_connection = True - elif "content-length" not in hkeys: - # "All 1xx (informational), 204 (no content), - # and 304 (not modified) responses MUST NOT - # include a message-body." So no point chunking. - if status < 200 or status in (204, 205, 304): - pass - else: - if self.response_protocol == 'HTTP/1.1': - # Use the chunked transfer-coding - self.chunked_write = True - self.outheaders.append(("Transfer-Encoding", "chunked")) - else: - # Closing the conn is the only way to determine len. - self.close_connection = True - - if "connection" not in hkeys: - if self.response_protocol == 'HTTP/1.1': - if self.close_connection: - self.outheaders.append(("Connection", "close")) - else: - if not self.close_connection: - self.outheaders.append(("Connection", "Keep-Alive")) - - if "date" not in hkeys: - self.outheaders.append(("Date", rfc822.formatdate())) - - server = self.connection.server - - if "server" not in hkeys: - self.outheaders.append(("Server", server.version)) - - buf = [server.protocol, " ", self.status, "\r\n"] - try: - buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] - except TypeError: - if not isinstance(k, str): - raise TypeError("WSGI response header key %r is not a string.") - if not isinstance(v, str): - raise TypeError("WSGI response header value %r is not a string.") - else: - raise - buf.append("\r\n") - self.sendall("".join(buf)) - - -class NoSSLError(Exception): - """Exception raised when a client speaks HTTP to an HTTPS socket.""" - pass - - -def _ssl_wrap_method(method, is_reader=False): - """Wrap the given method with SSL error-trapping. - - is_reader: if False (the default), EOF errors will be raised. - If True, EOF errors will return "" (to emulate normal sockets). - """ - def ssl_method_wrapper(self, *args, **kwargs): -## print (id(self), method, args, kwargs) - start = time.time() - while True: - try: - return method(self, *args, **kwargs) - except (SSL.WantReadError, SSL.WantWriteError): - # Sleep and try again. This is dangerous, because it means - # the rest of the stack has no way of differentiating - # between a "new handshake" error and "client dropped". - # Note this isn't an endless loop: there's a timeout below. - time.sleep(self.ssl_retry) - except SSL.SysCallError, e: - if is_reader and e.args == (-1, 'Unexpected EOF'): - return "" - - errno = e.args[0] - if is_reader and errno in socket_errors_to_ignore: - return "" - raise socket.error(errno) - except SSL.Error, e: - if is_reader and e.args == (-1, 'Unexpected EOF'): - return "" - - thirdarg = None - try: - thirdarg = e.args[0][0][2] - except IndexError: - pass - - if is_reader and thirdarg == 'ssl handshake failure': - return "" - if thirdarg == 'http request': - # The client is talking HTTP to an HTTPS server. - raise NoSSLError() - raise - if time.time() - start > self.ssl_timeout: - raise socket.timeout("timed out") - return ssl_method_wrapper - -class SSL_fileobject(socket._fileobject): - """Faux file object attached to a socket object.""" - - ssl_timeout = 3 - ssl_retry = .01 - - close = _ssl_wrap_method(socket._fileobject.close) - flush = _ssl_wrap_method(socket._fileobject.flush) - write = _ssl_wrap_method(socket._fileobject.write) - writelines = _ssl_wrap_method(socket._fileobject.writelines) - read = _ssl_wrap_method(socket._fileobject.read, is_reader=True) - readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True) - readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True) - - -class HTTPConnection(object): - """An HTTP connection (active socket). - - socket: the raw socket object (usually TCP) for this connection. - addr: the "bind address" for the remote end of the socket. - For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT). - For UNIX domain sockets, this will be a string. - server: the HTTP Server for this Connection. Usually, the server - object possesses a passive (server) socket which spawns multiple, - active (client) sockets, one for each connection. - - environ: a WSGI environ template. This will be copied for each request. - rfile: a fileobject for reading from the socket. - sendall: a function for writing (+ flush) to the socket. - """ - - rbufsize = -1 - RequestHandlerClass = HTTPRequest - environ = {"wsgi.version": (1, 0), - "wsgi.url_scheme": "http", - "wsgi.multithread": True, - "wsgi.multiprocess": False, - "wsgi.run_once": False, - "wsgi.errors": sys.stderr, - } - - def __init__(self, sock, addr, server): - self.socket = sock - self.addr = addr - self.server = server - - # Copy the class environ into self. - self.environ = self.environ.copy() - - if SSL and isinstance(sock, SSL.ConnectionType): - timeout = sock.gettimeout() - self.rfile = SSL_fileobject(sock, "r", self.rbufsize) - self.rfile.ssl_timeout = timeout - self.sendall = _ssl_wrap_method(sock.sendall) - self.environ["wsgi.url_scheme"] = "https" - self.environ["HTTPS"] = "on" - sslenv = getattr(server, "ssl_environ", None) - if sslenv: - self.environ.update(sslenv) - else: - self.rfile = sock.makefile("r", self.rbufsize) - self.sendall = sock.sendall - - self.environ.update({"wsgi.input": self.rfile, - "SERVER_NAME": self.server.server_name, - }) - - if isinstance(self.server.bind_addr, basestring): - # AF_UNIX. This isn't really allowed by WSGI, which doesn't - # address unix domain sockets. But it's better than nothing. - self.environ["SERVER_PORT"] = "" - else: - self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) - # optional values - # Until we do DNS lookups, omit REMOTE_HOST - self.environ["REMOTE_ADDR"] = self.addr[0] - self.environ["REMOTE_PORT"] = str(self.addr[1]) - - def communicate(self): - """Read each request and respond appropriately.""" - try: - while True: - # (re)set req to None so that if something goes wrong in - # the RequestHandlerClass constructor, the error doesn't - # get written to the previous request. - req = None - req = self.RequestHandlerClass(self) - # This order of operations should guarantee correct pipelining. - req.parse_request() - if not req.ready: - return - req.respond() - if req.close_connection: - return - except socket.error, e: - errno = e.args[0] - if errno not in socket_errors_to_ignore: - if req: - req.simple_response("500 Internal Server Error", - format_exc()) - return - except (KeyboardInterrupt, SystemExit): - raise - except NoSSLError: - # Unwrap our sendall - req.sendall = self.socket._sock.sendall - req.simple_response("400 Bad Request", - "The client sent a plain HTTP request, but " - "this server only speaks HTTPS on this port.") - except: - if req: - req.simple_response("500 Internal Server Error", format_exc()) - - def close(self): - """Close the socket underlying this connection.""" - self.rfile.close() - self.socket.close() - - -def format_exc(limit=None): - """Like print_exc() but return a string. Backport for Python 2.3.""" - try: - etype, value, tb = sys.exc_info() - return ''.join(traceback.format_exception(etype, value, tb, limit)) - finally: - etype = value = tb = None - - -_SHUTDOWNREQUEST = None - -class WorkerThread(threading.Thread): - """Thread which continuously polls a Queue for Connection objects. - - server: the HTTP Server which spawned this thread, and which owns the - Queue and is placing active connections into it. - ready: a simple flag for the calling server to know when this thread - has begun polling the Queue. - - Due to the timing issues of polling a Queue, a WorkerThread does not - check its own 'ready' flag after it has started. To stop the thread, - it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue - (one for each running WorkerThread). - """ - - def __init__(self, server): - self.ready = False - self.server = server - threading.Thread.__init__(self) - - def run(self): - try: - self.ready = True - while True: - conn = self.server.requests.get() - if conn is _SHUTDOWNREQUEST: - return - - try: - conn.communicate() - finally: - conn.close() - except (KeyboardInterrupt, SystemExit), exc: - self.server.interrupt = exc - - -class SSLConnection: - """A thread-safe wrapper for an SSL.Connection. - - *args: the arguments to create the wrapped SSL.Connection(*args). - """ - - def __init__(self, *args): - self._ssl_conn = SSL.Connection(*args) - self._lock = threading.RLock() - - for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', - 'renegotiate', 'bind', 'listen', 'connect', 'accept', - 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', - 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', - 'makefile', 'get_app_data', 'set_app_data', 'state_string', - 'sock_shutdown', 'get_peer_certificate', 'want_read', - 'want_write', 'set_connect_state', 'set_accept_state', - 'connect_ex', 'sendall', 'settimeout'): - exec """def %s(self, *args): - self._lock.acquire() - try: - return self._ssl_conn.%s(*args) - finally: - self._lock.release() -""" % (f, f) - - -class CherryPyWSGIServer(object): - """An HTTP server for WSGI. - - bind_addr: a (host, port) tuple if TCP sockets are desired; - for UNIX sockets, supply the filename as a string. - wsgi_app: the WSGI 'application callable'; multiple WSGI applications - may be passed as (script_name, callable) pairs. - numthreads: the number of worker threads to create (default 10). - server_name: the string to set for WSGI's SERVER_NAME environ entry. - Defaults to socket.gethostname(). - max: the maximum number of queued requests (defaults to -1 = no limit). - request_queue_size: the 'backlog' argument to socket.listen(); - specifies the maximum number of queued connections (default 5). - timeout: the timeout in seconds for accepted connections (default 10). - - protocol: the version string to write in the Status-Line of all - HTTP responses. For example, "HTTP/1.1" (the default). This - also limits the supported features used in the response. - - - SSL/HTTPS - --------- - The OpenSSL module must be importable for SSL functionality. - You can obtain it from http://pyopenssl.sourceforge.net/ - - ssl_certificate: the filename of the server SSL certificate. - ssl_privatekey: the filename of the server's private key file. - - If either of these is None (both are None by default), this server - will not use SSL. If both are given and are valid, they will be read - on server start and used in the SSL context for the listening socket. - """ - - protocol = "HTTP/1.1" - version = "CherryPy/3.0.1" - ready = False - _interrupt = None - ConnectionClass = HTTPConnection - - # Paths to certificate and private key files - ssl_certificate = None - ssl_private_key = None - - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, - max=-1, request_queue_size=5, timeout=10): - self.requests = Queue.Queue(max) - - if callable(wsgi_app): - # We've been handed a single wsgi_app, in CP-2.1 style. - # Assume it's mounted at "". - self.mount_points = [("", wsgi_app)] - else: - # We've been handed a list of (mount_point, wsgi_app) tuples, - # so that the server can call different wsgi_apps, and also - # correctly set SCRIPT_NAME. - self.mount_points = wsgi_app - self.mount_points.sort() - self.mount_points.reverse() - - self.bind_addr = bind_addr - self.numthreads = numthreads or 1 - if not server_name: - server_name = socket.gethostname() - self.server_name = server_name - self.request_queue_size = request_queue_size - self._workerThreads = [] - - self.timeout = timeout - - def start(self): - """Run the server forever.""" - # We don't have to trap KeyboardInterrupt or SystemExit here, - # because cherrpy.server already does so, calling self.stop() for us. - # If you're using this server with another framework, you should - # trap those exceptions in whatever code block calls start(). - self._interrupt = None - - # Select the appropriate socket - if isinstance(self.bind_addr, basestring): - # AF_UNIX socket - - # So we can reuse the socket... - try: os.unlink(self.bind_addr) - except: pass - - # So everyone can access the socket... - try: os.chmod(self.bind_addr, 0777) - except: pass - - info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] - else: - # AF_INET or AF_INET6 socket - # Get the correct address family for our host (allows IPv6 addresses) - host, port = self.bind_addr - flags = 0 - if host == '': - # Despite the socket module docs, using '' does not - # allow AI_PASSIVE to work. Passing None instead - # returns '0.0.0.0' like we want. In other words: - # host AI_PASSIVE result - # '' Y 192.168.x.y - # '' N 192.168.x.y - # None Y 0.0.0.0 - # None N 127.0.0.1 - host = None - flags = socket.AI_PASSIVE - try: - info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, flags) - except socket.gaierror: - # Probably a DNS issue. Assume IPv4. - info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)] - - self.socket = None - msg = "No socket could be created" - for res in info: - af, socktype, proto, canonname, sa = res - try: - self.bind(af, socktype, proto) - except socket.error, msg: - if self.socket: - self.socket.close() - self.socket = None - continue - break - if not self.socket: - raise socket.error, msg - - # Timeout so KeyboardInterrupt can be caught on Win32 - self.socket.settimeout(1) - self.socket.listen(self.request_queue_size) - - # Create worker threads - for i in xrange(self.numthreads): - self._workerThreads.append(WorkerThread(self)) - for worker in self._workerThreads: - worker.setName("CP WSGIServer " + worker.getName()) - worker.start() - for worker in self._workerThreads: - while not worker.ready: - time.sleep(.1) - - self.ready = True - while self.ready: - self.tick() - if self.interrupt: - while self.interrupt is True: - # Wait for self.stop() to complete. See _set_interrupt. - time.sleep(0.1) - raise self.interrupt - - def bind(self, family, type, proto=0): - """Create (or recreate) the actual socket object.""" - self.socket = socket.socket(family, type, proto) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -## self.socket.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) - if self.ssl_certificate and self.ssl_private_key: - if SSL is None: - raise ImportError("You must install pyOpenSSL to use HTTPS.") - - # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.use_privatekey_file(self.ssl_private_key) - ctx.use_certificate_file(self.ssl_certificate) - self.socket = SSLConnection(ctx, self.socket) - self.populate_ssl_environ() - self.socket.bind(self.bind_addr) - - def tick(self): - """Accept a new connection and put it on the Queue.""" - try: - s, addr = self.socket.accept() - if not self.ready: - return - if hasattr(s, 'settimeout'): - s.settimeout(self.timeout) - conn = self.ConnectionClass(s, addr, self) - self.requests.put(conn) - except socket.timeout: - # The only reason for the timeout in start() is so we can - # notice keyboard interrupts on Win32, which don't interrupt - # accept() by default - return - except socket.error, x: - msg = x.args[1] - if msg in ("Bad file descriptor", "Socket operation on non-socket"): - # Our socket was closed. - return - if msg == "Resource temporarily unavailable": - # Just try again. See http://www.cherrypy.org/ticket/479. - return - raise - - def _get_interrupt(self): - return self._interrupt - def _set_interrupt(self, interrupt): - self._interrupt = True - self.stop() - self._interrupt = interrupt - interrupt = property(_get_interrupt, _set_interrupt, - doc="Set this to an Exception instance to " - "interrupt the server.") - - def stop(self): - """Gracefully shutdown a server that is serving forever.""" - self.ready = False - - sock = getattr(self, "socket", None) - if sock: - if not isinstance(self.bind_addr, basestring): - # Touch our own socket to make accept() return immediately. - try: - host, port = sock.getsockname()[:2] - except socket.error, x: - if x.args[1] != "Bad file descriptor": - raise - else: - # Note that we're explicitly NOT using AI_PASSIVE, - # here, because we want an actual IP to touch. - # localhost won't work if we've bound to a public IP, - # but it would if we bound to INADDR_ANY via host = ''. - for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - s = None - try: - s = socket.socket(af, socktype, proto) - # See http://groups.google.com/group/cherrypy-users/ - # browse_frm/thread/bbfe5eb39c904fe0 - s.settimeout(1.0) - s.connect((host, port)) - s.close() - except socket.error: - if s: - s.close() - if hasattr(sock, "close"): - sock.close() - self.socket = None - - # Must shut down threads here so the code that calls - # this method can know when all threads are stopped. - for worker in self._workerThreads: - self.requests.put(_SHUTDOWNREQUEST) - - # Don't join currentThread (when stop is called inside a request). - current = threading.currentThread() - while self._workerThreads: - worker = self._workerThreads.pop() - if worker is not current and worker.isAlive: - try: - worker.join() - except AssertionError: - pass - - def populate_ssl_environ(self): - """Create WSGI environ entries to be merged into each request.""" - cert = open(self.ssl_certificate).read() - cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert) - self.ssl_environ = { - # pyOpenSSL doesn't provide access to any of these AFAICT -## 'SSL_PROTOCOL': 'SSLv2', -## SSL_CIPHER string The cipher specification name -## SSL_VERSION_INTERFACE string The mod_ssl program version -## SSL_VERSION_LIBRARY string The OpenSSL program version - } - - # Server certificate attributes - self.ssl_environ.update({ - 'SSL_SERVER_M_VERSION': cert.get_version(), - 'SSL_SERVER_M_SERIAL': cert.get_serial_number(), -## 'SSL_SERVER_V_START': Validity of server's certificate (start time), -## 'SSL_SERVER_V_END': Validity of server's certificate (end time), - }) - - for prefix, dn in [("I", cert.get_issuer()), - ("S", cert.get_subject())]: - # X509Name objects don't seem to have a way to get the - # complete DN string. Use str() and slice it instead, - # because str(dn) == "" - dnstr = str(dn)[18:-2] - - wsgikey = 'SSL_SERVER_%s_DN' % prefix - self.ssl_environ[wsgikey] = dnstr - - # The DN should be of the form: /k1=v1/k2=v2, but we must allow - # for any value to contain slashes itself (in a URL). - while dnstr: - pos = dnstr.rfind("=") - dnstr, value = dnstr[:pos], dnstr[pos + 1:] - pos = dnstr.rfind("/") - dnstr, key = dnstr[:pos], dnstr[pos + 1:] - if key and value: - wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) - self.ssl_environ[wsgikey] = value - diff --git a/plugins/WebUi/run_webserver b/plugins/WebUi/run_webserver deleted file mode 100755 index b8e9b47f5..000000000 --- a/plugins/WebUi/run_webserver +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python -import deluge_webserver -deluge_webserver.run() diff --git a/plugins/WebUi/scripts/add_torrent_to_deluge_webui b/plugins/WebUi/scripts/add_torrent_to_deluge_webui deleted file mode 100755 index 740857ccb..000000000 --- a/plugins/WebUi/scripts/add_torrent_to_deluge_webui +++ /dev/null @@ -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 - diff --git a/plugins/WebUi/scripts/add_torrents_to_deluge.user.js b/plugins/WebUi/scripts/add_torrents_to_deluge.user.js deleted file mode 100644 index ca6c59efc..000000000 --- a/plugins/WebUi/scripts/add_torrents_to_deluge.user.js +++ /dev/null @@ -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 = ""; - 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 = ""; - 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 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 '*.*~' \ No newline at end of file diff --git a/plugins/WebUi/scripts/curl-example b/plugins/WebUi/scripts/curl-example deleted file mode 100644 index f6fb0821a..000000000 --- a/plugins/WebUi/scripts/curl-example +++ /dev/null @@ -1 +0,0 @@ -curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add diff --git a/plugins/WebUi/scripts/extract_template_strings.py b/plugins/WebUi/scripts/extract_template_strings.py deleted file mode 100644 index b1a894b31..000000000 --- a/plugins/WebUi/scripts/extract_template_strings.py +++ /dev/null @@ -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 ) - diff --git a/plugins/WebUi/scripts/template_strings.py b/plugins/WebUi/scripts/template_strings.py deleted file mode 100644 index 925db255d..000000000 --- a/plugins/WebUi/scripts/template_strings.py +++ /dev/null @@ -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') diff --git a/plugins/WebUi/ssl/deluge.key b/plugins/WebUi/ssl/deluge.key deleted file mode 100644 index a9d5db5ce..000000000 --- a/plugins/WebUi/ssl/deluge.key +++ /dev/null @@ -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----- diff --git a/plugins/WebUi/ssl/deluge.pem b/plugins/WebUi/ssl/deluge.pem deleted file mode 100644 index effef476e..000000000 --- a/plugins/WebUi/ssl/deluge.pem +++ /dev/null @@ -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----- diff --git a/plugins/WebUi/static/images/deluge_icon.gif b/plugins/WebUi/static/images/deluge_icon.gif deleted file mode 100644 index f00149312..000000000 Binary files a/plugins/WebUi/static/images/deluge_icon.gif and /dev/null differ diff --git a/plugins/WebUi/static/images/downloading16.png b/plugins/WebUi/static/images/downloading16.png deleted file mode 100644 index 53a2b6e61..000000000 Binary files a/plugins/WebUi/static/images/downloading16.png and /dev/null differ diff --git a/plugins/WebUi/static/images/inactive16.png b/plugins/WebUi/static/images/inactive16.png deleted file mode 100644 index 10342be18..000000000 Binary files a/plugins/WebUi/static/images/inactive16.png and /dev/null differ diff --git a/plugins/WebUi/static/images/seeding16.png b/plugins/WebUi/static/images/seeding16.png deleted file mode 100644 index 6994323de..000000000 Binary files a/plugins/WebUi/static/images/seeding16.png and /dev/null differ diff --git a/plugins/WebUi/static/images/simple_bg.jpg b/plugins/WebUi/static/images/simple_bg.jpg deleted file mode 100755 index 7371fd9a0..000000000 Binary files a/plugins/WebUi/static/images/simple_bg.jpg and /dev/null differ diff --git a/plugins/WebUi/static/images/simple_line.jpg b/plugins/WebUi/static/images/simple_line.jpg deleted file mode 100755 index 9054b9816..000000000 Binary files a/plugins/WebUi/static/images/simple_line.jpg and /dev/null differ diff --git a/plugins/WebUi/static/images/simple_logo.jpg b/plugins/WebUi/static/images/simple_logo.jpg deleted file mode 100755 index 7ddc33455..000000000 Binary files a/plugins/WebUi/static/images/simple_logo.jpg and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/details.png b/plugins/WebUi/static/images/tango/details.png deleted file mode 100644 index 8dd48c494..000000000 Binary files a/plugins/WebUi/static/images/tango/details.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/down.png b/plugins/WebUi/static/images/tango/down.png deleted file mode 100644 index 732d46f37..000000000 Binary files a/plugins/WebUi/static/images/tango/down.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/list-add.png b/plugins/WebUi/static/images/tango/list-add.png deleted file mode 100644 index 1aa7f095c..000000000 Binary files a/plugins/WebUi/static/images/tango/list-add.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/list-remove.png b/plugins/WebUi/static/images/tango/list-remove.png deleted file mode 100644 index 00b654e8c..000000000 Binary files a/plugins/WebUi/static/images/tango/list-remove.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/pause.png b/plugins/WebUi/static/images/tango/pause.png deleted file mode 100644 index c8b4fe225..000000000 Binary files a/plugins/WebUi/static/images/tango/pause.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/preferences-system.png b/plugins/WebUi/static/images/tango/preferences-system.png deleted file mode 100644 index 9460dfc74..000000000 Binary files a/plugins/WebUi/static/images/tango/preferences-system.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/process-stop.png b/plugins/WebUi/static/images/tango/process-stop.png deleted file mode 100644 index ab6808fba..000000000 Binary files a/plugins/WebUi/static/images/tango/process-stop.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/queue-down.png b/plugins/WebUi/static/images/tango/queue-down.png deleted file mode 100644 index 3dd7fccdf..000000000 Binary files a/plugins/WebUi/static/images/tango/queue-down.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/queue-up.png b/plugins/WebUi/static/images/tango/queue-up.png deleted file mode 100644 index fa9a7d71b..000000000 Binary files a/plugins/WebUi/static/images/tango/queue-up.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/start.png b/plugins/WebUi/static/images/tango/start.png deleted file mode 100644 index a7de0feb0..000000000 Binary files a/plugins/WebUi/static/images/tango/start.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/stop.png b/plugins/WebUi/static/images/tango/stop.png deleted file mode 100644 index ede2815e5..000000000 Binary files a/plugins/WebUi/static/images/tango/stop.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/system-log-out.png b/plugins/WebUi/static/images/tango/system-log-out.png deleted file mode 100644 index 0010931e2..000000000 Binary files a/plugins/WebUi/static/images/tango/system-log-out.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/up.png b/plugins/WebUi/static/images/tango/up.png deleted file mode 100644 index c4fae73de..000000000 Binary files a/plugins/WebUi/static/images/tango/up.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/user-trash.png b/plugins/WebUi/static/images/tango/user-trash.png deleted file mode 100644 index 0e0953c73..000000000 Binary files a/plugins/WebUi/static/images/tango/user-trash.png and /dev/null differ diff --git a/plugins/WebUi/static/images/tango/view-refresh.png b/plugins/WebUi/static/images/tango/view-refresh.png deleted file mode 100644 index 3fd71d6e5..000000000 Binary files a/plugins/WebUi/static/images/tango/view-refresh.png and /dev/null differ diff --git a/plugins/WebUi/static/simple_site_style.css b/plugins/WebUi/static/simple_site_style.css deleted file mode 100755 index 3776994e8..000000000 --- a/plugins/WebUi/static/simple_site_style.css +++ /dev/null @@ -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 */ diff --git a/plugins/WebUi/templates/advanced/header.html b/plugins/WebUi/templates/advanced/header.html deleted file mode 100644 index 3d8f4cda3..000000000 --- a/plugins/WebUi/templates/advanced/header.html +++ /dev/null @@ -1,28 +0,0 @@ -$def with (title) - - - Deluge:$title - - - - - - - - - - - -
- - - - -
- -
-
diff --git a/plugins/WebUi/templates/advanced/index.html b/plugins/WebUi/templates/advanced/index.html deleted file mode 100644 index bb84f9d52..000000000 --- a/plugins/WebUi/templates/advanced/index.html +++ /dev/null @@ -1,147 +0,0 @@ -$def with (torrent_list, all_torrents) -$:render.header(_('Torrent list')) - -
- - - - - - - - - - - - - - - - - - $:category_tabs(all_torrents) - -
- - -
- - - - $:(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'))) - - - -$altrow(True) -$#4-space indentation is mandatory for for-loops in templetor! -$for torrent in torrent_list: - - - - - - - $if (not get('category')): - - - - - - - - - -
-
- -
-
$torrent.queue_pos - $(crop(torrent.name, 40))$fsize(torrent.total_size) -
-
- $torrent.message $int(torrent.progress) % -
-
-
$torrent.category$torrent.num_seeds ($torrent.total_seeds)$torrent.num_peers ($torrent.total_peers) - $if (torrent.download_rate): - $fspeed(torrent.download_rate) - $else: -   - - $if (torrent.upload_rate): - $fspeed(torrent.upload_rate) - $else: -   - $torrent.eta$("%.3f" % torrent.distributed_copies)$("%.3f" % torrent.ratio) -
-
- - -$:part_stats() - -
- -
- -
- - - - - -
- -
- - -$:render.footer() - diff --git a/plugins/WebUi/templates/advanced/part_categories.html b/plugins/WebUi/templates/advanced/part_categories.html deleted file mode 100644 index 3e8bbf806..000000000 --- a/plugins/WebUi/templates/advanced/part_categories.html +++ /dev/null @@ -1,37 +0,0 @@ -$def with (filter_tabs, category_tabs) -
- - - - - - - -
- - - - diff --git a/plugins/WebUi/templates/advanced/part_stats.html b/plugins/WebUi/templates/advanced/part_stats.html deleted file mode 100644 index 6ce594919..000000000 --- a/plugins/WebUi/templates/advanced/part_stats.html +++ /dev/null @@ -1,36 +0,0 @@ -$def with (stats) - - -
- -$_('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 -
- -
- - - $_('Connections') : $stats.num_connections ($stats.max_num_connections) - - $_('Down Speed') : $stats.download_rate ($stats.max_download) - - $_('Up Speed') : $stats.upload_rate ($stats.max_upload) - - - - -
- - - - - diff --git a/plugins/WebUi/templates/advanced/part_tb_button.html b/plugins/WebUi/templates/advanced/part_tb_button.html deleted file mode 100644 index bc5ec9a21..000000000 --- a/plugins/WebUi/templates/advanced/part_tb_button.html +++ /dev/null @@ -1,35 +0,0 @@ -$def with (method, func, title, image='') -
-
- -$if (get_config('button_style') == 0): - - -$if (get_config('button_style') == 1): - $if image: - - $else: - - -$if (get_config('button_style') == 2): - - -
-
- \ No newline at end of file diff --git a/plugins/WebUi/templates/advanced/static/advanced.css b/plugins/WebUi/templates/advanced/static/advanced.css deleted file mode 100644 index b4fa9db10..000000000 --- a/plugins/WebUi/templates/advanced/static/advanced.css +++ /dev/null @@ -1,263 +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.altrow0:hover { - background-color:#68a; -} -tr.altrow1:hover { - background-color:#68a; -} - -tr.altrow1{ - background-color: #37506f; -} - - - -tr.torrent_table_selected { - background-color:#900; -} - -th.torrent_table:hover { - background-color:#68a; -} -th.torrent_table { - background-color: #37506f; -} - -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 */ - - diff --git a/plugins/WebUi/templates/advanced/static/deluge.js b/plugins/WebUi/templates/advanced/static/deluge.js deleted file mode 100644 index da6be06e1..000000000 --- a/plugins/WebUi/templates/advanced/static/deluge.js +++ /dev/null @@ -1,148 +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) { - if (!(row.default_class_name)) { - row.default_class_name = row.className; - } - 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 = row.default_class_name - /*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 "" -} \ No newline at end of file diff --git a/plugins/WebUi/templates/advanced/static/scrolling_table.css b/plugins/WebUi/templates/advanced/static/scrolling_table.css deleted file mode 100644 index b046bd9c4..000000000 --- a/plugins/WebUi/templates/advanced/static/scrolling_table.css +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/plugins/WebUi/templates/advanced/torrent_info_inner.html b/plugins/WebUi/templates/advanced/torrent_info_inner.html deleted file mode 100644 index 907bfcf55..000000000 --- a/plugins/WebUi/templates/advanced/torrent_info_inner.html +++ /dev/null @@ -1,15 +0,0 @@ -$def with (torrent) - - - - - Deluge:$torrent.name - - - - - - -$:render.tab_meta(torrent) - -$:render.footer() diff --git a/plugins/WebUi/templates/deluge/about.html b/plugins/WebUi/templates/deluge/about.html deleted file mode 100644 index b34b7877a..000000000 --- a/plugins/WebUi/templates/deluge/about.html +++ /dev/null @@ -1,49 +0,0 @@ -$:render.header(_('About')) -
-

Version

-
$version 
-

Links

- - -

Authors

-
    -

    WebUi

    -
      -
    • Martijn Voncken
    • -
    - -

    Template

    -
      -
    • Martijn Voncken
    • -
    • somedude
    • -
    - -

    Deluge

    -
      -
    • Zach Tibbitts
    • -
    • Alon Zakai
    • - -
    • Alon Zakai
    • -
    • Marcos Pinto
    • -
    • Andrew Resch
    • -
    • Alex Dedul
    • -
    - -

    Windows Port

    -
      -
    • Slurdge
    • -
    - -
-*and all other authors/helpers/contributors I forgot to mention. -
- -$:render.footer() diff --git a/plugins/WebUi/templates/deluge/authors.txt b/plugins/WebUi/templates/deluge/authors.txt deleted file mode 100644 index 878772037..000000000 --- a/plugins/WebUi/templates/deluge/authors.txt +++ /dev/null @@ -1,5 +0,0 @@ --first layout taken from deluge website -improved by: --mvoncken --somedude - diff --git a/plugins/WebUi/templates/deluge/config.html b/plugins/WebUi/templates/deluge/config.html deleted file mode 100644 index e2670d0ae..000000000 --- a/plugins/WebUi/templates/deluge/config.html +++ /dev/null @@ -1,10 +0,0 @@ -$def with (form) -$:render.header(_('Config')) - -
Not Implemented!
-
-$:form.render() - -
- -$:render.footer() diff --git a/plugins/WebUi/templates/deluge/error.html b/plugins/WebUi/templates/deluge/error.html deleted file mode 100644 index 002cf3fc2..000000000 --- a/plugins/WebUi/templates/deluge/error.html +++ /dev/null @@ -1,6 +0,0 @@ -$def with (error_msg) -$:render.header(_('Error')) -
-    $error_msg
-
-$:render.footer() diff --git a/plugins/WebUi/templates/deluge/footer.html b/plugins/WebUi/templates/deluge/footer.html deleted file mode 100644 index ca03a0154..000000000 --- a/plugins/WebUi/templates/deluge/footer.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
-
-
- - diff --git a/plugins/WebUi/templates/deluge/header.html b/plugins/WebUi/templates/deluge/header.html deleted file mode 100644 index 42cc62eed..000000000 --- a/plugins/WebUi/templates/deluge/header.html +++ /dev/null @@ -1,23 +0,0 @@ -$def with (title) - - - Deluge:$title - - - - - - - -
- - - - -
- -
-
diff --git a/plugins/WebUi/templates/deluge/index.html b/plugins/WebUi/templates/deluge/index.html deleted file mode 100644 index 4c29278fc..000000000 --- a/plugins/WebUi/templates/deluge/index.html +++ /dev/null @@ -1,61 +0,0 @@ -$def with (torrent_list, all_torrents) -$:render.header(_('Torrent list')) - - - - $:(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'))) - -$#4-space indentation is mandatory for for-loops in templetor! -$for torrent in torrent_list: - - - - - - - - - - - - - -
-
-
$torrent.queue_pos - - $(crop(torrent.name, 40))$fsize(torrent.total_size) -
-
- $torrent.message $int(torrent.progress) % -
-
-
$torrent.num_seeds ($torrent.total_seeds)$torrent.num_peers ($torrent.total_peers)$fspeed(torrent.download_rate)$fspeed(torrent.upload_rate)$torrent.eta$("%.3f" % torrent.distributed_copies)$("%.3f" % torrent.ratio) -
- - - -
-$: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') - -
- -$:part_stats() - -$:render.footer() - diff --git a/plugins/WebUi/templates/deluge/login.html b/plugins/WebUi/templates/deluge/login.html deleted file mode 100644 index 0436e9076..000000000 --- a/plugins/WebUi/templates/deluge/login.html +++ /dev/null @@ -1,25 +0,0 @@ -$def with (error) -$:render.header(_('Login')) -
-$if error > 0: -
$_("Password is invalid,try again")
- -
- -
-
- $_('Password') - -
-
- - -
- -
- $_('About') -
-
-
-$:render.footer() diff --git a/plugins/WebUi/templates/deluge/part_button.html b/plugins/WebUi/templates/deluge/part_button.html deleted file mode 100644 index 8c420560f..000000000 --- a/plugins/WebUi/templates/deluge/part_button.html +++ /dev/null @@ -1,26 +0,0 @@ -$def with (method, url, title, image='') -
-
- -$if (get_config('button_style') == 0): - - -$if (get_config('button_style') == 1): - $if image: - - $else: - - -$if (get_config('button_style') == 2): - - -
-
diff --git a/plugins/WebUi/templates/deluge/part_stats.html b/plugins/WebUi/templates/deluge/part_stats.html deleted file mode 100644 index 342b8049f..000000000 --- a/plugins/WebUi/templates/deluge/part_stats.html +++ /dev/null @@ -1,35 +0,0 @@ -$def with (stats) - - -
- -$_('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 -
- -
- - - $_('Connections') : $stats.num_connections ($stats.max_num_connections) - - $_('Down Speed') : $stats.download_rate ($stats.max_download) - - $_('Up Speed') : $stats.upload_rate ($stats.max_upload) - - - - - - ($_('About')) - - -
- - diff --git a/plugins/WebUi/templates/deluge/refresh_form.html b/plugins/WebUi/templates/deluge/refresh_form.html deleted file mode 100644 index 0ac2cffda..000000000 --- a/plugins/WebUi/templates/deluge/refresh_form.html +++ /dev/null @@ -1,11 +0,0 @@ -$:render.header(_('Set Timeout')) -
-
- $_('Refresh page every:') - - $_('seconds') - -
-
-$:render.footer() diff --git a/plugins/WebUi/templates/deluge/sort_column_head.html b/plugins/WebUi/templates/deluge/sort_column_head.html deleted file mode 100644 index d354a6c4f..000000000 --- a/plugins/WebUi/templates/deluge/sort_column_head.html +++ /dev/null @@ -1,12 +0,0 @@ -$def with (column_id, column_name, order, active_up, active_down) - - -$column_name\ -$if active_up: - -$if active_down: - - - - - diff --git a/plugins/WebUi/templates/deluge/tab_meta.html b/plugins/WebUi/templates/deluge/tab_meta.html deleted file mode 100644 index e7f7f4bf2..000000000 --- a/plugins/WebUi/templates/deluge/tab_meta.html +++ /dev/null @@ -1,85 +0,0 @@ -$def with (torrent) - - -
- -
- - $torrent.progress %
-
- - - - - - - - - - - - - - - - - - - - - - - -
$_('Downloaded'):$torrent.calc_total_downloaded
$_('Uploaded'):$torrent.calc_total_uploaded
$_('Seeders'):$torrent.num_seeds ($torrent.total_seeds )
$_('Share Ratio'):$("%.3f" % torrent.ratio)
$_('Pieces'):$torrent.num_pieces x $fsize(torrent.piece_length)
  
-
- - - - - - - - - - - - - - - - - - - - - -
$_('Speed'): -$fspeed(torrent.download_rate)
$_('Speed'):$fspeed(torrent.upload_rate)
$_('Peers'):$torrent.num_peers ($torrent.total_peers )
$_('ETA'):$torrent.eta
$_('Availability'):$("%.3f" % torrent.distributed_copies)
  
- -
- - - - - - - - - - - - - - - - - - - - - - -
$_('Total Size'):$fspeed(torrent.total_size)
$_('# Of Files'):$torrent.num_files
$_('Tracker'):$(crop(torrent.tracker, 30))
$_('Tracker Status'):$(crop(torrent.tracker_status, 30))
$_('Next Announce'):$torrent.next_announce
$_('Queue Position'):$torrent.queue_pos
- -
diff --git a/plugins/WebUi/templates/deluge/torrent_add.html b/plugins/WebUi/templates/deluge/torrent_add.html deleted file mode 100644 index 0ceb15aae..000000000 --- a/plugins/WebUi/templates/deluge/torrent_add.html +++ /dev/null @@ -1,23 +0,0 @@ -$:render.header(_("Add Torrent")) -
-
- -
-
- $_('Url') - -
-
- $_('Upload torrent') - -
-
- - -
-
-
-
-$:render.footer() diff --git a/plugins/WebUi/templates/deluge/torrent_delete.html b/plugins/WebUi/templates/deluge/torrent_delete.html deleted file mode 100644 index 9c1c89e69..000000000 --- a/plugins/WebUi/templates/deluge/torrent_delete.html +++ /dev/null @@ -1,32 +0,0 @@ -$def with (torrent_ids, torrent_list) -$:render.header(_("Remove torrent")) -
-
- -
- -

$_("Remove torrent")

-
    -$for torrent in torrent_list: -
  • $torrent.name
  • -
- -
- - $_('Delete .torrent file') -
-
- - $_('Delete downloaded files.') -
-
- - -
-
-
-
-$:render.footer() \ No newline at end of file diff --git a/plugins/WebUi/templates/deluge/torrent_info.html b/plugins/WebUi/templates/deluge/torrent_info.html deleted file mode 100644 index 660273e1a..000000000 --- a/plugins/WebUi/templates/deluge/torrent_info.html +++ /dev/null @@ -1,50 +0,0 @@ -$def with (torrent) - -$:(render.header(torrent.message + '/' + torrent.name)) -
-

$_('Details')

- -$: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') - -
- - -
- - - - -$:part_stats() - -$:render.footer() diff --git a/plugins/WebUi/templates/hacking-templates.txt b/plugins/WebUi/templates/hacking-templates.txt deleted file mode 100644 index 600ba907e..000000000 --- a/plugins/WebUi/templates/hacking-templates.txt +++ /dev/null @@ -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. - - - diff --git a/plugins/WebUi/tests/test_all.py b/plugins/WebUi/tests/test_all.py deleted file mode 100644 index f6c750fea..000000000 --- a/plugins/WebUi/tests/test_all.py +++ /dev/null @@ -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 '' 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() - diff --git a/plugins/WebUi/webserver_common.py b/plugins/WebUi/webserver_common.py deleted file mode 100644 index a169159fc..000000000 --- a/plugins/WebUi/webserver_common.py +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) Martijn Voncken 2007 -# -# 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. -All hacks go here, so this is a really ugly source-file.. -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 lib.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)s] %(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', - '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', - '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 = [] - - - - - diff --git a/plugins/WebUi/webserver_framework.py b/plugins/WebUi/webserver_framework.py deleted file mode 100644 index b72acab66..000000000 --- a/plugins/WebUi/webserver_framework.py +++ /dev/null @@ -1,424 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# webserver_framework.py -# -# Copyright (C) Martijn Voncken 2007 -# -# 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 lib.webpy022 as web - -from lib.webpy022.webapi import cookies, setcookie as w_setcookie -from lib.webpy022.http import seeother, url -from lib.webpy022 import template,changequery as self_url -from lib.webpy022.utils import Storage -from lib.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 -debug_unicode = False -#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: - log.debug('%s.%s(%s)' ,self.__class__.__name__, func.__name__,name ) - print func(self, name) - except Exception, e: - print 'error:%s' % 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' - - #0.5-->0.6 - status.download_rate = status.download_payload_rate - status.upload_rate = status.upload_payload_rate - - #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") - elif status.paused: - status.message = _("Queued") - else: - status.message = (ws.STATE_MESSAGES[status.state]) - - #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. - #FIXED,l was a translation bug.. - if debug_unicode: - 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 ) - ,'traffic':lambda t: (t.download_rate > 0 or t.upload_rate > 0) - } - filter_func = filters[filter_name] - return [t for t in torrent_list if filter_func(t)] - -#/utils - -#template-defs: - -def get_category_choosers(torrent_list): - """ - todo: split into 2 parts... - """ - categories = get_categories(torrent_list) - - filter_tabs = [Storage(title='All (%s)' % len(torrent_list), - filter='', category=None)] - - #static filters - for title, filter_name in [ - (_('Downloading'),'downloading') , - (_('Queued'),'queued') , - (_('Paused'),'paused') , - (_('Seeding'),'seeding'), - (_('Traffic'),'traffic') - ]: - 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='')) - for title,count in categories: - category = title - title += ' (%s)' % (count, ) - category_tabs.append(Storage(title=title, category=category)) - - return filter_tabs, category_tabs - -def category_tabs(torrent_list): - filter_tabs, category_tabs = get_category_choosers(torrent_list) - 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) - -irow = 0 -def altrow(reset = False): - global irow - if reset: - irow = 1 - return - irow +=1 - irow = irow % 2 - return "altrow%s" % irow - - -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. - 'int':int, - 'sorted': sorted, - 'altrow':altrow, - '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 lib.webpy022.request import webpyfunc - from lib.webpy022 import webapi - from lib.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']