Change the gtkui to a new component based system.

Properly handle daemon quitting unexpectedly.
Many updates to the ConnectionManager.
This commit is contained in:
Andrew Resch 2007-10-20 22:26:43 +00:00
parent d5888131a0
commit c4d4e75667
16 changed files with 561 additions and 191 deletions

View File

@ -74,7 +74,7 @@ DEFAULT_PREFS = {
"max_upload_slots_global": -1,
"max_connections_per_torrent": -1,
"max_upload_slots_per_torrent": -1,
"enabled_plugins": ["Queue"]
"enabled_plugins": []
}
class Core(

View File

@ -65,7 +65,7 @@ class PluginManagerBase:
def shutdown(self):
log.debug("PluginManager shutting down..")
for plugin in self.plugins.values():
plugin.core.shutdown()
plugin.disable()
del self.plugins
def __getitem__(self, key):
@ -113,9 +113,11 @@ class PluginManagerBase:
def disable_plugin(self, name):
"""Disables a plugin"""
try:
del self.plugins[name]
except:
log.warning("Unable to disable non-existant plugin %s", name)
self.plugins[name].disable()
del self.plugins[name]
# except:
# log.warning("Unable to disable non-existant plugin %s", name)
log.info("Plugin %s disabled..", name)

View File

@ -33,6 +33,7 @@
import os.path
import pickle
import socket
import deluge.xmlrpclib as xmlrpclib
@ -48,17 +49,32 @@ class CoreProxy:
self._uri = None
self._core = None
self._on_new_core_callbacks = []
self._on_no_core_callbacks = []
def connect_on_new_core(self, callback):
"""Connect a callback to be called when a new core is connected to."""
self._on_new_core_callbacks.append(callback)
def connect_on_no_core(self, callback):
"""Connect a callback to be called when the current core is disconnected
from."""
self._on_no_core_callbacks.append(callback)
def set_core_uri(self, uri):
log.info("Setting core uri as %s", uri)
self._uri = uri
# Get a new core
self.get_core()
if self._uri == None:
for callback in self._on_no_core_callbacks:
callback()
self._core = None
else:
# Get a new core
self.get_core()
def get_core_uri(self):
"""Returns the URI of the core currently being used."""
return self._uri
def get_core(self):
if self._core is None and self._uri is not None:
log.debug("Creating ServerProxy..")
@ -88,13 +104,24 @@ def connect_on_new_core(callback):
"""Connect a callback whenever a new core is connected to."""
return _core.connect_on_new_core(callback)
def connect_on_no_core(callback):
"""Connect a callback whenever the core is disconnected from."""
return _core.connect_on_no_core(callback)
def set_core_uri(uri):
"""Sets the core uri"""
return _core.set_core_uri(uri)
def get_core_uri():
"""Get the core URI"""
return _core.get_core_uri()
def shutdown():
"""Shutdown the core daemon"""
get_core().shutdown()
try:
get_core().shutdown()
except (AttributeError, socket.error):
set_core_uri(None)
def add_torrent_file(torrent_files):
"""Adds torrent files to the core
@ -112,8 +139,12 @@ def add_torrent_file(torrent_files):
(path, filename) = os.path.split(torrent_file)
fdump = xmlrpclib.Binary(f.read())
f.close()
result = get_core().add_torrent_file(filename, str(), fdump)
try:
result = get_core().add_torrent_file(filename, str(), fdump)
except (AttributeError, socket.error):
set_core_uri(None)
result = False
if result is False:
# The torrent was not added successfully.
log.warning("Torrent %s was not added successfully.", filename)
@ -122,7 +153,12 @@ def add_torrent_url(torrent_url):
"""Adds torrents to the core via url"""
from deluge.common import is_url
if is_url(torrent_url):
result = get_core().add_torrent_url(torrent_url, str())
try:
result = get_core().add_torrent_url(torrent_url, str())
except (AttributeError, socket.error):
set_core_uri(None)
result = False
if result is False:
# The torrent url was not added successfully.
log.warning("Torrent %s was not added successfully.", torrent_url)
@ -132,62 +168,125 @@ def add_torrent_url(torrent_url):
def remove_torrent(torrent_ids):
"""Removes torrent_ids from the core.. Expects a list of torrent_ids"""
log.debug("Attempting to removing torrents: %s", torrent_ids)
for torrent_id in torrent_ids:
get_core().remove_torrent(torrent_id)
try:
for torrent_id in torrent_ids:
get_core().remove_torrent(torrent_id)
except (AttributeError, socket.error):
set_core_uri(None)
def pause_torrent(torrent_ids):
"""Pauses torrent_ids"""
for torrent_id in torrent_ids:
get_core().pause_torrent(torrent_id)
try:
for torrent_id in torrent_ids:
get_core().pause_torrent(torrent_id)
except (AttributeError, socket.error):
set_core_uri(None)
def resume_torrent(torrent_ids):
"""Resume torrent_ids"""
for torrent_id in torrent_ids:
get_core().resume_torrent(torrent_id)
try:
for torrent_id in torrent_ids:
get_core().resume_torrent(torrent_id)
except (AttributeError, socket.error):
set_core_uri(None)
def force_reannounce(torrent_ids):
"""Reannounce to trackers"""
for torrent_id in torrent_ids:
get_core().force_reannounce(torrent_id)
try:
for torrent_id in torrent_ids:
get_core().force_reannounce(torrent_id)
except (AttributeError, socket.error):
set_core_uri(None)
def get_torrent_status(torrent_id, keys):
"""Builds the status dictionary and returns it"""
status = get_core().get_torrent_status(torrent_id, keys)
try:
status = get_core().get_torrent_status(torrent_id, keys)
except (AttributeError, socket.error):
set_core_uri(None)
return {}
return pickle.loads(status.data)
def get_session_state():
return get_core().get_session_state()
try:
state = get_core().get_session_state()
except (AttributeError, socket.error):
set_core_uri(None)
state = []
return state
def get_config():
return get_core().get_config()
try:
config = get_core().get_config()
except (AttributeError, socket.error):
set_core_uri(None)
config = {}
return config
def get_config_value(key):
return get_core().get_config_value(key)
try:
config_value = get_core().get_config_value(key)
except (AttributeError, socket.error):
set_core_uri(None)
config_value = None
return config_value
def set_config(config):
if config == {}:
return
get_core().set_config(config)
try:
get_core().set_config(config)
except (AttributeError, socket.error):
set_core_uri(None)
def get_listen_port():
return int(get_core().get_listen_port())
try:
port = get_core().get_listen_port()
except (AttributeError, socket.error):
set_core_uri(None)
port = 0
return int(port)
def get_available_plugins():
return get_core().get_available_plugins()
try:
available = get_core().get_available_plugins()
except (AttributeError, socket.error):
set_core_uri(None)
available = []
return available
def get_enabled_plugins():
return get_core().get_enabled_plugins()
try:
enabled = get_core().get_enabled_plugins()
except (AttributeError, socket.error):
set_core_uri(None)
enabled = []
return enabled
def get_download_rate():
return get_core().get_download_rate()
try:
rate = get_core().get_download_rate()
except (AttributeError, socket.error):
set_core_uri(None)
rate = -1
return rate
def get_upload_rate():
return get_core().get_upload_rate()
try:
rate = get_core().get_upload_rate()
except (AttributeError, socket.error):
set_core_uri(None)
rate = -1
return rate
def get_num_connections():
return get_core().get_num_connections()
try:
num_connections = get_core().get_num_connections()
except (AttributeError, socket.error):
set_core_uri(None)
num_connections = 0
return num_connections
def open_url_in_browser(url):
"""Opens link in the desktop's default browser"""
def start_browser():

131
deluge/ui/component.py Normal file
View File

@ -0,0 +1,131 @@
#
# component.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 2 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 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
import gobject
from deluge.log import LOG as log
COMPONENT_STATE = [
"Stopped",
"Started"
]
class Component:
def __init__(self, name):
# Register with the ComponentRegistry
register(name, self)
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")
def stop(self):
pass
def _stop(self):
self._state = COMPONENT_STATE.index("Stopped")
def update(self):
pass
class ComponentRegistry:
def __init__(self):
self.components = {}
#self.component_state = {}
self.update_timer = None
def register(self, name, obj):
"""Registers a component"""
log.debug("Registered %s with ComponentRegistry..", name)
self.components[name] = obj
def get(self, name):
"""Returns a reference to the component 'name'"""
return self.components[name]
def start(self, update_interval=1000):
"""Starts all components"""
for component in self.components.keys():
self.components[component].start()
self.components[component]._start()
# Start the update timer
self.update_timer = gobject.timeout_add(update_interval, self.update)
# Do an update right away
self.update()
def stop(self):
"""Stops all components"""
for component in self.components.keys():
self.components[component].stop()
self.components[component]._stop()
# Stop the update timer
gobject.source_remove(self.update_timer)
def update(self):
"""Updates all components"""
for component in self.components.keys():
# Only update the component if it's started
if self.components[component].get_state() == \
COMPONENT_STATE.index("Started"):
self.components[component].update()
return True
_ComponentRegistry = ComponentRegistry()
def register(name, obj):
"""Registers a UI component with the registry"""
_ComponentRegistry.register(name, obj)
def start():
"""Starts all components"""
_ComponentRegistry.start()
def stop():
"""Stops all components"""
_ComponentRegistry.stop()
def update():
"""Updates all components"""
_ComponentRegistry.update()
def get(component):
"""Return a reference to the component"""
return _ComponentRegistry.get(component)

View File

@ -36,6 +36,7 @@ import pkg_resources
import gobject
import socket
import deluge.ui.component as component
import deluge.xmlrpclib as xmlrpclib
import deluge.common
import deluge.ui.client as client
@ -46,14 +47,24 @@ DEFAULT_CONFIG = {
"hosts": ["localhost:58846"]
}
class ConnectionManager:
def __init__(self, window):
HOSTLIST_COL_PIXBUF = 0
HOSTLIST_COL_URI = 1
HOSTLIST_COL_STATUS = 2
HOSTLIST_STATUS = [
"Offline",
"Online",
"Connected"
]
class ConnectionManager(component.Component):
def __init__(self):
component.Component.__init__(self, "ConnectionManager")
# Get the glade file for the connection manager
self.glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/connection_manager.glade"))
self.window = window
self.window = component.get("MainWindow")
self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG)
self.connection_manager = self.glade.get_widget("connection_manager")
self.hostlist = self.glade.get_widget("hostlist")
@ -62,20 +73,21 @@ class ConnectionManager:
self.glade.get_widget("image1").set_from_pixbuf(
deluge.common.get_logo(32))
self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str)
self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, int)
# Fill in hosts from config file
for host in self.config["hosts"]:
row = self.liststore.append()
self.liststore.set_value(row, 1, host)
self.liststore.set_value(row, HOSTLIST_COL_URI, host)
# Setup host list treeview
self.hostlist.set_model(self.liststore)
render = gtk.CellRendererPixbuf()
column = gtk.TreeViewColumn("Status", render, pixbuf=0)
column = gtk.TreeViewColumn(
"Status", render, pixbuf=HOSTLIST_COL_PIXBUF)
self.hostlist.append_column(column)
render = gtk.CellRendererText()
column = gtk.TreeViewColumn("Host", render, text=1)
column = gtk.TreeViewColumn("Host", render, text=HOSTLIST_COL_URI)
self.hostlist.append_column(column)
self.glade.signal_autoconnect({
@ -83,60 +95,126 @@ class ConnectionManager:
"on_button_removehost_clicked": self.on_button_removehost_clicked,
"on_button_startdaemon_clicked": \
self.on_button_startdaemon_clicked,
"on_button_cancel_clicked": self.on_button_cancel_clicked,
"on_button_close_clicked": self.on_button_close_clicked,
"on_button_connect_clicked": self.on_button_connect_clicked,
})
self.connection_manager.connect("delete-event", self.on_delete_event)
# Connect to the 'changed' event of TreeViewSelection to get selection
# changes.
self.hostlist.get_selection().connect("changed",
self.on_selection_changed)
def show(self):
self.update_timer = gobject.timeout_add(5000, self.update)
self.update()
self._update_timer = gobject.timeout_add(5000, self._update)
self._update()
self.connection_manager.show_all()
def hide(self):
self.connection_manager.hide()
gobject.source_remove(self.update_timer)
gobject.source_remove(self._update_timer)
def update(self):
def _update(self):
"""Updates the host status"""
def update_row(model=None, path=None, row=None, columns=None):
uri = model.get_value(row, 1)
uri = model.get_value(row, HOSTLIST_COL_URI)
uri = "http://" + uri
online = True
host = None
try:
host = xmlrpclib.ServerProxy(uri)
host.ping()
except socket.error:
print "socket.error!"
online = False
online = self.test_online_status(uri)
print "online: ", online
del host
if online:
image = gtk.STOCK_YES
online = HOSTLIST_STATUS.index("Online")
else:
image = gtk.STOCK_NO
online = HOSTLIST_STATUS.index("Offline")
if uri == current_uri:
# We are connected to this host, so lets display the connected
# icon.
image = gtk.STOCK_CONNECT
online = HOSTLIST_STATUS.index("Connected")
pixbuf = self.connection_manager.render_icon(
image, gtk.ICON_SIZE_MENU)
model.set_value(row, 0, pixbuf)
self.liststore.foreach(update_row)
return True
model.set_value(row, HOSTLIST_COL_PIXBUF, pixbuf)
model.set_value(row, HOSTLIST_COL_STATUS, online)
current_uri = client.get_core_uri()
self.liststore.foreach(update_row)
# Update the buttons
self.update_buttons()
return True
def update_buttons(self):
"""Updates the buttons based on selection"""
# Get the selected row's URI
paths = self.hostlist.get_selection().get_selected_rows()[1]
# If nothing is selected, just return
if len(paths) < 1:
return
row = self.liststore.get_iter(paths[0])
uri = self.liststore.get_value(row, HOSTLIST_COL_URI)
status = self.liststore.get_value(row, HOSTLIST_COL_STATUS)
# Check to see if a localhost is selected
localhost = False
if uri.split(":")[0] == "localhost" or uri.split(":")[0] == "127.0.0.1":
localhost = True
# Make actual URI string
uri = "http://" + uri
# See if this is the currently connected URI
if status == HOSTLIST_STATUS.index("Connected"):
# Display a disconnect button if we're connected to this host
self.glade.get_widget("button_connect").set_label("gtk-disconnect")
else:
self.glade.get_widget("button_connect").set_label("gtk-connect")
# Update the start daemon button if the selected host is localhost
if localhost:
# Check to see if the host is online
if status == HOSTLIST_STATUS.index("Connected") \
or status == HOSTLIST_STATUS.index("Online"):
self.glade.get_widget("image_startdaemon").set_from_stock(
gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
self.glade.get_widget("label_startdaemon").set_text(
"_Stop local daemon")
else:
# The localhost is not online
self.glade.get_widget("image_startdaemon").set_from_stock(
gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
self.glade.get_widget("label_startdaemon").set_text(
"_Start local daemon")
# Make sure label is displayed correctly using mnemonics
self.glade.get_widget("label_startdaemon").set_use_underline(
True)
def save(self):
"""Save the current host list to file"""
def append_row(model=None, path=None, row=None, columns=None):
hostlist.append(model.get_value(row, 1))
hostlist.append(model.get_value(row, HOSTLIST_COL_URI))
hostlist = []
self.liststore.foreach(append_row, hostlist)
self.config["hosts"] = hostlist
self.config.save()
def test_online_status(self, uri):
"""Tests the status of URI.. Returns True or False depending on status.
"""
online = True
host = None
try:
host = xmlrpclib.ServerProxy(uri)
host.ping()
except socket.error:
online = False
del host
return online
## Callbacks
def on_delete_event(self, widget, event):
@ -165,9 +243,11 @@ class ConnectionManager:
port = port_spinbutton.get_value_as_int()
hostname = hostname + ":" + str(port)
row = self.liststore.append()
self.liststore.set_value(row, 1, hostname)
self.liststore.set_value(row, HOSTLIST_COL_URI, hostname)
# Save the host list to file
self.save()
# Update the status of the hosts
self._update()
dialog.hide()
@ -184,16 +264,36 @@ class ConnectionManager:
def on_button_startdaemon_clicked(self, widget):
log.debug("on_button_startdaemon_clicked")
def on_button_cancel_clicked(self, widget):
log.debug("on_button_cancel_clicked")
def on_button_close_clicked(self, widget):
log.debug("on_button_close_clicked")
self.hide()
def on_button_connect_clicked(self, widget):
log.debug("on_button_connect_clicked")
paths = self.hostlist.get_selection().get_selected_rows()[1]
row = self.liststore.get_iter(paths[0])
uri = self.liststore.get_value(row, 1)
status = self.liststore.get_value(row, HOSTLIST_COL_STATUS)
uri = self.liststore.get_value(row, HOSTLIST_COL_URI)
uri = "http://" + uri
if status == HOSTLIST_STATUS.index("Connected"):
# If we are connected to this host, then we will disconnect.
client.set_core_uri(None)
self._update()
return
# Test the host to see if it is online or not. We don't use the status
# column information because it can be up to 5 seconds out of sync.
if not self.test_online_status(uri):
log.warning("Host does not appear to be online..")
# Update the list to show proper status
self._update()
return
# Status is OK, so lets change to this host
client.set_core_uri(uri)
self.window.start()
self.hide()
def on_selection_changed(self, treeselection):
log.debug("on_selection_changed")
self.update_buttons()

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.2.2 on Tue Oct 16 23:05:06 2007 by andrew@fragment-->
<!--Generated with glade3 3.2.0 on Sat Oct 20 14:16:39 2007 by andrew@delicious-->
<glade-interface>
<widget class="GtkDialog" id="connection_manager">
<property name="has_focus">True</property>
@ -99,7 +99,6 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">gtk-add</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_addhost_clicked"/>
</widget>
</child>
@ -111,7 +110,6 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">gtk-remove</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_removehost_clicked"/>
</widget>
<packing>
@ -130,7 +128,6 @@
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_startdaemon_clicked"/>
<child>
<widget class="GtkHBox" id="hbox4">
@ -138,14 +135,14 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">2</property>
<child>
<widget class="GtkImage" id="image2">
<widget class="GtkImage" id="image_startdaemon">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-execute</property>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label5">
<widget class="GtkLabel" id="label_startdaemon">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">_Start local daemon</property>
@ -193,7 +190,6 @@
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">checkbutton</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property>
</widget>
</child>
@ -221,15 +217,14 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="button_cancel">
<widget class="GtkButton" id="button_close">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">gtk-cancel</property>
<property name="label" translatable="yes">gtk-close</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_cancel_clicked"/>
<signal name="clicked" handler="on_button_close_clicked"/>
</widget>
</child>
<child>
@ -240,7 +235,6 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">gtk-connect</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_connect_clicked"/>
</widget>
<packing>
@ -344,7 +338,6 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
</child>
<child>

View File

@ -38,7 +38,17 @@ import gettext
import locale
import pkg_resources
import deluge.ui.component as component
import deluge.ui.client as client
from mainwindow import MainWindow
from menubar import MenuBar
from toolbar import ToolBar
from torrentview import TorrentView
from torrentdetails import TorrentDetails
from preferences import Preferences
from systemtray import SystemTray
from statusbar import StatusBar
from connectionmanager import ConnectionManager
from signals import Signals
from pluginmanager import PluginManager
from deluge.configmanager import ConfigManager
@ -67,7 +77,7 @@ DEFAULT_PREFS = {
"window_pane_position": -1,
"tray_download_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0],
"tray_upload_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0],
"enabled_plugins": ["Queue"]
"enabled_plugins": []
}
class GtkUI:
@ -88,19 +98,31 @@ class GtkUI:
# Make sure gtkui.conf has at least the defaults set
config = ConfigManager("gtkui.conf", DEFAULT_PREFS)
# Initialize the main window
self.mainwindow = MainWindow()
# We make sure that the UI components start once we get a core URI
client.connect_on_new_core(component.start)
client.connect_on_no_core(component.stop)
# Initialize various components of the gtkui
self.mainwindow = MainWindow()
self.menubar = MenuBar()
self.toolbar = ToolBar()
self.torrentview = TorrentView()
self.torrentdetails = TorrentDetails()
self.preferences = Preferences()
self.systemtray = SystemTray()
self.statusbar = StatusBar()
self.connectionmanager = ConnectionManager()
# Start the signal receiver
#self.signal_receiver = Signals(self)
# Initalize the plugins
self.plugins = PluginManager(self)
# Start the mainwindow and show it
#self.mainwindow.start()
# Show the connection manager
self.connectionmanager.show()
# Start the gtk main loop
gtk.gdk.threads_init()
gtk.main()
@ -113,6 +135,12 @@ class GtkUI:
# Clean-up
del self.mainwindow
del self.systemtray
del self.menubar
del self.toolbar
del self.torrentview
del self.torrentdetails
del self.preferences
# del self.signal_receiver
del self.plugins
del deluge.configmanager

View File

@ -38,21 +38,16 @@ import gobject
import pkg_resources
import deluge.ui.client as client
import deluge.ui.component as component
from deluge.configmanager import ConfigManager
from menubar import MenuBar
from toolbar import ToolBar
from torrentview import TorrentView
from torrentdetails import TorrentDetails
from preferences import Preferences
from systemtray import SystemTray
from statusbar import StatusBar
from connectionmanager import ConnectionManager
import deluge.common
from deluge.log import LOG as log
class MainWindow:
class MainWindow(component.Component):
def __init__(self):
component.Component.__init__(self, "MainWindow")
self.config = ConfigManager("gtkui.conf")
# Get the glade file for the main window
self.main_glade = gtk.glade.XML(
@ -74,38 +69,13 @@ class MainWindow:
self.window.connect("configure-event", self.on_window_configure_event)
self.window.connect("delete-event", self.on_window_delete_event)
self.vpaned.connect("notify::position", self.on_vpaned_position_event)
# Initialize various components of the gtkui
self.menubar = MenuBar(self)
self.toolbar = ToolBar(self)
self.torrentview = TorrentView(self)
self.torrentdetails = TorrentDetails(self)
self.preferences = Preferences(self)
self.systemtray = SystemTray(self)
self.statusbar = StatusBar(self)
self.connectionmanager = ConnectionManager(self)
client.connect_on_new_core(self.start)
if not(self.config["start_in_tray"] and \
self.config["enable_system_tray"]) and not \
self.window.get_property("visible"):
log.debug("Showing window")
self.show()
self.connectionmanager.show()
def start(self):
"""Start the update thread and show the window"""
self.torrentview.start()
self.update_timer = gobject.timeout_add(1000, self.update)
def update(self):
# Don't update the UI if the the window is minimized.
if self.is_minimized == True:
return True
self.torrentview.update()
self.torrentdetails.update()
self.statusbar.update()
return True
def show(self):
# Load the state prior to showing
self.load_window_state()
@ -126,17 +96,6 @@ class MainWindow:
return self.window.get_property("visible")
def quit(self):
# Stop the update timer from running
try:
gobject.source_remove(self.update_timer)
except:
pass
del self.systemtray
del self.menubar
del self.toolbar
del self.torrentview
del self.torrentdetails
del self.preferences
del self.config
self.hide()
gtk.main_quit()

View File

@ -36,14 +36,16 @@ pygtk.require('2.0')
import gtk, gtk.glade
import pkg_resources
import deluge.ui.component as component
import deluge.ui.client as client
from deluge.log import LOG as log
class MenuBar:
def __init__(self, window):
class MenuBar(component.Component):
def __init__(self):
log.debug("MenuBar init..")
self.window = window
component.Component.__init__(self, "MenuBar")
self.window = component.get("MainWindow")
# Get the torrent menu from the glade file
torrentmenu_glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui",
@ -124,27 +126,27 @@ class MenuBar:
## Edit Menu ##
def on_menuitem_preferences_activate(self, data=None):
log.debug("on_menuitem_preferences_activate")
self.window.preferences.show()
component.get("Preferences").show()
def on_menuitem_connectionmanager_activate(self, data=None):
log.debug("on_menuitem_connectionmanager_activate")
self.window.connectionmanager.show()
component.get("ConnectionManager").show()
## Torrent Menu ##
def on_menuitem_pause_activate(self, data=None):
log.debug("on_menuitem_pause_activate")
client.pause_torrent(
self.window.torrentview.get_selected_torrents())
component.get("TorrentView").get_selected_torrents())
def on_menuitem_resume_activate(self, data=None):
log.debug("on_menuitem_resume_activate")
client.resume_torrent(
self.window.torrentview.get_selected_torrents())
component.get("TorrentView").get_selected_torrents())
def on_menuitem_updatetracker_activate(self, data=None):
log.debug("on_menuitem_updatetracker_activate")
client.force_reannounce(
self.window.torrentview.get_selected_torrents())
component.get("TorrentView").get_selected_torrents())
def on_menuitem_edittrackers_activate(self, data=None):
log.debug("on_menuitem_edittrackers_activate")
@ -152,7 +154,7 @@ class MenuBar:
def on_menuitem_remove_activate(self, data=None):
log.debug("on_menuitem_remove_activate")
client.remove_torrent(
self.window.torrentview.get_selected_torrents())
component.get("TorrentView").get_selected_torrents())
## View Menu ##
def on_menuitem_toolbar_toggled(self, data=None):

