Remember the location the last torrent was added from.
[python-gnt.git] / example / delugent / delugent.py
blob84c159727d0b27c0705be2b0b6d5faed25b94e8c
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
32 import os.path
34 import gobject
35 import gnt
36 import signals
38 status_keys = {
39 "name" : "Name",
40 "save_path" : "Path",
41 "state" : "Status",
42 "total_size" : "Size",
43 "total_done" : "Downloaded",
44 "progress" : "Progress",
45 "num_seeds" : "# of seeds",
46 "total_seeds" : "Total seeds",
47 "num_peers" : "# of peers",
48 "total_peers" : "Total peers",
49 "eta" : "ETA",
50 "download_payload_rate" : "Download speed",
51 "upload_payload_rate" : "Upload speed",
52 "ratio" : "Share Ratio",
53 "distributed_copies" : "Availability",
54 "total_uploaded" : "Uploaded",
55 "num_files" : "# of files",
58 def show_size(size):
59 unit = "b"
60 if size > (1 << 30):
61 size = size >> 30
62 unit = "G"
63 elif size > (1 << 20):
64 size = size >> 20
65 unit = "M"
66 elif size > (1 << 10):
67 size = size >> 10
68 unit = "K"
69 return str(size) + unit
71 def show_speed(speed):
72 unit = "b"
73 if speed > (1 << 30):
74 speed = speed / (1 << 30)
75 unit = "G"
76 elif speed > (1 << 20):
77 speed = speed / (1 << 20)
78 unit = "M"
79 elif speed > (1 << 10):
80 speed = speed / (1 << 10)
81 uni = "K"
82 return ("%.01f" + unit) % speed
84 def show_state(state):
85 return {8 : 'Paused',
86 2 : 'Connecting',
87 }[state];
89 class TorRow(gobject.GObject):
90 COL_NAME = 0
91 COL_STATE = 1
92 COL_SIZE = 2
93 COL_PROG = 3
94 COL_SD = 4
95 COL_PR = 5
96 COL_DS = 6
97 COL_US = 7
98 COL_ETA = 8
99 COLS = 9
100 def __init__(self, id):
101 self.__gobject_init__()
102 self.id = id
104 def __del__(self):
105 pass
107 def info(self):
108 status = client.get_torrent_status(self.id, status_keys.keys())
109 state = int(status.get("state", 0))
110 name = str(status.get("name", ""))
111 size = int(status.get("total_size", 0))
112 progress = float(status.get("progress", 0.0))
113 peers = str(status.get("num_peers", 0))
114 seeds = str(status.get("num_seeds", 0))
115 dlspeed = float(status.get("download_payload_rate", 0.0))
116 upspeed= float(status.get("upload_payload_rate", 0.0))
117 eta = float(status.get("eta", 0.0))
119 return [name, show_state(state), show_size(size), str(progress), str(seeds), str(peers),
120 show_speed(dlspeed), show_speed(upspeed), str(eta)]
122 gobject.type_register(TorRow)
124 class TorList(gnt.Tree):
125 __gntbindings__ = {
126 'info' : ('show_info', 'i')
128 def __init__(self):
129 gnt.Tree.__init__(self)
130 self.set_property('columns', TorRow.COLS)
131 self.set_show_title(True)
133 titles = {TorRow.COL_NAME : "Name",
134 TorRow.COL_STATE : "State",
135 TorRow.COL_SIZE : "Size",
136 TorRow.COL_PROG : "Progr",
137 TorRow.COL_SD : "Sd",
138 TorRow.COL_PR : "Pr",
139 TorRow.COL_DS : "DL Sp",
140 TorRow.COL_US : "Up Sp",
141 TorRow.COL_ETA : "ETA"
143 widths = {TorRow.COL_NAME : 30,
144 TorRow.COL_STATE : 7,
145 TorRow.COL_SIZE : 5,
146 TorRow.COL_PROG : 5,
147 TorRow.COL_SD : 3,
148 TorRow.COL_PR : 3,
149 TorRow.COL_DS : 6,
150 TorRow.COL_US : 6,
151 TorRow.COL_ETA : 6
154 # Set column titles
155 for col in titles:
156 self.set_column_title(col, titles[col])
157 # Set column widths and alignments
158 for col in widths:
159 self.set_col_width(col, widths[col])
160 for col in [TorRow.COL_ETA, TorRow.COL_SD, TorRow.COL_PR, TorRow.COL_DS, TorRow.COL_US, TorRow.COL_SIZE, TorRow.COL_PROG]:
161 self.set_column_resizable(col, False)
162 for col in [TorRow.COL_SIZE, TorRow.COL_DS, TorRow.COL_SD, TorRow.COL_PR, TorRow.COL_US, TorRow.COL_PROG]:
163 self.set_column_is_right_aligned(col, True)
165 def get_row(self, id):
166 tors = self.get_rows()
167 for tor in tors:
168 if tor.id == id:
169 return tor
170 return None
172 def remove_torrent(self, id):
173 tor = self.get_row(id)
174 if tor: self.remove(tor)
176 def add_torrent(self, id):
177 row = TorRow(id)
178 self.add_row_after(row, row.info(), None)
180 def update_torrent(self, id, tor = None):
181 if not tor:
182 tor = self.get_row(id)
183 info = tor.info()
184 for i in range(0, TorRow.COLS):
185 self.change_text(tor, i, info[i])
187 def update_all(self):
188 for tor in self.get_rows():
189 self.update_torrent(tor.id, tor)
191 def show_info(self, null):
192 tor = self.get_selection_data()
193 if not tor: return True
194 show_details(tor.id)
195 return True
197 gobject.type_register(TorList)
198 gnt.register_bindings(TorList)
200 def show_details(tid):
201 """Show detailed information about a torrent."""
202 status = client.get_torrent_status(tid, status_keys.keys())
203 status['state'] = show_state(status.get('state', ''))
204 status['total_size'] = show_size(status.get('total_size', 0))
205 status['download_payload_rate'] = show_speed(status.get('download_payload_rate', 0.0))
206 status['upload_payload_rate'] = show_speed(status.get('upload_payload_rate', 0.0))
207 win = gnt.Box(vert = True, homo = False)
208 win.set_toplevel(True)
209 win.set_alignment(gnt.ALIGN_MID)
210 win.set_pad(0)
211 win.set_fill(True)
212 win.set_title("Details")
213 tv = gnt.TextView()
214 string = ""
216 # This is the order of the info we want to see
217 keys = [
218 "name",
219 "save_path",
220 "state",
221 "total_size",
222 "total_done",
223 "progress",
224 "num_seeds",
225 "total_seeds",
226 "num_peers",
227 "total_peers",
228 "eta",
229 "download_payload_rate",
230 "upload_payload_rate",
231 "ratio",
232 "distributed_copies",
233 "total_uploaded",
234 "num_files",
236 for iter in range(0, len(keys)):
237 key = keys[iter]
238 tag = ("%15s" % status_keys[key]) + ": "
239 value = str(status.get(key, ''))
240 tv.append_text_with_flags(tag, gnt.TEXT_FLAG_BOLD)
241 tv.append_text_with_flags(value + "\n", gnt.TEXT_FLAG_NORMAL)
242 string = string + tag + value + "\n"
243 tv.attach_scroll_widget(win)
244 tv.set_flag(gnt.TEXT_VIEW_TOP_ALIGN | gnt.TEXT_VIEW_NO_SCROLL)
245 w, h = gnt.get_text_bound(string)
246 tv.set_size(w, h)
247 win.add_widget(tv)
248 ok = gnt.Button("OK")
249 def close_info_dlg(b, window):
250 window.destroy()
251 ok.connect('activate', close_info_dlg, win)
252 box = gnt.Box(False, False)
253 box.add_widget(ok)
254 win.add_widget(box)
255 win.show()
257 gntui = None
259 def setup_deluge_core():
260 """Do the foreplay with the deluge daemon."""
261 DEFAULT_PREFS = {
262 "load_torrent_location" : "/tmp",
264 global gntui
265 client.set_core_uri("http://localhost:58846")
266 gntui = ConfigManager("gntui.conf", DEFAULT_PREFS)
268 def pack_widget(vert, widgets):
269 box = gnt.Box(vert = vert, homo = False)
270 for widget in widgets:
271 box.add_widget(widget)
272 return box
274 def dummy(item):
275 pass
277 def show_dl_preferences(item):
278 pass
280 def refine_config_value(config, value):
281 """Return the value of the configuration in correct type."""
282 config_convert = {
283 "max_connections_global" : int,
284 "max_download_speed" : float,
285 "max_upload_speed" : float,
286 "max_upload_slots_global" : int,
287 "max_connections_per_torrent" : int,
288 "max_upload_slots_per_torrent" : int,
289 "listen_ports" : int,
290 "enc_in_policy" : int,
291 "enc_out_policy" : int,
292 "enc_level" : int,
294 if config in config_convert:
295 conv = config_convert[config]
296 if isinstance(value, list):
297 for iter in range(0, len(value)):
298 value[iter] = conv(value[iter])
299 else:
300 value = conv(value)
301 return value
303 def pref_window(title):
304 win = gnt.Window()
305 win.set_property('vertical', True)
306 win.set_title(title)
307 win.set_alignment(gnt.ALIGN_MID)
308 win.set_pad(0)
309 save = gnt.Button("Save")
310 cancel = gnt.Button("Cancel")
311 def close_window(b, window):
312 window.destroy()
313 cancel.connect('activate', close_window, win)
314 return win, save, cancel
316 def add_section(win, title):
317 win.add_widget(gnt.gnt_label_new_with_format(title, gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
319 def show_bandwidth_pref(item):
320 """Bandwidth settings."""
321 win, save, cancel = pref_window("Bandwidth Preference")
323 configs = client.get_config()
324 entries = []
325 def hook_entry_with_config(entry, config):
326 value = str(configs[config])
327 entry.set_data("config", config)
328 entry.set_data("config-value", value)
329 entry.set_text(value)
330 entries.append(entry)
332 win.add_widget(gnt.gnt_label_new_with_format("Global Bandwidth Usage", gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
333 maxcon = gnt.Entry("")
334 maxdlsp = gnt.Entry("")
335 maxupsp = gnt.Entry("")
336 maxupsl = gnt.Entry("")
338 for entry, config in ((maxcon, "max_connections_global"),
339 (maxdlsp, "max_download_speed"),
340 (maxupsp, "max_upload_speed"),
341 (maxupsl, "max_upload_slots_global"),
343 hook_entry_with_config(entry, config)
345 for label, entry in (("Maximum Connections:", maxcon),
346 ("Maximum Download Speed (KB/s):", maxdlsp),
347 ("Maximum Upload Speed (KB/s):", maxupsp),
348 ("Maximum Upload Slots:", maxupsl),
350 win.add_widget(pack_widget(False, [gnt.Label(label), entry]))
352 win.add_widget(gnt.gnt_label_new_with_format("Per Torrent Bandwidth Usage", gnt.TEXT_FLAG_BOLD | gnt.TEXT_FLAG_UNDERLINE))
353 maxconpt = gnt.Entry("")
354 maxupslpt = gnt.Entry("")
355 for entry, config in ((maxconpt, "max_connections_per_torrent"),
356 (maxupslpt, "max_upload_slots_per_torrent"),
358 hook_entry_with_config(entry, config)
360 for label, entry in (("Maximum Connections:", maxconpt),
361 ("Maximum Upload Slots:", maxupslpt),
363 win.add_widget(pack_widget(False, [gnt.Label(label), entry]))
365 def save_prefs(b, window):
366 newconfigs = {}
367 for entry in entries:
368 global gntui
369 config = entry.get_data("config")
370 oldv = entry.get_data("config-value")
371 value = entry.get_text()
372 if oldv != value:
373 value = refine_config_value(config, value)
374 newconfigs[config] = value
375 sys.stderr.write("Changing " + config + " to " + str(value) + "\n")
376 try:
377 client.set_config(newconfigs)
378 window.destroy()
379 except Exception, exc:
380 log.error(str(exc))
381 save.connect('activate', save_prefs, win)
382 win.add_widget(gnt.Line(False))
383 win.add_widget(pack_widget(False, [save, cancel]))
385 win.show()
387 def show_network_pref(item):
388 """Network settings."""
389 win, save, cancel = pref_window("Network Preference")
391 configs = client.get_config()
393 checks = []
394 def create_checkbox(label, config):
395 check = gnt.CheckBox(label)
396 value = configs[config]
397 check.set_checked(value)
398 check.set_data("config", config)
399 check.set_data("config-value", value)
400 checks.append(check)
401 return check
404 add_section(win, "Ports")
405 randport = create_checkbox("Use Random Ports", "random_port")
406 win.add_widget(pack_widget(False, [randport, gnt.Label(" (Active Port: " + str(client.get_listen_port()) + ")")]))
408 add_section(win, "DHT")
409 dht = create_checkbox("Enable Mainline DHT", "dht")
410 win.add_widget(dht)
412 add_section(win, "Network Extras")
413 upnp = create_checkbox("UPnP", "upnp")
414 nat = create_checkbox("NAT-PMP", "natpmp")
415 pex = create_checkbox("uTorrent-PeX", "utpex")
416 win.add_widget(pack_widget(False, [upnp, nat, pex]))
418 def save_callback(vt, window):
419 newconfigs = {}
420 for check in checks:
421 oldv = check.get_data("config-value")
422 config = check.get_data("config")
423 val = check.get_checked()
424 if val != oldv:
425 newconfigs[config] = val
426 client.set_config(newconfigs)
427 window.destroy()
428 save.connect('activate', save_callback, win)
429 win.add_widget(gnt.Line(False))
430 win.add_widget(pack_widget(False, [save, cancel]))
431 win.show()
433 def create_menu():
434 """Create the menu for the main window."""
435 menu = gnt.Menu(gnt.MENU_TOPLEVEL)
437 file = gnt.MenuItem("File")
438 menu.add_item(file)
440 filesub = gnt.Menu(gnt.MENU_POPUP)
441 file.set_submenu(filesub)
443 qt = gnt.MenuItem("Quit")
444 def qt_cb(q):
445 gnt.gnt_quit()
446 sys.exit(0)
447 qt.connect('activate', qt_cb)
448 filesub.add_item(qt)
450 edit = gnt.MenuItem("Edit")
451 menu.add_item(edit)
453 editsub = gnt.Menu(gnt.MENU_POPUP)
454 edit.set_submenu(editsub)
456 pref = gnt.MenuItem("Preference")
457 prefsub = gnt.Menu(gnt.MENU_POPUP)
458 pref.set_submenu(prefsub)
459 editsub.add_item(pref)
461 for name, cb in (("Downloads", show_dl_preferences),
462 ("Network", show_network_pref),
463 ("Bandwidth", show_bandwidth_pref),
464 ("Other", dummy),
465 ("Plugins", dummy),
467 dl = gnt.MenuItem(name)
468 dl.connect('activate', cb)
469 prefsub.add_item(dl)
471 conn = gnt.MenuItem("Connections")
472 editsub.add_item(conn)
474 return menu
476 def main():
477 setup_deluge_core()
479 win = gnt.Window()
480 win.set_property('vertical', True)
481 win.set_toplevel(True)
482 win.set_title("Delugent")
483 win.set_pad(0)
484 win.set_alignment(gnt.ALIGN_MID)
486 list = TorList()
488 win.add_widget(list)
489 width, height = gnt.screen_size()
490 list.set_size(width, height)
492 hbox = gnt.Box(homo = False, vert = False)
494 add = gnt.Button("Add")
495 def add_clicked(b):
496 def file_selected(widget, path, file):
497 files = widget.get_selected_multi_files()
498 widget.destroy()
499 client.add_torrent_file(files)
500 path = os.path.dirname(files[0])
501 gntui['load_torrent_location'] = path
502 fl = gnt.FileSel()
503 fl.set_title("Select a Torrent")
504 fl.set_multi_select(True)
505 fl.set_current_location(gntui['load_torrent_location'])
506 fl.connect("file_selected", file_selected)
507 fl.show()
509 add.connect('activate', add_clicked)
510 hbox.add_widget(add)
511 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_INS, add)
513 rm = gnt.Button("Remove")
514 def rm_clicked(b):
515 tor = list.get_selection_data()
516 client.remove_torrent([tor.id])
517 rm.connect('activate', rm_clicked)
518 hbox.add_widget(rm)
519 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_DEL, rm)
521 hbox.add_widget(gnt.Label("|"))
523 pause = gnt.Button("Pause")
524 def pause_clicked(b):
525 tor = list.get_selection_data()
526 client.pause_torrent([tor.id])
527 pause.connect('activate', pause_clicked)
528 hbox.add_widget(pause)
529 gnt.gnt_util_set_trigger_widget(win, 'p', pause)
531 hbox.add_widget(gnt.Label("|"))
533 mup = gnt.Button("Up")
534 hbox.add_widget(mup)
535 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_CTRL_UP, mup)
537 mdown = gnt.Button("Down")
538 hbox.add_widget(mdown)
539 gnt.gnt_util_set_trigger_widget(win, gnt.KEY_CTRL_DOWN, mdown)
541 win.add_widget(hbox)
543 menu = create_menu() # XXX: Investigate why this causes warnings on exit
544 win.set_menu(menu)
546 win.show()
548 # Add the torrents in the list
549 session_state = client.get_session_state()
550 for torrent_id in session_state:
551 list.add_torrent(torrent_id)
553 signalhandler = signals.Signals(list)
554 gnt.gnt_main()
556 gnt.gnt_quit()
558 del signalhandler
560 if __name__ == "__main__":
561 main()