Change internals to work with newer libtorrent versions

This will once again change the save format of the torrent list, hopefully for the last
time.
This commit is contained in:
Kai Huuhko 2016-08-04 18:23:02 +03:00
parent 8bf8f56dcb
commit 0b1b72fcac
9 changed files with 991 additions and 738 deletions

23
README
View File

@ -2,22 +2,18 @@
REQUIREMENTS
============
* libtorrent-rasterbar 1.0 or later, currently tested and developed with
version 1.0.5
* libtorrent-rasterbar 1.2.0 or later, currently tested and developed with
version 1.2.0
http://www.libtorrent.org/
https://libtorrent.org/
* Enlightenment Foundation Libraries 1.8 or later
* Python-EFL 1.15 or later
https://www.enlightenment.org/download
* Python-EFL 1.8 or later
* Python 3.2 or later
https://www.enlightenment.org/download
* Python 2.6 or later, 3.2 or later
http://www.python.org/
https://www.python.org/
* python-distutils-extra
@ -25,11 +21,11 @@ REQUIREMENTS
* dbus-python
http://www.freedesktop.org/wiki/Software/DBusBindings/#dbus-python
https://www.freedesktop.org/wiki/Software/DBusBindings/#dbus-python
* python-xdg / pyxdg
http://www.freedesktop.org/wiki/Software/pyxdg/
https://www.freedesktop.org/wiki/Software/pyxdg/
=======
INSTALL
@ -43,5 +39,4 @@ CONTACT/BUGS
Email: kai.huuhko@gmail.com
Mailing list: enlightenment-users@lists.sourceforge.net
Forums: http://forums.bodhilinux.com/index.php?/topic/7722-epour/
Launchpad: https://launchpad.net/epour
Bugs: https://phab.enlightenment.org/maniphest/ (set Tags-field to Epour)

4
TODO
View File

@ -39,7 +39,7 @@ Misc:
☐ Moving finished torrents to a different folder
☐ Individual torrent move when finished
Can this setting be saved to torrent dicts user data?
☐ Save torrent & resume data periodically
✔ Save torrent & resume data periodically @done (16-07-30 08:05)
Will this accomplish anything?
http://libtorrent.org/reference-Core.html#save_resume_data()
http://libtorrent.org/reference-Core.html#need_save_resume_data()
@ -47,7 +47,7 @@ Misc:
☐ Total count of torrents in session status
☐ Local search function for torrents, files?
☐ Web search
Handle switching between py2 <--> py3, messes up with pickled list of torrents
Handle switching between py2 <--> py3, messes up with pickled list of torrents @cancelled (16-07-30 08:15)
File "/usr/bin/epour", line 7, in <module>
app = Epour()
File "/usr/lib/python2.7/dist-packages/epour/Epour.py", line 89, in __init__

View File

@ -1,10 +1,14 @@
#!/usr/bin/python2
#!/usr/bin/python
import logging
from epour.Epour import Epour
app = Epour()
app.gui.run()
app.quit()
epour = Epour()
epour.gui.run_mainloop()
try:
epour.save_conf()
except Exception:
epour.log.exception("Saving conf failed")
logging.shutdown()

View File

@ -2,7 +2,7 @@
#
# Epour - A bittorrent client using EFL and libtorrent
#
# Copyright 2012-2014 Kai Huuhko <kai.huuhko@gmail.com>
# Copyright 2012-2016 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
@ -27,8 +27,7 @@ parser.add_argument(
'-v', '--verbose', action="count", help="max is -vvv")
parser.add_argument(
'--disable-add-dialog', action="store_true",
help="Torrents to be added from arguments don't open a dialog"
)
help="Torrents to be added from arguments don't open a dialog")
parser.add_argument(
'torrents', nargs="*", help="file path, magnet uri, or info hash",
metavar="TORRENT")
@ -72,11 +71,13 @@ from .gui import MainInterface
class Epour(object):
def __init__(self):
self.log = self.setup_log()
self.log.debug("Logging started")
self.conf = self.setup_conf()
session = self.session = Session(self.conf)
session = self.session = Session(self.conf, self._quit_cb)
session.load_state()
self.gui = MainInterface(self, session)
@ -93,16 +94,7 @@ class Epour(object):
self.conf.getboolean("Settings", "add_dialog_enabled"):
self.gui.add_torrent(t)
else:
add_dict = {
"save_path": self.conf.get("Settings", "storage_path"),
"flags": 592
}
if os.path.isfile(t):
self.session.add_torrent_from_file(add_dict, t)
elif t.startswith("magnet:"):
self.session.add_torrent_from_magnet(add_dict, t)
else:
self.session.add_torrent_from_hash(add_dict, t)
self.session.add_torrent_with_uri(t)
def setup_log(self):
log_level = logging.ERROR
@ -115,18 +107,15 @@ class Epour(object):
ch = logging.StreamHandler()
ch_formatter = logging.Formatter(
'%(name)s: [%(levelname)s] %(message)s'
)
'%(name)s: [%(levelname)s] %(message)s (%(filename)s: %(lineno)d)')
ch.setFormatter(ch_formatter)
ch.setLevel(log_level)
log.addHandler(ch)
fh = logging.FileHandler(
os.path.join(save_data_path("epour"), "epour.log")
)
os.path.join(save_data_path("epour"), "epour.log"))
fh_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(fh_formatter)
fh.setLevel(logging.ERROR)
log.addHandler(fh)
@ -167,25 +156,8 @@ class Epour(object):
with open(conf_path, 'wb') as configfile:
self.conf.write(configfile)
def quit(self):
session = self.session
session.pause()
try:
session.save_torrents()
except:
self.log.exception("Saving torrents failed")
try:
session.save_state()
except:
self.log.exception("Saving session state failed")
try:
self.save_conf()
except:
self.log.exception("Saving conf failed")
def _quit_cb(self):
self.gui.stop_mainloop()
class EpourDBus(dbus.service.Object):
@ -198,8 +170,7 @@ class EpourDBus(dbus.service.Object):
self.conf = conf
dbus.service.Object.__init__(
self, dbus.SessionBus(),
"/net/launchpad/epour", "net.launchpad.epour"
)
"/net/launchpad/epour", "net.launchpad.epour")
self.props = {
}
@ -212,16 +183,7 @@ class EpourDBus(dbus.service.Object):
if self.conf.getboolean("Settings", "add_dialog_enabled"):
self.gui.add_torrent(t)
else:
add_dict = {
"save_path": self.conf.get("Settings", "storage_path"),
"flags": 592
}
if t.startswith("magnet:"):
self.session.add_torrent_from_magnet(add_dict, t)
elif os.path.isfile(t):
self.session.add_torrent_from_file(add_dict, t)
else:
self.session.add_torrent_from_hash(add_dict, t)
self.session.add_torrent_with_uri(t)
except Exception:
self.log.exception("Error while adding torrent from dbus")
@ -230,16 +192,7 @@ class EpourDBus(dbus.service.Object):
def AddTorrentWithoutDialog(self, t):
self.log.info("Adding %s from dbus" % t)
try:
add_dict = {
"save_path": self.conf.get("Settings", "storage_path"),
"flags": 592
}
if t.startswith("magnet:"):
self.session.add_torrent_from_magnet(add_dict, t)
elif os.path.isfile(t):
self.session.add_torrent_from_file(add_dict, t)
else:
self.session.add_torrent_from_hash(add_dict, t)
self.session.add_torrent_with_uri(t)
except Exception:
self.log.exception("Error while adding torrent from dbus")
@ -247,13 +200,16 @@ if __name__ == "__main__":
efllog = logging.getLogger("efl")
efllog.setLevel(logging.INFO)
efllog_formatter = logging.Formatter(
'%(name)s: [%(levelname)s] %(message)s'
)
'%(name)s: [%(levelname)s] %(message)s')
efllog_handler = logging.StreamHandler()
efllog_handler.setFormatter(efllog_formatter)
efllog.addHandler(efllog_handler)
epour = Epour()
epour.gui.run()
epour.quit()
epour.gui.run_mainloop()
try:
epour.save_conf()
except Exception:
epour.log.exception("Saving conf failed")
logging.shutdown()

View File

