epour/epour/gui/TorrentSelector.py

385 lines
12 KiB
Python

#
# Epour - A bittorrent client using EFL and libtorrent
#
# Copyright 2012-2017 Kai Huuhko <kai.huuhko@gmail.com>
#
# This program is free software; you can 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.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
import os
import logging
from libtorrent import add_torrent_params_flags_t
from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL
from efl import elementary as elm
from efl.elementary import DialogWindow
from efl.elementary import Button
from efl.elementary import Box
from efl.elementary import Frame
from efl.elementary import Fileselector
from efl.elementary import FileselectorEntry
from efl.elementary import Entry, markup_to_utf8, utf8_to_markup
from efl.elementary import Check
from efl.elementary import Scroller
from efl.elementary import Spinner
# from efl.elementary import ELM_SEL_TYPE_CLIPBOARD, \
# ELM_SEL_FORMAT_TEXT
from .Widgets import UnitSpinner
from ..session import lt_version_post_breaking_change
EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND
EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0
FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL
FILL_HORIZ = EVAS_HINT_FILL, 0.5
from efl.elementary import Configuration
elm_conf = Configuration()
scale = elm_conf.scale
log = logging.getLogger("epour.gui")
class TorrentSelector(DialogWindow):
names = {
1: _("Seed Mode"),
8: _("Share Mode"),
16: _("Apply IP Filter"),
32: _("Start Paused"),
128: _("Duplicate Is Error"),
1024: _("Super Seeding"),
2048: _("Sequential Download"),
}
tooltips = {
1: utf8_to_markup(_(
'''If Seed Mode is set, Epour will assume that all files are
present for this torrent and that they all match the hashes in the
torrent file. Each time a peer requests to download a block, the
piece is verified against the hash, unless it has been verified
already. If a hash fails, the torrent will automatically leave the
seed mode and recheck all the files. The use case for this mode is
if a torrent is created and seeded, or if the user already know
that the files are complete, this is a way to avoid the initial
file checks, and significantly reduce the startup time.
Setting Seed Mode on a torrent without metadata (a .torrent
file) is a no-op and will be ignored.'''
)),
8: utf8_to_markup(_(
'''Determines if the torrent should be added in share mode or not.
Share mode indicates that we are not interested in downloading the
torrent, but merely want to improve our share ratio (i.e. increase
it). A torrent started in share mode will do its best to never
download more than it uploads to the swarm. If the swarm does not
have enough demand for upload capacity, the torrent will not
download anything. This mode is intended to be safe to add any
number of torrents to, without manual screening, without the risk
of downloading more than is uploaded.
A torrent in share mode sets the priority to all pieces to 0,
except for the pieces that are downloaded, when pieces are decided
to be downloaded. This affects the progress bar, which might be set
to "100% finished" most of the time. Do not change file or piece
priorities for torrents in share mode, it will make it not work.
The share mode has one setting, the share ratio target.'''
)),
16: utf8_to_markup(_(
'''Determines if the IP filter should apply to this torrent or not. By
default all torrents are subject to filtering by the IP filter
(i.e. this flag is set by default). This is useful if certain
torrents needs to be excempt for some reason, being an auto-update
torrent for instance.'''
)),
32: utf8_to_markup(_(
'''Specifies whether or not the torrent is to be started in a paused
state. I.e. it won't connect to the tracker or any of the peers
until it's resumed. This is typically a good way of avoiding race
conditions when setting configuration options on torrents before
starting them.'''
)),
1024: utf8_to_markup(_(
'''Sets the torrent into super seeding mode. If the torrent is not a
seed, this flag has no effect.'''
)),
2048: utf8_to_markup(_(
'''Sets the sequential download state for the torrent.'''
)),
}
def __init__(self, parent, session, t_uri=None):
DialogWindow.__init__(
self, parent, "addtorrent", _("Add Torrent"),
size=(scale * 400, scale * 400), autodel=False
)
def _cb_close(self):
self.hide()
self.callback_delete_request_add(lambda o: _cb_close(o))
self.add_dict = {}
scrol = Scroller(
self, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH)
self.resize_object_add(scrol)
box = Box(
scrol, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH,
align=(0.5, 0.0))
scrol.content = box
hbox = Box(
box, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ,
horizontal=True)
box.pack_end(hbox)
hbox.show()
self.uri_entry = Entry(
box, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ,
single_line=True, scrollable=True
)
self.uri_entry.part_text_set("guide", _("Enter torrent file path / magnet URI / info hash"))
if t_uri:
self.uri_entry.entry = utf8_to_markup(t_uri)
hbox.pack_end(self.uri_entry)
self.uri_entry.show()
fsb = Button(box, text=_("Select file"))
fsb.callback_clicked_add(lambda x: TorrentFs(self, self.uri_entry))
hbox.pack_end(fsb)
fsb.show()
options = Frame(
self, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ,
text=_("Advanced Options") + u" \u25BA", collapse=True
)
box.pack_end(options)
options.show()
def toggler(obj):
if obj.collapse:
obj.text = obj.text.replace(u"\u25BA", u"\u25BC")
else:
obj.text = obj.text.replace(u"\u25BC", u"\u25BA")
obj.collapse_go(not obj.collapse)
options.callback_clicked_add(toggler)
opt_box = Box(
options, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH
)
options.content = opt_box
def entry_cb(e, key):
self.add_dict[key] = e.entry
for v, t in {
"name": _("Name"),
"trackerid": _("Tracker ID")
}.items():
e = Entry(opt_box, size_hint_align=FILL_HORIZ)
e.part_text_set("guide", t)
e.callback_changed_user_add(entry_cb, v)
opt_box.pack_end(e)
e.show()
def fs_cb(fs):
self.add_dict["save_path"] = fs.path
# Downloaded data is saved in this path
title = _("Data Save Path")
save_path = FsEntry(
opt_box, size_hint_align=FILL_HORIZ, text=title,
folder_only=True, expandable=False
)
save_path.callback_changed_add(fs_cb)
path = session.conf.get("Settings", "storage_path").strip()
save_path.path = path
opt_box.pack_end(save_path)
save_path.show()
self.add_dict["flags"] = (
add_torrent_params_flags_t.flag_apply_ip_filter +
add_torrent_params_flags_t.flag_update_subscribe +
add_torrent_params_flags_t.flag_duplicate_is_error +
add_torrent_params_flags_t.flag_auto_managed)
def option_flag_cb(c, flag):
flags = self.add_dict["flags"]
flags = flags ^ flag
if flag == int(add_torrent_params_flags_t.flag_paused):
flags = flags ^ int(add_torrent_params_flags_t.flag_auto_managed)
self.add_dict["flags"] = flags
if lt_version_post_breaking_change:
items = sorted(add_torrent_params_flags_t.__dict__.items())
flags_list = [flag for flag in items if not flag[0].startswith("_")]
else:
flags_list = sorted(add_torrent_params_flags_t.names.items())
for name, flag in flags_list:
if not int(flag) in self.names.keys():
continue
c = Check(
opt_box, size_hint_align=(0.0, 0.5),
text=self.names.get(int(flag), " ".join(name.split("_")[1:]).capitalize()),
state=bool(self.add_dict["flags"] & int(flag))
)
if int(flag) in self.tooltips:
c.tooltip_text_set(self.tooltips[int(flag)])
c.tooltip_window_mode_set(True)
c.callback_changed_add(option_flag_cb, int(flag))
opt_box.pack_end(c)
c.show()
INT_MAX = 2147483647
def spin_cb(s, key):
self.add_dict[key] = s.value
for v, t in {
"max_uploads": _("Max Uploads"),
"max_connections": _("Max Connections")
}.items():
s = Spinner(
opt_box, size_hint_align=FILL_HORIZ, min_max=(0, INT_MAX),
label_format=t + ": %0.f"
)
s.callback_changed_add(spin_cb, v)
opt_box.pack_end(s)
s.show()
for v, t in {
"upload_limit": _("Upload Limit"),
"download_limit": _("Download Limit")
}.items():
s = UnitSpinner(opt_box, "B/s", 1024, UnitSpinner.binary_prefixes)
s.size_hint_weight = EXPAND_HORIZ
s.size_hint_align = FILL_HORIZ
s.set_value(0)
s.callback_changed_add(
lambda x, y: self.add_dict.__setitem__(v, y)
)
s.spinner.label_format = t + ": %0.f"
opt_box.pack_end(s)
s.show()
# TODO:
# list(strings) trackers (list(entry))
# list(tuple(str host, int port), ...) dht_nodes
# storage_mode_t storage_mode
# list(int) file_priorities (list(spinner))
opt_box.show()
bbox = Box(
box, size_hint_weight=EXPAND_HORIZ,
size_hint_align=FILL_HORIZ, horizontal=True)
ok_btn = Button(bbox, text=_("Ok"), size_hint_align=(1.0, 0.5))
bbox.pack_end(ok_btn)
ok_btn.show()
cancel_btn = Button(bbox, text=_("Cancel"), size_hint_align=(1.0, 0.5))
bbox.pack_end(cancel_btn)
cancel_btn.show()
bbox.show()
box.pack_end(bbox)
box.show()
scrol.show()
def del_dialog_cb(btn):
self.hide()
def add_torrent_cb(btn, uri_entry, session, add_dict):
uri = uri_entry.entry
if not uri:
return
uri = markup_to_utf8(uri)
session.fill_add_dict_based_on_uri(add_dict, uri)
session.add_torrent_with_dict(add_dict)
self.hide()
# self.delete()
ok_btn.callback_clicked_add(
add_torrent_cb, self.uri_entry, session, self.add_dict)
cancel_btn.callback_clicked_add(del_dialog_cb)
# cancel_btn.callback_clicked_add(lambda x: self.delete())
self.show()
class FsEntry(Fileselector, FileselectorEntry):
def __init__(self, parent, **kwargs):
FileselectorEntry.__init__(self, parent, **kwargs)
class TorrentFs(DialogWindow):
def __init__(self, parent, uri_entry):
super(TorrentFs, self).__init__(
parent, "torrentselect", "Select Torrent", size=(500, 500),
autodel=True)
fs = Fileselector(
self, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH,
expandable=False, is_save=False, path=os.path.expanduser("~")
)
if elm.need_efreet():
fs.mime_types_filter_append(
["application/x-bittorrent"], "Torrent files")
fs.mime_types_filter_append(
["*"], "All files")
self.resize_object_add(fs)
fs.show()
def done_cb(fs, path):
if path and os.path.isfile(path):
uri_entry.entry_set(path)
self.delete()
fs.callback_done_add(done_cb)
self.show()