View File

@ -31,6 +31,7 @@
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
import deluge.ui.component as component
import deluge.pluginmanagerbase
import deluge.ui.client as client
from deluge.configmanager import ConfigManager
@ -58,20 +59,25 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase):
def get_torrentview(self):
"""Returns a reference to the torrentview component"""
return self._gtkui.mainwindow.torrentview
#return self._gtkui.mainwindow.torrentview
return component.get("TorrentView")
def get_toolbar(self):
"""Returns a reference to the toolbar component"""
return self._gtkui.mainwindow.toolbar
# return self._gtkui.mainwindow.toolbar
return component.get("ToolBar")
def get_menubar(self):
"""Returns a reference to the menubar component"""
return self._gtkui.mainwindow.menubar
# return self._gtkui.mainwindow.menubar
return component.get("MenuBar")
def get_torrentmenu(self):
"""Returns a reference to the torrentmenu component"""
return self._gtkui.mainwindow.menubar.torrentmenu
# return self._gtkui.mainwindow.menubar.torrentmenu
return component.get("MenuBar").torrentmenu
def get_selected_torrents(self):
"""Returns a list of the selected torrent_ids"""
return self._gtkui.mainwindow.torrentview.get_selected_torrents()
# return self._gtkui.mainwindow.torrentview.get_selected_torrents()
return component.get("TorrentView").get_selected_torrents()

