deluge/deluge/ui/gtk3/addtorrentdialog.py
Calum Lind 92a048625a [GTK3] Fix the transient parent for PathChooser
The filechooser dialog was wrongly transient to the main window causing
weird behaviour, namely the main window moving but dialog remaining in
place when attempting to move the child dialog.

The solution is to pass the parent dialog to PathChooser so it can be
properly set the filechooser dialog transient property.

Fixed the Preferences dialog not being set to be modal to main window.
2018-11-02 08:45:39 +00:00

1058 lines
39 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
from __future__ import division, unicode_literals
import logging
import os
from base64 import b64decode, b64encode
from xml.sax.saxutils import escape as xml_escape
from xml.sax.saxutils import unescape as xml_unescape
from gi.repository import Gtk
from gi.repository.GObject import TYPE_INT64, TYPE_UINT64
import deluge.common
import deluge.component as component
from deluge.configmanager import ConfigManager
from deluge.httpdownloader import download_file
from deluge.ui.client import client
from deluge.ui.common import TorrentInfo
from .common import get_clipboard_text, listview_replace_treestore, reparent_iter
from .dialogs import ErrorDialog
from .edittrackersdialog import trackers_tiers_from_text
from .path_chooser import PathChooser
from .torrentview_data_funcs import cell_data_size
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(
__package__, os.path.join('glade', 'add_torrent_dialog.ui')
)
)
# The infohash dialog
self.builder.add_from_file(
deluge.common.resource_filename(
__package__, os.path.join('glade', 'add_torrent_dialog.infohash.ui')
)
)
# The url dialog
self.builder.add_from_file(
deluge.common.resource_filename(
__package__, 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(self)
# download?, path, filesize, sequence number, inconsistent?
self.files_treestore = Gtk.TreeStore(
bool, str, TYPE_UINT64, TYPE_INT64, bool, str
)
self.files_treestore.set_sort_column_id(1, Gtk.SortType.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')
self.prefetching_magnets = []
render = Gtk.CellRendererText()
render.connect('edited', self._on_torrent_name_edit)
render.set_property('editable', True)
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, True)
column.set_cell_data_func(render, cell_data_size, 2)
self.listview_files.append_column(column)
self.torrent_liststore = Gtk.ListStore(str, str, str)
self.listview_torrents.set_model(self.torrent_liststore)
self.listview_torrents.set_tooltip_column(2)
self.listview_files.set_model(self.files_treestore)
self.listview_files.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
self.listview_torrents.get_selection().connect(
'changed', self._on_torrent_changed
)
self.torrent_liststore.connect('row-inserted', self.update_dialog_title_count)
self.torrent_liststore.connect('row-deleted', self.update_dialog_title_count)
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',
'super_seeding',
]
# 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):
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()
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.prefetching_magnets = []
self.dialog.set_transient_for(component.get('MainWindow').window)
def _on_config_values(self, config, show=False, focus=False):
self.core_config = config
if self.core_config:
self.set_default_options()
if show:
self._show(focus)
def update_core_config(self, show=False, focus=False):
# Send requests to the core for these config values
d = client.core.get_config_values(self.core_keys)
d.addCallback(self._on_config_values, show, focus)
def _add_torrent_liststore(self, info_hash, name, filename, files, filedata):
"""Add a torrent to torrent_liststore."""
if info_hash in self.files:
return False
torrent_row = [info_hash, name, xml_escape(filename)]
row_iter = self.torrent_liststore.append(torrent_row)
self.files[info_hash] = files
self.infos[info_hash] = filedata
self.listview_torrents.get_selection().select_iter(row_iter)
self.set_default_options()
self.save_torrent_options(row_iter)
return row_iter
def update_dialog_title_count(self, *args):
"""Update the AddTorrent dialog title with current torrent count."""
self.dialog.set_title(_('Add Torrents (%d)') % len(self.torrent_liststore))
def show_already_added_dialog(self, count):
"""Show a message about trying to add duplicate torrents."""
log.debug('Tried to add %d duplicate torrents!', count)
ErrorDialog(
_('Duplicate torrent(s)'),
_(
'You cannot add the same torrent twice.'
' %d torrents were already added.' % count
),
self.dialog,
).run()
def add_from_files(self, filenames):
already_added = 0
for filename in filenames:
# Get the torrent data from the torrent file
try:
info = TorrentInfo(filename)
except Exception as ex:
log.debug('Unable to open torrent file: %s', ex)
ErrorDialog(_('Invalid File'), ex, self.dialog).run()
continue
if not self._add_torrent_liststore(
info.info_hash, info.name, filename, info.files, info.filedata
):
already_added += 1
if already_added:
self.show_already_added_dialog(already_added)
def _on_uri_metadata(self, result, uri):
"""Process prefetched metadata to allow file priority selection."""
info_hash, b64_metadata = result
log.debug('on_uri_metadata for %s (%s)', uri, info_hash)
if info_hash not in self.prefetching_magnets:
return
if b64_metadata:
metadata = b64decode(b64_metadata)
info = TorrentInfo(metadata=metadata)
self.files[info_hash] = info.files
self.infos[info_hash] = info.filedata
else:
log.info('Unable to fetch metadata for magnet: %s', uri)
self.prefetching_magnets.remove(info_hash)
self._on_torrent_changed(self.listview_torrents.get_selection())
def prefetch_waiting_message(self, torrent_id, files):
"""Show magnet files fetching or failed message above files list."""
if torrent_id in self.prefetching_magnets:
self.builder.get_object('prefetch_label').set_text(
_('Please wait for files...')
)
self.builder.get_object('prefetch_spinner').show()
self.builder.get_object('prefetch_hbox').show()
elif not files:
self.builder.get_object('prefetch_label').set_text(
_('Unable to download files for this magnet')
)
self.builder.get_object('prefetch_spinner').hide()
self.builder.get_object('prefetch_hbox').show()
else:
self.builder.get_object('prefetch_hbox').hide()
def add_from_magnets(self, uris):
"""Add a list of magnet uris to torrent_liststore."""
already_added = 0
for uri in uris:
magnet = deluge.common.get_magnet_info(uri)
if not magnet:
log.error('Invalid magnet: %s', uri)
continue
torrent_id = magnet['info_hash']
files = magnet['files_tree']
if not self._add_torrent_liststore(
torrent_id, magnet['name'], xml_escape(uri), files, None
):
already_added += 1
continue
if files:
continue
d = client.core.prefetch_magnet_metadata(uri)
d.addCallback(self._on_uri_metadata, uri)
self.prefetching_magnets.append(magnet['info_hash'])
self.prefetch_waiting_message(torrent_id, None)
if already_added:
self.show_already_added_dialog(already_added)
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()
torrent_id = model.get_value(row, 0)
# Update files list
files_list = self.files[torrent_id]
self.prepare_file_store(files_list)
if self.core_config == {}:
self.update_core_config()
# Update the options frame
self.update_torrent_options(torrent_id)
# Update magnet prefetch message
self.prefetch_waiting_message(torrent_id, files_list)
self.previous_selected_torrent = row
def _on_torrent_name_edit(self, w, row, new_name):
# TODO: Update torrent name
pass
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):
with listview_replace_treestore(self.listview_files):
split_files = {}
for idx, _file in enumerate(files):
self.prepare_file(
_file, _file['path'], idx, _file.get('download', True), split_files
)
self.add_files(None, split_files)
root = Gtk.TreePath.new_first()
self.listview_files.expand_row(root, 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.items():
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],
)
ret += value[1]['size']
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)
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', parent=self.dialog
)
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', parent=self.dialog
)
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']
)
self.builder.get_object('chk_super_seeding').set_active(
options['super_seeding']
)
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()
options['super_seeding'] = self.builder.get_object(
'chk_super_seeding'
).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)
self.builder.get_object('chk_super_seeding').set_active(
self.core_config['super_seeding']
)
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:
# Default lt file priority is 4
files_list.append(4)
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.FileChooserAction.OPEN,
buttons=(
Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN,
Gtk.ResponseType.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('gtk3ui.conf')
if (
'default_load_path' in self.config
and 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.ResponseType.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.ResponseType.OK)
dialog.set_transient_for(self.dialog)
entry.grab_focus()
text = get_clipboard_text()
if text and deluge.common.is_url(text) or deluge.common.is_magnet(text):
entry.set_text(text)
dialog.show_all()
response = dialog.run()
if response == Gtk.ResponseType.OK:
url = deluge.common.decode_bytes(entry.get_text())
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:
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.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
parent=self.dialog,
)
dialog.set_transient_for(self.dialog)
pb = Gtk.ProgressBar()
dialog.vbox.pack_start(pb, True, True, 0)
dialog.show_all()
# Create a tmp file path
import tempfile
tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent')
def on_part(data, current_length, total_length):
if total_length:
percent = current_length / 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):
self.add_from_files([result])
dialog.destroy()
def on_download_fail(result):
log.debug('Download failed: %s', result)
dialog.destroy()
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)
os.close(tmp_fd)
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.ResponseType.OK)
dialog.set_transient_for(self.dialog)
entry.grab_focus()
text = get_clipboard_text()
if deluge.common.is_infohash(text):
entry.set_text(text)
dialog.show_all()
response = dialog.run()
infohash = entry.get_text().strip()
if response == Gtk.ResponseType.OK and deluge.common.is_infohash(infohash):
# Create a list of trackers from the textview buffer
tview_buf = textview.get_buffer()
trackers_text = tview_buf.get_text(
*tview_buf.get_bounds(), include_hidden_chars=False
)
log.debug('Create torrent tracker lines: %s', trackers_text)
trackers = list(trackers_tiers_from_text(trackers_text).keys())
# 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, infohash, 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')
self.add_torrents()
self.hide()
def add_torrents(self):
(model, row) = self.listview_torrents.get_selection().get_selected()
if row is not None:
self.save_torrent_options(row)
torrents_to_add = []
row = self.torrent_liststore.get_iter_first()
while row is not None:
torrent_id = self.torrent_liststore.get_value(row, 0)
filename = xml_unescape(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 self.infos[torrent_id]:
torrents_to_add.append(
(
os.path.split(filename)[-1],
b64encode(self.infos[torrent_id]),
options,
)
)
elif deluge.common.is_magnet(filename):
client.core.add_torrent_magnet(filename, options)
row = self.torrent_liststore.iter_next(row)
def on_torrents_added(errors):
if errors:
log.info(
'Failed to add %d out of %d torrents.',
len(errors),
len(torrents_to_add),
)
for e in errors:
log.info('Torrent add failed: %s', e)
else:
log.info('Successfully added %d torrents.', len(torrents_to_add))
client.core.add_torrent_files(torrents_to_add).addCallback(on_torrents_added)
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]
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
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
root = Gtk.TreePath.new_first()
self.listview_files.expand_row(root, 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)