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",
71 elif size
> (1 << 20):
74 elif size
> (1 << 10):
77 return str(size
) + unit
79 def show_speed(speed
):
82 speed
= speed
/ (1 << 30)
84 elif speed
> (1 << 20):
85 speed
= speed
/ (1 << 20)
87 elif speed
> (1 << 10):
88 speed
= speed
/ (1 << 10)
90 return ("%.01f" + unit
) % speed
92 def show_state(state
):
93 return deluge
.common
.TORRENT_STATE
[state
]
95 class TorRow(gobject
.GObject
):
106 def __init__(self
, id):
107 self
.__gobject
_init
__()
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
):
132 'info' : ('show_info', 'i'),
133 'toggle_border' : ('toggle_border', 't'),
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,
164 self
.set_column_title(col
, titles
[col
])
165 # Set column widths and alignments
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()
180 def remove_torrent(self
, id):
181 tor
= self
.get_row(id)
182 if tor
: self
.remove(tor
)
184 def add_torrent(self
, id):
186 self
.add_row_after(row
, row
.info(), None)
188 def update_torrent(self
, id, tor
= None):
190 tor
= self
.get_row(id)
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
205 def toggle_border(self
, null
):
206 self
.separator
= not self
.separator
207 self
.set_show_separator(self
.separator
)
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
)
222 win
.set_title("Details")
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
)
238 ("Name", "%s" % status
['name']),
239 ("Path", "%s" % status
['save_path']),
240 ("# of files", "%s" % status
['num_files']),
241 ("Status", show_state(status
['state'])),
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']))),
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
):
269 ok
.connect('activate', close_info_dlg
, win
)
270 box
= gnt
.Box(False, False)
277 def setup_deluge_core():
278 """Do the foreplay with the deluge daemon."""
280 "load_torrent_location" : "/tmp",
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
)
295 def show_dl_preferences(item
):
298 def refine_config_value(config
, value
):
299 """Return the value of the configuration in correct type."""
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,
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])
321 def pref_window(title
):
323 win
.set_property('vertical', True)
325 win
.set_alignment(gnt
.ALIGN_MID
)
327 save
= gnt
.Button("Save")
328 cancel
= gnt
.Button("Cancel")
329 def close_window(b
, window
):
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()
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
):
385 for entry
in entries
:
387 config
= entry
.get_data("config")
388 oldv
= entry
.get_data("config-value")
389 value
= entry
.get_text()
391 value
= refine_config_value(config
, value
)
392 newconfigs
[config
] = value
393 sys
.stderr
.write("Changing " + config
+ " to " + str(value
) + "\n")
395 client
.set_config(newconfigs
)
397 except Exception, exc
:
399 save
.connect('activate', save_prefs
, win
)
400 win
.add_widget(gnt
.Line(False))
401 win
.add_widget(pack_widget(False, [save
, cancel
]))
405 def show_network_pref(item
):
406 """Network settings."""
407 win
, save
, cancel
= pref_window("Network Preference")
409 configs
= client
.get_config()
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
)
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")
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
):
439 oldv
= check
.get_data("config-value")
440 config
= check
.get_data("config")
441 val
= check
.get_checked()
443 newconfigs
[config
] = val
444 client
.set_config(newconfigs
)
446 save
.connect('activate', save_callback
, win
)
447 win
.add_widget(gnt
.Line(False))
448 win
.add_widget(pack_widget(False, [save
, cancel
]))
452 """Create the menu for the main window."""
453 menu
= gnt
.Menu(gnt
.MENU_TOPLEVEL
)
455 file = gnt
.MenuItem("File")
458 filesub
= gnt
.Menu(gnt
.MENU_POPUP
)
459 file.set_submenu(filesub
)
461 qt
= gnt
.MenuItem("Quit")
465 qt
.connect('activate', qt_cb
)
468 edit
= gnt
.MenuItem("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
),
485 dl
= gnt
.MenuItem(name
)
486 dl
.connect('activate', cb
)
489 conn
= gnt
.MenuItem("Connections")
490 editsub
.add_item(conn
)
498 win
.set_property('vertical', True)
499 win
.set_toplevel(True)
500 win
.set_title("Delugent")
502 win
.set_alignment(gnt
.ALIGN_MID
)
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")
514 def file_selected(widget
, path
, file):
515 files
= widget
.get_selected_multi_files()
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
):
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
)
530 add
.connect('activate', add_clicked
)
532 gnt
.gnt_util_set_trigger_widget(win
, gnt
.KEY_INS
, add
)
534 rm
= gnt
.Button("Remove")
536 tor
= list.get_selection_data()
537 client
.remove_torrent([tor
.id])
538 rm
.connect('activate', rm_clicked
)
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")
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
)
564 menu
= create_menu() # XXX: Investigate why this causes warnings on exit
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)
581 if __name__
== "__main__":