View File

@ -36,14 +36,16 @@ pygtk.require('2.0')
import gtk, gtk.glade
import pkg_resources
import deluge.ui.component as component
from deluge.log import LOG as log
import deluge.ui.client as client
import deluge.common
from deluge.configmanager import ConfigManager
class Preferences:
def __init__(self, window):
self.window = window
class Preferences(component.Component):
def __init__(self):
component.Component.__init__(self, "Preferences")
self.window = component.get("MainWindow")
self.glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/preferences_dialog.glade"))
@ -395,6 +397,10 @@ class Preferences:
name = self.plugin_liststore.get_value(row, 0)
value = self.plugin_liststore.get_value(row, 1)
self.plugin_liststore.set_value(row, 1, not value)
if not value:
functions.enable_plugin(name)
else:
functions.disable_plugin(name)
def on_plugin_selection_changed(self, treeselection):
log.debug("on_plugin_selection_changed")

View File

@ -33,12 +33,15 @@
import gtk
import deluge.ui.component as component
import deluge.common
import deluge.ui.client as client
from deluge.log import LOG as log
class StatusBar:
def __init__(self, window):
self.window = window
class StatusBar(component.Component):
def __init__(self):
component.Component.__init__(self, "StatusBar")
self.window = component.get("MainWindow")
self.statusbar = self.window.main_glade.get_widget("statusbar")
# Add a HBox to the statusbar after removing the initial label widget
@ -47,8 +50,13 @@ class StatusBar:
frame = self.statusbar.get_children()[0]
frame.remove(frame.get_children()[0])
frame.add(self.hbox)
# Show the not connected status bar
self.show_not_connected()
def start(self):
log.debug("StatusBar start..")
# Add in images and labels
self.clear_statusbar()
image = gtk.Image()
image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU)
self.hbox.pack_start(image, expand=False, fill=False)
@ -65,12 +73,28 @@ class StatusBar:
self.hbox.pack_start(image, expand=False, fill=False)
self.label_upload_speed = gtk.Label()
self.hbox.pack_start(self.label_upload_speed,
expand=False, fill=False)
expand=False, fill=False)
# Update once before showing
# self.update()
self.statusbar.show_all()
def stop(self):
# When stopped, we just show the not connected thingy
self.show_not_connected()
def show_not_connected(self):
self.clear_statusbar()
image = gtk.Image()
image.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
self.hbox.pack_start(image, expand=False, fill=False)
label = gtk.Label("Not connected to daemon..")
self.hbox.pack_start(label, expand=False, fill=False)
self.statusbar.show_all()
def clear_statusbar(self):
def remove(child):
self.hbox.remove(child)
self.hbox.foreach(remove)
def update(self):
# Set the max connections label
max_connections = client.get_config_value("max_connections_global")
@ -101,3 +125,4 @@ class StatusBar:
self.label_upload_speed.set_text("%s/s (%s)" % (
deluge.common.fsize(client.get_upload_rate()),
max_upload_speed))