@ -1,7 +1,7 @@
#
# Epour - A bittorrent client using EFL and libtorrent
#
# Copyright 2012-2014 Kai Huuhko <kai.huuhko@gmail.com>
# Copyright 2012-2016 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
@ -26,18 +26,18 @@ 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.window import StandardWindow, Window, ELM_WIN_DIALOG_BASIC
from efl.elementary.background import Background
from efl.elementary.button import Button
from efl.elementary.box import Box
from efl.elementary.frame import Frame
from efl.elementary.fileselector import Fileselector
from efl.elementary.fileselector_entry import FileselectorEntry
from efl.elementary.entry import Entry, markup_to_utf8, utf8_to_markup
from efl.elementary.check import Check
from efl.elementary.scroller import Scroller
from efl.elementary.spinner import Spinner
# from efl.elementary.object import ELM_SEL_TYPE_CLIPBOARD, \
from efl.elementary import StandardWindow, Window, ELM_WIN_DIALOG_BASIC
from efl.elementary import Background
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
@ -47,7 +47,7 @@ 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.configuration import Configuration
from efl.elementary import Configuration
elm_conf = Configuration()
scale = elm_conf.scale
@ -287,8 +287,8 @@ always used.'''
opt_box.pack_end(e)
e.show()
def fs_cb(fs, key):
self.add_dict[key] = fs.path
def fs_cb(fs):
self.add_dict["save_path"] = fs.path
# Downloaded data is saved in this path
title = _("Data Save Path")
@ -296,8 +296,9 @@ always used.'''
opt_box, size_hint_align=FILL_HORIZ, text=title,
folder_only=True, expandable=False
)
save_path.path = session.conf.get("Settings", "storage_path")
save_path.callback_changed_add(fs_cb, "save_path")
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()
@ -376,8 +377,7 @@ always used.'''
bbox = Box(
box, size_hint_weight=EXPAND_HORIZ,
size_hint_align=FILL_HORIZ, horizontal=True
)
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)
@ -401,28 +401,13 @@ always used.'''
uri = markup_to_utf8(uri)
if uri.startswith("magnet:"):
log.debug("Adding torrent from magnet link: %s", uri)
session.add_torrent_from_magnet(add_dict, uri)
self.delete()
return
elif uri.startswith("file://"):
uri = uri[7:]
session.fill_add_dict_based_on_uri(add_dict, uri)
session.add_torrent_with_dict(add_dict)
if os.path.isfile(uri):
log.debug("Adding file: %s", uri)
session.add_torrent_from_file(add_dict, uri)
self.delete()
return
else:
log.debug("Adding torrent from info hash: %s", uri)
session.add_torrent_from_hash(add_dict, uri)
self.delete()
return
self.delete()
ok_btn.callback_clicked_add(
add_torrent_cb, uri_entry, session, self.add_dict
)
add_torrent_cb, uri_entry, session, self.add_dict)
cancel_btn.callback_clicked_add(lambda x: self.delete())
self.show()

View File

@ -1,15 +1,36 @@
#
# Epour - A bittorrent client using EFL and libtorrent
#
# Copyright 2012-2016 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.
#
from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL, Rectangle
from efl.ecore import Timer
from efl.elementary.box import Box
from efl.elementary.spinner import Spinner
from efl.elementary.hoversel import Hoversel
from efl.elementary.label import Label
from efl.elementary.notify import Notify
from efl.elementary.popup import Popup
from efl.elementary.button import Button
from efl.elementary.grid import Grid
from efl.elementary.fileselector import Fileselector
from efl.elementary.fileselector_button import FileselectorButton
from efl.elementary import Box
from efl.elementary import Spinner
from efl.elementary import Hoversel
from efl.elementary import Label
from efl.elementary import Notify
from efl.elementary import Popup
from efl.elementary import Button
from efl.elementary import Grid
from efl.elementary import Fileselector
from efl.elementary import FileselectorButton
EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND
EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0

View File

