Re-organize for better separation between gui and internals.
This commit is contained in:
parent
681932a0ef
commit
683299edce
10
bin/epour
10
bin/epour
|
@ -1,13 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
try:
|
||||
import elementary as elm
|
||||
except ImportError:
|
||||
from efl import elementary as elm
|
||||
import logging
|
||||
|
||||
import epour.Epour as Epour
|
||||
|
||||
elm.init()
|
||||
epour = Epour.Epour(sys.argv[1:])
|
||||
elm.run()
|
||||
elm.shutdown()
|
||||
logging.shutdown()
|
||||
|
|
277
epour/Epour.py
277
epour/Epour.py
|
@ -43,7 +43,8 @@ def setup_log():
|
|||
log.addHandler(ch)
|
||||
|
||||
fh = logging.FileHandler(os.path.join(data_dir, "epour.log"))
|
||||
fh_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
fh_formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
fh.setFormatter(fh_formatter)
|
||||
fh.setLevel(logging.ERROR)
|
||||
log.addHandler(fh)
|
||||
|
@ -52,7 +53,6 @@ def setup_log():
|
|||
|
||||
log = setup_log()
|
||||
|
||||
|
||||
try:
|
||||
from e_dbus import DBusEcoreMainLoop
|
||||
except ImportError:
|
||||
|
@ -73,11 +73,10 @@ except dbus.exceptions.DBusException:
|
|||
if dbo:
|
||||
if sys.argv[1:]:
|
||||
for f in sys.argv[1:]:
|
||||
log.info("Sending %s via dbus" % f)
|
||||
self.log.info("Sending %s via dbus" % f)
|
||||
dbo.AddTorrent(f, dbus_interface="net.launchpad.epour")
|
||||
sys.exit()
|
||||
|
||||
|
||||
import mimetypes
|
||||
from ConfigParser import SafeConfigParser
|
||||
import cPickle
|
||||
|
@ -87,14 +86,9 @@ import HTMLParser
|
|||
import libtorrent as lt
|
||||
|
||||
try:
|
||||
from efl import elementary as elm
|
||||
from efl.ecore import Timer
|
||||
except:
|
||||
import elementary as elm
|
||||
|
||||
try:
|
||||
from efl.ecore import timer_add
|
||||
except:
|
||||
from ecore import timer_add
|
||||
from ecore import Timer
|
||||
|
||||
from gui import MainInterface
|
||||
|
||||
|
@ -112,105 +106,12 @@ class Epour(object):
|
|||
for t in torrents:
|
||||
self.session.add_torrent(t)
|
||||
|
||||
self.dbusname = dbus.service.BusName("net.launchpad.epour", dbus.SessionBus())
|
||||
self.dbusname = dbus.service.BusName(
|
||||
"net.launchpad.epour", dbus.SessionBus()
|
||||
)
|
||||
self.dbo = EpourDBus(self)
|
||||
|
||||
timer = timer_add(1.0, self.update)
|
||||
|
||||
def update(self):
|
||||
while 1:
|
||||
a = self.session.pop_alert()
|
||||
if not a: break
|
||||
|
||||
log = logging.getLogger("epour.alert")
|
||||
|
||||
if isinstance(a, lt.torrent_alert):
|
||||
# Torrent alerts have handle
|
||||
h = a.handle
|
||||
if h.is_valid():
|
||||
try:
|
||||
i = h.get_torrent_info()
|
||||
except:
|
||||
pass
|
||||
ihash = str(h.info_hash())
|
||||
else:
|
||||
continue
|
||||
|
||||
if isinstance(a, lt.peer_alert):
|
||||
pass
|
||||
|
||||
elif isinstance(a, lt.state_changed_alert) \
|
||||
or isinstance(a, lt.torrent_paused_alert) \
|
||||
or isinstance(a, lt.torrent_resumed_alert):
|
||||
self.gui.update_icon(ihash)
|
||||
|
||||
elif isinstance(a, lt.metadata_received_alert):
|
||||
log.debug("Metadata received.")
|
||||
t_path = self.write_torrent(h)
|
||||
self.session.torrents[ihash] = t_path
|
||||
|
||||
#elif isinstance(a, lt.tracker_alert):
|
||||
# Tracker alerts also have url
|
||||
|
||||
elif isinstance(a, lt.torrent_finished_alert):
|
||||
msg = "Torrent {} has finished downloading.".format(cgi.escape(h.name()))
|
||||
log.info(msg)
|
||||
self.gui.show_information(msg)
|
||||
|
||||
else:
|
||||
log.debug(a)
|
||||
|
||||
elif isinstance(a, lt.listen_failed_alert):
|
||||
errmsg = "Cannot listen on the configured ports."
|
||||
log.error(errmsg)
|
||||
self.gui.show_error(
|
||||
errmsg,
|
||||
"Please try other listen ports in the configuration."
|
||||
)
|
||||
|
||||
elif isinstance(a, lt.file_error_alert):
|
||||
errmsg = "File error."
|
||||
log.error(errmsg)
|
||||
self.gui.show_error(
|
||||
errmsg,
|
||||
"Error with file {}, error message {}.".format(
|
||||
a.file,
|
||||
a.error
|
||||
)
|
||||
)
|
||||
|
||||
elif isinstance(a, lt.file_rename_failed_alert):
|
||||
errmsg = "File rename failed."
|
||||
log.error(errmsg)
|
||||
self.gui.show_error(
|
||||
errmsg,
|
||||
"Renaming a file with index {} failed, error message {}.".format(
|
||||
a.index,
|
||||
a.error
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
log.debug(a)
|
||||
|
||||
self.gui.update_texts(self.session)
|
||||
|
||||
return 1
|
||||
|
||||
def write_torrent(self, h):
|
||||
t_info = h.get_torrent_info()
|
||||
ihash = str(h.info_hash())
|
||||
|
||||
log.debug("Writing torrent file {}".format(ihash))
|
||||
|
||||
md = lt.bdecode(t_info.metadata())
|
||||
t = {}
|
||||
t["info"] = md
|
||||
t_path = os.path.join(data_dir, "{}.torrent".format(t_info.info_hash()))
|
||||
with open(t_path, "wb") as f:
|
||||
f.write(lt.bencode(t))
|
||||
|
||||
return t_path
|
||||
self.gui.run()
|
||||
|
||||
def quit(self):
|
||||
session = self.session
|
||||
|
@ -232,13 +133,13 @@ class Epour(object):
|
|||
except:
|
||||
log.exception("Saving conf failed")
|
||||
|
||||
elm.exit()
|
||||
|
||||
class Session(lt.session):
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
lt.session.__init__(self)
|
||||
|
||||
self.log = logging.getLogger("epour.session")
|
||||
|
||||
self.handles = {}
|
||||
self.torrents = {}
|
||||
|
||||
|
@ -254,16 +155,49 @@ class Session(lt.session):
|
|||
|
||||
conf = self.conf = self.setup_conf()
|
||||
|
||||
self.listen_on(conf.getint("Settings", "listen_low"), conf.getint("Settings", "listen_high"))
|
||||
self.listen_on(
|
||||
conf.getint("Settings", "listen_low"),
|
||||
conf.getint("Settings", "listen_high")
|
||||
)
|
||||
|
||||
self.alert_manager = AlertManager(self)
|
||||
|
||||
self.alert_manager.callback_add(lt.metadata_received_alert, self.metadata_received)
|
||||
|
||||
def metadata_received(self, h):
|
||||
ihash = str(h.info_hash())
|
||||
self.log.debug("Metadata received.")
|
||||
t_path = self.write_torrent(h)
|
||||
self.torrents[ihash] = t_path
|
||||
|
||||
def write_torrent(self, h):
|
||||
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
|
||||
t_path = os.path.join(
|
||||
data_dir, "{}.torrent".format(ihash)
|
||||
)
|
||||
with open(t_path, "wb") as f:
|
||||
f.write(lt.bencode(t))
|
||||
|
||||
return t_path
|
||||
|
||||
|
||||
def setup_conf(self):
|
||||
conf = SafeConfigParser({
|
||||
"storage_path": os.path.expanduser(os.path.join("~", "Downloads")),
|
||||
"storage_path": os.path.expanduser(
|
||||
os.path.join("~", "Downloads")
|
||||
),
|
||||
"confirmations": str(False),
|
||||
"delete_original": str(False),
|
||||
"listen_low": str(0),
|
||||
"listen_high": str(0),
|
||||
})
|
||||
})
|
||||
|
||||
conf.read(conf_path)
|
||||
|
||||
|
@ -282,7 +216,7 @@ class Session(lt.session):
|
|||
state = lt.bdecode(f.read())
|
||||
lt.session.load_state(self, state)
|
||||
except:
|
||||
log.debug("Could not load previous session state.")
|
||||
self.log.debug("Could not load previous session state.")
|
||||
|
||||
def save_state(self):
|
||||
state = lt.session.save_state(self)
|
||||
|
@ -290,30 +224,32 @@ class Session(lt.session):
|
|||
with open(os.path.join(data_dir, "session"), 'wb') as f:
|
||||
f.write(lt.bencode(state))
|
||||
|
||||
log.debug("Session state saved.")
|
||||
self.log.debug("Session state saved.")
|
||||
|
||||
def load_torrents(self):
|
||||
torrents_path = os.path.join(data_dir, "torrents")
|
||||
if not os.path.exists(torrents_path):
|
||||
log.debug("No list of torrents found.")
|
||||
self.log.debug("No list of torrents found.")
|
||||
return
|
||||
|
||||
try:
|
||||
pkl_file = open(torrents_path, 'rb')
|
||||
except IOError:
|
||||
log.warning("Could not open the list of torrents.")
|
||||
self.log.warning("Could not open the list of torrents.")
|
||||
else:
|
||||
try:
|
||||
paths = cPickle.load(pkl_file)
|
||||
except EOFError:
|
||||
log.exception("Opening the list of torrents failed.")
|
||||
self.log.exception("Opening the list of torrents failed.")
|
||||
else:
|
||||
log.debug("List of torrents opened, restoring torrents.")
|
||||
self.log.debug("List of torrents opened, restoring torrents.")
|
||||
for k, v in paths.iteritems():
|
||||
try:
|
||||
self.add_torrent(v)
|
||||
except:
|
||||
log.exception("Restoring torrent {0} failed".format(v))
|
||||
self.log.exception(
|
||||
"Restoring torrent {0} failed".format(v)
|
||||
)
|
||||
finally:
|
||||
pkl_file.close()
|
||||
|
||||
|
@ -321,17 +257,20 @@ class Session(lt.session):
|
|||
with open(os.path.join(data_dir, "torrents"), 'wb') as f:
|
||||
cPickle.dump(self.torrents, f)
|
||||
|
||||
log.debug("List of torrents saved.")
|
||||
self.log.debug("List of torrents saved.")
|
||||
|
||||
# Save fast resume data
|
||||
for h in self.handles.itervalues():
|
||||
if not h.is_valid() or not h.has_metadata():
|
||||
continue
|
||||
data = lt.bencode(h.write_resume_data())
|
||||
with open(os.path.join(data_dir, '{}.fastresume'.format(h.info_hash())), 'wb') as f:
|
||||
with open(os.path.join(
|
||||
data_dir, '{}.fastresume'.format(h.info_hash())
|
||||
), 'wb'
|
||||
) as f:
|
||||
f.write(data)
|
||||
|
||||
log.debug("Fast resume data saved.")
|
||||
self.log.debug("Fast resume data saved.")
|
||||
|
||||
def add_torrent(self, t_uri):
|
||||
if not t_uri:
|
||||
|
@ -342,37 +281,47 @@ class Session(lt.session):
|
|||
if not t_uri.startswith("magnet"):
|
||||
mimetype = mimetypes.guess_type(t_uri)[0]
|
||||
if not mimetype == "application/x-bittorrent":
|
||||
log.error("Invalid file")
|
||||
self.log.error("Invalid file")
|
||||
return
|
||||
|
||||
with open(t_uri, 'rb') as t:
|
||||
t_raw = lt.bdecode(t.read())
|
||||
|
||||
if self.conf.getboolean("Settings", "delete_original"):
|
||||
log.debug("Deleting original torrent file {}".format(t_uri))
|
||||
self.log.debug("Deleting original torrent file {}".format(t_uri))
|
||||
os.remove(t_uri)
|
||||
|
||||
info = lt.torrent_info(t_raw)
|
||||
rd = None
|
||||
try:
|
||||
with open(os.path.join(data_dir, "{}.fastresume".format(info.info_hash())), "rb") as f:
|
||||
with open(os.path.join(
|
||||
data_dir, "{}.fastresume".format(info.info_hash())
|
||||
), "rb"
|
||||
) as f:
|
||||
rd = lt.bdecode(f.read())
|
||||
except:
|
||||
try:
|
||||
with open(os.path.join(data_dir, "{}.fastresume".format(info.name())), "rb") as f:
|
||||
with open(os.path.join(
|
||||
data_dir, "{}.fastresume".format(info.name())
|
||||
), "rb"
|
||||
) as f:
|
||||
rd = lt.bdecode(f.read())
|
||||
except:
|
||||
log.debug("Invalid resume data")
|
||||
self.log.debug("Invalid resume data")
|
||||
|
||||
h = lt.session.add_torrent(self, info, storage_path, resume_data=rd)
|
||||
t_uri = self.parent.write_torrent(h)
|
||||
h = lt.session.add_torrent(
|
||||
self, info, storage_path, resume_data=rd)
|
||||
t_uri = self.write_torrent(h)
|
||||
else:
|
||||
t_uri = urllib.unquote(t_uri)
|
||||
t_uri = str(HTMLParser.HTMLParser().unescape(t_uri))
|
||||
h = lt.add_magnet_uri(self, t_uri, { "save_path": str(storage_path) })
|
||||
h = lt.add_magnet_uri(
|
||||
self, t_uri,
|
||||
{ "save_path": str(storage_path) }
|
||||
)
|
||||
|
||||
if not h.is_valid():
|
||||
log.error("Invalid torrent handle")
|
||||
self.log.error("Invalid torrent handle")
|
||||
return
|
||||
|
||||
ihash = str(h.info_hash())
|
||||
|
@ -384,37 +333,70 @@ class Session(lt.session):
|
|||
|
||||
def remove_torrent(self, h, with_data=False):
|
||||
ihash = str(h.info_hash())
|
||||
info = h.get_torrent_info()
|
||||
|
||||
fr_path1 = os.path.join(data_dir, "{}.fastresume".format(info.info_hash()))
|
||||
fr_path2 = os.path.join(data_dir, "{}.fastresume".format(info.name()))
|
||||
fr_path = os.path.join(
|
||||
data_dir, "{}.fastresume".format(ihash)
|
||||
)
|
||||
t_path = self.torrents[ihash]
|
||||
|
||||
del self.torrents[ihash]
|
||||
lt.session.remove_torrent(self, h, option=with_data)
|
||||
|
||||
for p in fr_path1, fr_path2:
|
||||
try:
|
||||
with open(p): pass
|
||||
except IOError:
|
||||
log.debug("Could not remove fast resume data.")
|
||||
else:
|
||||
os.remove(p)
|
||||
break
|
||||
try:
|
||||
with open(fr_path): pass
|
||||
except IOError:
|
||||
self.log.debug("Could not remove fast resume data.")
|
||||
else:
|
||||
os.remove(fr_path)
|
||||
|
||||
try:
|
||||
with open(t_path): pass
|
||||
except IOError:
|
||||
log.debug("Could not remove torrent file.")
|
||||
self.log.debug("Could not remove torrent file.")
|
||||
else:
|
||||
os.remove(t_path)
|
||||
|
||||
return ihash
|
||||
|
||||
class AlertManager(object):
|
||||
|
||||
log = logging.getLogger("epour.alert")
|
||||
update_interval = 0.2
|
||||
alerts = {}
|
||||
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
|
||||
self.timer = Timer(self.update_interval, self.update)
|
||||
|
||||
def callback_add(self, alert_type, cb, *args, **kwargs):
|
||||
if not self.alerts.has_key(alert_type):
|
||||
self.alerts[alert_type] = []
|
||||
self.alerts[alert_type].append((cb, args, kwargs))
|
||||
|
||||
def callback_del(self, alert_type, cb, *args, **kwargs):
|
||||
for i, a in enumerate(self.alerts):
|
||||
if a == (cb, args, kwargs):
|
||||
del(self.alerts[alert_type][i])
|
||||
|
||||
def update(self):
|
||||
while 1:
|
||||
a = self.session.pop_alert()
|
||||
if not a or not self.alerts.has_key(type(a)): break
|
||||
|
||||
for cb, args, kwargs in self.alerts[type(a)]:
|
||||
try:
|
||||
cb(a, *args, **kwargs)
|
||||
except:
|
||||
log.exception()
|
||||
|
||||
return True
|
||||
|
||||
class EpourDBus(dbus.service.Object):
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
dbus.service.Object.__init__(self, dbus.SessionBus(), "/net/launchpad/epour", "net.launchpad.epour")
|
||||
dbus.service.Object.__init__(self, dbus.SessionBus(),
|
||||
"/net/launchpad/epour", "net.launchpad.epour")
|
||||
|
||||
self.props = {
|
||||
}
|
||||
|
@ -422,12 +404,9 @@ class EpourDBus(dbus.service.Object):
|
|||
@dbus.service.method(dbus_interface='net.launchpad.epour',
|
||||
in_signature='s', out_signature='')
|
||||
def AddTorrent(self, f):
|
||||
log.info("Adding %s from dbus" % f)
|
||||
self.log.info("Adding %s from dbus" % f)
|
||||
self.parent.session.add_torrent(str(f))
|
||||
|
||||
if __name__ == "__main__":
|
||||
elm.init()
|
||||
epour = Epour(sys.argv[1:])
|
||||
elm.run()
|
||||
elm.shutdown()
|
||||
logging.shutdown()
|
||||
|
|
|
@ -22,14 +22,22 @@
|
|||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
import libtorrent as lt
|
||||
|
||||
try:
|
||||
from evas import EVAS_ASPECT_CONTROL_VERTICAL
|
||||
from elementary import Genlist, GenlistItemClass, StandardWindow, Icon, Box, \
|
||||
Label, Button, ELM_GENLIST_ITEM_FIELD_TEXT, ELM_GENLIST_ITEM_FIELD_CONTENT, \
|
||||
InnerWindow, Ctxpopup, Frame, Fileselector, Entry
|
||||
from ecore import Timer
|
||||
import elementary as elm
|
||||
from elementary import Genlist, GenlistItemClass, StandardWindow, Icon, \
|
||||
Box, Label, Button, ELM_GENLIST_ITEM_FIELD_TEXT, \
|
||||
ELM_GENLIST_ITEM_FIELD_CONTENT, InnerWindow, Ctxpopup, Frame, \
|
||||
Fileselector, Entry
|
||||
except ImportError:
|
||||
from efl.evas import EVAS_ASPECT_CONTROL_VERTICAL
|
||||
from efl.elementary.genlist import Genlist, GenlistItemClass, ELM_GENLIST_ITEM_FIELD_TEXT, ELM_GENLIST_ITEM_FIELD_CONTENT
|
||||
from efl.ecore import Timer
|
||||
from efl import elementary as elm
|
||||
from efl.elementary.genlist import Genlist, GenlistItemClass, \
|
||||
ELM_GENLIST_ITEM_FIELD_TEXT, ELM_GENLIST_ITEM_FIELD_CONTENT
|
||||
from efl.elementary.window import StandardWindow
|
||||
from efl.elementary.icon import Icon
|
||||
from efl.elementary.box import Box
|
||||
|
@ -47,75 +55,13 @@ from Notify import ConfirmExit, Error, Information
|
|||
|
||||
from intrepr import intrepr
|
||||
|
||||
class TorrentClass(GenlistItemClass):
|
||||
|
||||
state_str = ['Queued', 'Checking', 'Downloading metadata', \
|
||||
'Downloading', 'Finished', 'Seeding', 'Allocating', \
|
||||
'Checking resume data']
|
||||
|
||||
def text_get(self, obj, part, item_data):
|
||||
h = item_data
|
||||
name = h.get_torrent_info().name() if h.has_metadata() else "-"
|
||||
|
||||
if part == "elm.text":
|
||||
return '%s' % (
|
||||
name
|
||||
)
|
||||
elif part == "elm.text.sub":
|
||||
s = h.status()
|
||||
|
||||
return '{:.0%} complete, ETA: {} (Down: {}/s Up: {}/s Peers: {} Queue: {})'.format(
|
||||
s.progress,
|
||||
timedelta(seconds=self.get_eta(h)),
|
||||
intrepr(s.download_payload_rate, precision=0),
|
||||
intrepr(s.upload_payload_rate, precision=0),
|
||||
s.num_peers,
|
||||
h.queue_position(),
|
||||
)
|
||||
|
||||
def content_get(self, obj, part, item_data):
|
||||
if part == "elm.swallow.icon":
|
||||
h = item_data
|
||||
s = h.status()
|
||||
ic = Icon(obj)
|
||||
if h.is_paused():
|
||||
ic.standard = "player_pause"
|
||||
elif h.is_seed():
|
||||
ic.standard = "up"
|
||||
else:
|
||||
ic.standard = "down"
|
||||
ic.tooltip_text_set(self.state_str[s.state])
|
||||
ic.size_hint_aspect_set(EVAS_ASPECT_CONTROL_VERTICAL, 1, 1)
|
||||
return ic
|
||||
|
||||
def get_eta(self, h):
|
||||
s = h.status()
|
||||
if False: #self.is_finished 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.options["stop_ratio"]
|
||||
return ((s.all_time_download * stop_ratio) - s.all_time_upload) / s.upload_payload_rate
|
||||
|
||||
left = s.total_wanted - s.total_wanted_done
|
||||
|
||||
if left <= 0 or s.download_payload_rate == 0:
|
||||
return 0
|
||||
|
||||
try:
|
||||
eta = left / s.download_payload_rate
|
||||
except ZeroDivisionError:
|
||||
eta = 0
|
||||
|
||||
return eta
|
||||
|
||||
class MainInterface(object):
|
||||
def __init__(self, parent, session):
|
||||
self.parent = parent
|
||||
self.session = session
|
||||
|
||||
self.win = StandardWindow("epour", "Epour")
|
||||
self.win.callback_delete_request_add(self.quit)
|
||||
self.win.callback_delete_request_add(lambda x: elm.exit())
|
||||
|
||||
self.torrentitems = {}
|
||||
|
||||
|
@ -154,7 +100,8 @@ class MainInterface(object):
|
|||
addbtn.show()
|
||||
|
||||
togglebtn = self.togglebtn = Button(self.win)
|
||||
togglebtn.text_set("Resume all" if parent.session.is_paused() else "Pause all")
|
||||
togglebtn.text = "Resume all" if \
|
||||
parent.session.is_paused() else "Pause all"
|
||||
togglebtn.callback_clicked_add(self.toggle_paused_cb)
|
||||
btnbox.pack_end(togglebtn)
|
||||
togglebtn.show()
|
||||
|
@ -169,21 +116,39 @@ class MainInterface(object):
|
|||
btnbox.show()
|
||||
|
||||
self.win.resize(480, 480)
|
||||
|
||||
for a in lt.state_changed_alert, lt.torrent_paused_alert, lt.torrent_resumed_alert:
|
||||
session.alert_manager.callback_add(a, self.update_icon)
|
||||
|
||||
session.alert_manager.callback_add(lt.torrent_finished_alert, self.torrent_finished_cb)
|
||||
|
||||
def run(self):
|
||||
self.win.show()
|
||||
|
||||
def update_texts(self, session):
|
||||
self.timer = Timer(1.0, self.update)
|
||||
elm.run()
|
||||
self.quit()
|
||||
|
||||
def update(self):
|
||||
for v in self.torrentitems.itervalues():
|
||||
v.fields_update("elm.text", ELM_GENLIST_ITEM_FIELD_TEXT)
|
||||
v.fields_update("elm.text.sub", ELM_GENLIST_ITEM_FIELD_TEXT)
|
||||
s = session.status()
|
||||
s = self.session.status()
|
||||
self.status.text = "Down: {0}/s Up: {1}/s Peers: {2}".format(
|
||||
intrepr(s.payload_download_rate),
|
||||
intrepr(s.payload_upload_rate),
|
||||
s.num_peers
|
||||
)
|
||||
return True
|
||||
|
||||
def update_icon(self, ihash):
|
||||
self.torrentitems[ihash].fields_update("elm.swallow.icon", ELM_GENLIST_ITEM_FIELD_CONTENT)
|
||||
def update_icon(self, a):
|
||||
h = a.handle
|
||||
if not h.is_valid(): return
|
||||
ihash = str(h.info_hash())
|
||||
if not self.torrentitems.has_key(ihash): return
|
||||
self.torrentitems[ihash].fields_update(
|
||||
"elm.swallow.icon", ELM_GENLIST_ITEM_FIELD_CONTENT
|
||||
)
|
||||
|
||||
def select_torrent(self, btn):
|
||||
sel = Fileselector(self.win)
|
||||
|
@ -274,10 +239,14 @@ class MainInterface(object):
|
|||
self.resume_torrent_cb if h.is_paused() else self.pause_torrent_cb,
|
||||
h
|
||||
)
|
||||
cp.item_append("Remove torrent", None, self.remove_torrent_cb, item, h, False)
|
||||
cp.item_append("Remove torrent and files", None, self.remove_torrent_cb, item, h, True)
|
||||
cp.item_append("Force re-check", None, self.force_recheck, h)
|
||||
cp.item_append("Torrent preferences", None, self.torrent_preferences_cb, h)
|
||||
cp.item_append("Remove torrent", None,
|
||||
self.remove_torrent_cb, item, h, False)
|
||||
cp.item_append("Remove torrent and files", None,
|
||||
self.remove_torrent_cb, item, h, True)
|
||||
cp.item_append("Force re-check", None,
|
||||
self.force_recheck, h)
|
||||
cp.item_append("Torrent preferences", None,
|
||||
self.torrent_preferences_cb, h)
|
||||
cp.pos = self.win.evas.pointer_canvas_xy_get()
|
||||
cp.show()
|
||||
|
||||
|
@ -309,12 +278,82 @@ class MainInterface(object):
|
|||
def show_error(self, title, text):
|
||||
Error(self.win, title, text)
|
||||
|
||||
def show_information(self, text):
|
||||
Information(self.win, text)
|
||||
def torrent_finished_cb(self, h):
|
||||
msg = "Torrent {} has finished downloading.".format(
|
||||
cgi.escape(h.name())
|
||||
)
|
||||
self.log.info(msg)
|
||||
|
||||
Information(self.win, msg)
|
||||
|
||||
def quit(self, *args):
|
||||
if self.session.conf.getboolean("Settings", "confirmations"):
|
||||
ConfirmExit(self.win, self.parent.quit)
|
||||
ConfirmExit(self.win, self.shutdown())
|
||||
else:
|
||||
self.parent.quit()
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self):
|
||||
elm.shutdown()
|
||||
self.parent.quit()
|
||||
|
||||
class TorrentClass(GenlistItemClass):
|
||||
|
||||
state_str = ['Queued', 'Checking', 'Downloading metadata', \
|
||||
'Downloading', 'Finished', 'Seeding', 'Allocating', \
|
||||
'Checking resume data']
|
||||
|
||||
def text_get(self, obj, part, item_data):
|
||||
h = item_data
|
||||
name = h.get_torrent_info().name() if h.has_metadata() else "-"
|
||||
|
||||
if part == "elm.text":
|
||||
return '%s' % (
|
||||
name
|
||||
)
|
||||
elif part == "elm.text.sub":
|
||||
s = h.status()
|
||||
|
||||
return '{:.0%} complete, ETA: {} (Down: {}/s Up: {}/s Peers: {} Queue: {})'.format(
|
||||
s.progress,
|
||||
timedelta(seconds=self.get_eta(h)),
|
||||
intrepr(s.download_payload_rate, precision=0),
|
||||
intrepr(s.upload_payload_rate, precision=0),
|
||||
s.num_peers,
|
||||
h.queue_position(),
|
||||
)
|
||||
|
||||
def content_get(self, obj, part, item_data):
|
||||
if part == "elm.swallow.icon":
|
||||
h = item_data
|
||||
s = h.status()
|
||||
ic = Icon(obj)
|
||||
if h.is_paused():
|
||||
ic.standard = "player_pause"
|
||||
elif h.is_seed():
|
||||
ic.standard = "up"
|
||||
else:
|
||||
ic.standard = "down"
|
||||
ic.tooltip_text_set(self.state_str[s.state])
|
||||
ic.size_hint_aspect_set(EVAS_ASPECT_CONTROL_VERTICAL, 1, 1)
|
||||
return ic
|
||||
|
||||
def get_eta(self, h):
|
||||
s = h.status()
|
||||
if False: #self.is_finished 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.options["stop_ratio"]
|
||||
return ((s.all_time_download * stop_ratio) - s.all_time_upload) / s.upload_payload_rate
|
||||
|
||||
left = s.total_wanted - s.total_wanted_done
|
||||
|
||||
if left <= 0 or s.download_payload_rate == 0:
|
||||
return 0
|
||||
|
||||
try:
|
||||
eta = left / s.download_payload_rate
|
||||
except ZeroDivisionError:
|
||||
eta = 0
|
||||
|
||||
return eta
|
||||
|
|
|
@ -70,10 +70,18 @@ class Preferences(InnerWindow):
|
|||
lbl.text = "Click on a title to open/close configuration entry."
|
||||
|
||||
ports = ListenPorts(self, session)
|
||||
ul = LimitWidget(self, "Upload limit", session.upload_rate_limit, session.set_upload_rate_limit)
|
||||
dl = LimitWidget(self, "Download limit", session.download_rate_limit, session.set_download_rate_limit)
|
||||
lul = LimitWidget(self, "Upload limit for local connections", session.local_upload_rate_limit, session.set_local_upload_rate_limit)
|
||||
ldl = LimitWidget(self, "Download limit for local connections", session.local_download_rate_limit, session.set_local_download_rate_limit)
|
||||
ul = LimitWidget(self, "Upload limit",
|
||||
session.upload_rate_limit,
|
||||
session.set_upload_rate_limit)
|
||||
dl = LimitWidget(self, "Download limit",
|
||||
session.download_rate_limit,
|
||||
session.set_download_rate_limit)
|
||||
lul = LimitWidget(self, "Upload limit for local connections",
|
||||
session.local_upload_rate_limit,
|
||||
session.set_local_upload_rate_limit)
|
||||
ldl = LimitWidget(self, "Download limit for local connections",
|
||||
session.local_download_rate_limit,
|
||||
session.set_local_download_rate_limit)
|
||||
pf = ProxyConfig(self, parent.win)
|
||||
dlsel = DataStorageSelector(self, conf)
|
||||
|
||||
|
@ -88,13 +96,15 @@ class Preferences(InnerWindow):
|
|||
chk1.size_hint_align = 0.0, 0.0
|
||||
chk1.text = "Delete original .torrent file when added"
|
||||
chk1.state = conf.getboolean("Settings", "delete_original")
|
||||
chk1.callback_changed_add(lambda x: conf.set("Settings", "delete_original", str(bool(chk1.state))))
|
||||
chk1.callback_changed_add(lambda x: conf.set("Settings",
|
||||
"delete_original", str(bool(chk1.state))))
|
||||
|
||||
chk2 = Check(self)
|
||||
chk2.size_hint_align = 0.0, 0.0
|
||||
chk2.text = "Ask for confirmation on exit"
|
||||
chk2.state = conf.getboolean("Settings", "confirmations")
|
||||
chk2.callback_changed_add(lambda x: conf.set("Settings", "confirmations", str(bool(chk2.state))))
|
||||
chk2.callback_changed_add(lambda x: conf.set("Settings",
|
||||
"confirmations", str(bool(chk2.state))))
|
||||
|
||||
sep2 = Separator(self)
|
||||
sep2.horizontal = True
|
||||
|
@ -103,7 +113,8 @@ class Preferences(InnerWindow):
|
|||
xbtn.text_set("Close")
|
||||
xbtn.callback_clicked_add(lambda x: self.delete())
|
||||
|
||||
for w in lbl, ports, ul, dl, lul, ldl, pf, dlsel, ses_set, pad, sep1, chk1, chk2, sep2, xbtn:
|
||||
for w in lbl, ports, ul, dl, lul, ldl, pf, dlsel, ses_set, pad, \
|
||||
sep1, chk1, chk2, sep2, xbtn:
|
||||
w.show()
|
||||
mbox.pack_end(w)
|
||||
|
||||
|
@ -152,7 +163,8 @@ class DataStorageSelector(Frame):
|
|||
return
|
||||
|
||||
if not os.path.exists(self.dlsel.path):
|
||||
p = Notify.Error(self, "Invalid storage path", "You have selected an invalid data storage path for torrents.")
|
||||
p = Notify.Error(self, "Invalid storage path",
|
||||
"You have selected an invalid data storage path for torrents.")
|
||||
return
|
||||
|
||||
self.path_lbl.text = path
|
||||
|
@ -302,10 +314,14 @@ class ProxyConfig(Frame):
|
|||
b = Box(parent)
|
||||
|
||||
proxies = [
|
||||
["Proxy for torrent peer connections", session.peer_proxy, session.set_peer_proxy],
|
||||
["Proxy for torrent web seed connections", session.web_seed_proxy, session.set_web_seed_proxy],
|
||||
["Proxy for tracker connections", session.tracker_proxy, session.set_tracker_proxy],
|
||||
["Proxy for DHT connections", session.dht_proxy, session.set_dht_proxy],
|
||||
["Proxy for torrent peer connections",
|
||||
session.peer_proxy, session.set_peer_proxy],
|
||||
["Proxy for torrent web seed connections",
|
||||
session.web_seed_proxy, session.set_web_seed_proxy],
|
||||
["Proxy for tracker connections",
|
||||
session.tracker_proxy, session.set_tracker_proxy],
|
||||
["Proxy for DHT connections",
|
||||
session.dht_proxy, session.set_dht_proxy],
|
||||
]
|
||||
|
||||
for title, rfunc, wfunc in proxies:
|
||||
|
@ -423,8 +439,6 @@ class ProxyGroup(Frame):
|
|||
|
||||
# encryption: in policy, out policy, allowed enc level, prefer enc
|
||||
|
||||
# session.settings
|
||||
|
||||
class SessionSettings(Frame):
|
||||
def __init__(self, parent, session):
|
||||
Frame.__init__(self, parent)
|
||||
|
@ -480,7 +494,6 @@ class SessionSettings(Frame):
|
|||
f = Frame(parent)
|
||||
f.size_hint_align = -1.0, 0.0
|
||||
f.text = k
|
||||
#w.disabled = True
|
||||
w.show()
|
||||
widgets[k] = w
|
||||
f.content = w
|
||||
|
@ -521,4 +534,3 @@ class SessionSettings(Frame):
|
|||
|
||||
session.set_settings(s)
|
||||
Notify.Information(self.canvas, "Session settings saved.")
|
||||
#print(dir(lt.session_settings))
|
||||
|
|
|
@ -39,8 +39,8 @@ except ImportError:
|
|||
from efl.elementary.entry import Entry
|
||||
else:
|
||||
import ecore
|
||||
from elementary import Genlist, GenlistItemClass, InnerWindow, Button, Box, \
|
||||
Hoversel, Check, Label, ELM_WRAP_CHAR, ELM_WRAP_WORD, Entry
|
||||
from elementary import Genlist, GenlistItemClass, InnerWindow, Button, \
|
||||
Box, Hoversel, Check, Label, ELM_WRAP_CHAR, ELM_WRAP_WORD, Entry
|
||||
|
||||
from intrepr import intrepr
|
||||
|
||||
|
@ -87,14 +87,16 @@ class TorrentInfo(InnerWindow):
|
|||
if w:
|
||||
l = Label(self)
|
||||
l.ellipsis = True
|
||||
l.text = cgi.escape(w)
|
||||
l.text = cgi.escape(str(w))
|
||||
l.show()
|
||||
box.pack_end(l)
|
||||
|
||||
tpriv = Check(self)
|
||||
tpriv.size_hint_align = 0.0, 0.0
|
||||
tpriv.text = "Private"
|
||||
tpriv.tooltip_text_set("Whether this torrent is private.<br>i.e., it should not be distributed on the trackerless network<br>(the kademlia DHT).")
|
||||
tpriv.tooltip_text_set(
|
||||
"Whether this torrent is private.<br>i.e., it should not be\
|
||||
distributed on the trackerless network<br>(the kademlia DHT).")
|
||||
tpriv.disabled = True
|
||||
tpriv.state = i.priv()
|
||||
|
||||
|
@ -107,7 +109,10 @@ class TorrentInfo(InnerWindow):
|
|||
i = h.get_torrent_info()
|
||||
files = i.files()
|
||||
for n, file_entry in enumerate(files):
|
||||
filelist.item_append(FileSelectionClass(), (file_entry, progress[n], n, h), None, 0)
|
||||
filelist.item_append(
|
||||
FileSelectionClass(),
|
||||
(file_entry, progress[n], n, h),
|
||||
None, 0)
|
||||
|
||||
filelist.callback_activated_add(self.item_activated_cb)
|
||||
|
||||
|
@ -142,4 +147,4 @@ class TorrentInfo(InnerWindow):
|
|||
if sys.platform == 'linux2':
|
||||
ecore.Exe("xdg-open '{0}'".format(path))
|
||||
else:
|
||||
os.startfile(path)
|
||||
os.startfile(path)
|
||||
|
|
|
@ -1,2 +1,9 @@
|
|||
from Main import MainInterface
|
||||
from Preferences import Preferences
|
||||
|
||||
try:
|
||||
from efl import elementary
|
||||
except ImportError:
|
||||
import elementary
|
||||
|
||||
elementary.init()
|
||||
|
|
Loading…
Reference in New Issue