Remove trailing whitspace from *.py

This commit is contained in:
Andrew Resch 2008-11-17 00:57:32 +00:00
parent 1ca215d8f5
commit dac3c5d3e9
55 changed files with 1248 additions and 1249 deletions

View File

@ -2,19 +2,19 @@
# common.py # common.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -90,9 +90,9 @@ def get_revision():
f.close() f.close()
except IOError, e: except IOError, e:
pass pass
return revision return revision
def get_default_config_dir(filename=None): def get_default_config_dir(filename=None):
""" Returns the config path if no filename is specified """ Returns the config path if no filename is specified
Returns the config directory + filename as a path if filename is specified Returns the config directory + filename as a path if filename is specified
@ -134,7 +134,7 @@ def get_pixmap(fname):
def get_logo(size): def get_logo(size):
"""Returns a deluge logo pixbuf based on the size parameter.""" """Returns a deluge logo pixbuf based on the size parameter."""
import gtk import gtk
if windows_check(): if windows_check():
return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.png"), \ return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.png"), \
size, size) size, size)
else: else:
@ -161,16 +161,16 @@ def open_url_in_browser(url):
webbrowser.open(self.url) webbrowser.open(self.url)
BrowserThread(url).start() BrowserThread(url).start()
return False return False
import gobject import gobject
gobject.idle_add(start_browser) gobject.idle_add(start_browser)
def build_menu_radio_list(value_list, callback, pref_value=None, def build_menu_radio_list(value_list, callback, pref_value=None,
suffix=None, show_notset=False, notset_label=None, notset_lessthan=0, suffix=None, show_notset=False, notset_label=None, notset_lessthan=0,
show_other=False, show_activated=False, activated_label=None): show_other=False, show_activated=False, activated_label=None):
# Build a menu with radio menu items from a list and connect them to # Build a menu with radio menu items from a list and connect them to
# the callback. The pref_value is what you would like to test for the # the callback. The pref_value is what you would like to test for the
# default active radio item. # default active radio item.
import gtk import gtk
if notset_label is None: if notset_label is None:
@ -185,14 +185,14 @@ def build_menu_radio_list(value_list, callback, pref_value=None,
if pref_value > -1 and pref_value not in value_list: if pref_value > -1 and pref_value not in value_list:
value_list.pop() value_list.pop()
value_list.append(pref_value) value_list.append(pref_value)
for value in sorted(value_list): for value in sorted(value_list):
if suffix != None: if suffix != None:
menuitem = gtk.RadioMenuItem(group, str(value) + " " + \ menuitem = gtk.RadioMenuItem(group, str(value) + " " + \
suffix) suffix)
else: else:
menuitem = gtk.RadioMenuItem(group, str(value)) menuitem = gtk.RadioMenuItem(group, str(value))
group = menuitem group = menuitem
if value == pref_value and pref_value != None: if value == pref_value and pref_value != None:
@ -206,7 +206,7 @@ def build_menu_radio_list(value_list, callback, pref_value=None,
if show_activated is True: if show_activated is True:
for value in sorted(value_list): for value in sorted(value_list):
menuitem = gtk.RadioMenuItem(group, str(activated_label)) menuitem = gtk.RadioMenuItem(group, str(activated_label))
group = menuitem group = menuitem
if value == pref_value and pref_value != None: if value == pref_value and pref_value != None:
@ -226,7 +226,7 @@ def build_menu_radio_list(value_list, callback, pref_value=None,
menuitem.set_active(True) menuitem.set_active(True)
menuitem.connect("toggled", callback) menuitem.connect("toggled", callback)
menu.append(menuitem) menu.append(menuitem)
# Add the Other... menuitem # Add the Other... menuitem
if show_other is True: if show_other is True:
menuitem = gtk.SeparatorMenuItem() menuitem = gtk.SeparatorMenuItem()
@ -235,7 +235,7 @@ def build_menu_radio_list(value_list, callback, pref_value=None,
menuitem.set_name(_("Other...")) menuitem.set_name(_("Other..."))
menuitem.connect("activate", callback) menuitem.connect("activate", callback)
menu.append(menuitem) menu.append(menuitem)
return menu return menu
def show_other_dialog(string, default=None): def show_other_dialog(string, default=None):
@ -243,7 +243,7 @@ def show_other_dialog(string, default=None):
import gtk import gtk
import gtk.glade import gtk.glade
dialog_glade = gtk.glade.XML( dialog_glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/dgtkpopups.glade")) "glade/dgtkpopups.glade"))
speed_dialog = dialog_glade.get_widget("speed_dialog") speed_dialog = dialog_glade.get_widget("speed_dialog")
spin_title = dialog_glade.get_widget("spin_title") spin_title = dialog_glade.get_widget("spin_title")
@ -258,10 +258,10 @@ def show_other_dialog(string, default=None):
else: else:
speed_dialog.destroy() speed_dialog.destroy()
return None return None
speed_dialog.destroy() speed_dialog.destroy()
return value return value
## Formatting text functions ## Formatting text functions
def fsize(fsize_b): def fsize(fsize_b):
@ -292,7 +292,7 @@ def fpeer(num_peers, total_peers):
return "%d (%d)" % (num_peers, total_peers) return "%d (%d)" % (num_peers, total_peers)
else: else:
return "%d" % num_peers return "%d" % num_peers
def ftime(seconds): def ftime(seconds):
"""Returns a formatted time string""" """Returns a formatted time string"""
if seconds == 0: if seconds == 0:
@ -323,7 +323,7 @@ def is_url(url):
return bool(re.search('^(https?|ftp|udp)://', url)) return bool(re.search('^(https?|ftp|udp)://', url))
def fetch_url(url): def fetch_url(url):
"""Downloads a torrent file from a given """Downloads a torrent file from a given
URL and checks the file's validity.""" URL and checks the file's validity."""
import urllib import urllib
from deluge.log import LOG as log from deluge.log import LOG as log
@ -338,7 +338,7 @@ def fetch_url(url):
else: else:
log.debug("URL doesn't appear to be a valid torrent file: %s", url) log.debug("URL doesn't appear to be a valid torrent file: %s", url)
return None return None
def pythonize(var): def pythonize(var):
"""Translates DBUS types back to basic Python types.""" """Translates DBUS types back to basic Python types."""
if isinstance(var, list): if isinstance(var, list):

View File

@ -2,19 +2,19 @@
# component.py # component.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -47,18 +47,18 @@ class Component:
self._interval = interval self._interval = interval
self._timer = None self._timer = None
self._state = COMPONENT_STATE.index("Stopped") self._state = COMPONENT_STATE.index("Stopped")
def get_state(self): def get_state(self):
return self._state return self._state
def start(self): def start(self):
pass pass
def _start(self): def _start(self):
self._state = COMPONENT_STATE.index("Started") self._state = COMPONENT_STATE.index("Started")
if self._update(): if self._update():
self._timer = gobject.timeout_add(self._interval, self._update) self._timer = gobject.timeout_add(self._interval, self._update)
def stop(self): def stop(self):
pass pass
@ -68,20 +68,20 @@ class Component:
gobject.source_remove(self._timer) gobject.source_remove(self._timer)
except: except:
pass pass
def _pause(self): def _pause(self):
self._state = COMPONENT_STATE.index("Paused") self._state = COMPONENT_STATE.index("Paused")
try: try:
gobject.source_remove(self._timer) gobject.source_remove(self._timer)
except: except:
pass pass
def _resume(self): def _resume(self):
self._start() self._start()
def shutdown(self): def shutdown(self):
pass pass
def _update(self): def _update(self):
try: try:
self.update() self.update()
@ -90,29 +90,29 @@ class Component:
# update method. # update method.
return False return False
return True return True
class ComponentRegistry: class ComponentRegistry:
def __init__(self): def __init__(self):
self.components = {} self.components = {}
self.depend = {} self.depend = {}
def register(self, name, obj, depend): def register(self, name, obj, depend):
"""Registers a component.. depend must be list or None""" """Registers a component.. depend must be list or None"""
log.debug("Registered %s with ComponentRegistry..", name) log.debug("Registered %s with ComponentRegistry..", name)
self.components[name] = obj self.components[name] = obj
if depend != None: if depend != None:
self.depend[name] = depend self.depend[name] = depend
def get(self, name): def get(self, name):
"""Returns a reference to the component 'name'""" """Returns a reference to the component 'name'"""
return self.components[name] return self.components[name]
def start(self): def start(self):
"""Starts all components""" """Starts all components"""
for component in self.components.keys(): for component in self.components.keys():
self.start_component(component) self.start_component(component)
def start_component(self, name): def start_component(self, name):
"""Starts a component""" """Starts a component"""
# Check to see if this component has any dependencies # Check to see if this component has any dependencies
@ -125,24 +125,24 @@ class ComponentRegistry:
log.debug("Starting component %s..", name) log.debug("Starting component %s..", name)
self.components[name].start() self.components[name].start()
self.components[name]._start() self.components[name]._start()
def stop(self): def stop(self):
"""Stops all components""" """Stops all components"""
for component in self.components.keys(): for component in self.components.keys():
self.stop_component(component) self.stop_component(component)
def stop_component(self, component): def stop_component(self, component):
if self.components[component].get_state() != \ if self.components[component].get_state() != \
COMPONENT_STATE.index("Stopped"): COMPONENT_STATE.index("Stopped"):
log.debug("Stopping component %s..", component) log.debug("Stopping component %s..", component)
self.components[component].stop() self.components[component].stop()
self.components[component]._stop() self.components[component]._stop()
def pause(self): def pause(self):
"""Pauses all components. Stops calling update()""" """Pauses all components. Stops calling update()"""
for component in self.components.keys(): for component in self.components.keys():
self.pause_component(component) self.pause_component(component)
def pause_component(self, component): def pause_component(self, component):
if self.components[component].get_state() not in \ if self.components[component].get_state() not in \
[COMPONENT_STATE.index("Paused"), COMPONENT_STATE.index("Stopped")]: [COMPONENT_STATE.index("Paused"), COMPONENT_STATE.index("Stopped")]:
@ -153,11 +153,11 @@ class ComponentRegistry:
"""Resumes all components. Starts calling update()""" """Resumes all components. Starts calling update()"""
for component in self.components.keys(): for component in self.components.keys():
self.resume_component(component) self.resume_component(component)
def resume_component(self, component): def resume_component(self, component):
if self.components[component].get_state() == COMPONENT_STATE.index("Paused"): if self.components[component].get_state() == COMPONENT_STATE.index("Paused"):
log.debug("Resuming component %s..", component) log.debug("Resuming component %s..", component)
self.components[component]._resume() self.components[component]._resume()
def update(self): def update(self):
"""Updates all components""" """Updates all components"""
@ -166,9 +166,9 @@ class ComponentRegistry:
if self.components[component].get_state() == \ if self.components[component].get_state() == \
COMPONENT_STATE.index("Started"): COMPONENT_STATE.index("Started"):
self.components[component].update() self.components[component].update()
return True return True
def shutdown(self): def shutdown(self):
"""Shuts down all components. This should be called when the program """Shuts down all components. This should be called when the program
exits so that components can do any necessary clean-up.""" exits so that components can do any necessary clean-up."""
@ -180,8 +180,8 @@ class ComponentRegistry:
self.components[component].shutdown() self.components[component].shutdown()
except Exception, e: except Exception, e:
log.debug("Unable to call shutdown(): %s", e) log.debug("Unable to call shutdown(): %s", e)
_ComponentRegistry = ComponentRegistry() _ComponentRegistry = ComponentRegistry()
def register(name, obj, depend=None): def register(name, obj, depend=None):
@ -215,7 +215,7 @@ def resume(component=None):
_ComponentRegistry.resume() _ComponentRegistry.resume()
else: else:
_ComponentRegistry.resume_component(component) _ComponentRegistry.resume_component(component)
def update(): def update():
"""Updates all components""" """Updates all components"""
_ComponentRegistry.update() _ComponentRegistry.update()
@ -223,7 +223,7 @@ def update():
def shutdown(): def shutdown():
"""Shutdowns all components""" """Shutdowns all components"""
_ComponentRegistry.shutdown() _ComponentRegistry.shutdown()
def get(component): def get(component):
"""Return a reference to the component""" """Return a reference to the component"""
return _ComponentRegistry.get(component) return _ComponentRegistry.get(component)

View File

@ -2,19 +2,19 @@
# config.py # config.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -42,7 +42,7 @@ from deluge.log import LOG as log
class Config: class Config:
"""This class is used to access configuration files.""" """This class is used to access configuration files."""
def __init__(self, filename, defaults=None, config_dir=None): def __init__(self, filename, defaults=None, config_dir=None):
log.debug("Config created with filename: %s", filename) log.debug("Config created with filename: %s", filename)
log.debug("Config defaults: %s", defaults) log.debug("Config defaults: %s", defaults)
@ -50,7 +50,7 @@ class Config:
self.previous_config = {} self.previous_config = {}
self.set_functions = {} self.set_functions = {}
self._change_callback = None self._change_callback = None
# If defaults is not None then we need to use "defaults". # If defaults is not None then we need to use "defaults".
if defaults != None: if defaults != None:
self.config = defaults self.config = defaults
@ -60,18 +60,18 @@ class Config:
self.config_file = deluge.common.get_default_config_dir(filename) self.config_file = deluge.common.get_default_config_dir(filename)
else: else:
self.config_file = os.path.join(config_dir, filename) self.config_file = os.path.join(config_dir, filename)
self.load(self.config_file) self.load(self.config_file)
# Save # Save
self.save() self.save()
# This will get set with a gobject.timeout_add whenever a config option # This will get set with a gobject.timeout_add whenever a config option
# is set. # is set.
self.save_timer = None self.save_timer = None
def __del__(self): def __del__(self):
self.save() self.save()
def load(self, filename=None): def load(self, filename=None):
"""Load a config file either by 'filename' or the filename set during """Load a config file either by 'filename' or the filename set during
construction of this object.""" construction of this object."""
@ -88,7 +88,7 @@ class Config:
log.warning("IOError: Unable to load file '%s'", filename) log.warning("IOError: Unable to load file '%s'", filename)
except EOFError: except EOFError:
pkl_file.close() pkl_file.close()
def save(self, filename=None): def save(self, filename=None):
"""Save configuration to either 'filename' or the filename set during """Save configuration to either 'filename' or the filename set during
construction of this object.""" construction of this object."""
@ -107,16 +107,16 @@ class Config:
return return
except (EOFError, IOError): except (EOFError, IOError):
log.warning("IOError: Unable to open file: '%s'", filename) log.warning("IOError: Unable to open file: '%s'", filename)
try: try:
pkl_file = open(filename, "wb") pkl_file = open(filename, "wb")
cPickle.dump(self.config, pkl_file) cPickle.dump(self.config, pkl_file)
pkl_file.close() pkl_file.close()
except IOError: except IOError:
log.warning("IOError: Unable to save file '%s'", filename) log.warning("IOError: Unable to save file '%s'", filename)
self.save_timer = None self.save_timer = None
def set(self, key, value): def set(self, key, value):
"""Set the 'key' with 'value'.""" """Set the 'key' with 'value'."""
# Sets the "key" with "value" in the config dict # Sets the "key" with "value" in the config dict
@ -143,7 +143,7 @@ class Config:
def get(self, key): def get(self, key):
"""Get the value of 'key'. If it is an invalid key then get() will """Get the value of 'key'. If it is an invalid key then get() will
return None.""" return None."""
# Attempts to get the "key" value and returns None if the key is # Attempts to get the "key" value and returns None if the key is
# invalid # invalid
try: try:
value = self.config[key] value = self.config[key]
@ -156,15 +156,15 @@ class Config:
def get_config(self): def get_config(self):
"""Returns the entire configuration as a dictionary.""" """Returns the entire configuration as a dictionary."""
return self.config return self.config
def get_previous_config(self): def get_previous_config(self):
"""Returns the config prior to the last set()""" """Returns the config prior to the last set()"""
return self.previous_config return self.previous_config
def register_change_callback(self, callback): def register_change_callback(self, callback):
"""Registers a callback that will be called when a value is changed""" """Registers a callback that will be called when a value is changed"""
self._change_callback = callback self._change_callback = callback
def register_set_function(self, key, function, apply_now=True): def register_set_function(self, key, function, apply_now=True):
"""Register a function to be run when a config value changes.""" """Register a function to be run when a config value changes."""
log.debug("Registering function for %s key..", key) log.debug("Registering function for %s key..", key)
@ -173,13 +173,13 @@ class Config:
if apply_now: if apply_now:
self.set_functions[key](key, self.config[key]) self.set_functions[key](key, self.config[key])
return return
def apply_all(self): def apply_all(self):
"""Runs all set functions""" """Runs all set functions"""
log.debug("Running all set functions..") log.debug("Running all set functions..")
for key in self.set_functions.keys(): for key in self.set_functions.keys():
self.set_functions[key](key, self.config[key]) self.set_functions[key](key, self.config[key])
def __getitem__(self, key): def __getitem__(self, key):
return self.config[key] return self.config[key]

View File

@ -2,19 +2,19 @@
# configmanager.py # configmanager.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -62,36 +62,36 @@ class _ConfigManager:
os.makedirs(directory) os.makedirs(directory)
except Exception, e: except Exception, e:
log.warning("Unable to make config directory: %s", e) log.warning("Unable to make config directory: %s", e)
self.config_directory = directory self.config_directory = directory
def get_config_dir(self): def get_config_dir(self):
log.debug("get_config_dir: %s", self.config_directory) log.debug("get_config_dir: %s", self.config_directory)
return self.config_directory return self.config_directory
def close(self, config): def close(self, config):
"""Closes a config file.""" """Closes a config file."""
try: try:
del self.config_files[config] del self.config_files[config]
except KeyError: except KeyError:
pass pass
def save(self): def save(self):
"""Saves all the configs to disk.""" """Saves all the configs to disk."""
for key in self.config_files.keys(): for key in self.config_files.keys():
self.config_files[key].save() self.config_files[key].save()
# We need to return True to keep the timer active # We need to return True to keep the timer active
return True return True
def get_config(self, config_file, defaults=None): def get_config(self, config_file, defaults=None):
"""Get a reference to the Config object for this filename""" """Get a reference to the Config object for this filename"""
log.debug("Getting config '%s'", config_file) log.debug("Getting config '%s'", config_file)
# Create the config object if not already created # Create the config object if not already created
if config_file not in self.config_files.keys(): if config_file not in self.config_files.keys():
self.config_files[config_file] = Config(config_file, defaults, self.config_directory) self.config_files[config_file] = Config(config_file, defaults, self.config_directory)
return self.config_files[config_file] return self.config_files[config_file]
# Singleton functions # Singleton functions
_configmanager = _ConfigManager() _configmanager = _ConfigManager()
@ -107,6 +107,6 @@ def get_config_dir(filename=None):
return os.path.join(_configmanager.get_config_dir(), filename) return os.path.join(_configmanager.get_config_dir(), filename)
else: else:
return _configmanager.get_config_dir() return _configmanager.get_config_dir()
def close(config): def close(config):
return _configmanager.close(config) return _configmanager.close(config)

View File

@ -2,19 +2,19 @@
# alertmanager.py # alertmanager.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -54,17 +54,17 @@ class AlertManager(component.Component):
lt.alert.category_t.tracker_notification | lt.alert.category_t.tracker_notification |
lt.alert.category_t.status_notification | lt.alert.category_t.status_notification |
lt.alert.category_t.ip_block_notification) lt.alert.category_t.ip_block_notification)
# handlers is a dictionary of lists {"alert_type": [handler1,h2,..]} # handlers is a dictionary of lists {"alert_type": [handler1,h2,..]}
self.handlers = {} self.handlers = {}
def update(self): def update(self):
self.handle_alerts() self.handle_alerts()
def shutdown(self): def shutdown(self):
del self.session del self.session
del self.handlers del self.handlers
def register_handler(self, alert_type, handler): def register_handler(self, alert_type, handler):
"""Registers a function that will be called when 'alert_type' is pop'd """Registers a function that will be called when 'alert_type' is pop'd
in handle_alerts. The handler function should look like: in handle_alerts. The handler function should look like:
@ -75,11 +75,11 @@ class AlertManager(component.Component):
# There is no entry for this alert type yet, so lets make it with an # There is no entry for this alert type yet, so lets make it with an
# empty list. # empty list.
self.handlers[alert_type] = [] self.handlers[alert_type] = []
# Append the handler to the list in the handlers dictionary # Append the handler to the list in the handlers dictionary
self.handlers[alert_type].append(handler) self.handlers[alert_type].append(handler)
log.debug("Registered handler for alert %s", alert_type) log.debug("Registered handler for alert %s", alert_type)
def deregister_handler(self, handler): def deregister_handler(self, handler):
"""De-registers the 'handler' function from all alert types.""" """De-registers the 'handler' function from all alert types."""
# Iterate through all handlers and remove 'handler' where found # Iterate through all handlers and remove 'handler' where found
@ -87,7 +87,7 @@ class AlertManager(component.Component):
if handler in value: if handler in value:
# Handler is in this alert type list # Handler is in this alert type list
value.remove(handler) value.remove(handler)
def handle_alerts(self, wait=False): def handle_alerts(self, wait=False):
"""Pops all libtorrent alerts in the session queue and handles them """Pops all libtorrent alerts in the session queue and handles them
appropriately.""" appropriately."""
@ -101,7 +101,7 @@ class AlertManager(component.Component):
log.debug("%s: %s", alert_type, alert.message()) log.debug("%s: %s", alert_type, alert.message())
except RuntimeError: except RuntimeError:
log.debug("%s", alert_type) log.debug("%s", alert_type)
# Call any handlers for this alert type # Call any handlers for this alert type
if alert_type in self.handlers.keys(): if alert_type in self.handlers.keys():
for handler in self.handlers[alert_type]: for handler in self.handlers[alert_type]:
@ -109,7 +109,7 @@ class AlertManager(component.Component):
gobject.idle_add(handler, alert) gobject.idle_add(handler, alert)
else: else:
handler(alert) handler(alert)
alert = self.session.pop_alert() alert = self.session.pop_alert()
# Return True so that the timer continues # Return True so that the timer continues

View File

@ -2,19 +2,19 @@
# autoadd.py # autoadd.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -48,7 +48,7 @@ class AutoAdd(component.Component):
component.Component.__init__(self, "AutoAdd", depend=["TorrentManager"], interval=5000) component.Component.__init__(self, "AutoAdd", depend=["TorrentManager"], interval=5000)
# Get the core config # Get the core config
self.config = ConfigManager("core.conf") self.config = ConfigManager("core.conf")
# A list of filenames # A list of filenames
self.invalid_torrents = [] self.invalid_torrents = []
# Filename:Attempts # Filename:Attempts
@ -59,19 +59,19 @@ class AutoAdd(component.Component):
self._on_autoadd_enable, apply_now=True) self._on_autoadd_enable, apply_now=True)
self.config.register_set_function("autoadd_location", self.config.register_set_function("autoadd_location",
self._on_autoadd_location) self._on_autoadd_location)
def update(self): def update(self):
if not self.config["autoadd_enable"]: if not self.config["autoadd_enable"]:
# We shouldn't be updating because autoadd is not enabled # We shouldn't be updating because autoadd is not enabled
component.pause("AutoAdd") component.pause("AutoAdd")
return return
# Check the auto add folder for new torrents to add # Check the auto add folder for new torrents to add
if not os.path.exists(self.config["autoadd_location"]): if not os.path.exists(self.config["autoadd_location"]):
log.warning("Invalid AutoAdd folder: %s", self.config["autoadd_location"]) log.warning("Invalid AutoAdd folder: %s", self.config["autoadd_location"])
component.pause("AutoAdd") component.pause("AutoAdd")
return return
for filename in os.listdir(self.config["autoadd_location"]): for filename in os.listdir(self.config["autoadd_location"]):
if filename.split(".")[-1] == "torrent": if filename.split(".")[-1] == "torrent":
filepath = os.path.join(self.config["autoadd_location"], filename) filepath = os.path.join(self.config["autoadd_location"], filename)
@ -88,16 +88,16 @@ class AutoAdd(component.Component):
os.rename(filepath, filepath + ".invalid") os.rename(filepath, filepath + ".invalid")
del self.attempts[filename] del self.attempts[filename]
self.invalid_torrents.remove(filename) self.invalid_torrents.remove(filename)
else: else:
self.invalid_torrents.append(filename) self.invalid_torrents.append(filename)
self.attempts[filename] = 1 self.attempts[filename] = 1
continue continue
# The torrent looks good, so lets add it to the session # The torrent looks good, so lets add it to the session
component.get("TorrentManager").add(filedump=filedump, filename=filename) component.get("TorrentManager").add(filedump=filedump, filename=filename)
os.remove(filepath) os.remove(filepath)
def load_torrent(self, filename): def load_torrent(self, filename):
try: try:
log.debug("Attempting to open %s for add.", filename) log.debug("Attempting to open %s for add.", filename)
@ -109,10 +109,10 @@ class AutoAdd(component.Component):
except IOError, e: except IOError, e:
log.warning("Unable to open %s: %s", filename, e) log.warning("Unable to open %s: %s", filename, e)
raise e raise e
# Get the info to see if any exceptions are raised # Get the info to see if any exceptions are raised
info = lt.torrent_info(lt.bdecode(filedump)) info = lt.torrent_info(lt.bdecode(filedump))
return filedump return filedump
def _on_autoadd_enable(self, key, value): def _on_autoadd_enable(self, key, value):

View File

@ -2,19 +2,19 @@
# daemon.py # daemon.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -39,8 +39,8 @@ class Daemon:
def __init__(self, options, args): def __init__(self, options, args):
version = deluge.common.get_version() version = deluge.common.get_version()
if deluge.common.get_revision() != "": if deluge.common.get_revision() != "":
version = version + "r" + deluge.common.get_revision() version = version + "r" + deluge.common.get_revision()
log.info("Deluge daemon %s", version) log.info("Deluge daemon %s", version)
log.debug("options: %s", options) log.debug("options: %s", options)
log.debug("args: %s", args) log.debug("args: %s", args)

View File

@ -2,19 +2,19 @@
# oldstateupgrader.py # oldstateupgrader.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -73,14 +73,14 @@ class OldStateUpgrader:
if os.path.exists(self.state05_location) and not os.path.exists(self.state10_location): if os.path.exists(self.state05_location) and not os.path.exists(self.state10_location):
# If the 0.5 state file exists and the 1.0 doesn't, then let's upgrade it # If the 0.5 state file exists and the 1.0 doesn't, then let's upgrade it
self.upgrade05() self.upgrade05()
def upgrade05(self): def upgrade05(self):
try: try:
state = PickleUpgrader(open(self.state05_location, "rb")).load() state = PickleUpgrader(open(self.state05_location, "rb")).load()
except Exception, e: except Exception, e:
log.debug("Unable to open 0.5 state file: %s", e) log.debug("Unable to open 0.5 state file: %s", e)
return return
new_state = deluge.core.torrentmanager.TorrentManagerState() new_state = deluge.core.torrentmanager.TorrentManagerState()
for ti, uid in state.torrents.items(): for ti, uid in state.torrents.items():
torrent_path = os.path.join(self.config["config_location"], "torrentfiles", ti.filename) torrent_path = os.path.join(self.config["config_location"], "torrentfiles", ti.filename)
@ -92,7 +92,7 @@ class OldStateUpgrader:
_file.close() _file.close()
except (IOError, RuntimeError), e: except (IOError, RuntimeError), e:
log.warning("Unable to open %s: %s", filepath, e) log.warning("Unable to open %s: %s", filepath, e)
# Copy the torrent file to the new location # Copy the torrent file to the new location
import shutil import shutil
shutil.copyfile(torrent_path, os.path.join(self.config["state_location"], str(torrent_info.info_hash()) + ".torrent")) shutil.copyfile(torrent_path, os.path.join(self.config["state_location"], str(torrent_info.info_hash()) + ".torrent"))
@ -116,7 +116,7 @@ class OldStateUpgrader:
) )
# Append the object to the state list # Append the object to the state list
new_state.torrents.append(new_torrent) new_state.torrents.append(new_torrent)
# Now we need to write out the new state file # Now we need to write out the new state file
try: try:
log.debug("Saving torrent state file.") log.debug("Saving torrent state file.")
@ -127,7 +127,7 @@ class OldStateUpgrader:
except IOError, e: except IOError, e:
log.warning("Unable to save state file: %s", e) log.warning("Unable to save state file: %s", e)
return return
# Rename the persistent.state file # Rename the persistent.state file
try: try:
os.rename(self.state05_location, self.state05_location + ".old") os.rename(self.state05_location, self.state05_location + ".old")

View File

@ -2,19 +2,19 @@
# pluginmanager.py # pluginmanager.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -39,11 +39,11 @@ import deluge.pluginmanagerbase
import deluge.component as component import deluge.component as component
from deluge.log import LOG as log from deluge.log import LOG as log
class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
component.Component): component.Component):
"""PluginManager handles the loading of plugins and provides plugins with """PluginManager handles the loading of plugins and provides plugins with
functions to access parts of the core.""" functions to access parts of the core."""
def __init__(self, core): def __init__(self, core):
component.Component.__init__(self, "PluginManager") component.Component.__init__(self, "PluginManager")
self.core = core self.core = core
@ -53,7 +53,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
"post_torrent_remove": [], "post_torrent_remove": [],
"post_session_load": [] "post_session_load": []
} }
self.status_fields = {} self.status_fields = {}
# Call the PluginManagerBase constructor # Call the PluginManagerBase constructor
@ -63,19 +63,19 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
def start(self): def start(self):
# Enable plugins that are enabled in the config # Enable plugins that are enabled in the config
self.enable_plugins() self.enable_plugins()
# Set update timer to call update() in plugins every second # Set update timer to call update() in plugins every second
self.update_timer = gobject.timeout_add(1000, self.update_plugins) self.update_timer = gobject.timeout_add(1000, self.update_plugins)
def stop(self): def stop(self):
# Disable all enabled plugins # Disable all enabled plugins
self.disable_plugins() self.disable_plugins()
# Stop the update timer # Stop the update timer
gobject.source_remove(self.update_timer) gobject.source_remove(self.update_timer)
def shutdown(self): def shutdown(self):
self.stop() self.stop()
def update_plugins(self): def update_plugins(self):
for plugin in self.plugins.keys(): for plugin in self.plugins.keys():
try: try:
@ -87,13 +87,13 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
def get_core(self): def get_core(self):
"""Returns a reference to the core""" """Returns a reference to the core"""
return self.core return self.core
def register_status_field(self, field, function): def register_status_field(self, field, function):
"""Register a new status field. This can be used in the same way the """Register a new status field. This can be used in the same way the
client requests other status information from core.""" client requests other status information from core."""
log.debug("Registering status field %s with PluginManager", field) log.debug("Registering status field %s with PluginManager", field)
self.status_fields[field] = function self.status_fields[field] = function
def deregister_status_field(self, field): def deregister_status_field(self, field):
"""Deregisters a status field""" """Deregisters a status field"""
log.debug("Deregistering status field %s with PluginManager", field) log.debug("Deregistering status field %s with PluginManager", field)
@ -101,7 +101,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
del self.status_fields[field] del self.status_fields[field]
except: except:
log.warning("Unable to deregister status field %s", field) log.warning("Unable to deregister status field %s", field)
def get_status(self, torrent_id, fields): def get_status(self, torrent_id, fields):
"""Return the value of status fields for the selected torrent_id.""" """Return the value of status fields for the selected torrent_id."""
status = {} status = {}
@ -112,27 +112,27 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
log.warning("Status field %s is not registered with the\ log.warning("Status field %s is not registered with the\
PluginManager.", field) PluginManager.", field)
return status return status
def register_hook(self, hook, function): def register_hook(self, hook, function):
"""Register a hook function with the plugin manager""" """Register a hook function with the plugin manager"""
try: try:
self.hooks[hook].append(function) self.hooks[hook].append(function)
except KeyError: except KeyError:
log.warning("Plugin attempting to register invalid hook.") log.warning("Plugin attempting to register invalid hook.")
def deregister_hook(self, hook, function): def deregister_hook(self, hook, function):
"""Deregisters a hook function""" """Deregisters a hook function"""
try: try:
self.hooks[hook].remove(function) self.hooks[hook].remove(function)
except: except:
log.warning("Unable to deregister hook %s", hook) log.warning("Unable to deregister hook %s", hook)
def run_post_torrent_add(self, torrent_id): def run_post_torrent_add(self, torrent_id):
"""This hook is run after a torrent has been added to the session.""" """This hook is run after a torrent has been added to the session."""
log.debug("run_post_torrent_add") log.debug("run_post_torrent_add")
for function in self.hooks["post_torrent_add"]: for function in self.hooks["post_torrent_add"]:
function(torrent_id) function(torrent_id)
def run_post_torrent_remove(self, torrent_id): def run_post_torrent_remove(self, torrent_id):
"""This hook is run after a torrent has been removed from the session. """This hook is run after a torrent has been removed from the session.
""" """
@ -147,16 +147,16 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
log.debug("run_post_session_load") log.debug("run_post_session_load")
for function in self.hooks["post_session_load"]: for function in self.hooks["post_session_load"]:
function() function()
def get_torrent_list(self): def get_torrent_list(self):
"""Returns a list of torrent_id's in the current session.""" """Returns a list of torrent_id's in the current session."""
return component.get("TorrentManager").get_torrent_list() return component.get("TorrentManager").get_torrent_list()
def block_ip_range(self, range): def block_ip_range(self, range):
"""Blocks the ip range in the core""" """Blocks the ip range in the core"""
return self.core.export_block_ip_range(range) return self.core.export_block_ip_range(range)
def reset_ip_filter(self): def reset_ip_filter(self):
"""Resets the ip filter""" """Resets the ip filter"""
return self.core.export_reset_ip_filter() return self.core.export_reset_ip_filter()

View File

@ -2,19 +2,19 @@
# signalmanager.py # signalmanager.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,

View File

@ -2,19 +2,19 @@
# error.py # error.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -36,7 +36,7 @@ class DelugeError(Exception):
self.value = value self.value = value
def __str__(self): def __str__(self):
return repr(self.value) return repr(self.value)
class NoCoreError(DelugeError): class NoCoreError(DelugeError):
pass pass

View File

@ -2,19 +2,19 @@
# log.py # log.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,

View File

@ -2,19 +2,19 @@
# main.py # main.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -31,7 +31,7 @@
# this exception statement from your version. If you delete this exception # this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here. # statement from all source files in the program, then also delete it here.
# The main starting point for the program. This function is called when the # The main starting point for the program. This function is called when the
# user runs the command 'deluge'. # user runs the command 'deluge'.
"""Main starting point for Deluge. Contains the main() entry point.""" """Main starting point for Deluge. Contains the main() entry point."""
@ -46,7 +46,7 @@ import deluge.common
def start_ui(): def start_ui():
"""Entry point for ui script""" """Entry point for ui script"""
# Setup the argument parser # Setup the argument parser
parser = OptionParser(usage="%prog [options] [actions]", parser = OptionParser(usage="%prog [options] [actions]",
version=deluge.common.get_version()) version=deluge.common.get_version())
parser.add_option("-u", "--ui", dest="ui", parser.add_option("-u", "--ui", dest="ui",
@ -55,7 +55,7 @@ def start_ui():
help="Set the config location", action="store", type="str") help="Set the config location", action="store", type="str")
parser.add_option("-l", "--logfile", dest="logfile", parser.add_option("-l", "--logfile", dest="logfile",
help="Output to designated logfile instead of stdout", action="store", type="str") help="Output to designated logfile instead of stdout", action="store", type="str")
# Get the options and args from the OptionParser # Get the options and args from the OptionParser
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
@ -69,11 +69,11 @@ def start_ui():
else: else:
if not os.path.exists(deluge.common.get_default_config_dir()): if not os.path.exists(deluge.common.get_default_config_dir()):
os.makedirs(deluge.common.get_default_config_dir()) os.makedirs(deluge.common.get_default_config_dir())
# Always log to a file in Windows # Always log to a file in Windows
if deluge.common.windows_check() and not options.logfile: if deluge.common.windows_check() and not options.logfile:
options.logfile = "deluge.log" options.logfile = "deluge.log"
if options.logfile: if options.logfile:
if options.config: if options.config:
logfile = os.path.join(options.config, options.logfile) logfile = os.path.join(options.config, options.logfile)
@ -83,13 +83,13 @@ def start_ui():
sys.stdout = open(logfile, "wb") sys.stdout = open(logfile, "wb")
sys.stderr = sys.stdout sys.stderr = sys.stdout
sys.stdin = None sys.stdin = None
from deluge.log import LOG as log from deluge.log import LOG as log
version = deluge.common.get_version() version = deluge.common.get_version()
if deluge.common.get_revision() != "": if deluge.common.get_revision() != "":
version = version + "r" + deluge.common.get_revision() version = version + "r" + deluge.common.get_revision()
log.info("Deluge ui %s", version) log.info("Deluge ui %s", version)
log.debug("options: %s", options) log.debug("options: %s", options)
log.debug("args: %s", args) log.debug("args: %s", args)
@ -97,15 +97,15 @@ def start_ui():
from deluge.ui.ui import UI from deluge.ui.ui import UI
log.info("Starting ui..") log.info("Starting ui..")
UI(options, args) UI(options, args)
def start_daemon(): def start_daemon():
"""Entry point for daemon script""" """Entry point for daemon script"""
import deluge.common import deluge.common
# Setup the argument parser # Setup the argument parser
parser = OptionParser(usage="%prog [options] [actions]", parser = OptionParser(usage="%prog [options] [actions]",
version=deluge.common.get_version()) version=deluge.common.get_version())
parser.add_option("-p", "--port", dest="port", parser.add_option("-p", "--port", dest="port",
help="Port daemon will listen on", action="store", type="int") help="Port daemon will listen on", action="store", type="int")
parser.add_option("-d", "--do-not-daemonize", dest="donot", parser.add_option("-d", "--do-not-daemonize", dest="donot",
help="Do not daemonize", action="store_true", default=False) help="Do not daemonize", action="store_true", default=False)
@ -113,7 +113,7 @@ def start_daemon():
help="Set the config location", action="store", type="str") help="Set the config location", action="store", type="str")
parser.add_option("-l", "--logfile", dest="logfile", parser.add_option("-l", "--logfile", dest="logfile",
help="Set the logfile location", action="store", type="str") help="Set the logfile location", action="store", type="str")
# Get the options and args from the OptionParser # Get the options and args from the OptionParser
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
@ -127,7 +127,7 @@ def start_daemon():
else: else:
if not os.path.exists(deluge.common.get_default_config_dir()): if not os.path.exists(deluge.common.get_default_config_dir()):
os.makedirs(deluge.common.get_default_config_dir()) os.makedirs(deluge.common.get_default_config_dir())
# If the donot daemonize is set, then we just skip the forking # If the donot daemonize is set, then we just skip the forking
if not options.donot and not deluge.common.windows_check(): if not options.donot and not deluge.common.windows_check():
if os.fork() == 0: if os.fork() == 0:
@ -141,7 +141,7 @@ def start_daemon():
else: else:
config_dir = deluge.common.get_default_config_dir() config_dir = deluge.common.get_default_config_dir()
logfile = os.path.join(config_dir, "deluged.log") logfile = os.path.join(config_dir, "deluged.log")
sys.stdout = open(logfile, "wb") sys.stdout = open(logfile, "wb")
sys.stderr = sys.stdout sys.stderr = sys.stdout
sys.stdin = None sys.stdin = None
@ -160,11 +160,11 @@ def start_daemon():
else: else:
config_dir = deluge.common.get_default_config_dir() config_dir = deluge.common.get_default_config_dir()
logfile = os.path.join(config_dir, "deluged.log") logfile = os.path.join(config_dir, "deluged.log")
sys.stdout = open(logfile, "wb") sys.stdout = open(logfile, "wb")
sys.stderr = sys.stdout sys.stderr = sys.stdout
sys.stdin = None sys.stdin = None
from deluge.core.daemon import Daemon from deluge.core.daemon import Daemon
Daemon(options, args) Daemon(options, args)

View File

@ -2,19 +2,19 @@
# core.py # core.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -66,17 +66,17 @@ FORMATS = {
'p2bgz': ["PeerGuardian P2B (GZip)", PGReader] 'p2bgz': ["PeerGuardian P2B (GZip)", PGReader]
} }
class Core(CorePluginBase): class Core(CorePluginBase):
def enable(self): def enable(self):
log.debug('Blocklist: Plugin enabled..') log.debug('Blocklist: Plugin enabled..')
self.is_downloading = False self.is_downloading = False
self.is_importing = False self.is_importing = False
self.num_blocked = 0 self.num_blocked = 0
self.file_progress = 0.0 self.file_progress = 0.0
self.core = component.get("Core") self.core = component.get("Core")
self.config = deluge.configmanager.ConfigManager("blocklist.conf", DEFAULT_PREFS) self.config = deluge.configmanager.ConfigManager("blocklist.conf", DEFAULT_PREFS)
if self.config["load_on_start"]: if self.config["load_on_start"]:
self.export_import(self.need_new_blocklist()) self.export_import(self.need_new_blocklist())
@ -86,13 +86,13 @@ class Core(CorePluginBase):
self.update_timer = gobject.timeout_add( self.update_timer = gobject.timeout_add(
self.config["check_after_days"] * 24 * 60 * 60 * 1000, self.config["check_after_days"] * 24 * 60 * 60 * 1000,
self.download_blocklist, True) self.download_blocklist, True)
def disable(self): def disable(self):
log.debug("Reset IP Filter..") log.debug("Reset IP Filter..")
component.get("Core").export_reset_ip_filter() component.get("Core").export_reset_ip_filter()
self.config.save() self.config.save()
log.debug('Blocklist: Plugin disabled') log.debug('Blocklist: Plugin disabled')
def update(self): def update(self):
pass pass
@ -100,7 +100,7 @@ class Core(CorePluginBase):
def export_download(self, _import=False): def export_download(self, _import=False):
"""Download the blocklist specified in the config as url""" """Download the blocklist specified in the config as url"""
self.download_blocklist(_import) self.download_blocklist(_import)
def export_import(self, download=False, force=False): def export_import(self, download=False, force=False):
"""Import the blocklist from the blocklist.cache, if load is True, then """Import the blocklist from the blocklist.cache, if load is True, then
it will download the blocklist file if needed.""" it will download the blocklist file if needed."""
@ -109,12 +109,12 @@ class Core(CorePluginBase):
def export_get_config(self): def export_get_config(self):
"""Returns the config dictionary""" """Returns the config dictionary"""
return self.config.get_config() return self.config.get_config()
def export_set_config(self, config): def export_set_config(self, config):
"""Sets the config based on values in 'config'""" """Sets the config based on values in 'config'"""
for key in config.keys(): for key in config.keys():
self.config[key] = config[key] self.config[key] = config[key]
def export_get_status(self): def export_get_status(self):
"""Returns the status of the plugin.""" """Returns the status of the plugin."""
status = {} status = {}
@ -124,40 +124,40 @@ class Core(CorePluginBase):
status["state"] = "Importing" status["state"] = "Importing"
else: else:
status["state"] = "Idle" status["state"] = "Idle"
status["num_blocked"] = self.num_blocked status["num_blocked"] = self.num_blocked
status["file_progress"] = self.file_progress status["file_progress"] = self.file_progress
status["file_type"] = self.config["file_type"] status["file_type"] = self.config["file_type"]
status["file_url"] = self.config["file_url"] status["file_url"] = self.config["file_url"]
status["file_size"] = self.config["file_size"] status["file_size"] = self.config["file_size"]
status["file_date"] = self.config["file_date"] status["file_date"] = self.config["file_date"]
return status return status
#### ####
def on_download_blocklist(self, load): def on_download_blocklist(self, load):
self.is_downloading = False self.is_downloading = False
if load: if load:
self.export_import() self.export_import()
def import_blocklist(self, download=False, force=False): def import_blocklist(self, download=False, force=False):
"""Imports the downloaded blocklist into the session""" """Imports the downloaded blocklist into the session"""
if self.is_downloading: if self.is_downloading:
return return
if download: if download:
if force or self.need_new_blocklist(): if force or self.need_new_blocklist():
self.download_blocklist(True) self.download_blocklist(True)
return return
self.is_importing = True self.is_importing = True
log.debug("Reset IP Filter..") log.debug("Reset IP Filter..")
component.get("Core").export_reset_ip_filter() component.get("Core").export_reset_ip_filter()
self.num_blocked = 0 self.num_blocked = 0
# Open the file for reading # Open the file for reading
try: try:
read_list = FORMATS[self.config["listtype"]][1]( read_list = FORMATS[self.config["listtype"]][1](
@ -177,21 +177,21 @@ class Core(CorePluginBase):
log.debug("Exception during import: %s", e) log.debug("Exception during import: %s", e)
else: else:
log.debug("Blocklist import complete!") log.debug("Blocklist import complete!")
self.is_importing = False self.is_importing = False
def download_blocklist(self, load=False): def download_blocklist(self, load=False):
"""Runs download_blocklist_thread() in a thread and calls on_download_blocklist """Runs download_blocklist_thread() in a thread and calls on_download_blocklist
when finished. If load is True, then we will import the blocklist when finished. If load is True, then we will import the blocklist
upon download completion.""" upon download completion."""
if self.is_importing: if self.is_importing:
return return
self.is_downloading = True self.is_downloading = True
threading.Thread( threading.Thread(
target=self.download_blocklist_thread, target=self.download_blocklist_thread,
args=(self.on_download_blocklist, load)).start() args=(self.on_download_blocklist, load)).start()
def download_blocklist_thread(self, callback, load): def download_blocklist_thread(self, callback, load):
"""Downloads the blocklist specified by 'url' in the config""" """Downloads the blocklist specified by 'url' in the config"""
def _call_callback(callback, load): def _call_callback(callback, load):
@ -203,10 +203,10 @@ class Core(CorePluginBase):
if fp > 1.0: if fp > 1.0:
fp = 1.0 fp = 1.0
self.file_progress = fp self.file_progress = fp
import socket import socket
socket.setdefaulttimeout(self.config["timeout"]) socket.setdefaulttimeout(self.config["timeout"])
for i in xrange(self.config["try_times"]): for i in xrange(self.config["try_times"]):
log.debug("Attempting to download blocklist %s", self.config["url"]) log.debug("Attempting to download blocklist %s", self.config["url"])
try: try:
@ -225,10 +225,10 @@ class Core(CorePluginBase):
list_stats = os.stat(deluge.configmanager.get_config_dir("blocklist.cache")) list_stats = os.stat(deluge.configmanager.get_config_dir("blocklist.cache"))
self.config["file_date"] = datetime.datetime.fromtimestamp(list_stats.st_mtime).ctime() self.config["file_date"] = datetime.datetime.fromtimestamp(list_stats.st_mtime).ctime()
self.config["file_size"] = list_size = list_stats.st_size self.config["file_size"] = list_size = list_stats.st_size
gobject.idle_add(_call_callback, callback, load) gobject.idle_add(_call_callback, callback, load)
return return
def need_new_blocklist(self): def need_new_blocklist(self):
"""Returns True if a new blocklist file should be downloaded""" """Returns True if a new blocklist file should be downloaded"""
try: try:
@ -247,5 +247,5 @@ class Core(CorePluginBase):
if current_time >= (list_time + datetime.timedelta(days=self.config["check_after_days"])): if current_time >= (list_time + datetime.timedelta(days=self.config["check_after_days"])):
return True return True
return False return False

View File

@ -2,19 +2,19 @@
# gtkui.py # gtkui.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -44,59 +44,59 @@ from core import FORMATS
class GtkUI(ui.UI): class GtkUI(ui.UI):
def enable(self): def enable(self):
log.debug("Blocklist GtkUI enable..") log.debug("Blocklist GtkUI enable..")
self.load_preferences_page() self.load_preferences_page()
self.status_item = component.get("StatusBar").add_item( self.status_item = component.get("StatusBar").add_item(
image=self.get_resource("blocklist16.png"), image=self.get_resource("blocklist16.png"),
text="", text="",
callback=self._on_status_item_clicked, callback=self._on_status_item_clicked,
tooltip="Blocked IP Ranges") tooltip="Blocked IP Ranges")
# Register some hooks # Register some hooks
self.plugin.register_hook("on_apply_prefs", self._on_apply_prefs) self.plugin.register_hook("on_apply_prefs", self._on_apply_prefs)
self.plugin.register_hook("on_show_prefs", self._on_show_prefs) self.plugin.register_hook("on_show_prefs", self._on_show_prefs)
def disable(self): def disable(self):
log.debug("Blocklist GtkUI disable..") log.debug("Blocklist GtkUI disable..")
# Remove the preferences page # Remove the preferences page
self.plugin.remove_preferences_page("Blocklist") self.plugin.remove_preferences_page("Blocklist")
# Remove status item # Remove status item
component.get("StatusBar").remove_item(self.status_item) component.get("StatusBar").remove_item(self.status_item)
del self.status_item del self.status_item
# Deregister the hooks # Deregister the hooks
self.plugin.deregister_hook("on_apply_prefs", self._on_apply_prefs) self.plugin.deregister_hook("on_apply_prefs", self._on_apply_prefs)
self.plugin.deregister_hook("on_show_prefs", self._on_show_prefs) self.plugin.deregister_hook("on_show_prefs", self._on_show_prefs)
del self.glade del self.glade
def update(self): def update(self):
def _on_get_status(status): def _on_get_status(status):
if status["state"] == "Downloading": if status["state"] == "Downloading":
self.table_info.hide() self.table_info.hide()
self.glade.get_widget("button_check_download").set_sensitive(False) self.glade.get_widget("button_check_download").set_sensitive(False)
self.glade.get_widget("button_force_download").set_sensitive(False) self.glade.get_widget("button_force_download").set_sensitive(False)
self.status_item.set_text( self.status_item.set_text(
"Downloading %.2f%%" % (status["file_progress"] * 100)) "Downloading %.2f%%" % (status["file_progress"] * 100))
self.progress_bar.set_text("Downloading %.2f%%" % (status["file_progress"] * 100)) self.progress_bar.set_text("Downloading %.2f%%" % (status["file_progress"] * 100))
self.progress_bar.set_fraction(status["file_progress"]) self.progress_bar.set_fraction(status["file_progress"])
self.progress_bar.show() self.progress_bar.show()
elif status["state"] == "Importing": elif status["state"] == "Importing":
self.table_info.hide() self.table_info.hide()
self.glade.get_widget("button_check_download").set_sensitive(False) self.glade.get_widget("button_check_download").set_sensitive(False)
self.glade.get_widget("button_force_download").set_sensitive(False) self.glade.get_widget("button_force_download").set_sensitive(False)
self.status_item.set_text( self.status_item.set_text(
"Importing " + str(status["num_blocked"])) "Importing " + str(status["num_blocked"]))
self.progress_bar.set_text("Importing %s" % (status["num_blocked"])) self.progress_bar.set_text("Importing %s" % (status["num_blocked"]))
self.progress_bar.pulse() self.progress_bar.pulse()
self.progress_bar.show() self.progress_bar.show()
elif status["state"] == "Idle": elif status["state"] == "Idle":
self.progress_bar.hide() self.progress_bar.hide()
self.glade.get_widget("button_check_download").set_sensitive(True) self.glade.get_widget("button_check_download").set_sensitive(True)
@ -113,30 +113,30 @@ class GtkUI(ui.UI):
FORMATS[status["file_type"]][0]) FORMATS[status["file_type"]][0])
except KeyError: except KeyError:
self.glade.get_widget("label_type").set_text("") self.glade.get_widget("label_type").set_text("")
self.glade.get_widget("label_url").set_text( self.glade.get_widget("label_url").set_text(
status["file_url"]) status["file_url"])
client.blocklist_get_status(_on_get_status) client.blocklist_get_status(_on_get_status)
def _on_show_prefs(self): def _on_show_prefs(self):
def _on_get_config(config): def _on_get_config(config):
# Update the combo box. It's ugly, get over it. # Update the combo box. It's ugly, get over it.
self.glade.get_widget("combobox_types").set_active_iter( self.glade.get_widget("combobox_types").set_active_iter(
self.glade.get_widget("combobox_types").get_model().\ self.glade.get_widget("combobox_types").get_model().\
get_iter(FORMATS[config["listtype"]][1])) get_iter(FORMATS[config["listtype"]][1]))
self.glade.get_widget("entry_url").set_text( self.glade.get_widget("entry_url").set_text(
config["url"]) config["url"])
self.glade.get_widget("spin_check_days").set_value( self.glade.get_widget("spin_check_days").set_value(
config["check_after_days"]) config["check_after_days"])
self.glade.get_widget("chk_import_on_start").set_active( self.glade.get_widget("chk_import_on_start").set_active(
config["load_on_start"]) config["load_on_start"])
client.blocklist_get_config(_on_get_config) client.blocklist_get_config(_on_get_config)
def _on_apply_prefs(self): def _on_apply_prefs(self):
config = {} config = {}
config["listtype"] = self.glade.get_widget("combobox_types").\ config["listtype"] = self.glade.get_widget("combobox_types").\
@ -145,33 +145,33 @@ class GtkUI(ui.UI):
config["check_after_days"] = self.glade.get_widget("spin_check_days").get_value_as_int() config["check_after_days"] = self.glade.get_widget("spin_check_days").get_value_as_int()
config["load_on_start"] = self.glade.get_widget("chk_import_on_start").get_active() config["load_on_start"] = self.glade.get_widget("chk_import_on_start").get_active()
client.blocklist_set_config(None, config) client.blocklist_set_config(None, config)
def _on_button_check_download_clicked(self, widget): def _on_button_check_download_clicked(self, widget):
client.blocklist_import(None, True, False) client.blocklist_import(None, True, False)
def _on_button_force_download_clicked(self, widget): def _on_button_force_download_clicked(self, widget):
client.blocklist_import(None, True, True) client.blocklist_import(None, True, True)
def _on_status_item_clicked(self, widget, event): def _on_status_item_clicked(self, widget, event):
component.get("Preferences").show("Blocklist") component.get("Preferences").show("Blocklist")
def load_preferences_page(self): def load_preferences_page(self):
"""Initializes the preferences page and adds it to the preferences dialog""" """Initializes the preferences page and adds it to the preferences dialog"""
# Load the preferences page # Load the preferences page
self.glade = gtk.glade.XML(self.get_resource("blocklist_pref.glade")) self.glade = gtk.glade.XML(self.get_resource("blocklist_pref.glade"))
self.progress_bar = self.glade.get_widget("progressbar") self.progress_bar = self.glade.get_widget("progressbar")
self.table_info = self.glade.get_widget("table_info") self.table_info = self.glade.get_widget("table_info")
# Hide the progress bar initially # Hide the progress bar initially
self.progress_bar.hide() self.progress_bar.hide()
self.table_info.show() self.table_info.show()
self.glade.signal_autoconnect({ self.glade.signal_autoconnect({
"on_button_check_download_clicked": self._on_button_check_download_clicked, "on_button_check_download_clicked": self._on_button_check_download_clicked,
"on_button_force_download_clicked": self._on_button_force_download_clicked "on_button_force_download_clicked": self._on_button_force_download_clicked
}) })
# Setup types combobox # Setup types combobox
combo = self.glade.get_widget("combobox_types") combo = self.glade.get_widget("combobox_types")
combo_list = gtk.ListStore(str, str) combo_list = gtk.ListStore(str, str)
@ -182,21 +182,21 @@ class GtkUI(ui.UI):
for k in FORMATS.keys(): for k in FORMATS.keys():
i = combo_list.append([FORMATS[k][0], k]) i = combo_list.append([FORMATS[k][0], k])
FORMATS[k][1] = combo_list.get_path(i) FORMATS[k][1] = combo_list.get_path(i)
combo.set_active(0) combo.set_active(0)
# Set button icons # Set button icons
self.glade.get_widget("image_download").set_from_file( self.glade.get_widget("image_download").set_from_file(
self.get_resource("blocklist_download24.png")) self.get_resource("blocklist_download24.png"))
self.glade.get_widget("image_import").set_from_file( self.glade.get_widget("image_import").set_from_file(
self.get_resource("blocklist_import24.png")) self.get_resource("blocklist_import24.png"))
# Update the preferences page with config values from the core # Update the preferences page with config values from the core
self._on_show_prefs() self._on_show_prefs()
# Add the page to the preferences dialog # Add the page to the preferences dialog
self.plugin.add_preferences_page( self.plugin.add_preferences_page(
"Blocklist", "Blocklist",
self.glade.get_widget("blocklist_prefs_box")) self.glade.get_widget("blocklist_prefs_box"))

View File

@ -19,7 +19,7 @@ class FormatException(TextException):
pass pass
class TextBase: class TextBase:
def __init__(self, fd, regexp): def __init__(self, fd, regexp):
log.debug("TextBase loading") log.debug("TextBase loading")
self.count = 0 self.count = 0
@ -28,7 +28,7 @@ class TextBase:
def next(self): def next(self):
self.count += 1 self.count += 1
txt = self.fd.readline() txt = self.fd.readline()
if txt == "": if txt == "":
return False return False
@ -89,7 +89,7 @@ class GZMuleReader(MuleReader):
log.debug("Wrong file type or corrupted blocklist file.") log.debug("Wrong file type or corrupted blocklist file.")
# Reads zip files from SafePeer style files # Reads zip files from SafePeer style files
class PGZip(TextBase): class PGZip(TextBase):
def __init__(self, filename): def __init__(self, filename):
@ -125,7 +125,7 @@ class PGZip(TextBase):
# End of zip # End of zip
return False return False
return ret return ret
except FormatException, e: except FormatException, e:
log.debug("Blocklist: PGZip: Got format exception for zipfile") log.debug("Blocklist: PGZip: Got format exception for zipfile")
# Just skip # Just skip

View File

@ -2,19 +2,19 @@
# ui.py # ui.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -40,9 +40,9 @@ class UI:
def enable(self): def enable(self):
pass pass
def disable(self): def disable(self):
pass pass
def get_resource(self, filename): def get_resource(self, filename):
return pkg_resources.resource_filename("blocklist", os.path.join("data", filename)) return pkg_resources.resource_filename("blocklist", os.path.join("data", filename))

View File

@ -2,19 +2,19 @@
# core.py # core.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -39,7 +39,7 @@ class CorePluginBase:
# Register all export_* functions # Register all export_* functions
for func in dir(self): for func in dir(self):
if func.startswith("export_"): if func.startswith("export_"):
log.debug("Registering export function %s as %s", func, log.debug("Registering export function %s as %s", func,
plugin_name.lower() + "_" + func[7:]) plugin_name.lower() + "_" + func[7:])
self.plugin.get_core().register_function( self.plugin.get_core().register_function(
getattr(self, "%s" % func), plugin_name.lower()\ getattr(self, "%s" % func), plugin_name.lower()\

View File

@ -2,19 +2,19 @@
# init.py # init.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -36,7 +36,7 @@ from deluge.log import LOG as log
class PluginBase: class PluginBase:
def __init__(self): def __init__(self):
self.plugin = None self.plugin = None
def enable(self): def enable(self):
try: try:
self.plugin.enable() self.plugin.enable()
@ -45,7 +45,7 @@ class PluginBase:
else: else:
# If plugin was enabled, call it's update() right away # If plugin was enabled, call it's update() right away
self.update() self.update()
def disable(self): def disable(self):
try: try:
self.plugin.disable() self.plugin.disable()
@ -55,4 +55,4 @@ class PluginBase:
def update(self): def update(self):
if hasattr(self.plugin, "update"): if hasattr(self.plugin, "update"):
self.plugin.update() self.plugin.update()

View File

@ -4,19 +4,19 @@
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# Copyright (C) 2008 Mark Stahler ('kramed') <markstahler@gmail.com> # Copyright (C) 2008 Mark Stahler ('kramed') <markstahler@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -46,8 +46,7 @@ class UI:
def enable(self): def enable(self):
pass pass
def disable(self): def disable(self):
pass pass

File diff suppressed because one or more lines are too long

View File

@ -2,19 +2,19 @@
# coreconfig.py # coreconfig.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -42,22 +42,22 @@ class CoreConfig(component.Component):
self.config = {} self.config = {}
component.get("Signals").connect_to_signal("config_value_changed", component.get("Signals").connect_to_signal("config_value_changed",
self._on_config_value_changed) self._on_config_value_changed)
def start(self): def start(self):
client.get_config(self._on_get_config) client.get_config(self._on_get_config)
def stop(self): def stop(self):
self.config = {} self.config = {}
def __getitem__(self, key): def __getitem__(self, key):
return self.config[key] return self.config[key]
def __setitem__(self, key, value): def __setitem__(self, key, value):
client.set_config({key: value}) client.set_config({key: value})
def _on_get_config(self, config): def _on_get_config(self, config):
self.config = config self.config = config
def _on_config_value_changed(self, key, value): def _on_config_value_changed(self, key, value):
self.config[key] = value self.config[key] = value

View File

@ -2,19 +2,19 @@
# dbusinterface.py # dbusinterface.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -46,7 +46,7 @@ elif dbus.version >= (0,80,0):
import deluge.component as component import deluge.component as component
import deluge.common import deluge.common
from deluge.log import LOG as log from deluge.log import LOG as log
class DbusInterface(dbus.service.Object, component.Component): class DbusInterface(dbus.service.Object, component.Component):
def __init__(self, args, path="/org/deluge_torrent/Deluge"): def __init__(self, args, path="/org/deluge_torrent/Deluge"):
component.Component.__init__(self, "DbusInterface") component.Component.__init__(self, "DbusInterface")
@ -64,11 +64,11 @@ class DbusInterface(dbus.service.Object, component.Component):
if not deluge.common.is_url(arg): if not deluge.common.is_url(arg):
new_args.append(os.path.abspath(arg)) new_args.append(os.path.abspath(arg))
args = new_args args = new_args
# Send the args to the running session # Send the args to the running session
if args != [] and args != None: if args != [] and args != None:
bus = dbus.SessionBus() bus = dbus.SessionBus()
proxy = bus.get_object("org.deluge_torrent.Deluge", proxy = bus.get_object("org.deluge_torrent.Deluge",
"/org/deluge_torrent/Deluge") "/org/deluge_torrent/Deluge")
ui = dbus.Interface(proxy, "org.deluge_torrent.Deluge") ui = dbus.Interface(proxy, "org.deluge_torrent.Deluge")
ui.process_args(args) ui.process_args(args)
@ -80,7 +80,7 @@ class DbusInterface(dbus.service.Object, component.Component):
self.process_args(args) self.process_args(args)
# Register Deluge with Dbus # Register Deluge with Dbus
log.info("Registering with DBUS..") log.info("Registering with DBUS..")
bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", bus_name = dbus.service.BusName("org.deluge_torrent.Deluge",
bus=dbus.SessionBus()) bus=dbus.SessionBus())
dbus.service.Object.__init__(self, bus_name, path) dbus.service.Object.__init__(self, bus_name, path)
@ -89,4 +89,4 @@ class DbusInterface(dbus.service.Object, component.Component):
"""Process arguments sent to already running Deluge""" """Process arguments sent to already running Deluge"""
from ipcinterface import process_args from ipcinterface import process_args
process_args(args) process_args(args)

View File

@ -2,19 +2,19 @@
# details_tab.py # details_tab.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -46,11 +46,11 @@ class DetailsTab(Tab):
# Get the labels we need to update. # Get the labels we need to update.
# widgetname, modifier function, status keys # widgetname, modifier function, status keys
glade = component.get("MainWindow").main_glade glade = component.get("MainWindow").main_glade
self._name = "Details" self._name = "Details"
self._child_widget = glade.get_widget("details_tab") self._child_widget = glade.get_widget("details_tab")
self._tab_label = glade.get_widget("details_tab_label") self._tab_label = glade.get_widget("details_tab_label")
self.label_widgets = [ self.label_widgets = [
(glade.get_widget("summary_name"), None, ("name",)), (glade.get_widget("summary_name"), None, ("name",)),
(glade.get_widget("summary_total_size"), deluge.common.fsize, ("total_size",)), (glade.get_widget("summary_total_size"), deluge.common.fsize, ("total_size",)),
@ -60,11 +60,11 @@ class DetailsTab(Tab):
(glade.get_widget("summary_message"), str, ("message",)), (glade.get_widget("summary_message"), str, ("message",)),
(glade.get_widget("summary_hash"), str, ("hash",)) (glade.get_widget("summary_hash"), str, ("hash",))
] ]
def update(self): def update(self):
# Get the first selected torrent # Get the first selected torrent
selected = component.get("TorrentView").get_selected_torrents() selected = component.get("TorrentView").get_selected_torrents()
# Only use the first torrent in the list or return if None selected # Only use the first torrent in the list or return if None selected
if len(selected) != 0: if len(selected) != 0:
selected = selected[0] selected = selected[0]
@ -72,20 +72,20 @@ class DetailsTab(Tab):
# No torrent is selected in the torrentview # No torrent is selected in the torrentview
self.clear() self.clear()
return return
# Get the torrent status # Get the torrent status
status_keys = ["name", "total_size", "num_files", status_keys = ["name", "total_size", "num_files",
"tracker", "save_path", "message", "hash"] "tracker", "save_path", "message", "hash"]
client.get_torrent_status( client.get_torrent_status(
self._on_get_torrent_status, selected, status_keys) self._on_get_torrent_status, selected, status_keys)
def _on_get_torrent_status(self, status): def _on_get_torrent_status(self, status):
# Check to see if we got valid data from the core # Check to see if we got valid data from the core
if status is None: if status is None:
return return
# Update all the label widgets # Update all the label widgets
for widget in self.label_widgets: for widget in self.label_widgets:
if widget[1] != None: if widget[1] != None:
args = [] args = []
@ -95,14 +95,14 @@ class DetailsTab(Tab):
except Exception, e: except Exception, e:
log.debug("Unable to get status value: %s", e) log.debug("Unable to get status value: %s", e)
continue continue
txt = widget[1](*args) txt = widget[1](*args)
else: else:
txt = status[widget[2][0]] txt = status[widget[2][0]]
if widget[0].get_text() != txt: if widget[0].get_text() != txt:
widget[0].set_text(txt) widget[0].set_text(txt)
def clear(self): def clear(self):
for widget in self.label_widgets: for widget in self.label_widgets:
widget[0].set_text("") widget[0].set_text("")

View File

@ -2,19 +2,19 @@
# edittrackersdialog.py # edittrackersdialog.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -43,18 +43,18 @@ class EditTrackersDialog:
def __init__(self, torrent_id, parent=None): def __init__(self, torrent_id, parent=None):
self.torrent_id = torrent_id self.torrent_id = torrent_id
self.glade = gtk.glade.XML( self.glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/edit_trackers.glade")) "glade/edit_trackers.glade"))
self.dialog = self.glade.get_widget("edit_trackers_dialog") self.dialog = self.glade.get_widget("edit_trackers_dialog")
self.treeview = self.glade.get_widget("tracker_treeview") self.treeview = self.glade.get_widget("tracker_treeview")
self.add_tracker_dialog = self.glade.get_widget("add_tracker_dialog") self.add_tracker_dialog = self.glade.get_widget("add_tracker_dialog")
self.add_tracker_dialog.set_transient_for(self.dialog) self.add_tracker_dialog.set_transient_for(self.dialog)
self.edit_tracker_entry = self.glade.get_widget("edit_tracker_entry") self.edit_tracker_entry = self.glade.get_widget("edit_tracker_entry")
self.edit_tracker_entry.set_transient_for(self.dialog) self.edit_tracker_entry.set_transient_for(self.dialog)
self.dialog.set_icon(deluge.common.get_logo(32)) self.dialog.set_icon(deluge.common.get_logo(32))
if parent != None: if parent != None:
self.dialog.set_transient_for(parent) self.dialog.set_transient_for(parent)
@ -72,24 +72,24 @@ class EditTrackersDialog:
"on_button_add_ok_clicked": self.on_button_add_ok_clicked, "on_button_add_ok_clicked": self.on_button_add_ok_clicked,
"on_button_add_cancel_clicked": self.on_button_add_cancel_clicked "on_button_add_cancel_clicked": self.on_button_add_cancel_clicked
}) })
# Create a liststore for tier, url # Create a liststore for tier, url
self.liststore = gtk.ListStore(int, str) self.liststore = gtk.ListStore(int, str)
# Create the columns # Create the columns
self.treeview.append_column( self.treeview.append_column(
gtk.TreeViewColumn(_("Tier"), gtk.CellRendererText(), text=0)) gtk.TreeViewColumn(_("Tier"), gtk.CellRendererText(), text=0))
self.treeview.append_column( self.treeview.append_column(
gtk.TreeViewColumn(_("Tracker"), gtk.CellRendererText(), text=1)) gtk.TreeViewColumn(_("Tracker"), gtk.CellRendererText(), text=1))
self.treeview.set_model(self.liststore) self.treeview.set_model(self.liststore)
self.liststore.set_sort_column_id(0, gtk.SORT_ASCENDING) self.liststore.set_sort_column_id(0, gtk.SORT_ASCENDING)
def run(self): def run(self):
# Make sure we have a torrent_id.. if not just return # Make sure we have a torrent_id.. if not just return
if self.torrent_id == None: if self.torrent_id == None:
return return
# Get the trackers for this torrent # Get the trackers for this torrent
client.get_torrent_status( client.get_torrent_status(
@ -100,17 +100,17 @@ class EditTrackersDialog:
"""Display trackers dialog""" """Display trackers dialog"""
for tracker in status["trackers"]: for tracker in status["trackers"]:
self.add_tracker(tracker["tier"], tracker["url"]) self.add_tracker(tracker["tier"], tracker["url"])
self.dialog.show() self.dialog.show()
def add_tracker(self, tier, url): def add_tracker(self, tier, url):
"""Adds a tracker to the list""" """Adds a tracker to the list"""
self.liststore.append([tier, url]) self.liststore.append([tier, url])
def get_selected(self): def get_selected(self):
"""Returns the selected tracker""" """Returns the selected tracker"""
return self.treeview.get_selection().get_selected()[1] return self.treeview.get_selection().get_selected()[1]
def on_button_up_clicked(self, widget): def on_button_up_clicked(self, widget):
log.debug("on_button_up_clicked") log.debug("on_button_up_clicked")
selected = self.get_selected() selected = self.get_selected()
@ -120,13 +120,13 @@ class EditTrackersDialog:
new_tier = tier + 1 new_tier = tier + 1
# Now change the tier for this tracker # Now change the tier for this tracker
self.liststore.set_value(selected, 0, new_tier) self.liststore.set_value(selected, 0, new_tier)
def on_button_add_clicked(self, widget): def on_button_add_clicked(self, widget):
log.debug("on_button_add_clicked") log.debug("on_button_add_clicked")
# Show the add tracker dialog # Show the add tracker dialog
self.add_tracker_dialog.show() self.add_tracker_dialog.show()
self.glade.get_widget("entry_tracker").grab_focus() self.glade.get_widget("entry_tracker").grab_focus()
def on_button_remove_clicked(self, widget): def on_button_remove_clicked(self, widget):
log.debug("on_button_remove_clicked") log.debug("on_button_remove_clicked")
selected = self.get_selected() selected = self.get_selected()
@ -165,7 +165,7 @@ class EditTrackersDialog:
new_tier = tier - 1 new_tier = tier - 1
# Now change the tier for this tracker # Now change the tier for this tracker
self.liststore.set_value(selected, 0, new_tier) self.liststore.set_value(selected, 0, new_tier)
def on_button_ok_clicked(self, widget): def on_button_ok_clicked(self, widget):
log.debug("on_button_ok_clicked") log.debug("on_button_ok_clicked")
self.trackers = [] self.trackers = []
@ -178,11 +178,11 @@ class EditTrackersDialog:
# Set the torrens trackers # Set the torrens trackers
client.set_torrent_trackers(self.torrent_id, self.trackers) client.set_torrent_trackers(self.torrent_id, self.trackers)
self.dialog.destroy() self.dialog.destroy()
def on_button_cancel_clicked(self, widget): def on_button_cancel_clicked(self, widget):
log.debug("on_button_cancel_clicked") log.debug("on_button_cancel_clicked")
self.dialog.destroy() self.dialog.destroy()
def on_button_add_ok_clicked(self, widget): def on_button_add_ok_clicked(self, widget):
log.debug("on_button_add_ok_clicked") log.debug("on_button_add_ok_clicked")
from re import search as re_search from re import search as re_search
@ -190,7 +190,7 @@ class EditTrackersDialog:
if not re_search("[udp|http]s?://", tracker): if not re_search("[udp|http]s?://", tracker):
# Bad url.. lets prepend http:// # Bad url.. lets prepend http://
tracker = "http://" + tracker tracker = "http://" + tracker
# Figure out what tier number to use.. it's going to be the highest+1 # Figure out what tier number to use.. it's going to be the highest+1
# Also check for duplicates # Also check for duplicates
self.highest_tier = 0 self.highest_tier = 0
@ -209,7 +209,7 @@ class EditTrackersDialog:
self.liststore.foreach(tier_count, tracker) self.liststore.foreach(tier_count, tracker)
else: else:
self.highest_tier = -1 self.highest_tier = -1
# If not a duplicate, then add it to the list # If not a duplicate, then add it to the list
if not self.duplicate: if not self.duplicate:
# Add the tracker to the list # Add the tracker to the list
@ -218,9 +218,9 @@ class EditTrackersDialog:
# Clear the entry widget and hide the dialog # Clear the entry widget and hide the dialog
self.glade.get_widget("entry_tracker").set_text("") self.glade.get_widget("entry_tracker").set_text("")
self.add_tracker_dialog.hide() self.add_tracker_dialog.hide()
def on_button_add_cancel_clicked(self, widget): def on_button_add_cancel_clicked(self, widget):
log.debug("on_button_add_cancel_clicked") log.debug("on_button_add_cancel_clicked")
# Clear the entry widget and hide the dialog # Clear the entry widget and hide the dialog
self.glade.get_widget("entry_tracker").set_text("") self.glade.get_widget("entry_tracker").set_text("")
self.add_tracker_dialog.hide() self.add_tracker_dialog.hide()

View File

@ -2,19 +2,19 @@
# files_tab.py # files_tab.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -104,8 +104,8 @@ class FilesTab(Tab):
self.listview = glade.get_widget("files_listview") self.listview = glade.get_widget("files_listview")
# filename, size, progress string, progress value, priority, file index, icon id # filename, size, progress string, progress value, priority, file index, icon id
self.treestore = gtk.TreeStore(str, gobject.TYPE_UINT64, str, int, int, int, str) self.treestore = gtk.TreeStore(str, gobject.TYPE_UINT64, str, int, int, int, str)
# Filename column # Filename column
column = gtk.TreeViewColumn(_("Filename")) column = gtk.TreeViewColumn(_("Filename"))
render = gtk.CellRendererPixbuf() render = gtk.CellRendererPixbuf()
column.pack_start(render, False) column.pack_start(render, False)
@ -121,7 +121,7 @@ class FilesTab(Tab):
column.set_reorderable(True) column.set_reorderable(True)
self.listview.append_column(column) self.listview.append_column(column)
# Size column # Size column
column = gtk.TreeViewColumn(_("Size")) column = gtk.TreeViewColumn(_("Size"))
render = gtk.CellRendererText() render = gtk.CellRendererText()
column.pack_start(render, False) column.pack_start(render, False)
@ -134,7 +134,7 @@ class FilesTab(Tab):
column.set_reorderable(True) column.set_reorderable(True)
self.listview.append_column(column) self.listview.append_column(column)
# Progress column # Progress column
column = gtk.TreeViewColumn(_("Progress")) column = gtk.TreeViewColumn(_("Progress"))
render = gtk.CellRendererProgress() render = gtk.CellRendererProgress()
column.pack_start(render) column.pack_start(render)
@ -146,8 +146,8 @@ class FilesTab(Tab):
column.set_min_width(10) column.set_min_width(10)
column.set_reorderable(True) column.set_reorderable(True)
self.listview.append_column(column) self.listview.append_column(column)
# Priority column # Priority column
column = gtk.TreeViewColumn(_("Priority")) column = gtk.TreeViewColumn(_("Priority"))
render = gtk.CellRendererPixbuf() render = gtk.CellRendererPixbuf()
column.pack_start(render, False) column.pack_start(render, False)
@ -164,9 +164,9 @@ class FilesTab(Tab):
self.listview.append_column(column) self.listview.append_column(column)
self.listview.set_model(self.treestore) self.listview.set_model(self.treestore)
self.listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
self.file_menu = glade.get_widget("menu_file_tab") self.file_menu = glade.get_widget("menu_file_tab")
self.listview.connect("row-activated", self._on_row_activated) self.listview.connect("row-activated", self._on_row_activated)
self.listview.connect("button-press-event", self._on_button_press_event) self.listview.connect("button-press-event", self._on_button_press_event)
@ -179,22 +179,22 @@ class FilesTab(Tab):
"on_menuitem_highest_activate": self._on_menuitem_highest_activate, "on_menuitem_highest_activate": self._on_menuitem_highest_activate,
"on_menuitem_expand_all_activate": self._on_menuitem_expand_all_activate "on_menuitem_expand_all_activate": self._on_menuitem_expand_all_activate
}) })
# Attempt to load state # Attempt to load state
self.load_state() self.load_state()
# torrent_id: (filepath, size) # torrent_id: (filepath, size)
self.files_list = {} self.files_list = {}
self.torrent_id = None self.torrent_id = None
def save_state(self): def save_state(self):
filename = "files_tab.state" filename = "files_tab.state"
state = [] state = []
for index, column in enumerate(self.listview.get_columns()): for index, column in enumerate(self.listview.get_columns()):
state.append(ColumnState(column.get_title(), index, column.get_width(), state.append(ColumnState(column.get_title(), index, column.get_width(),
column.get_sort_indicator(), int(column.get_sort_order()))) column.get_sort_indicator(), int(column.get_sort_order())))
# Get the config location for saving the state file # Get the config location for saving the state file
config_location = ConfigManager("gtkui.conf")["config_location"] config_location = ConfigManager("gtkui.conf")["config_location"]
@ -205,13 +205,13 @@ class FilesTab(Tab):
state_file.close() state_file.close()
except IOError, e: except IOError, e:
log.warning("Unable to save state file: %s", e) log.warning("Unable to save state file: %s", e)
def load_state(self): def load_state(self):
filename = "files_tab.state" filename = "files_tab.state"
# Get the config location for loading the state file # Get the config location for loading the state file
config_location = ConfigManager("gtkui.conf")["config_location"] config_location = ConfigManager("gtkui.conf")["config_location"]
state = None state = None
try: try:
log.debug("Loading FilesTab state file: %s", filename) log.debug("Loading FilesTab state file: %s", filename)
state_file = open(os.path.join(config_location, filename), "rb") state_file = open(os.path.join(config_location, filename), "rb")
@ -219,10 +219,10 @@ class FilesTab(Tab):
state_file.close() state_file.close()
except (EOFError, IOError), e: except (EOFError, IOError), e:
log.warning("Unable to load state file: %s", e) log.warning("Unable to load state file: %s", e)
if state == None: if state == None:
return return
for column_state in state: for column_state in state:
# Find matching columns in the listview # Find matching columns in the listview
for (index, column) in enumerate(self.listview.get_columns()): for (index, column) in enumerate(self.listview.get_columns()):
@ -239,7 +239,7 @@ class FilesTab(Tab):
self.listview.move_column_after(column, None) self.listview.move_column_after(column, None)
else: else:
self.listview.move_column_after(column, self.listview.get_columns()[column_state.position - 1]) self.listview.move_column_after(column, self.listview.get_columns()[column_state.position - 1])
def update(self): def update(self):
# Get the first selected torrent # Get the first selected torrent
torrent_id = component.get("TorrentView").get_selected_torrents() torrent_id = component.get("TorrentView").get_selected_torrents()
@ -256,13 +256,13 @@ class FilesTab(Tab):
# We only want to do this if the torrent_id has changed # We only want to do this if the torrent_id has changed
self.treestore.clear() self.treestore.clear()
self.torrent_id = torrent_id self.torrent_id = torrent_id
if self.torrent_id not in self.files_list.keys(): if self.torrent_id not in self.files_list.keys():
# We need to get the files list # We need to get the files list
log.debug("Getting file list from core..") log.debug("Getting file list from core..")
client.get_torrent_status( client.get_torrent_status(
self._on_get_torrent_files, self._on_get_torrent_files,
self.torrent_id, self.torrent_id,
["files", "file_progress", "file_priorities"]) ["files", "file_progress", "file_priorities"])
client.force_call(block=True) client.force_call(block=True)
else: else:
@ -285,23 +285,23 @@ class FilesTab(Tab):
def get_file_path(self, row, path=""): def get_file_path(self, row, path=""):
if not row: if not row:
return path return path
path = self.treestore.get_value(row, 0) + path path = self.treestore.get_value(row, 0) + path
return self.get_file_path(self.treestore.iter_parent(row), path) return self.get_file_path(self.treestore.iter_parent(row), path)
def _on_open_file(self, status): def _on_open_file(self, status):
paths = self.listview.get_selection().get_selected_rows()[1] paths = self.listview.get_selection().get_selected_rows()[1]
selected = [] selected = []
for path in paths: for path in paths:
selected.append(self.treestore.get_iter(path)) selected.append(self.treestore.get_iter(path))
for select in selected: for select in selected:
path = self.get_file_path(select).split("/") path = self.get_file_path(select).split("/")
filepath = os.path.join(status["save_path"], *path) filepath = os.path.join(status["save_path"], *path)
log.debug("Open file '%s'", filepath) log.debug("Open file '%s'", filepath)
deluge.common.open_file(filepath) deluge.common.open_file(filepath)
## The following 3 methods create the folder/file view in the treeview ## The following 3 methods create the folder/file view in the treeview
def prepare_file_store(self, files): def prepare_file_store(self, files):
split_files = { } split_files = { }
i = 0 i = 0
@ -320,7 +320,7 @@ class FilesTab(Tab):
files_storage[file_name_chunk] = { } files_storage[file_name_chunk] = { }
self.prepare_file(file, file_name[first_slash_index+1:], self.prepare_file(file, file_name[first_slash_index+1:],
file_num, files_storage[file_name_chunk]) file_num, files_storage[file_name_chunk])
def add_files(self, parent_iter, split_files): def add_files(self, parent_iter, split_files):
ret = 0 ret = 0
for key,value in split_files.iteritems(): for key,value in split_files.iteritems():
@ -335,12 +335,12 @@ class FilesTab(Tab):
value[1]["size"], "", 0, 0, value[0], gtk.STOCK_FILE]) value[1]["size"], "", 0, 0, value[0], gtk.STOCK_FILE])
ret += value[1]["size"] ret += value[1]["size"]
return ret return ret
### ###
def update_files(self): def update_files(self):
self.prepare_file_store(self.files_list[self.torrent_id]) self.prepare_file_store(self.files_list[self.torrent_id])
self.listview.expand_row("0", False) self.listview.expand_row("0", False)
def get_selected_files(self): def get_selected_files(self):
"""Returns a list of file indexes that are selected""" """Returns a list of file indexes that are selected"""
def get_iter_children(itr, selected): def get_iter_children(itr, selected):
@ -350,7 +350,7 @@ class FilesTab(Tab):
if self.treestore.iter_has_child(i): if self.treestore.iter_has_child(i):
get_iter_children(i, selected) get_iter_children(i, selected)
i = self.treestore.iter_next(i) i = self.treestore.iter_next(i)
selected = [] selected = []
paths = self.listview.get_selection().get_selected_rows()[1] paths = self.listview.get_selection().get_selected_rows()[1]
for path in paths: for path in paths:
@ -358,24 +358,24 @@ class FilesTab(Tab):
selected.append(self.treestore[i][5]) selected.append(self.treestore[i][5])
if self.treestore.iter_has_child(i): if self.treestore.iter_has_child(i):
get_iter_children(i, selected) get_iter_children(i, selected)
return selected return selected
def _on_get_torrent_files(self, status): def _on_get_torrent_files(self, status):
self.files_list[self.torrent_id] = status["files"] self.files_list[self.torrent_id] = status["files"]
self.update_files() self.update_files()
self._on_get_torrent_status(status) self._on_get_torrent_status(status)
def get_files_from_tree(self, rows, files_list, indent): def get_files_from_tree(self, rows, files_list, indent):
if not rows: if not rows:
return None return None
for row in rows: for row in rows:
if row[5] > -1: if row[5] > -1:
files_list.append((row[5], row)) files_list.append((row[5], row))
self.get_files_from_tree(row.iterchildren(), files_list, indent+1) self.get_files_from_tree(row.iterchildren(), files_list, indent+1)
return None return None
def _on_get_torrent_status(self, status): def _on_get_torrent_status(self, status):
# (index, iter) # (index, iter)
files_list = [] files_list = []
@ -391,7 +391,7 @@ class FilesTab(Tab):
file_priority = status["file_priorities"][index] file_priority = status["file_priorities"][index]
if row[4] != file_priority: if row[4] != file_priority:
row[4] = file_priority row[4] = file_priority
def _on_button_press_event(self, widget, event): def _on_button_press_event(self, widget, event):
"""This is a callback for showing the right-click context menu.""" """This is a callback for showing the right-click context menu."""
log.debug("on_button_press_event") log.debug("on_button_press_event")
@ -409,10 +409,10 @@ class FilesTab(Tab):
self.listview.get_selection().select_iter(row) self.listview.get_selection().select_iter(row)
else: else:
self.listview.get_selection().select_iter(row) self.listview.get_selection().select_iter(row)
self.file_menu.popup(None, None, None, event.button, event.time) self.file_menu.popup(None, None, None, event.button, event.time)
return True return True
def _on_menuitem_open_file_activate(self, menuitem): def _on_menuitem_open_file_activate(self, menuitem):
self._on_row_activated(None, None, None) self._on_row_activated(None, None, None)
@ -431,27 +431,27 @@ class FilesTab(Tab):
file_priorities.sort() file_priorities.sort()
priorities = [p[1] for p in file_priorities] priorities = [p[1] for p in file_priorities]
log.debug("priorities: %s", priorities) log.debug("priorities: %s", priorities)
client.set_torrent_file_priorities(self.torrent_id, priorities) client.set_torrent_file_priorities(self.torrent_id, priorities)
def _on_menuitem_donotdownload_activate(self, menuitem): def _on_menuitem_donotdownload_activate(self, menuitem):
self._set_file_priorities_on_user_change( self._set_file_priorities_on_user_change(
self.get_selected_files(), self.get_selected_files(),
deluge.common.FILE_PRIORITY["Do Not Download"]) deluge.common.FILE_PRIORITY["Do Not Download"])
def _on_menuitem_normal_activate(self, menuitem): def _on_menuitem_normal_activate(self, menuitem):
self._set_file_priorities_on_user_change( self._set_file_priorities_on_user_change(
self.get_selected_files(), self.get_selected_files(),
deluge.common.FILE_PRIORITY["Normal Priority"]) deluge.common.FILE_PRIORITY["Normal Priority"])
def _on_menuitem_high_activate(self, menuitem): def _on_menuitem_high_activate(self, menuitem):
self._set_file_priorities_on_user_change( self._set_file_priorities_on_user_change(
self.get_selected_files(), self.get_selected_files(),
deluge.common.FILE_PRIORITY["High Priority"]) deluge.common.FILE_PRIORITY["High Priority"])
def _on_menuitem_highest_activate(self, menuitem): def _on_menuitem_highest_activate(self, menuitem):
self._set_file_priorities_on_user_change( self._set_file_priorities_on_user_change(
self.get_selected_files(), self.get_selected_files(),
deluge.common.FILE_PRIORITY["Highest Priority"]) deluge.common.FILE_PRIORITY["Highest Priority"])
def _on_menuitem_expand_all_activate(self, menuitem): def _on_menuitem_expand_all_activate(self, menuitem):

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# listview.py # listview.py
# #

View File

@ -2,19 +2,19 @@
# menubar.py # menubar.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -50,19 +50,19 @@ class MenuBar(component.Component):
component.Component.__init__(self, "MenuBar") component.Component.__init__(self, "MenuBar")
self.window = component.get("MainWindow") self.window = component.get("MainWindow")
self.config = ConfigManager("gtkui.conf") self.config = ConfigManager("gtkui.conf")
# Get the torrent menu from the glade file # Get the torrent menu from the glade file
self.torrentmenu_glade = gtk.glade.XML( self.torrentmenu_glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/torrent_menu.glade")) "glade/torrent_menu.glade"))
# Attach remove torrent menu # Attach remove torrent menu
self.torrentmenu_glade.get_widget("menuitem_remove").set_submenu( self.torrentmenu_glade.get_widget("menuitem_remove").set_submenu(
self.torrentmenu_glade.get_widget("remove_torrent_menu")) self.torrentmenu_glade.get_widget("remove_torrent_menu"))
self.torrentmenu_glade.get_widget("menuitem_queue").set_submenu( self.torrentmenu_glade.get_widget("menuitem_queue").set_submenu(
self.torrentmenu_glade.get_widget("queue_torrent_menu")) self.torrentmenu_glade.get_widget("queue_torrent_menu"))
# Attach options torrent menu # Attach options torrent menu
self.torrentmenu_glade.get_widget("menuitem_options").set_submenu( self.torrentmenu_glade.get_widget("menuitem_options").set_submenu(
self.torrentmenu_glade.get_widget("options_torrent_menu")) self.torrentmenu_glade.get_widget("options_torrent_menu"))
@ -70,7 +70,7 @@ class MenuBar(component.Component):
common.get_pixmap("downloading16.png")) common.get_pixmap("downloading16.png"))
self.torrentmenu_glade.get_widget("upload-limit-image").set_from_file( self.torrentmenu_glade.get_widget("upload-limit-image").set_from_file(
common.get_pixmap("seeding16.png")) common.get_pixmap("seeding16.png"))
for menuitem in ("menuitem_down_speed", "menuitem_up_speed", for menuitem in ("menuitem_down_speed", "menuitem_up_speed",
"menuitem_max_connections", "menuitem_upload_slots"): "menuitem_max_connections", "menuitem_upload_slots"):
submenu = gtk.Menu() submenu = gtk.Menu()
@ -84,7 +84,7 @@ class MenuBar(component.Component):
submenu.append(item) submenu.append(item)
submenu.show_all() submenu.show_all()
self.torrentmenu_glade.get_widget(menuitem).set_submenu(submenu) self.torrentmenu_glade.get_widget(menuitem).set_submenu(submenu)
submenu = gtk.Menu() submenu = gtk.Menu()
item = gtk.MenuItem(_("On")) item = gtk.MenuItem(_("On"))
item.connect("activate", self.on_menuitem_set_automanaged_on) item.connect("activate", self.on_menuitem_set_automanaged_on)
@ -94,10 +94,10 @@ class MenuBar(component.Component):
submenu.append(item) submenu.append(item)
submenu.show_all() submenu.show_all()
self.torrentmenu_glade.get_widget("menuitem_auto_managed").set_submenu(submenu) self.torrentmenu_glade.get_widget("menuitem_auto_managed").set_submenu(submenu)
self.torrentmenu = self.torrentmenu_glade.get_widget("torrent_menu") self.torrentmenu = self.torrentmenu_glade.get_widget("torrent_menu")
self.menu_torrent = self.window.main_glade.get_widget("menu_torrent") self.menu_torrent = self.window.main_glade.get_widget("menu_torrent")
# Attach the torrent_menu to the Torrent file menu # Attach the torrent_menu to the Torrent file menu
self.menu_torrent.set_submenu(self.torrentmenu) self.menu_torrent.set_submenu(self.torrentmenu)
@ -107,7 +107,7 @@ class MenuBar(component.Component):
self.config["show_sidebar"]) self.config["show_sidebar"])
self.window.main_glade.get_widget("menuitem_statusbar").set_active( self.window.main_glade.get_widget("menuitem_statusbar").set_active(
self.config["show_statusbar"]) self.config["show_statusbar"])
### Connect Signals ### ### Connect Signals ###
self.window.main_glade.signal_autoconnect({ self.window.main_glade.signal_autoconnect({
## File Menu ## File Menu
@ -122,12 +122,12 @@ class MenuBar(component.Component):
self.on_menuitem_preferences_activate, self.on_menuitem_preferences_activate,
"on_menuitem_connectionmanager_activate": \ "on_menuitem_connectionmanager_activate": \
self.on_menuitem_connectionmanager_activate, self.on_menuitem_connectionmanager_activate,
## View Menu ## View Menu
"on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled,
"on_menuitem_labels_toggled": self.on_menuitem_labels_toggled, "on_menuitem_labels_toggled": self.on_menuitem_labels_toggled,
"on_menuitem_statusbar_toggled": self.on_menuitem_statusbar_toggled, "on_menuitem_statusbar_toggled": self.on_menuitem_statusbar_toggled,
## Help Menu ## Help Menu
"on_menuitem_homepage_activate": self.on_menuitem_homepage_activate, "on_menuitem_homepage_activate": self.on_menuitem_homepage_activate,
"on_menuitem_faq_activate": self.on_menuitem_faq_activate, "on_menuitem_faq_activate": self.on_menuitem_faq_activate,
@ -135,7 +135,7 @@ class MenuBar(component.Component):
self.on_menuitem_community_activate, self.on_menuitem_community_activate,
"on_menuitem_about_activate": self.on_menuitem_about_activate "on_menuitem_about_activate": self.on_menuitem_about_activate
}) })
self.torrentmenu_glade.signal_autoconnect({ self.torrentmenu_glade.signal_autoconnect({
## Torrent Menu ## Torrent Menu
"on_menuitem_pause_activate": self.on_menuitem_pause_activate, "on_menuitem_pause_activate": self.on_menuitem_pause_activate,
@ -160,24 +160,24 @@ class MenuBar(component.Component):
"on_menuitem_queue_up_activate": self.on_menuitem_queue_up_activate, "on_menuitem_queue_up_activate": self.on_menuitem_queue_up_activate,
"on_menuitem_queue_down_activate": self.on_menuitem_queue_down_activate, "on_menuitem_queue_down_activate": self.on_menuitem_queue_down_activate,
"on_menuitem_queue_bottom_activate": self.on_menuitem_queue_bottom_activate, "on_menuitem_queue_bottom_activate": self.on_menuitem_queue_bottom_activate,
}) })
self.change_sensitivity = [ self.change_sensitivity = [
"menuitem_addtorrent" "menuitem_addtorrent"
] ]
if self.config["classic_mode"]: if self.config["classic_mode"]:
# We need to remove the 'quit and shutdown daemon' menu item # We need to remove the 'quit and shutdown daemon' menu item
self.window.main_glade.get_widget("menuitem_quitdaemon").hide() self.window.main_glade.get_widget("menuitem_quitdaemon").hide()
self.window.main_glade.get_widget("separatormenuitem").hide() self.window.main_glade.get_widget("separatormenuitem").hide()
self.window.main_glade.get_widget("menuitem_connectionmanager").hide() self.window.main_glade.get_widget("menuitem_connectionmanager").hide()
def start(self): def start(self):
for widget in self.change_sensitivity: for widget in self.change_sensitivity:
self.window.main_glade.get_widget(widget).set_sensitive(True) self.window.main_glade.get_widget(widget).set_sensitive(True)
# Hide the Open Folder menuitem and separator if not connected to a # Hide the Open Folder menuitem and separator if not connected to a
# localhost. # localhost.
non_remote_items = [ non_remote_items = [
"menuitem_open_folder", "menuitem_open_folder",
@ -190,14 +190,14 @@ class MenuBar(component.Component):
else: else:
for widget in non_remote_items: for widget in non_remote_items:
self.torrentmenu_glade.get_widget(widget).set_no_show_all(False) self.torrentmenu_glade.get_widget(widget).set_no_show_all(False)
# Show the Torrent menu because we're connected to a host # Show the Torrent menu because we're connected to a host
self.menu_torrent.show() self.menu_torrent.show()
if not self.config["classic_mode"]: if not self.config["classic_mode"]:
self.window.main_glade.get_widget("separatormenuitem").show() self.window.main_glade.get_widget("separatormenuitem").show()
self.window.main_glade.get_widget("menuitem_quitdaemon").show() self.window.main_glade.get_widget("menuitem_quitdaemon").show()
def stop(self): def stop(self):
for widget in self.change_sensitivity: for widget in self.change_sensitivity:
self.window.main_glade.get_widget(widget).set_sensitive(False) self.window.main_glade.get_widget(widget).set_sensitive(False)
@ -224,20 +224,20 @@ class MenuBar(component.Component):
self.torrentmenu.append(sep) self.torrentmenu.append(sep)
sep.show() sep.show()
return sep return sep
### Callbacks ### ### Callbacks ###
## File Menu ## ## File Menu ##
def on_menuitem_addtorrent_activate(self, data=None): def on_menuitem_addtorrent_activate(self, data=None):
log.debug("on_menuitem_addtorrent_activate") log.debug("on_menuitem_addtorrent_activate")
component.get("AddTorrentDialog").show() component.get("AddTorrentDialog").show()
def on_menuitem_quitdaemon_activate(self, data=None): def on_menuitem_quitdaemon_activate(self, data=None):
log.debug("on_menuitem_quitdaemon_activate") log.debug("on_menuitem_quitdaemon_activate")
# Tell the core to shutdown # Tell the core to shutdown
client.shutdown() client.shutdown()
self.window.quit() self.window.quit()
def on_menuitem_quit_activate(self, data=None): def on_menuitem_quit_activate(self, data=None):
log.debug("on_menuitem_quit_activate") log.debug("on_menuitem_quit_activate")
if self.config["classic_mode"]: if self.config["classic_mode"]:
@ -246,7 +246,7 @@ class MenuBar(component.Component):
except deluge.error.NoCoreError: except deluge.error.NoCoreError:
pass pass
self.window.quit() self.window.quit()
## Edit Menu ## ## Edit Menu ##
def on_menuitem_preferences_activate(self, data=None): def on_menuitem_preferences_activate(self, data=None):
log.debug("on_menuitem_preferences_activate") log.debug("on_menuitem_preferences_activate")
@ -255,31 +255,31 @@ class MenuBar(component.Component):
def on_menuitem_connectionmanager_activate(self, data=None): def on_menuitem_connectionmanager_activate(self, data=None):
log.debug("on_menuitem_connectionmanager_activate") log.debug("on_menuitem_connectionmanager_activate")
component.get("ConnectionManager").show() component.get("ConnectionManager").show()
## Torrent Menu ## ## Torrent Menu ##
def on_menuitem_pause_activate(self, data=None): def on_menuitem_pause_activate(self, data=None):
log.debug("on_menuitem_pause_activate") log.debug("on_menuitem_pause_activate")
client.pause_torrent( client.pause_torrent(
component.get("TorrentView").get_selected_torrents()) component.get("TorrentView").get_selected_torrents())
def on_menuitem_resume_activate(self, data=None): def on_menuitem_resume_activate(self, data=None):
log.debug("on_menuitem_resume_activate") log.debug("on_menuitem_resume_activate")
client.resume_torrent( client.resume_torrent(
component.get("TorrentView").get_selected_torrents()) component.get("TorrentView").get_selected_torrents())
def on_menuitem_updatetracker_activate(self, data=None): def on_menuitem_updatetracker_activate(self, data=None):
log.debug("on_menuitem_updatetracker_activate") log.debug("on_menuitem_updatetracker_activate")
client.force_reannounce( client.force_reannounce(
component.get("TorrentView").get_selected_torrents()) component.get("TorrentView").get_selected_torrents())
def on_menuitem_edittrackers_activate(self, data=None): def on_menuitem_edittrackers_activate(self, data=None):
log.debug("on_menuitem_edittrackers_activate") log.debug("on_menuitem_edittrackers_activate")
from edittrackersdialog import EditTrackersDialog from edittrackersdialog import EditTrackersDialog
dialog = EditTrackersDialog( dialog = EditTrackersDialog(
component.get("TorrentView").get_selected_torrent(), component.get("TorrentView").get_selected_torrent(),
component.get("MainWindow").window) component.get("MainWindow").window)
dialog.run() dialog.run()
def on_menuitem_remove_session_activate(self, data=None): def on_menuitem_remove_session_activate(self, data=None):
log.debug("on_menuitem_remove_session_activate") log.debug("on_menuitem_remove_session_activate")
from removetorrentdialog import RemoveTorrentDialog from removetorrentdialog import RemoveTorrentDialog
@ -299,7 +299,7 @@ class MenuBar(component.Component):
RemoveTorrentDialog( RemoveTorrentDialog(
component.get("TorrentView").get_selected_torrents(), component.get("TorrentView").get_selected_torrents(),
remove_data=True).run() remove_data=True).run()
def on_menuitem_remove_both_activate(self, data=None): def on_menuitem_remove_both_activate(self, data=None):
log.debug("on_menuitem_remove_both_activate") log.debug("on_menuitem_remove_both_activate")
from removetorrentdialog import RemoveTorrentDialog from removetorrentdialog import RemoveTorrentDialog
@ -312,7 +312,7 @@ class MenuBar(component.Component):
log.debug("on_menuitem_recheck_activate") log.debug("on_menuitem_recheck_activate")
client.force_recheck( client.force_recheck(
component.get("TorrentView").get_selected_torrents()) component.get("TorrentView").get_selected_torrents())
def on_menuitem_open_folder_activate(self, data=None): def on_menuitem_open_folder_activate(self, data=None):
log.debug("on_menuitem_open_folder") log.debug("on_menuitem_open_folder")
def _on_torrent_status(status): def _on_torrent_status(status):
@ -342,11 +342,11 @@ class MenuBar(component.Component):
else: else:
client.get_torrent_status(self.show_move_storage_dialog, component.get("TorrentView").get_selected_torrent(), ["save_path"]) client.get_torrent_status(self.show_move_storage_dialog, component.get("TorrentView").get_selected_torrent(), ["save_path"])
client.force_call(False) client.force_call(False)
def show_move_storage_dialog(self, status): def show_move_storage_dialog(self, status):
log.debug("show_move_storage_dialog") log.debug("show_move_storage_dialog")
glade = gtk.glade.XML( glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/move_storage_dialog.glade")) "glade/move_storage_dialog.glade"))
dialog = glade.get_widget("move_storage_dialog") dialog = glade.get_widget("move_storage_dialog")
dialog.set_transient_for(self.window.window) dialog.set_transient_for(self.window.window)
@ -360,7 +360,7 @@ class MenuBar(component.Component):
dialog.hide() dialog.hide()
dialog.connect("response", _on_response_event) dialog.connect("response", _on_response_event)
dialog.show() dialog.show()
def on_menuitem_queue_top_activate(self, value): def on_menuitem_queue_top_activate(self, value):
log.debug("on_menuitem_queue_top_activate") log.debug("on_menuitem_queue_top_activate")
client.queue_top(None, component.get("TorrentView").get_selected_torrents()) client.queue_top(None, component.get("TorrentView").get_selected_torrents())
@ -368,15 +368,15 @@ class MenuBar(component.Component):
def on_menuitem_queue_up_activate(self, value): def on_menuitem_queue_up_activate(self, value):
log.debug("on_menuitem_queue_up_activate") log.debug("on_menuitem_queue_up_activate")
client.queue_up(None, component.get("TorrentView").get_selected_torrents()) client.queue_up(None, component.get("TorrentView").get_selected_torrents())
def on_menuitem_queue_down_activate(self, value): def on_menuitem_queue_down_activate(self, value):
log.debug("on_menuitem_queue_down_activate") log.debug("on_menuitem_queue_down_activate")
client.queue_down(None, component.get("TorrentView").get_selected_torrents()) client.queue_down(None, component.get("TorrentView").get_selected_torrents())
def on_menuitem_queue_bottom_activate(self, value): def on_menuitem_queue_bottom_activate(self, value):
log.debug("on_menuitem_queue_bottom_activate") log.debug("on_menuitem_queue_bottom_activate")
client.queue_bottom(None, component.get("TorrentView").get_selected_torrents()) client.queue_bottom(None, component.get("TorrentView").get_selected_torrents())
## View Menu ## ## View Menu ##
def on_menuitem_toolbar_toggled(self, value): def on_menuitem_toolbar_toggled(self, value):
log.debug("on_menuitem_toolbar_toggled") log.debug("on_menuitem_toolbar_toggled")
@ -385,7 +385,7 @@ class MenuBar(component.Component):
def on_menuitem_labels_toggled(self, value): def on_menuitem_labels_toggled(self, value):
log.debug("on_menuitem_labels_toggled") log.debug("on_menuitem_labels_toggled")
component.get("SideBar").visible(value.get_active()) component.get("SideBar").visible(value.get_active())
def on_menuitem_statusbar_toggled(self, value): def on_menuitem_statusbar_toggled(self, value):
log.debug("on_menuitem_statusbar_toggled") log.debug("on_menuitem_statusbar_toggled")
component.get("StatusBar").visible(value.get_active()) component.get("StatusBar").visible(value.get_active())
@ -419,7 +419,7 @@ class MenuBar(component.Component):
if widget.name in funcs.keys(): if widget.name in funcs.keys():
for torrent in component.get("TorrentView").get_selected_torrents(): for torrent in component.get("TorrentView").get_selected_torrents():
funcs[widget.name](torrent, -1) funcs[widget.name](torrent, -1)
def on_menuitem_set_other(self, widget): def on_menuitem_set_other(self, widget):
log.debug("widget.name: %s", widget.name) log.debug("widget.name: %s", widget.name)
funcs = { funcs = {
@ -429,7 +429,7 @@ class MenuBar(component.Component):
"menuitem_upload_slots": client.set_torrent_max_upload_slots "menuitem_upload_slots": client.set_torrent_max_upload_slots
} }
dialog_glade = gtk.glade.XML( dialog_glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/dgtkpopups.glade")) "glade/dgtkpopups.glade"))
speed_dialog = dialog_glade.get_widget("speed_dialog") speed_dialog = dialog_glade.get_widget("speed_dialog")
spin_title = dialog_glade.get_widget("spin_title") spin_title = dialog_glade.get_widget("spin_title")
@ -441,7 +441,7 @@ class MenuBar(component.Component):
spin_title.set_text(_("Set Max Connections:")) spin_title.set_text(_("Set Max Connections:"))
elif widget.name == "menuitem_upload_slots": elif widget.name == "menuitem_upload_slots":
spin_title.set_text(_("Set Max Upload Slots:")) spin_title.set_text(_("Set Max Upload Slots:"))
spin_speed = dialog_glade.get_widget("spin_speed") spin_speed = dialog_glade.get_widget("spin_speed")
spin_speed.set_value(-1) spin_speed.set_value(-1)
spin_speed.select_region(0, -1) spin_speed.select_region(0, -1)
@ -457,7 +457,7 @@ class MenuBar(component.Component):
speed_dialog.destroy() speed_dialog.destroy()
if widget.name in funcs.keys(): if widget.name in funcs.keys():
for torrent in component.get("TorrentView").get_selected_torrents(): for torrent in component.get("TorrentView").get_selected_torrents():
funcs[widget.name](torrent, value) funcs[widget.name](torrent, value)
def on_menuitem_set_automanaged_on(self, widget): def on_menuitem_set_automanaged_on(self, widget):
for torrent in component.get("TorrentView").get_selected_torrents(): for torrent in component.get("TorrentView").get_selected_torrents():

View File

@ -2,19 +2,19 @@
# new_release_dialog.py # new_release_dialog.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -38,7 +38,7 @@ from deluge.configmanager import ConfigManager
class NewReleaseDialog: class NewReleaseDialog:
def __init__(self): def __init__(self):
pass pass
def show(self, available_version): def show(self, available_version):
self.config = ConfigManager("gtkui.conf") self.config = ConfigManager("gtkui.conf")
glade = component.get("MainWindow").main_glade glade = component.get("MainWindow").main_glade
@ -52,14 +52,14 @@ class NewReleaseDialog:
"clicked", self._on_button_goto_downloads) "clicked", self._on_button_goto_downloads)
glade.get_widget("button_close_new_release").connect( glade.get_widget("button_close_new_release").connect(
"clicked", self._on_button_close_new_release) "clicked", self._on_button_close_new_release)
self.dialog.show_all() self.dialog.show_all()
def _on_button_goto_downloads(self, widget): def _on_button_goto_downloads(self, widget):
deluge.common.open_url_in_browser("http://deluge-torrent.org") deluge.common.open_url_in_browser("http://deluge-torrent.org")
self.config["show_new_releases"] = not self.chk_not_show_dialog.get_active() self.config["show_new_releases"] = not self.chk_not_show_dialog.get_active()
self.dialog.destroy() self.dialog.destroy()
def _on_button_close_new_release(self, widget): def _on_button_close_new_release(self, widget):
self.config["show_new_releases"] = not self.chk_not_show_dialog.get_active() self.config["show_new_releases"] = not self.chk_not_show_dialog.get_active()
self.dialog.destroy() self.dialog.destroy()

View File

@ -2,19 +2,19 @@
# peers_tab.py # peers_tab.py
# #
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -53,7 +53,7 @@ class ColumnState:
self.width = width self.width = width
self.sort = sort self.sort = sort
self.sort_order = sort_order self.sort_order = sort_order
class PeersTab(Tab): class PeersTab(Tab):
def __init__(self): def __init__(self):
Tab.__init__(self) Tab.__init__(self)
@ -62,19 +62,19 @@ class PeersTab(Tab):
self._name = "Peers" self._name = "Peers"
self._child_widget = glade.get_widget("peers_tab") self._child_widget = glade.get_widget("peers_tab")
self._tab_label = glade.get_widget("peers_tab_label") self._tab_label = glade.get_widget("peers_tab_label")
self.listview = glade.get_widget("peers_listview") self.listview = glade.get_widget("peers_listview")
# country pixbuf, ip, client, downspeed, upspeed, country code, int_ip, seed/peer icon # country pixbuf, ip, client, downspeed, upspeed, country code, int_ip, seed/peer icon
self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, str, int, int, str, gobject.TYPE_UINT, gtk.gdk.Pixbuf) self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, str, int, int, str, gobject.TYPE_UINT, gtk.gdk.Pixbuf)
self.cached_flag_pixbufs = {} self.cached_flag_pixbufs = {}
self.seed_pixbuf = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("seeding16.png")) self.seed_pixbuf = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("seeding16.png"))
self.peer_pixbuf = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("downloading16.png")) self.peer_pixbuf = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("downloading16.png"))
# key is ip address, item is row iter # key is ip address, item is row iter
self.peers = {} self.peers = {}
# Country column # Country column
column = gtk.TreeViewColumn() column = gtk.TreeViewColumn()
render = gtk.CellRendererPixbuf() render = gtk.CellRendererPixbuf()
column.pack_start(render, False) column.pack_start(render, False)
@ -86,8 +86,8 @@ class PeersTab(Tab):
column.set_min_width(10) column.set_min_width(10)
column.set_reorderable(True) column.set_reorderable(True)
self.listview.append_column(column) self.listview.append_column(column)
# Address column # Address column
column = gtk.TreeViewColumn(_("Address")) column = gtk.TreeViewColumn(_("Address"))
render = gtk.CellRendererPixbuf() render = gtk.CellRendererPixbuf()
column.pack_start(render, False) column.pack_start(render, False)
@ -103,7 +103,7 @@ class PeersTab(Tab):
column.set_reorderable(True) column.set_reorderable(True)
self.listview.append_column(column) self.listview.append_column(column)
# Client column # Client column
column = gtk.TreeViewColumn(_("Client")) column = gtk.TreeViewColumn(_("Client"))
render = gtk.CellRendererText() render = gtk.CellRendererText()
column.pack_start(render, False) column.pack_start(render, False)
@ -127,8 +127,8 @@ class PeersTab(Tab):
column.set_expand(False) column.set_expand(False)
column.set_min_width(10) column.set_min_width(10)
column.set_reorderable(True) column.set_reorderable(True)
self.listview.append_column(column) self.listview.append_column(column)
# Up Speed column # Up Speed column
column = gtk.TreeViewColumn(_("Up Speed")) column = gtk.TreeViewColumn(_("Up Speed"))
render = gtk.CellRendererText() render = gtk.CellRendererText()
@ -140,21 +140,21 @@ class PeersTab(Tab):
column.set_expand(False) column.set_expand(False)
column.set_min_width(10) column.set_min_width(10)
column.set_reorderable(True) column.set_reorderable(True)
self.listview.append_column(column) self.listview.append_column(column)
self.listview.set_model(self.liststore) self.listview.set_model(self.liststore)
self.load_state() self.load_state()
self.torrent_id = None self.torrent_id = None
def save_state(self): def save_state(self):
filename = "peers_tab.state" filename = "peers_tab.state"
state = [] state = []
for index, column in enumerate(self.listview.get_columns()): for index, column in enumerate(self.listview.get_columns()):
state.append(ColumnState(column.get_title(), index, column.get_width(), state.append(ColumnState(column.get_title(), index, column.get_width(),
column.get_sort_indicator(), int(column.get_sort_order()))) column.get_sort_indicator(), int(column.get_sort_order())))
# Get the config location for saving the state file # Get the config location for saving the state file
config_location = ConfigManager("gtkui.conf")["config_location"] config_location = ConfigManager("gtkui.conf")["config_location"]
@ -165,13 +165,13 @@ class PeersTab(Tab):
state_file.close() state_file.close()
except IOError, e: except IOError, e:
log.warning("Unable to save state file: %s", e) log.warning("Unable to save state file: %s", e)
def load_state(self): def load_state(self):
filename = "peers_tab.state" filename = "peers_tab.state"
# Get the config location for loading the state file # Get the config location for loading the state file
config_location = ConfigManager("gtkui.conf")["config_location"] config_location = ConfigManager("gtkui.conf")["config_location"]
state = None state = None
try: try:
log.debug("Loading PeersTab state file: %s", filename) log.debug("Loading PeersTab state file: %s", filename)
state_file = open(os.path.join(config_location, filename), "rb") state_file = open(os.path.join(config_location, filename), "rb")
@ -179,14 +179,14 @@ class PeersTab(Tab):
state_file.close() state_file.close()
except (EOFError, IOError), e: except (EOFError, IOError), e:
log.warning("Unable to load state file: %s", e) log.warning("Unable to load state file: %s", e)
if state == None: if state == None:
return return
if len(state) != len(self.listview.get_columns()): if len(state) != len(self.listview.get_columns()):
log.warning("peers_tab.state is not compatible! rejecting..") log.warning("peers_tab.state is not compatible! rejecting..")
return return
for column_state in state: for column_state in state:
# Find matching columns in the listview # Find matching columns in the listview
for (index, column) in enumerate(self.listview.get_columns()): for (index, column) in enumerate(self.listview.get_columns()):
@ -203,7 +203,7 @@ class PeersTab(Tab):
self.listview.move_column_after(column, None) self.listview.move_column_after(column, None)
else: else:
self.listview.move_column_after(column, self.listview.get_columns()[column_state.position - 1]) self.listview.move_column_after(column, self.listview.get_columns()[column_state.position - 1])
def update(self): def update(self):
# Get the first selected torrent # Get the first selected torrent
torrent_id = component.get("TorrentView").get_selected_torrents() torrent_id = component.get("TorrentView").get_selected_torrents()
@ -215,7 +215,7 @@ class PeersTab(Tab):
# No torrent is selected in the torrentview # No torrent is selected in the torrentview
self.liststore.clear() self.liststore.clear()
return return
if torrent_id != self.torrent_id: if torrent_id != self.torrent_id:
# We only want to do this if the torrent_id has changed # We only want to do this if the torrent_id has changed
self.liststore.clear() self.liststore.clear()
@ -227,20 +227,20 @@ class PeersTab(Tab):
def get_flag_pixbuf(self, country): def get_flag_pixbuf(self, country):
if country == " ": if country == " ":
return None return None
if not self.cached_flag_pixbufs.has_key(country): if not self.cached_flag_pixbufs.has_key(country):
# We haven't created a pixbuf for this country yet # We haven't created a pixbuf for this country yet
try: try:
self.cached_flag_pixbufs[country] = gtk.gdk.pixbuf_new_from_file( self.cached_flag_pixbufs[country] = gtk.gdk.pixbuf_new_from_file(
pkg_resources.resource_filename( pkg_resources.resource_filename(
"deluge", "deluge",
os.path.join("data", "pixmaps", "flags", country.lower() + ".png"))) os.path.join("data", "pixmaps", "flags", country.lower() + ".png")))
except Exception, e: except Exception, e:
log.debug("Unable to load flag: %s", e) log.debug("Unable to load flag: %s", e)
return None return None
return self.cached_flag_pixbufs[country] return self.cached_flag_pixbufs[country]
def _on_get_torrent_status(self, status): def _on_get_torrent_status(self, status):
new_ips = set() new_ips = set()
for peer in status["peers"]: for peer in status["peers"]:
@ -267,10 +267,10 @@ class PeersTab(Tab):
if icon != values[3]: if icon != values[3]:
self.liststore.set_value(row, 7, icon) self.liststore.set_value(row, 7, icon)
else: else:
# Peer is not in list so we need to add it # Peer is not in list so we need to add it
# Create an int IP address for sorting purposes # Create an int IP address for sorting purposes
ip_int = sum([int(byte) << shift ip_int = sum([int(byte) << shift
for byte, shift in izip(peer["ip"].split(":")[0].split("."), (24, 16, 8, 0))]) for byte, shift in izip(peer["ip"].split(":")[0].split("."), (24, 16, 8, 0))])
@ -279,19 +279,19 @@ class PeersTab(Tab):
icon = self.seed_pixbuf icon = self.seed_pixbuf
else: else:
icon = self.peer_pixbuf icon = self.peer_pixbuf
row = self.liststore.append([ row = self.liststore.append([
self.get_flag_pixbuf(peer["country"]), self.get_flag_pixbuf(peer["country"]),
peer["ip"], peer["ip"],
peer["client"], peer["client"],
peer["down_speed"], peer["down_speed"],
peer["up_speed"], peer["up_speed"],
peer["country"], peer["country"],
ip_int, ip_int,
icon]) icon])
self.peers[peer["ip"]] = row self.peers[peer["ip"]] = row
# Now we need to remove any ips that were not in status["peers"] list # Now we need to remove any ips that were not in status["peers"] list
for ip in set(self.peers.keys()).difference(new_ips): for ip in set(self.peers.keys()).difference(new_ips):
self.liststore.remove(self.peers[ip]) self.liststore.remove(self.peers[ip])

View File

@ -2,19 +2,19 @@
# pluginmanager.py # pluginmanager.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -37,7 +37,7 @@ from deluge.ui.client import aclient as client
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.log import LOG as log from deluge.log import LOG as log
class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
component.Component): component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "PluginManager", depend=["Signals"]) component.Component.__init__(self, "PluginManager", depend=["Signals"])
@ -56,14 +56,14 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
self.hooks[hook].append(function) self.hooks[hook].append(function)
except KeyError: except KeyError:
log.warning("Plugin attempting to register invalid hook.") log.warning("Plugin attempting to register invalid hook.")
def deregister_hook(self, hook, function): def deregister_hook(self, hook, function):
"""Deregisters a hook function""" """Deregisters a hook function"""
try: try:
self.hooks[hook].remove(function) self.hooks[hook].remove(function)
except: except:
log.warning("Unable to deregister hook %s", hook) log.warning("Unable to deregister hook %s", hook)
def start(self): def start(self):
"""Start the plugin manager""" """Start the plugin manager"""
# Update the enabled_plugins from the core # Update the enabled_plugins from the core
@ -72,13 +72,13 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
def stop(self): def stop(self):
# Disable the plugins # Disable the plugins
self.disable_plugins() self.disable_plugins()
def update(self): def update(self):
# We call the plugins' update() method every second # We call the plugins' update() method every second
for plugin in self.plugins.values(): for plugin in self.plugins.values():
if hasattr(plugin, "update"): if hasattr(plugin, "update"):
plugin.update() plugin.update()
def _on_get_enabled_plugins(self, enabled_plugins): def _on_get_enabled_plugins(self, enabled_plugins):
log.debug("Core has these plugins enabled: %s", enabled_plugins) log.debug("Core has these plugins enabled: %s", enabled_plugins)
for plugin in enabled_plugins: for plugin in enabled_plugins:
@ -92,7 +92,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
log.debug("run_on_show_prefs") log.debug("run_on_show_prefs")
for function in self.hooks["on_show_prefs"]: for function in self.hooks["on_show_prefs"]:
function() function()
def run_on_apply_prefs(self): def run_on_apply_prefs(self):
"""This hook is run after the user clicks Apply or OK in the preferences """This hook is run after the user clicks Apply or OK in the preferences
dialog. dialog.
@ -100,39 +100,39 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
log.debug("run_on_apply_prefs") log.debug("run_on_apply_prefs")
for function in self.hooks["on_apply_prefs"]: for function in self.hooks["on_apply_prefs"]:
function() function()
## Plugin functions.. will likely move to own class.. ## Plugin functions.. will likely move to own class..
def add_torrentview_text_column(self, *args, **kwargs): def add_torrentview_text_column(self, *args, **kwargs):
return component.get("TorrentView").add_text_column(*args, **kwargs) return component.get("TorrentView").add_text_column(*args, **kwargs)
def remove_torrentview_column(self, *args): def remove_torrentview_column(self, *args):
return component.get("TorrentView").remove_column(*args) return component.get("TorrentView").remove_column(*args)
def add_toolbar_separator(self): def add_toolbar_separator(self):
return component.get("ToolBar").add_separator() return component.get("ToolBar").add_separator()
def add_toolbar_button(self, *args, **kwargs): def add_toolbar_button(self, *args, **kwargs):
return component.get("ToolBar").add_toolbutton(*args, **kwargs) return component.get("ToolBar").add_toolbutton(*args, **kwargs)
def remove_toolbar_button(self, *args): def remove_toolbar_button(self, *args):
return component.get("ToolBar").remove(*args) return component.get("ToolBar").remove(*args)
def add_torrentmenu_menu(self, *args): def add_torrentmenu_menu(self, *args):
return component.get("MenuBar").torrentmenu.append(*args) return component.get("MenuBar").torrentmenu.append(*args)
def add_torrentmenu_separator(self): def add_torrentmenu_separator(self):
return component.get("MenuBar").add_torrentmenu_separator() return component.get("MenuBar").add_torrentmenu_separator()
def remove_torrentmenu_item(self, *args): def remove_torrentmenu_item(self, *args):
return component.get("MenuBar").torrentmenu.remove(*args) return component.get("MenuBar").torrentmenu.remove(*args)
def add_preferences_page(self, *args): def add_preferences_page(self, *args):
return component.get("Preferences").add_page(*args) return component.get("Preferences").add_page(*args)
def remove_preferences_page(self, *args): def remove_preferences_page(self, *args):
return component.get("Preferences").remove_page(*args) return component.get("Preferences").remove_page(*args)
def update_torrent_view(self, *args): def update_torrent_view(self, *args):
return component.get("TorrentView").update(*args) return component.get("TorrentView").update(*args)

View File

@ -2,19 +2,19 @@
# preferences.py # preferences.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -63,7 +63,7 @@ class Preferences(component.Component):
self.treeview.append_column(column) self.treeview.append_column(column)
# Add the default categories # Add the default categories
i = 0 i = 0
for category in ["Downloads", "Network", "Bandwidth", "Interface", for category in ["Downloads", "Network", "Bandwidth", "Interface",
"Other", "Daemon", "Queue", "Plugins"]: "Other", "Daemon", "Queue", "Plugins"]:
self.liststore.append([i, category]) self.liststore.append([i, category])
i += 1 i += 1
@ -79,15 +79,15 @@ class Preferences(component.Component):
gtk.TreeViewColumn(_("Enabled"), render, active=1)) gtk.TreeViewColumn(_("Enabled"), render, active=1))
self.plugin_listview.append_column( self.plugin_listview.append_column(
gtk.TreeViewColumn(_("Plugin"), gtk.CellRendererText(), text=0)) gtk.TreeViewColumn(_("Plugin"), gtk.CellRendererText(), text=0))
# Connect to the 'changed' event of TreeViewSelection to get selection # Connect to the 'changed' event of TreeViewSelection to get selection
# changes. # changes.
self.treeview.get_selection().connect("changed", self.treeview.get_selection().connect("changed",
self.on_selection_changed) self.on_selection_changed)
self.plugin_listview.get_selection().connect("changed", self.plugin_listview.get_selection().connect("changed",
self.on_plugin_selection_changed) self.on_plugin_selection_changed)
self.glade.signal_autoconnect({ self.glade.signal_autoconnect({
"on_pref_dialog_delete_event": self.on_pref_dialog_delete_event, "on_pref_dialog_delete_event": self.on_pref_dialog_delete_event,
"on_button_ok_clicked": self.on_button_ok_clicked, "on_button_ok_clicked": self.on_button_ok_clicked,
@ -97,10 +97,10 @@ class Preferences(component.Component):
"on_test_port_clicked": self.on_test_port_clicked "on_test_port_clicked": self.on_test_port_clicked
}) })
# These get updated by requests done to the core # These get updated by requests done to the core
self.all_plugins = [] self.all_plugins = []
self.enabled_plugins = [] self.enabled_plugins = []
def __del__(self): def __del__(self):
del self.gtkui_config del self.gtkui_config
@ -125,9 +125,9 @@ class Preferences(component.Component):
vbox.pack_start(align, False, False, 0) vbox.pack_start(align, False, False, 0)
scrolled = gtk.ScrolledWindow() scrolled = gtk.ScrolledWindow()
viewport = gtk.Viewport() viewport = gtk.Viewport()
viewport.set_shadow_type(gtk.SHADOW_NONE) viewport.set_shadow_type(gtk.SHADOW_NONE)
viewport.add(vbox) viewport.add(vbox)
scrolled.add(viewport) scrolled.add(viewport)
scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolled.show_all() scrolled.show_all()
# Add this page to the notebook # Add this page to the notebook
@ -139,7 +139,7 @@ class Preferences(component.Component):
"""Removes a page from the notebook""" """Removes a page from the notebook"""
self.page_num_to_remove = None self.page_num_to_remove = None
self.iter_to_remove = None self.iter_to_remove = None
def check_row(model, path, iter, user_data): def check_row(model, path, iter, user_data):
row_name = model.get_value(iter, 1) row_name = model.get_value(iter, 1)
if row_name == user_data: if row_name == user_data:
@ -147,26 +147,26 @@ class Preferences(component.Component):
self.page_num_to_remove = model.get_value(iter, 0) self.page_num_to_remove = model.get_value(iter, 0)
self.iter_to_remove = iter self.iter_to_remove = iter
return return
self.liststore.foreach(check_row, name) self.liststore.foreach(check_row, name)
# Remove the page and row # Remove the page and row
if self.page_num_to_remove != None: if self.page_num_to_remove != None:
self.notebook.remove_page(self.page_num_to_remove) self.notebook.remove_page(self.page_num_to_remove)
if self.iter_to_remove != None: if self.iter_to_remove != None:
self.liststore.remove(self.iter_to_remove) self.liststore.remove(self.iter_to_remove)
def _on_get_config(self, config): def _on_get_config(self, config):
self.core_config = config self.core_config = config
def _on_get_available_plugins(self, plugins): def _on_get_available_plugins(self, plugins):
self.all_plugins = plugins self.all_plugins = plugins
def _on_get_enabled_plugins(self, plugins): def _on_get_enabled_plugins(self, plugins):
self.enabled_plugins = plugins self.enabled_plugins = plugins
def _on_get_listen_port(self, port): def _on_get_listen_port(self, port):
self.active_port = port self.active_port = port
def show(self, page=None): def show(self, page=None):
"""Page should be the string in the left list.. ie, 'Network' or 'Bandwidth'""" """Page should be the string in the left list.. ie, 'Network' or 'Bandwidth'"""
if page != None: if page != None:
@ -174,7 +174,7 @@ class Preferences(component.Component):
if page == string: if page == string:
self.treeview.get_selection().select_path(index) self.treeview.get_selection().select_path(index)
break break
# Update the preferences dialog to reflect current config settings # Update the preferences dialog to reflect current config settings
self.core_config = {} self.core_config = {}
try: try:
@ -208,7 +208,7 @@ class Preferences(component.Component):
"radio_full_allocation": \ "radio_full_allocation": \
("not_active", self.core_config["compact_allocation"]), ("not_active", self.core_config["compact_allocation"]),
"chk_prioritize_first_last_pieces": \ "chk_prioritize_first_last_pieces": \
("active", ("active",
self.core_config["prioritize_first_last_pieces"]), self.core_config["prioritize_first_last_pieces"]),
"spin_port_min": ("value", self.core_config["listen_ports"][0]), "spin_port_min": ("value", self.core_config["listen_ports"][0]),
"spin_port_max": ("value", self.core_config["listen_ports"][1]), "spin_port_max": ("value", self.core_config["listen_ports"][1]),
@ -264,7 +264,7 @@ class Preferences(component.Component):
"spin_share_ratio": ("value", self.core_config["stop_seed_ratio"]), "spin_share_ratio": ("value", self.core_config["stop_seed_ratio"]),
"chk_remove_ratio": ("active", self.core_config["remove_seed_at_ratio"]) "chk_remove_ratio": ("active", self.core_config["remove_seed_at_ratio"])
} }
# Change a few widgets if we're connected to a remote host # Change a few widgets if we're connected to a remote host
if not client.is_localhost(): if not client.is_localhost():
self.glade.get_widget("entry_download_path").show() self.glade.get_widget("entry_download_path").show()
@ -276,7 +276,7 @@ class Preferences(component.Component):
self.glade.get_widget("move_completed_path_button").hide() self.glade.get_widget("move_completed_path_button").hide()
core_widgets.pop("move_completed_path_button") core_widgets.pop("move_completed_path_button")
core_widgets["entry_move_completed_path"] = ("text", self.core_config["move_completed_path"]) core_widgets["entry_move_completed_path"] = ("text", self.core_config["move_completed_path"])
self.glade.get_widget("entry_torrents_path").show() self.glade.get_widget("entry_torrents_path").show()
self.glade.get_widget("torrent_files_button").hide() self.glade.get_widget("torrent_files_button").hide()
core_widgets.pop("torrent_files_button") core_widgets.pop("torrent_files_button")
@ -294,7 +294,7 @@ class Preferences(component.Component):
self.glade.get_widget("entry_torrents_path").hide() self.glade.get_widget("entry_torrents_path").hide()
self.glade.get_widget("torrent_files_button").show() self.glade.get_widget("torrent_files_button").show()
self.glade.get_widget("entry_autoadd").hide() self.glade.get_widget("entry_autoadd").hide()
self.glade.get_widget("folder_autoadd").show() self.glade.get_widget("folder_autoadd").show()
# Update the widgets accordingly # Update the widgets accordingly
for key in core_widgets.keys(): for key in core_widgets.keys():
@ -305,7 +305,7 @@ class Preferences(component.Component):
for child in widget.get_children(): for child in widget.get_children():
child.set_sensitive(True) child.set_sensitive(True)
widget.set_sensitive(True) widget.set_sensitive(True)
if modifier == "filename": if modifier == "filename":
if value: if value:
try: try:
@ -402,11 +402,11 @@ class Preferences(component.Component):
self.gtkui_config["lock_tray"]) self.gtkui_config["lock_tray"])
self.glade.get_widget("chk_classic_mode").set_active( self.glade.get_widget("chk_classic_mode").set_active(
self.gtkui_config["classic_mode"]) self.gtkui_config["classic_mode"])
## Other tab ## ## Other tab ##
self.glade.get_widget("chk_show_new_releases").set_active( self.glade.get_widget("chk_show_new_releases").set_active(
self.gtkui_config["show_new_releases"]) self.gtkui_config["show_new_releases"])
## Plugins tab ## ## Plugins tab ##
all_plugins = self.all_plugins all_plugins = self.all_plugins
enabled_plugins = self.enabled_plugins enabled_plugins = self.enabled_plugins
@ -421,22 +421,22 @@ class Preferences(component.Component):
row = self.plugin_liststore.append() row = self.plugin_liststore.append()
self.plugin_liststore.set_value(row, 0, plugin) self.plugin_liststore.set_value(row, 0, plugin)
self.plugin_liststore.set_value(row, 1, enabled) self.plugin_liststore.set_value(row, 1, enabled)
component.get("PluginManager").run_on_show_prefs() component.get("PluginManager").run_on_show_prefs()
# Now show the dialog # Now show the dialog
self.pref_dialog.show() self.pref_dialog.show()
def set_config(self): def set_config(self):
"""Sets all altered config values in the core""" """Sets all altered config values in the core"""
try: try:
from hashlib import sha1 as sha_hash from hashlib import sha1 as sha_hash
except ImportError: except ImportError:
from sha import new as sha_hash from sha import new as sha_hash
# Get the values from the dialog # Get the values from the dialog
new_core_config = {} new_core_config = {}
new_gtkui_config = {} new_gtkui_config = {}
## Downloads tab ## ## Downloads tab ##
new_gtkui_config["interactive_add"] = \ new_gtkui_config["interactive_add"] = \
self.glade.get_widget("chk_show_dialog").get_active() self.glade.get_widget("chk_show_dialog").get_active()
@ -460,7 +460,7 @@ class Preferences(component.Component):
self.glade.get_widget("entry_move_completed_path").get_text() self.glade.get_widget("entry_move_completed_path").get_text()
new_core_config["torrentfiles_location"] = \ new_core_config["torrentfiles_location"] = \
self.glade.get_widget("entry_torrents_path").get_text() self.glade.get_widget("entry_torrents_path").get_text()
new_core_config["autoadd_enable"] = \ new_core_config["autoadd_enable"] = \
self.glade.get_widget("chk_autoadd").get_active() self.glade.get_widget("chk_autoadd").get_active()
if client.is_localhost(): if client.is_localhost():
@ -469,7 +469,7 @@ class Preferences(component.Component):
else: else:
new_core_config["autoadd_location"] = \ new_core_config["autoadd_location"] = \
self.glade.get_widget("entry_autoadd").get_text() self.glade.get_widget("entry_autoadd").get_text()
new_core_config["compact_allocation"] = \ new_core_config["compact_allocation"] = \
self.glade.get_widget("radio_compact_allocation").get_active() self.glade.get_widget("radio_compact_allocation").get_active()
new_core_config["prioritize_first_last_pieces"] = \ new_core_config["prioritize_first_last_pieces"] = \
@ -501,7 +501,7 @@ class Preferences(component.Component):
self.glade.get_widget("combo_enclevel").get_active() self.glade.get_widget("combo_enclevel").get_active()
new_core_config["enc_prefer_rc4"] = \ new_core_config["enc_prefer_rc4"] = \
self.glade.get_widget("chk_pref_rc4").get_active() self.glade.get_widget("chk_pref_rc4").get_active()
## Bandwidth tab ## ## Bandwidth tab ##
new_core_config["max_connections_global"] = \ new_core_config["max_connections_global"] = \
self.glade.get_widget( self.glade.get_widget(
@ -532,7 +532,7 @@ class Preferences(component.Component):
"spin_max_download_per_torrent").get_value() "spin_max_download_per_torrent").get_value()
new_core_config["ignore_limits_on_local_network"] = \ new_core_config["ignore_limits_on_local_network"] = \
self.glade.get_widget("chk_ignore_limits_on_local_network").get_active() self.glade.get_widget("chk_ignore_limits_on_local_network").get_active()
## Interface tab ## ## Interface tab ##
new_gtkui_config["enable_system_tray"] = \ new_gtkui_config["enable_system_tray"] = \
self.glade.get_widget("chk_use_tray").get_active() self.glade.get_widget("chk_use_tray").get_active()
@ -548,8 +548,8 @@ class Preferences(component.Component):
new_gtkui_config["tray_password"] = passhex new_gtkui_config["tray_password"] = passhex
new_gtkui_config["classic_mode"] = \ new_gtkui_config["classic_mode"] = \
self.glade.get_widget("chk_classic_mode").get_active() self.glade.get_widget("chk_classic_mode").get_active()
## Other tab ## ## Other tab ##
new_gtkui_config["show_new_releases"] = \ new_gtkui_config["show_new_releases"] = \
self.glade.get_widget("chk_show_new_releases").get_active() self.glade.get_widget("chk_show_new_releases").get_active()
new_core_config["send_info"] = \ new_core_config["send_info"] = \
@ -562,7 +562,7 @@ class Preferences(component.Component):
self.glade.get_widget("chk_allow_remote_connections").get_active() self.glade.get_widget("chk_allow_remote_connections").get_active()
new_core_config["new_release_check"] = \ new_core_config["new_release_check"] = \
self.glade.get_widget("chk_new_releases").get_active() self.glade.get_widget("chk_new_releases").get_active()
## Queue tab ## ## Queue tab ##
new_core_config["queue_new_to_top"] = \ new_core_config["queue_new_to_top"] = \
self.glade.get_widget("chk_queue_new_top").get_active() self.glade.get_widget("chk_queue_new_top").get_active()
@ -586,16 +586,16 @@ class Preferences(component.Component):
self.glade.get_widget("spin_seed_time_ratio_limit").get_value() self.glade.get_widget("spin_seed_time_ratio_limit").get_value()
new_core_config["seed_time_limit"] = \ new_core_config["seed_time_limit"] = \
self.glade.get_widget("spin_seed_time_limit").get_value() self.glade.get_widget("spin_seed_time_limit").get_value()
# Run plugin hook to apply preferences # Run plugin hook to apply preferences
component.get("PluginManager").run_on_apply_prefs() component.get("PluginManager").run_on_apply_prefs()
# GtkUI # GtkUI
for key in new_gtkui_config.keys(): for key in new_gtkui_config.keys():
# The values do not match so this needs to be updated # The values do not match so this needs to be updated
if self.gtkui_config[key] != new_gtkui_config[key]: if self.gtkui_config[key] != new_gtkui_config[key]:
self.gtkui_config[key] = new_gtkui_config[key] self.gtkui_config[key] = new_gtkui_config[key]
# Core # Core
if client.get_core_uri() != None: if client.get_core_uri() != None:
# Only do this if we're connected to a daemon # Only do this if we're connected to a daemon
@ -610,17 +610,17 @@ class Preferences(component.Component):
client.force_call(True) client.force_call(True)
# Update the configuration # Update the configuration
self.core_config.update(config_to_set) self.core_config.update(config_to_set)
# Re-show the dialog to make sure everything has been updated # Re-show the dialog to make sure everything has been updated
self.show() self.show()
def hide(self): def hide(self):
self.pref_dialog.hide() self.pref_dialog.hide()
def on_pref_dialog_delete_event(self, widget, event): def on_pref_dialog_delete_event(self, widget, event):
self.hide() self.hide()
return True return True
def on_toggle(self, widget): def on_toggle(self, widget):
"""Handles widget sensitivity based on radio/check button values.""" """Handles widget sensitivity based on radio/check button values."""
try: try:
@ -674,7 +674,7 @@ class Preferences(component.Component):
log.debug("on_button_cancel_clicked") log.debug("on_button_cancel_clicked")
self.hide() self.hide()
return True return True
def on_selection_changed(self, treeselection): def on_selection_changed(self, treeselection):
# Show the correct notebook page based on what row is selected. # Show the correct notebook page based on what row is selected.
(model, row) = treeselection.get_selected() (model, row) = treeselection.get_selected()
@ -690,7 +690,7 @@ class Preferences(component.Component):
"http://deluge-torrent.org/test-port.php?port=%s" % port) "http://deluge-torrent.org/test-port.php?port=%s" % port)
client.get_listen_port(on_get_listen_port) client.get_listen_port(on_get_listen_port)
client.force_call() client.force_call()
def on_plugin_toggled(self, renderer, path): def on_plugin_toggled(self, renderer, path):
log.debug("on_plugin_toggled") log.debug("on_plugin_toggled")
row = self.plugin_liststore.get_iter_from_string(path) row = self.plugin_liststore.get_iter_from_string(path)
@ -703,7 +703,7 @@ class Preferences(component.Component):
else: else:
client.disable_plugin(name) client.disable_plugin(name)
component.get("PluginManager").disable_plugin(name) component.get("PluginManager").disable_plugin(name)
def on_plugin_selection_changed(self, treeselection): def on_plugin_selection_changed(self, treeselection):
log.debug("on_plugin_selection_changed") log.debug("on_plugin_selection_changed")

View File

@ -2,19 +2,19 @@
# queuedtorrents.py # queuedtorrents.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -48,16 +48,16 @@ class QueuedTorrents(component.Component):
component.Component.__init__(self, "QueuedTorrents", depend=["StatusBar"]) component.Component.__init__(self, "QueuedTorrents", depend=["StatusBar"])
self.queue = [] self.queue = []
self.status_item = None self.status_item = None
self.config = ConfigManager("gtkui.conf") self.config = ConfigManager("gtkui.conf")
self.glade = gtk.glade.XML( self.glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/queuedtorrents.glade")) "glade/queuedtorrents.glade"))
self.glade.get_widget("chk_autoadd").set_active( self.glade.get_widget("chk_autoadd").set_active(
self.config["autoadd_queued"]) self.config["autoadd_queued"])
self.dialog = self.glade.get_widget("queued_torrents_dialog") self.dialog = self.glade.get_widget("queued_torrents_dialog")
self.dialog.set_icon(deluge.common.get_logo(32)) self.dialog.set_icon(deluge.common.get_logo(32))
self.glade.signal_autoconnect({ self.glade.signal_autoconnect({
"on_button_remove_clicked": self.on_button_remove_clicked, "on_button_remove_clicked": self.on_button_remove_clicked,
"on_button_clear_clicked": self.on_button_clear_clicked, "on_button_clear_clicked": self.on_button_clear_clicked,
@ -65,14 +65,14 @@ class QueuedTorrents(component.Component):
"on_button_add_clicked": self.on_button_add_clicked, "on_button_add_clicked": self.on_button_add_clicked,
"on_chk_autoadd_toggled": self.on_chk_autoadd_toggled "on_chk_autoadd_toggled": self.on_chk_autoadd_toggled
}) })
self.treeview = self.glade.get_widget("treeview") self.treeview = self.glade.get_widget("treeview")
self.treeview.append_column( self.treeview.append_column(
gtk.TreeViewColumn(_("Torrent"), gtk.CellRendererText(), text=0)) gtk.TreeViewColumn(_("Torrent"), gtk.CellRendererText(), text=0))
self.liststore = gtk.ListStore(str, str) self.liststore = gtk.ListStore(str, str)
self.treeview.set_model(self.liststore) self.treeview.set_model(self.liststore)
def run(self): def run(self):
self.dialog.set_transient_for(component.get("MainWindow").window) self.dialog.set_transient_for(component.get("MainWindow").window)
self.dialog.show() self.dialog.show()
@ -90,12 +90,12 @@ class QueuedTorrents(component.Component):
# We only want the add button sensitive if we're connected to a host # We only want the add button sensitive if we're connected to a host
self.glade.get_widget("button_add").set_sensitive(True) self.glade.get_widget("button_add").set_sensitive(True)
self.run() self.run()
def stop(self): def stop(self):
# We only want the add button sensitive if we're connected to a host # We only want the add button sensitive if we're connected to a host
self.glade.get_widget("button_add").set_sensitive(False) self.glade.get_widget("button_add").set_sensitive(False)
self.update_status_bar() self.update_status_bar()
def add_to_queue(self, torrents): def add_to_queue(self, torrents):
"""Adds the list of torrents to the queue""" """Adds the list of torrents to the queue"""
# Add to the queue while removing duplicates # Add to the queue while removing duplicates
@ -105,10 +105,10 @@ class QueuedTorrents(component.Component):
self.liststore.clear() self.liststore.clear()
for torrent in self.queue: for torrent in self.queue:
self.liststore.append([os.path.split(torrent)[1], torrent]) self.liststore.append([os.path.split(torrent)[1], torrent])
# Update the status bar # Update the status bar
self.update_status_bar() self.update_status_bar()
def update_status_bar(self): def update_status_bar(self):
"""Attempts to update status bar""" """Attempts to update status bar"""
# If there are no queued torrents.. remove statusbar widgets and return # If there are no queued torrents.. remove statusbar widgets and return
@ -117,7 +117,7 @@ class QueuedTorrents(component.Component):
component.get("StatusBar").remove_item(self.status_item) component.get("StatusBar").remove_item(self.status_item)
self.status_item = None self.status_item = None
return False return False
try: try:
statusbar = component.get("StatusBar") statusbar = component.get("StatusBar")
except Exception, e: except Exception, e:
@ -125,18 +125,18 @@ class QueuedTorrents(component.Component):
# update it later. # update it later.
gobject.timeout_add(100, self.update_status_bar) gobject.timeout_add(100, self.update_status_bar)
return False return False
# Set the label text for statusbar # Set the label text for statusbar
if len(self.queue) > 1: if len(self.queue) > 1:
label = str(len(self.queue)) + _(" Torrents Queued") label = str(len(self.queue)) + _(" Torrents Queued")
else: else:
label = str(len(self.queue)) + _(" Torrent Queued") label = str(len(self.queue)) + _(" Torrent Queued")
# Add the statusbar items if needed, or just modify the label if they # Add the statusbar items if needed, or just modify the label if they
# have already been added. # have already been added.
if self.status_item == None: if self.status_item == None:
self.status_item = component.get("StatusBar").add_item( self.status_item = component.get("StatusBar").add_item(
stock=gtk.STOCK_SORT_DESCENDING, stock=gtk.STOCK_SORT_DESCENDING,
text=label, text=label,
callback=self.on_statusbar_click) callback=self.on_statusbar_click)
else: else:
@ -144,11 +144,11 @@ class QueuedTorrents(component.Component):
# We return False so the timer stops # We return False so the timer stops
return False return False
def on_statusbar_click(self, widget, event): def on_statusbar_click(self, widget, event):
log.debug("on_statusbar_click") log.debug("on_statusbar_click")
self.run() self.run()
def on_button_remove_clicked(self, widget): def on_button_remove_clicked(self, widget):
selected = self.treeview.get_selection().get_selected()[1] selected = self.treeview.get_selection().get_selected()[1]
if selected != None: if selected != None:
@ -173,13 +173,13 @@ class QueuedTorrents(component.Component):
component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) component.get("AddTorrentDialog").show(self.config["focus_add_dialog"])
else: else:
client.add_torrent_file([torrent_path]) client.add_torrent_file([torrent_path])
self.liststore.foreach(add_torrent, None) self.liststore.foreach(add_torrent, None)
del self.queue[:] del self.queue[:]
self.dialog.hide() self.dialog.hide()
self.update_status_bar() self.update_status_bar()
def on_chk_autoadd_toggled(self, widget): def on_chk_autoadd_toggled(self, widget):
self.config["autoadd_queued"] = widget.get_active() self.config["autoadd_queued"] = widget.get_active()

View File

@ -2,19 +2,19 @@
# removetorrentdialog.py # removetorrentdialog.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -44,27 +44,27 @@ class RemoveTorrentDialog:
self.torrent_ids = torrent_ids self.torrent_ids = torrent_ids
self.remove_torrentfile = remove_torrentfile self.remove_torrentfile = remove_torrentfile
self.remove_data = remove_data self.remove_data = remove_data
self.glade = gtk.glade.XML( self.glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/remove_torrent_dialog.glade")) "glade/remove_torrent_dialog.glade"))
self.dialog = self.glade.get_widget("remove_torrent_dialog") self.dialog = self.glade.get_widget("remove_torrent_dialog")
self.dialog.set_icon(deluge.common.get_logo(32)) self.dialog.set_icon(deluge.common.get_logo(32))
self.dialog.set_transient_for(component.get("MainWindow").window) self.dialog.set_transient_for(component.get("MainWindow").window)
self.glade.signal_autoconnect({ self.glade.signal_autoconnect({
"on_button_ok_clicked": self.on_button_ok_clicked, "on_button_ok_clicked": self.on_button_ok_clicked,
"on_button_cancel_clicked": self.on_button_cancel_clicked "on_button_cancel_clicked": self.on_button_cancel_clicked
}) })
if len(self.torrent_ids) > 1: if len(self.torrent_ids) > 1:
# We need to pluralize the dialog # We need to pluralize the dialog
self.dialog.set_title("Remove Torrents?") self.dialog.set_title("Remove Torrents?")
self.glade.get_widget("label_title").set_markup( self.glade.get_widget("label_title").set_markup(
_("<big><b>Are you sure you want to remove the selected torrents?</b></big>")) _("<big><b>Are you sure you want to remove the selected torrents?</b></big>"))
self.glade.get_widget("button_ok").set_label(_("Remove Selected Torrents")) self.glade.get_widget("button_ok").set_label(_("Remove Selected Torrents"))
if self.remove_torrentfile or self.remove_data: if self.remove_torrentfile or self.remove_data:
self.glade.get_widget("hseparator1").show() self.glade.get_widget("hseparator1").show()
if self.remove_torrentfile: if self.remove_torrentfile:
@ -77,14 +77,14 @@ class RemoveTorrentDialog:
self.dialog.destroy() self.dialog.destroy()
return return
self.dialog.show() self.dialog.show()
def on_button_ok_clicked(self, widget): def on_button_ok_clicked(self, widget):
client.remove_torrent( client.remove_torrent(
self.torrent_ids, self.remove_torrentfile, self.remove_data) self.torrent_ids, self.remove_torrentfile, self.remove_data)
# Unselect all to avoid issues with the selection changed event # Unselect all to avoid issues with the selection changed event
component.get("TorrentView").treeview.get_selection().unselect_all() component.get("TorrentView").treeview.get_selection().unselect_all()
self.dialog.destroy() self.dialog.destroy()
def on_button_cancel_clicked(self, widget): def on_button_cancel_clicked(self, widget):
self.dialog.destroy() self.dialog.destroy()

View File

@ -2,19 +2,19 @@
# sidebar.py # sidebar.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -49,12 +49,12 @@ class SideBar(component.Component):
self.scrolled = glade.get_widget("scrolledwindow_sidebar") self.scrolled = glade.get_widget("scrolledwindow_sidebar")
self.is_visible = True self.is_visible = True
self.config = ConfigManager("gtkui.conf") self.config = ConfigManager("gtkui.conf")
# Create the liststore # Create the liststore
# state str, str that's visible, icon # state str, str that's visible, icon
self.liststore = gtk.ListStore(str, str, gtk.gdk.Pixbuf) self.liststore = gtk.ListStore(str, str, gtk.gdk.Pixbuf)
self.liststore.append(["All", _("All"), None]) self.liststore.append(["All", _("All"), None])
self.liststore.append(["Downloading", _("Downloading"), self.liststore.append(["Downloading", _("Downloading"),
gtk.gdk.pixbuf_new_from_file( gtk.gdk.pixbuf_new_from_file(
deluge.common.get_pixmap("downloading16.png"))]) deluge.common.get_pixmap("downloading16.png"))])
self.liststore.append(["Seeding", _("Seeding"), self.liststore.append(["Seeding", _("Seeding"),
@ -82,29 +82,29 @@ class SideBar(component.Component):
column.pack_start(render, expand=True) column.pack_start(render, expand=True)
column.add_attribute(render, 'text', 1) column.add_attribute(render, 'text', 1)
self.label_view.append_column(column) self.label_view.append_column(column)
self.label_view.set_model(self.liststore) self.label_view.set_model(self.liststore)
self.label_view.get_selection().connect("changed", self.label_view.get_selection().connect("changed",
self.on_selection_changed) self.on_selection_changed)
# Select the 'All' label on init # Select the 'All' label on init
self.label_view.get_selection().select_iter( self.label_view.get_selection().select_iter(
self.liststore.get_iter_first()) self.liststore.get_iter_first())
# Hide if necessary # Hide if necessary
self.visible(self.config["show_sidebar"]) self.visible(self.config["show_sidebar"])
def visible(self, visible): def visible(self, visible):
if visible: if visible:
self.scrolled.show() self.scrolled.show()
else: else:
self.scrolled.hide() self.scrolled.hide()
self.hpaned.set_position(-1) self.hpaned.set_position(-1)
self.is_visible = visible self.is_visible = visible
self.config["show_sidebar"] = visible self.config["show_sidebar"] = visible
def on_selection_changed(self, selection): def on_selection_changed(self, selection):
try: try:
(model, row) = self.label_view.get_selection().get_selected() (model, row) = self.label_view.get_selection().get_selected()
@ -112,7 +112,7 @@ class SideBar(component.Component):
log.debug(e) log.debug(e)
# paths is likely None .. so lets return None # paths is likely None .. so lets return None
return None return None
value = model.get_value(row, 0) value = model.get_value(row, 0)
if value == "All": if value == "All":
component.get("TorrentView").set_filter(None, None) component.get("TorrentView").set_filter(None, None)

View File

@ -2,19 +2,19 @@
# signals.py # signals.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -46,21 +46,21 @@ class Signals(component.Component):
self.config = ConfigManager("gtkui.conf") self.config = ConfigManager("gtkui.conf")
self.config["signal_port"] = self.receiver.get_port() self.config["signal_port"] = self.receiver.get_port()
self.config.save() self.config.save()
def start(self): def start(self):
self.receiver.set_remote(not client.is_localhost()) self.receiver.set_remote(not client.is_localhost())
self.receiver.run() self.receiver.run()
self.receiver.connect_to_signal("torrent_added", self.receiver.connect_to_signal("torrent_added",
self.torrent_added_signal) self.torrent_added_signal)
self.receiver.connect_to_signal("torrent_removed", self.receiver.connect_to_signal("torrent_removed",
self.torrent_removed_signal) self.torrent_removed_signal)
self.receiver.connect_to_signal("torrent_paused", self.torrent_paused) self.receiver.connect_to_signal("torrent_paused", self.torrent_paused)
self.receiver.connect_to_signal("torrent_resumed", self.receiver.connect_to_signal("torrent_resumed",
self.torrent_resumed) self.torrent_resumed)
self.receiver.connect_to_signal("torrent_all_paused", self.receiver.connect_to_signal("torrent_all_paused",
self.torrent_all_paused) self.torrent_all_paused)
self.receiver.connect_to_signal("torrent_all_resumed", self.receiver.connect_to_signal("torrent_all_resumed",
self.torrent_all_resumed) self.torrent_all_resumed)
self.receiver.connect_to_signal("config_value_changed", self.receiver.connect_to_signal("config_value_changed",
self.config_value_changed) self.config_value_changed)
@ -74,17 +74,17 @@ class Signals(component.Component):
self.args_from_external) self.args_from_external)
self.receiver.connect_to_signal("torrent_state_changed", self.receiver.connect_to_signal("torrent_state_changed",
self.torrent_state_changed) self.torrent_state_changed)
def stop(self): def stop(self):
try: try:
self.receiver.shutdown() self.receiver.shutdown()
except: except:
pass pass
def connect_to_signal(self, signal, callback): def connect_to_signal(self, signal, callback):
"""Connects a callback to a signal""" """Connects a callback to a signal"""
self.receiver.connect_to_signal(signal, callback) self.receiver.connect_to_signal(signal, callback)
def torrent_added_signal(self, torrent_id): def torrent_added_signal(self, torrent_id):
log.debug("torrent_added signal received..") log.debug("torrent_added signal received..")
log.debug("torrent id: %s", torrent_id) log.debug("torrent id: %s", torrent_id)
@ -104,54 +104,54 @@ class Signals(component.Component):
component.get("TorrentView").update() component.get("TorrentView").update()
component.get("ToolBar").update_buttons() component.get("ToolBar").update_buttons()
component.get("MenuBar").update_menu() component.get("MenuBar").update_menu()
def torrent_resumed(self, torrent_id): def torrent_resumed(self, torrent_id):
log.debug("torrent_resumed signal received..") log.debug("torrent_resumed signal received..")
component.get("TorrentView").mark_dirty(torrent_id) component.get("TorrentView").mark_dirty(torrent_id)
component.get("TorrentView").update() component.get("TorrentView").update()
component.get("ToolBar").update_buttons() component.get("ToolBar").update_buttons()
component.get("MenuBar").update_menu() component.get("MenuBar").update_menu()
def torrent_all_paused(self): def torrent_all_paused(self):
log.debug("torrent_all_paused signal received..") log.debug("torrent_all_paused signal received..")
component.get("TorrentView").mark_dirty() component.get("TorrentView").mark_dirty()
component.get("TorrentView").update() component.get("TorrentView").update()
component.get("ToolBar").update_buttons() component.get("ToolBar").update_buttons()
component.get("MenuBar").update_menu() component.get("MenuBar").update_menu()
def torrent_all_resumed(self): def torrent_all_resumed(self):
log.debug("torrent_all_resumed signal received..") log.debug("torrent_all_resumed signal received..")
component.get("TorrentView").mark_dirty() component.get("TorrentView").mark_dirty()
component.get("TorrentView").update() component.get("TorrentView").update()
component.get("ToolBar").update_buttons() component.get("ToolBar").update_buttons()
component.get("MenuBar").update_menu() component.get("MenuBar").update_menu()
def config_value_changed(self, key, value): def config_value_changed(self, key, value):
log.debug("config_value_changed signal received..") log.debug("config_value_changed signal received..")
component.get("StatusBar").config_value_changed(key, value) component.get("StatusBar").config_value_changed(key, value)
component.get("SystemTray").config_value_changed(key, value) component.get("SystemTray").config_value_changed(key, value)
def torrent_queue_changed(self): def torrent_queue_changed(self):
log.debug("torrent_queue_changed signal received..") log.debug("torrent_queue_changed signal received..")
component.get("TorrentView").mark_dirty() component.get("TorrentView").mark_dirty()
component.get("TorrentView").update() component.get("TorrentView").update()
def torrent_resume_at_stop_ratio(self): def torrent_resume_at_stop_ratio(self):
log.debug("torrent_resume_at_stop_ratio") log.debug("torrent_resume_at_stop_ratio")
component.get("StatusBar").display_warning( component.get("StatusBar").display_warning(
text=_("Torrent is past stop ratio.")) text=_("Torrent is past stop ratio."))
def new_version_available(self, value): def new_version_available(self, value):
log.debug("new_version_available: %s", value) log.debug("new_version_available: %s", value)
if self.config["show_new_releases"]: if self.config["show_new_releases"]:
from deluge.ui.gtkui.new_release_dialog import NewReleaseDialog from deluge.ui.gtkui.new_release_dialog import NewReleaseDialog
NewReleaseDialog().show(value) NewReleaseDialog().show(value)
def args_from_external(self, value): def args_from_external(self, value):
log.debug("args_from_external: %s", value) log.debug("args_from_external: %s", value)
import ipcinterface import ipcinterface
ipcinterface.process_args(value) ipcinterface.process_args(value)
def torrent_state_changed(self, value): def torrent_state_changed(self, value):
log.debug("torrent_state_changed: %s", value) log.debug("torrent_state_changed: %s", value)

View File

@ -2,19 +2,19 @@
# systemtray.py # systemtray.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -58,7 +58,7 @@ class SystemTray(component.Component):
"separatormenuitem3", "separatormenuitem3",
"separatormenuitem4" "separatormenuitem4"
] ]
self.config.register_set_function("enable_system_tray", self.config.register_set_function("enable_system_tray",
self.on_enable_system_tray_set) self.on_enable_system_tray_set)
self.max_download_speed = -1.0 self.max_download_speed = -1.0
@ -70,12 +70,12 @@ class SystemTray(component.Component):
"max_download_speed": self._on_max_download_speed, "max_download_speed": self._on_max_download_speed,
"max_upload_speed": self._on_max_upload_speed "max_upload_speed": self._on_max_upload_speed
} }
def enable(self): def enable(self):
"""Enables the system tray icon.""" """Enables the system tray icon."""
log.debug("Enabling the system tray icon..") log.debug("Enabling the system tray icon..")
self.tray_glade = gtk.glade.XML( self.tray_glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/tray_menu.glade")) "glade/tray_menu.glade"))
if deluge.common.windows_check(): if deluge.common.windows_check():
@ -87,11 +87,11 @@ class SystemTray(component.Component):
except: except:
log.warning("Update PyGTK to 2.10 or greater for SystemTray..") log.warning("Update PyGTK to 2.10 or greater for SystemTray..")
return return
self.tray.connect("activate", self.on_tray_clicked) self.tray.connect("activate", self.on_tray_clicked)
self.tray.connect("popup-menu", self.on_tray_popup) self.tray.connect("popup-menu", self.on_tray_popup)
self.tray_glade.signal_autoconnect({ self.tray_glade.signal_autoconnect({
"on_menuitem_show_deluge_activate": \ "on_menuitem_show_deluge_activate": \
self.on_menuitem_show_deluge_activate, self.on_menuitem_show_deluge_activate,
@ -105,7 +105,7 @@ class SystemTray(component.Component):
"on_menuitem_quitdaemon_activate": \ "on_menuitem_quitdaemon_activate": \
self.on_menuitem_quitdaemon_activate self.on_menuitem_quitdaemon_activate
}) })
self.tray_menu = self.tray_glade.get_widget("tray_menu") self.tray_menu = self.tray_glade.get_widget("tray_menu")
self.tray_glade.get_widget("download-limit-image").set_from_file( self.tray_glade.get_widget("download-limit-image").set_from_file(
@ -118,12 +118,12 @@ class SystemTray(component.Component):
self.hide_widget_list.remove("separatormenuitem4") self.hide_widget_list.remove("separatormenuitem4")
self.tray_glade.get_widget("menuitem_quitdaemon").hide() self.tray_glade.get_widget("menuitem_quitdaemon").hide()
self.tray_glade.get_widget("separatormenuitem4").hide() self.tray_glade.get_widget("separatormenuitem4").hide()
if client.get_core_uri() == None: if client.get_core_uri() == None:
# Hide menu widgets because we're not connected to a host. # Hide menu widgets because we're not connected to a host.
for widget in self.hide_widget_list: for widget in self.hide_widget_list:
self.tray_glade.get_widget(widget).hide() self.tray_glade.get_widget(widget).hide()
def start(self): def start(self):
if self.config["enable_system_tray"]: if self.config["enable_system_tray"]:
# Show widgets in the hide list because we've connected to a host # Show widgets in the hide list because we've connected to a host
@ -132,7 +132,7 @@ class SystemTray(component.Component):
# Build the bandwidth speed limit menus # Build the bandwidth speed limit menus
self.build_tray_bwsetsubmenu() self.build_tray_bwsetsubmenu()
# Get some config values # Get some config values
client.get_config_value( client.get_config_value(
self._on_max_download_speed, "max_download_speed") self._on_max_download_speed, "max_download_speed")
@ -147,10 +147,10 @@ class SystemTray(component.Component):
self.tray_glade.get_widget(widget).hide() self.tray_glade.get_widget(widget).hide()
except Exception, e: except Exception, e:
log.debug("Unable to hide system tray menu widgets: %s", e) log.debug("Unable to hide system tray menu widgets: %s", e)
def shutdown(self): def shutdown(self):
self.tray.set_visible(False) self.tray.set_visible(False)
def send_status_request(self): def send_status_request(self):
client.get_download_rate(self._on_get_download_rate) client.get_download_rate(self._on_get_download_rate)
client.get_upload_rate(self._on_get_upload_rate) client.get_upload_rate(self._on_get_upload_rate)
@ -158,31 +158,31 @@ class SystemTray(component.Component):
def config_value_changed(self, key, value): def config_value_changed(self, key, value):
"""This is called when we received a config_value_changed signal from """This is called when we received a config_value_changed signal from
the core.""" the core."""
if key in self.config_value_changed_dict.keys(): if key in self.config_value_changed_dict.keys():
self.config_value_changed_dict[key](value) self.config_value_changed_dict[key](value)
def _on_max_download_speed(self, max_download_speed): def _on_max_download_speed(self, max_download_speed):
if self.max_download_speed != max_download_speed: if self.max_download_speed != max_download_speed:
self.max_download_speed = max_download_speed self.max_download_speed = max_download_speed
self.build_tray_bwsetsubmenu() self.build_tray_bwsetsubmenu()
def _on_get_download_rate(self, download_rate): def _on_get_download_rate(self, download_rate):
self.download_rate = deluge.common.fsize(download_rate) self.download_rate = deluge.common.fsize(download_rate)
def _on_max_upload_speed(self, max_upload_speed): def _on_max_upload_speed(self, max_upload_speed):
if self.max_upload_speed != max_upload_speed: if self.max_upload_speed != max_upload_speed:
self.max_upload_speed = max_upload_speed self.max_upload_speed = max_upload_speed
self.build_tray_bwsetsubmenu() self.build_tray_bwsetsubmenu()
def _on_get_upload_rate(self, upload_rate): def _on_get_upload_rate(self, upload_rate):
self.upload_rate = deluge.common.fsize(upload_rate) self.upload_rate = deluge.common.fsize(upload_rate)
def update(self): def update(self):
# Set the tool tip text # Set the tool tip text
max_download_speed = self.max_download_speed max_download_speed = self.max_download_speed
max_upload_speed = self.max_upload_speed max_upload_speed = self.max_upload_speed
if max_download_speed == -1: if max_download_speed == -1:
max_download_speed = _("Unlimited") max_download_speed = _("Unlimited")
else: else:
@ -191,39 +191,39 @@ class SystemTray(component.Component):
max_upload_speed = _("Unlimited") max_upload_speed = _("Unlimited")
else: else:
max_upload_speed = "%s KiB/s" % (max_upload_speed) max_upload_speed = "%s KiB/s" % (max_upload_speed)
msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (\ msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (\
_("Deluge"), _("Down"), self.download_rate, \ _("Deluge"), _("Down"), self.download_rate, \
max_download_speed, _("Up"), self.upload_rate, max_upload_speed) max_download_speed, _("Up"), self.upload_rate, max_upload_speed)
# Set the tooltip # Set the tooltip
self.tray.set_tooltip(msg) self.tray.set_tooltip(msg)
self.send_status_request() self.send_status_request()
def build_tray_bwsetsubmenu(self): def build_tray_bwsetsubmenu(self):
# Create the Download speed list sub-menu # Create the Download speed list sub-menu
submenu_bwdownset = deluge.common.build_menu_radio_list( submenu_bwdownset = deluge.common.build_menu_radio_list(
self.config["tray_download_speed_list"], self.tray_setbwdown, self.config["tray_download_speed_list"], self.tray_setbwdown,
self.max_download_speed, self.max_download_speed,
_("KiB/s"), show_notset=True, show_other=True) _("KiB/s"), show_notset=True, show_other=True)
# Create the Upload speed list sub-menu # Create the Upload speed list sub-menu
submenu_bwupset = deluge.common.build_menu_radio_list( submenu_bwupset = deluge.common.build_menu_radio_list(
self.config["tray_upload_speed_list"], self.tray_setbwup, self.config["tray_upload_speed_list"], self.tray_setbwup,
self.max_upload_speed, self.max_upload_speed,
_("KiB/s"), show_notset=True, show_other=True) _("KiB/s"), show_notset=True, show_other=True)
# Add the sub-menus to the tray menu # Add the sub-menus to the tray menu
self.tray_glade.get_widget("menuitem_download_limit").set_submenu( self.tray_glade.get_widget("menuitem_download_limit").set_submenu(
submenu_bwdownset) submenu_bwdownset)
self.tray_glade.get_widget("menuitem_upload_limit").set_submenu( self.tray_glade.get_widget("menuitem_upload_limit").set_submenu(
submenu_bwupset) submenu_bwupset)
# Show the sub-menus for all to see # Show the sub-menus for all to see
submenu_bwdownset.show_all() submenu_bwdownset.show_all()
submenu_bwupset.show_all() submenu_bwupset.show_all()
def disable(self): def disable(self):
"""Disables the system tray icon.""" """Disables the system tray icon."""
log.debug("Disabling the system tray icon..") log.debug("Disabling the system tray icon..")
@ -234,14 +234,14 @@ class SystemTray(component.Component):
del self.tray_menu del self.tray_menu
except Exception, e: except Exception, e:
log.debug("Unable to disable system tray: %s", e) log.debug("Unable to disable system tray: %s", e)
def on_enable_system_tray_set(self, key, value): def on_enable_system_tray_set(self, key, value):
"""Called whenever the 'enable_system_tray' config key is modified""" """Called whenever the 'enable_system_tray' config key is modified"""
if value: if value:
self.enable() self.enable()
else: else:
self.disable() self.disable()
def on_tray_clicked(self, icon): def on_tray_clicked(self, icon):
"""Called when the tray icon is left clicked.""" """Called when the tray icon is left clicked."""
if self.window.active(): if self.window.active():
@ -251,18 +251,18 @@ class SystemTray(component.Component):
if not self.unlock_tray(): if not self.unlock_tray():
return return
self.window.present() self.window.present()
def on_tray_popup(self, status_icon, button, activate_time): def on_tray_popup(self, status_icon, button, activate_time):
"""Called when the tray icon is right clicked.""" """Called when the tray icon is right clicked."""
if self.window.visible(): if self.window.visible():
self.tray_glade.get_widget("menuitem_show_deluge").set_active(True) self.tray_glade.get_widget("menuitem_show_deluge").set_active(True)
else: else:
self.tray_glade.get_widget("menuitem_show_deluge").set_active(False) self.tray_glade.get_widget("menuitem_show_deluge").set_active(False)
popup_function = gtk.status_icon_position_menu popup_function = gtk.status_icon_position_menu
if deluge.common.windows_check(): if deluge.common.windows_check():
popup_function = None popup_function = None
self.tray_menu.popup(None, None, popup_function, self.tray_menu.popup(None, None, popup_function,
button, activate_time, status_icon) button, activate_time, status_icon)
def on_menuitem_show_deluge_activate(self, menuitem): def on_menuitem_show_deluge_activate(self, menuitem):
@ -274,20 +274,20 @@ class SystemTray(component.Component):
self.window.present() self.window.present()
elif not menuitem.get_active() and self.window.visible(): elif not menuitem.get_active() and self.window.visible():
self.window.hide() self.window.hide()
def on_menuitem_add_torrent_activate(self, menuitem): def on_menuitem_add_torrent_activate(self, menuitem):
log.debug("on_menuitem_add_torrent_activate") log.debug("on_menuitem_add_torrent_activate")
from addtorrentdialog import AddTorrentDialog from addtorrentdialog import AddTorrentDialog
client.add_torrent_file(AddTorrentDialog().show()) client.add_torrent_file(AddTorrentDialog().show())
def on_menuitem_pause_all_activate(self, menuitem): def on_menuitem_pause_all_activate(self, menuitem):
log.debug("on_menuitem_pause_all_activate") log.debug("on_menuitem_pause_all_activate")
client.pause_all_torrents() client.pause_all_torrents()
def on_menuitem_resume_all_activate(self, menuitem): def on_menuitem_resume_all_activate(self, menuitem):
log.debug("on_menuitem_resume_all_activate") log.debug("on_menuitem_resume_all_activate")
client.resume_all_torrents() client.resume_all_torrents()
def on_menuitem_quit_activate(self, menuitem): def on_menuitem_quit_activate(self, menuitem):
log.debug("on_menuitem_quit_activate") log.debug("on_menuitem_quit_activate")
if self.config["lock_tray"]: if self.config["lock_tray"]:
@ -298,7 +298,7 @@ class SystemTray(component.Component):
client.shutdown() client.shutdown()
self.window.quit() self.window.quit()
def on_menuitem_quitdaemon_activate(self, menuitem): def on_menuitem_quitdaemon_activate(self, menuitem):
log.debug("on_menuitem_quitdaemon_activate") log.debug("on_menuitem_quitdaemon_activate")
if self.config["lock_tray"]: if self.config["lock_tray"]:
@ -307,54 +307,54 @@ class SystemTray(component.Component):
client.shutdown() client.shutdown()
self.window.quit() self.window.quit()
def tray_setbwdown(self, widget, data=None): def tray_setbwdown(self, widget, data=None):
self.setbwlimit(widget, _("Download"), "max_download_speed", self.setbwlimit(widget, _("Download"), "max_download_speed",
"tray_download_speed_list", self.max_download_speed) "tray_download_speed_list", self.max_download_speed)
def tray_setbwup(self, widget, data=None): def tray_setbwup(self, widget, data=None):
self.setbwlimit(widget, _("Upload"), "max_upload_speed", self.setbwlimit(widget, _("Upload"), "max_upload_speed",
"tray_upload_speed_list", self.max_upload_speed) "tray_upload_speed_list", self.max_upload_speed)
def setbwlimit(self, widget, string, core_key, ui_key, default): def setbwlimit(self, widget, string, core_key, ui_key, default):
"""Sets the bandwidth limit based on the user selection.""" """Sets the bandwidth limit based on the user selection."""
value = widget.get_children()[0].get_text().rstrip(" " + _("KiB/s")) value = widget.get_children()[0].get_text().rstrip(" " + _("KiB/s"))
if value == _("Unlimited"): if value == _("Unlimited"):
value = -1 value = -1
if value == _("Other..."): if value == _("Other..."):
value = deluge.common.show_other_dialog( value = deluge.common.show_other_dialog(
string + " Speed (KiB/s):", default) string + " Speed (KiB/s):", default)
if value == None: if value == None:
return return
# Set the config in the core # Set the config in the core
value = float(value) value = float(value)
config_to_set = {core_key: value} config_to_set = {core_key: value}
client.set_config(config_to_set) client.set_config(config_to_set)
self.build_tray_bwsetsubmenu() self.build_tray_bwsetsubmenu()
def unlock_tray(self, is_showing_dlg=[False]): def unlock_tray(self, is_showing_dlg=[False]):
try: try:
from hashlib import sha1 as sha_hash from hashlib import sha1 as sha_hash
except ImportError: except ImportError:
from sha import new as sha_hash from sha import new as sha_hash
log.debug("Show tray lock dialog") log.debug("Show tray lock dialog")
result = False result = False
if is_showing_dlg[0]: if is_showing_dlg[0]:
return return
is_showing_dlg[0] = True is_showing_dlg[0] = True
entered_pass = gtk.Entry(25) entered_pass = gtk.Entry(25)
entered_pass.set_activates_default(True) entered_pass.set_activates_default(True)
entered_pass.set_width_chars(25) entered_pass.set_width_chars(25)
entered_pass.set_visibility(False) entered_pass.set_visibility(False)
entered_pass.show() entered_pass.show()
tray_lock = gtk.Dialog(title=_("Deluge is locked"), parent=None, tray_lock = gtk.Dialog(title=_("Deluge is locked"), parent=None,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK,
gtk.RESPONSE_ACCEPT)) gtk.RESPONSE_ACCEPT))
label = gtk.Label(_("Deluge is password protected.\nTo show the Deluge \ label = gtk.Label(_("Deluge is password protected.\nTo show the Deluge \
window, please enter your password")) window, please enter your password"))
@ -372,6 +372,6 @@ window, please enter your password"))
result = True result = True
tray_lock.destroy() tray_lock.destroy()
is_showing_dlg[0] = False is_showing_dlg[0] = False
return result return result

View File

@ -2,19 +2,19 @@
# toolbar.py # toolbar.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -70,7 +70,7 @@ class ToolBar(component.Component):
"toolbutton_queue_up", "toolbutton_queue_up",
"toolbutton_queue_down" "toolbutton_queue_down"
] ]
# Set the Remove Torrent toolbuttons drop-down menu # Set the Remove Torrent toolbuttons drop-down menu
tb_remove = self.window.main_glade.get_widget("toolbutton_remove") tb_remove = self.window.main_glade.get_widget("toolbutton_remove")
tb_remove.set_menu( tb_remove.set_menu(
@ -78,7 +78,7 @@ class ToolBar(component.Component):
if self.config["classic_mode"]: if self.config["classic_mode"]:
self.window.main_glade.get_widget("toolbutton_connectionmanager").hide() self.window.main_glade.get_widget("toolbutton_connectionmanager").hide()
# Hide if necessary # Hide if necessary
self.visible(self.config["show_toolbar"]) self.visible(self.config["show_toolbar"])
@ -86,19 +86,19 @@ class ToolBar(component.Component):
for widget in self.change_sensitivity: for widget in self.change_sensitivity:
self.window.main_glade.get_widget(widget).set_sensitive(True) self.window.main_glade.get_widget(widget).set_sensitive(True)
self.update_buttons() self.update_buttons()
def stop(self): def stop(self):
for widget in self.change_sensitivity: for widget in self.change_sensitivity:
self.window.main_glade.get_widget(widget).set_sensitive(False) self.window.main_glade.get_widget(widget).set_sensitive(False)
def visible(self, visible): def visible(self, visible):
if visible: if visible:
self.toolbar.show() self.toolbar.show()
else: else:
self.toolbar.hide() self.toolbar.hide()
self.config["show_toolbar"] = visible self.config["show_toolbar"] = visible
def add_toolbutton(self, callback, label=None, image=None, stock=None, def add_toolbutton(self, callback, label=None, image=None, stock=None,
tooltip=None): tooltip=None):
"""Adds a toolbutton to the toolbar""" """Adds a toolbutton to the toolbar"""
@ -115,15 +115,15 @@ class ToolBar(component.Component):
toolbutton.set_tooltip_text(tooltip) toolbutton.set_tooltip_text(tooltip)
# Connect the 'clicked' event callback # Connect the 'clicked' event callback
toolbutton.connect("clicked", callback) toolbutton.connect("clicked", callback)
# Append the button to the toolbar # Append the button to the toolbar
self.toolbar.insert(toolbutton, -1) self.toolbar.insert(toolbutton, -1)
# Show the new toolbutton # Show the new toolbutton
toolbutton.show_all() toolbutton.show_all()
return toolbutton return toolbutton
def add_separator(self, position=None): def add_separator(self, position=None):
"""Adds a separator toolitem""" """Adds a separator toolitem"""
sep = gtk.SeparatorToolItem() sep = gtk.SeparatorToolItem()
@ -134,13 +134,13 @@ class ToolBar(component.Component):
self.toolbar.insert(sep, -1) self.toolbar.insert(sep, -1)
sep.show() sep.show()
return sep return sep
def remove(self, widget): def remove(self, widget):
"""Removes a widget from the toolbar""" """Removes a widget from the toolbar"""
self.toolbar.remove(widget) self.toolbar.remove(widget)
### Callbacks ### ### Callbacks ###
def on_toolbutton_add_clicked(self, data): def on_toolbutton_add_clicked(self, data):
log.debug("on_toolbutton_add_clicked") log.debug("on_toolbutton_add_clicked")
@ -156,7 +156,7 @@ class ToolBar(component.Component):
log.debug("on_toolbutton_pause_clicked") log.debug("on_toolbutton_pause_clicked")
# Use the menubar's callbacks # Use the menubar's callbacks
component.get("MenuBar").on_menuitem_pause_activate(data) component.get("MenuBar").on_menuitem_pause_activate(data)
def on_toolbutton_resume_clicked(self, data): def on_toolbutton_resume_clicked(self, data):
log.debug("on_toolbutton_resume_clicked") log.debug("on_toolbutton_resume_clicked")
# Use the menubar's calbacks # Use the menubar's calbacks
@ -171,18 +171,18 @@ class ToolBar(component.Component):
log.debug("on_toolbutton_connectionmanager_clicked") log.debug("on_toolbutton_connectionmanager_clicked")
# Use the menubar's callbacks # Use the menubar's callbacks
component.get("MenuBar").on_menuitem_connectionmanager_activate(data) component.get("MenuBar").on_menuitem_connectionmanager_activate(data)
def on_toolbutton_queue_up_clicked(self, data): def on_toolbutton_queue_up_clicked(self, data):
log.debug("on_toolbutton_queue_up_clicked") log.debug("on_toolbutton_queue_up_clicked")
component.get("MenuBar").on_menuitem_queue_up_activate(data) component.get("MenuBar").on_menuitem_queue_up_activate(data)
def on_toolbutton_queue_down_clicked(self, data): def on_toolbutton_queue_down_clicked(self, data):
log.debug("on_toolbutton_queue_down_clicked") log.debug("on_toolbutton_queue_down_clicked")
component.get("MenuBar").on_menuitem_queue_down_activate(data) component.get("MenuBar").on_menuitem_queue_down_activate(data)
def update_buttons(self, action=None, torrent_id=None): def update_buttons(self, action=None, torrent_id=None):
if action == None: if action == None:
# If all the selected torrents are paused, then disable the 'Pause' # If all the selected torrents are paused, then disable the 'Pause'
# button. # button.
# The same goes for the 'Resume' button. # The same goes for the 'Resume' button.
pause = False pause = False
@ -205,18 +205,18 @@ class ToolBar(component.Component):
if pause and resume: if pause and resume:
break break
# Enable the 'Remove Torrent' button only if there's some selected # Enable the 'Remove Torrent' button only if there's some selected
# torrent. # torrent.
remove = (len(selected) > 0) remove = (len(selected) > 0)
for name, sensitive in (("toolbutton_pause", pause), for name, sensitive in (("toolbutton_pause", pause),
("toolbutton_resume", resume), ("toolbutton_resume", resume),
("toolbutton_remove", remove)): ("toolbutton_remove", remove)):
self.window.main_glade.get_widget(name).set_sensitive(sensitive) self.window.main_glade.get_widget(name).set_sensitive(sensitive)
return False return False
pause = False pause = False
resume = False resume = False
if action == "paused": if action == "paused":
@ -231,5 +231,5 @@ class ToolBar(component.Component):
self.window.main_glade.get_widget("toolbutton_resume").set_sensitive(resume) self.window.main_glade.get_widget("toolbutton_resume").set_sensitive(resume)
else: else:
self.update_buttons() self.update_buttons()
return False return False

View File

@ -2,19 +2,19 @@
# torrentdetails.py # torrentdetails.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -49,38 +49,38 @@ class Tab:
self.is_visible = True self.is_visible = True
self.position = -1 self.position = -1
self.weight = -1 self.weight = -1
def get_name(self): def get_name(self):
return self._name return self._name
def get_child_widget(self): def get_child_widget(self):
parent = self._child_widget.get_parent() parent = self._child_widget.get_parent()
if parent is not None: if parent is not None:
parent.remove(self._child_widget) parent.remove(self._child_widget)
return self._child_widget return self._child_widget
def get_tab_label(self): def get_tab_label(self):
parent = self._tab_label.get_parent() parent = self._tab_label.get_parent()
log.debug("parent: %s", parent) log.debug("parent: %s", parent)
if parent is not None: if parent is not None:
parent.remove(self._tab_label) parent.remove(self._tab_label)
return self._tab_label return self._tab_label
class TorrentDetails(component.Component): class TorrentDetails(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "TorrentDetails", interval=2000) component.Component.__init__(self, "TorrentDetails", interval=2000)
self.window = component.get("MainWindow") self.window = component.get("MainWindow")
glade = self.window.main_glade glade = self.window.main_glade
self.notebook = glade.get_widget("torrent_info") self.notebook = glade.get_widget("torrent_info")
# This is the menu item we'll attach the tabs checklist menu to # This is the menu item we'll attach the tabs checklist menu to
self.menu_tabs = glade.get_widget("menu_tabs") self.menu_tabs = glade.get_widget("menu_tabs")
self.notebook.connect("switch-page", self._on_switch_page) self.notebook.connect("switch-page", self._on_switch_page)
# Tabs holds references to the Tab objects by their name # Tabs holds references to the Tab objects by their name
self.tabs = {} self.tabs = {}
@ -98,7 +98,7 @@ class TorrentDetails(component.Component):
"Peers": PeersTab, "Peers": PeersTab,
"Options": OptionsTab "Options": OptionsTab
} }
# tab_name, visible # tab_name, visible
default_order = [ default_order = [
("Statistics", True), ("Statistics", True),
@ -107,7 +107,7 @@ class TorrentDetails(component.Component):
("Peers", True), ("Peers", True),
("Options", True) ("Options", True)
] ]
# Get the state from saved file # Get the state from saved file
state = self.load_state() state = self.load_state()
@ -117,7 +117,7 @@ class TorrentDetails(component.Component):
log.debug("Old tabs.state, using default..") log.debug("Old tabs.state, using default..")
state = None state = None
break break
# The state is a list of tab_names in the order they should appear # The state is a list of tab_names in the order they should appear
if state == None: if state == None:
# Set the default order # Set the default order
@ -126,15 +126,15 @@ class TorrentDetails(component.Component):
# Add the tabs in the order from the state # Add the tabs in the order from the state
for tab_name, visible in state: for tab_name, visible in state:
self.add_tab(default_tabs[tab_name]()) self.add_tab(default_tabs[tab_name]())
# Hide any of the non-visible ones # Hide any of the non-visible ones
for tab_name, visible in state: for tab_name, visible in state:
if not visible: if not visible:
self.hide_tab(tab_name) self.hide_tab(tab_name)
# Generate the checklist menu # Generate the checklist menu
self.generate_menu() self.generate_menu()
def add_tab(self, tab_object, position=-1, generate_menu=True): def add_tab(self, tab_object, position=-1, generate_menu=True):
"""Adds a tab object to the notebook.""" """Adds a tab object to the notebook."""
self.tabs[tab_object.get_name()] = tab_object self.tabs[tab_object.get_name()] = tab_object
@ -145,18 +145,18 @@ class TorrentDetails(component.Component):
tab_object.position = pos tab_object.position = pos
tab_object.weight = pos tab_object.weight = pos
# Regenerate positions if an insert occured # Regenerate positions if an insert occured
if position > -1: if position > -1:
self.regenerate_positions() self.regenerate_positions()
if generate_menu: if generate_menu:
self.generate_menu() self.generate_menu()
if not self.notebook.get_property("visible"): if not self.notebook.get_property("visible"):
# If the notebook isn't visible, show it # If the notebook isn't visible, show it
self.visible(True) self.visible(True)
def regenerate_positions(self): def regenerate_positions(self):
"""This will sync up the positions in the tab, with the position stored """This will sync up the positions in the tab, with the position stored
in the tab object""" in the tab object"""
@ -171,7 +171,7 @@ class TorrentDetails(component.Component):
del self.tabs[tab_name] del self.tabs[tab_name]
self.regenerate_positions() self.regenerate_positions()
self.generate_menu() self.generate_menu()
# If there are no tabs visible, then do not show the notebook # If there are no tabs visible, then do not show the notebook
if len(self.tabs) == 0: if len(self.tabs) == 0:
self.visible(False) self.visible(False)
@ -181,13 +181,13 @@ class TorrentDetails(component.Component):
log.debug("n_pages: %s", self.notebook.get_n_pages()) log.debug("n_pages: %s", self.notebook.get_n_pages())
for n in xrange(self.notebook.get_n_pages() - 1, -1, -1): for n in xrange(self.notebook.get_n_pages() - 1, -1, -1):
self.notebook.remove_page(n) self.notebook.remove_page(n)
for tab in self.tabs: for tab in self.tabs:
self.tabs[tab].is_visible = False self.tabs[tab].is_visible = False
log.debug("n_pages: %s", self.notebook.get_n_pages()) log.debug("n_pages: %s", self.notebook.get_n_pages())
self.generate_menu() self.generate_menu()
self.visible(False) self.visible(False)
def show_all_tabs(self): def show_all_tabs(self):
"""Shows all tabs""" """Shows all tabs"""
for tab in self.tabs: for tab in self.tabs:
@ -201,7 +201,7 @@ class TorrentDetails(component.Component):
if not self.notebook.get_property("visible"): if not self.notebook.get_property("visible"):
# If the notebook isn't visible, show it # If the notebook isn't visible, show it
self.visible(True) self.visible(True)
def hide_tab(self, tab_name): def hide_tab(self, tab_name):
"""Hides tab by name""" """Hides tab by name"""
self.notebook.remove_page(self.tabs[tab_name].position) self.notebook.remove_page(self.tabs[tab_name].position)
@ -217,11 +217,11 @@ class TorrentDetails(component.Component):
# Determine insert position based on weight # Determine insert position based on weight
# weights is a list of visible tab names in weight order # weights is a list of visible tab names in weight order
weights = [] weights = []
for tab in self.tabs: for tab in self.tabs:
if self.tabs[tab].is_visible: if self.tabs[tab].is_visible:
weights.append((self.tabs[tab].weight, self.tabs[tab].get_name())) weights.append((self.tabs[tab].weight, self.tabs[tab].get_name()))
weights.sort() weights.sort()
log.debug("weights: %s", weights) log.debug("weights: %s", weights)
position = self.tabs[tab_name].position position = self.tabs[tab_name].position
@ -230,7 +230,7 @@ class TorrentDetails(component.Component):
if w[0] >= self.tabs[tab_name].weight: if w[0] >= self.tabs[tab_name].weight:
position = self.tabs[w[1]].position position = self.tabs[w[1]].position
break break
log.debug("position: %s", position) log.debug("position: %s", position)
self.notebook.insert_page( self.notebook.insert_page(
self.tabs[tab_name].get_child_widget(), self.tabs[tab_name].get_child_widget(),
@ -239,7 +239,7 @@ class TorrentDetails(component.Component):
self.tabs[tab_name].is_visible = True self.tabs[tab_name].is_visible = True
self.regenerate_positions() self.regenerate_positions()
self.generate_menu() self.generate_menu()
def generate_menu(self): def generate_menu(self):
"""Generates the checklist menu for all the tabs and attaches it""" """Generates the checklist menu for all the tabs and attaches it"""
menu = gtk.Menu() menu = gtk.Menu()
@ -252,35 +252,35 @@ class TorrentDetails(component.Component):
all_tabs = False all_tabs = False
break break
menuitem.set_active(all_tabs) menuitem.set_active(all_tabs)
menuitem.connect("toggled", self._on_menuitem_toggled) menuitem.connect("toggled", self._on_menuitem_toggled)
menu.append(menuitem) menu.append(menuitem)
menuitem = gtk.SeparatorMenuItem() menuitem = gtk.SeparatorMenuItem()
menu.append(menuitem) menu.append(menuitem)
# Create a list in order of tabs to create menu # Create a list in order of tabs to create menu
menuitem_list = [] menuitem_list = []
for tab_name in self.tabs: for tab_name in self.tabs:
menuitem_list.append((self.tabs[tab_name].weight, tab_name)) menuitem_list.append((self.tabs[tab_name].weight, tab_name))
menuitem_list.sort() menuitem_list.sort()
for pos, name in menuitem_list: for pos, name in menuitem_list:
menuitem = gtk.CheckMenuItem(name) menuitem = gtk.CheckMenuItem(name)
menuitem.set_active(self.tabs[name].is_visible) menuitem.set_active(self.tabs[name].is_visible)
menuitem.connect("toggled", self._on_menuitem_toggled) menuitem.connect("toggled", self._on_menuitem_toggled)
menu.append(menuitem) menu.append(menuitem)
self.menu_tabs.set_submenu(menu) self.menu_tabs.set_submenu(menu)
self.menu_tabs.show_all() self.menu_tabs.show_all()
def visible(self, visible): def visible(self, visible):
if visible: if visible:
self.notebook.show() self.notebook.show()
else: else:
self.notebook.hide() self.notebook.hide()
self.window.vpaned.set_position(-1) self.window.vpaned.set_position(-1)
def set_tab_visible(self, tab_name, visible): def set_tab_visible(self, tab_name, visible):
"""Sets the tab to visible""" """Sets the tab to visible"""
log.debug("set_tab_visible name: %s visible: %s", tab_name, visible) log.debug("set_tab_visible name: %s visible: %s", tab_name, visible)
@ -288,10 +288,10 @@ class TorrentDetails(component.Component):
self.show_tab(tab_name) self.show_tab(tab_name)
elif not visible and self.tabs[tab_name].is_visible: elif not visible and self.tabs[tab_name].is_visible:
self.hide_tab(tab_name) self.hide_tab(tab_name)
def stop(self): def stop(self):
self.clear() self.clear()
def shutdown(self): def shutdown(self):
# Save the state of the tabs # Save the state of the tabs
for tab in self.tabs: for tab in self.tabs:
@ -299,7 +299,7 @@ class TorrentDetails(component.Component):
self.tabs[tab].save_state() self.tabs[tab].save_state()
except AttributeError: except AttributeError:
pass pass
# Save tabs state # Save tabs state
self.save_state() self.save_state()
@ -307,7 +307,7 @@ class TorrentDetails(component.Component):
if len(component.get("TorrentView").get_selected_torrents()) == 0: if len(component.get("TorrentView").get_selected_torrents()) == 0:
# No torrents selected, so just clear # No torrents selected, so just clear
self.clear() self.clear()
if self.notebook.get_property("visible"): if self.notebook.get_property("visible"):
if page_num == None: if page_num == None:
page_num = self.notebook.get_current_page() page_num = self.notebook.get_current_page()
@ -322,7 +322,7 @@ class TorrentDetails(component.Component):
# Update the tab that is in view # Update the tab that is in view
if name: if name:
self.tabs[name].update() self.tabs[name].update()
def clear(self): def clear(self):
# Get the tab name # Get the tab name
try: try:
@ -349,9 +349,9 @@ class TorrentDetails(component.Component):
else: else:
self.hide_all_tabs() self.hide_all_tabs()
return return
self.set_tab_visible(name, widget.get_active()) self.set_tab_visible(name, widget.get_active())
def save_state(self): def save_state(self):
"""We save the state, which is basically the tab_index list""" """We save the state, which is basically the tab_index list"""
filename = "tabs.state" filename = "tabs.state"
@ -362,7 +362,7 @@ class TorrentDetails(component.Component):
# Sort by weight # Sort by weight
state.sort() state.sort()
state = [(n, v) for w, n, v in state] state = [(n, v) for w, n, v in state]
# Get the config location for saving the state file # Get the config location for saving the state file
config_location = ConfigManager("gtkui.conf")["config_location"] config_location = ConfigManager("gtkui.conf")["config_location"]
@ -379,7 +379,7 @@ class TorrentDetails(component.Component):
# Get the config location for loading the state file # Get the config location for loading the state file
config_location = ConfigManager("gtkui.conf")["config_location"] config_location = ConfigManager("gtkui.conf")["config_location"]
state = None state = None
try: try:
log.debug("Loading TorrentDetails state file: %s", filename) log.debug("Loading TorrentDetails state file: %s", filename)
state_file = open(os.path.join(config_location, filename), "rb") state_file = open(os.path.join(config_location, filename), "rb")
@ -387,5 +387,5 @@ class TorrentDetails(component.Component):
state_file.close() state_file.close()
except (EOFError, IOError), e: except (EOFError, IOError), e:
log.warning("Unable to load state file: %s", e) log.warning("Unable to load state file: %s", e)
return state return state

View File

@ -2,19 +2,19 @@
# torrentview.py # torrentview.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -59,7 +59,7 @@ icon_queued = gtk.gdk.pixbuf_new_from_file(
deluge.common.get_pixmap("queued16.png")) deluge.common.get_pixmap("queued16.png"))
icon_checking = gtk.gdk.pixbuf_new_from_file( icon_checking = gtk.gdk.pixbuf_new_from_file(
deluge.common.get_pixmap("checking16.png")) deluge.common.get_pixmap("checking16.png"))
# Holds the info for which status icon to display based on state # Holds the info for which status icon to display based on state
ICON_STATE = { ICON_STATE = {
"Allocating": icon_checking, "Allocating": icon_checking,
@ -70,7 +70,7 @@ ICON_STATE = {
"Error": icon_alert, "Error": icon_alert,
"Queued": icon_queued "Queued": icon_queued
} }
def cell_data_statusicon(column, cell, model, row, data): def cell_data_statusicon(column, cell, model, row, data):
"""Display text with an icon""" """Display text with an icon"""
try: try:
@ -79,7 +79,7 @@ def cell_data_statusicon(column, cell, model, row, data):
cell.set_property("pixbuf", icon) cell.set_property("pixbuf", icon)
except KeyError: except KeyError:
pass pass
def cell_data_progress(column, cell, model, row, data): def cell_data_progress(column, cell, model, row, data):
"""Display progress bar with text""" """Display progress bar with text"""
(value, state_str) = model.get(row, *data) (value, state_str) = model.get(row, *data)
@ -88,7 +88,7 @@ def cell_data_progress(column, cell, model, row, data):
textstr = "%s" % state_str textstr = "%s" % state_str
if state_str != "Seeding" and value < 100: if state_str != "Seeding" and value < 100:
textstr = textstr + " %.2f%%" % value textstr = textstr + " %.2f%%" % value
if cell.get_property("text") != textstr: if cell.get_property("text") != textstr:
cell.set_property("text", textstr) cell.set_property("text", textstr)
@ -98,19 +98,19 @@ def cell_data_queue(column, cell, model, row, data):
cell.set_property("text", "") cell.set_property("text", "")
else: else:
cell.set_property("text", value + 1) cell.set_property("text", value + 1)
class TorrentView(listview.ListView, component.Component): class TorrentView(listview.ListView, component.Component):
"""TorrentView handles the listing of torrents.""" """TorrentView handles the listing of torrents."""
def __init__(self): def __init__(self):
component.Component.__init__(self, "TorrentView", interval=2000) component.Component.__init__(self, "TorrentView", interval=2000)
self.window = component.get("MainWindow") self.window = component.get("MainWindow")
# Call the ListView constructor # Call the ListView constructor
listview.ListView.__init__(self, listview.ListView.__init__(self,
self.window.main_glade.get_widget("torrent_view")) self.window.main_glade.get_widget("torrent_view"))
log.debug("TorrentView Init..") log.debug("TorrentView Init..")
# Try to load the state file if available # Try to load the state file if available
self.load_state("torrentview.state") self.load_state("torrentview.state")
# This is where status updates are put # This is where status updates are put
self.status = {} self.status = {}
@ -118,31 +118,31 @@ class TorrentView(listview.ListView, component.Component):
# accordingly. # accordingly.
self.register_checklist_menu( self.register_checklist_menu(
self.window.main_glade.get_widget("menu_columns")) self.window.main_glade.get_widget("menu_columns"))
# Add the columns to the listview # Add the columns to the listview
self.add_text_column("torrent_id", hidden=True) self.add_text_column("torrent_id", hidden=True)
self.add_bool_column("filter", hidden=True) self.add_bool_column("filter", hidden=True)
self.add_bool_column("dirty", hidden=True) self.add_bool_column("dirty", hidden=True)
self.add_func_column("#", cell_data_queue, [int], status_field=["queue"]) self.add_func_column("#", cell_data_queue, [int], status_field=["queue"])
self.add_texticon_column(_("Name"), status_field=["state", "name"], self.add_texticon_column(_("Name"), status_field=["state", "name"],
function=cell_data_statusicon) function=cell_data_statusicon)
self.add_func_column(_("Size"), self.add_func_column(_("Size"),
listview.cell_data_size, listview.cell_data_size,
[gobject.TYPE_UINT64], [gobject.TYPE_UINT64],
status_field=["total_wanted"]) status_field=["total_wanted"])
self.add_progress_column(_("Progress"), self.add_progress_column(_("Progress"),
status_field=["progress", "state"], status_field=["progress", "state"],
col_types=[float, str], col_types=[float, str],
function=cell_data_progress) function=cell_data_progress)
self.add_func_column(_("Seeders"), self.add_func_column(_("Seeders"),
listview.cell_data_peer, listview.cell_data_peer,
[int, int], [int, int],
status_field=["num_seeds", status_field=["num_seeds",
"total_seeds"]) "total_seeds"])
self.add_func_column(_("Peers"), self.add_func_column(_("Peers"),
listview.cell_data_peer, listview.cell_data_peer,
[int, int], [int, int],
status_field=["num_peers", status_field=["num_peers",
"total_peers"]) "total_peers"])
self.add_func_column(_("Down Speed"), self.add_func_column(_("Down Speed"),
listview.cell_data_speed, listview.cell_data_speed,
@ -177,11 +177,11 @@ class TorrentView(listview.ListView, component.Component):
self.on_button_press_event) self.on_button_press_event)
# Connect to the 'changed' event of TreeViewSelection to get selection # Connect to the 'changed' event of TreeViewSelection to get selection
# changes. # changes.
self.treeview.get_selection().connect("changed", self.treeview.get_selection().connect("changed",
self.on_selection_changed) self.on_selection_changed)
self.treeview.connect("drag-drop", self.on_drag_drop) self.treeview.connect("drag-drop", self.on_drag_drop)
def start(self): def start(self):
"""Start the torrentview""" """Start the torrentview"""
# We need to get the core session state to know which torrents are in # We need to get the core session state to know which torrents are in
@ -190,7 +190,7 @@ class TorrentView(listview.ListView, component.Component):
def create_model_filter(self): def create_model_filter(self):
"""create new filter-model """create new filter-model
must be called after listview.create_new_liststore must be called after listview.create_new_liststore
""" """
sort_column = None sort_column = None
if self.treeview.get_model(): if self.treeview.get_model():
@ -209,10 +209,10 @@ class TorrentView(listview.ListView, component.Component):
for torrent_id in state: for torrent_id in state:
self.add_row(torrent_id, update=False) self.add_row(torrent_id, update=False)
self.mark_dirty(torrent_id) self.mark_dirty(torrent_id)
self.update_filter() self.update_filter()
self.update() self.update()
def stop(self): def stop(self):
"""Stops the torrentview""" """Stops the torrentview"""
# We need to clear the liststore # We need to clear the liststore
@ -221,27 +221,27 @@ class TorrentView(listview.ListView, component.Component):
def shutdown(self): def shutdown(self):
"""Called when GtkUi is exiting""" """Called when GtkUi is exiting"""
self.save_state("torrentview.state") self.save_state("torrentview.state")
def set_filter(self, field, condition): def set_filter(self, field, condition):
"""Sets filters for the torrentview..""" """Sets filters for the torrentview.."""
if self.filter != (None, None): if self.filter != (None, None):
self.filter = (None, None) self.filter = (None, None)
self.update_filter() self.update_filter()
self.filter = (field, condition) self.filter = (field, condition)
self.update_filter() self.update_filter()
self.update() self.update()
def send_status_request(self, columns=None): def send_status_request(self, columns=None):
# Store the 'status_fields' we need to send to core # Store the 'status_fields' we need to send to core
status_keys = [] status_keys = []
# Store the actual columns we will be updating # Store the actual columns we will be updating
self.columns_to_update = [] self.columns_to_update = []
if columns is None: if columns is None:
# We need to iterate through all columns # We need to iterate through all columns
columns = self.columns.keys() columns = self.columns.keys()
# Iterate through supplied list of columns to update # Iterate through supplied list of columns to update
for column in columns: for column in columns:
# Make sure column is visible and has 'status_field' set. # Make sure column is visible and has 'status_field' set.
@ -252,17 +252,17 @@ class TorrentView(listview.ListView, component.Component):
for field in self.columns[column].status_field: for field in self.columns[column].status_field:
status_keys.append(field) status_keys.append(field)
self.columns_to_update.append(column) self.columns_to_update.append(column)
# Remove duplicate keys # Remove duplicate keys
self.columns_to_update = list(set(self.columns_to_update)) self.columns_to_update = list(set(self.columns_to_update))
# If there is nothing in status_keys then we must not continue # If there is nothing in status_keys then we must not continue
if status_keys is []: if status_keys is []:
return return
# Remove duplicates from status_key list # Remove duplicates from status_key list
status_keys = list(set(status_keys)) status_keys = list(set(status_keys))
# Create list of torrent_ids in need of status updates # Create list of torrent_ids in need of status updates
torrent_ids = [] torrent_ids = []
for row in self.liststore: for row in self.liststore:
@ -279,7 +279,7 @@ class TorrentView(listview.ListView, component.Component):
# will deal with the return in a signal callback. # will deal with the return in a signal callback.
client.get_torrents_status( client.get_torrents_status(
self._on_get_torrents_status, torrent_ids, status_keys) self._on_get_torrents_status, torrent_ids, status_keys)
def update_filter(self): def update_filter(self):
# Update the filter view # Update the filter view
for row in self.liststore: for row in self.liststore:
@ -301,11 +301,11 @@ class TorrentView(listview.ListView, component.Component):
row[filter_column] = True row[filter_column] = True
else: else:
row[filter_column] = False row[filter_column] = False
def update(self): def update(self):
# Send a status request # Send a status request
self.send_status_request() self.send_status_request()
def update_view(self, columns=None): def update_view(self, columns=None):
"""Update the view. If columns is not None, it will attempt to only """Update the view. If columns is not None, it will attempt to only
update those columns selected. update those columns selected.
@ -319,7 +319,7 @@ class TorrentView(listview.ListView, component.Component):
for column in self.columns_to_update: for column in self.columns_to_update:
column_index = self.get_column_index(column) column_index = self.get_column_index(column)
if type(column_index) is not list: if type(column_index) is not list:
# We only have a single list store column we need to # We only have a single list store column we need to
# update # update
try: try:
# Only update if different # Only update if different
@ -328,7 +328,7 @@ class TorrentView(listview.ListView, component.Component):
row[column_index] = status[torrent_id][ row[column_index] = status[torrent_id][
self.columns[column].status_field[0]] self.columns[column].status_field[0]]
except (TypeError, KeyError), e: except (TypeError, KeyError), e:
log.warning("Unable to update column %s: %s", log.warning("Unable to update column %s: %s",
column, e) column, e)
else: else:
# We have more than 1 liststore column to update # We have more than 1 liststore column to update
@ -340,7 +340,7 @@ class TorrentView(listview.ListView, component.Component):
status[torrent_id][ status[torrent_id][
self.columns[column].status_field[ self.columns[column].status_field[
column_index.index(index)]]: column_index.index(index)]]:
row[index] = \ row[index] = \
status[torrent_id][ status[torrent_id][
self.columns[column].status_field[ self.columns[column].status_field[
@ -351,7 +351,7 @@ class TorrentView(listview.ListView, component.Component):
# Update the toolbar buttons just in case some state has changed # Update the toolbar buttons just in case some state has changed
component.get("ToolBar").update_buttons() component.get("ToolBar").update_buttons()
component.get("MenuBar").update_menu() component.get("MenuBar").update_menu()
def _on_get_torrents_status(self, status): def _on_get_torrents_status(self, status):
"""Callback function for get_torrents_status(). 'status' should be a """Callback function for get_torrents_status(). 'status' should be a
dictionary of {torrent_id: {key, value}}.""" dictionary of {torrent_id: {key, value}}."""
@ -359,10 +359,10 @@ class TorrentView(listview.ListView, component.Component):
self.status = status self.status = status
else: else:
self.status = {} self.status = {}
if self.status != {}: if self.status != {}:
self.update_view() self.update_view()
def add_row(self, torrent_id, update=True): def add_row(self, torrent_id, update=True):
"""Adds a new torrent row to the treeview""" """Adds a new torrent row to the treeview"""
# Insert a new row to the liststore # Insert a new row to the liststore
@ -370,12 +370,12 @@ class TorrentView(listview.ListView, component.Component):
# Store the torrent id # Store the torrent id
self.liststore.set_value( self.liststore.set_value(
row, row,
self.columns["torrent_id"].column_indices[0], self.columns["torrent_id"].column_indices[0],
torrent_id) torrent_id)
if update: if update:
self.update() self.update()
self.update_filter() self.update_filter()
def remove_row(self, torrent_id): def remove_row(self, torrent_id):
"""Removes a row with torrent_id""" """Removes a row with torrent_id"""
for row in self.liststore: for row in self.liststore:
@ -392,7 +392,7 @@ class TorrentView(listview.ListView, component.Component):
log.debug("marking %s dirty", torrent_id) log.debug("marking %s dirty", torrent_id)
row[self.columns["dirty"].column_indices[0]] = True row[self.columns["dirty"].column_indices[0]] = True
if torrent_id: break if torrent_id: break
def get_selected_torrent(self): def get_selected_torrent(self):
"""Returns a torrent_id or None. If multiple torrents are selected, """Returns a torrent_id or None. If multiple torrents are selected,
it will return the torrent_id of the first one.""" it will return the torrent_id of the first one."""
@ -401,7 +401,7 @@ class TorrentView(listview.ListView, component.Component):
return selected[0] return selected[0]
else: else:
return selected return selected
def get_selected_torrents(self): def get_selected_torrents(self):
"""Returns a list of selected torrents or None""" """Returns a list of selected torrents or None"""
torrent_ids = [] torrent_ids = []
@ -417,7 +417,7 @@ class TorrentView(listview.ListView, component.Component):
except Exception, e: except Exception, e:
log.debug("Unable to get iter from path: %s", e) log.debug("Unable to get iter from path: %s", e)
continue continue
child_row = self.model_filter.convert_iter_to_child_iter(None, row) child_row = self.model_filter.convert_iter_to_child_iter(None, row)
child_row = self.model_filter.get_model().convert_iter_to_child_iter(child_row) child_row = self.model_filter.get_model().convert_iter_to_child_iter(child_row)
if self.liststore.iter_is_valid(child_row): if self.liststore.iter_is_valid(child_row):
@ -429,22 +429,22 @@ class TorrentView(listview.ListView, component.Component):
torrent_ids.append(value) torrent_ids.append(value)
if len(torrent_ids) == 0: if len(torrent_ids) == 0:
return [] return []
return torrent_ids return torrent_ids
except ValueError, TypeError: except ValueError, TypeError:
return [] return []
def get_torrent_status(self, torrent_id): def get_torrent_status(self, torrent_id):
"""Returns data stored in self.status, it may not be complete""" """Returns data stored in self.status, it may not be complete"""
try: try:
return self.status[torrent_id] return self.status[torrent_id]
except: except:
return {} return {}
def get_visible_torrents(self): def get_visible_torrents(self):
return self.status.keys() return self.status.keys()
### Callbacks ### ### Callbacks ###
def on_button_press_event(self, widget, event): def on_button_press_event(self, widget, event):
"""This is a callback for showing the right-click context menu.""" """This is a callback for showing the right-click context menu."""
log.debug("on_button_press_event") log.debug("on_button_press_event")
@ -465,13 +465,13 @@ class TorrentView(listview.ListView, component.Component):
torrentmenu = component.get("MenuBar").torrentmenu torrentmenu = component.get("MenuBar").torrentmenu
torrentmenu.popup(None, None, None, event.button, event.time) torrentmenu.popup(None, None, None, event.button, event.time)
return True return True
def on_selection_changed(self, treeselection): def on_selection_changed(self, treeselection):
"""This callback is know when the selection has changed.""" """This callback is know when the selection has changed."""
log.debug("on_selection_changed") log.debug("on_selection_changed")
component.get("TorrentDetails").update() component.get("TorrentDetails").update()
component.get("ToolBar").update_buttons() component.get("ToolBar").update_buttons()
component.get("MenuBar").update_menu() component.get("MenuBar").update_menu()
def on_drag_drop(self, widget, drag_context, x, y, timestamp): def on_drag_drop(self, widget, drag_context, x, y, timestamp):
widget.stop_emission("drag-drop") widget.stop_emission("drag-drop")

View File

@ -201,7 +201,7 @@ class CommandConfig(Command):
color = CYAN color = CYAN
elif isinstance(value, list): elif isinstance(value, list):
color = MAGENTA color = MAGENTA
print ("* " + BLUE_B + "%s:" + color + " %s" + NORMAL) % (key, value) print ("* " + BLUE_B + "%s:" + color + " %s" + NORMAL) % (key, value)
client.get_config(_on_get_config) client.get_config(_on_get_config)

View File

@ -2,19 +2,19 @@
# signalreceiver.py # signalreceiver.py
# #
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -47,23 +47,23 @@ import socket
from deluge.log import LOG as log from deluge.log import LOG as log
class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer):
def __init__(self): def __init__(self):
log.debug("SignalReceiver init..") log.debug("SignalReceiver init..")
self.signals = {} self.signals = {}
self.emitted_signals = [] self.emitted_signals = []
self.remote = False self.remote = False
self.start_server() self.start_server()
def start_server(self, port=None): def start_server(self, port=None):
# Setup the xmlrpc server # Setup the xmlrpc server
host = "127.0.0.1" host = "127.0.0.1"
if self.remote: if self.remote:
host = "" host = ""
server_ready = False server_ready = False
while not server_ready: while not server_ready:
if port: if port:
@ -81,10 +81,10 @@ class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer):
else: else:
self.port = _port self.port = _port
server_ready = True server_ready = True
# Register the emit_signal function # Register the emit_signal function
self.register_function(self.emit_signal) self.register_function(self.emit_signal)
def shutdown(self): def shutdown(self):
"""Shutdowns receiver thread""" """Shutdowns receiver thread"""
self._shutdown = True self._shutdown = True
@ -94,18 +94,18 @@ class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer):
client.force_call() client.force_call()
except Exception, e: except Exception, e:
log.debug("Unable to deregister client from server: %s", e) log.debug("Unable to deregister client from server: %s", e)
log.debug("Shutting down signalreceiver") log.debug("Shutting down signalreceiver")
self.socket.shutdown(socket.SHUT_RDWR) self.socket.shutdown(socket.SHUT_RDWR)
log.debug("Joining listening thread..") log.debug("Joining listening thread..")
self.listening_thread.join(1.0) self.listening_thread.join(1.0)
return return
def set_remote(self, remote): def set_remote(self, remote):
self.remote = remote self.remote = remote
self.start_server(self.port) self.start_server(self.port)
def run(self): def run(self):
"""This gets called when we start the thread""" """This gets called when we start the thread"""
# Set to true so that the receiver thread will exit # Set to true so that the receiver thread will exit
@ -113,16 +113,16 @@ class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer):
# Register the signal receiver with the core # Register the signal receiver with the core
client.register_client(str(self.port)) client.register_client(str(self.port))
self.listening_thread = threading.Thread(target=self.handle_thread) self.listening_thread = threading.Thread(target=self.handle_thread)
gobject.timeout_add(50, self.handle_signals) gobject.timeout_add(50, self.handle_signals)
try: try:
self.listening_thread.start() self.listening_thread.start()
except Exception, e: except Exception, e:
log.debug("Thread: %s", e) log.debug("Thread: %s", e)
def handle_thread(self): def handle_thread(self):
try: try:
while not self._shutdown: while not self._shutdown:
@ -130,11 +130,11 @@ class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer):
self._shutdown = False self._shutdown = False
except Exception, e: except Exception, e:
log.debug("handle_thread: %s", e) log.debug("handle_thread: %s", e)
def get_port(self): def get_port(self):
"""Get the port that the SignalReceiver is listening on""" """Get the port that the SignalReceiver is listening on"""
return self.port return self.port
def emit_signal(self, signal, *data): def emit_signal(self, signal, *data):
"""Exported method used by the core to emit a signal to the client""" """Exported method used by the core to emit a signal to the client"""
self.emitted_signals.append((signal, data)) self.emitted_signals.append((signal, data))
@ -145,13 +145,13 @@ class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer):
try: try:
for callback in self.signals[signal]: for callback in self.signals[signal]:
gobject.idle_add(callback, *data) gobject.idle_add(callback, *data)
except Exception, e: except Exception, e:
log.warning("Unable to call callback for signal %s: %s", signal, e) log.warning("Unable to call callback for signal %s: %s", signal, e)
self.emitted_signals = [] self.emitted_signals = []
return True return True
def connect_to_signal(self, signal, callback): def connect_to_signal(self, signal, callback):
"""Connect to a signal""" """Connect to a signal"""
try: try:

View File

@ -2,19 +2,19 @@
# ui.py # ui.py
# #
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
# You may redistribute it and/or modify it under the terms of the # You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software # GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) # Foundation; either version 3 of the License, or (at your option)
# any later version. # any later version.
# #
# deluge is distributed in the hope that it will be useful, # deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details. # See the GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
@ -45,17 +45,17 @@ class UI:
# Set the config directory # Set the config directory
deluge.configmanager.set_config_dir(options.config) deluge.configmanager.set_config_dir(options.config)
config = deluge.configmanager.ConfigManager("ui.conf", DEFAULT_PREFS) config = deluge.configmanager.ConfigManager("ui.conf", DEFAULT_PREFS)
if not options.ui: if not options.ui:
selected_ui = config["default_ui"] selected_ui = config["default_ui"]
else: else:
selected_ui = options.ui selected_ui = options.ui
config.save() config.save()
del config del config
if selected_ui == "gtk": if selected_ui == "gtk":
log.info("Starting GtkUI..") log.info("Starting GtkUI..")
from deluge.ui.gtkui.gtkui import GtkUI from deluge.ui.gtkui.gtkui import GtkUI

View File

@ -305,7 +305,7 @@ class ModelChoiceField(ChoiceField):
help_text=None, *args, **kwargs): help_text=None, *args, **kwargs):
self.empty_label = empty_label self.empty_label = empty_label
self.cache_choices = cache_choices self.cache_choices = cache_choices
# Call Field instead of ChoiceField __init__() because we don't need # Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__(). # ChoiceField.__init__().
Field.__init__(self, required, widget, label, initial, help_text, Field.__init__(self, required, widget, label, initial, help_text,
@ -321,8 +321,8 @@ class ModelChoiceField(ChoiceField):
queryset = property(_get_queryset, _set_queryset) queryset = property(_get_queryset, _set_queryset)
# this method will be used to create object labels by the QuerySetIterator. # this method will be used to create object labels by the QuerySetIterator.
# Override it to customize the label. # Override it to customize the label.
def label_from_instance(self, obj): def label_from_instance(self, obj):
""" """
This method is used to convert objects into strings; it's used to This method is used to convert objects into strings; it's used to
@ -330,7 +330,7 @@ class ModelChoiceField(ChoiceField):
can override this method to customize the display of the choices. can override this method to customize the display of the choices.
""" """
return smart_unicode(obj) return smart_unicode(obj)
def _get_choices(self): def _get_choices(self):
# If self._choices is set, then somebody must have manually set # If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices. # the property self.choices. In this case, just return self._choices.

View File

@ -32,7 +32,7 @@ except ImportError:
def main(): def main():
import doctest import doctest
doctest.testmod(utils) doctest.testmod(utils)
doctest.testmod(db) doctest.testmod(db)
doctest.testmod(net) doctest.testmod(net)
@ -40,21 +40,21 @@ def main():
doctest.testmod(http) doctest.testmod(http)
doctest.testmod(webapi) doctest.testmod(webapi)
doctest.testmod(request) doctest.testmod(request)
try: try:
doctest.testmod(cheetah) doctest.testmod(cheetah)
except NameError: except NameError:
pass pass
template.test() template.test()
import sys import sys
urls = ('/web.py', 'source') urls = ('/web.py', 'source')
class source: class source:
def GET(self): def GET(self):
header('Content-Type', 'text/python') header('Content-Type', 'text/python')
print open(sys.argv[0]).read() print open(sys.argv[0]).read()
if listget(sys.argv, 1) != 'test': if listget(sys.argv, 1) != 'test':
run(urls, locals()) run(urls, locals())

View File

@ -20,22 +20,22 @@ def upvars(level=2):
r_include = re_compile(r'(?!\\)#include \"(.*?)\"($|#)', re.M) r_include = re_compile(r'(?!\\)#include \"(.*?)\"($|#)', re.M)
def __compiletemplate(template, base=None, isString=False): def __compiletemplate(template, base=None, isString=False):
if isString: if isString:
text = template text = template
else: else:
text = open('templates/'+template).read() text = open('templates/'+template).read()
# implement #include at compile-time # implement #include at compile-time
def do_include(match): def do_include(match):
text = open('templates/'+match.groups()[0]).read() text = open('templates/'+match.groups()[0]).read()
return text return text
while r_include.findall(text): while r_include.findall(text):
text = r_include.sub(do_include, text) text = r_include.sub(do_include, text)
execspace = _compiletemplate.bases.copy() execspace = _compiletemplate.bases.copy()
tmpl_compiler = Compiler(source=text, mainClassName='GenTemplate') tmpl_compiler = Compiler(source=text, mainClassName='GenTemplate')
tmpl_compiler.addImportedVarNames(execspace.keys()) tmpl_compiler.addImportedVarNames(execspace.keys())
exec str(tmpl_compiler) in execspace exec str(tmpl_compiler) in execspace
if base: if base:
_compiletemplate.bases[base] = execspace['GenTemplate'] _compiletemplate.bases[base] = execspace['GenTemplate']
return execspace['GenTemplate'] return execspace['GenTemplate']
@ -43,17 +43,17 @@ def __compiletemplate(template, base=None, isString=False):
_compiletemplate = memoize(__compiletemplate) _compiletemplate = memoize(__compiletemplate)
_compiletemplate.bases = {} _compiletemplate.bases = {}
def render(template, terms=None, asTemplate=False, base=None, def render(template, terms=None, asTemplate=False, base=None,
isString=False): isString=False):
""" """
Renders a template, caching where it can. Renders a template, caching where it can.
`template` is the name of a file containing the a template in `template` is the name of a file containing the a template in
the `templates/` folder, unless `isString`, in which case it's the the `templates/` folder, unless `isString`, in which case it's the
template itself. template itself.
`terms` is a dictionary used to fill the template. If it's None, then `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 the caller's local variables are used instead, plus context, if it's not
already set, is set to `context`. already set, is set to `context`.
If asTemplate is False, it `output`s the template directly. Otherwise, If asTemplate is False, it `output`s the template directly. Otherwise,
@ -69,7 +69,7 @@ def render(template, terms=None, asTemplate=False, base=None,
if isinstance(terms, list): if isinstance(terms, list):
new = {} new = {}
old = upvars() old = upvars()
for k in terms: for k in terms:
new[k] = old[k] new[k] = old[k]
terms = new terms = new
# default: grab all locals # default: grab all locals
@ -77,22 +77,22 @@ def render(template, terms=None, asTemplate=False, base=None,
terms = {'context': ctx, 'ctx':ctx} terms = {'context': ctx, 'ctx':ctx}
terms.update(sys._getframe(1).f_locals) terms.update(sys._getframe(1).f_locals)
# terms=d means use d as the searchList # terms=d means use d as the searchList
if not isinstance(terms, tuple): if not isinstance(terms, tuple):
terms = (terms,) terms = (terms,)
if 'headers' in ctx and not isString and template.endswith('.html'): if 'headers' in ctx and not isString and template.endswith('.html'):
header('Content-Type','text/html; charset=utf-8', unique=True) header('Content-Type','text/html; charset=utf-8', unique=True)
if loadhooks.has_key('reloader'): if loadhooks.has_key('reloader'):
compiled_tmpl = __compiletemplate(template, base=base, isString=isString) compiled_tmpl = __compiletemplate(template, base=base, isString=isString)
else: else:
compiled_tmpl = _compiletemplate(template, base=base, isString=isString) compiled_tmpl = _compiletemplate(template, base=base, isString=isString)
compiled_tmpl = compiled_tmpl(searchList=terms, filter=WebSafe) compiled_tmpl = compiled_tmpl(searchList=terms, filter=WebSafe)
if asTemplate: if asTemplate:
return compiled_tmpl return compiled_tmpl
else: else:
return output(str(compiled_tmpl)) return output(str(compiled_tmpl))
class WebSafe(Filter): class WebSafe(Filter):
def filter(self, val, **keywords): def filter(self, val, **keywords):
return websafe(val) return websafe(val)

View File

@ -12,7 +12,7 @@ __all__ = [
"sqllist", "sqlors", "aparam", "reparam", "sqllist", "sqlors", "aparam", "reparam",
"SQLQuery", "sqlquote", "SQLQuery", "sqlquote",
"SQLLiteral", "sqlliteral", "SQLLiteral", "sqlliteral",
"connect", "connect",
"TransactionError", "transaction", "transact", "commit", "rollback", "TransactionError", "transaction", "transact", "commit", "rollback",
"query", "query",
"select", "insert", "update", "delete" "select", "insert", "update", "delete"
@ -45,17 +45,17 @@ def _interpolate(format):
Takes a format string and returns a list of 2-tuples of the form Takes a format string and returns a list of 2-tuples of the form
(boolean, string) where boolean says whether string should be evaled (boolean, string) where boolean says whether string should be evaled
or not. or not.
from <http://lfw.org/python/Itpl.py> (public domain, Ka-Ping Yee) from <http://lfw.org/python/Itpl.py> (public domain, Ka-Ping Yee)
""" """
from tokenize import tokenprog from tokenize import tokenprog
def matchorfail(text, pos): def matchorfail(text, pos):
match = tokenprog.match(text, pos) match = tokenprog.match(text, pos)
if match is None: if match is None:
raise _ItplError(text, pos) raise _ItplError(text, pos)
return match, match.end() return match, match.end()
namechars = "abcdefghijklmnopqrstuvwxyz" \ namechars = "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
chunks = [] chunks = []
@ -63,7 +63,7 @@ def _interpolate(format):
while 1: while 1:
dollar = format.find("$", pos) dollar = format.find("$", pos)
if dollar < 0: if dollar < 0:
break break
nextchar = format[dollar + 1] nextchar = format[dollar + 1]
@ -74,9 +74,9 @@ def _interpolate(format):
match, pos = matchorfail(format, pos) match, pos = matchorfail(format, pos)
tstart, tend = match.regs[3] tstart, tend = match.regs[3]
token = format[tstart:tend] token = format[tstart:tend]
if token == "{": if token == "{":
level = level + 1 level = level + 1
elif token == "}": elif token == "}":
level = level - 1 level = level - 1
chunks.append((1, format[dollar + 2:pos - 1])) chunks.append((1, format[dollar + 2:pos - 1]))
@ -93,11 +93,11 @@ def _interpolate(format):
match, pos = matchorfail(format, pos) match, pos = matchorfail(format, pos)
tstart, tend = match.regs[3] tstart, tend = match.regs[3]
token = format[tstart:tend] token = format[tstart:tend]
if token[0] in "([": if token[0] in "([":
level = level + 1 level = level + 1
elif token[0] in ")]": elif token[0] in ")]":
level = level - 1 level = level - 1
else: else:
break break
chunks.append((1, format[dollar + 1:pos])) chunks.append((1, format[dollar + 1:pos]))
@ -105,14 +105,14 @@ def _interpolate(format):
chunks.append((0, format[pos:dollar + 1])) chunks.append((0, format[pos:dollar + 1]))
pos = dollar + 1 + (nextchar == "$") pos = dollar + 1 + (nextchar == "$")
if pos < len(format): if pos < len(format):
chunks.append((0, format[pos:])) chunks.append((0, format[pos:]))
return chunks return chunks
class UnknownParamstyle(Exception): class UnknownParamstyle(Exception):
""" """
raised for unsupported db paramstyles raised for unsupported db paramstyles
(currently supported: qmark, numeric, format, pyformat) (currently supported: qmark, numeric, format, pyformat)
""" """
pass pass
@ -122,7 +122,7 @@ def aparam():
Returns the appropriate string to be used to interpolate Returns the appropriate string to be used to interpolate
a value with the current `web.ctx.db_module` or simply %s a value with the current `web.ctx.db_module` or simply %s
if there isn't one. if there isn't one.
>>> aparam() >>> aparam()
'%s' '%s'
""" """
@ -130,12 +130,12 @@ def aparam():
style = web.ctx.db_module.paramstyle style = web.ctx.db_module.paramstyle
else: else:
style = 'pyformat' style = 'pyformat'
if style == 'qmark': if style == 'qmark':
return '?' return '?'
elif style == 'numeric': elif style == 'numeric':
return ':1' return ':1'
elif style in ['format', 'pyformat']: elif style in ['format', 'pyformat']:
return '%s' return '%s'
raise UnknownParamstyle, style raise UnknownParamstyle, style
@ -143,7 +143,7 @@ def reparam(string_, dictionary):
""" """
Takes a string and a dictionary and interpolates the string Takes a string and a dictionary and interpolates the string
using values from the dictionary. Returns an `SQLQuery` for the result. using values from the dictionary. Returns an `SQLQuery` for the result.
>>> reparam("s = $s", dict(s=True)) >>> reparam("s = $s", dict(s=True))
<sql: "s = 't'"> <sql: "s = 't'">
""" """
@ -159,7 +159,7 @@ def reparam(string_, dictionary):
def sqlify(obj): def sqlify(obj):
""" """
converts `obj` to its proper SQL version converts `obj` to its proper SQL version
>>> sqlify(None) >>> sqlify(None)
'NULL' 'NULL'
>>> sqlify(True) >>> sqlify(True)
@ -167,10 +167,10 @@ def sqlify(obj):
>>> sqlify(3) >>> sqlify(3)
'3' '3'
""" """
# because `1 == True and hash(1) == hash(True)` # because `1 == True and hash(1) == hash(True)`
# we have to do this the hard way... # we have to do this the hard way...
if obj is None: if obj is None:
return 'NULL' return 'NULL'
elif obj is True: elif obj is True:
@ -191,7 +191,7 @@ class SQLQuery:
# tested in sqlquote's docstring # tested in sqlquote's docstring
def __init__(self, s='', v=()): def __init__(self, s='', v=()):
self.s, self.v = str(s), tuple(v) self.s, self.v = str(s), tuple(v)
def __getitem__(self, key): # for backwards-compatibility def __getitem__(self, key): # for backwards-compatibility
return [self.s, self.v][key] return [self.s, self.v][key]
@ -209,13 +209,13 @@ class SQLQuery:
return self return self
else: else:
return NotImplemented return NotImplemented
def __str__(self): def __str__(self):
try: try:
return self.s % tuple([sqlify(x) for x in self.v]) return self.s % tuple([sqlify(x) for x in self.v])
except (ValueError, TypeError): except (ValueError, TypeError):
return self.s return self.s
def __repr__(self): def __repr__(self):
return '<sql: %s>' % repr(str(self)) return '<sql: %s>' % repr(str(self))
@ -226,10 +226,10 @@ class SQLLiteral:
>>> insert('foo', time=SQLLiteral('NOW()'), _test=True) >>> insert('foo', time=SQLLiteral('NOW()'), _test=True)
<sql: 'INSERT INTO foo (time) VALUES (NOW())'> <sql: 'INSERT INTO foo (time) VALUES (NOW())'>
""" """
def __init__(self, v): def __init__(self, v):
self.v = v self.v = v
def __repr__(self): def __repr__(self):
return self.v return self.v
sqlliteral = SQLLiteral sqlliteral = SQLLiteral
@ -237,7 +237,7 @@ sqlliteral = SQLLiteral
def sqlquote(a): def sqlquote(a):
""" """
Ensures `a` is quoted properly for use in a SQL query. Ensures `a` is quoted properly for use in a SQL query.
>>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3) >>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3)
<sql: "WHERE x = 't' AND y = 3"> <sql: "WHERE x = 't' AND y = 3">
""" """
@ -249,19 +249,19 @@ class UnknownDB(Exception):
def connect(dbn, **keywords): def connect(dbn, **keywords):
""" """
Connects to the specified database. Connects to the specified database.
`dbn` currently must be "postgres", "mysql", or "sqlite". `dbn` currently must be "postgres", "mysql", or "sqlite".
If DBUtils is installed, connection pooling will be used. If DBUtils is installed, connection pooling will be used.
""" """
if dbn == "postgres": if dbn == "postgres":
try: try:
import psycopg2 as db import psycopg2 as db
except ImportError: except ImportError:
try: try:
import psycopg as db import psycopg as db
except ImportError: except ImportError:
import pgdb as db import pgdb as db
if 'pw' in keywords: if 'pw' in keywords:
keywords['password'] = keywords['pw'] keywords['password'] = keywords['pw']
@ -289,7 +289,7 @@ def connect(dbn, **keywords):
web.config._hasPooling = False web.config._hasPooling = False
keywords['database'] = keywords['db'] keywords['database'] = keywords['db']
del keywords['db'] del keywords['db']
elif dbn == "firebird": elif dbn == "firebird":
import kinterbasdb as db import kinterbasdb as db
if 'pw' in keywords: if 'pw' in keywords:
@ -298,7 +298,7 @@ def connect(dbn, **keywords):
keywords['database'] = keywords['db'] keywords['database'] = keywords['db']
del keywords['db'] del keywords['db']
else: else:
raise UnknownDB, dbn raise UnknownDB, dbn
web.ctx.db_name = dbn web.ctx.db_name = dbn
@ -313,12 +313,12 @@ def connect(dbn, **keywords):
return PooledDB.PooledDB(dbapi=db, **keywords) return PooledDB.PooledDB(dbapi=db, **keywords)
else: else:
return PooledDB.PooledDB(creator=db, **keywords) return PooledDB.PooledDB(creator=db, **keywords)
def db_cursor(): def db_cursor():
if isinstance(web.ctx.db, dict): if isinstance(web.ctx.db, dict):
keywords = web.ctx.db keywords = web.ctx.db
if web.config._hasPooling: if web.config._hasPooling:
if 'db' not in globals(): if 'db' not in globals():
globals()['db'] = _PooledDB(db, keywords) globals()['db'] = _PooledDB(db, keywords)
web.ctx.db = globals()['db'].connection() web.ctx.db = globals()['db'].connection()
else: else:
@ -327,12 +327,12 @@ def connect(dbn, **keywords):
web.ctx.db_cursor = db_cursor web.ctx.db_cursor = db_cursor
web.ctx.dbq_count = 0 web.ctx.dbq_count = 0
def db_execute(cur, sql_query, dorollback=True): def db_execute(cur, sql_query, dorollback=True):
"""executes an sql query""" """executes an sql query"""
web.ctx.dbq_count += 1 web.ctx.dbq_count += 1
try: try:
a = time.time() a = time.time()
out = cur.execute(sql_query.s, sql_query.v) out = cur.execute(sql_query.s, sql_query.v)
@ -372,31 +372,31 @@ def transact():
"""Start a transaction.""" """Start a transaction."""
if not web.ctx.db_transaction: if not web.ctx.db_transaction:
# commit everything up to now, so we don't rollback it later # commit everything up to now, so we don't rollback it later
if hasattr(web.ctx.db, 'commit'): if hasattr(web.ctx.db, 'commit'):
web.ctx.db.commit() web.ctx.db.commit()
else: else:
db_cursor = web.ctx.db_cursor() db_cursor = web.ctx.db_cursor()
web.ctx.db_execute(db_cursor, web.ctx.db_execute(db_cursor,
SQLQuery("SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction)) SQLQuery("SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction))
web.ctx.db_transaction += 1 web.ctx.db_transaction += 1
def commit(): def commit():
"""Commits a transaction.""" """Commits a transaction."""
web.ctx.db_transaction -= 1 web.ctx.db_transaction -= 1
if web.ctx.db_transaction < 0: if web.ctx.db_transaction < 0:
raise TransactionError, "not in a transaction" raise TransactionError, "not in a transaction"
if not web.ctx.db_transaction: if not web.ctx.db_transaction:
if hasattr(web.ctx.db, 'commit'): if hasattr(web.ctx.db, 'commit'):
web.ctx.db.commit() web.ctx.db.commit()
else: else:
db_cursor = web.ctx.db_cursor() db_cursor = web.ctx.db_cursor()
web.ctx.db_execute(db_cursor, web.ctx.db_execute(db_cursor,
SQLQuery("RELEASE SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction)) SQLQuery("RELEASE SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction))
def rollback(care=True): def rollback(care=True):
"""Rolls back a transaction.""" """Rolls back a transaction."""
web.ctx.db_transaction -= 1 web.ctx.db_transaction -= 1
if web.ctx.db_transaction < 0: if web.ctx.db_transaction < 0:
web.db_transaction = 0 web.db_transaction = 0
if care: if care:
@ -405,7 +405,7 @@ def rollback(care=True):
return return
if not web.ctx.db_transaction: if not web.ctx.db_transaction:
if hasattr(web.ctx.db, 'rollback'): if hasattr(web.ctx.db, 'rollback'):
web.ctx.db.rollback() web.ctx.db.rollback()
else: else:
db_cursor = web.ctx.db_cursor() db_cursor = web.ctx.db_cursor()
@ -416,9 +416,9 @@ def rollback(care=True):
def query(sql_query, vars=None, processed=False, _test=False): def query(sql_query, vars=None, processed=False, _test=False):
""" """
Execute SQL query `sql_query` using dictionary `vars` to interpolate it. Execute SQL query `sql_query` using dictionary `vars` to interpolate it.
If `processed=True`, `vars` is a `reparam`-style list to use If `processed=True`, `vars` is a `reparam`-style list to use
instead of interpolating. instead of interpolating.
>>> query("SELECT * FROM foo", _test=True) >>> query("SELECT * FROM foo", _test=True)
<sql: 'SELECT * FROM foo'> <sql: 'SELECT * FROM foo'>
>>> query("SELECT * FROM foo WHERE x = $x", vars=dict(x='f'), _test=True) >>> query("SELECT * FROM foo WHERE x = $x", vars=dict(x='f'), _test=True)
@ -427,15 +427,15 @@ def query(sql_query, vars=None, processed=False, _test=False):
<sql: "SELECT * FROM foo WHERE x = 'f'"> <sql: "SELECT * FROM foo WHERE x = 'f'">
""" """
if vars is None: vars = {} if vars is None: vars = {}
if not processed and not isinstance(sql_query, SQLQuery): if not processed and not isinstance(sql_query, SQLQuery):
sql_query = reparam(sql_query, vars) sql_query = reparam(sql_query, vars)
if _test: return sql_query if _test: return sql_query
db_cursor = web.ctx.db_cursor() db_cursor = web.ctx.db_cursor()
web.ctx.db_execute(db_cursor, sql_query) web.ctx.db_execute(db_cursor, sql_query)
if db_cursor.description: if db_cursor.description:
names = [x[0] for x in db_cursor.description] names = [x[0] for x in db_cursor.description]
def iterwrapper(): def iterwrapper():
@ -450,28 +450,28 @@ def query(sql_query, vars=None, processed=False, _test=False):
for x in db_cursor.fetchall()] for x in db_cursor.fetchall()]
else: else:
out = db_cursor.rowcount out = db_cursor.rowcount
if not web.ctx.db_transaction: web.ctx.db.commit() if not web.ctx.db_transaction: web.ctx.db.commit()
return out return out
def sqllist(lst): def sqllist(lst):
""" """
Converts the arguments for use in something like a WHERE clause. Converts the arguments for use in something like a WHERE clause.
>>> sqllist(['a', 'b']) >>> sqllist(['a', 'b'])
'a, b' 'a, b'
>>> sqllist('a') >>> sqllist('a')
'a' 'a'
""" """
if isinstance(lst, str): if isinstance(lst, str):
return lst return lst
else: else:
return ', '.join(lst) return ', '.join(lst)
def sqlors(left, lst): def sqlors(left, lst):
""" """
`left is a SQL clause like `tablename.arg = ` `left is a SQL clause like `tablename.arg = `
and `lst` is a list of values. Returns a reparam-style and `lst` is a list of values. Returns a reparam-style
pair featuring the SQL that ORs together the clause pair featuring the SQL that ORs together the clause
for each item in the lst. for each item in the lst.
@ -490,11 +490,11 @@ def sqlors(left, lst):
ln = len(lst) ln = len(lst)
if ln == 0: if ln == 0:
return SQLQuery("2+2=5", []) return SQLQuery("2+2=5", [])
if ln == 1: if ln == 1:
lst = lst[0] lst = lst[0]
if isinstance(lst, iters): if isinstance(lst, iters):
return SQLQuery('(' + left + return SQLQuery('(' + left +
(' OR ' + left).join([aparam() for param in lst]) + ")", lst) (' OR ' + left).join([aparam() for param in lst]) + ")", lst)
else: else:
return SQLQuery(left + aparam(), [lst]) return SQLQuery(left + aparam(), [lst])
@ -502,24 +502,24 @@ def sqlors(left, lst):
def sqlwhere(dictionary, grouping=' AND '): def sqlwhere(dictionary, grouping=' AND '):
""" """
Converts a `dictionary` to an SQL WHERE clause `SQLQuery`. Converts a `dictionary` to an SQL WHERE clause `SQLQuery`.
>>> sqlwhere({'cust_id': 2, 'order_id':3}) >>> sqlwhere({'cust_id': 2, 'order_id':3})
<sql: 'order_id = 3 AND cust_id = 2'> <sql: 'order_id = 3 AND cust_id = 2'>
>>> sqlwhere({'cust_id': 2, 'order_id':3}, grouping=', ') >>> sqlwhere({'cust_id': 2, 'order_id':3}, grouping=', ')
<sql: 'order_id = 3, cust_id = 2'> <sql: 'order_id = 3, cust_id = 2'>
""" """
return SQLQuery(grouping.join([ return SQLQuery(grouping.join([
'%s = %s' % (k, aparam()) for k in dictionary.keys() '%s = %s' % (k, aparam()) for k in dictionary.keys()
]), dictionary.values()) ]), dictionary.values())
def select(tables, vars=None, what='*', where=None, order=None, group=None, def select(tables, vars=None, what='*', where=None, order=None, group=None,
limit=None, offset=None, _test=False): limit=None, offset=None, _test=False):
""" """
Selects `what` from `tables` with clauses `where`, `order`, Selects `what` from `tables` with clauses `where`, `order`,
`group`, `limit`, and `offset`. Uses vars to interpolate. `group`, `limit`, and `offset`. Uses vars to interpolate.
Otherwise, each clause can be a SQLQuery. Otherwise, each clause can be a SQLQuery.
>>> select('foo', _test=True) >>> select('foo', _test=True)
<sql: 'SELECT * FROM foo'> <sql: 'SELECT * FROM foo'>
>>> select(['foo', 'bar'], where="foo.bar_id = bar.id", limit=5, _test=True) >>> select(['foo', 'bar'], where="foo.bar_id = bar.id", limit=5, _test=True)
@ -527,7 +527,7 @@ def select(tables, vars=None, what='*', where=None, order=None, group=None,
""" """
if vars is None: vars = {} if vars is None: vars = {}
qout = "" qout = ""
def gen_clause(sql, val): def gen_clause(sql, val):
if isinstance(val, (int, long)): if isinstance(val, (int, long)):
if sql == 'WHERE': if sql == 'WHERE':
@ -540,14 +540,14 @@ def select(tables, vars=None, what='*', where=None, order=None, group=None,
nout = val nout = val
elif val: elif val:
nout = reparam(val, vars) nout = reparam(val, vars)
else: else:
return "" return ""
out = "" out = ""
if qout: out += " " if qout: out += " "
out += sql + " " + nout out += sql + " " + nout
return out return out
if web.ctx.get('db_name') == "firebird": if web.ctx.get('db_name') == "firebird":
for (sql, val) in ( for (sql, val) in (
('FIRST', limit), ('FIRST', limit),
@ -588,11 +588,11 @@ def insert(tablename, seqname=None, _test=False, **values):
Inserts `values` into `tablename`. Returns current sequence ID. Inserts `values` into `tablename`. Returns current sequence ID.
Set `seqname` to the ID if it's not the default, or to `False` Set `seqname` to the ID if it's not the default, or to `False`
if there isn't one. if there isn't one.
>>> insert('foo', joe='bob', a=2, _test=True) >>> insert('foo', joe='bob', a=2, _test=True)
<sql: "INSERT INTO foo (a, joe) VALUES (2, 'bob')"> <sql: "INSERT INTO foo (a, joe) VALUES (2, 'bob')">
""" """
if values: if values:
sql_query = SQLQuery("INSERT INTO %s (%s) VALUES (%s)" % ( sql_query = SQLQuery("INSERT INTO %s (%s) VALUES (%s)" % (
tablename, tablename,
@ -603,12 +603,12 @@ def insert(tablename, seqname=None, _test=False, **values):
sql_query = SQLQuery("INSERT INTO %s DEFAULT VALUES" % tablename) sql_query = SQLQuery("INSERT INTO %s DEFAULT VALUES" % tablename)
if _test: return sql_query if _test: return sql_query
db_cursor = web.ctx.db_cursor() db_cursor = web.ctx.db_cursor()
if seqname is False: if seqname is False:
pass pass
elif web.ctx.db_name == "postgres": elif web.ctx.db_name == "postgres":
if seqname is None: if seqname is None:
seqname = tablename + "_id_seq" seqname = tablename + "_id_seq"
sql_query += "; SELECT currval('%s')" % seqname sql_query += "; SELECT currval('%s')" % seqname
elif web.ctx.db_name == "mysql": elif web.ctx.db_name == "mysql":
@ -618,13 +618,13 @@ def insert(tablename, seqname=None, _test=False, **values):
web.ctx.db_execute(db_cursor, sql_query) web.ctx.db_execute(db_cursor, sql_query)
# not really the same... # not really the same...
sql_query = SQLQuery("SELECT last_insert_rowid()") sql_query = SQLQuery("SELECT last_insert_rowid()")
web.ctx.db_execute(db_cursor, sql_query) web.ctx.db_execute(db_cursor, sql_query)
try: try:
out = db_cursor.fetchone()[0] out = db_cursor.fetchone()[0]
except Exception: except Exception:
out = None out = None
if not web.ctx.db_transaction: web.ctx.db.commit() if not web.ctx.db_transaction: web.ctx.db.commit()
return out return out
@ -633,14 +633,14 @@ def update(tables, where, vars=None, _test=False, **values):
""" """
Update `tables` with clause `where` (interpolated using `vars`) Update `tables` with clause `where` (interpolated using `vars`)
and setting `values`. and setting `values`.
>>> joe = 'Joseph' >>> joe = 'Joseph'
>>> update('foo', where='name = $joe', name='bob', age=5, >>> update('foo', where='name = $joe', name='bob', age=5,
... vars=locals(), _test=True) ... vars=locals(), _test=True)
<sql: "UPDATE foo SET age = 5, name = 'bob' WHERE name = 'Joseph'"> <sql: "UPDATE foo SET age = 5, name = 'bob' WHERE name = 'Joseph'">
""" """
if vars is None: vars = {} if vars is None: vars = {}
if isinstance(where, (int, long)): if isinstance(where, (int, long)):
where = "id = " + sqlquote(where) where = "id = " + sqlquote(where)
elif isinstance(where, (list, tuple)) and len(where) == 2: elif isinstance(where, (list, tuple)) and len(where) == 2:
@ -649,24 +649,24 @@ def update(tables, where, vars=None, _test=False, **values):
pass pass
else: else:
where = reparam(where, vars) where = reparam(where, vars)
query = ( query = (
"UPDATE " + sqllist(tables) + "UPDATE " + sqllist(tables) +
" SET " + sqlwhere(values, ', ') + " SET " + sqlwhere(values, ', ') +
" WHERE " + where) " WHERE " + where)
if _test: return query if _test: return query
db_cursor = web.ctx.db_cursor() db_cursor = web.ctx.db_cursor()
web.ctx.db_execute(db_cursor, query) web.ctx.db_execute(db_cursor, query)
if not web.ctx.db_transaction: web.ctx.db.commit() if not web.ctx.db_transaction: web.ctx.db.commit()
return db_cursor.rowcount return db_cursor.rowcount
def delete(table, where=None, using=None, vars=None, _test=False): def delete(table, where=None, using=None, vars=None, _test=False):
""" """
Deletes from `table` with clauses `where` and `using`. Deletes from `table` with clauses `where` and `using`.
>>> name = 'Joe' >>> name = 'Joe'
>>> delete('foo', where='name = $name', vars=locals(), _test=True) >>> delete('foo', where='name = $name', vars=locals(), _test=True)
<sql: "DELETE FROM foo WHERE name = 'Joe'"> <sql: "DELETE FROM foo WHERE name = 'Joe'">
@ -689,9 +689,9 @@ def delete(table, where=None, using=None, vars=None, _test=False):
q += ' WHERE ' + where q += ' WHERE ' + where
if using and web.ctx.get('db_name') != "firebird": if using and web.ctx.get('db_name') != "firebird":
q += ' USING ' + sqllist(using) q += ' USING ' + sqllist(using)
if _test: return q if _test: return q
db_cursor = web.ctx.db_cursor() db_cursor = web.ctx.db_cursor()
web.ctx.db_execute(db_cursor, q) web.ctx.db_execute(db_cursor, q)

View File

@ -2,7 +2,7 @@
pretty debug errors pretty debug errors
(part of web.py) (part of web.py)
adapted from Django <djangoproject.com> adapted from Django <djangoproject.com>
Copyright (c) 2005, the Lawrence Journal-World Copyright (c) 2005, the Lawrence Journal-World
Used under the modified BSD license: Used under the modified BSD license:
http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
@ -37,11 +37,11 @@ $def with (exception_type, exception_value, frames)
h2 span { font-size:80%; color:#666; font-weight:normal; } h2 span { font-size:80%; color:#666; font-weight:normal; }
h3 { margin:1em 0 .5em 0; } h3 { margin:1em 0 .5em 0; }
h4 { margin:0 0 .5em 0; font-weight: normal; } h4 { margin:0 0 .5em 0; font-weight: normal; }
table { table {
border:1px solid #ccc; border-collapse: collapse; background:white; } border:1px solid #ccc; border-collapse: collapse; background:white; }
tbody td, tbody th { vertical-align:top; padding:2px 3px; } tbody td, tbody th { vertical-align:top; padding:2px 3px; }
thead th { thead th {
padding:1px 6px 1px 3px; background:#fefefe; text-align:left; padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
font-weight:normal; font-size:11px; border:1px solid #ddd; } font-weight:normal; font-size:11px; border:1px solid #ddd; }
tbody th { text-align:right; color:#666; padding-right:.5em; } tbody th { text-align:right; color:#666; padding-right:.5em; }
table.vars { margin:5px 0 2px 40px; } table.vars { margin:5px 0 2px 40px; }
@ -49,14 +49,14 @@ $def with (exception_type, exception_value, frames)
table td.code { width:100%;} table td.code { width:100%;}
table td.code div { overflow:hidden; } table td.code div { overflow:hidden; }
table.source th { color:#666; } table.source th { color:#666; }
table.source td { table.source td {
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; } font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
ul.traceback { list-style-type:none; } ul.traceback { list-style-type:none; }
ul.traceback li.frame { margin-bottom:1em; } ul.traceback li.frame { margin-bottom:1em; }
div.context { margin: 10px 0; } div.context { margin: 10px 0; }
div.context ol { div.context ol {
padding-left:30px; margin:0 10px; list-style-position: inside; } padding-left:30px; margin:0 10px; list-style-position: inside; }
div.context ol li { div.context ol li {
font-family:monospace; white-space:pre; color:#666; cursor:pointer; } font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
div.context ol.context-line li { color:black; background-color:#ccc; } div.context ol.context-line li { color:black; background-color:#ccc; }
div.context ol.context-line li span { float: right; } div.context ol.context-line li span { float: right; }
@ -78,7 +78,7 @@ $def with (exception_type, exception_value, frames)
<script type="text/javascript"> <script type="text/javascript">
//<!-- //<!--
function getElementsByClassName(oElm, strTagName, strClassName){ function getElementsByClassName(oElm, strTagName, strClassName){
// Written by Jonathan Snook, http://www.snook.ca/jon; // Written by Jonathan Snook, http://www.snook.ca/jon;
// Add-ons by Robert Nyman, http://www.robertnyman.com // Add-ons by Robert Nyman, http://www.robertnyman.com
var arrElements = (strTagName == "*" && document.all)? document.all : var arrElements = (strTagName == "*" && document.all)? document.all :
oElm.getElementsByTagName(strTagName); oElm.getElementsByTagName(strTagName);
@ -157,7 +157,7 @@ $for frame in frames:
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li> <li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
</ol> </ol>
</div> </div>
$if frame.vars: $if frame.vars:
<div class="commands"> <div class="commands">
<a href='#' onclick="return varToggle(this, '$frame.id')"><span>&#x25b6;</span> Local vars</a> <a href='#' onclick="return varToggle(this, '$frame.id')"><span>&#x25b6;</span> Local vars</a>
@ -183,7 +183,7 @@ $if ctx.output or ctx.headers:
<p class="req" style="padding-bottom: 2em"><code> <p class="req" style="padding-bottom: 2em"><code>
$ctx.output $ctx.output
</code></p> </code></p>
<h2>Request information</h2> <h2>Request information</h2>
<h3>INPUT</h3> <h3>INPUT</h3>
@ -253,8 +253,8 @@ def djangoerror():
return lower_bound, pre_context, context_line, post_context return lower_bound, pre_context, context_line, post_context
except (OSError, IOError): except (OSError, IOError):
return None, [], None, [] return None, [], None, []
exception_type, exception_value, tback = sys.exc_info() exception_type, exception_value, tback = sys.exc_info()
frames = [] frames = []
while tback is not None: while tback is not None:
@ -279,9 +279,9 @@ def djangoerror():
frames.reverse() frames.reverse()
urljoin = urlparse.urljoin urljoin = urlparse.urljoin
def prettify(x): def prettify(x):
try: try:
out = pprint.pformat(x) out = pprint.pformat(x)
except Exception, e: except Exception, e:
out = '[could not display: <' + e.__class__.__name__ + \ out = '[could not display: <' + e.__class__.__name__ + \
': '+str(e)+'>]' ': '+str(e)+'>]'
return out return out
@ -296,10 +296,10 @@ def debugerror():
A replacement for `internalerror` that presents a nice page with lots A replacement for `internalerror` that presents a nice page with lots
of debug information for the programmer. of debug information for the programmer.
(Based on the beautiful 500 page from [Django](http://djangoproject.com/), (Based on the beautiful 500 page from [Django](http://djangoproject.com/),
designed by [Wilson Miner](http://wilsonminer.com/).) designed by [Wilson Miner](http://wilsonminer.com/).)
""" """
web.ctx.headers = [('Content-Type', 'text/html')] web.ctx.headers = [('Content-Type', 'text/html')]
web.ctx.output = djangoerror() web.ctx.output = djangoerror()
@ -307,10 +307,10 @@ if __name__ == "__main__":
urls = ( urls = (
'/', 'index' '/', 'index'
) )
class index: class index:
def GET(self): def GET(self):
thisdoesnotexist thisdoesnotexist
web.internalerror = web.debugerror web.internalerror = web.debugerror
web.run(urls) web.run(urls)

View File

@ -23,7 +23,7 @@ class Form:
o = copy.deepcopy(self) o = copy.deepcopy(self)
if x: o.validates(x) if x: o.validates(x)
return o return o
def render(self): def render(self):
out = '' out = ''
out += self.rendernote(self.note) out += self.rendernote(self.note)
@ -34,7 +34,7 @@ class Form:
out += '<td id="note_%s">%s</td></tr>\n' % (i.id, self.rendernote(i.note)) out += '<td id="note_%s">%s</td></tr>\n' % (i.id, self.rendernote(i.note))
out += "</table>" out += "</table>"
return out return out
def rendernote(self, note): def rendernote(self, note):
if note: return '<strong class="wrong">%s</strong>' % note if note: return '<strong class="wrong">%s</strong>' % note
else: return "" else: return ""
@ -63,12 +63,12 @@ class Form:
def fill(self, source=None, **kw): def fill(self, source=None, **kw):
return self.validates(source, _validate=False, **kw) return self.validates(source, _validate=False, **kw)
def __getitem__(self, i): def __getitem__(self, i):
for x in self.inputs: for x in self.inputs:
if x.name == i: return x if x.name == i: return x
raise KeyError, i raise KeyError, i
def _get_d(self): #@@ should really be form.attr, no? def _get_d(self): #@@ should really be form.attr, no?
return utils.storage([(i.name, i.value) for i in self.inputs]) return utils.storage([(i.name, i.value) for i in self.inputs])
d = property(_get_d) d = property(_get_d)
@ -100,7 +100,7 @@ class Input(object):
for (n, v) in self.attrs.items(): for (n, v) in self.attrs.items():
str += ' %s="%s"' % (n, net.websafe(v)) str += ' %s="%s"' % (n, net.websafe(v))
return str return str
#@@ quoting #@@ quoting
class Textbox(Input): class Textbox(Input):
@ -139,7 +139,7 @@ class Dropdown(Input):
if type(arg) == tuple: if type(arg) == tuple:
value, desc= arg value, desc= arg
else: else:
value, desc = arg, arg value, desc = arg, arg
if self.value == value: select_p = ' selected="selected"' if self.value == value: select_p = ' selected="selected"'
else: select_p = '' else: select_p = ''
@ -196,11 +196,11 @@ class File(Input):
x += self.addatts() x += self.addatts()
x += ' />' x += ' />'
return x return x
class Validator: class Validator:
def __deepcopy__(self, memo): return copy.copy(self) def __deepcopy__(self, memo): return copy.copy(self)
def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals()) def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals())
def valid(self, value): def valid(self, value):
try: return self.test(value) try: return self.test(value)
except: return False except: return False
@ -210,6 +210,6 @@ class regexp(Validator):
def __init__(self, rexp, msg): def __init__(self, rexp, msg):
self.rexp = re.compile(rexp) self.rexp = re.compile(rexp)
self.msg = msg self.msg = msg
def valid(self, value): def valid(self, value):
return bool(self.rexp.match(value)) return bool(self.rexp.match(value))

View File

@ -4,9 +4,9 @@ Network Utilities
""" """
__all__ = [ __all__ = [
"validipaddr", "validipport", "validip", "validaddr", "validipaddr", "validipport", "validip", "validaddr",
"urlquote", "urlquote",
"httpdate", "parsehttpdate", "httpdate", "parsehttpdate",
"htmlquote", "websafe", "htmlquote", "websafe",
] ]
@ -37,7 +37,7 @@ def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
"""returns `(ip_address, port)` from string `ip_addr_port`""" """returns `(ip_address, port)` from string `ip_addr_port`"""
addr = defaultaddr addr = defaultaddr
port = defaultport port = defaultport
ip = ip.split(":", 1) ip = ip.split(":", 1)
if len(ip) == 1: if len(ip) == 1:
if not ip[0]: if not ip[0]:
@ -60,7 +60,7 @@ def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
def validaddr(string_): def validaddr(string_):
""" """
returns either (ip_address, port) or "/path/to/socket" from string_ returns either (ip_address, port) or "/path/to/socket" from string_
>>> validaddr('/path/to/socket') >>> validaddr('/path/to/socket')
'/path/to/socket' '/path/to/socket'
>>> validaddr('8000') >>> validaddr('8000')
@ -82,7 +82,7 @@ def validaddr(string_):
def urlquote(val): def urlquote(val):
""" """
Quotes a string for use in a URL. Quotes a string for use in a URL.
>>> urlquote('://?f=1&j=1') >>> urlquote('://?f=1&j=1')
'%3A//%3Ff%3D1%26j%3D1' '%3A//%3Ff%3D1%26j%3D1'
>>> urlquote(None) >>> urlquote(None)
@ -98,7 +98,7 @@ def urlquote(val):
def httpdate(date_obj): def httpdate(date_obj):
""" """
Formats a datetime object for use in HTTP headers. Formats a datetime object for use in HTTP headers.
>>> import datetime >>> import datetime
>>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1)) >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1))
'Thu, 01 Jan 1970 01:01:01 GMT' 'Thu, 01 Jan 1970 01:01:01 GMT'
@ -121,7 +121,7 @@ def parsehttpdate(string_):
def htmlquote(text): def htmlquote(text):
""" """
Encodes `text` for raw use in HTML. Encodes `text` for raw use in HTML.
>>> htmlquote("<'&\\">") >>> htmlquote("<'&\\">")
'&lt;&#39;&amp;&quot;&gt;' '&lt;&#39;&amp;&quot;&gt;'
""" """
@ -135,7 +135,7 @@ def htmlquote(text):
def websafe(val): def websafe(val):
""" """
Converts `val` so that it's safe for use in UTF-8 HTML. Converts `val` so that it's safe for use in UTF-8 HTML.
>>> websafe("<'&\\">") >>> websafe("<'&\\">")
'&lt;&#39;&amp;&quot;&gt;' '&lt;&#39;&amp;&quot;&gt;'
>>> websafe(None) >>> websafe(None)

View File

@ -20,9 +20,9 @@ def handle(mapping, fvars=None):
substitutions. `handle` will import modules as necessary. substitutions. `handle` will import modules as necessary.
""" """
for url, ofno in utils.group(mapping, 2): for url, ofno in utils.group(mapping, 2):
if isinstance(ofno, tuple): if isinstance(ofno, tuple):
ofn, fna = ofno[0], list(ofno[1:]) ofn, fna = ofno[0], list(ofno[1:])
else: else:
ofn, fna = ofno, [] ofn, fna = ofno, []
fn, result = utils.re_subm('^' + url + '$', ofn, web.ctx.path) fn, result = utils.re_subm('^' + url + '$', ofn, web.ctx.path)
if result: # it's a match if result: # it's a match
@ -30,10 +30,10 @@ def handle(mapping, fvars=None):
url = fn.split(' ', 1)[1] url = fn.split(' ', 1)[1]
if web.ctx.method == "GET": if web.ctx.method == "GET":
x = web.ctx.env.get('QUERY_STRING', '') x = web.ctx.env.get('QUERY_STRING', '')
if x: if x:
url += '?' + x url += '?' + x
return http.redirect(url) return http.redirect(url)
elif '.' in fn: elif '.' in fn:
x = fn.split('.') x = fn.split('.')
mod, cls = '.'.join(x[:-1]), x[-1] mod, cls = '.'.join(x[:-1]), x[-1]
mod = __import__(mod, globals(), locals(), [""]) mod = __import__(mod, globals(), locals(), [""])
@ -41,18 +41,18 @@ def handle(mapping, fvars=None):
else: else:
cls = fn cls = fn
mod = fvars mod = fvars
if isinstance(mod, types.ModuleType): if isinstance(mod, types.ModuleType):
mod = vars(mod) mod = vars(mod)
try: try:
cls = mod[cls] cls = mod[cls]
except KeyError: except KeyError:
return web.notfound() return web.notfound()
meth = web.ctx.method meth = web.ctx.method
if meth == "HEAD": if meth == "HEAD":
if not hasattr(cls, meth): if not hasattr(cls, meth):
meth = "GET" meth = "GET"
if not hasattr(cls, meth): if not hasattr(cls, meth):
return nomethod(cls) return nomethod(cls)
tocall = getattr(cls(), meth) tocall = getattr(cls(), meth)
args = list(result.groups()) args = list(result.groups())
@ -86,9 +86,9 @@ def autodelegate(prefix=''):
def GET_password(self): pass def GET_password(self): pass
def GET_privacy(self): pass def GET_privacy(self): pass
`GET_password` would get called for `/prefs/password` while `GET_privacy` for `GET_password` would get called for `/prefs/password` while `GET_privacy` for
`GET_privacy` gets called for `/prefs/privacy`. `GET_privacy` gets called for `/prefs/privacy`.
If a user visits `/prefs/password/change` then `GET_password(self, '/change')` If a user visits `/prefs/password/change` then `GET_password(self, '/change')`
is called. is called.
""" """
@ -100,7 +100,7 @@ def autodelegate(prefix=''):
else: else:
func = prefix + arg func = prefix + arg
args = [] args = []
if hasattr(self, func): if hasattr(self, func):
try: try:
return getattr(self, func)(*args) return getattr(self, func)(*args)
@ -118,11 +118,11 @@ def webpyfunc(inp, fvars, autoreload=False):
"""find name of the module name from fvars.""" """find name of the module name from fvars."""
file, name = fvars['__file__'], fvars['__name__'] file, name = fvars['__file__'], fvars['__name__']
if name == '__main__': if name == '__main__':
# Since the __main__ module can't be reloaded, the module has # Since the __main__ module can't be reloaded, the module has
# to be imported using its file name. # to be imported using its file name.
name = os.path.splitext(os.path.basename(file))[0] name = os.path.splitext(os.path.basename(file))[0]
return name return name
mod = __import__(modname(), None, None, [""]) mod = __import__(modname(), None, None, [""])
#@@probably should replace this with some inspect magic #@@probably should replace this with some inspect magic
name = utils.dictfind(fvars, inp) name = utils.dictfind(fvars, inp)

View File

@ -56,20 +56,20 @@ class Parser:
self.p = 0 self.p = 0
self._lock = [False] self._lock = [False]
self.name = name self.name = name
def lock(self): def lock(self):
self._lock[-1] = True self._lock[-1] = True
def curline(self): def curline(self):
return self.t[:self.p].count('\n')+1 return self.t[:self.p].count('\n')+1
def csome(self): def csome(self):
return repr(self.t[self.p:self.p+5]+'...') return repr(self.t[self.p:self.p+5]+'...')
def Error(self, x, y=None): def Error(self, x, y=None):
if y is None: y = self.csome() if y is None: y = self.csome()
raise ParseError, "%s: expected %s, got %s (line %s)" % (self.name, x, y, self.curline()) raise ParseError, "%s: expected %s, got %s (line %s)" % (self.name, x, y, self.curline())
def q(self, f): def q(self, f):
def internal(*a, **kw): def internal(*a, **kw):
checkp = self.p checkp = self.p
@ -85,29 +85,29 @@ class Parser:
self._lock.pop() self._lock.pop()
return q or True return q or True
return internal return internal
def tokr(self, t): def tokr(self, t):
text = self.c(len(t)) text = self.c(len(t))
if text != t: if text != t:
self.Error(repr(t), repr(text)) self.Error(repr(t), repr(text))
return t return t
def ltokr(self, *l): def ltokr(self, *l):
for x in l: for x in l:
o = self.tokq(x) o = self.tokq(x)
if o: return o if o: return o
self.Error('one of '+repr(l)) self.Error('one of '+repr(l))
def rer(self, r): def rer(self, r):
x = re.match(r, self.t[self.p:]) #@@re_compile x = re.match(r, self.t[self.p:]) #@@re_compile
if not x: if not x:
self.Error('r'+repr(r)) self.Error('r'+repr(r))
return self.tokr(x.group()) return self.tokr(x.group())
def endr(self): def endr(self):
if self.p != len(self.t): if self.p != len(self.t):
self.Error('EOF') self.Error('EOF')
def c(self, n=1): def c(self, n=1):
out = self.t[self.p:self.p+n] out = self.t[self.p:self.p+n]
if out == '' and n != 0: if out == '' and n != 0:
@ -117,7 +117,7 @@ class Parser:
def lookbehind(self, t): def lookbehind(self, t):
return self.t[self.p-len(t):self.p] == t return self.t[self.p-len(t):self.p] == t
def __getattr__(self, a): def __getattr__(self, a):
if a.endswith('q'): if a.endswith('q'):
return self.q(getattr(self, a[:-1]+'r')) return self.q(getattr(self, a[:-1]+'r'))
@ -128,25 +128,25 @@ class TemplateParser(Parser):
Parser.__init__(self, *a, **kw) Parser.__init__(self, *a, **kw)
self.curws = '' self.curws = ''
self.curind = '' self.curind = ''
def o(self, *a): def o(self, *a):
return a+('lineno', self.curline()) return a+('lineno', self.curline())
def go(self): def go(self):
# maybe try to do some traceback parsing/hacking # maybe try to do some traceback parsing/hacking
return self.gor() return self.gor()
def gor(self): def gor(self):
header = self.defwithq() header = self.defwithq()
results = self.lines(start=True) results = self.lines(start=True)
self.endr() self.endr()
return header, results return header, results
def ws(self): def ws(self):
n = 0 n = 0
while self.tokq(" "): n += 1 while self.tokq(" "): n += 1
return " " * n return " " * n
def defwithr(self): def defwithr(self):
self.tokr('$def with ') self.tokr('$def with ')
self.lock() self.lock()
@ -158,12 +158,12 @@ class TemplateParser(Parser):
if self.tokq('='): if self.tokq('='):
v = self.exprr() v = self.exprr()
kw.append((x, v)) kw.append((x, v))
else: else:
args.append(x) args.append(x)
x = self.tokq(', ') and self.req(r_var) x = self.tokq(', ') and self.req(r_var)
self.tokr(')\n') self.tokr(')\n')
return self.o('defwith', 'null', None, 'args', args, 'kwargs', kw) return self.o('defwith', 'null', None, 'args', args, 'kwargs', kw)
def literalr(self): def literalr(self):
o = ( o = (
self.req('"[^"]*"') or #@@ no support for escapes self.req('"[^"]*"') or #@@ no support for escapes
@ -177,7 +177,7 @@ class TemplateParser(Parser):
if o is False: self.Error('literal') if o is False: self.Error('literal')
return self.o('literal', 'thing', o) return self.o('literal', 'thing', o)
def listr(self): def listr(self):
self.tokr('[') self.tokr('[')
self.lock() self.lock()
@ -189,7 +189,7 @@ class TemplateParser(Parser):
if not self.tokq(', '): break if not self.tokq(', '): break
self.tokr(']') self.tokr(']')
return self.o('list', 'thing', x) return self.o('list', 'thing', x)
def dictr(self): def dictr(self):
self.tokr('{') self.tokr('{')
self.lock() self.lock()
@ -210,11 +210,11 @@ class TemplateParser(Parser):
o = self.exprr() # todo: allow list o = self.exprr() # todo: allow list
self.tokr(')') self.tokr(')')
return self.o('paren', 'thing', o) return self.o('paren', 'thing', o)
def atomr(self): def atomr(self):
"""returns var, literal, paren, dict, or list""" """returns var, literal, paren, dict, or list"""
o = ( o = (
self.varq() or self.varq() or
self.parenq() or self.parenq() or
self.dictq() or self.dictq() or
self.listq() or self.listq() or
@ -222,9 +222,9 @@ class TemplateParser(Parser):
) )
if o is False: self.Error('atom') if o is False: self.Error('atom')
return o return o
def primaryr(self): def primaryr(self):
"""returns getattr, call, or getitem""" """returns getattr, call, or getitem"""
n = self.atomr() n = self.atomr()
while 1: while 1:
if self.tokq('.'): if self.tokq('.'):
@ -237,7 +237,7 @@ class TemplateParser(Parser):
elif self.tokq('('): elif self.tokq('('):
args = [] args = []
kw = [] kw = []
while 1: while 1:
# need to see if we're doing a keyword argument # need to see if we're doing a keyword argument
checkp = self.p checkp = self.p
@ -252,7 +252,7 @@ class TemplateParser(Parser):
args.append(x) args.append(x)
else: else:
break break
if not self.tokq(', '): break if not self.tokq(', '): break
self.tokr(')') self.tokr(')')
n = self.o('call', 'thing', n, 'args', args, 'kwargs', kw) n = self.o('call', 'thing', n, 'args', args, 'kwargs', kw)
@ -262,9 +262,9 @@ class TemplateParser(Parser):
n = self.o('getitem', 'thing', n, 'item', v) n = self.o('getitem', 'thing', n, 'item', v)
else: else:
break break
return n return n
def exprr(self): def exprr(self):
negate = self.tokq('not ') negate = self.tokq('not ')
x = self.primaryr() x = self.primaryr()
@ -273,12 +273,12 @@ class TemplateParser(Parser):
self.tokr(' ') self.tokr(' ')
y = self.exprr() y = self.exprr()
x = self.o('test', 'x', x, 'op', operator, 'y', y) x = self.o('test', 'x', x, 'op', operator, 'y', y)
return self.o('expr', 'thing', x, 'negate', negate) return self.o('expr', 'thing', x, 'negate', negate)
def varr(self): def varr(self):
return self.o('var', 'name', self.rer(r_var)) return self.o('var', 'name', self.rer(r_var))
def liner(self): def liner(self):
out = [] out = []
o = self.curws o = self.curws
@ -293,7 +293,7 @@ class TemplateParser(Parser):
o = o[:-1] + c o = o[:-1] + c
else: else:
filter = not bool(self.tokq(':')) filter = not bool(self.tokq(':'))
if self.tokq('{'): if self.tokq('{'):
out.append(o) out.append(o)
out.append(self.o('itpl', 'name', self.exprr(), 'filter', filter)) out.append(self.o('itpl', 'name', self.exprr(), 'filter', filter))
@ -301,7 +301,7 @@ class TemplateParser(Parser):
o = '' o = ''
else: else:
g = self.primaryq() g = self.primaryq()
if g: if g:
out.append(o) out.append(o)
out.append(self.o('itpl', 'name', g, 'filter', filter)) out.append(self.o('itpl', 'name', g, 'filter', filter))
o = '' o = ''
@ -316,7 +316,7 @@ class TemplateParser(Parser):
o = o[:-1] o = o[:-1]
out.append(o) out.append(o)
return self.o('line', 'thing', out) return self.o('line', 'thing', out)
def varsetr(self): def varsetr(self):
self.tokr('$var ') self.tokr('$var ')
self.lock() self.lock()
@ -331,21 +331,21 @@ class TemplateParser(Parser):
expr = self.exprr() expr = self.exprr()
self.tokr(":") self.tokr(":")
ifc = self.lines() ifc = self.lines()
elifs = [] elifs = []
while self.tokq(self.curws + self.curind + '$elif '): while self.tokq(self.curws + self.curind + '$elif '):
v = self.exprr() v = self.exprr()
self.tokr(':') self.tokr(':')
c = self.lines() c = self.lines()
elifs.append(self.o('elif', 'clause', v, 'body', c)) elifs.append(self.o('elif', 'clause', v, 'body', c))
if self.tokq(self.curws + self.curind + "$else:"): if self.tokq(self.curws + self.curind + "$else:"):
elsec = self.lines() elsec = self.lines()
else: else:
elsec = None elsec = None
return self.o('if', 'clause', expr, 'then', ifc, 'elif', elifs, 'else', elsec) return self.o('if', 'clause', expr, 'then', ifc, 'elif', elifs, 'else', elsec)
def forr(self): def forr(self):
self.tokr("$for ") self.tokr("$for ")
self.lock() self.lock()
@ -359,44 +359,44 @@ class TemplateParser(Parser):
elsec = self.lines() elsec = self.lines()
else: else:
elsec = None elsec = None
return self.o('for', 'name', v, 'body', l, 'in', g, 'else', elsec) return self.o('for', 'name', v, 'body', l, 'in', g, 'else', elsec)
def whiler(self): def whiler(self):
self.tokr('$while ') self.tokr('$while ')
self.lock() self.lock()
v = self.exprr() v = self.exprr()
self.tokr(":") self.tokr(":")
l = self.lines() l = self.lines()
if self.tokq(self.curws + self.curind + '$else:'): if self.tokq(self.curws + self.curind + '$else:'):
elsec = self.lines() elsec = self.lines()
else: else:
elsec = None elsec = None
return self.o('while', 'clause', v, 'body', l, 'null', None, 'else', elsec) return self.o('while', 'clause', v, 'body', l, 'null', None, 'else', elsec)
def assignr(self): def assignr(self):
self.tokr('$ ') self.tokr('$ ')
assign = self.rer(r_var) # NOTE: setable assign = self.rer(r_var) # NOTE: setable
self.tokr(' = ') self.tokr(' = ')
expr = self.exprr() expr = self.exprr()
self.tokr('\n') self.tokr('\n')
return self.o('assign', 'name', assign, 'expr', expr) return self.o('assign', 'name', assign, 'expr', expr)
def commentr(self): def commentr(self):
self.tokr('$#') self.tokr('$#')
self.lock() self.lock()
while self.c() != '\n': pass while self.c() != '\n': pass
return self.o('comment') return self.o('comment')
def setabler(self): def setabler(self):
out = [self.varr()] #@@ not quite right out = [self.varr()] #@@ not quite right
while self.tokq(', '): while self.tokq(', '):
out.append(self.varr()) out.append(self.varr())
return out return out
def lines(self, start=False): def lines(self, start=False):
""" """
This function gets called from two places: This function gets called from two places:
@ -417,13 +417,13 @@ class TemplateParser(Parser):
oldws = self.curws oldws = self.curws
t = self.tokq(oldws + self.curind) t = self.tokq(oldws + self.curind)
if not t: break if not t: break
self.curws += self.ws() self.curws += self.ws()
x = t and ( x = t and (
self.varsetq() or self.varsetq() or
self.ifq() or self.ifq() or
self.forq() or self.forq() or
self.whileq() or self.whileq() or
self.assignq() or self.assignq() or
self.commentq() or self.commentq() or
self.lineq()) self.lineq())
@ -437,7 +437,7 @@ class TemplateParser(Parser):
if not start: self.curind = oldind if not start: self.curind = oldind
return o return o
class Stowage(storage): class Stowage(storage):
def __str__(self): return self.get('_str') def __str__(self): return self.get('_str')
#@@ edits in place #@@ edits in place
@ -453,7 +453,7 @@ class Stowage(storage):
return self return self
else: else:
raise TypeError, 'cannot add' raise TypeError, 'cannot add'
class WTF(AssertionError): pass class WTF(AssertionError): pass
class SecurityError(Exception): class SecurityError(Exception):
"""The template seems to be trying to do something naughty.""" """The template seems to be trying to do something naughty."""
@ -469,7 +469,7 @@ class Template:
'.html' : 'text/html; charset=utf-8', '.html' : 'text/html; charset=utf-8',
'.txt' : 'text/plain', '.txt' : 'text/plain',
} }
def __init__(self, text, filter=None, filename=""): def __init__(self, text, filter=None, filename=""):
self.filter = filter self.filter = filter
self.filename = filename self.filename = filename
@ -482,7 +482,7 @@ class Template:
self.h_defwith(header) self.h_defwith(header)
else: else:
self.args, self.kwargs = (), {} self.args, self.kwargs = (), {}
def __call__(self, *a, **kw): def __call__(self, *a, **kw):
d = self.globals.copy() d = self.globals.copy()
d.update(self._parseargs(a, kw)) d.update(self._parseargs(a, kw))
@ -494,14 +494,14 @@ class Template:
content_type = self.find_content_type() content_type = self.find_content_type()
if content_type: if content_type:
web.header('Content-Type', content_type, unique=True) web.header('Content-Type', content_type, unique=True)
return f.go() return f.go()
def find_content_type(self): def find_content_type(self):
for ext, content_type in self.content_types.iteritems(): for ext, content_type in self.content_types.iteritems():
if self.filename.endswith(ext): if self.filename.endswith(ext):
return content_type return content_type
def _parseargs(self, inargs, inkwargs): def _parseargs(self, inargs, inkwargs):
# difference from Python: # difference from Python:
# no error on setting a keyword arg twice # no error on setting a keyword arg twice
@ -526,10 +526,10 @@ class Template:
if v is Required: if v is Required:
unset.append(k) unset.append(k)
if unset: if unset:
raise TypeError, 'values for %s are required' % unset raise TypeError, 'values for %s are required' % unset
return d return d
def h_defwith(self, header): def h_defwith(self, header):
assert header[WHAT] == 'defwith' assert header[WHAT] == 'defwith'
f = Fill(self.tree, d={}) f = Fill(self.tree, d={})
@ -546,18 +546,18 @@ class Handle:
def __init__(self, parsetree, **kw): def __init__(self, parsetree, **kw):
self._funccache = {} self._funccache = {}
self.parsetree = parsetree self.parsetree = parsetree
for (k, v) in kw.iteritems(): setattr(self, k, v) for (k, v) in kw.iteritems(): setattr(self, k, v)
def h(self, item): def h(self, item):
return getattr(self, 'h_' + item[WHAT])(item) return getattr(self, 'h_' + item[WHAT])(item)
class Fill(Handle): class Fill(Handle):
builtins = global_globals builtins = global_globals
def filter(self, text): def filter(self, text):
if text is None: return '' if text is None: return ''
else: return utf8(text) else: return utf8(text)
# often replaced with stuff like net.websafe # often replaced with stuff like net.websafe
def h_literal(self, i): def h_literal(self, i):
item = i[THING] item = i[THING]
if isinstance(item, (unicode, str)) and item[0] in ['"', "'"]: if isinstance(item, (unicode, str)) and item[0] in ['"', "'"]:
@ -565,27 +565,27 @@ class Fill(Handle):
elif isinstance(item, (float, int)): elif isinstance(item, (float, int)):
pass pass
return item return item
def h_list(self, i): def h_list(self, i):
x = i[THING] x = i[THING]
out = [] out = []
for item in x: for item in x:
out.append(self.h(item)) out.append(self.h(item))
return out return out
def h_dict(self, i): def h_dict(self, i):
x = i[THING] x = i[THING]
out = {} out = {}
for k, v in x.iteritems(): for k, v in x.iteritems():
out[self.h(k)] = self.h(v) out[self.h(k)] = self.h(v)
return out return out
def h_paren(self, i): def h_paren(self, i):
item = i[THING] item = i[THING]
if isinstance(item, list): if isinstance(item, list):
raise NotImplementedError, 'tuples' raise NotImplementedError, 'tuples'
return self.h(item) return self.h(item)
def h_getattr(self, i): def h_getattr(self, i):
thing, attr = i[THING], i[ATTR] thing, attr = i[THING], i[ATTR]
thing = self.h(thing) thing = self.h(thing)
@ -603,25 +603,25 @@ class Fill(Handle):
return lambda s: s.join(thing) return lambda s: s.join(thing)
else: else:
raise raise
def h_call(self, i): def h_call(self, i):
call = self.h(i[THING]) call = self.h(i[THING])
args = [self.h(x) for x in i[ARGS]] args = [self.h(x) for x in i[ARGS]]
kw = dict([(x, self.h(y)) for (x, y) in i[KWARGS]]) kw = dict([(x, self.h(y)) for (x, y) in i[KWARGS]])
return call(*args, **kw) return call(*args, **kw)
def h_getitem(self, i): def h_getitem(self, i):
thing, item = i[THING], i[ITEM] thing, item = i[THING], i[ITEM]
thing = self.h(thing) thing = self.h(thing)
item = self.h(item) item = self.h(item)
return thing[item] return thing[item]
def h_expr(self, i): def h_expr(self, i):
item = self.h(i[THING]) item = self.h(i[THING])
if i[NEGATE]: if i[NEGATE]:
item = not item item = not item
return item return item
def h_test(self, item): def h_test(self, item):
ox, op, oy = item[X], item[OP], item[Y] ox, op, oy = item[X], item[OP], item[Y]
# for short-circuiting to work, we can't eval these here # for short-circuiting to work, we can't eval these here
@ -662,7 +662,7 @@ class Fill(Handle):
return e(ox) % e(oy) return e(ox) % e(oy)
else: else:
raise WTF, 'op ' + op raise WTF, 'op ' + op
def h_var(self, i): def h_var(self, i):
v = i[NAME] v = i[NAME]
if v in self.d: if v in self.d:
@ -673,7 +673,7 @@ class Fill(Handle):
return self.output return self.output
else: else:
raise NameError, 'could not find %s (line %s)' % (repr(i[NAME]), i[LINENO]) raise NameError, 'could not find %s (line %s)' % (repr(i[NAME]), i[LINENO])
def h_line(self, i): def h_line(self, i):
out = [] out = []
for x in i[THING]: for x in i[THING]:
@ -684,13 +684,13 @@ class Fill(Handle):
o = self.h(x[NAME]) o = self.h(x[NAME])
if x[FILTER]: if x[FILTER]:
o = self.filter(o) o = self.filter(o)
else: else:
o = (o is not None and utf8(o)) or "" o = (o is not None and utf8(o)) or ""
out.append(o) out.append(o)
else: else:
raise WTF, x raise WTF, x
return ''.join(out) return ''.join(out)
def h_varset(self, i): def h_varset(self, i):
self.output[i[NAME]] = ''.join(self.h_lines(i[BODY])) self.output[i[NAME]] = ''.join(self.h_lines(i[BODY]))
return '' return ''
@ -708,7 +708,7 @@ class Fill(Handle):
else: else:
do = i[ELSE] do = i[ELSE]
return ''.join(self.h_lines(do)) return ''.join(self.h_lines(do))
def h_for(self, i): def h_for(self, i):
out = [] out = []
assert i[IN][WHAT] == 'expr' assert i[IN][WHAT] == 'expr'
@ -724,13 +724,13 @@ class Fill(Handle):
for x, y in zip(forvar, nv): for x, y in zip(forvar, nv):
assert x[WHAT] == 'var' assert x[WHAT] == 'var'
self.d[x[NAME]] = y self.d[x[NAME]] = y
out.extend(self.h_lines(i[BODY])) out.extend(self.h_lines(i[BODY]))
else: else:
if i[ELSE]: if i[ELSE]:
out.extend(self.h_lines(i[ELSE])) out.extend(self.h_lines(i[ELSE]))
return ''.join(out) return ''.join(out)
def h_while(self, i): def h_while(self, i):
out = [] out = []
expr = self.h(i[CLAUSE]) expr = self.h(i[CLAUSE])
@ -750,11 +750,11 @@ class Fill(Handle):
return '' return ''
def h_comment(self, i): pass def h_comment(self, i): pass
def h_lines(self, lines): def h_lines(self, lines):
if lines is None: return [] if lines is None: return []
return map(self.h, lines) return map(self.h, lines)
def go(self): def go(self):
self.output = Stowage() self.output = Stowage()
self.output._str = ''.join(map(self.h, self.parsetree)) self.output._str = ''.join(map(self.h, self.parsetree))
@ -769,11 +769,11 @@ class render:
self.cache = {} self.cache = {}
else: else:
self.cache = False self.cache = False
def _do(self, name, filter=None): def _do(self, name, filter=None):
if self.cache is False or name not in self.cache: if self.cache is False or name not in self.cache:
tmplpath = os.path.join(self.loc, name) tmplpath = os.path.join(self.loc, name)
p = [f for f in glob.glob(tmplpath + '.*') if not f.endswith('~')] # skip backup files p = [f for f in glob.glob(tmplpath + '.*') if not f.endswith('~')] # skip backup files
if not p and os.path.isdir(tmplpath): if not p and os.path.isdir(tmplpath):
return render(tmplpath, cache=self.cache) return render(tmplpath, cache=self.cache)
@ -783,7 +783,7 @@ class render:
p = p[0] p = p[0]
c = Template(open(p).read(), filename=p) c = Template(open(p).read(), filename=p)
if self.cache is not False: self.cache[name] = (p, c) if self.cache is not False: self.cache[name] = (p, c)
if self.cache is not False: p, c = self.cache[name] if self.cache is not False: p, c = self.cache[name]
if p.endswith('.html') or p.endswith('.xml'): if p.endswith('.html') or p.endswith('.xml'):
@ -806,7 +806,7 @@ def test():
sys.stderr.flush() sys.stderr.flush()
else: else:
assert a == b, "\nexpected: %s\ngot: %s" % (repr(b), repr(a)) assert a == b, "\nexpected: %s\ngot: %s" % (repr(b), repr(a))
from utils import storage, group from utils import storage, group
class t: class t:
@ -828,7 +828,7 @@ def test():
else: else:
print >> sys.stderr, 'FAIL:', repr(self.source), 'expected', repr(other), ', got', repr(self.value) print >> sys.stderr, 'FAIL:', repr(self.source), 'expected', repr(other), ', got', repr(self.value)
sys.stderr.flush() sys.stderr.flush()
t('1')() == '1\n' t('1')() == '1\n'
t('$def with ()\n1')() == '1\n' t('$def with ()\n1')() == '1\n'
t('$def with (a)\n$a')(1) == '1\n' t('$def with (a)\n$a')(1) == '1\n'
@ -872,7 +872,7 @@ def test():
assertEqual(str(j), '') assertEqual(str(j), '')
assertEqual(j.foo, 'bar\n') assertEqual(j.foo, 'bar\n')
if verbose: sys.stderr.write('\n') if verbose: sys.stderr.write('\n')
if __name__ == "__main__": if __name__ == "__main__":
test() test()

View File

@ -4,8 +4,8 @@ General Utilities
""" """
__all__ = [ __all__ = [
"Storage", "storage", "storify", "Storage", "storage", "storify",
"iters", "iters",
"rstrips", "lstrips", "strips", "utf8", "rstrips", "lstrips", "strips", "utf8",
"TimeoutError", "timelimit", "TimeoutError", "timelimit",
"Memoize", "memoize", "Memoize", "memoize",
@ -31,7 +31,7 @@ class Storage(dict):
""" """
A Storage object is like a dictionary except `obj.foo` can be used A Storage object is like a dictionary except `obj.foo` can be used
in addition to `obj['foo']`. in addition to `obj['foo']`.
>>> o = storage(a=1) >>> o = storage(a=1)
>>> o.a >>> o.a
1 1
@ -45,15 +45,15 @@ class Storage(dict):
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: 'a' AttributeError: 'a'
""" """
def __getattr__(self, key): def __getattr__(self, key):
try: try:
return self[key] return self[key]
except KeyError, k: except KeyError, k:
raise AttributeError, k raise AttributeError, k
def __setattr__(self, key, value): def __setattr__(self, key, value):
self[key] = value self[key] = value
def __delattr__(self, key): def __delattr__(self, key):
@ -62,7 +62,7 @@ class Storage(dict):
except KeyError, k: except KeyError, k:
raise AttributeError, k raise AttributeError, k
def __repr__(self): def __repr__(self):
return '<Storage ' + dict.__repr__(self) + '>' return '<Storage ' + dict.__repr__(self) + '>'
storage = Storage storage = Storage
@ -70,16 +70,16 @@ storage = Storage
def storify(mapping, *requireds, **defaults): def storify(mapping, *requireds, **defaults):
""" """
Creates a `storage` object from dictionary `mapping`, raising `KeyError` if Creates a `storage` object from dictionary `mapping`, raising `KeyError` if
d doesn't have all of the keys in `requireds` and using the default d doesn't have all of the keys in `requireds` and using the default
values for keys found in `defaults`. values for keys found in `defaults`.
For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of
`storage({'a':1, 'b':2, 'c':3})`. `storage({'a':1, 'b':2, 'c':3})`.
If a `storify` value is a list (e.g. multiple values in a form submission), 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 `storify` returns the last element of the list, unless the key appears in
`defaults` as a list. Thus: `defaults` as a list. Thus:
>>> storify({'a':[1, 2]}).a >>> storify({'a':[1, 2]}).a
2 2
>>> storify({'a':[1, 2]}, a=[]).a >>> storify({'a':[1, 2]}, a=[]).a
@ -88,24 +88,24 @@ def storify(mapping, *requireds, **defaults):
[1] [1]
>>> storify({}, a=[]).a >>> storify({}, a=[]).a
[] []
Similarly, if the value has a `value` attribute, `storify will return _its_ Similarly, if the value has a `value` attribute, `storify will return _its_
value, unless the key appears in `defaults` as a dictionary. value, unless the key appears in `defaults` as a dictionary.
>>> storify({'a':storage(value=1)}).a >>> storify({'a':storage(value=1)}).a
1 1
>>> storify({'a':storage(value=1)}, a={}).a >>> storify({'a':storage(value=1)}, a={}).a
<Storage {'value': 1}> <Storage {'value': 1}>
>>> storify({}, a={}).a >>> storify({}, a={}).a
{} {}
""" """
def getvalue(x): def getvalue(x):
if hasattr(x, 'value'): if hasattr(x, 'value'):
return x.value return x.value
else: else:
return x return x
stor = Storage() stor = Storage()
for key in requireds + tuple(mapping.keys()): for key in requireds + tuple(mapping.keys()):
value = mapping[key] value = mapping[key]
@ -122,22 +122,22 @@ def storify(mapping, *requireds, **defaults):
for (key, value) in defaults.iteritems(): for (key, value) in defaults.iteritems():
result = value result = value
if hasattr(stor, key): if hasattr(stor, key):
result = stor[key] result = stor[key]
if value == () and not isinstance(result, tuple): if value == () and not isinstance(result, tuple):
result = (result,) result = (result,)
setattr(stor, key, result) setattr(stor, key, result)
return stor return stor
iters = [list, tuple] iters = [list, tuple]
import __builtin__ import __builtin__
if hasattr(__builtin__, 'set'): if hasattr(__builtin__, 'set'):
iters.append(set) iters.append(set)
try: try:
from sets import Set from sets import Set
iters.append(Set) iters.append(Set)
except ImportError: except ImportError:
pass pass
class _hack(tuple): pass class _hack(tuple): pass
@ -148,13 +148,13 @@ of lists, tuples, sets, and Sets are available in this version of Python.
""" """
def _strips(direction, text, remove): def _strips(direction, text, remove):
if direction == 'l': if direction == 'l':
if text.startswith(remove): if text.startswith(remove):
return text[len(remove):] return text[len(remove):]
elif direction == 'r': elif direction == 'r':
if text.endswith(remove): if text.endswith(remove):
return text[:-len(remove)] return text[:-len(remove)]
else: else:
raise ValueError, "Direction needs to be r or l." raise ValueError, "Direction needs to be r or l."
return text return text
@ -164,17 +164,17 @@ def rstrips(text, remove):
>>> rstrips("foobar", "bar") >>> rstrips("foobar", "bar")
'foo' 'foo'
""" """
return _strips('r', text, remove) return _strips('r', text, remove)
def lstrips(text, remove): def lstrips(text, remove):
""" """
removes the string `remove` from the left of `text` removes the string `remove` from the left of `text`
>>> lstrips("foobar", "foo") >>> lstrips("foobar", "foo")
'bar' 'bar'
""" """
return _strips('l', text, remove) return _strips('l', text, remove)
@ -183,7 +183,7 @@ def strips(text, remove):
>>> strips("foobarfoo", "foo") >>> strips("foobarfoo", "foo")
'bar' 'bar'
""" """
return rstrips(lstrips(text, remove), remove) return rstrips(lstrips(text, remove), remove)
@ -201,12 +201,12 @@ def timelimit(timeout):
""" """
A decorator to limit a function to `timeout` seconds, raising `TimeoutError` A decorator to limit a function to `timeout` seconds, raising `TimeoutError`
if it takes longer. if it takes longer.
>>> import time >>> import time
>>> def meaningoflife(): >>> def meaningoflife():
... time.sleep(.2) ... time.sleep(.2)
... return 42 ... return 42
>>> >>>
>>> timelimit(.1)(meaningoflife)() >>> timelimit(.1)(meaningoflife)()
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -214,7 +214,7 @@ def timelimit(timeout):
>>> timelimit(1)(meaningoflife)() >>> timelimit(1)(meaningoflife)()
42 42
_Caveat:_ The function isn't stopped after `timeout` seconds but continues _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.) executing in a separate thread. (There seems to be no way to kill a thread.)
inspired by <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473878> inspired by <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473878>
@ -249,7 +249,7 @@ def timelimit(timeout):
class Memoize: class Memoize:
""" """
'Memoizes' a function, caching its return values for each input. 'Memoizes' a function, caching its return values for each input.
>>> import time >>> import time
>>> def meaningoflife(): >>> def meaningoflife():
... time.sleep(.2) ... time.sleep(.2)
@ -265,14 +265,14 @@ class Memoize:
42 42
>>> timelimit(.1)(fastlife)() >>> timelimit(.1)(fastlife)()
42 42
""" """
def __init__(self, func): def __init__(self, func):
self.func = func self.func = func
self.cache = {} self.cache = {}
def __call__(self, *args, **keywords): def __call__(self, *args, **keywords):
key = (args, tuple(keywords.items())) key = (args, tuple(keywords.items()))
if key not in self.cache: if key not in self.cache:
self.cache[key] = self.func(*args, **keywords) self.cache[key] = self.func(*args, **keywords)
return self.cache[key] return self.cache[key]
@ -284,16 +284,16 @@ A memoized version of re.compile.
""" """
class _re_subm_proxy: class _re_subm_proxy:
def __init__(self): def __init__(self):
self.match = None self.match = None
def __call__(self, match): def __call__(self, match):
self.match = match self.match = match
return '' return ''
def re_subm(pat, repl, string): def re_subm(pat, repl, string):
""" """
Like re.sub, but returns the replacement _and_ the match object. Like re.sub, but returns the replacement _and_ the match object.
>>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball') >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball')
>>> t >>> t
'foooooolish' 'foooooolish'
@ -305,25 +305,25 @@ def re_subm(pat, repl, string):
compiled_pat.sub(proxy.__call__, string) compiled_pat.sub(proxy.__call__, string)
return compiled_pat.sub(repl, string), proxy.match return compiled_pat.sub(repl, string), proxy.match
def group(seq, size): def group(seq, size):
""" """
Returns an iterator over a series of lists of length size from iterable. Returns an iterator over a series of lists of length size from iterable.
>>> list(group([1,2,3,4], 2)) >>> list(group([1,2,3,4], 2))
[[1, 2], [3, 4]] [[1, 2], [3, 4]]
""" """
if not hasattr(seq, 'next'): if not hasattr(seq, 'next'):
seq = iter(seq) seq = iter(seq)
while True: while True:
yield [seq.next() for i in xrange(size)] yield [seq.next() for i in xrange(size)]
class IterBetter: class IterBetter:
""" """
Returns an object that can be used as an iterator Returns an object that can be used as an iterator
but can also be used via __getitem__ (although it but can also be used via __getitem__ (although it
cannot go backwards -- that is, you cannot request cannot go backwards -- that is, you cannot request
`iterbetter[0]` after requesting `iterbetter[1]`). `iterbetter[0]` after requesting `iterbetter[1]`).
>>> import itertools >>> import itertools
>>> c = iterbetter(itertools.count()) >>> c = iterbetter(itertools.count())
>>> c[1] >>> c[1]
@ -335,24 +335,24 @@ class IterBetter:
... ...
IndexError: already passed 3 IndexError: already passed 3
""" """
def __init__(self, iterator): def __init__(self, iterator):
self.i, self.c = iterator, 0 self.i, self.c = iterator, 0
def __iter__(self): def __iter__(self):
while 1: while 1:
yield self.i.next() yield self.i.next()
self.c += 1 self.c += 1
def __getitem__(self, i): def __getitem__(self, i):
#todo: slices #todo: slices
if i < self.c: if i < self.c:
raise IndexError, "already passed "+str(i) raise IndexError, "already passed "+str(i)
try: try:
while i > self.c: while i > self.c:
self.i.next() self.i.next()
self.c += 1 self.c += 1
# now self.c == i # now self.c == i
self.c += 1 self.c += 1
return self.i.next() return self.i.next()
except StopIteration: except StopIteration:
raise IndexError, str(i) raise IndexError, str(i)
iterbetter = IterBetter iterbetter = IterBetter
@ -365,23 +365,23 @@ def dictreverse(mapping):
def dictfind(dictionary, element): def dictfind(dictionary, element):
""" """
Returns a key whose value in `dictionary` is `element` Returns a key whose value in `dictionary` is `element`
or, if none exists, None. or, if none exists, None.
>>> d = {1:2, 3:4} >>> d = {1:2, 3:4}
>>> dictfind(d, 4) >>> dictfind(d, 4)
3 3
>>> dictfind(d, 5) >>> dictfind(d, 5)
""" """
for (key, value) in dictionary.iteritems(): for (key, value) in dictionary.iteritems():
if element is value: if element is value:
return key return key
def dictfindall(dictionary, element): def dictfindall(dictionary, element):
""" """
Returns the keys whose values in `dictionary` are `element` Returns the keys whose values in `dictionary` are `element`
or, if none exists, []. or, if none exists, [].
>>> d = {1:4, 3:4} >>> d = {1:4, 3:4}
>>> dictfindall(d, 4) >>> dictfindall(d, 4)
[1, 3] [1, 3]
@ -396,9 +396,9 @@ def dictfindall(dictionary, element):
def dictincr(dictionary, element): def dictincr(dictionary, element):
""" """
Increments `element` in `dictionary`, Increments `element` in `dictionary`,
setting it to one if it doesn't exist. setting it to one if it doesn't exist.
>>> d = {1:2, 3:4} >>> d = {1:2, 3:4}
>>> dictincr(d, 1) >>> dictincr(d, 1)
3 3
@ -417,7 +417,7 @@ def dictadd(*dicts):
""" """
Returns a dictionary consisting of the keys in the argument dictionaries. Returns a dictionary consisting of the keys in the argument dictionaries.
If they share a key, the value from the last argument is used. If they share a key, the value from the last argument is used.
>>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1}) >>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1})
{1: 0, 2: 1, 3: 1} {1: 0, 2: 1, 3: 1}
""" """
@ -429,21 +429,21 @@ def dictadd(*dicts):
def listget(lst, ind, default=None): def listget(lst, ind, default=None):
""" """
Returns `lst[ind]` if it exists, `default` otherwise. Returns `lst[ind]` if it exists, `default` otherwise.
>>> listget(['a'], 0) >>> listget(['a'], 0)
'a' 'a'
>>> listget(['a'], 1) >>> listget(['a'], 1)
>>> listget(['a'], 1, 'b') >>> listget(['a'], 1, 'b')
'b' 'b'
""" """
if len(lst)-1 < ind: if len(lst)-1 < ind:
return default return default
return lst[ind] return lst[ind]
def intget(integer, default=None): def intget(integer, default=None):
""" """
Returns `integer` as an int or `default` if it can't. Returns `integer` as an int or `default` if it can't.
>>> intget('3') >>> intget('3')
3 3
>>> intget('3a') >>> intget('3a')
@ -458,7 +458,7 @@ def intget(integer, default=None):
def datestr(then, now=None): def datestr(then, now=None):
""" """
Converts a (UTC) datetime object to a nice string representation. Converts a (UTC) datetime object to a nice string representation.
>>> from datetime import datetime, timedelta >>> from datetime import datetime, timedelta
>>> d = datetime(1970, 5, 1) >>> d = datetime(1970, 5, 1)
>>> datestr(d, now=d) >>> datestr(d, now=d)
@ -533,12 +533,12 @@ def datestr(then, now=None):
def numify(string): def numify(string):
""" """
Removes all non-digit characters from `string`. Removes all non-digit characters from `string`.
>>> numify('800-555-1212') >>> numify('800-555-1212')
'8005551212' '8005551212'
>>> numify('800.555.1212') >>> numify('800.555.1212')
'8005551212' '8005551212'
""" """
return ''.join([c for c in str(string) if c.isdigit()]) return ''.join([c for c in str(string) if c.isdigit()])
@ -546,10 +546,10 @@ def denumify(string, pattern):
""" """
Formats `string` according to `pattern`, where the letter X gets replaced Formats `string` according to `pattern`, where the letter X gets replaced
by characters from `string`. by characters from `string`.
>>> denumify("8005551212", "(XXX) XXX-XXXX") >>> denumify("8005551212", "(XXX) XXX-XXXX")
'(800) 555-1212' '(800) 555-1212'
""" """
out = [] out = []
for c in pattern: for c in pattern:
@ -569,15 +569,15 @@ def dateify(datestring):
class CaptureStdout: class CaptureStdout:
""" """
Captures everything `func` prints to stdout and returns it instead. Captures everything `func` prints to stdout and returns it instead.
>>> def idiot(): >>> def idiot():
... print "foo" ... print "foo"
>>> capturestdout(idiot)() >>> capturestdout(idiot)()
'foo\\n' 'foo\\n'
**WARNING:** Not threadsafe! **WARNING:** Not threadsafe!
""" """
def __init__(self, func): def __init__(self, func):
self.func = func self.func = func
def __call__(self, *args, **keywords): def __call__(self, *args, **keywords):
from cStringIO import StringIO from cStringIO import StringIO
@ -585,9 +585,9 @@ class CaptureStdout:
out = StringIO() out = StringIO()
oldstdout = sys.stdout oldstdout = sys.stdout
sys.stdout = out sys.stdout = out
try: try:
self.func(*args, **keywords) self.func(*args, **keywords)
finally: finally:
sys.stdout = oldstdout sys.stdout = oldstdout
return out.getvalue() return out.getvalue()
@ -597,14 +597,14 @@ class Profile:
""" """
Profiles `func` and returns a tuple containing its output Profiles `func` and returns a tuple containing its output
and a string with human-readable profiling information. and a string with human-readable profiling information.
>>> import time >>> import time
>>> out, inf = profile(time.sleep)(.001) >>> out, inf = profile(time.sleep)(.001)
>>> out >>> out
>>> inf[:10].strip() >>> inf[:10].strip()
'took 0.0' 'took 0.0'
""" """
def __init__(self, func): def __init__(self, func):
self.func = func self.func = func
def __call__(self, *args): ##, **kw): kw unused def __call__(self, *args): ##, **kw): kw unused
import hotshot, hotshot.stats, tempfile ##, time already imported import hotshot, hotshot.stats, tempfile ##, time already imported
@ -639,31 +639,31 @@ if not hasattr(traceback, 'format_exc'):
def tryall(context, prefix=None): def tryall(context, prefix=None):
""" """
Tries a series of functions and prints their results. Tries a series of functions and prints their results.
`context` is a dictionary mapping names to values; `context` is a dictionary mapping names to values;
the value will only be tried if it's callable. the value will only be tried if it's callable.
>>> tryall(dict(j=lambda: True)) >>> tryall(dict(j=lambda: True))
j: True j: True
---------------------------------------- ----------------------------------------
results: results:
True: 1 True: 1
For example, you might have a file `test/stuff.py` For example, you might have a file `test/stuff.py`
with a series of functions testing various things in it. with a series of functions testing various things in it.
At the bottom, have a line: At the bottom, have a line:
if __name__ == "__main__": tryall(globals()) if __name__ == "__main__": tryall(globals())
Then you can run `python test/stuff.py` and get the results of Then you can run `python test/stuff.py` and get the results of
all the tests. all the tests.
""" """
context = context.copy() # vars() would update context = context.copy() # vars() would update
results = {} results = {}
for (key, value) in context.iteritems(): for (key, value) in context.iteritems():
if not hasattr(value, '__call__'): if not hasattr(value, '__call__'):
continue continue
if prefix and not key.startswith(prefix): if prefix and not key.startswith(prefix):
continue continue
print key + ':', print key + ':',
try: try:
@ -674,7 +674,7 @@ def tryall(context, prefix=None):
print 'ERROR' print 'ERROR'
dictincr(results, 'ERROR') dictincr(results, 'ERROR')
print ' ' + '\n '.join(traceback.format_exc().split('\n')) print ' ' + '\n '.join(traceback.format_exc().split('\n'))
print '-'*40 print '-'*40
print 'results:' print 'results:'
for (key, value) in results.iteritems(): for (key, value) in results.iteritems():
@ -682,18 +682,18 @@ def tryall(context, prefix=None):
class ThreadedDict: class ThreadedDict:
""" """
Takes a dictionary that maps threads to objects. Takes a dictionary that maps threads to objects.
When a thread tries to get or set an attribute or item When a thread tries to get or set an attribute or item
of the threadeddict, it passes it on to the object of the threadeddict, it passes it on to the object
for that thread in dictionary. for that thread in dictionary.
""" """
def __init__(self, dictionary): def __init__(self, dictionary):
self.__dict__['_ThreadedDict__d'] = dictionary self.__dict__['_ThreadedDict__d'] = dictionary
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.__d[threading.currentThread()], attr) return getattr(self.__d[threading.currentThread()], attr)
def __getitem__(self, item): def __getitem__(self, item):
return self.__d[threading.currentThread()][item] return self.__d[threading.currentThread()][item]
def __setattr__(self, attr, value): def __setattr__(self, attr, value):
@ -711,10 +711,10 @@ class ThreadedDict:
def __delitem__(self, item): def __delitem__(self, item):
del self.__d[threading.currentThread()][item] del self.__d[threading.currentThread()][item]
def __setitem__(self, item, value): def __setitem__(self, item, value):
self.__d[threading.currentThread()][item] = value self.__d[threading.currentThread()][item] = value
def __hash__(self): def __hash__(self):
return hash(self.__d[threading.currentThread()]) return hash(self.__d[threading.currentThread()])
threadeddict = ThreadedDict threadeddict = ThreadedDict
@ -722,25 +722,25 @@ threadeddict = ThreadedDict
def autoassign(self, locals): def autoassign(self, locals):
""" """
Automatically assigns local variables to `self`. Automatically assigns local variables to `self`.
>>> self = storage() >>> self = storage()
>>> autoassign(self, dict(a=1, b=2)) >>> autoassign(self, dict(a=1, b=2))
>>> self >>> self
<Storage {'a': 1, 'b': 2}> <Storage {'a': 1, 'b': 2}>
Generally used in `__init__` methods, as in: Generally used in `__init__` methods, as in:
def __init__(self, foo, bar, baz=1): autoassign(self, locals()) def __init__(self, foo, bar, baz=1): autoassign(self, locals())
""" """
for (key, value) in locals.iteritems(): for (key, value) in locals.iteritems():
if key == 'self': if key == 'self':
continue continue
setattr(self, key, value) setattr(self, key, value)
def to36(q): def to36(q):
""" """
Converts an integer to base 36 (a useful scheme for human-sayable IDs). Converts an integer to base 36 (a useful scheme for human-sayable IDs).
>>> to36(35) >>> to36(35)
'z' 'z'
>>> to36(119292) >>> to36(119292)
@ -751,9 +751,9 @@ def to36(q):
'0' '0'
>>> to36(-393) >>> to36(-393)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: must supply a positive integer ValueError: must supply a positive integer
""" """
if q < 0: raise ValueError, "must supply a positive integer" if q < 0: raise ValueError, "must supply a positive integer"
letters = "0123456789abcdefghijklmnopqrstuvwxyz" letters = "0123456789abcdefghijklmnopqrstuvwxyz"

View File

@ -9,7 +9,7 @@ __all__ = [
"header", "output", "flush", "debug", "header", "output", "flush", "debug",
"input", "data", "input", "data",
"setcookie", "cookies", "setcookie", "cookies",
"ctx", "ctx",
"loadhooks", "load", "unloadhooks", "unload", "_loadhooks", "loadhooks", "load", "unloadhooks", "unload", "_loadhooks",
"wsgifunc" "wsgifunc"
] ]
@ -59,19 +59,19 @@ def internalerror():
def header(hdr, value, unique=False): def header(hdr, value, unique=False):
""" """
Adds the header `hdr: value` with the response. Adds the header `hdr: value` with the response.
If `unique` is True and a header with that name already exists, If `unique` is True and a header with that name already exists,
it doesn't add a new one. it doesn't add a new one.
""" """
hdr, value = utf8(hdr), utf8(value) hdr, value = utf8(hdr), utf8(value)
# protection against HTTP response splitting attack # protection against HTTP response splitting attack
if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value: if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value:
raise ValueError, 'invalid characters in header' raise ValueError, 'invalid characters in header'
if unique is True: if unique is True:
for h, v in ctx.headers: for h, v in ctx.headers:
if h.lower() == hdr.lower(): return if h.lower() == hdr.lower(): return
ctx.headers.append((hdr, value)) ctx.headers.append((hdr, value))
def output(string_): def output(string_):
@ -88,20 +88,20 @@ def flush():
def input(*requireds, **defaults): def input(*requireds, **defaults):
""" """
Returns a `storage` object with the GET and POST arguments. Returns a `storage` object with the GET and POST arguments.
See `storify` for how `requireds` and `defaults` work. See `storify` for how `requireds` and `defaults` work.
""" """
from cStringIO import StringIO from cStringIO import StringIO
def dictify(fs): return dict([(k, fs[k]) for k in fs.keys()]) def dictify(fs): return dict([(k, fs[k]) for k in fs.keys()])
_method = defaults.pop('_method', 'both') _method = defaults.pop('_method', 'both')
e = ctx.env.copy() e = ctx.env.copy()
a = b = {} a = b = {}
if _method.lower() in ['both', 'post']: if _method.lower() in ['both', 'post']:
if e['REQUEST_METHOD'] == 'POST': if e['REQUEST_METHOD'] == 'POST':
a = cgi.FieldStorage(fp = StringIO(data()), environ=e, a = cgi.FieldStorage(fp = StringIO(data()), environ=e,
keep_blank_values=1) keep_blank_values=1)
a = dictify(a) a = dictify(a)
@ -125,15 +125,15 @@ def data():
def setcookie(name, value, expires="", domain=None): def setcookie(name, value, expires="", domain=None):
"""Sets a cookie.""" """Sets a cookie."""
if expires < 0: if expires < 0:
expires = -1000000000 expires = -1000000000
kargs = {'expires': expires, 'path':'/'} kargs = {'expires': expires, 'path':'/'}
if domain: if domain:
kargs['domain'] = domain kargs['domain'] = domain
# @@ should we limit cookies to a different path? # @@ should we limit cookies to a different path?
cookie = Cookie.SimpleCookie() cookie = Cookie.SimpleCookie()
cookie[name] = value cookie[name] = value
for key, val in kargs.iteritems(): for key, val in kargs.iteritems():
cookie[name][key] = val cookie[name][key] = val
header('Set-Cookie', cookie.items()[0][1].OutputString()) header('Set-Cookie', cookie.items()[0][1].OutputString())
@ -154,18 +154,18 @@ def debug(*args):
""" """
Prints a prettyprinted version of `args` to stderr. Prints a prettyprinted version of `args` to stderr.
""" """
try: try:
out = ctx.environ['wsgi.errors'] out = ctx.environ['wsgi.errors']
except: except:
out = sys.stderr out = sys.stderr
for arg in args: for arg in args:
print >> out, pprint.pformat(arg) print >> out, pprint.pformat(arg)
return '' return ''
def _debugwrite(x): def _debugwrite(x):
try: try:
out = ctx.environ['wsgi.errors'] out = ctx.environ['wsgi.errors']
except: except:
out = sys.stderr out = sys.stderr
out.write(x) out.write(x)
debug.write = _debugwrite debug.write = _debugwrite
@ -173,8 +173,8 @@ debug.write = _debugwrite
class _outputter: class _outputter:
"""Wraps `sys.stdout` so that print statements go into the response.""" """Wraps `sys.stdout` so that print statements go into the response."""
def __init__(self, file): self.file = file def __init__(self, file): self.file = file
def write(self, string_): def write(self, string_):
if hasattr(ctx, 'output'): if hasattr(ctx, 'output'):
return output(string_) return output(string_)
else: else:
self.file.write(string_) self.file.write(string_)
@ -186,7 +186,7 @@ def _capturedstdout():
while hasattr(sysstd, 'file'): while hasattr(sysstd, 'file'):
if isinstance(sys.stdout, _outputter): return True if isinstance(sys.stdout, _outputter): return True
sysstd = sysstd.file sysstd = sysstd.file
if isinstance(sys.stdout, _outputter): return True if isinstance(sys.stdout, _outputter): return True
return False return False
if not _capturedstdout(): if not _capturedstdout():
@ -197,7 +197,7 @@ ctx = context = threadeddict(_context)
ctx.__doc__ = """ ctx.__doc__ = """
A `storage` object containing various information about the request: A `storage` object containing various information about the request:
`environ` (aka `env`) `environ` (aka `env`)
: A dictionary containing the standard WSGI environment variables. : A dictionary containing the standard WSGI environment variables.
@ -215,7 +215,7 @@ A `storage` object containing various information about the request:
`path` `path`
: The path request. : The path request.
`query` `query`
: If there are no query arguments, the empty string. Otherwise, a `?` followed : If there are no query arguments, the empty string. Otherwise, a `?` followed
by the query string. by the query string.
@ -241,8 +241,8 @@ _loadhooks = {}
def load(): def load():
""" """
Loads a new context for the thread. Loads a new context for the thread.
You can ask for a function to be run at loadtime by You can ask for a function to be run at loadtime by
adding it to the dictionary `loadhooks`. adding it to the dictionary `loadhooks`.
""" """
_context[threading.currentThread()] = storage() _context[threading.currentThread()] = storage()
@ -251,7 +251,7 @@ def load():
if config.get('db_parameters'): if config.get('db_parameters'):
import db import db
db.connect(**config.db_parameters) db.connect(**config.db_parameters)
for x in loadhooks.values(): x() for x in loadhooks.values(): x()
def _load(env): def _load(env):
@ -267,14 +267,14 @@ def _load(env):
ctx.path = env.get('PATH_INFO') ctx.path = env.get('PATH_INFO')
# http://trac.lighttpd.net/trac/ticket/406 requires: # http://trac.lighttpd.net/trac/ticket/406 requires:
if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'): if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'):
ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0],
os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))) os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', '')))
if env.get('QUERY_STRING'): if env.get('QUERY_STRING'):
ctx.query = '?' + env.get('QUERY_STRING', '') ctx.query = '?' + env.get('QUERY_STRING', '')
else: else:
ctx.query = '' ctx.query = ''
ctx.fullpath = ctx.path + ctx.query ctx.fullpath = ctx.path + ctx.query
for x in _loadhooks.values(): x() for x in _loadhooks.values(): x()
@ -283,7 +283,7 @@ unloadhooks = {}
def unload(): def unload():
""" """
Unloads the context for the thread. Unloads the context for the thread.
You can ask for a function to be run at loadtime by You can ask for a function to be run at loadtime by
adding it ot the dictionary `unloadhooks`. adding it ot the dictionary `unloadhooks`.
""" """
@ -297,7 +297,7 @@ def _unload():
def wsgifunc(func, *middleware): def wsgifunc(func, *middleware):
"""Returns a WSGI-compatible function from a webpy-function.""" """Returns a WSGI-compatible function from a webpy-function."""
middleware = list(middleware) middleware = list(middleware)
def wsgifunc(env, start_resp): def wsgifunc(env, start_resp):
_load(env) _load(env)
try: try:
@ -307,7 +307,7 @@ def wsgifunc(func, *middleware):
except: except:
print >> debug, traceback.format_exc() print >> debug, traceback.format_exc()
result = internalerror() result = internalerror()
is_generator = result and hasattr(result, 'next') is_generator = result and hasattr(result, 'next')
if is_generator: if is_generator:
# wsgi requires the headers first # wsgi requires the headers first
@ -322,19 +322,19 @@ def wsgifunc(func, *middleware):
ctx._write = start_resp(status, headers) ctx._write = start_resp(status, headers)
# and now, the fun: # and now, the fun:
def cleanup(): def cleanup():
# we insert this little generator # we insert this little generator
# at the end of our itertools.chain # at the end of our itertools.chain
# so that it unloads the request # so that it unloads the request
# when everything else is done # when everything else is done
yield '' # force it to be a generator yield '' # force it to be a generator
_unload() _unload()
# result is the output of calling the webpy function # result is the output of calling the webpy function
# it could be a generator... # it could be a generator...
if is_generator: if is_generator:
if firstchunk is flush: if firstchunk is flush:
# oh, it's just our special flush mode # oh, it's just our special flush mode
@ -348,22 +348,22 @@ def wsgifunc(func, *middleware):
return [] return []
else: else:
return itertools.chain([firstchunk], result, cleanup()) return itertools.chain([firstchunk], result, cleanup())
# ... but it's usually just None # ... but it's usually just None
# #
# output is the stuff in ctx.output # output is the stuff in ctx.output
# it's usually a string... # it's usually a string...
if isinstance(output, str): #@@ other stringlikes? if isinstance(output, str): #@@ other stringlikes?
_unload() _unload()
return [output] return [output]
# it could be a generator... # it could be a generator...
elif hasattr(output, 'next'): elif hasattr(output, 'next'):
return itertools.chain(output, cleanup()) return itertools.chain(output, cleanup())
else: else:
_unload() _unload()
raise Exception, "Invalid ctx.output" raise Exception, "Invalid ctx.output"
for mw_func in middleware: for mw_func in middleware:
wsgifunc = mw_func(wsgifunc) wsgifunc = mw_func(wsgifunc)
return wsgifunc return wsgifunc

View File

@ -991,7 +991,7 @@ class MultiCall:
def get_call_list(self): def get_call_list(self):
return self.__call_list return self.__call_list
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# convenience functions # convenience functions