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
28 import deluge
.ui
.client
as client
29 from deluge
.log
import LOG
as log
30 from deluge
.configmanager
import ConfigManager
37 gobject
.threads_init()
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",
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",
72 elif size
> (1 << 20):
75 elif size
> (1 << 10):
78 return str(size
) + unit
80 def show_speed(speed
):
83 speed
= speed
/ (1 << 30)
85 elif speed
> (1 << 20):
86 speed
= speed
/ (1 << 20)
88 elif speed
> (1 << 10):
89 speed
= speed
/ (1 << 10)
91 return ("%.01f" + unit
) % speed
93 def show_state(state
):
94 return deluge
.common
.TORRENT_STATE
[state
]
96 def compare_tor_row(row1
, row2
):
97 v1
= row1
.row
[TorRow
.compare_column
]
98 v2
= row2
.row
[TorRow
.compare_column
]
99 if not TorRow
.compare_asc
:
107 class TorRow(gobject
.GObject
):
119 COMPARE_FUNC
= compare_tor_row
120 compare_column
= COL_NAME
123 def __init__(self
, id):
124 self
.__gobject
_init
__()
132 status
= client
.get_torrent_status(self
.id, status_keys
.keys())
133 state
= int(status
.get("state", 0))
134 name
= str(status
.get("name", ""))
135 size
= int(status
.get("total_size", 0))
136 progress
= float(status
.get("progress", 0.0))
137 peers
= str(status
.get("num_peers", 0))
138 seeds
= str(status
.get("num_seeds", 0))
139 dlspeed
= float(status
.get("download_payload_rate", 0.0))
140 upspeed
= float(status
.get("upload_payload_rate", 0.0))
141 eta
= float(status
.get("eta", 0.0))
143 row
= [""] * self
.COLS
144 row
[self
.COL_NAME
] = name
145 row
[self
.COL_STATE
] = show_state(state
)
146 row
[self
.COL_SIZE
] = show_size(size
)
147 row
[self
.COL_PROG
] = str(progress
)
148 row
[self
.COL_SD
] = str(seeds
)
149 row
[self
.COL_PR
] = str(peers
)
150 row
[self
.COL_DS
] = show_speed(dlspeed
)
151 row
[self
.COL_US
] = show_speed(upspeed
)
152 row
[self
.COL_ETA
] = str(eta
)
156 gobject
.type_register(TorRow
)
158 class TorFileRow(gobject
.GObject
):
159 def decide_priority(self
):
162 if self
.priorities
[0] == 1:
164 elif self
.priorities
[0] < 7:
169 def __init__(self
, file):
170 self
.__gobject
_init
__()
171 self
.path
= file['path']
172 self
.size
= file['size']
173 self
.pieces
= len(file['priorities'])
174 self
.priorities
= file['priorities']
175 self
.enabled
= len([p
for p
in self
.priorities
if p
> 0]) > 0
178 return [self
.path
, show_size(self
.size
), self
.decide_priority()]
180 class TorList(gnt
.Tree
):
182 'info' : ('show_info', 'i'),
183 'toggle_border' : ('toggle_border', 't'),
184 'change-sort' : ('change_sort_column', 'S'),
188 gnt
.Tree
.__init
__(self
)
189 self
.set_property('columns', TorRow
.COLS
)
190 self
.set_show_title(True)
191 self
.separator
= True
193 titles
= {TorRow
.COL_NAME
: "Name",
194 TorRow
.COL_STATE
: "State",
195 TorRow
.COL_SIZE
: "Size",
196 TorRow
.COL_PROG
: "Progr",
197 TorRow
.COL_SD
: "Sd",
198 TorRow
.COL_PR
: "Pr",
199 TorRow
.COL_DS
: "DL Sp",
200 TorRow
.COL_US
: "Up Sp",
201 TorRow
.COL_ETA
: "ETA"
203 widths
= {TorRow
.COL_NAME
: 30,
204 TorRow
.COL_STATE
: 7,
216 self
.set_column_title(col
, titles
[col
])
217 # Set column widths and alignments
219 self
.set_col_width(col
, widths
[col
])
220 for col
in [TorRow
.COL_ETA
, TorRow
.COL_SD
, TorRow
.COL_PR
, TorRow
.COL_DS
, TorRow
.COL_US
, TorRow
.COL_SIZE
, TorRow
.COL_PROG
]:
221 self
.set_column_resizable(col
, False)
222 for col
in [TorRow
.COL_SIZE
, TorRow
.COL_DS
, TorRow
.COL_SD
, TorRow
.COL_PR
, TorRow
.COL_US
, TorRow
.COL_PROG
]:
223 self
.set_column_is_right_aligned(col
, True)
225 def get_row(self
, id):
226 tors
= self
.get_rows()
232 def remove_torrent(self
, id):
233 tor
= self
.get_row(id)
234 if tor
: self
.remove(tor
)
236 def add_torrent(self
, id):
238 self
.add_row_after(row
, row
.info(), None, None)
240 def update_torrent(self
, id, tor
= None):
242 tor
= self
.get_row(id)
244 for i
in range(0, TorRow
.COLS
):
245 self
.change_text(tor
, i
, info
[i
])
247 def update_all(self
):
248 for tor
in self
.get_rows():
249 self
.update_torrent(tor
.id, tor
)
251 def show_info(self
, null
):
252 tor
= self
.get_selection_data()
253 if not tor
: return True
257 def toggle_border(self
, null
):
258 self
.separator
= not self
.separator
259 self
.set_show_separator(self
.separator
)
263 def change_sort_column(self
, null
):
264 def sort_col_cb(item
, col
):
265 if TorRow
.compare_column
== col
:
266 TorRow
.compare_asc
= not TorRow
.compare_asc
268 TorRow
.compare_column
= col
269 rows
= self
.get_rows()
272 self
.add_row_after(row
, row
.info(), None, None)
273 menu
= gnt
.Menu(gnt
.MENU_POPUP
)
274 for label
, column
in (("Sort by Name", TorRow
.COL_NAME
),
275 ("Sort by Size", TorRow
.COL_SIZE
),
276 ("Sort by Progress", TorRow
.COL_PROG
),
277 ("Sort by Download Speed", TorRow
.COL_DS
),
278 ("Sort by ETA", TorRow
.COL_ETA
),
280 item
= gnt
.MenuItemCheck(label
)
281 if TorRow
.compare_column
== column
:
282 item
.set_checked(True)
283 item
.connect('activate', sort_col_cb
, column
)
287 gobject
.type_register(TorList
)
288 gnt
.register_bindings(TorList
)
290 def setup_fileselector(fl
, win
):
291 def close_filesel(cancel
, window
):
293 fl
.cancel_button().connect('activate', close_filesel
, win
)
296 def show_details(tid
):
297 """Show detailed information about a torrent."""
298 status
= client
.get_torrent_status(tid
, status_keys
.keys())
299 win
= gnt
.Box(vert
= True, homo
= False)
300 win
.set_toplevel(True)
301 win
.set_alignment(gnt
.ALIGN_MID
)
304 win
.set_title("Details")
309 for label
, value
in info
:
310 label
= "%15s: " % label
311 tv
.append_text_with_flags(label
, gnt
.TEXT_FLAG_BOLD
)
312 tv
.append_text_with_flags(value
+ "\n", gnt
.TEXT_FLAG_NORMAL
)
313 string
= string
+ label
+ value
+ "\n"
314 tv
.set_flag(gnt
.TEXT_VIEW_TOP_ALIGN | gnt
.TEXT_VIEW_NO_SCROLL
)
315 w
, h
= gnt
.get_text_bound(string
.strip())
316 tv
.set_size(w
+ 3, h
)
320 ("Name", "%s" % status
['name']),
321 ("Path", "%s" % status
['save_path']),
322 ("# of files", "%s" % status
['num_files']),
323 ("Status", show_state(status
['state'])),
327 ("Downloaded", "%s (%s)" % (deluge
.common
.fsize(float(status
['total_done'])),
328 deluge
.common
.fsize(float(status
['total_size'])))),
329 ("Download Speed", "%s" % deluge
.common
.fspeed(float(status
['download_payload_rate']))),
330 ("Uploaded", "%s (%s)" % (deluge
.common
.fsize(float(status
['total_uploaded'])),
331 deluge
.common
.fsize(float(status
['total_payload_upload'])))),
332 ("Upload Speed", "%s" % deluge
.common
.fspeed(float(status
['upload_payload_rate']))),
333 ("Pieces", "%s (%s)" % (status
['num_pieces'], deluge
.common
.fsize(status
['piece_length']))),
337 ("Seeders", "%s (%s)" % (status
['num_seeds'], status
['total_seeds'])),
338 ("Peers", "%s (%s)" % (status
['num_peers'], status
['total_peers'])),
339 ("ETA", "%s" % deluge
.common
.ftime(status
['eta'])),
340 ("Share Ratio", "%.1f" % status
['ratio']),
341 ("Availability", "%.1f" % status
['distributed_copies']),
344 win
.add_widget(add_info(top
))
345 win
.add_widget(gnt
.Line(False))
346 win
.add_widget(pack_widget(False, [add_info(left
), add_info(right
)]))
348 files
.set_property('columns', 3)
349 files
.set_col_width(1, 5)
350 files
.set_column_resizable(1, False)
351 files
.set_col_width(2, 10)
352 files
.set_show_title(True)
353 files
.set_column_title(0, 'File')
354 files
.set_column_title(1, 'Size')
355 files
.set_column_title(2, 'Priority')
356 files
.set_column_is_right_aligned(1, True)
357 for file in status
['files']:
358 torfile
= TorFileRow(file)
359 files
.add_row_after(torfile
, torfile
.info(), None)
360 win
.add_widget(files
)
362 ok
= gnt
.Button("OK")
363 def close_info_dlg(b
, window
):
365 ok
.connect('activate', close_info_dlg
, win
)
366 box
= gnt
.Box(False, False)
373 def setup_deluge_core(list):
374 """Do the foreplay with the deluge daemon."""
376 "load_torrent_location" : "/tmp",
379 client
.set_core_uri("http://localhost:58846")
380 gntui
= ConfigManager("gntui.conf", DEFAULT_PREFS
)
381 signalhandler
= signals
.Signals(list)
382 def update_rows(list):
385 id = gobject
.timeout_add_seconds(1, update_rows
, list)
386 def remove_update_timeout(list, id):
387 gobject
.source_remove(id)
388 list.connect('destroy', remove_update_timeout
, id)
390 def pack_widget(vert
, widgets
):
391 box
= gnt
.Box(vert
= vert
, homo
= False)
392 for widget
in widgets
:
393 box
.add_widget(widget
)
399 def show_dl_preferences(item
):
402 def refine_config_value(config
, value
):
403 """Return the value of the configuration in correct type."""
405 "max_connections_global" : int,
406 "max_download_speed" : float,
407 "max_upload_speed" : float,
408 "max_upload_slots_global" : int,
409 "max_connections_per_torrent" : int,
410 "max_upload_slots_per_torrent" : int,
411 "listen_ports" : int,
412 "enc_in_policy" : int,
413 "enc_out_policy" : int,
416 if config
in config_convert
:
417 conv
= config_convert
[config
]
418 if isinstance(value
, list):
419 for iter in range(0, len(value
)):
420 value
[iter] = conv(value
[iter])
425 def pref_window(title
):
427 win
.set_property('vertical', True)
429 win
.set_alignment(gnt
.ALIGN_MID
)
431 save
= gnt
.Button("Save")
432 cancel
= gnt
.Button("Cancel")
433 def close_window(b
, window
):
435 cancel
.connect('activate', close_window
, win
)
436 return win
, save
, cancel
438 def add_section(win
, title
):
439 win
.add_widget(gnt
.gnt_label_new_with_format(title
, gnt
.TEXT_FLAG_BOLD | gnt
.TEXT_FLAG_UNDERLINE
))
441 def show_bandwidth_pref(item
):
442 """Bandwidth settings."""
443 win
, save
, cancel
= pref_window("Bandwidth Preference")
445 configs
= client
.get_config()
447 def hook_entry_with_config(entry
, config
):
448 value
= str(configs
[config
])
449 entry
.set_data("config", config
)
450 entry
.set_data("config-value", value
)
451 entry
.set_text(value
)
452 entries
.append(entry
)
454 win
.add_widget(gnt
.gnt_label_new_with_format("Global Bandwidth Usage", gnt
.TEXT_FLAG_BOLD | gnt
.TEXT_FLAG_UNDERLINE
))
455 maxcon
= gnt
.Entry("")
456 maxdlsp
= gnt
.Entry("")
457 maxupsp
= gnt
.Entry("")
458 maxupsl
= gnt
.Entry("")
460 for entry
, config
in ((maxcon
, "max_connections_global"),
461 (maxdlsp
, "max_download_speed"),
462 (maxupsp
, "max_upload_speed"),
463 (maxupsl
, "max_upload_slots_global"),
465 hook_entry_with_config(entry
, config
)
467 for label
, entry
in (("Maximum Connections:", maxcon
),
468 ("Maximum Download Speed (KB/s):", maxdlsp
),
469 ("Maximum Upload Speed (KB/s):", maxupsp
),
470 ("Maximum Upload Slots:", maxupsl
),
472 win
.add_widget(pack_widget(False, [gnt
.Label(label
), entry
]))
474 win
.add_widget(gnt
.gnt_label_new_with_format("Per Torrent Bandwidth Usage", gnt
.TEXT_FLAG_BOLD | gnt
.TEXT_FLAG_UNDERLINE
))
475 maxconpt
= gnt
.Entry("")
476 maxupslpt
= gnt
.Entry("")
477 for entry
, config
in ((maxconpt
, "max_connections_per_torrent"),
478 (maxupslpt
, "max_upload_slots_per_torrent"),
480 hook_entry_with_config(entry
, config
)
482 for label
, entry
in (("Maximum Connections:", maxconpt
),
483 ("Maximum Upload Slots:", maxupslpt
),
485 win
.add_widget(pack_widget(False, [gnt
.Label(label
), entry
]))
487 def save_prefs(b
, window
):
489 for entry
in entries
:
491 config
= entry
.get_data("config")
492 oldv
= entry
.get_data("config-value")
493 value
= entry
.get_text()
495 value
= refine_config_value(config
, value
)
496 newconfigs
[config
] = value
497 sys
.stderr
.write("Changing " + config
+ " to " + str(value
) + "\n")
499 client
.set_config(newconfigs
)
501 except Exception, exc
:
503 save
.connect('activate', save_prefs
, win
)
504 win
.add_widget(gnt
.Line(False))
505 win
.add_widget(pack_widget(False, [save
, cancel
]))
509 def show_network_pref(item
):
510 """Network settings."""
511 win
, save
, cancel
= pref_window("Network Preference")
513 configs
= client
.get_config()
516 def create_checkbox(label
, config
):
517 check
= gnt
.CheckBox(label
)
518 value
= configs
[config
]
519 check
.set_checked(value
)
520 check
.set_data("config", config
)
521 check
.set_data("config-value", value
)
526 add_section(win
, "Ports")
527 randport
= create_checkbox("Use Random Ports", "random_port")
528 win
.add_widget(pack_widget(False, [randport
, gnt
.Label(" (Active Port: " + str(client
.get_listen_port()) + ")")]))
530 add_section(win
, "DHT")
531 dht
= create_checkbox("Enable Mainline DHT", "dht")
534 add_section(win
, "Network Extras")
535 upnp
= create_checkbox("UPnP", "upnp")
536 nat
= create_checkbox("NAT-PMP", "natpmp")
537 pex
= create_checkbox("uTorrent-PeX", "utpex")
538 win
.add_widget(pack_widget(False, [upnp
, nat
, pex
]))
540 def save_callback(vt
, window
):
543 oldv
= check
.get_data("config-value")
544 config
= check
.get_data("config")
545 val
= check
.get_checked()
547 newconfigs
[config
] = val
548 client
.set_config(newconfigs
)
550 save
.connect('activate', save_callback
, win
)
551 win
.add_widget(gnt
.Line(False))
552 win
.add_widget(pack_widget(False, [save
, cancel
]))
556 """Create the menu for the main window."""
557 menu
= gnt
.Menu(gnt
.MENU_TOPLEVEL
)
559 file = gnt
.MenuItem("File")
562 filesub
= gnt
.Menu(gnt
.MENU_POPUP
)
563 file.set_submenu(filesub
)
565 qt
= gnt
.MenuItem("Quit")
569 qt
.connect('activate', qt_cb
)
572 edit
= gnt
.MenuItem("Edit")
575 editsub
= gnt
.Menu(gnt
.MENU_POPUP
)
576 edit
.set_submenu(editsub
)
578 pref
= gnt
.MenuItem("Preference")
579 prefsub
= gnt
.Menu(gnt
.MENU_POPUP
)
580 pref
.set_submenu(prefsub
)
581 editsub
.add_item(pref
)
583 for name
, cb
in (("Downloads", show_dl_preferences
),
584 ("Network", show_network_pref
),
585 ("Bandwidth", show_bandwidth_pref
),
589 dl
= gnt
.MenuItem(name
)
590 dl
.connect('activate', cb
)
593 conn
= gnt
.MenuItem("Connections")
594 editsub
.add_item(conn
)
600 win
.set_property('vertical', True)
601 win
.set_toplevel(True)
602 win
.set_title("Delugent")
604 win
.set_alignment(gnt
.ALIGN_MID
)
610 width
, height
= gnt
.screen_size()
611 list.set_size(width
, height
)
613 hbox
= gnt
.Box(homo
= False, vert
= False)
615 add
= gnt
.Button("Add")
617 def file_selected(widget
, path
, file, window
):
618 files
= widget
.get_selected_multi_files()
620 client
.add_torrent_file(files
)
621 path
= os
.path
.dirname(files
[0])
622 gntui
['load_torrent_location'] = path
623 def change_dl_loc(b
, label
):
624 def location_selected(fl
, path
, file, label
):
626 client
.set_config({'download_location': path
})
627 label
.set_text("Download to: " + path
)
629 fl
.set_title("Select Location")
630 fl
.set_dirs_only(True)
631 fl
.set_current_location(configs
['download_location'])
632 setup_fileselector(fl
, fl
)
633 fl
.connect('file_selected', location_selected
, label
)
637 win
.set_property('vertical', True)
640 configs
= client
.get_config()
643 win
.set_title("Select a Torrent")
644 fl
.set_multi_select(True)
645 fl
.set_current_location(gntui
['load_torrent_location'])
646 fl
.connect("file_selected", file_selected
, win
)
647 setup_fileselector(fl
, win
)
648 gnt
.set_flag(fl
, 8 |
16)
651 downloadto
= gnt
.gnt_label_new_with_format("Download to: " + configs
['download_location'], gnt
.TEXT_FLAG_BOLD
)
652 change
= gnt
.Button("...")
653 change
.connect('activate', change_dl_loc
, downloadto
)
654 box
= pack_widget(False, [downloadto
, change
])
656 box
.set_alignment(gnt
.ALIGN_MID
)
658 win
.add_widget(gnt
.Line(False))
660 win
.give_focus_to_child(fl
)
663 add
.connect('activate', add_clicked
)
665 gnt
.gnt_util_set_trigger_widget(win
, gnt
.KEY_INS
, add
)
667 rm
= gnt
.Button("Remove")
669 tor
= list.get_selection_data()
670 client
.remove_torrent([tor
.id])
671 rm
.connect('activate', rm_clicked
)
673 gnt
.gnt_util_set_trigger_widget(win
, gnt
.KEY_DEL
, rm
)
675 hbox
.add_widget(gnt
.Label("|"))
677 pause
= gnt
.Button("Pause")
678 def pause_clicked(b
):
679 tor
= list.get_selection_data()
680 client
.pause_torrent([tor
.id])
681 pause
.connect('activate', pause_clicked
)
682 hbox
.add_widget(pause
)
683 gnt
.gnt_util_set_trigger_widget(win
, 'p', pause
)
685 resume
= gnt
.Button("Resume")
686 def resume_clicked(b
):
687 tor
= list.get_selection_data()
688 client
.resume_torrent([tor
.id])
689 resume
.connect('activate', resume_clicked
)
690 hbox
.add_widget(resume
)
691 gnt
.gnt_util_set_trigger_widget(win
, 'r', resume
)
693 hbox
.add_widget(gnt
.Label("|"))
695 info
= gnt
.Button("Details")
697 list.perform_action_named('info')
698 info
.connect('activate', info_clicked
)
699 hbox
.add_widget(info
)
703 menu
= create_menu() # XXX: Investigate why this causes warnings on exit
709 setup_deluge_core(list)
710 except Exception, msg
:
715 # Add the torrents in the list
716 session_state
= client
.get_session_state()
717 for torrent_id
in session_state
:
718 list.add_torrent(torrent_id
)
723 if __name__
== "__main__":