Minor refinements.
[python-gnt.git] / example / delugent / delugent.py
bloba508046c0805a74c59a3ebcd5b9b510a05d9f521
1 #!/usr/bin/env python
3 """
4 delugent : Frontend for deluge.
6 This is meant to work with deluge 0.60 (and above, I am sure), which has
7 a core-ui split. This is a work in progress.
9 Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
11 This application is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Lesser General Public
13 License as published by the Free Software Foundation; either
14 version 2.1 of the License, or (at your option) any later version.
16 This application is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public
22 License along with this application; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
24 USA
25 """
27 import deluge.ui.client as client
28 from deluge.log import LOG as log
29 from deluge.configmanager import ConfigManager
31 import sys
33 import gobject
34 import gnt
35 import signals
37 status_keys = {
38 "name" : "Name",
39 "save_path" : "Path",
40 "state" : "Status",
41 "total_size" : "Size",
42 "total_done" : "Downloaded",
43 "progress" : "Progress",
44 "num_seeds" : "# of seeds",
45 "total_seeds" : "Total seeds",
46 "num_peers" : "# of peers",
47 "total_peers" : "Total peers",
48 "eta" : "ETA",
49 "download_payload_rate" : "Download speed",
50 "upload_payload_rate" : "Upload speed",
51 "ratio" : "Share Ratio",
52 "distributed_copies" : "Availability",
53 "total_uploaded" : "Uploaded",
54 "num_files" : "# of files",
57 def show_size(size):
58 unit = "b"
59 if size > (1 << 30):
60 size = size >> 30
61 unit = "G"
62 elif size > (1 << 20):
63 size = size >> 20
64 unit = "M"
65 elif size > (1 << 10):
66 size = size >> 10
67 unit = "K"
68 return str(size) + unit
70 def show_speed(speed):
71 unit = "b"
72 if speed > (1 << 30):
73 speed = speed / (1 << 30)
74 unit = "G"
75 elif speed > (1 << 20):
76 speed = speed / (1 << 20)
77 unit = "M"
78 elif speed > (1 << 10):
79 speed = speed / (1 << 10)
80 uni = "K"
81 return ("%.01f" + unit) % speed
83 def show_state(state):
84 return {8 : 'Paused',
85 2 : 'Connecting',
86 }[state];
88 class TorRow(gobject.GObject):
89 COL_NAME = 0
90 COL_STATE = 1
91 COL_SIZE = 2
92 COL_PROG = 3
93 COL_SD = 4
94 COL_PR = 5
95 COL_DS = 6
96 COL_US = 7
97 COL_ETA = 8
98 COLS = 9
99 def __init__(self, id):
100 self.__gobject_init__()
101 self.id = id
103 def __del__(self):
104 pass
106 def info(self):
107 status = client.get_torrent_status(self.id, status_keys.keys())
108 state = int(status.get("state", 0))
109 name = str(status.get("name", ""))
110 size = int(status.get("total_size", 0))
111 progress = float(status.get("progress", 0.0))
112 peers = str(status.get("num_peers", 0))
113 seeds = str(status.get("num_seeds", 0))
114 dlspeed = float(status.get("download_payload_rate", 0.0))
115 upspeed= float(status.get("upload_payload_rate", 0.0))
116 eta = float(status.get("eta", 0.0))
118 return [name, show_state(state), show_size(size), str(progress), str(seeds), str(peers),
119 show_speed(dlspeed), show_speed(upspeed), str(eta)]
121 gobject.type_register(TorRow)
123 class TorList(gnt.Tree):
124 __gntbindings__ = {
125 'info' : ('show_info', 'i')
127 def __init__(self):
128 gnt.Tree.__init__(self)
129 self.set_property('columns', TorRow.COLS)
130 self.set_show_title(True)
132 titles = {TorRow.COL_NAME : "Name",
133 TorRow.COL_STATE : "State",
134 TorRow.COL_SIZE : "Size",
135 TorRow.COL_PROG : "Progr",
136 TorRow.COL_SD : "Sd",
137 TorRow.COL_PR : "Pr",
138 TorRow.COL_DS : "DL Sp",
139 TorRow.COL_US : "Up Sp",
140 TorRow.COL_ETA : "ETA"
142 widths = {TorRow.COL_NAME : 30,
143 TorRow.COL_STATE : 7,
144 TorRow.COL_SIZE : 5,
145 TorRow.COL_PROG : 5,
146 TorRow.COL_SD : 3,
147 TorRow.COL_PR : 3,
148 TorRow.COL_DS : 6,
149 TorRow.COL_US : 6,
150 TorRow.COL_ETA : 6
153 # Set column titles
154 for col in titles:
155 self.set_column_title(col, titles[col])
156 # Set column widths and alignments
157 for col in widths:
158 self.set_col_width(col, widths[col])
159 for col in [TorRow.COL_ETA, TorRow.COL_SD, TorRow.COL_PR, TorRow.COL_DS, TorRow.COL_US, TorRow.COL_SIZE, TorRow.COL_PROG]:
160 self.set_column_resizable(col, False)
161 for col in [TorRow.COL_SIZE, TorRow.COL_DS, TorRow.COL_SD, TorRow.COL_PR, TorRow.COL_US, TorRow.COL_PROG]:
162 self.set_column_is_right_aligned(col, True)
164 def get_row(self, id):
165 tors = self.get_rows()
166 for tor in tors:
167 if tor.id == id:
168 return tor
169 return None
171 def remove_torrent(self, id):
172 tor = self.get_row(id)
173 if tor: self.remove(tor)
175 def add_torrent(self, id):
176 row = TorRow(id)
177 self.add_row_after(row, row.info(), None)
179 def update_torrent(self, id, tor = None):
180 if not tor:
181 tor = self.get_row(id)
182 info = tor.info()
183 for i in range(0, TorRow.COLS):
184 self.change_text(tor, i, info[i])
186 def update_all(self):
187 for tor in self.get_rows():
188 self.update_torrent(tor.id, tor)
190 def show_info(self, null):
191 tor = self.get_selection_data()
192 if not tor: return True
193 show_details(tor.id)
194 return True
196 gobject.type_register(TorList)
197 gnt.register_bindings(TorList)
199 def show_details(tid):
200 """Show detailed information about a torrent."""
201 status = client.get_torrent_status(tid, status_keys.keys())
202 status['state'] = show_state(status.get('state', ''))
203 status['total_size'] = show_size(status.get('total_size', 0))
204 status['download_payload_rate'] = show_speed(status.get('download_payload_rate', 0.0))
205 status['upload_payload_rate'] = show_speed(status.get('upload_payload_rate', 0.0))
206 win = gnt.Box(vert = True, homo = False)
207 win.set_toplevel(True)
208 win.set_alignment(gnt.ALIGN_MID)
209 win.set_pad(0)
210 win.set_fill(True)
211 win.set_title("Details")
212 tv = gnt.TextView()
213 string = ""
215 # This is the order of the info we want to see
216 keys = [
217 "name",
218 "save_path",
219 "state",
220 "total_size",
221 "total_done",
222 "progress",
223 "num_seeds",
224 "total_seeds",
225 "num_peers",
226 "total_peers",
227 "eta",
228 "download_payload_rate",
229 "upload_payload_rate",
230 "ratio",
231 "distributed_copies",
232 "total_uploaded",
233 "num_files",
235 for iter in range(0, len(keys)):
236 key = keys[iter]
237 tag = ("%15s" % status_keys[key]) + ": "
238 value = str(status.get(key, ''))
239 tv.append_text_with_flags(tag, gnt.TEXT_FLAG_BOLD)
240 tv.append_text_with_flags(value + "\n", gnt.TEXT_FLAG_NORMAL)
241 string = string + tag + value + "\n"
242 tv.attach_scroll_widget(win)
243 tv.set_flag(gnt.TEXT_VIEW_TOP_ALIGN | gnt.TEXT_VIEW_NO_SCROLL)
244 w, h = gnt.get_text_bound(string)
245 tv.set_size(w, h)
246 win.add_widget(tv)
247 ok = gnt.Button("OK")
248 def close_info_dlg(b, window):
249 window.destroy()
250 ok.connect('activate', close_info_dlg, win)
251 box = gnt.Box(False, False)
252 box.add_widget(ok)
253 win.add_widget(box)
254 win.show()
256 gntui = None
257 def setup_deluge_core():
258 global gntui
259 client.set_core_uri("http://localhost:58846")
260 ConfigManager("ui.conf", {})
261 gntui = ConfigManager("gntui.conf", {"Dummy": ""})
263 def pack_widget(vert, widgets):
264 box = gnt.Box(vert = vert, homo = False)
265 for widget in widgets:
266 box.add_widget(widget)
267 return box
269 def dummy(item):
270 pass
272 def show_dl_preferences(item):
273 pass
275 def refine_config_value(config, value):
276 """Return the value of the configuration in correct type."""
277 config_convert = {
278 "max_connections_global" : int,
279 "max_download_speed" : float,
280 "max_upload_speed" : float,
281 "max_upload_slots_global" : int,
282 "max_connections_per_torrent" : int,
283 "max_upload_slots_per_torrent" : int,
284 "listen_ports" : int,
285 "enc_in_policy" : int,
286 "enc_out_policy" : int,
287 "enc_level" : int,
289 if config in config_convert:
290 conv = config_convert[config]
291 if isinstance(value, list):
292 for iter in range(0, len(value)):
293 value[iter] = conv(value[iter])
294 else:
295 value = conv(value)
296 return value
298 def pref_window(title):
299 win = gnt.Window()
300 win.set_property('vertical', True)
301 win.set_title(title)
302 win.set_alignment(gnt.ALIGN_MID)
303 win.set_pad(0)
304 save = gnt.Button("Save")
305 cancel = gnt.Button("Cancel")
306 def close_window(b, window):
307 window.destroy()
308 cancel.connect('activate', close_window, win)
309 return win, save, cancel
311 def add_section(win, title):
312 win.add_widget(gnt.gnt_label_new_with_format(title, gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
314 def show_bandwidth_pref(item):
315 """Bandwidth settings."""
316 win, save, cancel = pref_window("Bandwidth Preference")
318 configs = client.get_config()
319 entries = []
320 def hook_entry_with_config(entry, config):
321 value = str(configs[config])
322 entry.set_data("config", config)
323 entry.set_data("config-value", value)
324 entry.set_text(value)
325 entries.append(entry)
327 win.add_widget(gnt.gnt_label_new_with_format("Global Bandwidth Usage", gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
328 maxcon = gnt.Entry("")
329 maxdlsp = gnt.Entry("")
330 maxupsp = gnt.Entry("")
331 maxupsl = gnt.Entry("")
333 for entry, config in ((maxcon, "max_connections_global"),
334 (maxdlsp, "max_download_speed"),
335 (maxupsp, "max_upload_speed"),
336 (maxupsl, "max_upload_slots_global"),
338 hook_entry_with_config(entry, config)
340 for label, entry in (("Maximum Connections:", maxcon),
341 ("Maximum Download Speed (KB/s):", maxdlsp),
342 ("Maximum Upload Speed (KB/s):", maxupsp),
343 ("Maximum Upload Slots:", maxupsl),
345 win.add_widget(pack_widget(False, [gnt.Label(label), entry]))
347 win.add_widget(gnt.gnt_label_new_with_format("Per Torrent Bandwidth Usage", gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
348 maxconpt = gnt.Entry("")
349 maxupslpt = gnt.Entry("")
350 for entry, config in ((maxconpt, "max_connections_per_torrent"),
351 (maxupslpt, "max_upload_slots_per_torrent"),
353 hook_entry_with_config(entry, config)
355 for label, entry in (("Maximum Connections:", maxconpt),
356 ("Maximum Upload Slots:", maxupslpt),
358 win.add_widget(pack_widget(False, [gnt.Label(label), entry]))
360 def save_prefs(b, window):
361 newconfigs = {}
362 for entry in entries:
363 global gntui
364 config = entry.get_data("config")
365 oldv = entry.get_data("config-value")
366 value = entry.get_text()
367 if oldv != value:
368 value = refine_config_value(config, value)
369 newconfigs[config] = value
370 sys.stderr.write("Changing " + config + " to " + str(value) + "\n")
371 try:
372 client.set_config(newconfigs)
373 window.destroy()
374 except Exception, exc:
375 log.error(str(exc))
376 save.connect('activate', save_prefs, win)
377 win.add_widget(gnt.Line(False))
378 win.add_widget(pack_widget(False, [save, cancel]))
380 win.show()
382 def show_network_pref(item):
383 """Network settings."""
384 win, save, cancel = pref_window("Network Preference")
386 configs = client.get_config()
388 checks = []
389 def create_checkbox(label, config):
390 check = gnt.CheckBox(label)
391 value = configs[config]
392 check.set_checked(value)
393 check.set_data("config", config)
394 check.set_data("config-value", value)
395 checks.append(check)
396 return check
399 add_section(win, "Ports")
400 randport = create_checkbox("Use Random Ports", "random_port")
401 win.add_widget(pack_widget(False, [randport, gnt.Label(" (Active Port: " + str(client.get_listen_port()) + ")")]))
403 add_section(win, "DHT")
404 dht = create_checkbox("Enable Mainline DHT", "dht")
405 win.add_widget(dht)
407 add_section(win, "Network Extras")
408 upnp = create_checkbox("UPnP", "upnp")
409 nat = create_checkbox("NAT-PMP", "natpmp")
410 pex = create_checkbox("uTorrent-PeX", "utpex")
411 win.add_widget(pack_widget(False, [upnp, nat, pex]))
413 def save_callback(vt, window):
414 newconfigs = {}
415 for check in checks:
416 oldv = check.get_data("config-value")
417 config = check.get_data("config")
418 val = check.get_checked()
419 if val != oldv:
420 newconfigs[config] = val
421 client.set_config(newconfigs)
422 window.destroy()
423 save.connect('activate', save_callback, win)
424 win.add_widget(gnt.Line(False))
425 win.add_widget(pack_widget(False, [save, cancel]))
426 win.show()
428 def create_menu():
429 """Create the menu for the main window."""
430 menu = gnt.Menu(gnt.MENU_TOPLEVEL)
432 file = gnt.MenuItem("File")
433 menu.add_item(file)
435 filesub = gnt.Menu(gnt.MENU_POPUP)
436 file.set_submenu(filesub)
438 qt = gnt.MenuItem("Quit")
439 def qt_cb(q):
440 gnt.gnt_quit()
441 sys.exit(0)
442 qt.connect('activate', qt_cb)
443 filesub.add_item(qt)
445 edit = gnt.MenuItem("Edit")
446 menu.add_item(edit)
448 editsub = gnt.Menu(gnt.MENU_POPUP)
449 edit.set_submenu(editsub)
451 pref = gnt.MenuItem("Preference")
452 prefsub = gnt.Menu(gnt.MENU_POPUP)
453 pref.set_submenu(prefsub)
454 editsub.add_item(pref)
456 for name, cb in (("Downloads", show_dl_preferences),
457 ("Network", show_network_pref),
458 ("Bandwidth", show_bandwidth_pref),
459 ("Other", dummy),
460 ("Plugins", dummy),
462 dl = gnt.MenuItem(name)
463 dl.connect('activate', cb)
464 prefsub.add_item(dl)
466 conn = gnt.MenuItem("Connections")
467 editsub.add_item(conn)
469 return menu
471 def main():
472 setup_deluge_core()
474 win = gnt.Window()
475 win.set_property('vertical', True)
476 win.set_toplevel(True)
477 win.set_title("Delugent")
478 win.set_pad(0)
479 win.set_alignment(gnt.ALIGN_MID)
481 list = TorList()
483 win.add_widget(list)
484 width, height = gnt.screen_size()
485 list.set_size(width, height)
487 hbox = gnt.Box(homo = False, vert = False)
489 add = gnt.Button("Add")
490 def add_clicked(b):
491 def file_selected(widget, path, file):
492 files = widget.get_selected_multi_files()
493 widget.destroy()
494 client.add_torrent_file(files)
495 fl = gnt.FileSel()
496 fl.set_title("Select a Torrent")
497 fl.set_multi_select(True)
498 fl.connect("file_selected", file_selected)
499 fl.show()
501 add.connect('activate', add_clicked)
502 hbox.add_widget(add)
503 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_INS, add)
505 rm = gnt.Button("Remove")
506 def rm_clicked(b):
507 tor = list.get_selection_data()
508 client.remove_torrent([tor.id])
509 rm.connect('activate', rm_clicked)
510 hbox.add_widget(rm)
511 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_DEL, rm)
513 hbox.add_widget(gnt.Label("|"))
515 pause = gnt.Button("Pause")
516 def pause_clicked(b):
517 tor = list.get_selection_data()
518 client.pause_torrent([tor.id])
519 pause.connect('activate', pause_clicked)
520 hbox.add_widget(pause)
521 gnt.gnt_util_set_trigger_widget(win, 'p', pause)
523 hbox.add_widget(gnt.Label("|"))
525 mup = gnt.Button("Up")
526 hbox.add_widget(mup)
527 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_CTRL_UP, mup)
529 mdown = gnt.Button("Down")
530 hbox.add_widget(mdown)
531 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_CTRL_DOWN, mdown)
533 win.add_widget(hbox)
535 menu = create_menu() # XXX: Investigate why this causes warnings on exit
536 win.set_menu(menu)
538 win.show()
540 # Add the torrents in the list
541 session_state = client.get_session_state()
542 for torrent_id in session_state:
543 list.add_torrent(torrent_id)
545 signalhandler = signals.Signals(list)
546 gnt.gnt_main()
548 gnt.gnt_quit()
550 del signalhandler
552 if __name__ == "__main__":
553 main()