summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--deluge/plugins/Stats/deluge/plugins/stats/common.py3
-rw-r--r--deluge/plugins/Stats/deluge/plugins/stats/core.py151
-rw-r--r--deluge/plugins/Stats/deluge/plugins/stats/data/config.glade265
-rw-r--r--deluge/plugins/Stats/deluge/plugins/stats/data/tabs.glade167
-rw-r--r--deluge/plugins/Stats/deluge/plugins/stats/graph.py281
-rw-r--r--deluge/plugins/Stats/deluge/plugins/stats/gtkui.py234
-rw-r--r--deluge/plugins/Stats/deluge/plugins/stats/template/graph.html12
-rw-r--r--deluge/plugins/Stats/deluge/plugins/stats/webui.py16
-rw-r--r--deluge/plugins/Stats/setup.py28
9 files changed, 873 insertions, 284 deletions
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/common.py b/deluge/plugins/Stats/deluge/plugins/stats/common.py
index 674d6f4b3..c5fd57250 100644
--- a/deluge/plugins/Stats/deluge/plugins/stats/common.py
+++ b/deluge/plugins/Stats/deluge/plugins/stats/common.py
@@ -36,5 +36,4 @@ import pkg_resources
import os.path
def get_resource(filename):
- return pkg_resources.resource_filename("deluge.plugins.stats",
- os.path.join("data", filename))
+ return pkg_resources.resource_filename("deluge.plugins.stats", os.path.join("data", filename))
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/core.py b/deluge/plugins/Stats/deluge/plugins/stats/core.py
index 8499d7de0..d1e977608 100644
--- a/deluge/plugins/Stats/deluge/plugins/stats/core.py
+++ b/deluge/plugins/Stats/deluge/plugins/stats/core.py
@@ -22,18 +22,7 @@
# 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.
-#
+# 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
@@ -44,21 +33,19 @@
# 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
-import logging
from twisted.internet.task import LoopingCall
import time
import deluge
+from deluge.log import LOG as log
from deluge.plugins.pluginbase import CorePluginBase
from deluge import component
from deluge import configmanager
from deluge.core.rpcserver import export
-log = logging.getLogger(__name__)
-
DEFAULT_PREFS = {
"test": "NiNiNi",
- "update_interval": 2, #2 seconds.
+ "update_interval": 1, #2 seconds.
"length": 150, # 2 seconds * 150 --> 5 minutes.
}
@@ -70,24 +57,55 @@ DEFAULT_TOTALS = {
"stats": {}
}
+def get_key(config, key):
+ try:
+ return config[key]
+ except KeyError:
+ return None
+
+def mean(items):
+ try:
+ return sum(items)/ len(items)
+ except Exception:
+ return 0
+
class Core(CorePluginBase):
totals = {} #class var to catch only updating this once per session in enable.
def enable(self):
+ log.debug("Stats plugin enabled")
self.core = component.get("Core")
self.stats ={}
+ self.count = {}
+ self.intervals = [1, 5, 30, 300]
+
+ self.last_update = {}
+ t = time.time()
+ for i in self.intervals:
+ self.stats[i] = {}
+ self.last_update[i] = t
+ self.count[i] = 0
+
self.config = configmanager.ConfigManager("stats.conf", DEFAULT_PREFS)
self.saved_stats = configmanager.ConfigManager("stats.totals", DEFAULT_TOTALS)
if self.totals == {}:
self.totals.update(self.saved_stats.config)
- self.stats = self.saved_stats["stats"] or {}
+ self.length = self.config["length"]
+
+ #self.stats = get_key(self.saved_stats, "stats") or {}
+ self.stats_keys = []
+ self.add_stats(
+ 'upload_rate',
+ 'download_rate',
+ 'num_connections',
+ 'dht_nodes',
+ 'dht_cache_nodes',
+ 'dht_torrents',
+ 'num_peers',
+ )
- self.stats_keys = [
- "payload_download_rate",
- "payload_upload_rate"
- ]
self.update_stats()
self.update_timer = LoopingCall(self.update_stats)
@@ -104,40 +122,94 @@ class Core(CorePluginBase):
except:
pass
+ def add_stats(self, *stats):
+ for stat in stats:
+ if stat not in self.stats_keys:
+ self.stats_keys.append(stat)
+ for i in self.intervals:
+ if stat not in self.stats[i]:
+ self.stats[i][stat] = []
+
def update_stats(self):
try:
- status = self.core.get_session_status(self.stats_keys)
- for key, value in status.items():
- if key not in self.stats:
- self.stats[key] = []
- self.stats[key].insert(0, value)
-
- for stat_list in self.stats.values():
- if len(stat_list) > self.config["length"]:
+ #Get all possible stats!
+ stats = {}
+ for key in self.stats_keys:
+ #try all keys we have, very inefficient but saves having to
+ #work out where a key comes from...
+ try:
+ stats.update(self.core.get_session_status([key]))
+ except AttributeError:
+ pass
+ stats["num_connections"] = self.core.get_num_connections()
+ stats.update(self.core.get_config_values(["max_download",
+ "max_upload",
+ "max_num_connections"]))
+ # status = self.core.session.status()
+ # for stat in dir(status):
+ # if not stat.startswith('_') and stat not in stats:
+ # stats[stat] = getattr(status, stat, None)
+
+ update_time = time.time()
+ self.last_update[1] = update_time
+
+ #extract the ones we are interested in
+ #adding them to the 1s array
+ for stat, stat_list in self.stats[1].iteritems():
+ if stat in stats:
+ stat_list.insert(0, int(stats[stat]))
+ else:
+ stat_list.insert(0, 0)
+ if len(stat_list) > self.length:
stat_list.pop()
- self.last_update = time.time()
+
+ def update_interval(interval, base, multiplier):
+ self.count[interval] = self.count[interval] + 1
+ if self.count[interval] >= interval:
+ self.last_update[interval] = update_time
+ self.count[interval] = 0
+ current_stats = self.stats[interval]
+ for stat, stat_list in self.stats[base].iteritems():
+ try:
+ avg = mean(stat_list[0:multiplier])
+ except ValueError:
+ avg = 0
+ current_stats[stat].insert(0, avg)
+ if len(current_stats[stat]) > self.length:
+ current_stats[stat].pop()
+
+ update_interval(5, 1, 5)
+ update_interval(30, 5, 6)
+ update_interval(300, 30, 10)
except Exception, e:
- log.exception(e)
+ log.error("Stats update error %s" % e)
+ return True
def save_stats(self):
try:
self.saved_stats["stats"] = self.stats
self.saved_stats.config.update(self.get_totals())
self.saved_stats.save()
- except Exception,e:
- log.exception(e)
+ except Exception, e:
+ log.error("Stats save error", e)
return True
# export:
@export
- def get_stats(self, keys):
+ def get_stats(self, keys, interval):
+ if interval not in self.intervals:
+ return None
+
stats_dict = {}
for key in keys:
- if key in self.stats:
- stats_dict[key] = self.stats[key]
- stats_dict["_last_update"] = self.last_update
+ if key in self.stats[interval]:
+ stats_dict[key] = self.stats[interval][key]
+
+ stats_dict["_last_update"] = self.last_update[interval]
+ stats_dict["_length"] = self.config["length"]
+ stats_dict["_update_interval"] = interval
return stats_dict
@export
@@ -169,3 +241,8 @@ class Core(CorePluginBase):
def get_config(self):
"returns the config dictionary"
return self.config.config
+
+ @export
+ def get_intervals(self):
+ "Returns the available resolutions"
+ return self.intervals
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/data/config.glade b/deluge/plugins/Stats/deluge/plugins/stats/data/config.glade
index e39b5204b..858acd767 100644
--- a/deluge/plugins/Stats/deluge/plugins/stats/data/config.glade
+++ b/deluge/plugins/Stats/deluge/plugins/stats/data/config.glade
@@ -1,24 +1,263 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.4.5 on Fri Aug 8 23:34:44 2008 -->
+<?xml version="1.0"?>
<glade-interface>
+ <!-- interface-requires gtk+ 2.16 -->
+ <!-- interface-naming-policy toplevel-contextual -->
<widget class="GtkWindow" id="window1">
<child>
- <widget class="GtkHBox" id="prefs_box">
+ <widget class="GtkVBox" id="prefs_box">
<property name="visible">True</property>
+ <property name="orientation">vertical</property>
<child>
- <widget class="GtkLabel" id="label1">
+ <widget class="GtkFrame" id="frame1">
<property name="visible">True</property>
- <property name="label" translatable="yes">Test config value:</property>
- </widget>
- </child>
- <child>
- <widget class="GtkEntry" id="txt_test">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="left_padding">15</property>
+ <child>
+ <widget class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="n_rows">10</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">15</property>
+ <child>
+ <widget class="GtkColorButton" id="bandwidth_graph_download_rate_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#000000000000</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Download color:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Upload color:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkColorButton" id="bandwidth_graph_upload_rate_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#000000000000</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Connections Graph&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Bandwidth Graph&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkColorButton" id="connections_graph_dht_nodes_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#000000000000</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">DHT nodes:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkColorButton" id="connections_graph_dht_cache_nodes_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#000000000000</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Cached DHT nodes:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">DHT torrents:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Connections:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkColorButton" id="connections_graph_dht_torrents_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#000000000000</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="x_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkColorButton" id="connections_graph_num_connections_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#000000000000</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ <property name="x_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Seeds / Peers&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">8</property>
+ <property name="bottom_attach">9</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkColorButton" id="seeds_graph_num_peers_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#000000000000</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">9</property>
+ <property name="bottom_attach">10</property>
+ <property name="x_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Peers:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">9</property>
+ <property name="bottom_attach">10</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Graph Colors&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
</widget>
<packing>
- <property name="position">1</property>
+ <property name="position">0</property>
</packing>
</child>
</widget>
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/data/tabs.glade b/deluge/plugins/Stats/deluge/plugins/stats/data/tabs.glade
index c3616a6a5..8732caeaf 100644
--- a/deluge/plugins/Stats/deluge/plugins/stats/data/tabs.glade
+++ b/deluge/plugins/Stats/deluge/plugins/stats/data/tabs.glade
@@ -1,7 +1,7 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.4.5 on Mon Oct 13 20:17:39 2008 -->
+<?xml version="1.0"?>
<glade-interface>
+ <!-- interface-requires gtk+ 2.6 -->
+ <!-- interface-naming-policy toplevel-contextual -->
<widget class="GtkWindow" id="window1">
<child>
<widget class="GtkVBox" id="vbox1">
@@ -14,6 +14,9 @@
<property name="visible">True</property>
<property name="stock">gtk-page-setup</property>
</widget>
+ <packing>
+ <property name="position">0</property>
+ </packing>
</child>
<child>
<widget class="GtkLabel" id="graph_label_text">
@@ -25,70 +28,120 @@
</packing>
</child>
</widget>
+ <packing>
+ <property name="position">0</property>
+ </packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="graph_tab">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
- <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
<child>
- <widget class="GtkNotebook" id="graph_notebook">
+ <widget class="GtkViewport" id="viewport1">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="tab_pos">GTK_POS_LEFT</property>
- <child>
- <widget class="GtkDrawingArea" id="bandwidth_graph">
- <property name="visible">True</property>
- </widget>
- </child>
- <child>
- <widget class="GtkLabel" id="bandwidth_label">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Bandwidth</property>
- </widget>
- <packing>
- <property name="type">tab</property>
- <property name="tab_fill">False</property>
- </packing>
- </child>
- <child>
- <widget class="GtkDrawingArea" id="connections_graph">
- <property name="visible">True</property>
- </widget>
- <packing>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <widget class="GtkLabel" id="connections_label">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Connections</property>
- </widget>
- <packing>
- <property name="type">tab</property>
- <property name="position">1</property>
- <property name="tab_fill">False</property>
- </packing>
- </child>
- <child>
- <widget class="GtkDrawingArea" id="seeds_graph">
- <property name="visible">True</property>
- </widget>
- <packing>
- <property name="position">2</property>
- </packing>
- </child>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
<child>
- <widget class="GtkLabel" id="seeds_label">
+ <widget class="GtkVBox" id="vbox2">
<property name="visible">True</property>
- <property name="label" translatable="yes">Seeds/Peers</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Resolution</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="combo_intervals">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkNotebook" id="graph_notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tab_pos">left</property>
+ <child>
+ <widget class="GtkDrawingArea" id="bandwidth_graph">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="bandwidth_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Bandwidth</property>
+ </widget>
+ <packing>
+ <property name="tab_fill">False</property>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkDrawingArea" id="connections_graph">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="connections_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Connections</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkDrawingArea" id="seeds_graph">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="seeds_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Seeds/Peers</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
</widget>
- <packing>
- <property name="type">tab</property>
- <property name="position">2</property>
- <property name="tab_fill">False</property>
- </packing>
</child>
</widget>
</child>
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/graph.py b/deluge/plugins/Stats/deluge/plugins/stats/graph.py
index 7f6a0548e..b5a265501 100644
--- a/deluge/plugins/Stats/deluge/plugins/stats/graph.py
+++ b/deluge/plugins/Stats/deluge/plugins/stats/graph.py
@@ -1,6 +1,7 @@
#
# graph.py
#
+# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# Copyright (C) Marcos Pinto 2007 <markybob@gmail.com>
@@ -21,7 +22,7 @@
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
-# Boston, MA 02110-1301, USA.
+# 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
@@ -32,23 +33,14 @@
# 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.
-#
-#
-# 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
"""
port of old plugin by markybob.
"""
import time
+import math
import cairo
-import logging
+from deluge.log import LOG as log
from deluge.ui.client import client
black = (0, 0, 0)
@@ -60,13 +52,18 @@ green = (0, 1.0, 0)
blue = (0, 0, 1.0)
orange = (1.0, 0.74, 0)
-log = logging.getLogger(__name__)
-
def default_formatter(value):
return str(value)
+def size_formatter_scale(value):
+ scale = 1.0
+ for i in range(0,3):
+ scale = scale * 1024.0
+ if value / scale < 1024:
+ return scale
+
def change_opacity(color, opactiy):
- """A method to assist in changing the opacity of a color in order to draw the
+ """A method to assist in changing the opactiy of a color inorder to draw the
fills.
"""
color = list(color)
@@ -83,6 +80,7 @@ class Graph:
self.length = 150
self.stat_info = {}
self.line_size = 2
+ self.dash_length = [10]
self.mean_selected = True
self.legend_selected = True
self.max_selected = True
@@ -105,125 +103,198 @@ class Graph:
def set_stats(self, stats):
self.last_update = stats["_last_update"]
- log.debug("Last update: %s" % self.last_update)
del stats["_last_update"]
+ self.length = stats["_length"]
+ del stats["_length"]
+ self.interval = stats["_update_interval"]
+ del stats["_update_interval"]
self.stats = stats
+ return
+
+ # def set_config(self, config):
+ # self.length = config["length"]
+ # self.interval = config["update_interval"]
- def set_config(self, config):
- self.length = config["length"]
- self.interval = config["update_interval"]
+ def set_interval(self, interval):
+ self.interval = interval
def draw_to_context(self, context, width, height):
self.ctx = context
self.width, self.height = width, height
- try:
- self.draw_rect(white, 0, 0, self.width, self.height)
- self.draw_x_axis()
- self.draw_left_axis()
-
- if self.legend_selected:
- self.draw_legend()
- except cairo.Error, e:
- log.exception(e)
+ self.draw_rect(white, 0, 0, self.width, self.height)
+ self.draw_graph()
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)
- self.draw_x_axis()
- self.draw_left_axis()
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ ctx = cairo.Context(surface)
+ self.draw_to_context(ctx, width, height)
+ return surface
- if self.legend_selected:
- self.draw_legend()
- return self.surface
- def draw_x_axis(self):
- duration = float(self.length * self.interval)
+ def draw_x_axis(self, bounds):
+ (left, top, right, bottom) = bounds
+ duration = self.length * self.interval
start = self.last_update - duration
- ratio = (self.width - 40) / duration
- seconds_to_minute = 60 - time.localtime(start)[5]
+ ratio = (right - left) / float(duration)
+
+ if duration < 1800 * 10:
+ #try rounding to nearest 1min, 5mins, 10mins, 30mins
+ for step in [60, 300, 600, 1800]:
+ if duration / step < 10:
+ x_step = step
+ break
+ else:
+ # if there wasnt anything useful find a nice fitting hourly divisor
+ x_step = ((duration / 5) /3600 )* 3600
- 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)
+ #this doesnt allow for dst and timezones...
+ seconds_to_step = math.ceil(start/float(x_step)) * x_step - start
- y = self.height - 22.5
- self.draw_dotted_line(gray, 60, y, int(self.width), y)
+ for i in xrange(0, duration/x_step + 1):
+ text = time.strftime('%H:%M', time.localtime(start + seconds_to_step + i*x_step))
+ # + 0.5 to allign x to nearest pixel
+ x = int(ratio * (seconds_to_step + i*x_step) + left) + 0.5
+ self.draw_x_text(text, x, bottom)
+ self.draw_dotted_line(gray, x, top-0.5, x, bottom+0.5)
- def draw_left_axis(self):
+ self.draw_line(gray, left, bottom+0.5, right, bottom+0.5)
+
+ def draw_graph(self):
+ font_extents = self.ctx.font_extents()
+ x_axis_space = font_extents[2] + 2 + self.line_size / 2.0
+ plot_height = self.height - x_axis_space
+ #lets say we need 2n-1*font height pixels to plot the y ticks
+ tick_limit = (plot_height / font_extents[3] )# / 2.0
+
+ max_value = 0
+ for stat in self.stat_info:
+ if self.stat_info[stat]['axis'] == 'left':
+ try:
+ l_max = max(self.stats[stat])
+ except ValueError:
+ l_max = 0
+ if l_max > max_value:
+ max_value = l_max
+ if max_value < self.left_axis['min']:
+ max_value = self.left_axis['min']
+
+ y_ticks = self.intervalise(max_value, tick_limit)
+ max_value = y_ticks[-1]
+ #find the width of the y_ticks
+ y_tick_text = [self.left_axis['formatter'](tick) for tick in y_ticks]
+ def space_required(text):
+ te = self.ctx.text_extents(text)
+ return math.ceil(te[4] - te[0])
+ y_tick_width = max((space_required(text) for text in y_tick_text))
+
+ top = font_extents[2] / 2.0
+ #bounds(left, top, right, bottom)
+ bounds = (y_tick_width + 4, top + 2, self.width, self.height - x_axis_space)
+
+ self.draw_x_axis(bounds)
+ self.draw_left_axis(bounds, y_ticks, y_tick_text)
+
+ def intervalise(self, x, limit=None):
+ """Given a value x create an array of tick points to got with the graph
+ The number of ticks returned can be constrained by limit, minimum of 3
+ """
+ #Limit is the number of ticks which is 1 + the number of steps as we
+ #count the 0 tick in limit
+ if limit is not None:
+ if limit <3:
+ limit = 2
+ else:
+ limit = limit -1
+ scale = 1
+ if 'formatter_scale' in self.left_axis:
+ scale = self.left_axis['formatter_scale'](x)
+ x = x / float(scale)
+
+ #Find the largest power of 10 less than x
+ log = math.log10(x)
+ intbit = math.floor(log)
+
+ interval = math.pow(10, intbit)
+ steps = int(math.ceil(x / interval))
+
+ if steps <= 1 and (limit is None or limit >= 10*steps):
+ interval = interval * 0.1
+ steps = steps * 10
+ elif steps <= 2 and (limit is None or limit >= 5*steps):
+ interval = interval * 0.2
+ steps = steps * 5
+ elif steps <=5 and (limit is None or limit >= 2*steps):
+ interval = interval * 0.5
+ steps = steps * 2
+
+ if limit is not None and steps > limit:
+ multi = steps / float(limit)
+ if multi > 2:
+ interval = interval * 5
+ else:
+ interval = interval * 2
+
+ intervals = [i * interval * scale for i in xrange(1+int(math.ceil(x/ interval)))]
+ return intervals
+
+ def draw_left_axis(self, bounds, y_ticks, y_tick_text):
+ (left, top, right, bottom) = bounds
stats = {}
- max_values = []
for stat in self.stat_info:
if self.stat_info[stat]['axis'] == 'left':
stats[stat] = self.stat_info[stat]
stats[stat]['values'] = self.stats[stat]
stats[stat]['fill_color'] = change_opacity(stats[stat]['color'], 0.5)
stats[stat]['color'] = change_opacity(stats[stat]['color'], 0.8)
- stats[stat]['max_value'] = max(self.stats[stat])
- max_values.append(stats[stat]['max_value'])
- if len(max_values) > 1:
- 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)
+ height = bottom - top
+ max_value = y_ticks[-1]
ratio = height / max_value
- for i in xrange(1, 6):
- y = int(ratio * ((max_value / 5) * i)) - 0.5
- if i < 5:
- self.draw_dotted_line(gray, 60, y, self.width, y)
- 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 i, y_val in enumerate(y_ticks):
+ y = int(bottom - y_val * ratio ) - 0.5
+ if i != 0:
+ self.draw_dotted_line(gray, left, y, right, y)
+ self.draw_y_text(y_tick_text[i], left, y)
+ self.draw_line(gray, left, top, left, bottom)
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'])
+ if len(info['values']) > 0:
+ self.draw_value_poly(info['values'], info['color'], max_value, bounds)
+ self.draw_value_poly(info['values'], info['fill_color'], max_value, bounds, info['fill'])
def draw_legend(self):
pass
- def trace_path(self, values, max_value):
- height = self.height - 24
- width = self.width
+
+ def trace_path(self, values, max_value, bounds):
+ (left, top, right, bottom) = bounds
+ ratio = (bottom - top) / max_value
line_width = self.line_size
self.ctx.set_line_width(line_width)
- self.ctx.move_to(width, height)
+ self.ctx.move_to(right, bottom)
- self.ctx.line_to(width,
- int(height - ((height - 28) * values[0] / max_value)))
+ self.ctx.line_to(right, int(bottom - values[0] * ratio ))
- x = width
- step = (width - 60) / float(self.length)
+ x = right
+ step = (right - left) / float(self.length -1)
for i, value in enumerate(values):
if i == self.length - 1:
- x = 62
- self.ctx.line_to(x,
- int(height - 1 - ((height - 28) * value / max_value))
- )
+ x = left
+
+ self.ctx.line_to(x, int(bottom - value * ratio))
x -= step
self.ctx.line_to(
- int(width + 62 - (((len(values) - 1) * width) / (self.length - 1))),
- height)
+ int(right - (len(values) - 1) * step),
+ bottom)
self.ctx.close_path()
- def draw_value_poly(self, values, color, max_value, fill=False):
- self.trace_path(values, max_value)
+
+ def draw_value_poly(self, values, color, max_value, bounds, fill=False):
+ self.trace_path(values, max_value, bounds)
self.ctx.set_source_rgba(*color)
if fill:
@@ -231,9 +302,26 @@ class Graph:
else:
self.ctx.stroke()
- def draw_text(self, text, x, y):
- self.ctx.set_font_size(9)
- self.ctx.move_to(x, y + 9)
+ def draw_x_text(self, text, x, y):
+ """Draws text below and horizontally centered about x,y"""
+ fe = self.ctx.font_extents()
+ te = self.ctx.text_extents(text)
+ height = fe[2]
+ x_bearing = te[0]
+ width = te[2]
+ self.ctx.move_to(int(x - width/2.0 + x_bearing), int(y + height))
+ self.ctx.set_source_rgba(*self.black)
+ self.ctx.show_text(text)
+
+ def draw_y_text(self, text, x, y):
+ """Draws text left of and vertically centered about x,y"""
+ fe = self.ctx.font_extents()
+ te = self.ctx.text_extents(text)
+ descent = fe[1]
+ ascent = fe[0]
+ x_bearing = te[0]
+ width = te[4]
+ self.ctx.move_to(int(x - width - x_bearing - 2), int(y + (ascent - descent)/2.0))
self.ctx.set_source_rgba(*self.black)
self.ctx.show_text(text)
@@ -252,13 +340,12 @@ class Graph:
def draw_dotted_line(self, color, x1, y1, x2, y2):
self.ctx.set_source_rgba(*color)
self.ctx.set_line_width(1)
+ dash, offset = self.ctx.get_dash()
+ self.ctx.set_dash(self.dash_length, 0)
self.ctx.move_to(x1, y1)
self.ctx.line_to(x2, y2)
- #self.ctx.stroke_preserve()
- #self.ctx.set_source_rgba(*white)
- #self.ctx.set_dash((1, 1), 4)
self.ctx.stroke()
- #self.ctx.set_dash((1, 1), 0)
+ self.ctx.set_dash(dash, offset)
if __name__ == "__main__":
import test
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/gtkui.py b/deluge/plugins/Stats/deluge/plugins/stats/gtkui.py
index d3bec7353..607fc406f 100644
--- a/deluge/plugins/Stats/deluge/plugins/stats/gtkui.py
+++ b/deluge/plugins/Stats/deluge/plugins/stats/gtkui.py
@@ -1,6 +1,7 @@
#
# gtkui.py
#
+# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
# Basic plugin template created by:
@@ -23,18 +24,7 @@
# 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.
-#
+# 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
@@ -45,89 +35,211 @@
# 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
-import os
import gtk
-import logging
+import gobject
from gtk.glade import XML
-from twisted.internet import defer
-
-# Relative imports
-from . import common
-from . import graph
-
+import graph
+import deluge
from deluge import component
+from deluge.log import LOG as log
from deluge.common import fspeed
from deluge.ui.client import client
from deluge.ui.gtkui.torrentdetails import Tab
from deluge.plugins.pluginbase import GtkPluginBase
-log = logging.getLogger(__name__)
+import common
+
+DEFAULT_CONF = { 'version': 1,
+ 'colors' :{
+ 'bandwidth_graph': {'upload_rate': str(gtk.gdk.Color("blue")),
+ 'download_rate': str(gtk.gdk.Color("green")),
+ },
+ 'connections_graph': { 'dht_nodes': str(gtk.gdk.Color("orange")),
+ 'dht_cache_nodes': str(gtk.gdk.Color("blue")),
+ 'dht_torrents': str(gtk.gdk.Color("green")),
+ 'num_connections': str(gtk.gdk.Color("darkred")),
+ },
+ 'seeds_graph': { 'num_peers': str(gtk.gdk.Color("blue")),
+ },
+ }
+ }
+
+def neat_time(column, cell, model, iter):
+ """Render seconds as seconds or minutes with label"""
+ seconds = model.get_value(iter, 0)
+ if seconds >60:
+ text = "%d %s" % (seconds / 60, _("minutes"))
+ elif seconds == 60:
+ text = _("1 minute")
+ elif seconds == 1:
+ text = _("1 second")
+ else:
+ text = "%d %s" % (seconds, _("seconds"))
+ cell.set_property('text', text)
+ return
+
+def int_str(number):
+ return (str(int(number)))
+
+def gtk_to_graph_color(color):
+ """Turns a gtk.gdk.Color into a tuple with range 0-1 as used by the graph"""
+ MAX = float(65535)
+ gtk_color = gtk.gdk.Color(color)
+ red = gtk_color.red / MAX
+ green = gtk_color.green / MAX
+ blue = gtk_color.blue / MAX
+ return (red, green, blue)
+
class GraphsTab(Tab):
- def __init__(self, glade):
+ def __init__(self, glade, colors):
Tab.__init__(self)
- self._name = 'Graphs'
self.glade = glade
self.window = self.glade.get_widget('graph_tab')
- self._child_widget = self.window
self.notebook = self.glade.get_widget('graph_notebook')
self.label = self.glade.get_widget('graph_label')
+
+ self._name = 'Graphs'
+ self._child_widget = self.window
self._tab_label = self.label
+
+ self.colors = colors
+
self.bandwidth_graph = self.glade.get_widget('bandwidth_graph')
- self.bandwidth_graph.connect('expose_event', self.expose)
+ self.bandwidth_graph.connect('expose_event', self.graph_expose)
+
+ self.connections_graph = self.glade.get_widget('connections_graph')
+ self.connections_graph.connect('expose_event', self.graph_expose)
+
+ self.seeds_graph = self.glade.get_widget('seeds_graph')
+ self.seeds_graph.connect('expose_event', self.graph_expose)
+
+ self.notebook.connect('switch-page', self._on_notebook_switch_page)
+
+ self.selected_interval = 1 #should come from config or similar
+ self.select_bandwidth_graph()
+
self.window.unparent()
self.label.unparent()
- self.graph_widget = self.bandwidth_graph
- self.graph = graph.Graph()
- self.graph.add_stat('payload_download_rate', label='Download Rate', color=graph.green)
- self.graph.add_stat('payload_upload_rate', label='Upload Rate', color=graph.blue)
- self.graph.set_left_axis(formatter=fspeed, min=10240)
+ self.intervals = None
+ self.intervals_combo = self.glade.get_widget('combo_intervals')
+ cell = gtk.CellRendererText()
+ self.intervals_combo.pack_start(cell, True)
+ self.intervals_combo.set_cell_data_func(cell, neat_time)
+ self.intervals_combo.connect("changed", self._on_selected_interval_changed)
+ self.update_intervals()
+
- def expose(self, widget, event):
- """Redraw"""
+ def graph_expose(self, widget, event):
context = self.graph_widget.window.cairo_create()
# set a clip region
context.rectangle(event.area.x, event.area.y,
event.area.width, event.area.height)
context.clip()
-
- width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
- self.graph.draw_to_context(context, width, height)
+ self.graph.draw_to_context(context,
+ self.graph_widget.allocation.width,
+ self.graph_widget.allocation.height)
#Do not propagate the event
return False
def update(self):
- log.debug("getstat keys: %s", self.graph.stat_info.keys())
- d1 = client.stats.get_stats(self.graph.stat_info.keys())
+ d1 = client.stats.get_stats(self.graph.stat_info.keys(), self.selected_interval)
d1.addCallback(self.graph.set_stats)
- d2 = client.stats.get_config()
- d2.addCallback(self.graph.set_config)
- dl = defer.DeferredList([d1, d2])
-
- def _on_update(result):
- width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
- rect = gtk.gdk.Rectangle(0, 0, width, height)
- self.graph_widget.window.invalidate_rect(rect, True)
-
- dl.addCallback(_on_update)
+ def _update_complete(result):
+ self.graph_widget.queue_draw()
+ d1.addCallback(_update_complete)
+ return True
def clear(self):
pass
+ def update_intervals(self):
+ client.stats.get_intervals().addCallback(self._on_intervals_changed)
+ def select_bandwidth_graph(self):
+ log.debug("Selecting bandwidth graph")
+ self.graph_widget = self.bandwidth_graph
+ self.graph = graph.Graph()
+ colors = self.colors['bandwidth_graph']
+ self.graph.add_stat('download_rate', label='Download Rate',
+ color=gtk_to_graph_color(colors['download_rate']))
+ self.graph.add_stat('upload_rate', label='Upload Rate',
+ color=gtk_to_graph_color(colors['upload_rate']))
+ self.graph.set_left_axis(formatter=fspeed, min=10240,
+ formatter_scale=graph.size_formatter_scale)
+
+ def select_connections_graph(self):
+ log.debug("Selecting connections graph")
+ self.graph_widget = self.connections_graph
+ g = graph.Graph()
+ self.graph = g
+ colors = self.colors['connections_graph']
+ g.add_stat('dht_nodes', color=gtk_to_graph_color(colors['dht_nodes']))
+ g.add_stat('dht_cache_nodes', color=gtk_to_graph_color(colors['dht_cache_nodes']))
+ g.add_stat('dht_torrents', color=gtk_to_graph_color(colors['dht_torrents']))
+ g.add_stat('num_connections', color=gtk_to_graph_color(colors['num_connections']))
+ g.set_left_axis(formatter=int_str, min=10)
+
+ def select_seeds_graph(self):
+ log.debug("Selecting connections graph")
+ self.graph_widget = self.seeds_graph
+ self.graph = graph.Graph()
+ colors = self.colors['seeds_graph']
+ self.graph.add_stat('num_peers', color=gtk_to_graph_color(colors['num_peers']))
+ self.graph.set_left_axis(formatter=int_str, min=10)
+
+ def set_colors(self, colors):
+ self.colors = colors
+ # Fake switch page to update the graph colors (HACKY)
+ self._on_notebook_switch_page(self.notebook,
+ None, #This is unused
+ self.notebook.get_current_page())
+
+ def _on_intervals_changed(self, intervals):
+ liststore = gtk.ListStore(int)
+ for inter in intervals:
+ liststore.append([inter])
+ self.intervals_combo.set_model(liststore)
+ try:
+ current = intervals.index(self.selected_interval)
+ except:
+ current = 0
+ #should select the value saved in config
+ self.intervals_combo.set_active(current)
+
+ def _on_selected_interval_changed(self, combobox):
+ model = combobox.get_model()
+ iter = combobox.get_active_iter()
+ self.selected_interval = model.get_value(iter, 0)
+ self.update()
+ return True
+
+ def _on_notebook_switch_page(self, notebook, page, page_num):
+ p = notebook.get_nth_page(page_num)
+ if p is self.bandwidth_graph:
+ self.select_bandwidth_graph()
+ self.update()
+ elif p is self.connections_graph:
+ self.select_connections_graph()
+ self.update()
+ elif p is self.seeds_graph:
+ self.select_seeds_graph()
+ self.update()
+ return True
class GtkUI(GtkPluginBase):
def enable(self):
log.debug("Stats plugin enable called")
+ self.config = deluge.configmanager.ConfigManager("stats.gtkui.conf", DEFAULT_CONF)
self.glade = XML(common.get_resource("config.glade"))
component.get("Preferences").add_page("Stats", self.glade.get_widget("prefs_box"))
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
self.on_show_prefs()
- self.graphs_tab = GraphsTab(XML(common.get_resource("tabs.glade")))
+ self.graphs_tab = GraphsTab(XML(common.get_resource("tabs.glade")), self.config['colors'])
self.torrent_details = component.get('TorrentDetails')
self.torrent_details.add_tab(self.graphs_tab)
@@ -139,15 +251,31 @@ class GtkUI(GtkPluginBase):
def on_apply_prefs(self):
log.debug("applying prefs for Stats")
- config = {
- "test":self.glade.get_widget("txt_test").get_text()
- }
+ gtkconf = {}
+ for graph, colors in self.config['colors'].items():
+ gtkconf[graph] = {}
+ for value, color in colors.items():
+ try:
+ color_btn = self.glade.get_widget("%s_%s_color" % (graph, value))
+ gtkconf[graph][value] = str(color_btn.get_color())
+ except:
+ gtkconf[graph][value] = DEFAULT_CONF['colors'][graph][value]
+ self.config['colors'] = gtkconf
+ self.graphs_tab.set_colors(self.config['colors'])
+
+ config = { }
client.stats.set_config(config)
def on_show_prefs(self):
+ for graph, colors in self.config['colors'].items():
+ for value, color in colors.items():
+ try:
+ color_btn = self.glade.get_widget("%s_%s_color" % (graph, value))
+ color_btn.set_color(gtk.gdk.Color(color))
+ except:
+ log.debug("Unable to set %s %s %s" % (graph, value, color))
client.stats.get_config().addCallback(self.cb_get_config)
def cb_get_config(self, config):
"callback for on show_prefs"
- self.glade.get_widget("txt_test").set_text(config["test"])
-
+ pass
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/template/graph.html b/deluge/plugins/Stats/deluge/plugins/stats/template/graph.html
new file mode 100644
index 000000000..2ff803bcb
--- /dev/null
+++ b/deluge/plugins/Stats/deluge/plugins/stats/template/graph.html
@@ -0,0 +1,12 @@
+$:render.header(_("Network Graph"), 'graph')
+$:render.admin_toolbar('graph')
+
+<div style="padding-left:20px">
+
+<img src="$base/graph/network.png?height=300&width=1000"><br \>
+<img src="$base/graph/connections.png?height=300&width=1000"><br \>
+</div>
+
+
+
+$:render.footer()
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/webui.py b/deluge/plugins/Stats/deluge/plugins/stats/webui.py
index e48d4d654..6ec261655 100644
--- a/deluge/plugins/Stats/deluge/plugins/stats/webui.py
+++ b/deluge/plugins/Stats/deluge/plugins/stats/webui.py
@@ -1,7 +1,11 @@
#
# webui.py
#
-# Copyright (C) 2009 Damien Churchill <mvoncken@gmail.com>
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@@ -33,20 +37,18 @@
#
#
-import logging
+from deluge.log import LOG as log
from deluge.ui.client import client
from deluge import component
from deluge.plugins.pluginbase import WebPluginBase
from common import get_resource
-log = logging.getLogger(__name__)
-
class WebUI(WebPluginBase):
-
+
scripts = [get_resource("stats.js")]
-
- # The enable and disable methods are not strictly required on the WebUI
+
+ # The enable and disable methods are not scrictly required on the WebUI
# plugins. They are only here if you need to register images/stylesheets
# with the webserver.
def enable(self):
diff --git a/deluge/plugins/Stats/setup.py b/deluge/plugins/Stats/setup.py
index f84f76f8e..c5cf13ce5 100644
--- a/deluge/plugins/Stats/setup.py
+++ b/deluge/plugins/Stats/setup.py
@@ -1,6 +1,6 @@
#
# setup.py
-#
+# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
# Basic plugin template created by:
@@ -23,18 +23,7 @@
# 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.
-#
+# 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
@@ -48,13 +37,16 @@
from setuptools import setup, find_packages
__plugin_name__ = "Stats"
-__author__ = "Martijn Voncken"
-__author_email__ = "mvoncken@gmail.com"
-__version__ = "0.1"
+__author__ = "Ian Martin"
+__author_email__ = "ianmartin@cantab.net"
+__version__ = "0.3.2"
__url__ = "http://deluge-torrent.org"
__license__ = "GPLv3"
-__description__ = ""
-__long_description__ = """"""
+__description__ = "Display stats graphs"
+__long_description__ = """
+Records lots of extra stats
+and produces time series
+graphs"""
__pkg_data__ = {"deluge.plugins."+__plugin_name__.lower(): ["template/*", "data/*"]}
setup(