@ -1,7 +1,7 @@
#
# Epour - A bittorrent client using EFL and libtorrent
#
# Copyright 2012-2014 Kai Huuhko <kai.huuhko@gmail.com>
# Copyright 2012-2016 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
@ -34,20 +34,19 @@ from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL, \
EVAS_ASPECT_CONTROL_VERTICAL, Rectangle
from efl.ecore import Timer
from efl import elementary as elm
elm.init()
from efl.elementary.genlist import Genlist, GenlistItemClass, \
from efl.elementary import Genlist, GenlistItemClass, \
ELM_GENLIST_ITEM_FIELD_TEXT, ELM_GENLIST_ITEM_FIELD_CONTENT, \
ELM_OBJECT_SELECT_MODE_NONE, ELM_OBJECT_SELECT_MODE_DEFAULT, \
ELM_LIST_COMPRESS
from efl.elementary.window import StandardWindow
from efl.elementary.icon import Icon
from efl.elementary.box import Box
from efl.elementary.label import Label
from efl.elementary.panel import Panel, ELM_PANEL_ORIENT_BOTTOM
from efl.elementary.table import Table
from efl.elementary.menu import Menu
from efl.elementary.configuration import Configuration
from efl.elementary.toolbar import Toolbar, ELM_TOOLBAR_SHRINK_NONE
from efl.elementary import StandardWindow
from efl.elementary import Icon
from efl.elementary import Box
from efl.elementary import Label
from efl.elementary import Panel, ELM_PANEL_ORIENT_BOTTOM
from efl.elementary import Table
from efl.elementary import Menu
from efl.elementary import Configuration
from efl.elementary import Toolbar, ELM_TOOLBAR_SHRINK_NONE
from .Widgets import ConfirmExit, Error, Information, BlockGraph
@ -65,16 +64,16 @@ log = logging.getLogger("epour.gui")
class MainInterface(object):
def __init__(self, parent, session):
self.parent = parent
self.session = session
self._session = session
self.itc = TorrentClass(self._session, "double_label")
self.torrentitems = {}
win = self.win = StandardWindow(
"epour", "Epour", size=(480 * scale, 400 * scale),
screen_constrain=True
)
screen_constrain=True)
win.callback_delete_request_add(lambda x: self.quit())
mbox = Box(win, size_hint_weight=EXPAND_BOTH)
@ -85,17 +84,15 @@ class MainInterface(object):
tb = Toolbar(
win, size_hint_align=FILL_HORIZ, homogeneous=False,
shrink_mode=ELM_TOOLBAR_SHRINK_NONE,
select_mode=ELM_OBJECT_SELECT_MODE_NONE
)
select_mode=ELM_OBJECT_SELECT_MODE_NONE)
tb.menu_parent = win
item = tb.item_append(
"document-new", _("Add torrent"),
lambda x, y: self.add_torrent()
)
lambda x, y: self.add_torrent())
def pause_session(it):
self.session.pause()
self._session.pause()
it.state_set(it.state_next())
def resume_session(it):
@ -104,42 +101,37 @@ class MainInterface(object):
item = tb.item_append(
"media-playback-pause", _("Pause Session"),
lambda tb, it: pause_session(it)
)
lambda tb, it: pause_session(it))
item.state_add(
"media-playback-start", _("Resume Session"),
lambda tb, it: resume_session(it)
)
lambda tb, it: resume_session(it))
def prefs_general_cb():
from .Preferences import PreferencesGeneral
PreferencesGeneral(self, self.session).show()
PreferencesGeneral(self, self._session).show()
def prefs_proxy_cb():
from .Preferences import PreferencesProxy
PreferencesProxy(self, self.session).show()
PreferencesProxy(self, self._session).show()
def prefs_session_cb():
from .Preferences import PreferencesSession
PreferencesSession(self, self.session).show()
PreferencesSession(self, self._session).show()
item = tb.item_append("preferences-system", _("Preferences"))
item.menu = True
item.menu.item_add(
None, _("General"), "preferences-system",
lambda o, i: prefs_general_cb()
)
lambda o, i: prefs_general_cb())
item.menu.item_add(
None, _("Proxy"), "preferences-system",
lambda o, i: prefs_proxy_cb()
)
lambda o, i: prefs_proxy_cb())
item.menu.item_add(
None, _("Session"), "preferences-system",
lambda o, i: prefs_session_cb()
)
lambda o, i: prefs_session_cb())
item = tb.item_append("application-exit", _("Exit"),
lambda tb, it: elm.exit())
lambda tb, it: self.quit())
mbox.pack_start(tb)
tb.show()
@ -148,12 +140,12 @@ class MainInterface(object):
self.tlist = tlist = Genlist(
mbox, select_mode=ELM_OBJECT_SELECT_MODE_DEFAULT,
mode=ELM_LIST_COMPRESS, size_hint_weight=EXPAND_BOTH,
size_hint_align=FILL_BOTH, homogeneous=True
)
size_hint_align=FILL_BOTH, homogeneous=True)
def item_activated_cb(gl, item):
h = item.data
itm = ItemMenu(tlist, item, self.session, h)
torrent = item.data
handle = torrent.handle
itm = ItemMenu(tlist, item, self._session, handle)
itm.focus = True
tlist.callback_activated_add(item_activated_cb)
@ -167,8 +159,8 @@ class MainInterface(object):
p = Panel(
topbox, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH,
color=(200, 200, 200, 200), orient=ELM_PANEL_ORIENT_BOTTOM
)
color=(200, 200, 200, 200), orient=ELM_PANEL_ORIENT_BOTTOM)
p.content = SessionStatus(win, session)
p.hidden = True
p.show()
@ -182,28 +174,31 @@ class MainInterface(object):
def torrent_added_cb(a):
h = a.handle
self.add_torrent_item(h)
info_hash = h.info_hash()
log.debug("Torrent added: %s" % str(info_hash))
torrent = session.torrents[str(info_hash)]
self.add_torrent_item(torrent)
def torrent_removed_cb(a):
self.remove_torrent_item(str(a.info_hash))
def torrent_update_cb(a):
self.remove_torrent_item(str(a.old_ih))
new_h = self.session.find_torrent(str(a.new_ih))
self.add_torrent_item(new_h)
# def torrent_update_cb(a):
# self.remove_torrent_item(str(a.old_ih))
# new_h = self._session.find_torrent(str(a.new_ih))
# self.add_torrent_item(new_h)
session.alert_manager.callback_add(
"torrent_added_alert", torrent_added_cb)
session.alert_manager.callback_add(
"torrent_removed_alert", torrent_removed_cb)
session.alert_manager.callback_add(
"torrent_update_alert", torrent_update_cb)
# session.alert_manager.callback_add(
# "torrent_update_alert", torrent_update_cb)
def torrent_paused_cb(a):
self.update_icon(a.handle)
# def torrent_paused_cb(a):
# self.update_icon(a.handle)
for a_name in "torrent_paused_alert", "torrent_resumed_alert":
session.alert_manager.callback_add(a_name, torrent_paused_cb)
# for a_name in "torrent_paused_alert", "torrent_resumed_alert":
# session.alert_manager.callback_add(a_name, torrent_paused_cb)
def state_changed_cb(a):
h = a.handle
@ -211,16 +206,36 @@ class MainInterface(object):
log.debug("State changed for invalid handle.")
return
ihash = str(h.info_hash())
if not ihash in self.torrentitems:
log.debug("%s state changed but not in list", str(ihash))
info_hash = str(h.info_hash())
if info_hash not in self.torrentitems:
log.debug("%s state changed but not in list", str(info_hash))
return
self.update_icon(h)
torrent = self._session.torrents[info_hash]
torrent.state = a.state
self.torrentitems[info_hash].fields_update(
"elm.swallow.icon", ELM_GENLIST_ITEM_FIELD_CONTENT
)
session.alert_manager.callback_add(
"state_changed_alert", state_changed_cb)
# def state_update_alert_cb(a):
# statuses = a.status
# for status in statuses:
# info_hash = str(status.info_hash)
# if info_hash not in self.torrentitems:
# return
# self.torrentitems[info_hash].fields_update(
# "*", ELM_GENLIST_ITEM_FIELD_TEXT
# )
# session.alert_manager.callback_add(
# "state_update_alert", state_update_alert_cb)
def torrent_finished_cb(a):
msg = _("Torrent {} has finished downloading.").format(
cgi.escape(a.handle.name())
@ -234,9 +249,9 @@ class MainInterface(object):
def add_torrent(self, t_uri=None):
from .TorrentSelector import TorrentSelector
TorrentSelector(self.win, self.session, t_uri)
TorrentSelector(self.win, self._session, t_uri)
def run(self):
def run_mainloop(self):
self.win.show()
self.timer = Timer(1.0, self.update)
@ -245,45 +260,27 @@ class MainInterface(object):
self.win.callback_iconified_add(lambda x: self.timer.freeze())
self.win.callback_normal_add(lambda x: self.timer.thaw())
elm.run()
elm.shutdown()
def stop_mainloop(self):
elm.exit()
def update(self):
#log.debug("Torrent list TICK")
for v in self.tlist.realized_items_get():
v.fields_update("*", ELM_GENLIST_ITEM_FIELD_TEXT)
self._session.post_torrent_updates(64)
return True
def update_icon(self, h):
if not h.is_valid():
return
ihash = str(h.info_hash())
if not ihash in self.torrentitems:
return
self.torrentitems[ihash].fields_update(
"elm.swallow.icon", ELM_GENLIST_ITEM_FIELD_CONTENT
)
def _torrent_item_tooltip_cb(self, gl, it, tooltip, session, torrent):
return TorrentTooltip(tooltip, session, torrent)
def _torrent_item_tooltip_cb(self, gl, it, tooltip, h):
if not h.is_valid():
return
def add_torrent_item(self, torrent):
info_hash = torrent.info_hash
s = h.status(8)
if not s.has_metadata:
return
tt = TorrentTooltip(tooltip, h, s)
return tt
def add_torrent_item(self, h):
ihash = str(h.info_hash())
itc = TorrentClass(self.session, "double_label")
item = self.tlist.item_append(itc, h)
item.tooltip_content_cb_set(self._torrent_item_tooltip_cb, h)
item = self.tlist.item_append(self.itc, torrent)
item.tooltip_content_cb_set(self._torrent_item_tooltip_cb, self._session, torrent)
item.tooltip_window_mode_set(True)
self.torrentitems[ihash] = item
self.torrentitems[str(info_hash)] = item
def remove_torrent_item(self, info_hash):
it = self.torrentitems.pop(info_hash, None)
@ -294,10 +291,14 @@ class MainInterface(object):
Error(self.win, title, text)
def quit(self, *args):
if self.session.conf.getboolean("Settings", "confirm_exit"):
ConfirmExit(self.win, elm.exit)
if self._session.conf.getboolean("Settings", "confirm_exit"):
ConfirmExit(self.win, self._quit_cb)
else:
elm.exit()
self._quit_cb()
def _quit_cb(self):
self.win.hide()
self._session.shutdown()
class SessionStatus(Table):
@ -306,7 +307,7 @@ class SessionStatus(Table):
def __init__(self, parent, session):
Table.__init__(self, parent)
self.session = session
self._session = session
s = session.status()
@ -398,30 +399,29 @@ class SessionStatus(Table):
self.update_timer = Timer(1.0, self.update)
self.on_del_add(lambda x: self.update_timer.delete())
self.top_widget.callback_withdrawn_add(
lambda x: self.update_timer.freeze()
)
lambda x: self.update_timer.freeze())
self.top_widget.callback_iconified_add(
lambda x: self.update_timer.freeze()
)
lambda x: self.update_timer.freeze())
self.top_widget.callback_normal_add(lambda x: self.update_timer.thaw())
def update(self):
#log.debug("Session status TICK")
s = self.session.status()
s = self._session.status()
self.d_l.text = "{}/s".format(intrepr(s.payload_download_rate))
self.u_l.text = "{}/s".format(intrepr(s.payload_upload_rate))
self.peer_l.text = str(s.num_peers)
t = "%s/%s" % (str(s.num_unchoked), str(s.allowed_upload_slots))
self.uploads_l.text = t
self.listen_l.text = str(self.session.is_listening())
if self.session.is_paused():
icon = "media-playback-pause"
else:
icon = "media-playback-play"
self.listen_l.text = str(self._session.is_listening())
icon = "media-playback-pause" if self._session.is_paused() else "media-playback-play"
try:
self.ses_pause_ic.standard = icon
except Exception as e:
log.debug(e)
except Exception:
pass
return True
@ -431,54 +431,53 @@ class TorrentClass(GenlistItemClass):
state_str = (
_('Queued'), _('Checking'), _('Downloading metadata'),
_('Downloading'), _('Finished'), _('Seeding'), _('Allocating'),
_('Checking resume data')
)
_('Checking resume data'))
log = logging.getLogger("epour.gui.torrent_list")
def __init__(self, session, *args, **kwargs):
GenlistItemClass.__init__(self, *args, **kwargs)
self.session = session
self._session = session
def text_get(self, obj, part, item_data):
h = item_data
torrent = item_data
handle = torrent.handle
if not handle.is_valid():
return _("Invalid torrent")
if part == "elm.text":
name = h.name()
return '%s' % (
name
)
return '%s' % (torrent.status.name)
elif part == "elm.text.sub":
if not h.is_valid():
return _("Invalid torrent")
s = h.status(0)
qp = h.queue_position()
status = torrent.status
qp = handle.queue_position()
if qp == -1:
qp = "seeding"
return _("{0:.0%} complete, ETA: {1} "
return _(
"{0:.0%} complete, ETA: {1} "
"(Down: {2}/s Up: {3}/s Queue pos: {4})").format(
s.progress,
timedelta(seconds=self.get_eta(s)),
intrepr(s.download_payload_rate, precision=0),
intrepr(s.upload_payload_rate, precision=0),
qp,
)
status.progress,
timedelta(seconds=self.get_eta(status)),
intrepr(status.download_payload_rate, precision=0),
intrepr(status.upload_payload_rate, precision=0),
qp)
def content_get(self, obj, part, item_data):
if part != "elm.swallow.icon":
return
h = item_data
torrent = item_data
handle = torrent.handle
if not h.is_valid():
if not handle.is_valid():
return
s = h.status(0)
status = torrent.status
ic = Icon(obj)
try:
if h.is_paused():
if status.paused:
try:
ic.standard = "player_pause"
except Exception:
@ -486,7 +485,7 @@ class TorrentClass(GenlistItemClass):
ic.standard = "media-playback-pause"
except Exception:
pass
elif h.is_seed():
elif status.is_seeding:
try:
ic.standard = "up"
except Exception:
@ -504,16 +503,16 @@ class TorrentClass(GenlistItemClass):
pass
except RuntimeError:
log.debug("Setting torrent ic failed")
ic.tooltip_text_set(self.state_str[s.state])
ic.tooltip_text_set(self.state_str[torrent.state])
ic.size_hint_aspect_set(EVAS_ASPECT_CONTROL_VERTICAL, 1, 1)
return ic
def get_eta(self, s):
# if self.is_finished and self.options["stop_at_ratio"]:
# if s.is_seeding and self.options["stop_at_ratio"]:
# # We're a seed, so calculate the time to the 'stop_share_ratio'
# if not s.upload_payload_rate:
# return 0
# stop_ratio = self.session.settings().share_ratio_limit
# stop_ratio = self._session.settings().share_ratio_limit
# return (
# (s.all_time_download * stop_ratio) -
# s.all_time_upload
@ -530,6 +529,7 @@ class TorrentClass(GenlistItemClass):
class ItemMenu(Menu):
def __init__(self, parent, item, session, h):
Menu.__init__(self, parent)
@ -542,29 +542,22 @@ class ItemMenu(Menu):
)
q = self.item_add(None, _("Queue"), None, None)
self.item_add(
q, _("Up"), None, lambda x, y: h.queue_position_up()
)
q, _("Up"), None, lambda x, y: h.queue_position_up())
self.item_add(
q, _("Down"), None, lambda x, y: h.queue_position_down()
)
q, _("Down"), None, lambda x, y: h.queue_position_down())
self.item_add(
q, _("Top"), None, lambda x, y: h.queue_position_top()
)
q, _("Top"), None, lambda x, y: h.queue_position_top())
self.item_add(
q, _("Bottom"), None, lambda x, y: h.queue_position_bottom()
)
q, _("Bottom"), None, lambda x, y: h.queue_position_bottom())
rem = self.item_add(
None, _("Remove torrent"), None,
self.remove_torrent_cb, item, session, h, False
)
self.remove_torrent_cb, item, session, h, False)
self.item_add(
rem, _("and data files"), None,
self.remove_torrent_cb, item, session, h, True
)
self.remove_torrent_cb, item, session, h, True)
self.item_separator_add(None)
it = self.item_add(
None, _("Force reannounce"), None, lambda x, y: h.force_reannounce()
)
None, _("Force reannounce"), None, lambda x, y: h.force_reannounce())
it.tooltip_text_set(
"<b>Force reannounce</b> will force this torrent<br>"
"to do another tracker request, to receive new<br>"
@ -573,26 +566,21 @@ class ItemMenu(Menu):
"since the last announce, the forced announce<br>"
"will be scheduled to happen immediately as<br>"
"the min_interval expires. This is to honor<br>"
"trackers minimum re-announce interval settings."
)
"trackers minimum re-announce interval settings.")
self.item_add(
None, _("Force DHT reannounce"), None,
lambda x, y: h.force_dht_announce()
)
lambda x, y: h.force_dht_announce())
it = self.item_add(
None, _("Scrape tracker"), None,
lambda x, y: h.scrape_tracker()
)
lambda x, y: h.scrape_tracker())
it.tooltip_text_set(
"<b>Scrape tracker</b> will send a scrape request to the<br>"
"tracker. A scrape request queries the tracker for<br>"
"statistics such as total number of incomplete peers,<br>"
"complete peers, number of downloads etc."
)
"complete peers, number of downloads etc.")
it = self.item_add(
None, _("Force re-check"), None,
lambda x, y: h.force_recheck()
)
lambda x, y: h.force_recheck())
it.tooltip_text_set(
"force_recheck puts the torrent back in a state<br>"
"where it assumes to have no resume data.<br>"
@ -602,30 +590,25 @@ class ItemMenu(Menu):
"checked (all the files will be read and compared<br>"
"to the piece hashes).<br>"
"Once the check is complete, the torrent will start<br>"
"connecting to peers again, as normal."
)
"connecting to peers again, as normal.")
self.item_separator_add(None)
it = self.item_add(
None, _("Torrent properties"), None,
self.torrent_props_cb, h
)
self.torrent_props_cb, h)
def files_cb(menu, it, h):
from .TorrentProps import TorrentFiles
TorrentFiles(h)
self.item_add(
it, "Files", None,
files_cb, h
)
files_cb, h)
self.move(*item.track_object.pos)
del item.track_object
self.show()
def remove_torrent_cb(
self, menu, item, glitem, session, h, with_data=False
):
def remove_torrent_cb(self, menu, item, glitem, session, h, with_data=False):
menu.close()
session.remove_torrent(h, with_data)
@ -657,12 +640,22 @@ class TorrentTooltip(Table):
(_("Total uploaded this session"), intrepr, "total_payload_upload"),
(_("Total failed"), intrepr, "total_failed_bytes"),
(_("Number of seeds"), None, "num_seeds"),
(_("Number of peers"), None, "num_peers"),
)
(_("Number of peers"), None, "num_peers"))
bold = "font_weight=Bold"
def __init__(self, parent, h, s):
def __init__(self, parent, session, torrent):
handle = torrent.handle
if not handle.is_valid():
raise ValueError("Invalid handle")
flags = lt.status_flags_t.query_pieces
status = handle.status(flags)
# if not s.has_metadata:
# return
Table.__init__(self, parent, size_hint_weight=EXPAND_BOTH)
@ -673,7 +666,7 @@ class TorrentTooltip(Table):
l1 = Label(self, text=desc)
l1.show()
self.pack(l1, 0, i, 1, 1)
v = getattr(s, attr_name)
v = getattr(status, attr_name)
if conv:
if conv == datetime.fromtimestamp and v == 0:
v = _("N/A")
@ -693,27 +686,27 @@ class TorrentTooltip(Table):
WIDTH = 30
HEIGHT = 4
g = BlockGraph(
self, s.pieces, s.num_pieces,
size=(WIDTH, HEIGHT), size_hint_align=FILL_BOTH
)
graph = BlockGraph(
self, status.pieces, status.num_pieces,
size=(WIDTH, HEIGHT), size_hint_align=FILL_BOTH)
l.text = "".join((
"<font %s>" % (self.bold),
_("Pieces (scaled 1:%d)") % (g.block_size),
"</>"
))
_("Pieces (scaled 1:%d)") % (graph.block_size),
"</>"))
self.pack(g, 1, i, 1, 1)
g.show()
self.pack(graph, 1, i, 1, 1)
graph.show()
self.timer = Timer(1.0, self.update, h, self.items, value_labels, g)
self.timer = Timer(1.0, self.update, torrent, self.items, value_labels, graph)
self.on_del_add(lambda x: self.timer.delete())
@staticmethod
def update(h, items, value_labels, g):
def update(torrent, items, value_labels, g):
#log.debug("Tooltip TICK")
s = h.status(8)
handle = torrent.handle
flags = lt.status_flags_t.query_pieces
s = handle.status(flags)
for i, l in enumerate(value_labels):
conv, attr_name = items[i][1:]
v = getattr(s, attr_name)

