basic Multi EPG support

This commit is contained in:
DYefremov
2022-07-16 23:56:02 +03:00
parent 681b43b164
commit 2441d3726b
5 changed files with 185 additions and 82 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -75,7 +75,7 @@ Author: Dmitriy Yefremov
<property name="layout_style">expand</property>
<child>
<object class="GtkRadioButton" id="http_src_button">
<property name="label" translatable="yes">WebIf</property>
<property name="label" translatable="yes">Receiver</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>

View File

@@ -35,6 +35,8 @@ Author: Dmitriy Yefremov
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="epg_model">
<columns>
<!-- column-name service -->
<column type="gchararray"/>
<!-- column-name title -->
<column type="gchararray"/>
<!-- column-name time -->
@@ -74,6 +76,23 @@ Author: Dmitriy Yefremov
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkComboBoxText" id="src_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">EPG source</property>
<property name="active">0</property>
<property name="active_id">0</property>
<items>
<item id="0" translatable="yes">Receiver</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="epg_filter_button">
<property name="visible">True</property>
@@ -93,7 +112,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -116,7 +135,21 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
<child type="center">
<object class="GtkToggleButton" id="multi_epg_button">
<property name="label" translatable="yes">Multi EPG</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<signal name="toggled" handler="on_multi_epg_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
@@ -225,81 +258,113 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="epg_view_scrolled_window">
<object class="GtkStack" id="stack">
<property name="name">stack</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can_focus">False</property>
<child>
<object class="GtkTreeView" id="epg_view">
<object class="GtkScrolledWindow" id="epg_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">epg_sort_model</property>
<property name="rules_hint">True</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<property name="tooltip_column">3</property>
<signal name="button-press-event" handler="on_epg_press" swapped="no"/>
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="epg_selection">
<property name="mode">multiple</property>
</object>
</child>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeViewColumn" id="epg_title_column">
<property name="resizable">True</property>
<property name="fixed_width">170</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Title</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">0</property>
<child>
<object class="GtkCellRendererText" id="epg_title_renderer">
<property name="xpad">5</property>
<object class="GtkTreeView" id="epg_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">epg_sort_model</property>
<property name="rules_hint">True</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<property name="tooltip_column">3</property>
<signal name="button-press-event" handler="on_epg_press" swapped="no"/>
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="epg_selection">
<property name="mode">multiple</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_time_column">
<property name="resizable">True</property>
<property name="fixed_width">210</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Time</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererText" id="epg_time_renderer">
<property name="xpad">5</property>
<property name="xalign">0.49000000953674316</property>
<object class="GtkTreeViewColumn" id="epg_service_column">
<property name="visible">False</property>
<property name="resizable">True</property>
<property name="fixed_width">100</property>
<property name="min_width">40</property>
<property name="title" translatable="yes">Service</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">0</property>
<property name="visible" bind-source="multi_epg_button" bind-property="active"/>
<child>
<object class="GtkCellRendererText" id="epg_service_renderer">
<property name="xpad">5</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_desc_column">
<property name="fixed_width">100</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Description</property>
<property name="expand">True</property>
<property name="alignment">0.49000000953674316</property>
<child>
<object class="GtkCellRendererText" id="epg_desc_renderer">
<property name="ellipsize">end</property>
<object class="GtkTreeViewColumn" id="epg_title_column">
<property name="resizable">True</property>
<property name="fixed_width">170</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Title</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererText" id="epg_title_renderer">
<property name="xpad">5</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_time_column">
<property name="resizable">True</property>
<property name="fixed_width">210</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Time</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">2</property>
<child>
<object class="GtkCellRendererText" id="epg_time_renderer">
<property name="xpad">5</property>
<property name="xalign">0.49000000953674316</property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_desc_column">
<property name="fixed_width">100</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Description</property>
<property name="expand">True</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererText" id="epg_desc_renderer">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
<property name="name">epg</property>
<property name="title" translatable="yes">EPG</property>
</packing>
</child>
</object>
<packing>