deluge/deluge/ui/gtk3/addtorrentdialog.py
Calum Lind 01fafd4fe0 [GTK3] Change module structure from ui/gtkui to ui/gtk3
This moves the directory structure so that there is no conflict with the
old gtk2 UI. Also changes the conf and state files being loaded.
2018-11-02 08:45:39 +00:00

1052 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)
# FIXME add back expand_row
# self.listview_files.expand_row(b'0', False)
self.listview_files.expand_all()
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')
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']
)
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 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 = 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:
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.RESPONSE_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())
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
# FIXME add back expand_row
# self.listview_files.expand_row(b'0', False)
self.listview_files.expand_all()
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)