mirror of
https://git.deluge-torrent.org/deluge
synced 2025-04-20 19:44:52 +00:00
Remove trailing whitspace from *.py
This commit is contained in:
parent
7813bd4098
commit
a0bf6d8fe1
62 changed files with 1138 additions and 1131 deletions
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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.,
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()\
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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) ]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) ]
|
||||
|
||||
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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("")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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"]:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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>▶</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)
|
|
@ -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))
|
||||
|
|
|
@ -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("<'&\\">")
|
||||
'<'&">'
|
||||
"""
|
||||
|
@ -135,7 +135,7 @@ def htmlquote(text):
|
|||
def websafe(val):
|
||||
"""
|
||||
Converts `val` so that it's safe for use in UTF-8 HTML.
|
||||
|
||||
|
||||
>>> websafe("<'&\\">")
|
||||
'<'&">'
|
||||
>>> websafe(None)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -991,7 +991,7 @@ class MultiCall:
|
|||
|
||||
def get_call_list(self):
|
||||
return self.__call_list
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# convenience functions
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue