Get the status strings from deluge.
[python-gnt.git] / example / delugent / delugent.py
blob91e5f0aaa5a47bbc74c5b0080e78033132560a61
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.common
28 import deluge.ui.client as client
29 from deluge.log import LOG as log
30 from deluge.configmanager import ConfigManager
32 import sys
33 import os.path
35 import gobject
37 gobject.threads_init()
39 import gnt
40 import signals
42 status_keys = {
43 "name" : "Name",
44 "save_path" : "Path",
45 "state" : "Status",
46 "tracker_status" : "Summary",
47 "total_size" : "Size",
48 "total_done" : "Downloaded",
49 "total_payload_upload" : "Uploaded",
50 "progress" : "Progress",
51 "num_seeds" : "# of seeds",
52 "total_seeds" : "Total seeds",
53 "num_peers" : "# of peers",
54 "total_peers" : "Total peers",
55 "eta" : "ETA",
56 "download_payload_rate" : "Download speed",
57 "upload_payload_rate" : "Upload speed",
58 "ratio" : "Share Ratio",
59 "distributed_copies" : "Availability",
60 "total_uploaded" : "Uploaded",
61 "num_files" : "# of files",
62 "num_pieces" : "# of pieces",
63 "piece_length" : "Piece length",
66 def show_size(size):
67 unit = "b"
68 if size > (1 << 30):
69 size = size >> 30
70 unit = "G"
71 elif size > (1 << 20):
72 size = size >> 20
73 unit = "M"
74 elif size > (1 << 10):
75 size = size >> 10
76 unit = "K"
77 return str(size) + unit
79 def show_speed(speed):
80 unit = "b"
81 if speed > (1 << 30):
82 speed = speed / (1 << 30)
83 unit = "G"
84 elif speed > (1 << 20):
85 speed = speed / (1 << 20)
86 unit = "M"
87 elif speed > (1 << 10):
88 speed = speed / (1 << 10)
89 uni = "K"
90 return ("%.01f" + unit) % speed
92 def show_state(state):
93 return deluge.common.TORRENT_STATE[state]
95 class TorRow(gobject.GObject):
96 COL_NAME = 0
97 COL_STATE = 1
98 COL_SIZE = 2
99 COL_PROG = 3
100 COL_SD = 4
101 COL_PR = 5
102 COL_DS = 6
103 COL_US = 7
104 COL_ETA = 8
105 COLS = 9
106 def __init__(self, id):
107 self.__gobject_init__()
108 self.id = id
110 def __del__(self):
111 pass
113 def info(self):
114 status = client.get_torrent_status(self.id, status_keys.keys())
115 state = int(status.get("state", 0))
116 name = str(status.get("name", ""))
117 size = int(status.get("total_size", 0))
118 progress = float(status.get("progress", 0.0))
119 peers = str(status.get("num_peers", 0))
120 seeds = str(status.get("num_seeds", 0))
121 dlspeed = float(status.get("download_payload_rate", 0.0))
122 upspeed= float(status.get("upload_payload_rate", 0.0))
123 eta = float(status.get("eta", 0.0))
125 return [name, show_state(state), show_size(size), str(progress), str(seeds), str(peers),
126 show_speed(dlspeed), show_speed(upspeed), str(eta)]
128 gobject.type_register(TorRow)
130 class TorList(gnt.Tree):
131 __gntbindings__ = {
132 'info' : ('show_info', 'i'),
133 'toggle_border' : ('toggle_border', 't'),
135 def __init__(self):
136 gnt.Tree.__init__(self)
137 self.set_property('columns', TorRow.COLS)
138 self.set_show_title(True)
139 self.separator = True
141 titles = {TorRow.COL_NAME : "Name",
142 TorRow.COL_STATE : "State",
143 TorRow.COL_SIZE : "Size",
144 TorRow.COL_PROG : "Progr",
145 TorRow.COL_SD : "Sd",
146 TorRow.COL_PR : "Pr",
147 TorRow.COL_DS : "DL Sp",
148 TorRow.COL_US : "Up Sp",
149 TorRow.COL_ETA : "ETA"
151 widths = {TorRow.COL_NAME : 30,
152 TorRow.COL_STATE : 7,
153 TorRow.COL_SIZE : 5,
154 TorRow.COL_PROG : 5,
155 TorRow.COL_SD : 3,
156 TorRow.COL_PR : 3,
157 TorRow.COL_DS : 6,
158 TorRow.COL_US : 6,
159 TorRow.COL_ETA : 6
162 # Set column titles
163 for col in titles:
164 self.set_column_title(col, titles[col])
165 # Set column widths and alignments
166 for col in widths:
167 self.set_col_width(col, widths[col])
168 for col in [TorRow.COL_ETA, TorRow.COL_SD, TorRow.COL_PR, TorRow.COL_DS, TorRow.COL_US, TorRow.COL_SIZE, TorRow.COL_PROG]:
169 self.set_column_resizable(col, False)
170 for col in [TorRow.COL_SIZE, TorRow.COL_DS, TorRow.COL_SD, TorRow.COL_PR, TorRow.COL_US, TorRow.COL_PROG]:
171 self.set_column_is_right_aligned(col, True)
173 def get_row(self, id):
174 tors = self.get_rows()
175 for tor in tors:
176 if tor.id == id:
177 return tor
178 return None
180 def remove_torrent(self, id):
181 tor = self.get_row(id)
182 if tor: self.remove(tor)
184 def add_torrent(self, id):
185 row = TorRow(id)
186 self.add_row_after(row, row.info(), None)
188 def update_torrent(self, id, tor = None):
189 if not tor:
190 tor = self.get_row(id)
191 info = tor.info()
192 for i in range(0, TorRow.COLS):
193 self.change_text(tor, i, info[i])
195 def update_all(self):
196 for tor in self.get_rows():
197 self.update_torrent(tor.id, tor)
199 def show_info(self, null):
200 tor = self.get_selection_data()
201 if not tor: return True
202 show_details(tor.id)
203 return True
205 def toggle_border(self, null):
206 self.separator = not self.separator
207 self.set_show_separator(self.separator)
208 self.draw()
209 return True
211 gobject.type_register(TorList)
212 gnt.register_bindings(TorList)
214 def show_details(tid):
215 """Show detailed information about a torrent."""
216 status = client.get_torrent_status(tid, status_keys.keys())
217 win = gnt.Box(vert = True, homo = False)
218 win.set_toplevel(True)
219 win.set_alignment(gnt.ALIGN_MID)
220 win.set_pad(0)
221 win.set_fill(True)
222 win.set_title("Details")
224 def add_info(info):
225 string = ""
226 tv = gnt.TextView()
227 for label, value in info:
228 label = "%15s: " % label
229 string = string + label + value + "\n"
230 tv.append_text_with_flags(label, gnt.TEXT_FLAG_BOLD)
231 tv.append_text_with_flags(value + "\n", gnt.TEXT_FLAG_NORMAL)
232 tv.set_flag(gnt.TEXT_VIEW_TOP_ALIGN | gnt.TEXT_VIEW_NO_SCROLL)
233 w, h = gnt.get_text_bound(string.strip())
234 tv.set_size(w + 1, h)
235 return tv
237 top = (
238 ("Name", "%s" % status['name']),
239 ("Path", "%s" % status['save_path']),
240 ("# of files", "%s" % status['num_files']),
241 ("Status", show_state(status['state'])),
244 left = (
245 ("Downloaded", "%s (%s)" % (deluge.common.fsize(float(status['total_done'])),
246 deluge.common.fsize(float(status['total_size'])))),
247 ("Download Speed", "%s" % deluge.common.fspeed(float(status['download_payload_rate']))),
248 ("Uploaded", "%s (%s)" % (deluge.common.fsize(float(status['total_uploaded'])),
249 deluge.common.fsize(float(status['total_payload_upload'])))),
250 ("Upload Speed", "%s" % deluge.common.fspeed(float(status['upload_payload_rate']))),
251 ("Pieces", "%s (%s)" % (status['num_pieces'], deluge.common.fsize(status['piece_length']))),
254 right = (
255 ("Seeders", "%s (%s)" % (status['num_seeds'], status['total_seeds'])),
256 ("Peers", "%s (%s)" % (status['num_peers'], status['total_peers'])),
257 ("ETA", "%s" % deluge.common.ftime(status['eta'])),
258 ("Share Ratio", "%.1f" % status['ratio']),
259 ("Availability", "%.1f" % status['distributed_copies']),
262 win.add_widget(add_info(top))
263 win.add_widget(gnt.Line(False))
264 win.add_widget(pack_widget(False, [add_info(left), add_info(right)]))
266 ok = gnt.Button("OK")
267 def close_info_dlg(b, window):
268 window.destroy()
269 ok.connect('activate', close_info_dlg, win)
270 box = gnt.Box(False, False)
271 box.add_widget(ok)
272 win.add_widget(box)
273 win.show()
275 gntui = None
277 def setup_deluge_core():
278 """Do the foreplay with the deluge daemon."""
279 DEFAULT_PREFS = {
280 "load_torrent_location" : "/tmp",
282 global gntui
283 client.set_core_uri("http://localhost:58846")
284 gntui = ConfigManager("gntui.conf", DEFAULT_PREFS)
286 def pack_widget(vert, widgets):
287 box = gnt.Box(vert = vert, homo = False)
288 for widget in widgets:
289 box.add_widget(widget)
290 return box
292 def dummy(item):
293 pass
295 def show_dl_preferences(item):
296 pass
298 def refine_config_value(config, value):
299 """Return the value of the configuration in correct type."""
300 config_convert = {
301 "max_connections_global" : int,
302 "max_download_speed" : float,
303 "max_upload_speed" : float,
304 "max_upload_slots_global" : int,
305 "max_connections_per_torrent" : int,
306 "max_upload_slots_per_torrent" : int,
307 "listen_ports" : int,
308 "enc_in_policy" : int,
309 "enc_out_policy" : int,
310 "enc_level" : int,
312 if config in config_convert:
313 conv = config_convert[config]
314 if isinstance(value, list):
315 for iter in range(0, len(value)):
316 value[iter] = conv(value[iter])
317 else:
318 value = conv(value)
319 return value
321 def pref_window(title):
322 win = gnt.Window()
323 win.set_property('vertical', True)
324 win.set_title(title)
325 win.set_alignment(gnt.ALIGN_MID)
326 win.set_pad(0)
327 save = gnt.Button("Save")
328 cancel = gnt.Button("Cancel")
329 def close_window(b, window):
330 window.destroy()
331 cancel.connect('activate', close_window, win)
332 return win, save, cancel
334 def add_section(win, title):
335 win.add_widget(gnt.gnt_label_new_with_format(title, gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
337 def show_bandwidth_pref(item):
338 """Bandwidth settings."""
339 win, save, cancel = pref_window("Bandwidth Preference")
341 configs = client.get_config()
342 entries = []
343 def hook_entry_with_config(entry, config):
344 value = str(configs[config])
345 entry.set_data("config", config)
346 entry.set_data("config-value", value)
347 entry.set_text(value)
348 entries.append(entry)
350 win.add_widget(gnt.gnt_label_new_with_format("Global Bandwidth Usage", gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
351 maxcon = gnt.Entry("")
352 maxdlsp = gnt.Entry("")
353 maxupsp = gnt.Entry("")
354 maxupsl = gnt.Entry("")
356 for entry, config in ((maxcon, "max_connections_global"),
357 (maxdlsp, "max_download_speed"),
358 (maxupsp, "max_upload_speed"),
359 (maxupsl, "max_upload_slots_global"),
361 hook_entry_with_config(entry, config)
363 for label, entry in (("Maximum Connections:", maxcon),
364 ("Maximum Download Speed (KB/s):", maxdlsp),
365 ("Maximum Upload Speed (KB/s):", maxupsp),
366 ("Maximum Upload Slots:", maxupsl),
368 win.add_widget(pack_widget(False, [gnt.Label(label), entry]))
370 win.add_widget(gnt.gnt_label_new_with_format("Per Torrent Bandwidth Usage", gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
371 maxconpt = gnt.Entry("")
372 maxupslpt = gnt.Entry("")
373 for entry, config in ((maxconpt, "max_connections_per_torrent"),
374 (maxupslpt, "max_upload_slots_per_torrent"),
376 hook_entry_with_config(entry, config)
378 for label, entry in (("Maximum Connections:", maxconpt),
379 ("Maximum Upload Slots:", maxupslpt),
381 win.add_widget(pack_widget(False, [gnt.Label(label), entry]))
383 def save_prefs(b, window):
384 newconfigs = {}
385 for entry in entries:
386 global gntui
387 config = entry.get_data("config")
388 oldv = entry.get_data("config-value")
389 value = entry.get_text()
390 if oldv != value:
391 value = refine_config_value(config, value)
392 newconfigs[config] = value
393 sys.stderr.write("Changing " + config + " to " + str(value) + "\n")
394 try:
395 client.set_config(newconfigs)
396 window.destroy()
397 except Exception, exc:
398 log.error(str(exc))
399 save.connect('activate', save_prefs, win)
400 win.add_widget(gnt.Line(False))
401 win.add_widget(pack_widget(False, [save, cancel]))
403 win.show()
405 def show_network_pref(item):
406 """Network settings."""
407 win, save, cancel = pref_window("Network Preference")
409 configs = client.get_config()
411 checks = []
412 def create_checkbox(label, config):
413 check = gnt.CheckBox(label)
414 value = configs[config]
415 check.set_checked(value)
416 check.set_data("config", config)
417 check.set_data("config-value", value)
418 checks.append(check)
419 return check
422 add_section(win, "Ports")
423 randport = create_checkbox("Use Random Ports", "random_port")
424 win.add_widget(pack_widget(False, [randport, gnt.Label(" (Active Port: " + str(client.get_listen_port()) + ")")]))
426 add_section(win, "DHT")
427 dht = create_checkbox("Enable Mainline DHT", "dht")
428 win.add_widget(dht)
430 add_section(win, "Network Extras")
431 upnp = create_checkbox("UPnP", "upnp")
432 nat = create_checkbox("NAT-PMP", "natpmp")
433 pex = create_checkbox("uTorrent-PeX", "utpex")
434 win.add_widget(pack_widget(False, [upnp, nat, pex]))
436 def save_callback(vt, window):
437 newconfigs = {}
438 for check in checks:
439 oldv = check.get_data("config-value")
440 config = check.get_data("config")
441 val = check.get_checked()
442 if val != oldv:
443 newconfigs[config] = val
444 client.set_config(newconfigs)
445 window.destroy()
446 save.connect('activate', save_callback, win)
447 win.add_widget(gnt.Line(False))
448 win.add_widget(pack_widget(False, [save, cancel]))
449 win.show()
451 def create_menu():
452 """Create the menu for the main window."""
453 menu = gnt.Menu(gnt.MENU_TOPLEVEL)
455 file = gnt.MenuItem("File")
456 menu.add_item(file)
458 filesub = gnt.Menu(gnt.MENU_POPUP)
459 file.set_submenu(filesub)
461 qt = gnt.MenuItem("Quit")
462 def qt_cb(q):
463 gnt.gnt_quit()
464 sys.exit(0)
465 qt.connect('activate', qt_cb)
466 filesub.add_item(qt)
468 edit = gnt.MenuItem("Edit")
469 menu.add_item(edit)
471 editsub = gnt.Menu(gnt.MENU_POPUP)
472 edit.set_submenu(editsub)
474 pref = gnt.MenuItem("Preference")
475 prefsub = gnt.Menu(gnt.MENU_POPUP)
476 pref.set_submenu(prefsub)
477 editsub.add_item(pref)
479 for name, cb in (("Downloads", show_dl_preferences),
480 ("Network", show_network_pref),
481 ("Bandwidth", show_bandwidth_pref),
482 ("Other", dummy),
483 ("Plugins", dummy),
485 dl = gnt.MenuItem(name)
486 dl.connect('activate', cb)
487 prefsub.add_item(dl)
489 conn = gnt.MenuItem("Connections")
490 editsub.add_item(conn)
492 return menu
494 def main():
495 setup_deluge_core()
497 win = gnt.Window()
498 win.set_property('vertical', True)
499 win.set_toplevel(True)
500 win.set_title("Delugent")
501 win.set_pad(0)
502 win.set_alignment(gnt.ALIGN_MID)
504 list = TorList()
506 win.add_widget(list)
507 width, height = gnt.screen_size()
508 list.set_size(width, height)
510 hbox = gnt.Box(homo = False, vert = False)
512 add = gnt.Button("Add")
513 def add_clicked(b):
514 def file_selected(widget, path, file):
515 files = widget.get_selected_multi_files()
516 widget.destroy()
517 client.add_torrent_file(files)
518 path = os.path.dirname(files[0])
519 gntui['load_torrent_location'] = path
520 def close_filesel(cancel, filesel):
521 filesel.destroy()
522 fl = gnt.FileSel()
523 fl.set_title("Select a Torrent")
524 fl.set_multi_select(True)
525 fl.set_current_location(gntui['load_torrent_location'])
526 fl.connect("file_selected", file_selected)
527 fl.cancel_button().connect('activate', close_filesel, fl)
528 fl.show()
530 add.connect('activate', add_clicked)
531 hbox.add_widget(add)
532 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_INS, add)
534 rm = gnt.Button("Remove")
535 def rm_clicked(b):
536 tor = list.get_selection_data()
537 client.remove_torrent([tor.id])
538 rm.connect('activate', rm_clicked)
539 hbox.add_widget(rm)
540 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_DEL, rm)
542 hbox.add_widget(gnt.Label("|"))
544 pause = gnt.Button("Pause")
545 def pause_clicked(b):
546 tor = list.get_selection_data()
547 client.pause_torrent([tor.id])
548 pause.connect('activate', pause_clicked)
549 hbox.add_widget(pause)
550 gnt.gnt_util_set_trigger_widget(win, 'p', pause)
552 hbox.add_widget(gnt.Label("|"))
554 mup = gnt.Button("Up")
555 hbox.add_widget(mup)
556 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_CTRL_UP, mup)
558 mdown = gnt.Button("Down")
559 hbox.add_widget(mdown)
560 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_CTRL_DOWN, mdown)
562 win.add_widget(hbox)
564 menu = create_menu() # XXX: Investigate why this causes warnings on exit
565 win.set_menu(menu)
567 win.show()
569 # Add the torrents in the list
570 session_state = client.get_session_state()
571 for torrent_id in session_state:
572 list.add_torrent(torrent_id)
574 signalhandler = signals.Signals(list)
575 gnt.gnt_main()
577 gnt.gnt_quit()
579 del signalhandler
581 if __name__ == "__main__":
582 main()