Remove dependency on libtorrent for add torrent dialog

This commit is contained in:
Andrew Resch 2008-09-07 05:26:03 +00:00
parent cc128029f9
commit 4bd88df4de
3 changed files with 216 additions and 91 deletions

View File

@ -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

128
deluge/bencode.py Normal file
View File

@ -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)

View File

@ -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()