mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-02-21 05:58:05 +01:00
basic Multi EPG support
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user