934 lines
37 KiB
Python
934 lines
37 KiB
Python
#
|
|
# addtorrentdialog.py
|
|
#
|
|
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
|
|
#
|
|
# Deluge is free software.
|
|
#
|
|
# You may redistribute it and/or modify it under the terms of the
|
|
# GNU General Public License, as published by the Free Software
|
|
# Foundation; either version 3 of the License, or (at your option)
|
|
# any later version.
|
|
#
|
|
# deluge is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with deluge. If not, write to:
|
|
# The Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor
|
|
# Boston, MA 02110-1301, USA.
|
|
#
|
|
# In addition, as a special exception, the copyright holders give
|
|
# permission to link the code of portions of this program with the OpenSSL
|
|
# library.
|
|
# You must obey the GNU General Public License in all respects for all of
|
|
# the code used other than OpenSSL. If you modify file(s) with this
|
|
# exception, you may extend this exception to your version of the file(s),
|
|
# but you are not obligated to do so. If you do not wish to do so, delete
|
|
# this exception statement from your version. If you delete this exception
|
|
# statement from all source files in the program, then also delete it here.
|
|
#
|
|
#
|
|
|
|
|
|
import pygtk
|
|
pygtk.require('2.0')
|
|
import gtk
|
|
import gobject
|
|
import base64
|
|
import logging
|
|
import os
|
|
from urlparse import urljoin
|
|
|
|
import twisted.web.client
|
|
import twisted.web.error
|
|
from deluge.ui.client import client
|
|
from deluge.httpdownloader import download_file
|
|
import deluge.component as component
|
|
from torrentview_data_funcs import cell_data_size
|
|
from deluge.configmanager import ConfigManager
|
|
import deluge.common
|
|
import deluge.ui.common
|
|
import dialogs
|
|
import common
|
|
|
|
from deluge.ui.gtkui.path_chooser import PathChooser
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class AddTorrentDialog(component.Component):
|
|
def __init__(self):
|
|
component.Component.__init__(self, "AddTorrentDialog")
|
|
self.builder = gtk.Builder()
|
|
# The base dialog
|
|
self.builder.add_from_file(deluge.common.resource_filename(
|
|
"deluge.ui.gtkui", os.path.join("glade", "add_torrent_dialog.ui")
|
|
))
|
|
# The infohash dialog
|
|
self.builder.add_from_file(deluge.common.resource_filename(
|
|
"deluge.ui.gtkui", os.path.join("glade", "add_torrent_dialog.infohash.ui")
|
|
))
|
|
# The url dialog
|
|
self.builder.add_from_file(deluge.common.resource_filename(
|
|
"deluge.ui.gtkui", os.path.join("glade", "add_torrent_dialog.url.ui")
|
|
))
|
|
|
|
self.dialog = self.builder.get_object("dialog_add_torrent")
|
|
|
|
self.dialog.connect("delete-event", self._on_delete_event)
|
|
|
|
self.builder.connect_signals({
|
|
"on_button_file_clicked": self._on_button_file_clicked,
|
|
"on_button_url_clicked": self._on_button_url_clicked,
|
|
"on_button_hash_clicked": self._on_button_hash_clicked,
|
|
"on_button_remove_clicked": self._on_button_remove_clicked,
|
|
"on_button_trackers_clicked": self._on_button_trackers_clicked,
|
|
"on_button_cancel_clicked": self._on_button_cancel_clicked,
|
|
"on_button_add_clicked": self._on_button_add_clicked,
|
|
"on_button_apply_clicked": self._on_button_apply_clicked,
|
|
"on_button_revert_clicked": self._on_button_revert_clicked,
|
|
"on_chk_move_completed_toggled": self._on_chk_move_completed_toggled
|
|
})
|
|
|
|
self.torrent_liststore = gtk.ListStore(str, str, str)
|
|
#download?, path, filesize, sequence number, inconsistent?
|
|
self.files_treestore = gtk.TreeStore(
|
|
bool, str, gobject.TYPE_UINT64, gobject.TYPE_INT64, bool, str)
|
|
self.files_treestore.set_sort_column_id(1, gtk.SORT_ASCENDING)
|
|
|
|
# Holds the files info
|
|
self.files = {}
|
|
self.infos = {}
|
|
self.core_config = {}
|
|
self.options = {}
|
|
|
|
self.previous_selected_torrent = None
|
|
|
|
self.listview_torrents = self.builder.get_object("listview_torrents")
|
|
self.listview_files = self.builder.get_object("listview_files")
|
|
|
|
render = gtk.CellRendererText()
|
|
column = gtk.TreeViewColumn(_("Torrent"), render, text=1)
|
|
self.listview_torrents.append_column(column)
|
|
|
|
render = gtk.CellRendererToggle()
|
|
render.connect("toggled", self._on_file_toggled)
|
|
column = gtk.TreeViewColumn(None, render, active=0, inconsistent=4)
|
|
self.listview_files.append_column(column)
|
|
|
|
column = gtk.TreeViewColumn(_("Filename"))
|
|
render = gtk.CellRendererPixbuf()
|
|
column.pack_start(render, False)
|
|
column.add_attribute(render, "stock-id", 5)
|
|
render = gtk.CellRendererText()
|
|
render.set_property("editable", True)
|
|
render.connect("edited", self._on_filename_edited)
|
|
column.pack_start(render, True)
|
|
column.add_attribute(render, "text", 1)
|
|
column.set_expand(True)
|
|
self.listview_files.append_column(column)
|
|
|
|
render = gtk.CellRendererText()
|
|
column = gtk.TreeViewColumn(_("Size"))
|
|
column.pack_start(render)
|
|
column.set_cell_data_func(render, cell_data_size, 2)
|
|
self.listview_files.append_column(column)
|
|
|
|
self.listview_torrents.set_model(self.torrent_liststore)
|
|
self.listview_files.set_model(self.files_treestore)
|
|
|
|
self.listview_files.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
|
|
self.listview_torrents.get_selection().connect("changed", self._on_torrent_changed)
|
|
|
|
self.setup_move_completed_path_chooser()
|
|
self.setup_download_location_path_chooser()
|
|
|
|
# Get default config values from the core
|
|
self.core_keys = [
|
|
"pre_allocate_storage",
|
|
"max_connections_per_torrent",
|
|
"max_upload_slots_per_torrent",
|
|
"max_upload_speed_per_torrent",
|
|
"max_download_speed_per_torrent",
|
|
"prioritize_first_last_pieces",
|
|
"sequential_download",
|
|
"add_paused",
|
|
"download_location",
|
|
"download_location_paths_list",
|
|
"move_completed",
|
|
"move_completed_path",
|
|
"move_completed_paths_list",
|
|
]
|
|
#self.core_keys += self.move_completed_path_chooser.get_config_keys()
|
|
self.builder.get_object("notebook1").connect("switch-page", self._on_switch_page)
|
|
|
|
def start(self):
|
|
self.update_core_config()
|
|
|
|
def show(self, focus=False):
|
|
return self.update_core_config(True, focus)
|
|
|
|
def _show(self, focus=False):
|
|
if component.get("MainWindow").is_on_active_workspace():
|
|
self.dialog.set_transient_for(component.get("MainWindow").window)
|
|
else:
|
|
self.dialog.set_transient_for(None)
|
|
|
|
self.dialog.present()
|
|
if focus:
|
|
self.dialog.window.focus()
|
|
|
|
return None
|
|
|
|
def hide(self):
|
|
self.dialog.hide()
|
|
self.files = {}
|
|
self.infos = {}
|
|
self.options = {}
|
|
self.previous_selected_torrent = None
|
|
self.torrent_liststore.clear()
|
|
self.files_treestore.clear()
|
|
self.dialog.set_transient_for(component.get("MainWindow").window)
|
|
return None
|
|
|
|
def update_core_config(self, show=False, focus=False):
|
|
def _on_config_values(config):
|
|
self.core_config = config
|
|
if self.core_config:
|
|
self.set_default_options()
|
|
if show:
|
|
self._show(focus)
|
|
|
|
# Send requests to the core for these config values
|
|
return client.core.get_config_values(self.core_keys).addCallback(_on_config_values)
|
|
|
|
def add_from_files(self, filenames):
|
|
new_row = None
|
|
|
|
for filename in filenames:
|
|
# Get the torrent data from the torrent file
|
|
try:
|
|
info = deluge.ui.common.TorrentInfo(filename)
|
|
except Exception as ex:
|
|
log.debug("Unable to open torrent file: %s", ex)
|
|
dialogs.ErrorDialog(_("Invalid File"), ex, self.dialog).run()
|
|
continue
|
|
|
|
if info.info_hash in self.files:
|
|
log.debug("Trying to add a duplicate torrent!")
|
|
dialogs.ErrorDialog(
|
|
_("Duplicate Torrent"),
|
|
_("You cannot add the same torrent twice."),
|
|
self.dialog
|
|
).run()
|
|
continue
|
|
|
|
new_row = self.torrent_liststore.append([info.info_hash, info.name, filename])
|
|
self.files[info.info_hash] = info.files
|
|
self.infos[info.info_hash] = info.filedata
|
|
self.listview_torrents.get_selection().select_iter(new_row)
|
|
|
|
self.set_default_options()
|
|
self.save_torrent_options(new_row)
|
|
|
|
(model, row) = self.listview_torrents.get_selection().get_selected()
|
|
if not row and new_row:
|
|
self.listview_torrents.get_selection().select_iter(new_row)
|
|
|
|
def add_from_magnets(self, uris):
|
|
new_row = None
|
|
|
|
for uri in uris:
|
|
s = uri.split("&")[0][20:]
|
|
if len(s) == 32:
|
|
info_hash = base64.b32decode(s).encode("hex")
|
|
elif len(s) == 40:
|
|
info_hash = s
|
|
if info_hash in self.infos:
|
|
log.debug("Torrent already in list!")
|
|
continue
|
|
name = None
|
|
for i in uri.split("&"):
|
|
if i[:3] == "dn=":
|
|
name = "%s (%s)" % (i.split("=")[1], uri)
|
|
if not name:
|
|
name = uri
|
|
new_row = self.torrent_liststore.append(
|
|
[info_hash, name, uri])
|
|
self.files[info_hash] = []
|
|
self.infos[info_hash] = None
|
|
self.listview_torrents.get_selection().select_iter(new_row)
|
|
self.set_default_options()
|
|
self.save_torrent_options(new_row)
|
|
|
|
(model, row) = self.listview_torrents.get_selection().get_selected()
|
|
if not row and new_row:
|
|
self.listview_torrents.get_selection().select_iter(new_row)
|
|
|
|
def _on_torrent_changed(self, treeselection):
|
|
(model, row) = treeselection.get_selected()
|
|
if row is None or not model.iter_is_valid(row):
|
|
self.files_treestore.clear()
|
|
self.previous_selected_torrent = None
|
|
return
|
|
|
|
if model[row][0] not in self.files:
|
|
self.files_treestore.clear()
|
|
self.previous_selected_torrent = None
|
|
return
|
|
|
|
# Save the previous torrents options
|
|
self.save_torrent_options()
|
|
# Update files list
|
|
files_list = self.files[model.get_value(row, 0)]
|
|
|
|
self.prepare_file_store(files_list)
|
|
|
|
if self.core_config == {}:
|
|
self.update_core_config()
|
|
|
|
# Update the options frame
|
|
self.update_torrent_options(model.get_value(row, 0))
|
|
|
|
self.previous_selected_torrent = row
|
|
|
|
def _on_switch_page(self, widget, page, page_num):
|
|
# Save the torrent options when switching notebook pages
|
|
self.save_torrent_options()
|
|
|
|
def prepare_file_store(self, files):
|
|
self.listview_files.set_model(None)
|
|
self.files_treestore.clear()
|
|
split_files = {}
|
|
i = 0
|
|
for file in files:
|
|
self.prepare_file(
|
|
file, file["path"], i, file["download"], split_files
|
|
)
|
|
i += 1
|
|
self.add_files(None, split_files)
|
|
self.listview_files.set_model(self.files_treestore)
|
|
self.listview_files.expand_row("0", False)
|
|
|
|
def prepare_file(self, file, file_name, file_num, download, files_storage):
|
|
first_slash_index = file_name.find(os.path.sep)
|
|
if first_slash_index == -1:
|
|
files_storage[file_name] = (file_num, file, download)
|
|
else:
|
|
file_name_chunk = file_name[:first_slash_index+1]
|
|
if file_name_chunk not in files_storage:
|
|
files_storage[file_name_chunk] = {}
|
|
self.prepare_file(file, file_name[first_slash_index+1:],
|
|
file_num, download, files_storage[file_name_chunk])
|
|
|
|
def add_files(self, parent_iter, split_files):
|
|
ret = 0
|
|
for key, value in split_files.iteritems():
|
|
if key.endswith(os.path.sep):
|
|
chunk_iter = self.files_treestore.append(
|
|
parent_iter, [True, key, 0, -1, False, gtk.STOCK_DIRECTORY])
|
|
chunk_size = self.add_files(chunk_iter, value)
|
|
self.files_treestore.set(chunk_iter, 2, chunk_size)
|
|
ret += chunk_size
|
|
else:
|
|
self.files_treestore.append(parent_iter, [
|
|
value[2], key, value[1]["size"],
|
|
value[0], False, gtk.STOCK_FILE
|
|
])
|
|
|
|
if parent_iter and self.files_treestore.iter_has_child(parent_iter):
|
|
# Iterate through the children and see what we should label the
|
|
# folder, download true, download false or inconsistent.
|
|
itr = self.files_treestore.iter_children(parent_iter)
|
|
download = []
|
|
download_value = False
|
|
inconsistent = False
|
|
while itr:
|
|
download.append(self.files_treestore.get_value(itr, 0))
|
|
itr = self.files_treestore.iter_next(itr)
|
|
|
|
if sum(download) == len(download):
|
|
download_value = True
|
|
elif sum(download) == 0:
|
|
download_value = False
|
|
else:
|
|
inconsistent = True
|
|
|
|
self.files_treestore.set_value(parent_iter, 0, download_value)
|
|
self.files_treestore.set_value(parent_iter, 4, inconsistent)
|
|
|
|
ret += value[1]["size"]
|
|
return ret
|
|
|
|
def load_path_choosers_data(self):
|
|
self.move_completed_path_chooser.set_text(self.core_config["move_completed_path"],
|
|
cursor_end=False, default_text=True)
|
|
self.download_location_path_chooser.set_text(self.core_config["download_location"],
|
|
cursor_end=False, default_text=True)
|
|
self.builder.get_object("chk_move_completed").set_active(self.core_config["move_completed"])
|
|
|
|
def setup_move_completed_path_chooser(self):
|
|
self.move_completed_hbox = self.builder.get_object("hbox_move_completed_chooser")
|
|
self.move_completed_path_chooser = PathChooser("move_completed_paths_list")
|
|
self.move_completed_hbox.add(self.move_completed_path_chooser)
|
|
self.move_completed_hbox.show_all()
|
|
|
|
def setup_download_location_path_chooser(self):
|
|
self.download_location_hbox = self.builder.get_object("hbox_download_location_chooser")
|
|
self.download_location_path_chooser = PathChooser("download_location_paths_list")
|
|
self.download_location_hbox.add(self.download_location_path_chooser)
|
|
self.download_location_hbox.show_all()
|
|
|
|
def update_torrent_options(self, torrent_id):
|
|
if torrent_id not in self.options:
|
|
self.set_default_options()
|
|
return
|
|
|
|
options = self.options[torrent_id]
|
|
|
|
self.download_location_path_chooser.set_text(options["download_location"], cursor_end=True)
|
|
self.move_completed_path_chooser.set_text(options["move_completed_path"], cursor_end=True)
|
|
|
|
self.builder.get_object("spin_maxdown").set_value(
|
|
options["max_download_speed"])
|
|
self.builder.get_object("spin_maxup").set_value(
|
|
options["max_upload_speed"])
|
|
self.builder.get_object("spin_maxconnections").set_value(
|
|
options["max_connections"])
|
|
self.builder.get_object("spin_maxupslots").set_value(
|
|
options["max_upload_slots"])
|
|
self.builder.get_object("chk_paused").set_active(
|
|
options["add_paused"])
|
|
self.builder.get_object("chk_pre_alloc").set_active(
|
|
options["pre_allocate_storage"])
|
|
self.builder.get_object("chk_prioritize").set_active(
|
|
options["prioritize_first_last_pieces"])
|
|
self.builder.get_object("chk_sequential_download").set_active(
|
|
options["sequential_download"])
|
|
self.builder.get_object("chk_move_completed").set_active(
|
|
options["move_completed"])
|
|
|
|
def save_torrent_options(self, row=None):
|
|
# Keeps the torrent options dictionary up-to-date with what the user has
|
|
# selected.
|
|
if row is None:
|
|
if self.previous_selected_torrent and \
|
|
self.torrent_liststore.iter_is_valid(self.previous_selected_torrent):
|
|
row = self.previous_selected_torrent
|
|
else:
|
|
return
|
|
|
|
torrent_id = self.torrent_liststore.get_value(row, 0)
|
|
|
|
if torrent_id in self.options:
|
|
options = self.options[torrent_id]
|
|
else:
|
|
options = {}
|
|
|
|
options["download_location"] = self.download_location_path_chooser.get_text()
|
|
options["move_completed_path"] = self.move_completed_path_chooser.get_text()
|
|
options["pre_allocate_storage"] = self.builder.get_object("chk_pre_alloc").get_active()
|
|
options["move_completed"] = self.builder.get_object("chk_move_completed").get_active()
|
|
options["max_download_speed"] = \
|
|
self.builder.get_object("spin_maxdown").get_value()
|
|
options["max_upload_speed"] = \
|
|
self.builder.get_object("spin_maxup").get_value()
|
|
options["max_connections"] = \
|
|
self.builder.get_object("spin_maxconnections").get_value_as_int()
|
|
options["max_upload_slots"] = \
|
|
self.builder.get_object("spin_maxupslots").get_value_as_int()
|
|
options["add_paused"] = \
|
|
self.builder.get_object("chk_paused").get_active()
|
|
options["prioritize_first_last_pieces"] = \
|
|
self.builder.get_object("chk_prioritize").get_active()
|
|
options["sequential_download"] = \
|
|
self.builder.get_object("chk_sequential_download").get_active() or False
|
|
options["move_completed"] = \
|
|
self.builder.get_object("chk_move_completed").get_active()
|
|
options["seed_mode"] = self.builder.get_object("chk_seed_mode").get_active()
|
|
|
|
self.options[torrent_id] = options
|
|
|
|
# Save the file priorities
|
|
files_priorities = self.build_priorities(
|
|
self.files_treestore.get_iter_first(), {}
|
|
)
|
|
|
|
if len(files_priorities) > 0:
|
|
for i, file_dict in enumerate(self.files[torrent_id]):
|
|
file_dict["download"] = files_priorities[i]
|
|
|
|
def build_priorities(self, iter, priorities):
|
|
while iter is not None:
|
|
if self.files_treestore.iter_has_child(iter):
|
|
self.build_priorities(self.files_treestore.iter_children(iter), priorities)
|
|
elif not self.files_treestore.get_value(iter, 1).endswith(os.path.sep):
|
|
priorities[self.files_treestore.get_value(iter, 3)] = self.files_treestore.get_value(iter, 0)
|
|
iter = self.files_treestore.iter_next(iter)
|
|
return priorities
|
|
|
|
def set_default_options(self):
|
|
if not self.core_config:
|
|
# update_core_config will call this method again.
|
|
self.update_core_config()
|
|
return
|
|
|
|
self.load_path_choosers_data()
|
|
|
|
self.builder.get_object("chk_pre_alloc").set_active(
|
|
self.core_config["pre_allocate_storage"])
|
|
self.builder.get_object("spin_maxdown").set_value(
|
|
self.core_config["max_download_speed_per_torrent"])
|
|
self.builder.get_object("spin_maxup").set_value(
|
|
self.core_config["max_upload_speed_per_torrent"])
|
|
self.builder.get_object("spin_maxconnections").set_value(
|
|
self.core_config["max_connections_per_torrent"])
|
|
self.builder.get_object("spin_maxupslots").set_value(
|
|
self.core_config["max_upload_slots_per_torrent"])
|
|
self.builder.get_object("chk_paused").set_active(
|
|
self.core_config["add_paused"])
|
|
self.builder.get_object("chk_prioritize").set_active(
|
|
self.core_config["prioritize_first_last_pieces"])
|
|
self.builder.get_object("chk_sequential_download").set_active(
|
|
self.core_config["sequential_download"])
|
|
self.builder.get_object("chk_move_completed").set_active(
|
|
self.core_config["move_completed"])
|
|
self.builder.get_object("chk_seed_mode").set_active(False)
|
|
|
|
def get_file_priorities(self, torrent_id):
|
|
# A list of priorities
|
|
files_list = []
|
|
|
|
for file_dict in self.files[torrent_id]:
|
|
if not file_dict["download"]:
|
|
files_list.append(0)
|
|
else:
|
|
files_list.append(1)
|
|
|
|
return files_list
|
|
|
|
def _on_file_toggled(self, render, path):
|
|
(model, paths) = self.listview_files.get_selection().get_selected_rows()
|
|
if len(paths) > 1:
|
|
for path in paths:
|
|
row = model.get_iter(path)
|
|
self.toggle_iter(row)
|
|
else:
|
|
row = model.get_iter(path)
|
|
self.toggle_iter(row)
|
|
self.update_treeview_toggles(self.files_treestore.get_iter_first())
|
|
|
|
def toggle_iter(self, iter, toggle_to=None):
|
|
if toggle_to is None:
|
|
toggle_to = not self.files_treestore.get_value(iter, 0)
|
|
self.files_treestore.set_value(iter, 0, toggle_to)
|
|
if self.files_treestore.iter_has_child(iter):
|
|
child = self.files_treestore.iter_children(iter)
|
|
while child is not None:
|
|
self.toggle_iter(child, toggle_to)
|
|
child = self.files_treestore.iter_next(child)
|
|
|
|
def update_treeview_toggles(self, iter):
|
|
toggle_inconsistent = -1
|
|
this_level_toggle = None
|
|
while iter is not None:
|
|
if self.files_treestore.iter_has_child(iter):
|
|
toggle = self.update_treeview_toggles(self.files_treestore.iter_children(iter))
|
|
if toggle == toggle_inconsistent:
|
|
self.files_treestore.set_value(iter, 4, True)
|
|
else:
|
|
self.files_treestore.set_value(iter, 0, toggle)
|
|
#set inconsistent to false
|
|
self.files_treestore.set_value(iter, 4, False)
|
|
else:
|
|
toggle = self.files_treestore.get_value(iter, 0)
|
|
if this_level_toggle is None:
|
|
this_level_toggle = toggle
|
|
elif this_level_toggle != toggle:
|
|
this_level_toggle = toggle_inconsistent
|
|
iter = self.files_treestore.iter_next(iter)
|
|
return this_level_toggle
|
|
|
|
def _on_button_file_clicked(self, widget):
|
|
log.debug("_on_button_file_clicked")
|
|
# Setup the filechooserdialog
|
|
chooser = gtk.FileChooserDialog(
|
|
_("Choose a .torrent file"),
|
|
None,
|
|
gtk.FILE_CHOOSER_ACTION_OPEN,
|
|
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
|
|
gtk.RESPONSE_OK)
|
|
)
|
|
|
|
chooser.set_transient_for(self.dialog)
|
|
chooser.set_select_multiple(True)
|
|
chooser.set_property("skip-taskbar-hint", True)
|
|
chooser.set_local_only(False)
|
|
|
|
# Add .torrent and * file filters
|
|
file_filter = gtk.FileFilter()
|
|
file_filter.set_name(_("Torrent files"))
|
|
file_filter.add_pattern("*." + "torrent")
|
|
chooser.add_filter(file_filter)
|
|
file_filter = gtk.FileFilter()
|
|
file_filter.set_name(_("All files"))
|
|
file_filter.add_pattern("*")
|
|
chooser.add_filter(file_filter)
|
|
|
|
# Load the 'default_load_path' from the config
|
|
self.config = ConfigManager("gtkui.conf")
|
|
if self.config["default_load_path"] is not None:
|
|
chooser.set_current_folder(self.config["default_load_path"])
|
|
|
|
# Run the dialog
|
|
response = chooser.run()
|
|
|
|
if response == gtk.RESPONSE_OK:
|
|
result = chooser.get_filenames()
|
|
self.config["default_load_path"] = chooser.get_current_folder()
|
|
else:
|
|
chooser.destroy()
|
|
return
|
|
|
|
chooser.destroy()
|
|
self.add_from_files(result)
|
|
|
|
def _on_button_url_clicked(self, widget):
|
|
log.debug("_on_button_url_clicked")
|
|
dialog = self.builder.get_object("url_dialog")
|
|
entry = self.builder.get_object("entry_url")
|
|
|
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
|
dialog.set_transient_for(self.dialog)
|
|
entry.grab_focus()
|
|
|
|
text = (gtk.clipboard_get(selection='PRIMARY').wait_for_text() or
|
|
gtk.clipboard_get().wait_for_text())
|
|
if text:
|
|
text = text.strip()
|
|
if deluge.common.is_url(text) or deluge.common.is_magnet(text):
|
|
entry.set_text(text)
|
|
|
|
dialog.show_all()
|
|
response = dialog.run()
|
|
|
|
if response == gtk.RESPONSE_OK:
|
|
url = entry.get_text().decode("utf-8")
|
|
else:
|
|
url = None
|
|
|
|
entry.set_text("")
|
|
dialog.hide()
|
|
|
|
# This is where we need to fetch the .torrent file from the URL and
|
|
# add it to the list.
|
|
log.debug("url: %s", url)
|
|
if url:
|
|
if deluge.common.is_url(url):
|
|
self.add_from_url(url)
|
|
elif deluge.common.is_magnet(url):
|
|
self.add_from_magnets([url])
|
|
else:
|
|
dialogs.ErrorDialog(
|
|
_("Invalid URL"),
|
|
"%s %s" % (url, _("is not a valid URL.")),
|
|
self.dialog
|
|
).run()
|
|
|
|
def add_from_url(self, url):
|
|
dialog = gtk.Dialog(
|
|
_("Downloading..."),
|
|
flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
|
|
parent=self.dialog)
|
|
dialog.set_transient_for(self.dialog)
|
|
|
|
pb = gtk.ProgressBar()
|
|
dialog.vbox.pack_start(pb, True, True)
|
|
dialog.show_all()
|
|
|
|
# Create a tmp file path
|
|
import tempfile
|
|
(tmp_handle, tmp_file) = tempfile.mkstemp()
|
|
|
|
def on_part(data, current_length, total_length):
|
|
if total_length:
|
|
percent = float(current_length) / float(total_length)
|
|
pb.set_fraction(percent)
|
|
pb.set_text("%.2f%% (%s / %s)" % (
|
|
percent * 100,
|
|
deluge.common.fsize(current_length),
|
|
deluge.common.fsize(total_length)))
|
|
else:
|
|
pb.pulse()
|
|
pb.set_text("%s" % deluge.common.fsize(current_length))
|
|
|
|
def on_download_success(result):
|
|
log.debug("Download success!")
|
|
self.add_from_files([result])
|
|
dialog.destroy()
|
|
|
|
def on_download_fail(result):
|
|
if result.check(twisted.web.error.PageRedirect):
|
|
new_url = urljoin(url, result.getErrorMessage().split(" to ")[1])
|
|
result = download_file(new_url, tmp_file, on_part)
|
|
result.addCallbacks(on_download_success, on_download_fail)
|
|
elif result.check(twisted.web.client.PartialDownloadError):
|
|
result = download_file(url, tmp_file, on_part, allow_compression=False)
|
|
result.addCallbacks(on_download_success, on_download_fail)
|
|
else:
|
|
log.debug("Download failed: %s", result)
|
|
dialog.destroy()
|
|
dialogs.ErrorDialog(
|
|
_("Download Failed"), "%s %s" % (_("Failed to download:"), url),
|
|
details=result.getErrorMessage(), parent=self.dialog
|
|
).run()
|
|
return result
|
|
|
|
d = download_file(url, tmp_file, on_part)
|
|
d.addCallbacks(on_download_success, on_download_fail)
|
|
|
|
def _on_button_hash_clicked(self, widget):
|
|
log.debug("_on_button_hash_clicked")
|
|
dialog = self.builder.get_object("dialog_infohash")
|
|
entry = self.builder.get_object("entry_hash")
|
|
textview = self.builder.get_object("text_trackers")
|
|
|
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
|
dialog.set_transient_for(self.dialog)
|
|
entry.grab_focus()
|
|
dialog.show_all()
|
|
response = dialog.run()
|
|
if response == gtk.RESPONSE_OK and len(entry.get_text()) == 40:
|
|
trackers = []
|
|
b = textview.get_buffer()
|
|
lines = b.get_text(b.get_start_iter(), b.get_end_iter()).strip().split("\n")
|
|
log.debug("lines: %s", lines)
|
|
for l in lines:
|
|
if deluge.common.is_url(l):
|
|
trackers.append(l)
|
|
# Convert the information to a magnet uri, this is just easier to
|
|
# handle this way.
|
|
log.debug("trackers: %s", trackers)
|
|
magnet = deluge.common.create_magnet_uri(
|
|
infohash=entry.get_text().decode("utf-8"),
|
|
trackers=trackers)
|
|
log.debug("magnet uri: %s", magnet)
|
|
self.add_from_magnets([magnet])
|
|
|
|
entry.set_text("")
|
|
textview.get_buffer().set_text("")
|
|
dialog.hide()
|
|
|
|
def _on_button_remove_clicked(self, widget):
|
|
log.debug("_on_button_remove_clicked")
|
|
(model, row) = self.listview_torrents.get_selection().get_selected()
|
|
if row is None:
|
|
return
|
|
|
|
torrent_id = model.get_value(row, 0)
|
|
|
|
model.remove(row)
|
|
del self.files[torrent_id]
|
|
del self.infos[torrent_id]
|
|
|
|
def _on_button_trackers_clicked(self, widget):
|
|
log.debug("_on_button_trackers_clicked")
|
|
|
|
def _on_button_cancel_clicked(self, widget):
|
|
log.debug("_on_button_cancel_clicked")
|
|
self.hide()
|
|
|
|
def _on_button_add_clicked(self, widget):
|
|
log.debug("_on_button_add_clicked")
|
|
# Save the options for selected torrent prior to adding
|
|
(model, row) = self.listview_torrents.get_selection().get_selected()
|
|
if row is not None:
|
|
self.save_torrent_options(row)
|
|
|
|
row = self.torrent_liststore.get_iter_first()
|
|
while row is not None:
|
|
torrent_id = self.torrent_liststore.get_value(row, 0)
|
|
filename = self.torrent_liststore.get_value(row, 2)
|
|
try:
|
|
options = self.options[torrent_id]
|
|
except KeyError:
|
|
options = None
|
|
|
|
file_priorities = self.get_file_priorities(torrent_id)
|
|
if options is not None:
|
|
options["file_priorities"] = file_priorities
|
|
|
|
if deluge.common.is_magnet(filename):
|
|
del options["file_priorities"]
|
|
client.core.add_torrent_magnet(filename, options)
|
|
else:
|
|
client.core.add_torrent_file(
|
|
os.path.split(filename)[-1],
|
|
base64.encodestring(self.infos[torrent_id]),
|
|
options
|
|
)
|
|
|
|
row = self.torrent_liststore.iter_next(row)
|
|
self.hide()
|
|
|
|
def _on_button_apply_clicked(self, widget):
|
|
log.debug("_on_button_apply_clicked")
|
|
(model, row) = self.listview_torrents.get_selection().get_selected()
|
|
if row is None:
|
|
return
|
|
|
|
self.save_torrent_options(row)
|
|
|
|
# The options, except file renames, we want all the torrents to have
|
|
options = self.options[model.get_value(row, 0)].copy()
|
|
options.pop("mapped_files", None)
|
|
|
|
# Set all the torrent options
|
|
row = model.get_iter_first()
|
|
while row is not None:
|
|
torrent_id = model.get_value(row, 0)
|
|
self.options[torrent_id].update(options)
|
|
row = model.iter_next(row)
|
|
|
|
def _on_button_revert_clicked(self, widget):
|
|
log.debug("_on_button_revert_clicked")
|
|
(model, row) = self.listview_torrents.get_selection().get_selected()
|
|
if row is None:
|
|
return
|
|
|
|
del self.options[model.get_value(row, 0)]
|
|
self.set_default_options()
|
|
|
|
def _on_chk_move_completed_toggled(self, widget):
|
|
value = widget.get_active()
|
|
self.move_completed_path_chooser.set_sensitive(value)
|
|
|
|
def _on_delete_event(self, widget, event):
|
|
self.hide()
|
|
return True
|
|
|
|
def get_file_path(self, row, path=""):
|
|
if not row:
|
|
return path
|
|
|
|
path = self.files_treestore[row][1] + path
|
|
return self.get_file_path(self.files_treestore.iter_parent(row), path)
|
|
|
|
def _on_filename_edited(self, renderer, path, new_text):
|
|
index = self.files_treestore[path][3]
|
|
|
|
new_text = new_text.strip(os.path.sep).strip()
|
|
|
|
# Return if the text hasn't changed
|
|
if new_text == self.files_treestore[path][1]:
|
|
return
|
|
|
|
# Get the tree iter
|
|
itr = self.files_treestore.get_iter(path)
|
|
|
|
# Get the torrent_id
|
|
(model, row) = self.listview_torrents.get_selection().get_selected()
|
|
torrent_id = model[row][0]
|
|
|
|
if "mapped_files" not in self.options[torrent_id]:
|
|
self.options[torrent_id]["mapped_files"] = {}
|
|
|
|
if index > -1:
|
|
# We're renaming a file! Yay! That's easy!
|
|
if not new_text:
|
|
return
|
|
parent = self.files_treestore.iter_parent(itr)
|
|
file_path = os.path.join(self.get_file_path(parent), new_text)
|
|
# Don't rename if filename exists
|
|
if parent:
|
|
for row in self.files_treestore[parent].iterchildren():
|
|
if new_text == row[1]:
|
|
return
|
|
if os.path.sep in new_text:
|
|
# There are folders in this path, so we need to create them
|
|
# and then move the file iter to top
|
|
split_text = new_text.split(os.path.sep)
|
|
for s in split_text[:-1]:
|
|
parent = self.files_treestore.append(parent, [True, s, 0, -1, False, gtk.STOCK_DIRECTORY])
|
|
|
|
self.files_treestore[itr][1] = split_text[-1]
|
|
common.reparent_iter(self.files_treestore, itr, parent)
|
|
else:
|
|
# Update the row's text
|
|
self.files_treestore[itr][1] = new_text
|
|
|
|
# Update the mapped_files dict in the options with the index and new
|
|
# file path.
|
|
# We'll send this to the core when adding the torrent so it knows
|
|
# what to rename before adding.
|
|
self.options[torrent_id]["mapped_files"][index] = file_path
|
|
self.files[torrent_id][index]['path'] = file_path
|
|
else:
|
|
# Folder!
|
|
def walk_tree(row):
|
|
if not row:
|
|
return
|
|
|
|
# Get the file path base once, since it will be the same for
|
|
# all siblings
|
|
file_path_base = self.get_file_path(
|
|
self.files_treestore.iter_parent(row)
|
|
)
|
|
|
|
# Iterate through all the siblings at this level
|
|
while row:
|
|
# We recurse if there are children
|
|
if self.files_treestore.iter_has_child(row):
|
|
walk_tree(self.files_treestore.iter_children(row))
|
|
|
|
index = self.files_treestore[row][3]
|
|
|
|
if index > -1:
|
|
# Get the new full path for this file
|
|
file_path = file_path_base + self.files_treestore[row][1]
|
|
|
|
# Update the file path in the mapped_files dict
|
|
self.options[torrent_id]["mapped_files"][index] = file_path
|
|
self.files[torrent_id][index]['path'] = file_path
|
|
|
|
# Get the next siblings iter
|
|
row = self.files_treestore.iter_next(row)
|
|
|
|
# Update the treestore row first so that when walking the tree
|
|
# we can construct the new proper paths
|
|
|
|
# We need to check if this folder has been split
|
|
if os.path.sep in new_text:
|
|
# It's been split, so we need to add new folders and then re-parent
|
|
# itr.
|
|
parent = self.files_treestore.iter_parent(itr)
|
|
split_text = new_text.split(os.path.sep)
|
|
for s in split_text[:-1]:
|
|
# We don't iterate over the last item because we'll just use
|
|
# the existing itr and change the text
|
|
parent = self.files_treestore.append(parent, [
|
|
True, s + os.path.sep, 0, -1, False, gtk.STOCK_DIRECTORY
|
|
])
|
|
|
|
self.files_treestore[itr][1] = split_text[-1] + os.path.sep
|
|
|
|
# Now re-parent itr to parent
|
|
common.reparent_iter(self.files_treestore, itr, parent)
|
|
itr = parent
|
|
|
|
# We need to re-expand the view because it might contracted
|
|
# if we change the root iter
|
|
self.listview_files.expand_row("0", False)
|
|
else:
|
|
# This was a simple folder rename without any splits, so just
|
|
# change the path for itr
|
|
self.files_treestore[itr][1] = new_text + os.path.sep
|
|
|
|
# Walk through the tree from 'itr' and add all the new file paths
|
|
# to the 'mapped_files' option
|
|
walk_tree(itr)
|