forked from enlightenment/epour
385 lines
12 KiB
Python
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()
|