From 2e2851dc13d73352530dd4495c7e05603b2e520d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 23:38:38 +0200 Subject: Adding upstream version 2.1.2~dev0+20240219. Signed-off-by: Daniel Baumann --- deluge/ui/gtk3/createtorrentdialog.py | 517 ++++++++++++++++++++++++++++++++++ 1 file changed, 517 insertions(+) create mode 100644 deluge/ui/gtk3/createtorrentdialog.py (limited to 'deluge/ui/gtk3/createtorrentdialog.py') diff --git a/deluge/ui/gtk3/createtorrentdialog.py b/deluge/ui/gtk3/createtorrentdialog.py new file mode 100644 index 0000000..e9f1690 --- /dev/null +++ b/deluge/ui/gtk3/createtorrentdialog.py @@ -0,0 +1,517 @@ +# +# Copyright (C) 2008 Andrew Resch +# +# 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. +# + +import logging +import os.path +from base64 import b64encode + +from gi.repository import Gtk +from gi.repository.GObject import TYPE_UINT64, idle_add +from twisted.internet.threads import deferToThread + +import deluge.component as component +from deluge.common import decode_bytes, get_path_size, is_url, resource_filename +from deluge.configmanager import ConfigManager +from deluge.ui.client import client + +from .edittrackersdialog import ( + last_tier_trackers_from_liststore, + trackers_tiers_from_text, +) +from .torrentview_data_funcs import cell_data_size + +log = logging.getLogger(__name__) + + +class CreateTorrentDialog: + def __init__(self): + pass + + def show(self): + self.builder = Gtk.Builder() + + ui_filenames = [ + 'create_torrent_dialog.ui', + 'create_torrent_dialog.remote_path.ui', + 'create_torrent_dialog.remote_save.ui', + 'create_torrent_dialog.progress.ui', + ] + for filename in ui_filenames: + self.builder.add_from_file( + resource_filename(__package__, os.path.join('glade', filename)) + ) + + self.config = ConfigManager('gtk3ui.conf') + + self.dialog = self.builder.get_object('create_torrent_dialog') + self.dialog.set_transient_for(component.get('MainWindow').window) + + self.builder.connect_signals(self) + + # path, icon, size + self.files_treestore = Gtk.TreeStore(str, str, TYPE_UINT64) + + column = Gtk.TreeViewColumn(_('Filename')) + render = Gtk.CellRendererPixbuf() + column.pack_start(render, False) + column.add_attribute(render, 'icon-name', 1) + render = Gtk.CellRendererText() + column.pack_start(render, True) + column.add_attribute(render, 'text', 0) + column.set_expand(True) + self.builder.get_object('treeview_files').append_column(column) + + column = Gtk.TreeViewColumn(_('Size')) + render = Gtk.CellRendererText() + column.pack_start(render, True) + column.set_cell_data_func(render, cell_data_size, 2) + self.builder.get_object('treeview_files').append_column(column) + + self.builder.get_object('treeview_files').set_model(self.files_treestore) + self.builder.get_object('treeview_files').set_show_expanders(False) + + # tier, url + self.trackers_liststore = Gtk.ListStore(int, str) + + self.builder.get_object('tracker_treeview').append_column( + Gtk.TreeViewColumn(_('Tier'), Gtk.CellRendererText(), text=0) + ) + self.builder.get_object('tracker_treeview').append_column( + Gtk.TreeViewColumn(_('Tracker'), Gtk.CellRendererText(), text=1) + ) + + self.builder.get_object('tracker_treeview').set_model(self.trackers_liststore) + self.trackers_liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING) + + if not client.is_localhost() and client.connected(): + self.builder.get_object('button_remote_path').show() + else: + self.builder.get_object('button_remote_path').hide() + + self.dialog.show() + + def parse_piece_size_text(self, value): + psize, metric = value.split() + psize = int(psize) + if psize < 32: + # This is a MiB value + psize = psize * 1024 * 1024 + else: + # This is a KiB value + psize = psize * 1024 + + return psize + + def adjust_piece_size(self): + """Adjusts the recommended piece based on the file/folder/path selected.""" + size = self.files_treestore[0][2] + model = self.builder.get_object('combo_piece_size').get_model() + for index, value in enumerate(model): + psize = self.parse_piece_size_text(value[0]) + pieces = size // psize + if pieces < 2048 or (index + 1) == len(model): + self.builder.get_object('combo_piece_size').set_active(index) + break + + def on_button_file_clicked(self, widget): + log.debug('on_button_file_clicked') + # Setup the filechooserdialog + chooser = Gtk.FileChooserDialog( + _('Choose a file'), + self.dialog, + Gtk.FileChooserAction.OPEN, + buttons=( + _('_Cancel'), + Gtk.ResponseType.CANCEL, + _('_Open'), + Gtk.ResponseType.OK, + ), + ) + + chooser.set_transient_for(self.dialog) + chooser.set_select_multiple(False) + chooser.set_property('skip-taskbar-hint', True) + + # Run the dialog + response = chooser.run() + + if response == Gtk.ResponseType.OK: + result = chooser.get_filename() + else: + chooser.destroy() + return + + path = decode_bytes(result) + + self.files_treestore.clear() + self.files_treestore.append( + None, [result, 'text-x-generic-symbolic', get_path_size(path)] + ) + self.adjust_piece_size() + chooser.destroy() + + def on_button_folder_clicked(self, widget): + log.debug('on_button_folder_clicked') + # Setup the filechooserdialog + chooser = Gtk.FileChooserDialog( + _('Choose a folder'), + self.dialog, + Gtk.FileChooserAction.SELECT_FOLDER, + buttons=( + _('_Cancel'), + Gtk.ResponseType.CANCEL, + _('_Open'), + Gtk.ResponseType.OK, + ), + ) + + chooser.set_transient_for(self.dialog) + chooser.set_select_multiple(False) + chooser.set_property('skip-taskbar-hint', True) + # Run the dialog + response = chooser.run() + + if response == Gtk.ResponseType.OK: + result = chooser.get_filename() + else: + chooser.destroy() + return + + path = decode_bytes(result) + + self.files_treestore.clear() + self.files_treestore.append( + None, [result, 'document-open-symbolic', get_path_size(path)] + ) + self.adjust_piece_size() + chooser.destroy() + + def on_button_remote_path_clicked(self, widget): + log.debug('on_button_remote_path_clicked') + dialog = self.builder.get_object('remote_path_dialog') + entry = self.builder.get_object('entry_path') + dialog.set_transient_for(self.dialog) + entry.set_text('/') + entry.grab_focus() + response = dialog.run() + + if response == Gtk.ResponseType.OK: + result = entry.get_text() + + def _on_get_path_size(size): + log.debug('size: %s', size) + if size > 0: + self.files_treestore.clear() + self.files_treestore.append( + None, [result, 'network-workgroup-symbolic', size] + ) + self.adjust_piece_size() + + client.core.get_path_size(result).addCallback(_on_get_path_size) + client.force_call(True) + + dialog.hide() + + def on_button_cancel_clicked(self, widget): + log.debug('on_button_cancel_clicked') + self.dialog.destroy() + + def on_button_save_clicked(self, widget): + log.debug('on_button_save_clicked') + if len(self.files_treestore) == 0: + return + + # Get the path + path = self.files_treestore[0][0].rstrip('\\/') + torrent_filename = '%s.torrent' % os.path.split(path)[-1] + + is_remote = 'network' in self.files_treestore[0][1] + + if is_remote: + # This is a remote path + dialog = self.builder.get_object('remote_save_dialog') + dialog.set_transient_for(self.dialog) + dialog_save_path = self.builder.get_object('entry_save_path') + dialog_save_path.set_text(path + '.torrent') + response = dialog.run() + if response == Gtk.ResponseType.OK: + result = dialog_save_path.get_text() + else: + dialog.hide() + return + dialog.hide() + else: + # Setup the filechooserdialog + chooser = Gtk.FileChooserDialog( + _('Save .torrent file'), + self.dialog, + Gtk.FileChooserAction.SAVE, + buttons=( + _('_Cancel'), + Gtk.ResponseType.CANCEL, + _('_Save'), + Gtk.ResponseType.OK, + ), + ) + + chooser.set_transient_for(self.dialog) + chooser.set_select_multiple(False) + chooser.set_property('skip-taskbar-hint', True) + + # 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) + + chooser.set_current_name(torrent_filename) + # Run the dialog + response = chooser.run() + + if response == Gtk.ResponseType.OK: + result = chooser.get_filename() + else: + chooser.destroy() + return + chooser.destroy() + + # Fix up torrent filename + if len(result) < 9: + result += '.torrent' + elif result[-8:] != '.torrent': + result += '.torrent' + + # Get a list of trackers + trackers = [] + if not len(self.trackers_liststore): + tracker = None + else: + # Create a list of lists [[tier0, ...], [tier1, ...], ...] + tier_dict = {} + for tier, tracker in self.trackers_liststore: + tier_dict.setdefault(tier, []).append(tracker) + + trackers = [tier_dict[tier] for tier in sorted(tier_dict)] + # Get the first tracker in the first tier + tracker = trackers[0][0] + + # Get a list of webseeds + textview_buf = self.builder.get_object('textview_webseeds').get_buffer() + lines = ( + textview_buf.get_text( + *textview_buf.get_bounds(), include_hidden_chars=False + ) + .strip() + .split('\n') + ) + webseeds = [] + for line in lines: + line = line.replace('\\', '/') # Fix any mistyped urls. + if is_url(line): + webseeds.append(line) + # Get the piece length in bytes + combo = self.builder.get_object('combo_piece_size') + piece_length = self.parse_piece_size_text( + combo.get_model()[combo.get_active()][0] + ) + + author = self.builder.get_object('entry_author').get_text() + comment = self.builder.get_object('entry_comments').get_text() + private = self.builder.get_object('chk_private_flag').get_active() + add_to_session = self.builder.get_object('chk_add_to_session').get_active() + + if is_remote: + + def torrent_created(): + self.builder.get_object('progress_dialog').hide() + client.deregister_event_handler( + 'CreateTorrentProgressEvent', on_create_torrent_progress_event + ) + + def on_create_torrent_progress_event(piece_count, num_pieces): + self._on_create_torrent_progress(piece_count, num_pieces) + if piece_count == num_pieces: + from twisted.internet import reactor + + reactor.callLater(0.5, torrent_created) + + client.register_event_handler( + 'CreateTorrentProgressEvent', on_create_torrent_progress_event + ) + + client.core.create_torrent( + decode_bytes(path), + tracker, + piece_length, + comment, + decode_bytes(result), + webseeds, + private, + author, + trackers, + add_to_session, + ) + + else: + + def hide_progress(result): + self.builder.get_object('progress_dialog').hide() + + deferToThread( + self.create_torrent, + decode_bytes(path), + tracker, + piece_length, + self._on_create_torrent_progress, + comment, + decode_bytes(result), + webseeds, + private, + author, + trackers, + add_to_session, + ).addCallback(hide_progress) + + # Setup progress dialog + self.builder.get_object('progress_dialog').set_transient_for( + component.get('MainWindow').window + ) + self.builder.get_object('progress_dialog').show_all() + + self.dialog.destroy() + + def create_torrent( + self, + path, + tracker, + piece_length, + progress, + comment, + target, + webseeds, + private, + created_by, + trackers, + add_to_session, + ): + import deluge.metafile + + deluge.metafile.make_meta_file( + path, + tracker, + piece_length, + progress=progress, + comment=comment, + target=target, + webseeds=webseeds, + private=private, + created_by=created_by, + trackers=trackers, + ) + + if add_to_session: + with open(target, 'rb') as _file: + filedump = b64encode(_file.read()) + client.core.add_torrent_file_async( + os.path.split(target)[-1], + filedump, + {'download_location': os.path.split(path)[0]}, + ) + + def _on_create_torrent_progress(self, value, num_pieces): + percent = value / num_pieces + + def update_pbar_with_gobject(percent): + pbar = self.builder.get_object('progressbar') + pbar.set_text('%.2f%%' % (percent * 100)) + pbar.set_fraction(percent) + return False + + if percent >= 0 and percent <= 1.0: + # Make sure there are no threads race conditions that can + # crash the UI while updating it. + idle_add(update_pbar_with_gobject, percent) + + def on_button_up_clicked(self, widget): + log.debug('on_button_up_clicked') + row = ( + self.builder.get_object('tracker_treeview') + .get_selection() + .get_selected()[1] + ) + if row is None: + return + if self.trackers_liststore[row][0] == 0: + return + else: + self.trackers_liststore[row][0] -= 1 + + def on_button_down_clicked(self, widget): + log.debug('on_button_down_clicked') + row = ( + self.builder.get_object('tracker_treeview') + .get_selection() + .get_selected()[1] + ) + if row is None: + return + self.trackers_liststore[row][0] += 1 + + def on_button_add_clicked(self, widget): + log.debug('on_button_add_clicked') + builder = Gtk.Builder() + builder.add_from_file( + resource_filename( + __package__, os.path.join('glade', 'edit_trackers.add.ui') + ) + ) + dialog = builder.get_object('add_tracker_dialog') + dialog.set_transient_for(self.dialog) + textview = builder.get_object('textview_trackers') + if self.config['createtorrent.trackers']: + textview.get_buffer().set_text( + '\n'.join(self.config['createtorrent.trackers']) + ) + else: + textview.get_buffer().set_text('') + textview.grab_focus() + response = dialog.run() + + if response == Gtk.ResponseType.OK: + # Create a list of trackers from the textview buffer + textview_buf = textview.get_buffer() + trackers_text = textview_buf.get_text( + *textview_buf.get_bounds(), include_hidden_chars=False + ) + log.debug('Create torrent tracker lines: %s', trackers_text) + self.config['createtorrent.trackers'] = trackers_text.split('/n') + + # Append trackers liststore with unique trackers and tiers starting from last tier number. + last_tier, orig_trackers = last_tier_trackers_from_liststore( + self.trackers_liststore + ) + for tracker, tier in trackers_tiers_from_text(trackers_text).items(): + if tracker not in orig_trackers: + self.trackers_liststore.append([tier + last_tier, tracker]) + + dialog.destroy() + + def on_button_remove_clicked(self, widget): + log.debug('on_button_remove_clicked') + row = ( + self.builder.get_object('tracker_treeview') + .get_selection() + .get_selected()[1] + ) + if row is None: + return + self.trackers_liststore.remove(row) -- cgit v1.2.3