View File

@ -1,7 +1,7 @@
#
# Epour - A bittorrent client using EFL and libtorrent
#
# Copyright 2012-2015 Kai Huuhko <kai.huuhko@gmail.com>
# Copyright 2012-2016 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
@ -19,14 +19,9 @@
# MA 02110-1301, USA.
#
import sys
import os
import mimetypes
import urllib
try:
import urlparse
except ImportError:
from urllib import parse as urlparse
from urllib.parse import urlparse, urlsplit
import logging
import shutil
try:
@ -42,10 +37,63 @@ from efl.ecore import Timer
from xdg.BaseDirectory import save_data_path, load_data_paths
log = logging.getLogger("epour.session")
flags_t = lt.add_torrent_params_flags_t
default_flags = (
flags_t.flag_apply_ip_filter +
flags_t.flag_update_subscribe +
flags_t.flag_duplicate_is_error +
flags_t.flag_auto_managed)
def read_torrent_file(info_hash):
if not info_hash:
log.debug("Tried to read torrent with invalid info_hash.")
return
info_hash = str(info_hash)
log.debug("Reading torrent file %s.torrent", info_hash)
paths = load_data_paths("epour")
for p in paths:
t_path = os.path.join(p, "%s.torrent" % info_hash)
if os.path.exists(t_path):
ti = lt.torrent_info(t_path)
return ti
def read_resume_data(info_hash):
log.debug("Reading resume data for %s", info_hash)
data = None
paths = load_data_paths("epour")
for p in paths:
t_path = os.path.join(p, "%s.fastresume" % info_hash)
if os.path.exists(t_path):
with open(t_path, "rb") as fp:
data = lt.read_resume_data(fp.read())
break
if data:
return data
else:
raise ValueError("Fast Resume data not found")
class Session(lt.session):
def __init__(self, conf):
def __init__(self, conf, shutdown_cb):
self.conf = conf
self.log = logging.getLogger("epour.session")
self._shutdown_cb = shutdown_cb
self._shutdown_timer = None
self._torrents_changed = False
self.torrents = OrderedDict()
self._outstanding_resume_data = 0
from epour import __version__ as version
ver_ints = []
@ -54,7 +102,7 @@ class Session(lt.session):
ver_ints.append(0)
fp = lt.fingerprint("EP", *ver_ints)
self.log.debug("peer-id: {}".format(fp))
log.debug("peer-id: %s", fp)
lt.session.__init__(
self,
@ -64,9 +112,7 @@ class Session(lt.session):
#lt.session_flags_t.start_default_features
)
self.log.info("Session started")
self.torrents = OrderedDict()
log.info("Session started")
#rsdpipsdtsppe
#stheprtertoer
@ -80,14 +126,32 @@ class Session(lt.session):
self.listen_on(
conf.getint("Settings", "listen_low"),
conf.getint("Settings", "listen_high")
)
conf.getint("Settings", "listen_high"))
self.alert_manager = AlertManager(self)
self.alert_manager.callback_add(
"add_torrent_alert", self._add_torrent_cb)
self.alert_manager.callback_add(
"metadata_received_alert", self._metadata_received_cb)
self.alert_manager.callback_add("add_torrent_alert", self._add_torrent_cb)
self.alert_manager.callback_add("metadata_received_alert", self._metadata_received_cb)
def save_resume_alert_cb(a):
handle = a.handle
if handle is None:
log.error("Tried to write resume data with invalid handle.")
return
info_hash = str(handle.info_hash())
data = a.resume_data
self.torrents[info_hash].write_resume_data(data)
self._outstanding_resume_data -= 1
log.debug("Resume data written for %s", info_hash)
def save_resume_failed_alert_cb(a):
log.error(a.message)
self._outstanding_resume_data -= 1
self.alert_manager.callback_add("save_resume_data_alert", save_resume_alert_cb)
self.alert_manager.callback_add("save_resume_data_failed_alert", save_resume_failed_alert_cb)
def torrent_finished_move_cb(a):
h = a.handle
@ -100,22 +164,51 @@ class Session(lt.session):
self.alert_manager.callback_add(
"torrent_finished_alert", torrent_finished_move_cb)
def periodic_save_func():
self.save_resume_data()
if self._torrents_changed:
self.save_torrents()
self._torrents_changed = False
return True
self.periodic_save_timer = Timer(15.0, periodic_save_func)
def state_update_alert_cb(a):
statuses = a.status
for status in statuses:
info_hash = status.info_hash
self.torrents[str(info_hash)].status = status
self.alert_manager.callback_add("state_update_alert", state_update_alert_cb)
def _add_torrent_cb(self, a):
e = a.error
if e.value() > 0:
self.log.error("Adding torrent failed: %r" % (e.message()))
params = a.params
info_hash = str(params["info_hash"])
if info_hash in self.torrents:
del self.torrents[info_hash]
log.error("Adding torrent %s failed: %s", info_hash, e.message())
return
h = a.handle
ihash = str(h.info_hash())
self.torrents[ihash] = a.params
self.log.debug("Torrent added.")
handle = a.handle
info_hash = handle.info_hash()
log.debug("Torrent %s added", info_hash)
if str(info_hash) in self.torrents:
log.debug("Torrent already in list, setting session")
self.torrents[str(info_hash)].set_session(self)
else:
torrent = Torrent(self, info_hash)
self.torrents[str(info_hash)] = torrent
self._torrents_changed = True
def _metadata_received_cb(self, a):
h = a.handle
ihash = str(h.info_hash())
self.log.debug("Metadata received.")
t_info = h.get_torrent_info()
self.torrents[ihash]["ti"] = t_info
handle = a.handle
info_hash = handle.info_hash()
log.debug("Metadata received for %s", str(info_hash))
torrent = self.torrents[str(info_hash)]
torrent.add_metadata()
def load_state(self):
for p in load_data_paths("epour"):
@ -126,18 +219,20 @@ class Session(lt.session):
state = lt.bdecode(f.read())
lt.session.load_state(self, state)
except Exception as e:
self.log.debug("Could not load previous session state.")
self.log.debug(e)
log.debug("Could not load previous session state.")
log.debug(e)
else:
self.log.info("Session restored from disk.")
log.info("Session restored from disk.")
break
settings = self.settings()
from epour import __version__ as version
version += ".0"
ver_s = "Epour/{} libtorrent/{}".format(version, lt.version)
settings.user_agent = ver_s
self.log.debug("User agent: {}".format(ver_s))
log.debug("User agent: %s", ver_s)
self.set_settings(settings)
def save_state(self):
@ -147,7 +242,7 @@ class Session(lt.session):
with open(path, 'wb') as f:
f.write(lt.bencode(state))
self.log.debug("Session state saved.")
log.debug("Session state saved.")
def load_torrents(self):
for p in load_data_paths("epour"):
@ -156,204 +251,171 @@ class Session(lt.session):
break
if not torrents_path:
self.info.debug("No previous list of torrents found.")
log.debug("Previous list of torrents not found.")
return
try:
pkl_file = open(torrents_path, 'rb')
except IOError:
self.log.warning("Could not open the list of torrents.")
log.warning("Could not open the list of torrents.")
else:
try:
torrents = cPickle.load(pkl_file)
except Exception:
self.log.exception("Opening the list of torrents failed.")
log.exception("Opening the list of torrents failed.")
else:
self.log.debug(
log.debug(
"List of torrents opened, "
"restoring {} torrents.".format(len(torrents))
"restoring %d torrents.", len(torrents)
)
for i, t in torrents.items():
try:
for k, v in t.items():
if v is None:
continue
elif k == "ti":
# Epour <= 0.6 compat
if isinstance(v, dict):
t[k] = lt.torrent_info(lt.bdecode(v))
else:
t[k] = lt.bdecode(v)
# elif k == "info_hash":
# torrents[i][k] = lt.big_number(v)
except Exception:
self.log.exception("Opening torrent %s failed", i)
continue
for info_hash, torrent in torrents.items():
log.debug("Restoring torrent %s", info_hash)
self.torrents[info_hash] = torrent
self.async_add_torrent(t)
params_dict = torrent.get_params()
params = None
if "ti" in params_dict and params_dict["ti"]:
try:
ti = read_torrent_file(info_hash)
except Exception:
log.exception("Opening torrent %s failed", info_hash)
else:
params_dict["ti"] = ti
try:
params = read_resume_data(info_hash)
except Exception:
pass
else:
params.trackers = list(set(params.trackers))
if params is None:
params = lt.add_torrent_params()
for k, v in params_dict.items():
setattr(params, k, v)
try:
self.async_add_torrent(params)
except Exception:
log.exception("Opening torrent %s failed", info_hash)
continue
finally:
pkl_file.close()
def save_torrents(self):
self.log.debug("Saving {} torrents.".format(len(self.torrents)))
def save_resume_data(self):
for handle in self.get_torrents():
if not handle.is_valid():
log.error("Invalid handle while trying to save resume data")
continue
status = handle.status(0)
if not status.has_metadata:
continue
if not status.need_save_resume:
continue
for i, t in self.torrents.items():
for k, v in t.items():
if k == "info_hash":
if v.is_all_zeros():
del self.torrents[i][k]
else:
self.torrents[i][k] = v.to_bytes()
handle.save_resume_data()
self._outstanding_resume_data += 1
handles = self.get_torrents()
for h in handles:
if h.is_valid():
i = str(h.info_hash())
t_dict = self.torrents[i]
t_dict["save_path"] = h.save_path()
s = h.status(0)
if s.has_metadata:
resume_data = lt.bencode(h.write_resume_data())
t_dict["resume_data"] = resume_data
t_info = h.get_torrent_info()
t_dict["ti"] = lt.bencode(t_info)
def shutdown(self):
self.pause()
self.save_resume_data()
def _check_outstanding():
if self._outstanding_resume_data == 0:
self.save_torrents()
self.save_state()
self._shutdown_cb()
return False
else:
self.log.debug("Handle is invalid, skipping")
return True
self._shutdown_timer = Timer(1.0, _check_outstanding)
def save_torrents(self):
for info_hash, torrent in self.torrents.items():
info_hash2 = str(torrent.handle.info_hash())
assert info_hash == info_hash2, "%s is not %s" % (info_hash, info_hash2)
path = os.path.join(save_data_path("epour"), "torrents")
with open(path, 'wb') as f:
cPickle.dump(self.torrents, f, protocol=cPickle.HIGHEST_PROTOCOL)
self.log.debug("List of torrents saved.")
# def write_torrent(self, h):
# if h is None:
# self.log.debug("Tried to write torrent while handle was empty.")
# return
# t_info = h.get_torrent_info()
# ihash = str(h.info_hash())
# self.log.debug("Writing torrent file {}".format(ihash))
# md = lt.bdecode(t_info.metadata())
# t = {}
# t["info"] = md
# p = save_data_path("epour")
# t_path = os.path.join(p, "{0}.torrent".format(ihash))
# if t_path:
# with open(t_path, "wb") as f:
# f.write(lt.bencode(t))
# return t_path
try:
data = cPickle.dumps(self.torrents, protocol=cPickle.HIGHEST_PROTOCOL)
except Exception:
log.exception("Failed to save torrents")
else:
with open(path, 'wb') as fp:
fp.write(data)
log.debug("List of torrents saved.")
def remove_torrent(self, h, with_data=False):
ihash = str(h.info_hash())
info_hash = str(h.info_hash())
del self.torrents[ihash]
torrent = self.torrents[info_hash]
torrent.delete_torrent_file()
torrent.delete_resume_data()
del self.torrents[info_hash]
lt.session.remove_torrent(self, h, option=with_data)
for p in load_data_paths("epour"):
fr_path = os.path.join(
p, "{0}.fastresume".format(ihash)
)
self._torrents_changed = True
try:
with open(fr_path):
pass
except IOError:
self.log.debug("Could not remove %s", fr_path)
def add_torrent_with_uri(self, uri):
storage_path = self.conf.get("Settings", "storage_path")
#default_flags = self.conf.get("Settings", "default_flags")
add_dict = {
"save_path": storage_path,
"flags": default_flags,
}
self.fill_add_dict_based_on_uri(add_dict, uri)
self.add_torrent_with_dict(add_dict)
def fill_add_dict_based_on_uri(self, add_dict, uri):
parsed_uri = urlparse(uri)
if parsed_uri.scheme == "magnet":
add_dict["url"] = uri
elif parsed_uri.scheme == "file" or parsed_uri.scheme == "" and os.path.isfile(parsed_uri.path):
path = parsed_uri.path
mimetype = mimetypes.guess_type(path)[0]
if not mimetype == "application/x-bittorrent":
log.warning("%s is not of a known torrent file type", path)
with open(path, 'rb') as t:
t_raw = lt.bdecode(t.read())
info = lt.torrent_info(t_raw)
add_dict["ti"] = info
ihash = str(info.info_hash())
path_dir = save_data_path("epour")
new_path = os.path.join(path_dir, "{0}.torrent".format(ihash))
if path == new_path:
pass
else:
os.remove(fr_path)
shutil.copy(path, new_path)
t_path = None
for p in load_data_paths("epour"):
t_path = os.path.join(p, "{0}.torrent".format(ihash))
break
if self.conf.getboolean("Settings", "delete_original"):
log.debug(
"Deleting original torrent file %s", path)
os.remove(path)
if t_path:
try:
with open(t_path):
pass
except IOError:
self.log.debug("Could not remove torrent file.")
else:
os.remove(t_path)
path = new_path
if not hasattr(lt, "torrent_removed_alert"):
class torrent_removed_alert(object):
def __init__(self, h, info_hash):
self.handle = h
self.info_hash = info_hash
a = torrent_removed_alert(h, ihash)
self.alert_manager.signal(a)
return ihash
def add_torrent_from_file(self, add_dict, t_uri):
mimetype = mimetypes.guess_type(t_uri)[0]
if not mimetype == "application/x-bittorrent":
self.log.error("Invalid file")
return
if t_uri.startswith("file://"):
t_uri = urllib.unquote(urlparse.urlsplit(t_uri).path)
with open(t_uri, 'rb') as t:
t_raw = lt.bdecode(t.read())
info = lt.torrent_info(t_raw)
add_dict["ti"] = info
rd = None
fr_file_name = "{}.fastresume".format(info.info_hash())
for p in load_data_paths("epour"):
path = os.path.join(p, fr_file_name)
if os.path.isfile(path):
try:
with open(path, "rb") as f:
rd = f.read()
except Exception:
self.log.debug("Invalid resume data")
else:
add_dict["resume_data"] = rd
break
ihash = str(info.info_hash())
path = save_data_path("epour")
new_uri = os.path.join(path, "{0}.torrent".format(ihash))
if t_uri == new_uri:
pass
elif len(uri) == 40: # looks like a sha1 string
add_dict["info_hash"] = uri
# elif uri.scheme == "http" or uri.scheme == "https":
# pass
else:
shutil.copy(t_uri, new_uri)
raise RuntimeError("Could not parse the torrent string.")
if self.conf.getboolean("Settings", "delete_original"):
self.log.debug(
"Deleting original torrent file {}".format(t_uri))
os.remove(t_uri)
t_uri = new_uri
self.async_add_torrent(add_dict)
def add_torrent_from_magnet(self, add_dict, t_uri):
self.log.debug("Adding %r", t_uri)
t_uri = t_uri.encode("ascii")
tmp_dict = lt.parse_magnet_uri(t_uri)
tmp_dict.update(add_dict)
tmp_dict["info_hash"] = tmp_dict["info_hash"].to_bytes()
self.async_add_torrent(tmp_dict)
def add_torrent_from_hash(self, add_dict, t_uri):
t_uri = t_uri.encode("ascii")
add_dict["info_hash"] = t_uri
self.log.debug("Adding %s", t_uri)
def add_torrent_with_dict(self, add_dict):
self.async_add_torrent(add_dict)
@ -383,18 +445,255 @@ class AlertManager(object):
a_name = type(a).__name__
if a_name not in self.alerts:
self.log.debug("No handler: {} | {}".format(a_name, a))
log.debug("No handler: %s | %s", a_name, a)
return
for cb, args, kwargs in self.alerts[a_name]:
try:
cb(a, *args, **kwargs)
except:
self.log.exception("Exception while handling alerts")
log.exception("Exception while handling alerts")
def update(self):
#self.log.debug("Alerts TICK")
for a in self.session.pop_alerts():
self.signal(a)
return True
def status_to_flags(status):
#flags_t = lt.add_torrent_params_flags_t
flags = 0
flags += 1 if status.is_seeding else 0
#flags += 2 deprecated
flags += 4 if status.upload_mode else 0
flags += 8 if status.share_mode else 0
flags += 16 if status.ip_filter_applies else 0
flags += 32 if status.paused else 0
flags += 64 # auto_managed
flags += 128 # duplicate_is_error
#flags += 256 deprecated
flags += 512 # update_subscribe
flags += 1024 if status.super_seeding else 0
flags += 2048 if status.sequential_download else 0
#flags += 4096 if pinned else 0
#flags += 8192 if stop_when_ready else 0
#flags += 16384 if override_trackers else 0
#flags += 32768 if override_web_seeds else 0
#flags += 65536 deprecated
#flags += 131072 if override_resume_data else 0
#flags += 262144 if merge_resume_trackers else 0
#flags += 524288 if use_resume_save_path else 0
#flags += 1048576 if merge_resume_http_seeds else 0
return flags
class Torrent(object):
def __init__(self, session, info_hash, options={}):
assert isinstance(session, lt.session)
assert isinstance(info_hash, lt.sha1_hash)
self.session = session
self.info_hash = info_hash
self.options = options
self._status = None
# @property
# def info_hash(self):
# return self.handle.info_hash()
@property
def handle(self):
return self.session.find_torrent(self.info_hash)
@property
def state(self):
if getattr(self, "_state", None):
return self._state
else:
status = self.handle.status(0)
state = status.state
self._state = state
return state
@state.setter
def state(self, value):
self._state = value
@property
def status(self):
if getattr(self, "_status", None):
return self._status
else:
status = self.handle.status(64)
self._status = status
return status
@status.setter
def status(self, value):
self._status = value
def get_params(self):
return self._params
def _get_params(self):
handle = self.handle
status = handle.status()
flags = status_to_flags(status)
# trackers = []
# for tracker in handle.trackers():
# trackers.append(tracker["url"])
# trackers = ",".join(trackers)
params = {
#"trackers": trackers,
"url_seeds": handle.url_seeds(),
#"dht_nodes":
"name": status.name,
"save_path": status.save_path,
"storage_mode": status.storage_mode,
#"storage":
#"userdata":
"file_priorities": handle.file_priorities(),
#"trackerid":
#"url":
#"uuid":
#"source_feed_url":
"flags": flags,
#"info_hash": handle.info_hash().to_bytes(),
"info_hash": self.info_hash,
"max_uploads": handle.max_uploads(),
"max_connections": handle.max_connections(),
"upload_limit": handle.upload_limit(),
"download_limit": handle.download_limit(),
}
if self.has_metadata:
params["ti"] = self.torrent_file_path
return params
def __getstate__(self):
state = self.__dict__.copy()
params = self._get_params()
info_hash1 = params["info_hash"].to_bytes()
params["info_hash"] = info_hash1
state["_params"] = params
info_hash2 = state["info_hash"].to_bytes()
state["info_hash"] = info_hash2
del state["session"]
del state["_status"]
del state["_state"]
return state
def __setstate__(self, state):
params = state["_params"]
info_hash1 = params["info_hash"]
info_hash1 = lt.sha1_hash(info_hash1)
params["info_hash"] = info_hash1
info_hash2 = state["info_hash"]
info_hash2 = lt.sha1_hash(info_hash2)
state["info_hash"] = info_hash1
self.__dict__.update(state)
def set_session(self, session):
self.session = session
@property
def has_metadata(self):
return self.handle.status(0).has_metadata
@property
def torrent_file_path(self):
paths = load_data_paths("epour")
for p in paths:
t_path = os.path.join(p, "{0}.torrent".format(self.info_hash))
if os.path.exists(t_path):
return t_path
return None
def write_torrent_file(self):
assert self.handle is not None
handle = self.handle
info_hash = str(handle.info_hash())
log.debug("Writing torrent file {0}.torrent".format(info_hash))
p = save_data_path("epour")
t_path = os.path.join(p, "{0}.torrent".format(info_hash))
if t_path:
t_info = handle.torrent_file()
metadata = lt.bdecode(t_info.metadata())
torrent_file = {"info": metadata}
with open(t_path, "wb") as fp:
fp.write(lt.bencode(torrent_file))
log.debug("Torrent file was written to %s" % t_path)
return t_path
def write_resume_data(self, data):
assert self.handle is not None
info_hash = self.handle.info_hash()
info_hash = str(info_hash)
log.debug("Writing resume data for {}".format(info_hash))
t_path = os.path.join(
save_data_path("epour"),
info_hash + ".fastresume")
if t_path:
with open(t_path, "wb") as f:
f.write(lt.bencode(data))
else:
return
return t_path
def delete_torrent_file(self):
assert self.handle is not None
info_hash = str(self.handle.info_hash())
for p in load_data_paths("epour"):
t_path = os.path.join(p, "{0}.torrent".format(info_hash))
try:
with open(t_path):
pass
except IOError:
continue
else:
os.remove(t_path)
break
def delete_resume_data(self):
assert self.handle is not None
info_hash = str(self.handle.info_hash())
for p in load_data_paths("epour"):
fr_path = os.path.join(p, "{0}.fastresume".format(info_hash))
try:
with open(fr_path):
pass
except IOError:
continue
else:
os.remove(fr_path)
def add_metadata(self):
self.write_torrent_file()

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-01-20 22:04+0900\n"
"POT-Creation-Date: 2016-08-04 18:18+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,6 +17,228 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../epour/gui/Widgets.py:130
msgid "Close"
msgstr ""
#: ../epour/gui/Widgets.py:143
msgid "OK"
msgstr ""
#: ../epour/gui/Widgets.py:152
msgid "Confirm exit"
msgstr ""
#: ../epour/gui/Widgets.py:153
msgid "Are you sure you wish to exit Epour?"
msgstr ""
#: ../epour/gui/Widgets.py:155
msgid "Yes"
msgstr ""
#: ../epour/gui/Widgets.py:159
msgid "No"
msgstr ""
#: ../epour/gui/__init__.py:91
msgid "Add torrent"
msgstr ""
#: ../epour/gui/__init__.py:103
msgid "Pause Session"
msgstr ""
#: ../epour/gui/__init__.py:106
msgid "Resume Session"
msgstr ""
#: ../epour/gui/__init__.py:121
msgid "Preferences"
msgstr ""
#: ../epour/gui/__init__.py:124
msgid "General"
msgstr ""
#: ../epour/gui/__init__.py:127
msgid "Proxy"
msgstr ""
#: ../epour/gui/__init__.py:130 ../epour/gui/__init__.py:340
msgid "Session"
msgstr ""
#: ../epour/gui/__init__.py:133
msgid "Exit"
msgstr ""
#: ../epour/gui/__init__.py:240
msgid "Torrent {} has finished downloading."
msgstr ""
#: ../epour/gui/__init__.py:371
msgid "Peer connections"
msgstr ""
#: ../epour/gui/__init__.py:379
msgid "Upload slots"
msgstr ""
#: ../epour/gui/__init__.py:388
msgid "Listening"
msgstr ""
#: ../epour/gui/__init__.py:432 ../epour/gui/TorrentProps.py:539
msgid "Queued"
msgstr ""
#: ../epour/gui/__init__.py:432 ../epour/gui/TorrentProps.py:539
msgid "Checking"
msgstr ""
#: ../epour/gui/__init__.py:432 ../epour/gui/TorrentProps.py:539
msgid "Downloading metadata"
msgstr ""
#: ../epour/gui/__init__.py:433 ../epour/gui/TorrentProps.py:539
msgid "Downloading"
msgstr ""
#: ../epour/gui/__init__.py:433 ../epour/gui/TorrentProps.py:540
msgid "Finished"
msgstr ""
#: ../epour/gui/__init__.py:433 ../epour/gui/TorrentProps.py:540
msgid "Seeding"
msgstr ""
#: ../epour/gui/__init__.py:433 ../epour/gui/TorrentProps.py:540
msgid "Allocating"
msgstr ""
#: ../epour/gui/__init__.py:434 ../epour/gui/TorrentProps.py:540
msgid "Checking resume data"
msgstr ""
#: ../epour/gui/__init__.py:448
msgid "Invalid torrent"
msgstr ""
#: ../epour/gui/__init__.py:459
#, python-brace-format
msgid "{0:.0%} complete, ETA: {1} (Down: {2}/s Up: {3}/s Queue pos: {4})"
msgstr ""
#: ../epour/gui/__init__.py:538
msgid "Resume"
msgstr ""
#: ../epour/gui/__init__.py:538
msgid "Pause"
msgstr ""
#: ../epour/gui/__init__.py:543
msgid "Queue"
msgstr ""
#: ../epour/gui/__init__.py:545
msgid "Up"
msgstr ""
#: ../epour/gui/__init__.py:548
msgid "Down"
msgstr ""
#: ../epour/gui/__init__.py:549
msgid "Top"
msgstr ""
#: ../epour/gui/__init__.py:551
msgid "Bottom"
msgstr ""
#: ../epour/gui/__init__.py:553
msgid "Remove torrent"
msgstr ""
#: ../epour/gui/__init__.py:556
msgid "and data files"
msgstr ""
#: ../epour/gui/__init__.py:560
msgid "Force reannounce"
msgstr ""
#: ../epour/gui/__init__.py:571
msgid "Force DHT reannounce"
msgstr ""
#: ../epour/gui/__init__.py:574
msgid "Scrape tracker"
msgstr ""
#: ../epour/gui/__init__.py:582
msgid "Force re-check"
msgstr ""
#: ../epour/gui/__init__.py:596
msgid "Torrent properties"
msgstr ""
#: ../epour/gui/__init__.py:632
msgid "Time when added"
msgstr ""
#: ../epour/gui/__init__.py:633
msgid "Time when completed"
msgstr ""
#: ../epour/gui/__init__.py:634
msgid "All time downloaded"
msgstr ""
#: ../epour/gui/__init__.py:635
msgid "All time uploaded"
msgstr ""
#: ../epour/gui/__init__.py:636
msgid "Total wanted done"
msgstr ""
#: ../epour/gui/__init__.py:637
msgid "Total wanted"
msgstr ""
#: ../epour/gui/__init__.py:638
msgid "Total downloaded this session"
msgstr ""
#: ../epour/gui/__init__.py:640
msgid "Total uploaded this session"
msgstr ""
#: ../epour/gui/__init__.py:641
msgid "Total failed"
msgstr ""
#: ../epour/gui/__init__.py:642
msgid "Number of seeds"
msgstr ""
#: ../epour/gui/__init__.py:643
msgid "Number of peers"
msgstr ""
#: ../epour/gui/__init__.py:672 ../epour/gui/__init__.py:715
msgid "N/A"
msgstr ""
#: ../epour/gui/__init__.py:695
#, python-format
msgid "Pieces (scaled 1:%d)"
msgstr ""
#: ../epour/gui/TorrentProps.py:90
msgid "Enable/disable file download"
msgstr ""
@ -74,228 +296,6 @@ msgstr ""
msgid "disabled"
msgstr ""
#: ../epour/gui/TorrentProps.py:539 ../epour/gui/__init__.py:432
msgid "Queued"
msgstr ""
#: ../epour/gui/TorrentProps.py:539 ../epour/gui/__init__.py:432
msgid "Checking"
msgstr ""
#: ../epour/gui/TorrentProps.py:539 ../epour/gui/__init__.py:432
msgid "Downloading metadata"
msgstr ""
#: ../epour/gui/TorrentProps.py:539 ../epour/gui/__init__.py:433
msgid "Downloading"
msgstr ""
#: ../epour/gui/TorrentProps.py:540 ../epour/gui/__init__.py:433
msgid "Finished"
msgstr ""
#: ../epour/gui/TorrentProps.py:540 ../epour/gui/__init__.py:433
msgid "Seeding"
msgstr ""
#: ../epour/gui/TorrentProps.py:540 ../epour/gui/__init__.py:433
msgid "Allocating"
msgstr ""
#: ../epour/gui/TorrentProps.py:540 ../epour/gui/__init__.py:434
msgid "Checking resume data"
msgstr ""
#: ../epour/gui/Widgets.py:109
msgid "Close"
msgstr ""
#: ../epour/gui/Widgets.py:122
msgid "OK"
msgstr ""
#: ../epour/gui/Widgets.py:131
msgid "Confirm exit"
msgstr ""
#: ../epour/gui/Widgets.py:132
msgid "Are you sure you wish to exit Epour?"
msgstr ""
#: ../epour/gui/Widgets.py:134
msgid "Yes"
msgstr ""
#: ../epour/gui/Widgets.py:138
msgid "No"
msgstr ""
#: ../epour/gui/__init__.py:93
msgid "Add torrent"
msgstr ""
#: ../epour/gui/__init__.py:106
msgid "Pause Session"
msgstr ""
#: ../epour/gui/__init__.py:110
msgid "Resume Session"
msgstr ""
#: ../epour/gui/__init__.py:126
msgid "Preferences"
msgstr ""
#: ../epour/gui/__init__.py:129
msgid "General"
msgstr ""
#: ../epour/gui/__init__.py:133
msgid "Proxy"
msgstr ""
#: ../epour/gui/__init__.py:137 ../epour/gui/__init__.py:339
msgid "Session"
msgstr ""
#: ../epour/gui/__init__.py:141
msgid "Exit"
msgstr ""
#: ../epour/gui/__init__.py:225
msgid "Torrent {} has finished downloading."
msgstr ""
#: ../epour/gui/__init__.py:370
msgid "Peer connections"
msgstr ""
#: ../epour/gui/__init__.py:378
msgid "Upload slots"
msgstr ""
#: ../epour/gui/__init__.py:387
msgid "Listening"
msgstr ""
#: ../epour/gui/__init__.py:454
msgid "Invalid torrent"
msgstr ""
#: ../epour/gui/__init__.py:460
#, python-brace-format
msgid "{0:.0%} complete, ETA: {1} (Down: {2}/s Up: {3}/s Queue pos: {4})"
msgstr ""
#: ../epour/gui/__init__.py:538
msgid "Resume"
msgstr ""
#: ../epour/gui/__init__.py:538
msgid "Pause"
msgstr ""
#: ../epour/gui/__init__.py:543
msgid "Queue"
msgstr ""
#: ../epour/gui/__init__.py:545
msgid "Up"
msgstr ""
#: ../epour/gui/__init__.py:548
msgid "Down"
msgstr ""
#: ../epour/gui/__init__.py:551
msgid "Top"
msgstr ""
#: ../epour/gui/__init__.py:554
msgid "Bottom"
msgstr ""
#: ../epour/gui/__init__.py:557
msgid "Remove torrent"
msgstr ""
#: ../epour/gui/__init__.py:561
msgid "and data files"
msgstr ""
#: ../epour/gui/__init__.py:566
msgid "Force reannounce"
msgstr ""
#: ../epour/gui/__init__.py:579
msgid "Force DHT reannounce"
msgstr ""
#: ../epour/gui/__init__.py:583
msgid "Scrape tracker"
msgstr ""
#: ../epour/gui/__init__.py:593
msgid "Force re-check"
msgstr ""
#: ../epour/gui/__init__.py:609
msgid "Torrent properties"
msgstr ""
#: ../epour/gui/__init__.py:649
msgid "Time when added"
msgstr ""
#: ../epour/gui/__init__.py:650
msgid "Time when completed"
msgstr ""
#: ../epour/gui/__init__.py:651
msgid "All time downloaded"
msgstr ""
#: ../epour/gui/__init__.py:652
msgid "All time uploaded"
msgstr ""
#: ../epour/gui/__init__.py:653
msgid "Total wanted done"
msgstr ""
#: ../epour/gui/__init__.py:654
msgid "Total wanted"
msgstr ""
#: ../epour/gui/__init__.py:655
msgid "Total downloaded this session"
msgstr ""
#: ../epour/gui/__init__.py:657
msgid "Total uploaded this session"
msgstr ""
#: ../epour/gui/__init__.py:658
msgid "Total failed"
msgstr ""
#: ../epour/gui/__init__.py:659
msgid "Number of seeds"
msgstr ""
#: ../epour/gui/__init__.py:660
msgid "Number of peers"
msgstr ""
#: ../epour/gui/__init__.py:679 ../epour/gui/__init__.py:722
msgid "N/A"
msgstr ""
#: ../epour/gui/__init__.py:703
#, python-format
msgid "Pieces (scaled 1:%d)"
msgstr ""
#: ../epour/gui/Preferences.py:97
msgid "Epour General Preferences"
msgstr ""