View File

@ -34,14 +34,16 @@
import gtk
import pkg_resources
import deluge.ui.component as component
import deluge.ui.client as client
import deluge.common
from deluge.configmanager import ConfigManager
from deluge.log import LOG as log
class SystemTray:
def __init__(self, window):
self.window = window
class SystemTray(component.Component):
def __init__(self):
component.Component.__init__(self, "SystemTray")
self.window = component.get("MainWindow")
self.config = ConfigManager("gtkui.conf")
self.config.register_set_function("enable_system_tray",
self.on_enable_system_tray_set)
@ -79,6 +81,7 @@ class SystemTray:
deluge.common.get_pixmap("seeding16.png"))
def start(self):
log.debug("SystemTray start..")
# Build the bandwidth speed limit menus
self.build_tray_bwsetsubmenu()

View File

@ -35,12 +35,14 @@ import pygtk
pygtk.require('2.0')
import gtk, gtk.glade
import deluge.ui.component as component
from deluge.log import LOG as log
class ToolBar:
def __init__(self, window):
class ToolBar(component.Component):
def __init__(self):
component.Component.__init__(self, "ToolBar")
log.debug("ToolBar Init..")
self.window = window
self.window = component.get("MainWindow")
self.toolbar = self.window.main_glade.get_widget("toolbar")
### Connect Signals ###
self.window.main_glade.signal_autoconnect({
@ -94,34 +96,34 @@ class ToolBar:
def on_toolbutton_add_clicked(self, data):
log.debug("on_toolbutton_add_clicked")
# Use the menubar's callback
self.window.menubar.on_menuitem_addtorrent_activate(data)
component.get("MenuBar").on_menuitem_addtorrent_activate(data)
def on_toolbutton_remove_clicked(self, data):
log.debug("on_toolbutton_remove_clicked")
# Use the menubar's callbacks
self.window.menubar.on_menuitem_remove_activate(data)
component.get("MenuBar").on_menuitem_remove_activate(data)
def on_toolbutton_clear_clicked(self, data):
log.debug("on_toolbutton_clear_clicked")
# Use the menubar's callbacks
self.window.menubar.on_menuitem_clear_activate(data)
component.get("MenuBar").on_menuitem_clear_activate(data)
def on_toolbutton_pause_clicked(self, data):
log.debug("on_toolbutton_pause_clicked")
# Use the menubar's callbacks
self.window.menubar.on_menuitem_pause_activate(data)
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
self.window.menubar.on_menuitem_resume_activate(data)
component.get("MenuBar").on_menuitem_resume_activate(data)
def on_toolbutton_preferences_clicked(self, data):
log.debug("on_toolbutton_preferences_clicked")
# Use the menubar's callbacks
self.window.menubar.on_menuitem_preferences_activate(data)
component.get("MenuBar").on_menuitem_preferences_activate(data)
def on_toolbutton_plugins_clicked(self, data):
log.debug("on_toolbutton_plugins_clicked")
# Use the menubar's callbacks
self.window.menubar.on_menuitem_preferences_activate(data)
component.get("MenuBar").on_menuitem_preferences_activate(data)

View File

@ -38,13 +38,15 @@ pygtk.require('2.0')
import gtk, gtk.glade
import gettext
import deluge.ui.component as component
import deluge.ui.client as client
import deluge.common
from deluge.log import LOG as log
class TorrentDetails:
def __init__(self, window):
self.window = window
class TorrentDetails(component.Component):
def __init__(self):
component.Component.__init__(self, "TorrentDetails")
self.window = component.get("MainWindow")
glade = self.window.main_glade
self.notebook = glade.get_widget("torrent_info")
@ -71,12 +73,16 @@ class TorrentDetails:
self.eta = glade.get_widget("summary_eta")
self.torrent_path = glade.get_widget("summary_torrent_path")
def stop(self):
self.clear()
def update(self):
# Only update if this page is showing
if self.notebook.page_num(self.details_tab) is \
self.notebook.get_current_page():
# Get the first selected torrent
selected = self.window.torrentview.get_selected_torrents()
#selected = self.window.torrentview.get_selected_torrents()
selected = component.get("TorrentView").get_selected_torrents()
# Only use the first torrent in the list or return if None selected
if selected is not None:
@ -154,3 +160,4 @@ class TorrentDetails:
self.tracker_status.set_text("")
self.next_announce.set_text("")
self.eta.set_text("")
self.torrent_path.set_text("")

View File

@ -40,6 +40,7 @@ import gettext
import gobject
import deluge.common
import deluge.ui.component as component
import deluge.ui.client as client
from deluge.log import LOG as log
import deluge.ui.gtkui.listview as listview
@ -96,10 +97,11 @@ def cell_data_progress(column, cell, model, row, data):
textstr = textstr + " %.2f%%" % value
cell.set_property("text", textstr)
class TorrentView(listview.ListView):
class TorrentView(listview.ListView, component.Component):
"""TorrentView handles the listing of torrents."""
def __init__(self, window):
self.window = window
def __init__(self):
component.Component.__init__(self, "TorrentView")
self.window = component.get("MainWindow")
# Call the ListView constructor
listview.ListView.__init__(self,
self.window.main_glade.get_widget("torrent_view"))
@ -170,7 +172,12 @@ class TorrentView(listview.ListView):
session_state = client.get_session_state()
for torrent_id in session_state:
self.add_row(torrent_id)
def stop(self):
"""Stops the torrentview"""
# We need to clear the liststore
self.liststore.clear()
def update(self, columns=None):
"""Update the view. If columns is not None, it will attempt to only
update those columns selected.
@ -294,12 +301,12 @@ class TorrentView(listview.ListView):
# We only care about right-clicks
if event.button == 3:
# Show the Torrent menu from the MenuBar
torrentmenu = self.window.menubar.torrentmenu
torrentmenu = component.get("MenuBar").torrentmenu
torrentmenu.popup(None, None, None, event.button, event.time)
def on_selection_changed(self, treeselection):
"""This callback is know when the selection has changed."""
log.debug("on_selection_changed")
self.window.torrentdetails.update()
component.get("TorrentDetails").update()