Remove dependency on libtorrent for add torrent dialog
This commit is contained in:
parent
cc128029f9
commit
4bd88df4de
|
@ -7,3 +7,4 @@ Deluge 1.1.0 - "" (In Development)
|
|||
GtkUI:
|
||||
* Add peer progress to the peers tab
|
||||
* Sorting # column will place downloaders above seeds
|
||||
* Remove dependency on libtorrent for add torrent dialog
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
# The contents of this file are subject to the BitTorrent Open Source License
|
||||
# Version 1.1 (the License). You may not copy or use this file, in either
|
||||
# source code or executable form, except in compliance with the License. You
|
||||
# may obtain a copy of the License at http://www.bittorrent.com/license/.
|
||||
#
|
||||
# Software distributed under the License is distributed on an AS IS basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
|
||||
# Written by Petru Paler
|
||||
|
||||
def decode_int(x, f):
|
||||
f += 1
|
||||
newf = x.index('e', f)
|
||||
n = int(x[f:newf])
|
||||
if x[f] == '-':
|
||||
if x[f + 1] == '0':
|
||||
raise ValueError
|
||||
elif x[f] == '0' and newf != f+1:
|
||||
raise ValueError
|
||||
return (n, newf+1)
|
||||
|
||||
def decode_string(x, f):
|
||||
colon = x.index(':', f)
|
||||
n = int(x[f:colon])
|
||||
if x[f] == '0' and colon != f+1:
|
||||
raise ValueError
|
||||
colon += 1
|
||||
return (x[colon:colon+n], colon+n)
|
||||
|
||||
def decode_list(x, f):
|
||||
r, f = [], f+1
|
||||
while x[f] != 'e':
|
||||
v, f = decode_func[x[f]](x, f)
|
||||
r.append(v)
|
||||
return (r, f + 1)
|
||||
|
||||
def decode_dict(x, f):
|
||||
r, f = {}, f+1
|
||||
while x[f] != 'e':
|
||||
k, f = decode_string(x, f)
|
||||
r[k], f = decode_func[x[f]](x, f)
|
||||
return (r, f + 1)
|
||||
|
||||
decode_func = {}
|
||||
decode_func['l'] = decode_list
|
||||
decode_func['d'] = decode_dict
|
||||
decode_func['i'] = decode_int
|
||||
decode_func['0'] = decode_string
|
||||
decode_func['1'] = decode_string
|
||||
decode_func['2'] = decode_string
|
||||
decode_func['3'] = decode_string
|
||||
decode_func['4'] = decode_string
|
||||
decode_func['5'] = decode_string
|
||||
decode_func['6'] = decode_string
|
||||
decode_func['7'] = decode_string
|
||||
decode_func['8'] = decode_string
|
||||
decode_func['9'] = decode_string
|
||||
|
||||
def bdecode(x):
|
||||
try:
|
||||
r, l = decode_func[x[0]](x, 0)
|
||||
except (IndexError, KeyError, ValueError):
|
||||
raise Exception("not a valid bencoded string")
|
||||
if l != len(x):
|
||||
raise Exception("invalid bencoded value (data after valid prefix)")
|
||||
return r
|
||||
|
||||
from types import StringType, IntType, LongType, DictType, ListType, TupleType
|
||||
|
||||
|
||||
class Bencached(object):
|
||||
|
||||
__slots__ = ['bencoded']
|
||||
|
||||
def __init__(self, s):
|
||||
self.bencoded = s
|
||||
|
||||
def encode_bencached(x,r):
|
||||
r.append(x.bencoded)
|
||||
|
||||
def encode_int(x, r):
|
||||
r.extend(('i', str(x), 'e'))
|
||||
|
||||
def encode_bool(x, r):
|
||||
if x:
|
||||
encode_int(1, r)
|
||||
else:
|
||||
encode_int(0, r)
|
||||
|
||||
def encode_string(x, r):
|
||||
r.extend((str(len(x)), ':', x))
|
||||
|
||||
def encode_list(x, r):
|
||||
r.append('l')
|
||||
for i in x:
|
||||
encode_func[type(i)](i, r)
|
||||
r.append('e')
|
||||
|
||||
def encode_dict(x,r):
|
||||
r.append('d')
|
||||
ilist = x.items()
|
||||
ilist.sort()
|
||||
for k, v in ilist:
|
||||
r.extend((str(len(k)), ':', k))
|
||||
encode_func[type(v)](v, r)
|
||||
r.append('e')
|
||||
|
||||
encode_func = {}
|
||||
encode_func[Bencached] = encode_bencached
|
||||
encode_func[IntType] = encode_int
|
||||
encode_func[LongType] = encode_int
|
||||
encode_func[StringType] = encode_string
|
||||
encode_func[ListType] = encode_list
|
||||
encode_func[TupleType] = encode_list
|
||||
encode_func[DictType] = encode_dict
|
||||
|
||||
try:
|
||||
from types import BooleanType
|
||||
encode_func[BooleanType] = encode_bool
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def bencode(x):
|
||||
r = []
|
||||
encode_func[type(x)](x, r)
|
||||
return ''.join(r)
|
|
@ -2,24 +2,24 @@
|
|||
# addtorrentdialog.py
|
||||
#
|
||||
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
|
||||
#
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
|
@ -52,11 +52,11 @@ class AddTorrentDialog(component.Component):
|
|||
self.glade = gtk.glade.XML(
|
||||
pkg_resources.resource_filename(
|
||||
"deluge.ui.gtkui", "glade/add_torrent_dialog.glade"))
|
||||
|
||||
|
||||
self.dialog = self.glade.get_widget("dialog_add_torrent")
|
||||
|
||||
|
||||
self.dialog.connect("delete-event", self._on_delete_event)
|
||||
|
||||
|
||||
self.glade.signal_autoconnect({
|
||||
"on_button_file_clicked": self._on_button_file_clicked,
|
||||
"on_button_url_clicked": self._on_button_url_clicked,
|
||||
|
@ -74,15 +74,15 @@ class AddTorrentDialog(component.Component):
|
|||
self.files_treestore = gtk.TreeStore(bool, str, gobject.TYPE_UINT64,
|
||||
gobject.TYPE_INT64, bool, str)
|
||||
self.files_treestore.set_sort_column_id(1, gtk.SORT_ASCENDING)
|
||||
|
||||
|
||||
# Holds the files info
|
||||
self.files = {}
|
||||
self.infos = {}
|
||||
self.core_config = {}
|
||||
self.options = {}
|
||||
self.previous_selected_torrent = None
|
||||
|
||||
|
||||
|
||||
|
||||
self.listview_torrents = self.glade.get_widget("listview_torrents")
|
||||
self.listview_files = self.glade.get_widget("listview_files")
|
||||
|
||||
|
@ -110,12 +110,12 @@ class AddTorrentDialog(component.Component):
|
|||
column.pack_start(render)
|
||||
column.set_cell_data_func(render, listview.cell_data_size, 2)
|
||||
self.listview_files.append_column(column)
|
||||
|
||||
|
||||
self.listview_torrents.set_model(self.torrent_liststore)
|
||||
self.listview_files.set_model(self.files_treestore)
|
||||
|
||||
self.listview_files.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
|
||||
self.listview_torrents.get_selection().connect("changed",
|
||||
self.listview_torrents.get_selection().connect("changed",
|
||||
self._on_torrent_changed)
|
||||
|
||||
# Get default config values from the core
|
||||
|
@ -130,10 +130,10 @@ class AddTorrentDialog(component.Component):
|
|||
"add_paused"
|
||||
]
|
||||
self.core_config = {}
|
||||
|
||||
|
||||
def start(self):
|
||||
self.update_core_config()
|
||||
|
||||
|
||||
def show(self, focus=False):
|
||||
self.update_core_config()
|
||||
|
||||
|
@ -142,15 +142,15 @@ class AddTorrentDialog(component.Component):
|
|||
self.glade.get_widget("entry_download_path").hide()
|
||||
else:
|
||||
self.glade.get_widget("button_location").hide()
|
||||
self.glade.get_widget("entry_download_path").show()
|
||||
|
||||
self.dialog.set_transient_for(component.get("MainWindow").window)
|
||||
self.glade.get_widget("entry_download_path").show()
|
||||
|
||||
self.dialog.set_transient_for(component.get("MainWindow").window)
|
||||
self.dialog.present()
|
||||
if focus:
|
||||
self.dialog.window.focus()
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def hide(self):
|
||||
self.dialog.hide()
|
||||
self.files = {}
|
||||
|
@ -159,9 +159,9 @@ class AddTorrentDialog(component.Component):
|
|||
self.previous_selected_torrent = None
|
||||
self.torrent_liststore.clear()
|
||||
self.files_treestore.clear()
|
||||
self.dialog.set_transient_for(component.get("MainWindow").window)
|
||||
self.dialog.set_transient_for(component.get("MainWindow").window)
|
||||
return None
|
||||
|
||||
|
||||
def update_core_config(self):
|
||||
self.core_config = {}
|
||||
# Send requests to the core for these config values
|
||||
|
@ -171,77 +171,72 @@ class AddTorrentDialog(component.Component):
|
|||
# Force a call to the core because we need this data now
|
||||
client.force_call()
|
||||
self.set_default_options()
|
||||
|
||||
|
||||
def _on_config_value(self, value):
|
||||
for key in self.core_keys:
|
||||
if not self.core_config.has_key(key):
|
||||
self.core_config[key] = value
|
||||
break
|
||||
|
||||
|
||||
def add_from_files(self, filenames):
|
||||
import deluge.libtorrent as lt
|
||||
import deluge.bencode
|
||||
import os.path
|
||||
new_row = None
|
||||
|
||||
|
||||
for filename in filenames:
|
||||
# Get the torrent data from the torrent file
|
||||
try:
|
||||
log.debug("Attempting to open %s for add.", filename)
|
||||
_file = open(filename, "rb")
|
||||
filedump = _file.read()
|
||||
if not filedump:
|
||||
log.warning("Torrent appears to be corrupt!")
|
||||
continue
|
||||
filedump = lt.bdecode(filedump)
|
||||
_file.close()
|
||||
metadata = deluge.bencode.bdecode(open(filename, "rb").read())
|
||||
except Exception, e:
|
||||
log.warning("Unable to open %s: %s", filename, e)
|
||||
continue
|
||||
|
||||
try:
|
||||
info = lt.torrent_info(filedump)
|
||||
except RuntimeError, e:
|
||||
log.warning("Torrent appears to be corrupt!")
|
||||
continue
|
||||
from sha import sha
|
||||
info_hash = sha(deluge.bencode.bencode(metadata["info"])).hexdigest()
|
||||
|
||||
if str(info.info_hash()) in self.infos:
|
||||
if info_hash in self.infos:
|
||||
log.debug("Torrent already in list!")
|
||||
continue
|
||||
|
||||
|
||||
# Get list of files from torrent info
|
||||
files = []
|
||||
for f in info.files():
|
||||
prefix = ""
|
||||
if len(metadata["info"]["files"]) > 1:
|
||||
prefix = metadata["info"]["name"]
|
||||
|
||||
for f in metadata["info"]["files"]:
|
||||
files.append({
|
||||
'path': f.path,
|
||||
'size': f.size,
|
||||
'path': os.path.join(prefix, *f["path"]),
|
||||
'size': f["length"],
|
||||
'download': True
|
||||
})
|
||||
|
||||
name = "%s (%s)" % (info.name(), os.path.split(filename)[-1])
|
||||
name = "%s (%s)" % (metadata["info"]["name"], os.path.split(filename)[-1])
|
||||
new_row = self.torrent_liststore.append(
|
||||
[str(info.info_hash()), name, filename])
|
||||
self.files[str(info.info_hash())] = files
|
||||
self.infos[str(info.info_hash())] = info
|
||||
[info_hash, name, filename])
|
||||
self.files[info_hash] = files
|
||||
self.infos[info_hash] = metadata
|
||||
|
||||
(model, row) = self.listview_torrents.get_selection().get_selected()
|
||||
if not row and new_row:
|
||||
self.listview_torrents.get_selection().select_iter(new_row)
|
||||
|
||||
|
||||
def _on_torrent_changed(self, treeselection):
|
||||
(model, row) = treeselection.get_selected()
|
||||
self.files_treestore.clear()
|
||||
|
||||
|
||||
if row is None:
|
||||
return
|
||||
|
||||
# Update files list
|
||||
|
||||
# Update files list
|
||||
files_list = self.files[model.get_value(row, 0)]
|
||||
|
||||
self.prepare_file_store(files_list)
|
||||
|
||||
if self.core_config == {}:
|
||||
self.update_core_config()
|
||||
|
||||
|
||||
# Save the previous torrents options
|
||||
self.save_torrent_options()
|
||||
# Update the options frame
|
||||
|
@ -258,6 +253,7 @@ class AddTorrentDialog(component.Component):
|
|||
self.add_files(None, split_files)
|
||||
|
||||
def prepare_file(self, file, file_name, file_num, files_storage):
|
||||
log.debug("file_name: %s", file_name)
|
||||
first_slash_index = file_name.find("/")
|
||||
if first_slash_index == -1:
|
||||
files_storage[file_name] = (file_num, file)
|
||||
|
@ -287,16 +283,16 @@ class AddTorrentDialog(component.Component):
|
|||
if torrent_id not in self.options:
|
||||
self.set_default_options()
|
||||
return
|
||||
|
||||
|
||||
options = self.options[torrent_id]
|
||||
|
||||
|
||||
if client.is_localhost():
|
||||
self.glade.get_widget("button_location").set_current_folder(
|
||||
options["download_location"])
|
||||
else:
|
||||
self.glade.get_widget("entry_download_path").set_text(
|
||||
options["download_location"])
|
||||
|
||||
|
||||
self.glade.get_widget("radio_compact").set_active(
|
||||
options["compact_allocation"])
|
||||
self.glade.get_widget("spin_maxdown").set_value(
|
||||
|
@ -311,7 +307,7 @@ class AddTorrentDialog(component.Component):
|
|||
options["add_paused"])
|
||||
self.glade.get_widget("chk_prioritize").set_active(
|
||||
options["prioritize_first_last_pieces"])
|
||||
|
||||
|
||||
def save_torrent_options(self, row=None):
|
||||
# Keeps the torrent options dictionary up-to-date with what the user has
|
||||
# selected.
|
||||
|
@ -319,10 +315,10 @@ class AddTorrentDialog(component.Component):
|
|||
row = self.previous_selected_torrent
|
||||
if row is None or not self.torrent_liststore.iter_is_valid(row):
|
||||
return
|
||||
|
||||
|
||||
torrent_id = self.torrent_liststore.get_value(row, 0)
|
||||
|
||||
options = {}
|
||||
|
||||
options = {}
|
||||
if client.is_localhost():
|
||||
options["download_location"] = \
|
||||
self.glade.get_widget("button_location").get_current_folder()
|
||||
|
@ -345,16 +341,16 @@ class AddTorrentDialog(component.Component):
|
|||
self.glade.get_widget("chk_prioritize").get_active()
|
||||
options["default_private"] = \
|
||||
self.glade.get_widget("chk_private").get_active()
|
||||
|
||||
|
||||
self.options[torrent_id] = options
|
||||
|
||||
|
||||
# Save the file priorities
|
||||
files_priorities = self.build_priorities(
|
||||
self.files_treestore.get_iter_first(), {})
|
||||
|
||||
|
||||
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):
|
||||
|
@ -372,7 +368,7 @@ class AddTorrentDialog(component.Component):
|
|||
else:
|
||||
self.glade.get_widget("entry_download_path").set_text(
|
||||
self.core_config["download_location"])
|
||||
|
||||
|
||||
self.glade.get_widget("radio_compact").set_active(
|
||||
self.core_config["compact_allocation"])
|
||||
self.glade.get_widget("spin_maxdown").set_value(
|
||||
|
@ -387,7 +383,7 @@ class AddTorrentDialog(component.Component):
|
|||
self.core_config["add_paused"])
|
||||
self.glade.get_widget("chk_prioritize").set_active(
|
||||
self.core_config["prioritize_first_last_pieces"])
|
||||
|
||||
|
||||
def get_file_priorities(self, torrent_id):
|
||||
# A list of priorities
|
||||
files_list = []
|
||||
|
@ -397,9 +393,9 @@ class AddTorrentDialog(component.Component):
|
|||
files_list.append(0)
|
||||
else:
|
||||
files_list.append(1)
|
||||
|
||||
|
||||
return files_list
|
||||
|
||||
|
||||
def _on_file_toggled(self, render, path):
|
||||
(model, paths) = self.listview_files.get_selection().get_selected_rows()
|
||||
if len(paths) > 1:
|
||||
|
@ -449,13 +445,13 @@ class AddTorrentDialog(component.Component):
|
|||
chooser = gtk.FileChooserDialog(_("Choose a .torrent file"),
|
||||
None,
|
||||
gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
|
||||
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
|
||||
gtk.RESPONSE_OK))
|
||||
|
||||
chooser.set_transient_for(self.dialog)
|
||||
chooser.set_select_multiple(True)
|
||||
chooser.set_property("skip-taskbar-hint", True)
|
||||
|
||||
|
||||
# Add .torrent and * file filters
|
||||
file_filter = gtk.FileFilter()
|
||||
file_filter.set_name(_("Torrent files"))
|
||||
|
@ -483,7 +479,7 @@ class AddTorrentDialog(component.Component):
|
|||
|
||||
chooser.destroy()
|
||||
self.add_from_files(result)
|
||||
|
||||
|
||||
def _on_button_url_clicked(self, widget):
|
||||
log.debug("_on_button_url_clicked")
|
||||
dialog = self.glade.get_widget("url_dialog")
|
||||
|
@ -492,13 +488,13 @@ class AddTorrentDialog(component.Component):
|
|||
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||
dialog.set_transient_for(self.dialog)
|
||||
entry.grab_focus()
|
||||
|
||||
|
||||
if deluge.common.windows_check():
|
||||
import win32clipboard as clip
|
||||
import win32clipboard as clip
|
||||
import win32con
|
||||
clip.OpenClipboard()
|
||||
text = clip.GetClipboardData(win32con.CF_UNICODETEXT)
|
||||
clip.CloseClipboard()
|
||||
clip.OpenClipboard()
|
||||
text = clip.GetClipboardData(win32con.CF_UNICODETEXT)
|
||||
clip.CloseClipboard()
|
||||
else:
|
||||
clip = gtk.clipboard_get(selection='PRIMARY')
|
||||
text = clip.wait_for_text()
|
||||
|
@ -520,7 +516,7 @@ class AddTorrentDialog(component.Component):
|
|||
log.debug("url: %s", url)
|
||||
if url != None:
|
||||
self.add_from_url(url)
|
||||
|
||||
|
||||
entry.set_text("")
|
||||
dialog.hide()
|
||||
|
||||
|
@ -535,7 +531,7 @@ class AddTorrentDialog(component.Component):
|
|||
filename, headers = urllib.urlretrieve(url, tmp_file)
|
||||
log.debug("filename: %s", filename)
|
||||
self.add_from_files([filename])
|
||||
|
||||
|
||||
def _on_button_hash_clicked(self, widget):
|
||||
log.debug("_on_button_hash_clicked")
|
||||
|
||||
|
@ -544,9 +540,9 @@ class AddTorrentDialog(component.Component):
|
|||
(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]
|
||||
|
@ -567,7 +563,7 @@ class AddTorrentDialog(component.Component):
|
|||
|
||||
torrent_filenames = []
|
||||
torrent_options = []
|
||||
|
||||
|
||||
row = self.torrent_liststore.get_iter_first()
|
||||
while row != None:
|
||||
torrent_id = self.torrent_liststore.get_value(row, 0)
|
||||
|
@ -576,16 +572,16 @@ class AddTorrentDialog(component.Component):
|
|||
options = self.options[torrent_id]
|
||||
except:
|
||||
options = None
|
||||
|
||||
|
||||
file_priorities = self.get_file_priorities(torrent_id)
|
||||
if options != None:
|
||||
options["file_priorities"] = file_priorities
|
||||
|
||||
|
||||
torrent_filenames.append(filename)
|
||||
torrent_options.append(options)
|
||||
|
||||
|
||||
row = self.torrent_liststore.iter_next(row)
|
||||
|
||||
|
||||
client.add_torrent_file(torrent_filenames, torrent_options)
|
||||
client.force_call()
|
||||
self.hide()
|
||||
|
@ -595,25 +591,25 @@ class AddTorrentDialog(component.Component):
|
|||
(model, row) = self.listview_torrents.get_selection().get_selected()
|
||||
if row is None:
|
||||
return
|
||||
|
||||
|
||||
self.save_torrent_options(row)
|
||||
|
||||
|
||||
# The options we want all the torrents to have
|
||||
options = self.options[model.get_value(row, 0)]
|
||||
|
||||
|
||||
# Set all the torrent options
|
||||
row = model.get_iter_first()
|
||||
while row != None:
|
||||
torrent_id = model.get_value(row, 0)
|
||||
self.options[torrent_id] = 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()
|
||||
|
||||
|
|
Loading…
Reference in New Issue