forked from enlightenment/epour
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:
parent
8bf8f56dcb
commit
0b1b72fcac
23
README
23
README
|
@ -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
4
TODO
|
@ -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__
|
||||
|
|
12
bin/epour
12
bin/epour
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
703
epour/session.py
703
epour/session.py
|
@ -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()
|
||||
|
|
446
po/epour.pot
446
po/epour.pot
|
@ -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 ""
|
||||
|
|
Loading…
Reference in New Issue