Remove trailing whitspace from *.py

This commit is contained in:
Andrew Resch 2008-11-17 00:57:32 +00:00
parent 7813bd4098
commit a0bf6d8fe1
62 changed files with 1138 additions and 1131 deletions

View file

@ -88,7 +88,7 @@ def encode_bool(x, r):
encode_int(1, r)
else:
encode_int(0, r)
def encode_string(x, r):
r.extend((str(len(x)), ':', x))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -72,7 +72,7 @@ DEFAULT_PREFS = {
"max_upload_speed": -1.0,
"max_download_speed": -1.0,
"max_upload_slots_global": 4,
"max_half_open_connections": (lambda: deluge.common.windows_check() and
"max_half_open_connections": (lambda: deluge.common.windows_check() and
(lambda: deluge.common.vista_check() and 4 or 8)() or -1)(),
"max_connections_per_second": 20,
"ignore_limits_on_local_network": True,
@ -114,13 +114,13 @@ class PreferencesManager(component.Component):
component.Component.__init__(self, "PreferencesManager")
self.config = deluge.configmanager.ConfigManager("core.conf", DEFAULT_PREFS)
def start(self):
self.core = component.get("Core")
self.session = component.get("Core").session
self.settings = component.get("Core").settings
self.signals = component.get("SignalManager")
# Register set functions in the Config
self.config.register_set_function("torrentfiles_location",
self._on_set_torrentfiles_location)
@ -194,7 +194,7 @@ class PreferencesManager(component.Component):
self._on_new_release_check)
self.config.register_change_callback(self._on_config_value_change)
# Config set functions
def _on_config_value_change(self, key, value):
self.signals.emit("config_value_changed", key, value)

View file

@ -2,19 +2,19 @@
# rpcserver.py
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
#
# Deluge is free software.
#
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
@ -43,14 +43,14 @@ import deluge.configmanager
def export(func):
func._rpcserver_export = True
return func
class RPCServer(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer, component.Component):
def __init__(self, port):
component.Component.__init__(self, "RPCServer")
# Get config
self.config = deluge.configmanager.ConfigManager("core.conf")
if port == None:
port = self.config["daemon_port"]
@ -67,31 +67,31 @@ class RPCServer(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer, component
except:
log.info("Daemon already running or port not available..")
sys.exit(0)
self.register_multicall_functions()
self.register_introspection_functions()
self.socket.setblocking(False)
gobject.io_add_watch(self.socket.fileno(), gobject.IO_IN | gobject.IO_OUT | gobject.IO_PRI | gobject.IO_ERR | gobject.IO_HUP, self._on_socket_activity)
def _on_socket_activity(self, source, condition):
"""This gets called when there is activity on the socket, ie, data to read
or to write."""
self.handle_request()
return True
def register_object(self, obj, name=None):
if not name:
name = obj.__class__.__name__
for d in dir(obj):
if d[0] == "_":
continue
if getattr(getattr(obj, d), '_rpcserver_export', False):
log.debug("Registering method: %s", name + "." + d)
self.register_function(getattr(obj, d), name + "." + d)
def get_request(self):
"""Get the request and client address from the socket.
We override this so that we can get the ip address of the client.

View file

@ -2,19 +2,19 @@
# signalmanager.py
#
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
#
#
# Deluge is free software.
#
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
@ -53,7 +53,7 @@ class SignalManager(component.Component):
"""Registers a handler for signals"""
if signal not in self.handler.keys():
self.handler[signal] = []
self.handler[signal].append(handler)
log.debug("Registered signal handler for %s", signal)
@ -63,7 +63,7 @@ class SignalManager(component.Component):
for (key, value) in self.handlers:
if handler in value:
value.remove(handler)
def deregister_client(self, address):
"""Deregisters a client"""
log.debug("Deregistering %s as a signal reciever..", address)
@ -83,7 +83,7 @@ class SignalManager(component.Component):
if signal in self.handlers.keys():
for handler in self.handlers[signal]:
handler(*data)
for uri in self.clients:
gobject.idle_add(self._emit, uri, signal, 1, *data)

View file

@ -397,6 +397,14 @@ class Torrent:
else:
status = self.status
if self.is_finished and (self.options["stop_at_ratio"] or self.config["stop_seed_at_ratio"]):
# We're a seed, so calculate the time to the 'stop_share_ratio'
if not status.upload_payload_rate:
return 0
stop_ratio = self.config["stop_seed_ratio"] if self.config["stop_at_seed_ratio"] else self.options["stop_seed_ratio"]
return ((status.all_time_download * stop_ratio) - status.all_time_upload) / status.upload_payload_rate
left = status.total_wanted - status.total_done
if left <= 0 or status.download_payload_rate == 0:

View file

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

View file

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

View file

@ -71,7 +71,7 @@ def decode_from_filesystem(path):
def dummy(v):
pass
def make_meta_file(path, url, piece_length, progress=dummy,
title=None, comment=None, safe=None, content_type=None,
target=None, url_list=None, name=None, private=False,
@ -105,7 +105,7 @@ def make_meta_file(path, url, piece_length, progress=dummy,
data['created by'] = created_by
if httpseeds:
data['httpseeds'] = httpseeds
h.write(bencode(data))
h.close()
@ -153,7 +153,7 @@ def makeinfo(path, piece_length, progress, name = None,
num_pieces = totalsize / piece_length
else:
num_pieces = 1
for p, f in subs:
pos = 0
size = os.path.getsize(f)
@ -197,7 +197,7 @@ def makeinfo(path, piece_length, progress, name = None,
num_pieces = size / piece_length
else:
num_pieces = 1
pieces = []
p = 0
h = file(path, 'rb')

View file

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

View file

@ -19,7 +19,7 @@ class FormatException(TextException):
pass
class TextBase:
def __init__(self, fd, regexp):
log.debug("TextBase loading")
self.count = 0
@ -28,7 +28,7 @@ class TextBase:
def next(self):
self.count += 1
txt = self.fd.readline()
if txt == "":
return False
@ -89,7 +89,7 @@ class GZMuleReader(MuleReader):
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):
def __init__(self, filename):
@ -125,7 +125,7 @@ class PGZip(TextBase):
# End of zip
return False
return ret
except FormatException, e:
log.debug("Blocklist: PGZip: Got format exception for zipfile")
# Just skip

View file

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

View file

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

View file

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

View file

@ -77,10 +77,10 @@ class Graph:
self.interval = 2000 # 2 secs
self.text_bg = (255, 255 , 255, 128) # prototyping
self.set_left_axis()
def set_left_axis(self, **kargs):
self.left_axis = kargs
def add_stat(self, stat, label='', axis='left', line=True, fill=True, color=None):
self.stat_info[stat] = {
'axis': axis,
@ -89,18 +89,18 @@ class Graph:
'fill': fill,
'color': color
}
def async_request(self):
aclient.stats_get_stats(self.set_stats, self.stat_info.keys())
aclient.stats_get_config(self.set_config)
def set_stats(self, stats):
self.stats = stats
def set_config(self, config):
self.length = config["length"]
self.interval = config["update_interval"]
def draw_to_context(self, context, width, height):
self.ctx = context
self.width, self.height = width, height
@ -111,11 +111,11 @@ class Graph:
if self.legend_selected:
self.draw_legend()
return self.ctx
def draw(self, width, height):
self.width = width
self.height = height
self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height)
self.ctx = cairo.Context(self.surface)
self.draw_rect(white, 0, 0, self.width, self.height)
@ -125,24 +125,24 @@ class Graph:
if self.legend_selected:
self.draw_legend()
return self.surface
def draw_x_axis(self):
now = time.time()
duration = self.length * (self.interval / 1000.0)
start = now - duration
ratio = (self.width - 40) / duration
seconds_to_minute = 60 - time.localtime(start)[5]
for i in xrange(0, 5):
text = time.strftime('%H:%M', time.localtime(start + seconds_to_minute + (60*i)))
x = int(ratio * (seconds_to_minute + (60*i)))
self.draw_text(text, x + 46, self.height - 20)
x = x + 59.5
self.draw_dotted_line(gray, x, 20, x, self.height - 20)
y = self.height - 22.5
self.draw_dotted_line(gray, 60, y, int(self.width), y)
def draw_left_axis(self):
stats = {}
max_values = []
@ -158,15 +158,15 @@ class Graph:
max_value = max(*max_values)
else:
max_value = max_values[0]
if max_value < self.left_axis['min']:
max_value = self.left_axis['min']
height = self.height - self.line_size - 22
#max_value = float(round(max_value, len(str(max_value)) * -1))
max_value = float(max_value)
ratio = height / max_value
for i in xrange(1, 6):
y = int(ratio * ((max_value / 5) * i)) - 0.5
if i < 5:
@ -174,19 +174,19 @@ class Graph:
text = self.left_axis['formatter']((max_value / 5) * (5 - i))
self.draw_text(text, 0, y - 6)
self.draw_dotted_line(gray, 60.5, 20, 60.5, self.height - 20)
for stat, info in stats.iteritems():
self.draw_value_poly(info['values'], info['color'], max_value)
self.draw_value_poly(info['values'], info['fill_color'], max_value, info['fill'])
def draw_legend(self):
pass
def trace_path(self, values, max_value):
height = self.height - 24
width = self.width
line_width = self.line_size
self.ctx.set_line_width(line_width)
self.ctx.move_to(width, height)
@ -198,43 +198,43 @@ class Graph:
for i, value in enumerate(values):
if i == self.length - 1:
x = 62
self.ctx.line_to(x,
self.ctx.line_to(x,
int(height - 1 - ((height - 28) * value / max_value))
)
x -= step
self.ctx.line_to(
int(width + 62 - (((len(values) - 1) * width) / (self.length - 1))),
height)
self.ctx.close_path()
def draw_value_poly(self, values, color, max_value, fill=False):
self.trace_path(values, max_value)
self.ctx.set_source_rgba(*color)
if fill:
self.ctx.fill()
else:
self.ctx.stroke()
def draw_text(self, text, x, y):
self.ctx.set_font_size(9)
self.ctx.move_to(x, y + 9)
self.ctx.set_source_rgba(*self.black)
self.ctx.show_text(text)
def draw_rect(self, color, x, y, height, width):
self.ctx.set_source_rgba(*color)
self.ctx.rectangle(x, y, height, width)
self.ctx.fill()
def draw_line(self, color, x1, y1, x2, y2):
self.ctx.set_source_rgba(*color)
self.ctx.set_line_width(1)
self.ctx.move_to(x1, y1)
self.ctx.line_to(x2, y2)
self.ctx.stroke()
def draw_dotted_line(self, color, x1, y1, x2, y2):
self.ctx.set_source_rgba(*color)
self.ctx.set_line_width(1)

View file

@ -57,7 +57,7 @@ class GraphsTab(Tab):
self.bandwidth_graph.connect('expose_event', self.bandwidth_expose)
self.window.unparent()
self.label.unparent()
def bandwidth_expose(self, widget, event):
self.graph_widget = self.bandwidth_graph
self.graph = graph.Graph()
@ -66,10 +66,10 @@ class GraphsTab(Tab):
self.graph.set_left_axis(formatter=fspeed, min=10240)
self.update_timer = gobject.timeout_add(2000, self.update_graph)
self.update_graph()
def update_graph(self):
width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
context = self.graph_widget.window.cairo_create()
width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
context = self.graph_widget.window.cairo_create()
self.graph.async_request()
aclient.force_call(True)
self.graph.draw_to_context(context, width, height)
@ -86,7 +86,7 @@ class GtkUI(object):
self.plugin.register_hook("on_apply_prefs", self.on_apply_prefs)
self.plugin.register_hook("on_show_prefs", self.on_show_prefs)
self.on_show_prefs()
self.graphs_tab = GraphsTab(XML(self.get_resource("tabs.glade")))
self.torrent_details = component.get('TorrentDetails')
self.torrent_details.notebook.append_page(self.graphs_tab.window, self.graphs_tab.label)

View file

@ -159,7 +159,7 @@ class BaseClient(object):
"""
no_callback_list = ["add_torrent_url", "pause_all_torrents",
"resume_all_torrents", "set_config", "enable_plugin",
"disable_plugin", "set_torrent_trackers", "connect_peer",
"disable_plugin", "set_torrent_trackers", "connect_peer",
"set_torrent_max_connections", "set_torrent_max_upload_slots",
"set_torrent_max_upload_speed", "set_torrent_max_download_speed",
"set_torrent_private_flag", "set_torrent_file_priorities",
@ -169,7 +169,7 @@ class BaseClient(object):
"set_torrent_prioritize_first_last", "set_torrent_auto_managed",
"set_torrent_stop_ratio", "set_torrent_stop_at_ratio",
"set_torrent_remove_at_ratio", "set_torrent_move_on_completed",
"set_torrent_move_on_completed_path", "add_torrent_magnets",
"set_torrent_move_on_completed_path", "add_torrent_magnets",
"create_torrent", "upload_plugin", "rescan_plugins", "rename_files",
"rename_folder"]

View file

@ -35,7 +35,7 @@ default_style = {
'magenta' : make_style(fg='magenta'),
'cyan' : make_style(fg='cyan'),
'white' : make_style(fg='white'),
'bold_black' : make_style(fg='black', attrs='bright'),
'bold_red' : make_style(fg='red', attrs='bright'),
'bold_green' : make_style(fg='green', attrs='bright'),

View file

@ -9,7 +9,7 @@ import re
import cStringIO, tokenize
def atom(next, token):
"""taken with slight modifications from http://effbot.org/zone/simple-iterator-parser.htm"""
"""taken with slight modifications from http://effbot.org/zone/simple-iterator-parser.htm"""
if token[1] == "(":
out = []
token = next()
@ -34,7 +34,7 @@ def atom(next, token):
def simple_eval(source):
""" evaluates the 'source' string into a combination of primitive python objects
taken from http://effbot.org/zone/simple-iterator-parser.htm"""
taken from http://effbot.org/zone/simple-iterator-parser.htm"""
src = cStringIO.StringIO(source).readline
src = tokenize.generate_tokens(src)
src = (token for token in src if token[0] is not tokenize.NL)
@ -46,11 +46,11 @@ def simple_eval(source):
class Command(BaseCommand):
"""Show and set configuration values"""
option_list = BaseCommand.option_list + (
make_option('-s', '--set', action='store_true', default=False, dest='set',
help='set value for key'),
)
)
usage = "Usage: config [key1 [key2 ...]]\n"\
" config --set key value"
@ -77,7 +77,7 @@ class Command(BaseCommand):
color = 'cyan'
elif isinstance(value, list):
color = 'magenta'
print templates.config_display(key, style[color](str(value)))
client.get_config(_on_get_config)
@ -113,4 +113,4 @@ class Command(BaseCommand):
def split(self, text):
return str.split(text)

View file

@ -14,7 +14,7 @@ class Command(BaseCommand):
# get a list of commands, exclude 'help' so we won't run into a recursive loop.
self._commands = load_commands(os.path.join(UI_PATH,'commands'), exclude=['help'])
self._commands['help'] = self
def handle(self, *args, **options):
if args:
if len(args) > 1:
@ -24,7 +24,7 @@ class Command(BaseCommand):
cmd = self._commands[args[0]]
except KeyError:
print templates.ERROR('unknown command %r' % args[0])
return
return
try:
parser = cmd.create_parser()
print parser.format_help()
@ -34,8 +34,8 @@ class Command(BaseCommand):
max_length = max( len(k) for k in self._commands)
for cmd in sorted(self._commands):
print templates.help(max_length, cmd, self._commands[cmd].__doc__ or '')
print
print
print 'for help on a specific command, use "<command> --help"'
def complete(self, text, *args):
return [ x for x in self._commands.keys() if x.startswith(text) ]

View file

@ -47,18 +47,18 @@ class Command(BaseCommand):
usage = "Usage: info [<torrent-id> [<torrent-id> ...]]\n"\
" You can give the first few characters of a torrent-id to identify the torrent."
def handle(self, *args, **options):
args = mapping.to_ids(args)
self.torrents = match_torrents(args)
for tor in self.torrents:
self.show_info(tor, options.get('verbose'))
def complete(self, text, *args):
torrents = match_torrents()
names = mapping.get_names(torrents)
return [ x[1] for x in names if x[1].startswith(text) ]
def show_info(self, torrent, verbose):
def _got_torrent_status(state):
print templates.info_general('ID', torrent)
@ -98,7 +98,7 @@ class Command(BaseCommand):
for peer in state['peers']:
client_str = unicode(peer['client'])
client_str += unicode(peer['seed']) if peer['seed'] else ''
print templates.info_peers(str(peer['ip']), unicode(client_str),
print templates.info_peers(str(peer['ip']), unicode(client_str),
str(common.fspeed(peer['up_speed'])), str(common.fspeed(peer['down_speed'])))
print ""
client.get_torrent_status(_got_torrent_status, torrent, status_keys)

View file

@ -26,4 +26,4 @@ class Command(BaseCommand):
torrents = match_torrents()
names = mapping.get_names(torrents)
return [ x[1] for x in names if x[1].startswith(text) ]

View file

@ -10,7 +10,7 @@ class Command(BaseCommand):
"""Remove a torrent"""
usage = "Usage: rm <torrent-id>"
aliases = ['del']
option_list = BaseCommand.option_list + (
make_option('--remove_torrent', action='store_true', default=False,
help="remove the torrent's file"),

File diff suppressed because one or more lines are too long

View file

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

View file

@ -2,19 +2,19 @@
# createtorrentdialog.py
#
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
#
#
# Deluge is free software.
#
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
@ -47,11 +47,11 @@ class CreateTorrentDialog:
def show(self):
self.glade = gtk.glade.XML(
pkg_resources.resource_filename(
"deluge.ui.gtkui",
"deluge.ui.gtkui",
"glade/create_torrent_dialog.glade"))
self.config = ConfigManager("gtkui.conf")
self.dialog = self.glade.get_widget("create_torrent_dialog")
self.dialog.set_transient_for(component.get("MainWindow").window)
@ -66,7 +66,7 @@ class CreateTorrentDialog:
"on_button_remove_clicked": self._on_button_remove_clicked,
"on_button_down_clicked": self._on_button_down_clicked
})
# path, icon, size
self.files_treestore = gtk.TreeStore(str, str, gobject.TYPE_UINT64)
@ -85,7 +85,7 @@ class CreateTorrentDialog:
column.pack_start(render)
column.set_cell_data_func(render, listview.cell_data_size, 2)
self.glade.get_widget("treeview_files").append_column(column)
self.glade.get_widget("treeview_files").set_model(self.files_treestore)
self.glade.get_widget("treeview_files").set_show_expanders(False)
@ -99,12 +99,12 @@ class CreateTorrentDialog:
self.glade.get_widget("tracker_treeview").set_model(self.trackers_liststore)
self.trackers_liststore.set_sort_column_id(0, gtk.SORT_ASCENDING)
if not client.is_localhost() and client.connected():
self.glade.get_widget("button_remote_path").show()
else:
self.glade.get_widget("button_remote_path").hide()
self.dialog.show()
def adjust_piece_size(self):
@ -118,7 +118,7 @@ class CreateTorrentDialog:
if pieces < 2048 or (index + 1) == len(model):
self.glade.get_widget("combo_piece_size").set_active(index)
break
def _on_button_file_clicked(self, widget):
log.debug("_on_button_file_clicked")
# Setup the filechooserdialog
@ -145,7 +145,7 @@ class CreateTorrentDialog:
self.files_treestore.append(None, [result, gtk.STOCK_FILE, deluge.common.get_path_size(result)])
self.adjust_piece_size()
chooser.destroy()
def _on_button_folder_clicked(self, widget):
log.debug("_on_button_folder_clicked")
# Setup the filechooserdialog
@ -168,11 +168,11 @@ class CreateTorrentDialog:
return
self.files_treestore.clear()
self.files_treestore.append(None, [result, gtk.STOCK_OPEN, deluge.common.get_path_size(result)])
self.adjust_piece_size()
chooser.destroy()
def _on_button_remote_path_clicked(self, widget):
log.debug("_on_button_remote_path_clicked")
dialog = self.glade.get_widget("remote_path_dialog")
@ -181,7 +181,7 @@ class CreateTorrentDialog:
entry.set_text("/")
entry.grab_focus()
response = dialog.run()
if response == gtk.RESPONSE_OK:
result = entry.get_text()
def _on_get_path_size(size):
@ -189,16 +189,16 @@ class CreateTorrentDialog:
if size > 0:
self.files_treestore.clear()
self.files_treestore.append(None, [result, gtk.STOCK_NETWORK, size])
self.adjust_piece_size()
self.adjust_piece_size()
client.get_path_size(_on_get_path_size, result)
client.force_call(True)
dialog.destroy()
def _on_button_cancel_clicked(self, widget):
log.debug("_on_button_cancel_clicked")
self.dialog.destroy()
def _on_button_save_clicked(self, widget):
log.debug("_on_button_save_clicked")
if len(self.files_treestore) == 0:
@ -223,7 +223,7 @@ class CreateTorrentDialog:
chooser.set_transient_for(self.dialog)
chooser.set_select_multiple(False)
chooser.set_property("skip-taskbar-hint", True)
# Add .torrent and * file filters
file_filter = gtk.FileFilter()
file_filter.set_name(_("Torrent files"))
@ -233,7 +233,7 @@ class CreateTorrentDialog:
file_filter.set_name(_("All files"))
file_filter.add_pattern("*")
chooser.add_filter(file_filter)
chooser.set_current_name(os.path.split(self.files_treestore[0][0])[-1] + ".torrent")
# Run the dialog
response = chooser.run()
@ -271,7 +271,7 @@ class CreateTorrentDialog:
combo = self.glade.get_widget("combo_piece_size")
piece_length = int(combo.get_model()[combo.get_active()][0].split()[0]) * 1024
num_pieces = self.files_treestore[0][2] / piece_length
author = self.glade.get_widget("entry_author").get_text()
comment = self.glade.get_widget("entry_comments").get_text()
private = self.glade.get_widget("chk_private_flag").get_active()
@ -298,7 +298,7 @@ class CreateTorrentDialog:
import threading
threading.Thread(target=self.create_torrent,
args=(
path,
path,
tracker,
piece_length,
self._on_create_torrent_progress,
@ -309,7 +309,7 @@ class CreateTorrentDialog:
author,
webseeds,
add_to_session)).start()
chooser.destroy()
self.dialog.destroy()
@ -330,14 +330,14 @@ class CreateTorrentDialog:
self.glade.get_widget("progress_dialog").hide_all()
if add_to_session:
client.add_torrent_file([target])
def _on_create_torrent_progress(self, value, num_pieces):
percent = float(value)/float(num_pieces)
pbar = self.glade.get_widget("progressbar")
pbar.set_text(_("%.2f%%") % (percent*100))
if percent >= 0 and percent <= 1.0:
pbar.set_fraction(percent)
def _on_button_up_clicked(self, widget):
log.debug("_on_button_up_clicked")
row = self.glade.get_widget("tracker_treeview").get_selection().get_selected()[1]
@ -350,12 +350,12 @@ class CreateTorrentDialog:
log.debug("_on_button_down_clicked")
row = self.glade.get_widget("tracker_treeview").get_selection().get_selected()[1]
self.trackers_liststore[row][0] += 1
def _on_button_add_clicked(self, widget):
log.debug("_on_button_add_clicked")
glade = gtk.glade.XML(
pkg_resources.resource_filename(
"deluge.ui.gtkui",
"deluge.ui.gtkui",
"glade/edit_trackers.glade"))
dialog = glade.get_widget("add_tracker_dialog")
dialog.set_transient_for(self.dialog)
@ -372,7 +372,7 @@ class CreateTorrentDialog:
for l in lines:
if deluge.common.is_url(l):
trackers.append(l)
# We are going to add these trackers to the heighest tier + 1
tier = 0
for row in self.trackers_liststore:
@ -383,10 +383,10 @@ class CreateTorrentDialog:
self.trackers_liststore.append([tier, tracker])
dialog.destroy()
def _on_button_remove_clicked(self, widget):
log.debug("_on_button_remove_clicked")
row = self.glade.get_widget("tracker_treeview").get_selection().get_selected()[1]
self.trackers_liststore.remove(row)

View file

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

View file

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

View file

@ -2,19 +2,19 @@
# edittrackersdialog.py
#
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
#
#
# Deluge is free software.
#
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
@ -44,18 +44,18 @@ class EditTrackersDialog:
def __init__(self, torrent_id, parent=None):
self.torrent_id = torrent_id
self.glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui",
pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/edit_trackers.glade"))
self.dialog = self.glade.get_widget("edit_trackers_dialog")
self.treeview = self.glade.get_widget("tracker_treeview")
self.add_tracker_dialog = self.glade.get_widget("add_tracker_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.set_transient_for(self.dialog)
self.dialog.set_icon(common.get_logo(32))
if parent != None:
self.dialog.set_transient_for(parent)
@ -73,24 +73,24 @@ class EditTrackersDialog:
"on_button_add_ok_clicked": self.on_button_add_ok_clicked,
"on_button_add_cancel_clicked": self.on_button_add_cancel_clicked
})
# Create a liststore for tier, url
self.liststore = gtk.ListStore(int, str)
# Create the columns
self.treeview.append_column(
gtk.TreeViewColumn(_("Tier"), gtk.CellRendererText(), text=0))
self.treeview.append_column(
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)
def run(self):
# Make sure we have a torrent_id.. if not just return
if self.torrent_id == None:
return
# Get the trackers for this torrent
client.get_torrent_status(
@ -101,17 +101,17 @@ class EditTrackersDialog:
"""Display trackers dialog"""
for tracker in status["trackers"]:
self.add_tracker(tracker["tier"], tracker["url"])
self.dialog.show()
def add_tracker(self, tier, url):
"""Adds a tracker to the list"""
self.liststore.append([tier, url])
def get_selected(self):
"""Returns the selected tracker"""
return self.treeview.get_selection().get_selected()[1]
def on_button_up_clicked(self, widget):
log.debug("on_button_up_clicked")
selected = self.get_selected()
@ -121,13 +121,13 @@ class EditTrackersDialog:
new_tier = tier + 1
# Now change the tier for this tracker
self.liststore.set_value(selected, 0, new_tier)
def on_button_add_clicked(self, widget):
log.debug("on_button_add_clicked")
# Show the add tracker dialog
self.add_tracker_dialog.show()
self.glade.get_widget("textview_trackers").grab_focus()
def on_button_remove_clicked(self, widget):
log.debug("on_button_remove_clicked")
selected = self.get_selected()
@ -166,7 +166,7 @@ class EditTrackersDialog:
new_tier = tier - 1
# Now change the tier for this tracker
self.liststore.set_value(selected, 0, new_tier)
def on_button_ok_clicked(self, widget):
log.debug("on_button_ok_clicked")
self.trackers = []
@ -179,11 +179,11 @@ class EditTrackersDialog:
# Set the torrens trackers
client.set_torrent_trackers(self.torrent_id, self.trackers)
self.dialog.destroy()
def on_button_cancel_clicked(self, widget):
log.debug("on_button_cancel_clicked")
self.dialog.destroy()
def on_button_add_ok_clicked(self, widget):
log.debug("on_button_add_ok_clicked")
@ -215,7 +215,7 @@ class EditTrackersDialog:
self.liststore.foreach(tier_count, [tracker, highest_tier, duplicate])
else:
highest_tier = -1
# If not a duplicate, then add it to the list
if not duplicate:
# Add the tracker to the list
@ -224,9 +224,9 @@ class EditTrackersDialog:
# Clear the entry widget and hide the dialog
textview.get_buffer().set_text("")
self.add_tracker_dialog.hide()
def on_button_add_cancel_clicked(self, widget):
log.debug("on_button_add_cancel_clicked")
# Clear the entry widget and hide the dialog
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
#
# Copyright (C) 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
#
#
# Deluge is free software.
#
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
@ -104,12 +104,12 @@ class FilesTab(Tab):
self.listview = glade.get_widget("files_listview")
# filename, size, progress string, progress value, priority, file index, icon id
self.treestore = gtk.TreeStore(str, gobject.TYPE_UINT64, str, int, int, int, str)
# We need to store the row that's being edited to prevent updating it until
# it's been done editing
self._editing_index = None
# Filename column
# Filename column
column = gtk.TreeViewColumn(_("Filename"))
render = gtk.CellRendererPixbuf()
column.pack_start(render, False)
@ -129,7 +129,7 @@ class FilesTab(Tab):
column.set_reorderable(True)
self.listview.append_column(column)
# Size column
# Size column
column = gtk.TreeViewColumn(_("Size"))
render = gtk.CellRendererText()
column.pack_start(render, False)
@ -142,7 +142,7 @@ class FilesTab(Tab):
column.set_reorderable(True)
self.listview.append_column(column)
# Progress column
# Progress column
column = gtk.TreeViewColumn(_("Progress"))
render = gtk.CellRendererProgress()
column.pack_start(render)
@ -154,8 +154,8 @@ class FilesTab(Tab):
column.set_min_width(10)
column.set_reorderable(True)
self.listview.append_column(column)
# Priority column
# Priority column
column = gtk.TreeViewColumn(_("Priority"))
render = gtk.CellRendererPixbuf()
column.pack_start(render, False)
@ -172,9 +172,9 @@ class FilesTab(Tab):
self.listview.append_column(column)
self.listview.set_model(self.treestore)
self.listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
self.file_menu = glade.get_widget("menu_file_tab")
self.listview.connect("row-activated", self._on_row_activated)
self.listview.connect("button-press-event", self._on_button_press_event)
@ -187,7 +187,7 @@ class FilesTab(Tab):
self.listview.connect("drag_data_get", self._on_drag_data_get_data)
self.listview.connect("drag_data_received", self._on_drag_data_received_data)
glade.signal_autoconnect({
"on_menuitem_open_file_activate": self._on_menuitem_open_file_activate,
"on_menuitem_donotdownload_activate": self._on_menuitem_donotdownload_activate,
@ -200,22 +200,22 @@ class FilesTab(Tab):
# Connect to the 'torrent_file_renamed' signal
component.get("Signals").connect_to_signal("torrent_file_renamed", self._on_torrent_file_renamed_signal)
component.get("Signals").connect_to_signal("torrent_folder_renamed", self._on_torrent_folder_renamed_signal)
# Attempt to load state
self.load_state()
# torrent_id: (filepath, size)
self.files_list = {}
self.torrent_id = None
def save_state(self):
filename = "files_tab.state"
state = []
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())))
# Get the config location for saving the state file
config_location = ConfigManager("gtkui.conf")["config_location"]
@ -226,13 +226,13 @@ class FilesTab(Tab):
state_file.close()
except IOError, e:
log.warning("Unable to save state file: %s", e)
def load_state(self):
filename = "files_tab.state"
# Get the config location for loading the state file
config_location = ConfigManager("gtkui.conf")["config_location"]
state = None
try:
log.debug("Loading FilesTab state file: %s", filename)
state_file = open(os.path.join(config_location, filename), "rb")
@ -240,10 +240,10 @@ class FilesTab(Tab):
state_file.close()
except (EOFError, IOError), e:
log.warning("Unable to load state file: %s", e)
if state == None:
return
for column_state in state:
# Find matching columns in the listview
for (index, column) in enumerate(self.listview.get_columns()):
@ -272,18 +272,18 @@ class FilesTab(Tab):
# No torrent is selected in the torrentview
self.clear()
return
if torrent_id != self.torrent_id:
# We only want to do this if the torrent_id has changed
self.treestore.clear()
self.torrent_id = torrent_id
if self.torrent_id not in self.files_list.keys():
# We need to get the files list
log.debug("Getting file list from core..")
client.get_torrent_status(
self._on_get_torrent_files,
self.torrent_id,
self._on_get_torrent_files,
self.torrent_id,
["files", "file_progress", "file_priorities"])
client.force_call(block=True)
else:
@ -306,23 +306,23 @@ class FilesTab(Tab):
def get_file_path(self, row, path=""):
if not row:
return path
path = self.treestore.get_value(row, 0) + path
return self.get_file_path(self.treestore.iter_parent(row), path)
def _on_open_file(self, status):
paths = self.listview.get_selection().get_selected_rows()[1]
selected = []
for path in paths:
selected.append(self.treestore.get_iter(path))
for select in selected:
path = self.get_file_path(select).split("/")
filepath = os.path.join(status["save_path"], *path)
log.debug("Open file '%s'", 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):
split_files = { }
i = 0
@ -341,7 +341,7 @@ class FilesTab(Tab):
files_storage[file_name_chunk] = { }
self.prepare_file(file, file_name[first_slash_index+1:],
file_num, files_storage[file_name_chunk])
def add_files(self, parent_iter, split_files):
ret = 0
for key,value in split_files.iteritems():
@ -356,13 +356,13 @@ class FilesTab(Tab):
value[1]["size"], "", 0, 0, value[0], gtk.STOCK_FILE])
ret += value[1]["size"]
return ret
###
###
def update_files(self):
self.treestore.clear()
self.prepare_file_store(self.files_list[self.torrent_id])
self.listview.expand_row("0", False)
def get_selected_files(self):
"""Returns a list of file indexes that are selected"""
def get_iter_children(itr, selected):
@ -372,7 +372,7 @@ class FilesTab(Tab):
if self.treestore.iter_has_child(i):
get_iter_children(i, selected)
i = self.treestore.iter_next(i)
selected = []
paths = self.listview.get_selection().get_selected_rows()[1]
for path in paths:
@ -380,24 +380,24 @@ class FilesTab(Tab):
selected.append(self.treestore[i][5])
if self.treestore.iter_has_child(i):
get_iter_children(i, selected)
return selected
def _on_get_torrent_files(self, status):
self.files_list[self.torrent_id] = status["files"]
self.update_files()
self._on_get_torrent_status(status)
def get_files_from_tree(self, rows, files_list, indent):
if not rows:
return None
for row in rows:
if row[5] > -1:
files_list.append((row[5], row))
self.get_files_from_tree(row.iterchildren(), files_list, indent+1)
return None
def _on_get_torrent_status(self, status):
# (index, iter)
files_list = []
@ -418,7 +418,7 @@ class FilesTab(Tab):
file_priority = status["file_priorities"][index]
if row[4] != file_priority:
row[4] = file_priority
def _on_button_press_event(self, widget, event):
"""This is a callback for showing the right-click context menu."""
log.debug("on_button_press_event")
@ -436,10 +436,10 @@ class FilesTab(Tab):
self.listview.get_selection().select_iter(row)
else:
self.listview.get_selection().select_iter(row)
self.file_menu.popup(None, None, None, event.button, event.time)
return True
def _on_menuitem_open_file_activate(self, menuitem):
self._on_row_activated(None, None, None)
@ -458,32 +458,32 @@ class FilesTab(Tab):
file_priorities.sort()
priorities = [p[1] for p in file_priorities]
log.debug("priorities: %s", priorities)
client.set_torrent_file_priorities(self.torrent_id, priorities)
def _on_menuitem_donotdownload_activate(self, menuitem):
self._set_file_priorities_on_user_change(
self.get_selected_files(),
self.get_selected_files(),
deluge.common.FILE_PRIORITY["Do Not Download"])
def _on_menuitem_normal_activate(self, menuitem):
self._set_file_priorities_on_user_change(
self.get_selected_files(),
self.get_selected_files(),
deluge.common.FILE_PRIORITY["Normal Priority"])
def _on_menuitem_high_activate(self, menuitem):
self._set_file_priorities_on_user_change(
self.get_selected_files(),
self.get_selected_files(),
deluge.common.FILE_PRIORITY["High Priority"])
def _on_menuitem_highest_activate(self, menuitem):
self._set_file_priorities_on_user_change(
self.get_selected_files(),
self.get_selected_files(),
deluge.common.FILE_PRIORITY["Highest Priority"])
def _on_menuitem_expand_all_activate(self, menuitem):
self.listview.expand_all()
def _on_filename_edited(self, renderer, path, new_text):
index = self.treestore[path][5]
@ -503,7 +503,7 @@ class FilesTab(Tab):
fp += self.treestore[i][0]
return fp
return fp
# Only recurse if file is in a folder..
if self.treestore.iter_parent(itr):
filepath = get_filepath(itr, str()) + new_text
@ -511,35 +511,35 @@ class FilesTab(Tab):
filepath = new_text
log.debug("filepath: %s", filepath)
client.rename_files(self.torrent_id, [(index, filepath)])
else:
# We are renaming a folder
folder = self.treestore[path][0]
parent_path = ""
itr = self.treestore.iter_parent(self.treestore.get_iter(path))
while itr:
parent_path = self.treestore[itr][0] + parent_path
itr = self.treestore.iter_parent(itr)
client.rename_folder(self.torrent_id, parent_path + folder, parent_path + new_text)
self._editing_index = None
def _on_filename_editing_start(self, renderer, editable, path):
self._editing_index = self.treestore[path][5]
def _on_filename_editing_canceled(self, renderer):
self._editing_index = None
def _on_torrent_file_renamed_signal(self, torrent_id, index, name):
log.debug("index: %s name: %s", index, name)
old_name = self.files_list[torrent_id][index]["path"]
self.files_list[torrent_id][index]["path"] = name
# We need to update the filename displayed if we're currently viewing
# this torrents files.
# this torrents files.
if torrent_id == self.torrent_id:
old_name_len = len(old_name.split("/"))
name_len = len(name.split("/"))
@ -572,18 +572,18 @@ class FilesTab(Tab):
if create:
parent_iter = self.treestore.append(parent_iter,
[tc + "/", 0, "", 0, 0, -1, gtk.STOCK_DIRECTORY])
# Find the iter for the file that needs to be moved
def get_file_iter(model, path, itr, user_data):
if model[itr][5] == index:
model[itr][0] = name.split("/")[-1]
t = self.treestore.append(
parent_iter,
self.treestore.get(itr,
parent_iter,
self.treestore.get(itr,
*xrange(self.treestore.get_n_columns())))
self.treestore.remove(itr)
self.treestore.remove(itr)
return True
self.treestore.foreach(get_file_iter, None)
return True
else:
@ -602,30 +602,30 @@ class FilesTab(Tab):
def _on_torrent_folder_renamed_signal(self, torrent_id, old_folder, new_folder):
log.debug("old_folder: %s new_folder: %s", old_folder, new_folder)
if old_folder[-1] != "/":
old_folder += "/"
if new_folder[-1] != "/":
new_folder += "/"
for fd in self.files_list[torrent_id]:
if fd["path"].startswith(old_folder):
fd["path"] = fd["path"].replace(old_folder, new_folder, 1)
if torrent_id == self.torrent_id:
old_split = old_folder.split("/")
try:
old_split.remove("")
except:
pass
new_split = new_folder.split("/")
try:
new_split.remove("")
except:
pass
itr = None
for i, old in enumerate(old_split):
itr = self.treestore.iter_children(itr)
@ -636,11 +636,11 @@ class FilesTab(Tab):
self.treestore[itr][0] = new_split[i] + "/"
break
itr = self.treestore.iter_next(itr)
def _on_drag_data_get_data(self, treeview, context, selection, target_id, etime):
indexes = self.get_selected_files()
selection.set_text(",".join([str(x) for x in indexes]))
def _on_drag_data_received_data(self, treeview, context, x, y, selection, info, etime):
log.debug("selection.data: %s", selection.data)
drop_info = treeview.get_dest_row_at_pos(x, y)
@ -651,11 +651,11 @@ class FilesTab(Tab):
parent_path = ""
if model[itr][5] == -1:
parent_path += model[itr][0]
while parent_iter:
parent_path = model[parent_iter][0] + parent_path
parent_iter = model.iter_parent(parent_iter)
#[(index, filepath), ...]
to_rename = []
@ -665,8 +665,8 @@ class FilesTab(Tab):
to_rename.append((model[itr][5], parent_path + model[itr][0]))
if len(to_rename) == len(selected):
return True
model.foreach(find_file, None)
log.debug("to_rename: %s", to_rename)
client.rename_files(self.torrent_id, to_rename)
client.rename_files(self.torrent_id, to_rename)

View file

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

View file

@ -2,19 +2,19 @@
# notification.py
#
# Copyright (C) 2008 Marcos Pinto ('markybob') <markybob@gmail.com>
#
#
# Deluge is free software.
#
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
@ -72,7 +72,7 @@ class Notification:
log.warning("pynotify is not installed")
else:
if pynotify.init("Deluge"):
self.note = pynotify.Notification(_("Torrent complete"),
self.note = pynotify.Notification(_("Torrent complete"),
status["name"] + "\n" + _("Including %i files" % status["num_files"]))
self.note.set_icon_from_pixbuf(common.get_logo(48))
if not self.note.show():
@ -99,7 +99,7 @@ class Notification:
"""sends email notification of finished torrent"""
import smtplib
headers = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % (
self.config["ntf_email_add"], self.config["ntf_email_add"],
self.config["ntf_email_add"], self.config["ntf_email_add"],
"Finished torrent %s" % (status["name"]))
text = _("This email is to inform you that Deluge has finished downloading %s , \
which includes %i files.\nTo stop receiving these alerts, simply turn off \

View file

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

View file

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

View file

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

View file

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

View file

@ -53,17 +53,17 @@ class SideBar(component.Component):
self.hpaned = glade.get_widget("hpaned")
self.config = ConfigManager("gtkui.conf")
#self.hpaned_position = self.hpaned.get_position()
# Tabs holds references to the Tab widgets by their name
self.tabs = {}
# Hide if necessary
self.visible(self.config["show_sidebar"])
def shutdown(self):
log.debug("hpaned.position: %s", self.hpaned.get_position())
self.config["sidebar_position"] = self.hpaned.get_position()
def visible(self, visible):
if visible:
if self.config["sidebar_position"]:

View file

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

View file

@ -2,19 +2,19 @@
# statusbar.py
#
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
#
#
# Deluge is free software.
#
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
@ -63,40 +63,40 @@ class StatusBarItem:
# Add text
if text != None:
self.set_text(text)
if callback != None:
self.set_callback(callback)
self.show_all()
def set_callback(self, callback):
self._ebox.connect("button-press-event", callback)
def show_all(self):
self._ebox.show()
self._hbox.show()
self._image.show()
self._label.show()
def set_image_from_file(self, image):
self._image.set_from_file(image)
def set_image_from_stock(self, stock):
self._image.set_from_stock(stock, gtk.ICON_SIZE_MENU)
def set_text(self, text):
if self._label.get_text() != text:
self._label.set_text(text)
def get_widgets(self):
return self._widgets()
def get_eventbox(self):
return self._ebox
def get_text(self):
return self._label.get_text()
class StatusBar(component.Component):
def __init__(self):
component.Component.__init__(self, "StatusBar", interval=3000)
@ -104,7 +104,7 @@ class StatusBar(component.Component):
self.statusbar = self.window.main_glade.get_widget("statusbar")
self.tooltips = gtk.Tooltips()
self.config = ConfigManager("gtkui.conf")
# Status variables that are updated via callback
self.max_connections = -1
self.num_connections = 0
@ -115,7 +115,7 @@ class StatusBar(component.Component):
self.dht_nodes = 0
self.dht_status = False
self.health = False
self.config_value_changed_dict = {
"max_connections_global": self._on_max_connections_global,
"max_download_speed": self._on_max_download_speed,
@ -132,11 +132,11 @@ class StatusBar(component.Component):
self.statusbar.show_all()
# Create the not connected item
self.not_connected_item = StatusBarItem(
stock=gtk.STOCK_STOP, text=_("Not Connected"),
stock=gtk.STOCK_STOP, text=_("Not Connected"),
callback=self._on_notconnected_item_clicked)
# Show the not connected status bar
self.show_not_connected()
# Hide if necessary
self.visible(self.config["show_statusbar"])
@ -169,7 +169,7 @@ class StatusBar(component.Component):
callback=self._on_health_icon_clicked)
self.health = False
# Get some config values
client.get_config_value(
self._on_max_connections_global, "max_connections_global")
@ -180,9 +180,9 @@ class StatusBar(component.Component):
client.get_config_value(
self._on_dht, "dht")
client.get_health(self._on_get_health)
self.send_status_request()
def stop(self):
# When stopped, we just show the not connected thingy
try:
@ -194,20 +194,20 @@ class StatusBar(component.Component):
self.remove_item(self.health_item)
except Exception, e:
log.debug("Unable to remove StatusBar item: %s", e)
self.show_not_connected()
self.show_not_connected()
def visible(self, visible):
if visible:
self.statusbar.show()
else:
self.statusbar.hide()
self.config["show_statusbar"] = visible
def show_not_connected(self):
self.hbox.pack_start(
self.not_connected_item.get_eventbox(), expand=False, fill=False)
def add_item(self, image=None, stock=None, text=None, callback=None, tooltip=None):
"""Adds an item to the status bar"""
# The return tuple.. we return whatever widgets we add
@ -216,7 +216,7 @@ class StatusBar(component.Component):
if tooltip:
self.tooltips.set_tip(item.get_eventbox(), tooltip)
return item
def remove_item(self, item):
"""Removes an item from the statusbar"""
if item.get_eventbox() in self.hbox.get_children():
@ -230,7 +230,7 @@ class StatusBar(component.Component):
item = self.add_item(image, stock, text, callback)
# Start a timer to remove this item in seconds
gobject.timeout_add(seconds * 1000, self.remove_item, item)
def display_warning(self, text, callback=None):
"""Displays a warning to the user in the status bar"""
if text not in self.current_warnings:
@ -238,16 +238,16 @@ class StatusBar(component.Component):
stock=gtk.STOCK_DIALOG_WARNING, text=text, callback=callback)
self.current_warnings.append(text)
gobject.timeout_add(3000, self.remove_warning, item)
def remove_warning(self, item):
self.current_warnings.remove(item.get_text())
self.remove_item(item)
def clear_statusbar(self):
def remove(child):
self.hbox.remove(child)
self.hbox.foreach(remove)
def send_status_request(self):
# Sends an async request for data from the core
client.get_num_connections(self._on_get_num_connections)
@ -262,22 +262,22 @@ class StatusBar(component.Component):
def config_value_changed(self, key, value):
"""This is called when we received a config_value_changed signal from
the core."""
if key in self.config_value_changed_dict.keys():
self.config_value_changed_dict[key](value)
def _on_max_connections_global(self, max_connections):
self.max_connections = max_connections
self.update_connections_label()
def _on_get_num_connections(self, num_connections):
self.num_connections = num_connections
self.update_connections_label()
def _on_get_dht_nodes(self, dht_nodes):
self.dht_nodes = dht_nodes
self.update_dht_label()
def _on_dht(self, value):
self.dht_status = value
if value:
@ -290,7 +290,7 @@ class StatusBar(component.Component):
def _on_max_download_speed(self, max_download_speed):
self.max_download_speed = max_download_speed
self.update_download_label()
def _on_get_download_rate(self, download_rate):
self.download_rate = deluge.common.fsize(download_rate)
self.update_download_label()
@ -298,16 +298,16 @@ class StatusBar(component.Component):
def _on_max_upload_speed(self, max_upload_speed):
self.max_upload_speed = max_upload_speed
self.update_upload_label()
def _on_get_upload_rate(self, upload_rate):
self.upload_rate = deluge.common.fsize(upload_rate)
self.update_upload_label()
def _on_get_health(self, value):
self.health = value
if self.health:
self.remove_item(self.health_item)
self.remove_item(self.health_item)
def update_connections_label(self):
# Set the max connections label
if self.max_connections < 0:
@ -316,11 +316,11 @@ class StatusBar(component.Component):
label_string = "%s (%s)" % (self.num_connections, self.max_connections)
self.connections_item.set_text(label_string)
def update_dht_label(self):
# Set the max connections label
self.dht_item.set_text("%s" % (self.dht_nodes))
def update_download_label(self):
# Set the download speed label
if self.max_download_speed < 0:
@ -330,7 +330,7 @@ class StatusBar(component.Component):
self.download_rate, self.max_download_speed, _("KiB/s"))
self.download_item.set_text(label_string)
def update_upload_label(self):
# Set the upload speed label
if self.max_upload_speed < 0:
@ -338,16 +338,16 @@ class StatusBar(component.Component):
else:
label_string = "%s/s (%s %s)" % (
self.upload_rate, self.max_upload_speed, _("KiB/s"))
self.upload_item.set_text(label_string)
def update(self):
# Send status request
self.send_status_request()
def _on_download_item_clicked(self, widget, event):
menu = common.build_menu_radio_list(
self.config["tray_download_speed_list"],
self.config["tray_download_speed_list"],
self._on_set_download_speed,
self.max_download_speed,
_("KiB/s"), show_notset=True, show_other=True)
@ -368,14 +368,14 @@ class StatusBar(component.Component):
value = float(widget.get_children()[0].get_text().split(" ")[0])
log.debug("value: %s", value)
# Set the config in the core
# Set the config in the core
if value != self.max_download_speed:
client.set_config({"max_download_speed": value})
def _on_upload_item_clicked(self, widget, event):
menu = common.build_menu_radio_list(
self.config["tray_upload_speed_list"],
self.config["tray_upload_speed_list"],
self._on_set_upload_speed,
self.max_upload_speed,
_("KiB/s"), show_notset=True, show_other=True)
@ -396,22 +396,22 @@ class StatusBar(component.Component):
value = float(widget.get_children()[0].get_text().split(" ")[0])
log.debug("value: %s", value)
# Set the config in the core
if value != self.max_upload_speed:
client.set_config({"max_upload_speed": value})
def _on_connection_item_clicked(self, widget, event):
menu = common.build_menu_radio_list(
self.config["connection_limit_list"],
self.config["connection_limit_list"],
self._on_set_connection_limit,
self.max_connections, show_notset=True, show_other=True)
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
def _on_set_connection_limit(self, widget):
log.debug("_on_set_connection_limit")
if widget.get_name() == _("Unlimited"):
value = -1
elif widget.get_name() == _("Other..."):
@ -423,11 +423,11 @@ class StatusBar(component.Component):
value = int(widget.get_children()[0].get_text().split(" ")[0])
log.debug("value: %s", value)
# Set the config in the core
# Set the config in the core
if value != self.max_connections:
client.set_config({"max_connections_global": value})
def _on_health_icon_clicked(self, widget, event):
component.get("Preferences").show("Network")

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -241,7 +241,7 @@ class json_rpc:
class json_upload:
def GET(self):
pass
@deco.check_session
def POST(self, name=None):
import os
@ -254,7 +254,7 @@ class json_upload:
shutil.copyfileobj(vars.torrentFile.file, tmp_file)
tmp_file.close()
print path
def register():
component.get("PageManager").register_page("/json/rpc",json_rpc)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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