deluge/deluge/ui/gtkui/createtorrentdialog.py

455 lines
17 KiB
Python

# -*- coding: utf-8 -*-
#
# 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.
#
from __future__ import division
import base64
import logging
import os.path
import gobject
import gtk
from twisted.internet.threads import deferToThread
import deluge.component as component
from deluge.common import get_path_size, is_url, resource_filename
from deluge.configmanager import ConfigManager
from deluge.ui.client import client
from deluge.ui.gtkui.torrentview_data_funcs import cell_data_size
log = logging.getLogger(__name__)
class CreateTorrentDialog(object):
def __init__(self):
pass
def show(self):
self.builder = gtk.Builder()
# The main dialog
self.builder.add_from_file(resource_filename(
"deluge.ui.gtkui", os.path.join("glade", "create_torrent_dialog.ui")
))
# The remote path dialog
self.builder.add_from_file(resource_filename(
"deluge.ui.gtkui", os.path.join("glade", "create_torrent_dialog.remote_path.ui")
))
# The remote save dialog
self.builder.add_from_file(resource_filename(
"deluge.ui.gtkui", os.path.join("glade", "create_torrent_dialog.remote_save.ui")
))
# The progress dialog
self.builder.add_from_file(resource_filename(
"deluge.ui.gtkui", os.path.join("glade", "create_torrent_dialog.progress.ui")
))
self.config = ConfigManager("gtkui.conf")
self.dialog = self.builder.get_object("create_torrent_dialog")
self.dialog.set_transient_for(component.get("MainWindow").window)
self.builder.connect_signals({
"on_button_file_clicked": self._on_button_file_clicked,
"on_button_folder_clicked": self._on_button_folder_clicked,
"on_button_remote_path_clicked": self._on_button_remote_path_clicked,
"on_button_cancel_clicked": self._on_button_cancel_clicked,
"on_button_save_clicked": self._on_button_save_clicked,
"on_button_up_clicked": self._on_button_up_clicked,
"on_button_add_clicked": self._on_button_add_clicked,
"on_button_remove_clicked": self._on_button_remove_clicked,
"on_button_down_clicked": self._on_button_down_clicked
})
# path, icon, size
self.files_treestore = gtk.TreeStore(str, str, gobject.TYPE_UINT64)
column = gtk.TreeViewColumn(_("Filename"))
render = gtk.CellRendererPixbuf()
column.pack_start(render, False)
column.add_attribute(render, "stock-id", 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)
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.SORT_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.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(False)
chooser.set_property("skip-taskbar-hint", True)
# Run the dialog
response = chooser.run()
if response == gtk.RESPONSE_OK:
result = chooser.get_filename()
else:
chooser.destroy()
return
path = result.decode('utf-8')
self.files_treestore.clear()
self.files_treestore.append(None, [result, gtk.STOCK_FILE, 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.FILE_CHOOSER_ACTION_SELECT_FOLDER,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_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.RESPONSE_OK:
result = chooser.get_filename()
else:
chooser.destroy()
return
path = result.decode('utf-8')
self.files_treestore.clear()
self.files_treestore.append(None, [result, gtk.STOCK_OPEN, 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.RESPONSE_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, gtk.STOCK_NETWORK, 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 = self.files_treestore[0][1] == gtk.STOCK_NETWORK
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.RESPONSE_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.FILE_CHOOSER_ACTION_SAVE,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_SAVE, gtk.RESPONSE_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.RESPONSE_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
webseeds = []
b = self.builder.get_object("textview_webseeds").get_buffer()
lines = b.get_text(b.get_start_iter(), b.get_end_iter()).strip().split("\n")
for l in lines:
if is_url(l):
webseeds.append(l)
# 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_all()
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(
path,
tracker,
piece_length,
comment,
result,
webseeds,
private,
author,
trackers,
add_to_session)
else:
def hide_progress(result):
self.builder.get_object("progress_dialog").hide_all()
deferToThread(self.create_torrent,
path.decode('utf-8'),
tracker,
piece_length,
self._on_create_torrent_progress,
comment,
result.decode('utf-8'),
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 = base64.encodestring(_file.read())
client.core.add_torrent_file(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.
gobject.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(
"deluge.ui.gtkui", 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.RESPONSE_OK:
# Create a list of trackers from the textview buffer
trackers = []
b = textview.get_buffer()
lines = b.get_text(b.get_start_iter(), b.get_end_iter()).strip().split("\n")
self.config["createtorrent.trackers"] = lines
log.debug("lines: %s", lines)
for l in lines:
if is_url(l):
trackers.append(l)
# We are going to add these trackers to the highest tier + 1
tier = 0
for row in self.trackers_liststore:
if row[0] > tier:
tier = row[0]
for tracker in trackers:
self.trackers_liststore.append([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)