diff --git a/app/connections.py b/app/connections.py
index f54d06f0..4cfce30b 100644
--- a/app/connections.py
+++ b/app/connections.py
@@ -596,7 +596,8 @@ def telnet(host, port=23, user="", password="", timeout=5):
# ***************** HTTP API ******************* #
class HttpAPI:
- __MAX_WORKERS = 4
+ _MAX_WORKERS = 4
+ _TIMEOUT = 10
class Request(str, Enum):
ZAP = "zap?sRef="
@@ -623,6 +624,7 @@ class HttpAPI:
# EPG
EPG = "epgservice?sRef="
EPG_NOW = "epgnow?bRef="
+ EPG_MULTI = "epgmulti?bRef="
# Timer
TIMER = ""
TIMER_LIST = "timerlist"
@@ -667,6 +669,7 @@ class HttpAPI:
Request.VOL,
Request.EPG,
Request.EPG_NOW,
+ Request.EPG_MULTI,
Request.TIMER,
Request.RECORDINGS,
Request.N_ZAP}
@@ -678,7 +681,7 @@ class HttpAPI:
def __init__(self, settings):
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
- self._executor = PoolExecutor(max_workers=self.__MAX_WORKERS)
+ self._executor = PoolExecutor(max_workers=self._MAX_WORKERS)
self._settings = settings
self._shutdown = False
@@ -690,7 +693,7 @@ class HttpAPI:
self._s_type = SettingsType.ENIGMA_2
self.init()
- def send(self, req_type, ref, callback=print, ref_prefix=""):
+ def send(self, req_type, ref, callback=print, ref_prefix="", timeout=_TIMEOUT):
if self._shutdown:
return
@@ -710,7 +713,7 @@ class HttpAPI:
def done_callback(f):
callback(f.result())
- future = self._executor.submit(self.get_response, req_type, url, data, self._s_type)
+ future = self._executor.submit(self.get_response, req_type, url, data, self._s_type, timeout)
future.add_done_callback(done_callback)
@run_task
@@ -747,7 +750,7 @@ class HttpAPI:
self._executor.shutdown()
@staticmethod
- def get_response(req_type, url, data=None, s_type=SettingsType.ENIGMA_2, timeout=10):
+ def get_response(req_type, url, data=None, s_type=SettingsType.ENIGMA_2, timeout=_TIMEOUT):
try:
with urlopen(Request(url, data=data), timeout=timeout) as f:
if s_type is SettingsType.ENIGMA_2:
@@ -780,7 +783,7 @@ class HttpAPI:
elif req_type is HttpAPI.Request.PLAYER_LIST:
return [{el.tag: el.text for el in el.iter()} for el in
ETree.fromstring(f.read().decode("utf-8")).iter("e2file")]
- elif req_type is HttpAPI.Request.EPG or req_type is HttpAPI.Request.EPG_NOW:
+ elif req_type in (HttpAPI.Request.EPG, HttpAPI.Request.EPG_NOW, HttpAPI.Request.EPG_MULTI):
return {"event_list": [{el.tag: el.text for el in el.iter()} for el in
ETree.fromstring(f.read().decode("utf-8")).iter("e2event")]}
elif req_type is HttpAPI.Request.TIMER_LIST:
diff --git a/app/tools/epg.py b/app/tools/epg.py
index 97fe2d66..748db96c 100644
--- a/app/tools/epg.py
+++ b/app/tools/epg.py
@@ -54,8 +54,8 @@ except ModuleNotFoundError:
else:
DETECT_ENCODING = True
-EpgEvent = namedtuple("EpgEvent", ["title", "time", "desc", "event_data"])
-EpgEvent.__new__.__defaults__ = ("N/A", "N/A", "N/A", None) # For Python3 < 3.7
+EpgEvent = namedtuple("EpgEvent", ["service_name", "title", "time", "desc", "event_data"])
+EpgEvent.__new__.__defaults__ = ("N/A", "N/A", "N/A", "N/A", None) # For Python3 < 3.7
class Reader(metaclass=abc.ABCMeta):
@@ -305,7 +305,7 @@ class XmlTvReader(Reader):
start = datetime.fromtimestamp(ev.start) + offset
end_time = datetime.fromtimestamp(ev.duration) + offset
tm = f"{start.strftime('%H:%M')} - {end_time.strftime('%H:%M')}"
- events[srv.name] = EpgEvent(ev.title, tm, ev.desc, ev)
+ events[srv.name] = EpgEvent(srv.name, ev.title, tm, ev.desc, ev)
return events
diff --git a/app/ui/epg/epg.py b/app/ui/epg/epg.py
index cf64e3af..9b865174 100644
--- a/app/ui/epg/epg.py
+++ b/app/ui/epg/epg.py
@@ -47,7 +47,7 @@ from app.settings import SEP, EpgSource
from app.tools.epg import EPG, ChannelsParser, EpgEvent, XmlTvReader
from app.ui.dialogs import get_message, show_dialog, DialogType, get_builder
from app.ui.timers import TimerTool
-from ..main_helper import on_popup_menu, update_entry_data
+from ..main_helper import on_popup_menu, update_entry_data, scroll_to
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, IS_GNOME_SESSION, Page
@@ -185,14 +185,17 @@ class EpgTool(Gtk.Box):
def __init__(self, app, *args, **kwargs):
super().__init__(*args, **kwargs)
+ self._current_bq = None
self._app = app
self._app.connect("fav-changed", self.on_service_changed)
+ self._app.connect("bouquet-changed", self.on_bouquet_changed)
handlers = {"on_epg_press": self.on_epg_press,
"on_timer_add": self.on_timer_add,
"on_epg_filter_changed": self.on_epg_filter_changed,
"on_epg_filter_toggled": self.on_epg_filter_toggled,
- "on_view_query_tooltip": self.on_view_query_tooltip}
+ "on_view_query_tooltip": self.on_view_query_tooltip,
+ "on_multi_epg_toggled": self.on_multi_epg_toggled}
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}tab.glade", handlers)
@@ -201,9 +204,10 @@ class EpgTool(Gtk.Box):
self._filter_model = builder.get_object("epg_filter_model")
self._filter_model.set_visible_func(self.epg_filter_function)
self._filter_entry = builder.get_object("epg_filter_entry")
+ self._multi_epg_button = builder.get_object("multi_epg_button")
self.pack_start(builder.get_object("epg_frame"), True, True, 0)
# Custom sort function.
- self._view.get_model().set_sort_func(1, self.time_sort_func, 1)
+ self._view.get_model().set_sort_func(2, self.time_sort_func, 2)
self.show()
@@ -229,7 +233,7 @@ class EpgTool(Gtk.Box):
def add_timers_list(self, paths):
ref_str = "timeraddbyeventid?sRef={}&eventid={}&justplay=0"
- refs = [ref_str.format(ev.get("e2eventservicereference", ""), ev.get("e2eventid", "")) for ev in paths]
+ refs = [ref_str.format(quote(ev.get("e2eventservicereference", "")), ev.get("e2eventid", "")) for ev in paths]
gen = self.write_timers_list(refs)
GLib.idle_add(lambda: next(gen, False))
@@ -251,18 +255,26 @@ class EpgTool(Gtk.Box):
self.on_timer_add()
def on_service_changed(self, app, ref):
- self._app.wait_dialog.show()
- self._app.send_http_request(HttpAPI.Request.EPG, quote(ref), self.update_epg_data)
+ if app.page is Page.EPG:
+ if self._multi_epg_button.get_active():
+ ref += ":"
+ path = next((r.path for r in self._model if r[-1].get("e2eventservicereference", None) == ref), None)
+ scroll_to(path, self._view) if path else None
+ else:
+ self._app.wait_dialog.show()
+ self._app.send_http_request(HttpAPI.Request.EPG, quote(ref), self.update_epg_data)
@run_idle
def update_epg_data(self, epg):
self._model.clear()
- list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", []))))
+ list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", [])
+ if e.get("e2eventid", "").isdigit())))
self._app.wait_dialog.hide()
@staticmethod
def get_event(event, show_day=True):
t_str = f"{'%a, ' if show_day else ''}%x, %H:%M"
+ s_name = event.get("e2eventservicename", "")
title = event.get("e2eventtitle", "") or ""
desc = event.get("e2eventdescription", "") or ""
desc = desc.strip()
@@ -272,7 +284,7 @@ class EpgTool(Gtk.Box):
end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0")))
ev_time = f"{start_time.strftime(t_str)} - {end_time.strftime('%H:%M')}"
- return EpgEvent(title, ev_time, desc, event)
+ return EpgEvent(s_name, title, ev_time, desc, event)
def on_epg_filter_changed(self, entry):
self._filter_model.refilter()
@@ -283,12 +295,12 @@ class EpgTool(Gtk.Box):
def epg_filter_function(self, model, itr, data):
txt = self._filter_entry.get_text().upper()
- return next((s for s in model.get(itr, 0, 1, 2) if txt in s.upper()), False)
+ return next((s for s in model.get(itr, 0, 1, 2, 3) if txt in s.upper()), False)
def time_sort_func(self, model, iter1, iter2, column):
""" Custom sort function for time column. """
- event1 = model.get_value(iter1, 3)
- event2 = model.get_value(iter2, 3)
+ event1 = model.get_value(iter1, 4)
+ event2 = model.get_value(iter2, 4)
return int(event1.get("e2eventstart", "0")) - int(event2.get("e2eventstart", "0"))
@@ -308,6 +320,29 @@ class EpgTool(Gtk.Box):
return True
+ def on_multi_epg_toggled(self, button):
+ self._model.clear()
+ if button.get_active():
+ self.get_multi_epg()
+
+ def on_bouquet_changed(self, app, bq):
+ self._current_bq = bq
+ if app.page is Page.EPG and self._multi_epg_button.get_active():
+ self.get_multi_epg()
+
+ def get_multi_epg(self):
+ if not self._current_bq:
+ return
+
+ self._app.wait_dialog.show()
+ bq = self._app.current_bouquet_files.get(self._current_bq, None)
+ api = self._app.http_api
+
+ if bq and api:
+ tm = datetime.now().timestamp()
+ req = quote(f'FROM BOUQUET "userbouquet.{bq}.{self._current_bq.split(":")[-1]}"&time={tm}')
+ api.send(HttpAPI.Request.EPG_MULTI, f'1:7:1:0:0:0:0:0:0:0:{req}', self.update_epg_data, timeout=15)
+
class EpgDialog:
diff --git a/app/ui/epg/settings.glade b/app/ui/epg/settings.glade
index 7e13bba1..90d1fefe 100644
--- a/app/ui/epg/settings.glade
+++ b/app/ui/epg/settings.glade
@@ -75,7 +75,7 @@ Author: Dmitriy Yefremov
expand
-
+
+ stack
True
- True
- in
+ False
-
+
True
True
- epg_sort_model
- True
- True
- both
- 3
-
-
-
-
- multiple
-
-
+ in
-
- True
- 170
- 50
- Title
- 0.49000000953674316
- 0
-
-
- 5
+
+ True
+ True
+ epg_sort_model
+ True
+ True
+ both
+ 3
+
+
+
+
+ multiple
-
- 0
-
-
-
-
-
- True
- 210
- 50
- Time
- 0.49000000953674316
- 1
-
- 5
- 0.49000000953674316
+
+ False
+ True
+ 100
+ 40
+ Service
+ 0.49000000953674316
+ 0
+
+
+
+ 5
+
+
+ 0
+
+
-
- 1
-
-
-
-
-
- 100
- 50
- Description
- True
- 0.49000000953674316
-
- end
+
+ True
+ 170
+ 50
+ Title
+ 0.49000000953674316
+ 1
+
+
+ 5
+
+
+ 1
+
+
+
+
+
+
+ True
+ 210
+ 50
+ Time
+ 0.49000000953674316
+ 2
+
+
+ 5
+ 0.49000000953674316
+
+
+ 2
+
+
+
+
+
+
+ 100
+ 50
+ Description
+ True
+ 0.49000000953674316
+ 3
+
+
+ end
+
+
+ 3
+
+
-
- 2
-
+
+ epg
+ EPG
+