summaryrefslogtreecommitdiffstats
path: root/deluge/ui/gtk3/createtorrentdialog.py
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/ui/gtk3/createtorrentdialog.py')
-rw-r--r--deluge/ui/gtk3/createtorrentdialog.py517
1 files changed, 517 insertions, 0 deletions
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 <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.
+#
+
+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)