2021-08-30 15:04:15 +03:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
#
|
|
|
|
|
|
# The MIT License (MIT)
|
|
|
|
|
|
#
|
2023-12-31 10:50:05 +03:00
|
|
|
|
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
2021-08-30 15:04:15 +03:00
|
|
|
|
#
|
|
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
|
# in the Software without restriction, including without limitation the rights
|
|
|
|
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
|
# furnished to do so, subject to the following conditions:
|
|
|
|
|
|
#
|
|
|
|
|
|
# The above copyright notice and this permission notice shall be included in
|
|
|
|
|
|
# all copies or substantial portions of the Software.
|
|
|
|
|
|
#
|
|
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
|
# THE SOFTWARE.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Author: Dmitriy Yefremov
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-05-22 23:55:13 +03:00
|
|
|
|
""" Module for working with EPG. """
|
2023-12-21 01:01:08 +03:00
|
|
|
|
import abc
|
2019-04-27 19:05:37 +03:00
|
|
|
|
import gzip
|
2019-05-04 23:54:58 +03:00
|
|
|
|
import locale
|
2019-04-27 19:05:37 +03:00
|
|
|
|
import os
|
2019-05-04 23:54:58 +03:00
|
|
|
|
import re
|
2019-04-27 19:05:37 +03:00
|
|
|
|
import shutil
|
|
|
|
|
|
import urllib.request
|
2022-05-22 23:55:13 +03:00
|
|
|
|
from datetime import datetime
|
2019-04-24 20:27:47 +03:00
|
|
|
|
from enum import Enum
|
2023-12-29 14:09:13 +03:00
|
|
|
|
from itertools import chain
|
2019-06-08 15:45:41 +03:00
|
|
|
|
from urllib.error import HTTPError, URLError
|
2022-05-22 23:55:13 +03:00
|
|
|
|
from urllib.parse import quote
|
2019-04-24 20:27:47 +03:00
|
|
|
|
|
2019-06-03 15:47:04 +03:00
|
|
|
|
from gi.repository import GLib
|
2021-04-28 14:12:59 +03:00
|
|
|
|
|
2022-01-24 19:17:11 +03:00
|
|
|
|
from app.commons import run_idle, run_task, run_with_delay
|
2022-05-22 23:55:13 +03:00
|
|
|
|
from app.connections import download_data, DownloadType, HttpAPI
|
2019-04-22 20:25:19 +03:00
|
|
|
|
from app.eparser.ecommons import BouquetService, BqServiceType
|
2023-02-18 11:30:06 +03:00
|
|
|
|
from app.settings import SEP, EpgSource, IS_WIN
|
2022-06-14 20:14:08 +03:00
|
|
|
|
from app.tools.epg import EPG, ChannelsParser, EpgEvent, XmlTvReader
|
2024-01-06 00:12:48 +03:00
|
|
|
|
from app.ui.dialogs import translate, show_dialog, DialogType, get_builder, get_chooser_dialog
|
2022-08-12 09:14:13 +03:00
|
|
|
|
from app.ui.tasks import BGTaskWidget
|
2022-05-22 23:55:13 +03:00
|
|
|
|
from app.ui.timers import TimerTool
|
2023-01-21 15:06:27 +03:00
|
|
|
|
from ..main_helper import on_popup_menu, update_entry_data, scroll_to, update_toggle_model, update_filter_sat_positions
|
2023-02-18 11:30:06 +03:00
|
|
|
|
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, Page, HeaderBar
|
2022-06-06 20:33:37 +03:00
|
|
|
|
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2019-04-24 20:27:47 +03:00
|
|
|
|
class RefsSource(Enum):
|
2019-04-27 19:05:37 +03:00
|
|
|
|
SERVICES = 0
|
2019-04-24 20:27:47 +03:00
|
|
|
|
XML = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-12-21 01:01:08 +03:00
|
|
|
|
class EpgCache(abc.ABC):
|
2022-06-06 20:33:37 +03:00
|
|
|
|
def __init__(self, app):
|
|
|
|
|
|
super().__init__()
|
2023-12-21 01:01:08 +03:00
|
|
|
|
self.events = {}
|
|
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
self._reader = None
|
2022-08-12 09:14:13 +03:00
|
|
|
|
self._canceled = False
|
2023-12-28 23:05:01 +03:00
|
|
|
|
self._is_run = False
|
2023-12-15 18:07:02 +03:00
|
|
|
|
self._current_bq = app.current_bouquet
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._page = Page.SERVICES
|
2022-08-12 09:14:13 +03:00
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
self._settings = app.app_settings
|
2023-12-21 01:01:08 +03:00
|
|
|
|
self._src = EpgSource.XML
|
2023-12-31 10:50:05 +03:00
|
|
|
|
self._xml_src = None
|
2023-12-21 01:01:08 +03:00
|
|
|
|
|
2022-06-06 20:33:37 +03:00
|
|
|
|
self._app = app
|
|
|
|
|
|
self._app.connect("bouquet-changed", self.on_bouquet_changed)
|
|
|
|
|
|
self._app.connect("profile-changed", self.on_profile_changed)
|
2023-12-28 23:05:01 +03:00
|
|
|
|
self._app.connect("epg-settings-changed", self.on_settings_changed)
|
2022-08-12 09:14:13 +03:00
|
|
|
|
self._app.connect("task-canceled", self.on_xml_load_cancel)
|
2022-06-06 20:33:37 +03:00
|
|
|
|
|
2023-12-21 01:01:08 +03:00
|
|
|
|
def on_bouquet_changed(self, app, bq):
|
|
|
|
|
|
self._current_bq = bq
|
|
|
|
|
|
|
|
|
|
|
|
def on_profile_changed(self, app, p):
|
2023-12-31 10:50:05 +03:00
|
|
|
|
self._xml_src = self._settings.epg_xml_source
|
2023-12-28 23:05:01 +03:00
|
|
|
|
self.reset()
|
|
|
|
|
|
|
2024-01-05 15:03:36 +03:00
|
|
|
|
def on_settings_changed(self, app, page):
|
|
|
|
|
|
if page is self._page:
|
|
|
|
|
|
self.reset()
|
2023-12-21 01:01:08 +03:00
|
|
|
|
|
|
|
|
|
|
def on_xml_load_cancel(self, app, widget):
|
|
|
|
|
|
self._canceled = True
|
|
|
|
|
|
|
2023-12-28 23:05:01 +03:00
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def reset(self) -> None: pass
|
|
|
|
|
|
|
2023-12-21 01:01:08 +03:00
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def update_epg_data(self) -> bool: pass
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def get_current_event(self, service_name) -> EpgEvent: pass
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def get_current_events(self, service_name) -> list[EpgEvent]: pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FavEpgCache(EpgCache):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, app):
|
|
|
|
|
|
super().__init__(app)
|
|
|
|
|
|
self._src = self._settings.epg_source
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._xml_src = self._settings.epg_xml_source
|
2023-12-28 23:05:01 +03:00
|
|
|
|
GLib.timeout_add_seconds(self._settings.epg_update_interval, self.init)
|
2023-12-21 01:01:08 +03:00
|
|
|
|
|
2022-06-06 20:33:37 +03:00
|
|
|
|
def init(self):
|
2023-12-28 23:05:01 +03:00
|
|
|
|
self._is_run = True
|
2022-06-14 20:14:08 +03:00
|
|
|
|
if self._src is EpgSource.XML:
|
|
|
|
|
|
url = self._settings.epg_xml_source
|
|
|
|
|
|
gz_file = f"{self._settings.profile_data_path}epg{os.sep}epg.gz"
|
|
|
|
|
|
self._reader = XmlTvReader(gz_file, url)
|
|
|
|
|
|
|
2023-12-31 10:50:05 +03:00
|
|
|
|
if url != self._xml_src:
|
|
|
|
|
|
self._xml_src = url
|
|
|
|
|
|
if os.path.isfile(gz_file):
|
|
|
|
|
|
os.remove(gz_file)
|
|
|
|
|
|
|
2023-01-30 14:41:16 +03:00
|
|
|
|
@run_with_delay(2)
|
2022-06-17 17:53:39 +03:00
|
|
|
|
def process_data():
|
2022-08-16 11:36:57 +03:00
|
|
|
|
t = BGTaskWidget(self._app, "Processing XMLTV data...", self._reader.parse, )
|
|
|
|
|
|
self._app.emit("add-background-task", t)
|
2022-06-17 17:53:39 +03:00
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
if os.path.isfile(gz_file):
|
|
|
|
|
|
# Difference calculation between the current time and file modification.
|
|
|
|
|
|
dif = datetime.now() - datetime.fromtimestamp(os.path.getmtime(gz_file))
|
|
|
|
|
|
# We will update daily. -> Temporarily!!!
|
2022-08-12 09:14:13 +03:00
|
|
|
|
if dif.days > 0 and not self._canceled:
|
2022-08-16 11:36:57 +03:00
|
|
|
|
task = BGTaskWidget(self._app, "Downloading EPG...", self._reader.download, process_data, )
|
2022-08-12 09:14:13 +03:00
|
|
|
|
self._app.emit("add-background-task", task)
|
|
|
|
|
|
else:
|
|
|
|
|
|
process_data()
|
2022-06-14 20:14:08 +03:00
|
|
|
|
else:
|
2022-08-12 09:14:13 +03:00
|
|
|
|
if not self._canceled:
|
|
|
|
|
|
task = BGTaskWidget(self._app, "Downloading EPG...", self._reader.download, process_data, )
|
|
|
|
|
|
self._app.emit("add-background-task", task)
|
2022-06-14 20:14:08 +03:00
|
|
|
|
elif self._src is EpgSource.DAT:
|
|
|
|
|
|
self._reader = EPG.DatReader(f"{self._settings.profile_data_path}epg{os.sep}epg.dat")
|
|
|
|
|
|
self._reader.download()
|
|
|
|
|
|
|
|
|
|
|
|
GLib.timeout_add_seconds(self._settings.epg_update_interval, self.update_epg_data, priority=GLib.PRIORITY_LOW)
|
2022-06-06 20:33:37 +03:00
|
|
|
|
|
2023-12-28 23:05:01 +03:00
|
|
|
|
def reset(self) -> None:
|
2023-12-31 10:50:05 +03:00
|
|
|
|
self.events.clear()
|
2023-12-29 14:09:13 +03:00
|
|
|
|
if self._is_run:
|
|
|
|
|
|
self._is_run = False
|
|
|
|
|
|
GLib.timeout_add_seconds(self._settings.epg_update_interval, self.init)
|
2023-12-28 23:05:01 +03:00
|
|
|
|
|
2022-06-06 20:33:37 +03:00
|
|
|
|
def update_epg_data(self):
|
2022-06-14 20:14:08 +03:00
|
|
|
|
if self._src is EpgSource.HTTP:
|
2024-01-11 17:31:40 +03:00
|
|
|
|
api, bq = self._app.http_api, self._app.current_bouquet_files.get(self._current_bq, None)
|
2022-06-14 20:14:08 +03:00
|
|
|
|
if bq and api:
|
2024-01-11 17:31:40 +03:00
|
|
|
|
req = quote(f'FROM BOUQUET "{bq}"')
|
2022-06-14 20:14:08 +03:00
|
|
|
|
api.send(HttpAPI.Request.EPG_NOW, f'1:7:1:0:0:0:0:0:0:0:{req}', self.update_http_data)
|
|
|
|
|
|
elif self._src is EpgSource.XML:
|
|
|
|
|
|
self.update_xml_data()
|
2022-06-06 20:33:37 +03:00
|
|
|
|
|
2023-12-28 23:05:01 +03:00
|
|
|
|
return self._app.display_epg and self._is_run
|
2022-06-06 20:33:37 +03:00
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
def update_http_data(self, epg):
|
2022-06-06 20:33:37 +03:00
|
|
|
|
for e in (EpgTool.get_event(e, False) for e in epg.get("event_list", []) if e.get("e2eventid", "").isdigit()):
|
2023-12-21 01:01:08 +03:00
|
|
|
|
self.events[e.event_data.get("e2eventservicename", "")] = e
|
2022-06-06 20:33:37 +03:00
|
|
|
|
|
2022-08-16 11:36:57 +03:00
|
|
|
|
@run_task
|
2022-06-14 20:14:08 +03:00
|
|
|
|
def update_xml_data(self):
|
|
|
|
|
|
services = self._app.current_services
|
|
|
|
|
|
names = {services[s].service for s in self._app.current_bouquets.get(self._current_bq, [])}
|
2023-12-21 01:01:08 +03:00
|
|
|
|
for name, events in self._reader.get_current_events(names).items():
|
2023-12-29 14:09:13 +03:00
|
|
|
|
ev = min(events, key=lambda x: x.start, default=None)
|
2023-12-21 01:01:08 +03:00
|
|
|
|
if ev:
|
|
|
|
|
|
self.events[name] = ev
|
2022-06-14 20:14:08 +03:00
|
|
|
|
|
2022-06-06 20:33:37 +03:00
|
|
|
|
def get_current_event(self, service_name):
|
2023-12-21 01:01:08 +03:00
|
|
|
|
return self.events.get(service_name, EpgEvent())
|
|
|
|
|
|
|
|
|
|
|
|
def get_current_events(self, service_name):
|
|
|
|
|
|
return [EpgEvent()]
|
2022-06-06 20:33:37 +03:00
|
|
|
|
|
|
|
|
|
|
|
2023-12-29 14:09:13 +03:00
|
|
|
|
class TabEpgCache(EpgCache):
|
|
|
|
|
|
|
2024-01-06 00:12:48 +03:00
|
|
|
|
def __init__(self, app, path, url=None):
|
2023-12-29 14:09:13 +03:00
|
|
|
|
super().__init__(app)
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._page = Page.EPG
|
2024-01-06 00:12:48 +03:00
|
|
|
|
self._path = path
|
|
|
|
|
|
self._xml_src = url
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._task = None
|
2023-12-29 14:09:13 +03:00
|
|
|
|
self.init()
|
|
|
|
|
|
|
2024-01-06 00:12:48 +03:00
|
|
|
|
def on_bouquet_changed(self, app, bq):
|
|
|
|
|
|
self._current_bq = bq
|
|
|
|
|
|
self.update_epg_data()
|
|
|
|
|
|
|
2023-12-29 14:09:13 +03:00
|
|
|
|
def init(self):
|
|
|
|
|
|
self._is_run = True
|
2024-01-06 00:12:48 +03:00
|
|
|
|
self._reader = XmlTvReader(self._path, url=self._xml_src)
|
2024-01-11 14:16:38 +03:00
|
|
|
|
if self._canceled:
|
|
|
|
|
|
return
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
2024-01-06 00:12:48 +03:00
|
|
|
|
if os.path.isfile(self._path):
|
2024-01-11 14:16:38 +03:00
|
|
|
|
if self._xml_src:
|
|
|
|
|
|
# Difference calculation between the current time and file modification.
|
|
|
|
|
|
dif = datetime.now() - datetime.fromtimestamp(os.path.getmtime(self._path))
|
|
|
|
|
|
# We will update daily. -> Temporarily!!!
|
|
|
|
|
|
if dif.days > 0:
|
|
|
|
|
|
self._task = BGTaskWidget(self._app, "Downloading EPG...", self._reader.download,
|
|
|
|
|
|
self.process_data, )
|
|
|
|
|
|
self._app.emit("add-background-task", self._task)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.process_data()
|
2023-12-29 14:09:13 +03:00
|
|
|
|
else:
|
2024-01-11 14:16:38 +03:00
|
|
|
|
self._task = BGTaskWidget(self._app, "", self.process_data, )
|
2023-12-29 14:09:13 +03:00
|
|
|
|
else:
|
2024-01-11 14:16:38 +03:00
|
|
|
|
self._task = BGTaskWidget(self._app, "Downloading EPG...", self._reader.download, self.process_data, )
|
|
|
|
|
|
self._app.emit("add-background-task", self._task)
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
|
|
|
|
|
def process_data(self):
|
2024-01-05 15:03:36 +03:00
|
|
|
|
GLib.idle_add(self._app.wait_dialog.show)
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
|
|
|
|
|
def process():
|
|
|
|
|
|
self._reader.parse()
|
|
|
|
|
|
self.update_epg_data()
|
|
|
|
|
|
self._app.wait_dialog.hide()
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._task = None
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._task = BGTaskWidget(self._app, "Processing XMLTV data...", process, )
|
|
|
|
|
|
self._app.emit("add-background-task", self._task)
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._is_run = False
|
|
|
|
|
|
if self._task:
|
|
|
|
|
|
self._task.cancel()
|
|
|
|
|
|
|
|
|
|
|
|
self.init()
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
|
|
|
|
|
def update_epg_data(self) -> bool:
|
|
|
|
|
|
services = self._app.current_services
|
2024-01-06 00:12:48 +03:00
|
|
|
|
names = {services[s].service for s in chain.from_iterable(self._app.current_bouquets.values())}
|
2023-12-29 14:09:13 +03:00
|
|
|
|
for name, events in self._reader.get_current_events(names).items():
|
|
|
|
|
|
self.events[name] = events
|
|
|
|
|
|
|
|
|
|
|
|
return self._is_run
|
|
|
|
|
|
|
|
|
|
|
|
def get_current_event(self, service_name) -> EpgEvent:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def get_current_events(self, service_name) -> list[EpgEvent]:
|
|
|
|
|
|
return self.events.get(service_name, [])
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
class EpgSettingsPopover(Gtk.Popover):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, app, **kwarg):
|
|
|
|
|
|
super().__init__(**kwarg)
|
|
|
|
|
|
self._app = app
|
|
|
|
|
|
self._app.connect("profile-changed", self.on_profile_changed)
|
|
|
|
|
|
|
2023-12-23 19:28:45 +03:00
|
|
|
|
handlers = {"on_add_url": self.on_ad_url,
|
|
|
|
|
|
"on_remove_url": self.on_remove_url,
|
|
|
|
|
|
"on_apply_url": self.on_apply_url,
|
|
|
|
|
|
"on_url_entry_focus_out": self.on_url_entry_focus_out,
|
|
|
|
|
|
"on_apply": self.on_apply,
|
2023-12-28 23:05:01 +03:00
|
|
|
|
"on_close": self.on_close}
|
2023-12-23 19:28:45 +03:00
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}settings.glade", handlers)
|
|
|
|
|
|
self.add(builder.get_object("main_box"))
|
|
|
|
|
|
|
2023-12-29 15:16:58 +03:00
|
|
|
|
self._src_selection_box = builder.get_object("source_selection_box")
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._xml_source_box = builder.get_object("xml_source_box")
|
|
|
|
|
|
self._interval_box = builder.get_object("interval_box")
|
2022-06-14 20:14:08 +03:00
|
|
|
|
self._http_src_button = builder.get_object("http_src_button")
|
|
|
|
|
|
self._xml_src_button = builder.get_object("xml_src_button")
|
|
|
|
|
|
self._dat_src_button = builder.get_object("dat_src_button")
|
|
|
|
|
|
self._interval_button = builder.get_object("interval_button")
|
2023-12-23 19:28:45 +03:00
|
|
|
|
self._download_interval_button = builder.get_object("download_interval_button")
|
|
|
|
|
|
self._url_combo_box = builder.get_object("url_combo_box")
|
2022-06-14 20:14:08 +03:00
|
|
|
|
self._url_entry = builder.get_object("url_entry")
|
|
|
|
|
|
self._dat_path_box = builder.get_object("dat_path_box")
|
2023-12-23 19:28:45 +03:00
|
|
|
|
self._remove_url_button = builder.get_object("remove_url_button")
|
2022-06-14 20:14:08 +03:00
|
|
|
|
|
|
|
|
|
|
self.init()
|
|
|
|
|
|
|
|
|
|
|
|
def init(self):
|
|
|
|
|
|
settings = self._app.app_settings
|
|
|
|
|
|
src = settings.epg_source
|
|
|
|
|
|
if src is EpgSource.HTTP:
|
|
|
|
|
|
self._http_src_button.set_active(True)
|
|
|
|
|
|
elif src is EpgSource.XML:
|
|
|
|
|
|
self._xml_src_button.set_active(True)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._dat_src_button.set_active(True)
|
|
|
|
|
|
|
|
|
|
|
|
self._interval_button.set_value(settings.epg_update_interval)
|
|
|
|
|
|
self._dat_path_box.set_active_id(settings.epg_dat_path)
|
2023-12-28 23:05:01 +03:00
|
|
|
|
self._url_combo_box.get_model().clear()
|
2023-12-23 19:28:45 +03:00
|
|
|
|
[self._url_combo_box.append(i, i) for i in settings.epg_xml_sources if i]
|
|
|
|
|
|
self._url_combo_box.set_active_id(settings.epg_xml_source)
|
|
|
|
|
|
|
|
|
|
|
|
def on_ad_url(self, button):
|
|
|
|
|
|
self._url_entry.set_can_focus(True)
|
|
|
|
|
|
self._url_entry.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
def on_remove_url(self, button):
|
|
|
|
|
|
self._url_combo_box.remove(self._url_combo_box.get_active())
|
|
|
|
|
|
self._url_combo_box.set_active(0)
|
|
|
|
|
|
self._remove_url_button.set_sensitive(len(self._url_combo_box.get_model()) > 1)
|
|
|
|
|
|
|
|
|
|
|
|
def on_apply_url(self, button):
|
|
|
|
|
|
url = self._url_entry.get_text()
|
|
|
|
|
|
ids = {r[0] for r in self._url_combo_box.get_model()}
|
|
|
|
|
|
if url in ids:
|
|
|
|
|
|
self._app.show_error_message("This URL already exists!")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
self._url_combo_box.append(url, url)
|
|
|
|
|
|
self._url_combo_box.set_active_id(url)
|
|
|
|
|
|
self._download_interval_button.grab_focus()
|
|
|
|
|
|
self._remove_url_button.set_sensitive(len(ids))
|
|
|
|
|
|
|
|
|
|
|
|
def on_url_entry_focus_out(self, entry, event):
|
|
|
|
|
|
entry.set_can_focus(False)
|
|
|
|
|
|
active = self._url_combo_box.get_active_id()
|
|
|
|
|
|
txt = entry.get_text()
|
|
|
|
|
|
if active != txt:
|
|
|
|
|
|
entry.set_text(active or "")
|
2022-06-14 20:14:08 +03:00
|
|
|
|
|
2022-06-16 23:30:28 +03:00
|
|
|
|
def on_apply(self, button):
|
2022-06-14 20:14:08 +03:00
|
|
|
|
settings = self._app.app_settings
|
|
|
|
|
|
if self._http_src_button.get_active():
|
2023-12-28 23:05:01 +03:00
|
|
|
|
src = EpgSource.HTTP
|
2022-06-14 20:14:08 +03:00
|
|
|
|
elif self._xml_src_button.get_active():
|
2023-12-28 23:05:01 +03:00
|
|
|
|
src = EpgSource.XML
|
2022-06-14 20:14:08 +03:00
|
|
|
|
else:
|
2023-12-28 23:05:01 +03:00
|
|
|
|
src = EpgSource.DAT
|
|
|
|
|
|
|
|
|
|
|
|
xml_src = self._url_combo_box.get_active_id()
|
|
|
|
|
|
update_interval = self._interval_button.get_value()
|
|
|
|
|
|
dat_path = self._dat_path_box.get_active_id()
|
|
|
|
|
|
|
|
|
|
|
|
if any((src != settings.epg_source,
|
|
|
|
|
|
xml_src != settings.epg_xml_source,
|
|
|
|
|
|
update_interval != settings.epg_update_interval,
|
|
|
|
|
|
dat_path != settings.epg_dat_path)):
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._app.emit("epg-settings-changed", Page.SERVICES)
|
2022-06-14 20:14:08 +03:00
|
|
|
|
|
2023-12-28 23:05:01 +03:00
|
|
|
|
settings.epg_update_interval = update_interval
|
|
|
|
|
|
settings.epg_source = src
|
|
|
|
|
|
settings.epg_xml_source = xml_src
|
2023-12-23 19:28:45 +03:00
|
|
|
|
settings.epg_xml_sources = [r[0] for r in self._url_combo_box.get_model()]
|
2023-12-28 23:05:01 +03:00
|
|
|
|
settings.epg_dat_path = dat_path
|
2022-06-14 20:14:08 +03:00
|
|
|
|
self.popdown()
|
|
|
|
|
|
|
2023-12-28 23:05:01 +03:00
|
|
|
|
def on_close(self, button):
|
|
|
|
|
|
self.init()
|
|
|
|
|
|
self.popdown()
|
2022-06-16 23:30:28 +03:00
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
def on_profile_changed(self, app, p):
|
|
|
|
|
|
self.init()
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-12-29 15:16:58 +03:00
|
|
|
|
class TabEpgSettingsPopover(EpgSettingsPopover):
|
|
|
|
|
|
|
|
|
|
|
|
def init(self):
|
|
|
|
|
|
self._xml_src_button.set_active(True)
|
|
|
|
|
|
self._http_src_button.set_visible(False)
|
|
|
|
|
|
self._src_selection_box.set_visible(False)
|
2024-01-05 15:03:36 +03:00
|
|
|
|
self._interval_box.set_visible(False)
|
|
|
|
|
|
self._xml_source_box.set_margin_top(5)
|
2023-12-29 15:16:58 +03:00
|
|
|
|
|
|
|
|
|
|
settings = self._app.app_settings
|
|
|
|
|
|
self._interval_button.set_value(settings.epg_update_interval)
|
|
|
|
|
|
self._url_combo_box.get_model().clear()
|
|
|
|
|
|
[self._url_combo_box.append(i, i) for i in settings.epg_xml_sources if i]
|
|
|
|
|
|
self._url_combo_box.set_active_id(settings.epg_xml_source)
|
|
|
|
|
|
|
|
|
|
|
|
def on_apply(self, button):
|
|
|
|
|
|
settings = self._app.app_settings
|
|
|
|
|
|
xml_src = self._url_combo_box.get_active_id()
|
|
|
|
|
|
|
2024-01-05 15:03:36 +03:00
|
|
|
|
if xml_src != settings.epg_xml_source:
|
|
|
|
|
|
self._app.emit("epg-settings-changed", Page.EPG)
|
2023-12-29 15:16:58 +03:00
|
|
|
|
|
|
|
|
|
|
settings.epg_xml_source = xml_src
|
|
|
|
|
|
settings.epg_xml_sources = [r[0] for r in self._url_combo_box.get_model()]
|
|
|
|
|
|
self.popdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-05-22 23:55:13 +03:00
|
|
|
|
class EpgTool(Gtk.Box):
|
2024-01-10 14:34:26 +03:00
|
|
|
|
# Batch size to data load in one pass.
|
|
|
|
|
|
LOAD_FACTOR = 100
|
|
|
|
|
|
|
2023-02-18 11:30:06 +03:00
|
|
|
|
def __init__(self, app, **kwargs):
|
|
|
|
|
|
super().__init__(**kwargs)
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2023-12-29 14:09:13 +03:00
|
|
|
|
self._epg_cache = None
|
|
|
|
|
|
self._src = EpgSource.HTTP
|
|
|
|
|
|
self._current_bq = app.current_bouquet
|
|
|
|
|
|
|
2022-05-22 23:55:13 +03:00
|
|
|
|
self._app = app
|
2024-01-06 00:12:48 +03:00
|
|
|
|
self._app.connect("data-open", self.on_data_open)
|
2024-01-06 12:40:04 +03:00
|
|
|
|
self._app.connect("data-send", self.on_data_send)
|
2022-05-22 23:55:13 +03:00
|
|
|
|
self._app.connect("fav-changed", self.on_service_changed)
|
2023-01-28 14:43:13 +03:00
|
|
|
|
self._app.connect("profile-changed", self.on_profile_changed)
|
2022-07-16 23:56:02 +03:00
|
|
|
|
self._app.connect("bouquet-changed", self.on_bouquet_changed)
|
2023-01-25 17:54:09 +03:00
|
|
|
|
self._app.connect("filter-toggled", self.on_filter_toggled)
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2024-01-06 12:40:04 +03:00
|
|
|
|
handlers = {"on_epg_filter_changed": self.on_epg_filter_changed,
|
2022-07-14 10:32:58 +03:00
|
|
|
|
"on_epg_filter_toggled": self.on_epg_filter_toggled,
|
2022-07-16 23:56:02 +03:00
|
|
|
|
"on_view_query_tooltip": self.on_view_query_tooltip,
|
2023-12-29 14:09:13 +03:00
|
|
|
|
"on_multi_epg_toggled": self.on_multi_epg_toggled,
|
2024-01-06 12:40:04 +03:00
|
|
|
|
"on_xmltv_toggled": self.on_xmltv_toggled,
|
|
|
|
|
|
"on_epg_press": self.on_epg_press,
|
|
|
|
|
|
"on_timer_add": self.on_timer_add}
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}tab.glade", handlers)
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
|
|
|
|
|
self._view = builder.get_object("epg_view")
|
|
|
|
|
|
self._model = builder.get_object("epg_model")
|
|
|
|
|
|
self._filter_model = builder.get_object("epg_filter_model")
|
|
|
|
|
|
self._filter_model.set_visible_func(self.epg_filter_function)
|
2023-01-25 17:54:09 +03:00
|
|
|
|
self._filter_button = builder.get_object("epg_filter_button")
|
2022-05-22 23:55:13 +03:00
|
|
|
|
self._filter_entry = builder.get_object("epg_filter_entry")
|
2022-07-16 23:56:02 +03:00
|
|
|
|
self._multi_epg_button = builder.get_object("multi_epg_button")
|
2024-01-06 00:12:48 +03:00
|
|
|
|
self._src_xmltv_button = builder.get_object("src_xmltv_button")
|
2023-12-29 15:16:58 +03:00
|
|
|
|
self._epg_options_button = builder.get_object("epg_options_button")
|
|
|
|
|
|
self._epg_options_button.connect("realize", lambda b: b.set_popover(TabEpgSettingsPopover(self._app)))
|
2022-07-20 23:50:31 +03:00
|
|
|
|
self._event_count_label = builder.get_object("event_count_label")
|
2022-05-22 23:55:13 +03:00
|
|
|
|
self.pack_start(builder.get_object("epg_frame"), True, True, 0)
|
2023-01-25 17:54:09 +03:00
|
|
|
|
# Custom data functions.
|
|
|
|
|
|
renderer = builder.get_object("epg_start_renderer")
|
|
|
|
|
|
column = builder.get_object("epg_start_column")
|
|
|
|
|
|
column.set_cell_data_func(renderer, self.start_data_func)
|
|
|
|
|
|
renderer = builder.get_object("epg_end_renderer")
|
|
|
|
|
|
column = builder.get_object("epg_end_column")
|
|
|
|
|
|
column.set_cell_data_func(renderer, self.end_data_func)
|
|
|
|
|
|
renderer = builder.get_object("epg_length_renderer")
|
|
|
|
|
|
column = builder.get_object("epg_length_column")
|
|
|
|
|
|
column.set_cell_data_func(renderer, self.duration_data_func)
|
|
|
|
|
|
# Time formats.
|
|
|
|
|
|
self._time_fmt = "%a %x - %H:%M"
|
2023-02-02 00:09:51 +03:00
|
|
|
|
self._duration_fmt = f"%{'' if IS_WIN else '-'}Hh %Mm"
|
2022-07-10 18:05:52 +03:00
|
|
|
|
|
2022-05-22 23:55:13 +03:00
|
|
|
|
self.show()
|
|
|
|
|
|
|
2024-01-06 00:12:48 +03:00
|
|
|
|
def on_data_open(self, app, page):
|
|
|
|
|
|
if page is not Page.EPG:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
response = get_chooser_dialog(self._app.app_window, self._app.app_settings, "XMLTV", ("*.xml",))
|
|
|
|
|
|
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2024-01-11 14:16:38 +03:00
|
|
|
|
if next(self.clear(), False):
|
|
|
|
|
|
self._epg_cache = TabEpgCache(self._app, response)
|
|
|
|
|
|
if not self._src_xmltv_button.get_active():
|
|
|
|
|
|
self._src_xmltv_button.set_active(True)
|
2024-01-06 00:12:48 +03:00
|
|
|
|
|
2024-01-06 12:40:04 +03:00
|
|
|
|
def on_data_send(self, app, page):
|
|
|
|
|
|
if page is not Page.EPG:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if self._src_xmltv_button.get_active():
|
|
|
|
|
|
self._app.show_error_message("Not implemented yet!")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._app.show_error_message("Not allowed in this context!")
|
|
|
|
|
|
|
2023-12-15 18:07:02 +03:00
|
|
|
|
def on_service_changed(self, app, srv):
|
2023-12-29 14:09:13 +03:00
|
|
|
|
if app.page is not Page.EPG:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if self._src is EpgSource.HTTP:
|
2023-12-15 18:07:02 +03:00
|
|
|
|
ref = app.get_service_ref_data(srv)
|
|
|
|
|
|
if not ref:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2022-07-16 23:56:02 +03:00
|
|
|
|
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()
|
2023-12-29 14:09:13 +03:00
|
|
|
|
self._app.send_http_request(HttpAPI.Request.EPG, quote(ref), self.update_http_epg_data)
|
|
|
|
|
|
else:
|
|
|
|
|
|
if self._multi_epg_button.get_active():
|
|
|
|
|
|
path = next((r.path for r in self._model if r[-1].get("e2eventservicename", None) == srv.service), None)
|
|
|
|
|
|
scroll_to(path, self._view) if path else None
|
|
|
|
|
|
else:
|
2024-01-10 14:34:26 +03:00
|
|
|
|
self._app.wait_dialog.show()
|
2023-12-29 14:09:13 +03:00
|
|
|
|
self.update_xmltv_epg_data([srv.service])
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2023-01-28 14:43:13 +03:00
|
|
|
|
def on_profile_changed(self, app, prf):
|
2024-01-10 14:34:26 +03:00
|
|
|
|
gen = self.update_epg_data()
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
2023-01-28 14:43:13 +03:00
|
|
|
|
|
2023-12-29 14:09:13 +03:00
|
|
|
|
def update_http_epg_data(self, epg=None):
|
2023-01-28 14:43:13 +03:00
|
|
|
|
if epg:
|
2024-01-10 14:34:26 +03:00
|
|
|
|
events = (self.get_event(e) for e in epg.get("event_list", []) if e.get("e2eventid", "").isdigit())
|
2024-01-11 14:16:38 +03:00
|
|
|
|
else:
|
|
|
|
|
|
events = ()
|
2024-01-10 14:34:26 +03:00
|
|
|
|
gen = self.update_epg_data(events)
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2023-12-29 14:09:13 +03:00
|
|
|
|
def update_xmltv_epg_data(self, names):
|
2024-01-10 14:34:26 +03:00
|
|
|
|
gen = self.update_epg_data(chain.from_iterable(self._epg_cache.get_current_events(n) for n in names))
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
|
|
|
|
|
|
|
|
|
|
|
def update_epg_data(self, events=()):
|
|
|
|
|
|
c_gen = self.clear()
|
|
|
|
|
|
yield from c_gen
|
|
|
|
|
|
for index, e in enumerate(events):
|
|
|
|
|
|
if index % self.LOAD_FACTOR == 0:
|
|
|
|
|
|
self._event_count_label.set_text(str(len(self._model)))
|
|
|
|
|
|
yield True
|
|
|
|
|
|
self._model.append(e)
|
2023-12-29 14:09:13 +03:00
|
|
|
|
self._event_count_label.set_text(str(len(self._model)))
|
2024-01-10 14:34:26 +03:00
|
|
|
|
self._app.wait_dialog.hide()
|
|
|
|
|
|
yield True
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
|
|
|
|
|
def clear(self):
|
2024-01-10 14:34:26 +03:00
|
|
|
|
if len(self._model) < self.LOAD_FACTOR * 20:
|
|
|
|
|
|
self._model.clear()
|
|
|
|
|
|
else:
|
|
|
|
|
|
# Init new models.
|
|
|
|
|
|
column_types = (self._model.get_column_type(i) for i in range(self._model.get_n_columns()))
|
|
|
|
|
|
self._model = Gtk.ListStore(*column_types)
|
|
|
|
|
|
self._filter_model = self._model.filter_new()
|
|
|
|
|
|
self._filter_model.set_visible_func(self.epg_filter_function)
|
|
|
|
|
|
self._view.set_model(Gtk.TreeModelSort(model=self._filter_model))
|
2023-12-29 14:09:13 +03:00
|
|
|
|
self._event_count_label.set_text("0")
|
2024-01-10 14:34:26 +03:00
|
|
|
|
yield True
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
2022-06-06 20:33:37 +03:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
def get_event(event, show_day=True):
|
2022-07-16 23:56:02 +03:00
|
|
|
|
s_name = event.get("e2eventservicename", "")
|
2022-05-22 23:55:13 +03:00
|
|
|
|
title = event.get("e2eventtitle", "") or ""
|
|
|
|
|
|
desc = event.get("e2eventdescription", "") or ""
|
2022-07-14 10:32:58 +03:00
|
|
|
|
desc = desc.strip()
|
2023-01-25 17:54:09 +03:00
|
|
|
|
start, duration = int(event.get("e2eventstart", "0")), int(event.get("e2eventduration", "0"))
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2023-01-25 17:54:09 +03:00
|
|
|
|
return EpgEvent(s_name, title, start, start + duration, duration, desc, event)
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2023-01-25 17:54:09 +03:00
|
|
|
|
def start_data_func(self, column, renderer, model, itr, data):
|
|
|
|
|
|
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_START))
|
|
|
|
|
|
renderer.set_property("text", value.strftime(self._time_fmt))
|
|
|
|
|
|
|
|
|
|
|
|
def end_data_func(self, column, renderer, model, itr, data):
|
|
|
|
|
|
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_END))
|
|
|
|
|
|
renderer.set_property("text", value.strftime(self._time_fmt))
|
|
|
|
|
|
|
|
|
|
|
|
def duration_data_func(self, column, renderer, model, itr, data):
|
|
|
|
|
|
value = datetime.utcfromtimestamp(model.get_value(itr, Column.EPG_LENGTH))
|
|
|
|
|
|
renderer.set_property("text", value.strftime(self._duration_fmt))
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2024-01-10 14:34:26 +03:00
|
|
|
|
@run_with_delay(2)
|
2022-05-22 23:55:13 +03:00
|
|
|
|
def on_epg_filter_changed(self, entry):
|
|
|
|
|
|
self._filter_model.refilter()
|
|
|
|
|
|
|
|
|
|
|
|
def on_epg_filter_toggled(self, button):
|
|
|
|
|
|
if not button.get_active():
|
|
|
|
|
|
self._filter_entry.set_text("")
|
|
|
|
|
|
|
|
|
|
|
|
def epg_filter_function(self, model, itr, data):
|
|
|
|
|
|
txt = self._filter_entry.get_text().upper()
|
2023-01-25 17:54:09 +03:00
|
|
|
|
return next((s for s in model.get(itr,
|
|
|
|
|
|
Column.EPG_SERVICE,
|
|
|
|
|
|
Column.EPG_TITLE,
|
2023-12-30 12:52:06 +03:00
|
|
|
|
Column.EPG_DESC) if s and txt in s.upper()), False)
|
2023-01-25 17:54:09 +03:00
|
|
|
|
|
|
|
|
|
|
def on_filter_toggled(self, app, value):
|
|
|
|
|
|
if self._app.page is Page.EPG:
|
|
|
|
|
|
active = not self._filter_button.get_active()
|
|
|
|
|
|
self._filter_button.set_active(active)
|
|
|
|
|
|
if active:
|
|
|
|
|
|
self._filter_entry.grab_focus()
|
2023-01-28 14:43:13 +03:00
|
|
|
|
|
2022-07-14 10:32:58 +03:00
|
|
|
|
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
|
|
|
|
|
|
dst = view.get_dest_row_at_pos(x, y)
|
|
|
|
|
|
if not dst:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
path, pos = dst
|
|
|
|
|
|
model = view.get_model()
|
|
|
|
|
|
data = model[path][-1]
|
2023-12-29 14:09:13 +03:00
|
|
|
|
if not data:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2022-07-14 10:32:58 +03:00
|
|
|
|
desc = data.get("e2eventdescription", "") or ""
|
|
|
|
|
|
ext_desc = data.get("e2eventdescriptionextended", "") or ""
|
2023-12-29 14:09:13 +03:00
|
|
|
|
if not any((desc, ext_desc)):
|
|
|
|
|
|
return False
|
2022-07-14 10:32:58 +03:00
|
|
|
|
|
|
|
|
|
|
tooltip.set_text(ext_desc if ext_desc else desc)
|
|
|
|
|
|
view.set_tooltip_row(tooltip, path)
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2022-07-16 23:56:02 +03:00
|
|
|
|
def on_multi_epg_toggled(self, button):
|
|
|
|
|
|
if button.get_active():
|
|
|
|
|
|
self.get_multi_epg()
|
2024-01-11 14:16:38 +03:00
|
|
|
|
else:
|
|
|
|
|
|
next(self.clear(), False)
|
2022-07-16 23:56:02 +03:00
|
|
|
|
|
2023-12-29 14:09:13 +03:00
|
|
|
|
def on_xmltv_toggled(self, button):
|
|
|
|
|
|
if button.get_active():
|
|
|
|
|
|
self._src = EpgSource.XML
|
|
|
|
|
|
if self._epg_cache is None:
|
2024-01-06 00:12:48 +03:00
|
|
|
|
settings = self._app.app_settings
|
|
|
|
|
|
gz_file = f"{settings.profile_data_path}epg{os.sep}epg.gz"
|
|
|
|
|
|
self._epg_cache = TabEpgCache(self._app, gz_file, url=settings.epg_xml_source)
|
2023-12-29 14:09:13 +03:00
|
|
|
|
else:
|
|
|
|
|
|
self._src = EpgSource.HTTP
|
2024-01-06 00:12:48 +03:00
|
|
|
|
self.update_http_epg_data()
|
2023-12-29 14:09:13 +03:00
|
|
|
|
|
2022-07-16 23:56:02 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2024-01-10 14:34:26 +03:00
|
|
|
|
self._app.wait_dialog.show()
|
2022-07-16 23:56:02 +03:00
|
|
|
|
|
2024-01-10 14:34:26 +03:00
|
|
|
|
if self._src is EpgSource.HTTP:
|
|
|
|
|
|
bq, api = self._app.current_bouquet_files.get(self._current_bq, None), self._app.http_api
|
2023-12-29 14:09:13 +03:00
|
|
|
|
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_http_epg_data, timeout=15)
|
|
|
|
|
|
else:
|
|
|
|
|
|
srvs = self._app.current_services
|
|
|
|
|
|
self.update_xmltv_epg_data(srvs[s].service for s in self._app.current_bouquets.get(self._current_bq, []))
|
2022-07-16 23:56:02 +03:00
|
|
|
|
|
2024-01-06 12:40:04 +03:00
|
|
|
|
# ****************** Timers ***************** #
|
|
|
|
|
|
|
|
|
|
|
|
def on_epg_press(self, view, event):
|
2024-01-11 14:16:38 +03:00
|
|
|
|
if self._src_xmltv_button.get_active():
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2024-01-06 12:40:04 +03:00
|
|
|
|
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(view.get_model()) > 0:
|
|
|
|
|
|
self.on_timer_add()
|
|
|
|
|
|
|
|
|
|
|
|
def on_timer_add(self, action=None, value=None):
|
|
|
|
|
|
model, paths = self._view.get_selection().get_selected_rows()
|
|
|
|
|
|
p_count = len(paths)
|
|
|
|
|
|
|
|
|
|
|
|
if p_count == 1:
|
|
|
|
|
|
dialog = TimerTool.TimerDialog(self._app.app_window, TimerTool.TimerAction.EVENT, model[paths][-1] or {})
|
|
|
|
|
|
response = dialog.run()
|
|
|
|
|
|
if response == Gtk.ResponseType.OK:
|
|
|
|
|
|
gen = self.write_timers_list([dialog.get_request()])
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False))
|
|
|
|
|
|
dialog.destroy()
|
|
|
|
|
|
elif p_count > 1:
|
|
|
|
|
|
if show_dialog(DialogType.QUESTION, self._app.app_window,
|
|
|
|
|
|
"Add timers for selected events?") != Gtk.ResponseType.OK:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
self.add_timers_list((model[p][-1] for p in paths))
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._app.show_error_message("No selected item!")
|
|
|
|
|
|
|
|
|
|
|
|
def add_timers_list(self, paths):
|
|
|
|
|
|
ref_str = "timeraddbyeventid?sRef={}&eventid={}&justplay=0"
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
|
|
def write_timers_list(self, refs):
|
|
|
|
|
|
self._app.wait_dialog.show()
|
|
|
|
|
|
tasks = list(refs)
|
|
|
|
|
|
for ref in refs:
|
|
|
|
|
|
self._app.send_http_request(HttpAPI.Request.TIMER, ref, lambda x: tasks.pop())
|
|
|
|
|
|
yield True
|
|
|
|
|
|
|
|
|
|
|
|
while tasks:
|
|
|
|
|
|
yield True
|
|
|
|
|
|
|
|
|
|
|
|
self._app.emit("change-page", Page.TIMERS.value)
|
|
|
|
|
|
|
2022-05-22 23:55:13 +03:00
|
|
|
|
|
2019-04-18 23:05:19 +03:00
|
|
|
|
class EpgDialog:
|
|
|
|
|
|
|
2022-11-21 22:12:56 +03:00
|
|
|
|
def __init__(self, app, bouquet_name):
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2019-06-08 15:45:41 +03:00
|
|
|
|
handlers = {"on_close_dialog": self.on_close_dialog,
|
|
|
|
|
|
"on_apply": self.on_apply,
|
2019-04-27 19:05:37 +03:00
|
|
|
|
"on_update": self.on_update,
|
2019-04-21 01:18:54 +03:00
|
|
|
|
"on_save_to_xml": self.on_save_to_xml,
|
|
|
|
|
|
"on_auto_configuration": self.on_auto_configuration,
|
|
|
|
|
|
"on_filter_toggled": self.on_filter_toggled,
|
2023-01-21 15:06:27 +03:00
|
|
|
|
"on_filter_satellite_toggled": self.on_filter_satellite_toggled,
|
2019-04-21 01:18:54 +03:00
|
|
|
|
"on_filter_changed": self.on_filter_changed,
|
|
|
|
|
|
"on_info_bar_close": self.on_info_bar_close,
|
2019-04-22 00:12:04 +03:00
|
|
|
|
"on_popup_menu": on_popup_menu,
|
2019-04-24 21:53:01 +03:00
|
|
|
|
"on_bouquet_popup_menu": self.on_bouquet_popup_menu,
|
|
|
|
|
|
"on_copy_ref": self.on_copy_ref,
|
|
|
|
|
|
"on_assign_ref": self.on_assign_ref,
|
2019-05-07 17:22:18 +03:00
|
|
|
|
"on_reset": self.on_reset,
|
|
|
|
|
|
"on_list_reset": self.on_list_reset,
|
2019-04-22 00:12:04 +03:00
|
|
|
|
"on_drag_begin": self.on_drag_begin,
|
|
|
|
|
|
"on_drag_data_get": self.on_drag_data_get,
|
2019-04-22 20:25:19 +03:00
|
|
|
|
"on_drag_data_received": self.on_drag_data_received,
|
2019-04-25 00:18:49 +03:00
|
|
|
|
"on_resize": self.on_resize,
|
|
|
|
|
|
"on_names_source_changed": self.on_names_source_changed,
|
2019-04-26 22:07:21 +03:00
|
|
|
|
"on_options_save": self.on_options_save,
|
|
|
|
|
|
"on_use_web_source_switch": self.on_use_web_source_switch,
|
2019-04-27 19:05:37 +03:00
|
|
|
|
"on_enable_filtering_switch": self.on_enable_filtering_switch,
|
2019-04-30 14:17:45 +03:00
|
|
|
|
"on_update_on_start_switch": self.on_update_on_start_switch,
|
2019-05-01 13:11:19 +03:00
|
|
|
|
"on_field_icon_press": self.on_field_icon_press,
|
2022-01-24 16:39:59 +03:00
|
|
|
|
"on_key_press": self.on_key_press,
|
2023-02-16 14:42:20 +03:00
|
|
|
|
"on_bq_cursor_changed": self.on_bq_cursor_changed,
|
2023-10-06 22:00:20 +03:00
|
|
|
|
"on_source_view_query_tooltip": self.on_source_view_query_tooltip,
|
|
|
|
|
|
"on_paned_size_allocate": lambda p, a: p.set_position(0.5 * a.width)}
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2022-01-24 16:39:59 +03:00
|
|
|
|
self._app = app
|
|
|
|
|
|
self._ex_services = self._app.current_services
|
|
|
|
|
|
self._ex_fav_model = self._app.fav_view.get_model()
|
|
|
|
|
|
self._settings = self._app.app_settings
|
2019-06-04 13:31:54 +03:00
|
|
|
|
self._bouquet_name = bouquet_name
|
2019-04-24 21:53:01 +03:00
|
|
|
|
self._current_ref = []
|
2019-04-27 19:05:37 +03:00
|
|
|
|
self._enable_dat_filter = False
|
|
|
|
|
|
self._use_web_source = False
|
2019-05-04 20:13:57 +03:00
|
|
|
|
self._update_epg_data_on_start = False
|
2019-04-27 19:05:37 +03:00
|
|
|
|
self._refs_source = RefsSource.SERVICES
|
2019-06-08 15:45:41 +03:00
|
|
|
|
self._download_xml_is_active = False
|
2023-01-21 15:06:27 +03:00
|
|
|
|
self._sat_positions = None
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2022-06-14 20:14:08 +03:00
|
|
|
|
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}dialog.glade", handlers)
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
|
|
|
|
|
self._dialog = builder.get_object("epg_dialog_window")
|
2022-01-24 16:39:59 +03:00
|
|
|
|
self._dialog.set_transient_for(self._app.app_window)
|
2019-04-22 00:12:04 +03:00
|
|
|
|
self._source_view = builder.get_object("source_view")
|
|
|
|
|
|
self._bouquet_view = builder.get_object("bouquet_view")
|
2019-04-18 23:05:19 +03:00
|
|
|
|
self._bouquet_model = builder.get_object("bouquet_list_store")
|
|
|
|
|
|
self._services_model = builder.get_object("services_list_store")
|
|
|
|
|
|
self._info_bar = builder.get_object("info_bar")
|
|
|
|
|
|
self._message_label = builder.get_object("info_bar_message_label")
|
2019-04-24 21:53:01 +03:00
|
|
|
|
self._assign_ref_popup_item = builder.get_object("bouquet_assign_ref_popup_item")
|
2022-04-03 19:08:08 +03:00
|
|
|
|
self._left_action_box = builder.get_object("left_action_box")
|
2019-06-08 15:45:41 +03:00
|
|
|
|
self._xml_download_progress_bar = builder.get_object("xml_download_progress_bar")
|
2023-01-21 15:06:27 +03:00
|
|
|
|
self._src_load_spinner = builder.get_object("src_load_spinner")
|
2019-04-21 01:18:54 +03:00
|
|
|
|
# Filter
|
|
|
|
|
|
self._filter_bar = builder.get_object("filter_bar")
|
|
|
|
|
|
self._filter_entry = builder.get_object("filter_entry")
|
2023-10-06 22:00:20 +03:00
|
|
|
|
self._filter_auto_button = builder.get_object("filter_auto_button")
|
2019-04-21 01:18:54 +03:00
|
|
|
|
self._services_filter_model = builder.get_object("services_filter_model")
|
|
|
|
|
|
self._services_filter_model.set_visible_func(self.services_filter_function)
|
2023-01-21 15:06:27 +03:00
|
|
|
|
self._sat_pos_filter_model = builder.get_object("sat_pos_filter_model")
|
2019-04-30 14:17:45 +03:00
|
|
|
|
# Info
|
|
|
|
|
|
self._source_count_label = builder.get_object("source_count_label")
|
|
|
|
|
|
self._source_info_label = builder.get_object("source_info_label")
|
|
|
|
|
|
self._bouquet_count_label = builder.get_object("bouquet_count_label")
|
|
|
|
|
|
self._bouquet_epg_count_label = builder.get_object("bouquet_epg_count_label")
|
2019-04-25 00:18:49 +03:00
|
|
|
|
# Options
|
2019-04-27 19:05:37 +03:00
|
|
|
|
self._xml_radiobutton = builder.get_object("xml_radiobutton")
|
2019-04-25 00:18:49 +03:00
|
|
|
|
self._xml_chooser_button = builder.get_object("xml_chooser_button")
|
2019-04-27 19:05:37 +03:00
|
|
|
|
self._names_source_box = builder.get_object("names_source_box")
|
2019-04-26 22:07:21 +03:00
|
|
|
|
self._web_source_box = builder.get_object("web_source_box")
|
2019-04-27 19:05:37 +03:00
|
|
|
|
self._use_web_source_switch = builder.get_object("use_web_source_switch")
|
|
|
|
|
|
self._url_to_xml_entry = builder.get_object("url_to_xml_entry")
|
|
|
|
|
|
self._enable_filtering_switch = builder.get_object("enable_filtering_switch")
|
2019-04-30 14:17:45 +03:00
|
|
|
|
self._epg_dat_path_entry = builder.get_object("epg_dat_path_entry")
|
|
|
|
|
|
self._epg_dat_stb_path_entry = builder.get_object("epg_dat_stb_path_entry")
|
2019-05-04 20:13:57 +03:00
|
|
|
|
self._update_on_start_switch = builder.get_object("update_on_start_switch")
|
2019-04-27 19:05:37 +03:00
|
|
|
|
self._epg_dat_source_box = builder.get_object("epg_dat_source_box")
|
2021-10-31 16:09:07 +03:00
|
|
|
|
|
2023-02-18 11:30:06 +03:00
|
|
|
|
if self._settings.use_header_bar:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
header_bar = HeaderBar(title="EPG", subtitle=translate("List configuration"))
|
2021-10-31 16:09:07 +03:00
|
|
|
|
self._dialog.set_titlebar(header_bar)
|
|
|
|
|
|
builder.get_object("left_action_box").reparent(header_bar)
|
|
|
|
|
|
right_box = builder.get_object("right_action_box")
|
|
|
|
|
|
builder.get_object("main_actions_box").remove(right_box)
|
|
|
|
|
|
header_bar.pack_end(right_box)
|
|
|
|
|
|
builder.get_object("toolbar_box").set_visible(False)
|
|
|
|
|
|
|
2022-01-24 16:39:59 +03:00
|
|
|
|
self._app.connect("epg-dat-downloaded", self.on_epg_dat_downloaded)
|
|
|
|
|
|
|
2019-04-22 20:25:19 +03:00
|
|
|
|
# Setting the last size of the dialog window
|
2019-12-13 13:31:07 +03:00
|
|
|
|
window_size = self._settings.get("epg_tool_window_size")
|
2019-04-22 20:25:19 +03:00
|
|
|
|
if window_size:
|
|
|
|
|
|
self._dialog.resize(*window_size)
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2019-04-22 00:12:04 +03:00
|
|
|
|
self.init_drag_and_drop()
|
2019-05-01 13:11:19 +03:00
|
|
|
|
self.on_update()
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2019-06-03 15:47:04 +03:00
|
|
|
|
def show(self):
|
|
|
|
|
|
self._dialog.show()
|
|
|
|
|
|
|
2019-06-08 15:45:41 +03:00
|
|
|
|
def on_close_dialog(self, window, event):
|
|
|
|
|
|
self._download_xml_is_active = False
|
|
|
|
|
|
|
2019-04-18 23:05:19 +03:00
|
|
|
|
@run_idle
|
2019-06-03 15:47:04 +03:00
|
|
|
|
def on_apply(self, item):
|
2022-11-19 00:20:59 +03:00
|
|
|
|
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
|
2019-06-03 15:47:04 +03:00
|
|
|
|
return
|
2019-05-04 20:13:57 +03:00
|
|
|
|
|
2022-12-04 16:45:46 +03:00
|
|
|
|
p = re.compile(r"\d+")
|
2022-11-21 22:12:56 +03:00
|
|
|
|
updated = {}
|
2022-12-04 16:45:46 +03:00
|
|
|
|
|
2022-11-19 00:20:59 +03:00
|
|
|
|
for i, row in enumerate(self._bouquet_model):
|
|
|
|
|
|
if row[Column.FAV_LOCKED]:
|
2022-11-21 22:12:56 +03:00
|
|
|
|
fav_id = self._ex_fav_model[row.path][Column.FAV_ID]
|
|
|
|
|
|
srv = self._ex_services.pop(fav_id, None)
|
2019-06-03 15:47:04 +03:00
|
|
|
|
if srv:
|
2022-12-04 16:45:46 +03:00
|
|
|
|
new_fav_id, picon_id = row[Column.FAV_ID], row[Column.FAV_POS]
|
|
|
|
|
|
if picon_id:
|
|
|
|
|
|
picon_id = re.sub(p, re.search(p, srv.picon_id).group(), picon_id, count=1)
|
|
|
|
|
|
else:
|
|
|
|
|
|
picon_id = srv.picon_id
|
2022-11-21 22:12:56 +03:00
|
|
|
|
new = srv._replace(fav_id=new_fav_id, data_id=new_fav_id.strip(), picon_id=picon_id)
|
|
|
|
|
|
self._ex_services[new_fav_id] = new
|
|
|
|
|
|
updated[fav_id] = (srv, new)
|
|
|
|
|
|
|
|
|
|
|
|
if updated:
|
|
|
|
|
|
self._app.emit("iptv-service-edited", updated)
|
2022-11-19 00:20:59 +03:00
|
|
|
|
|
2019-06-03 15:47:04 +03:00
|
|
|
|
self._dialog.destroy()
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2019-06-03 15:47:04 +03:00
|
|
|
|
@run_idle
|
|
|
|
|
|
def on_update(self, item=None):
|
2019-06-08 15:45:41 +03:00
|
|
|
|
self.clear_data()
|
|
|
|
|
|
self.init_options()
|
2022-01-24 16:39:59 +03:00
|
|
|
|
if self._update_epg_data_on_start:
|
|
|
|
|
|
self.download_epg_from_stb()
|
|
|
|
|
|
else:
|
|
|
|
|
|
gen = self.init_data()
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
2019-06-08 15:45:41 +03:00
|
|
|
|
|
|
|
|
|
|
def clear_data(self):
|
2019-06-03 15:47:04 +03:00
|
|
|
|
self._services_model.clear()
|
|
|
|
|
|
self._bouquet_model.clear()
|
|
|
|
|
|
self._source_info_label.set_text("")
|
|
|
|
|
|
self._bouquet_epg_count_label.set_text("")
|
|
|
|
|
|
self.on_info_bar_close()
|
|
|
|
|
|
|
|
|
|
|
|
def init_data(self):
|
|
|
|
|
|
gen = self.init_bouquet_data()
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
2019-04-30 14:17:45 +03:00
|
|
|
|
|
2019-06-08 15:45:41 +03:00
|
|
|
|
refs = None
|
|
|
|
|
|
if self._enable_dat_filter:
|
|
|
|
|
|
try:
|
2022-06-14 20:14:08 +03:00
|
|
|
|
epg_reader = EPG.DatReader(f"{self._epg_dat_path_entry.get_text()}epg.dat")
|
2022-06-06 20:33:37 +03:00
|
|
|
|
epg_reader.read()
|
|
|
|
|
|
refs = epg_reader.get_refs()
|
2022-01-24 16:39:59 +03:00
|
|
|
|
except (OSError, ValueError) as e:
|
2021-10-31 13:34:48 +03:00
|
|
|
|
self.show_info_message(f"Read data error: {e}", Gtk.MessageType.ERROR)
|
2019-04-24 20:27:47 +03:00
|
|
|
|
return
|
2019-06-08 15:45:41 +03:00
|
|
|
|
yield True
|
|
|
|
|
|
|
2023-01-21 15:06:27 +03:00
|
|
|
|
self._src_load_spinner.start()
|
|
|
|
|
|
|
2019-06-08 15:45:41 +03:00
|
|
|
|
if self._refs_source is RefsSource.SERVICES:
|
2022-01-24 16:39:59 +03:00
|
|
|
|
yield from self.init_lamedb_source(refs)
|
2019-06-08 15:45:41 +03:00
|
|
|
|
elif self._refs_source is RefsSource.XML:
|
|
|
|
|
|
xml_gen = self.init_xml_source(refs)
|
|
|
|
|
|
try:
|
|
|
|
|
|
yield from xml_gen
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
|
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
2019-04-18 23:05:19 +03:00
|
|
|
|
else:
|
2019-06-08 15:45:41 +03:00
|
|
|
|
self.show_info_message("Unknown names source!", Gtk.MessageType.ERROR)
|
2023-01-21 15:06:27 +03:00
|
|
|
|
|
|
|
|
|
|
self._src_load_spinner.stop()
|
2019-06-03 15:47:04 +03:00
|
|
|
|
yield True
|
|
|
|
|
|
|
|
|
|
|
|
def init_bouquet_data(self):
|
|
|
|
|
|
for r in self._ex_fav_model:
|
|
|
|
|
|
row = [*r[:]]
|
|
|
|
|
|
yield self._bouquet_model.append(row)
|
|
|
|
|
|
self._bouquet_count_label.set_text(str(len(self._bouquet_model)))
|
|
|
|
|
|
yield True
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2019-04-24 20:27:47 +03:00
|
|
|
|
def init_lamedb_source(self, refs):
|
2019-05-07 00:04:53 +03:00
|
|
|
|
srvs = {k[:k.rfind(":")]: v for k, v in self._ex_services.items()}
|
2019-04-27 19:05:37 +03:00
|
|
|
|
s_types = (BqServiceType.MARKER.value, BqServiceType.IPTV.value)
|
|
|
|
|
|
filtered = filter(None, [srvs.get(ref) for ref in refs]) if refs else filter(
|
2019-05-07 00:04:53 +03:00
|
|
|
|
lambda s: s.service_type not in s_types, self._ex_services.values())
|
2022-01-24 16:39:59 +03:00
|
|
|
|
|
|
|
|
|
|
factor = self._app.DEL_FACTOR / 4
|
|
|
|
|
|
for index, srv in enumerate(filtered):
|
2023-02-16 14:42:20 +03:00
|
|
|
|
self._services_model.append((srv.service, srv.pos, srv.fav_id, srv.picon_id, srv.picon_id))
|
2022-01-24 16:39:59 +03:00
|
|
|
|
if index % factor == 0:
|
|
|
|
|
|
yield True
|
|
|
|
|
|
|
2019-06-08 15:45:41 +03:00
|
|
|
|
self.update_source_count_info()
|
2022-01-24 16:39:59 +03:00
|
|
|
|
yield True
|
2019-04-24 20:27:47 +03:00
|
|
|
|
|
|
|
|
|
|
def init_xml_source(self, refs):
|
2019-06-08 15:45:41 +03:00
|
|
|
|
path = self._epg_dat_path_entry.get_text() if self._use_web_source else self._xml_chooser_button.get_filename()
|
|
|
|
|
|
if not path:
|
|
|
|
|
|
self.show_info_message("The path to the xml file is not set!", Gtk.MessageType.ERROR)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if self._use_web_source:
|
|
|
|
|
|
# Downloading gzipped xml file that contains services names with references from the web.
|
|
|
|
|
|
self._download_xml_is_active = True
|
|
|
|
|
|
self.update_active_header_elements(False)
|
|
|
|
|
|
url = self._url_to_xml_entry.get_text()
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
with urllib.request.urlopen(url, timeout=2) as fp:
|
|
|
|
|
|
headers = fp.info()
|
|
|
|
|
|
content_type = headers.get("Content-Type", "")
|
|
|
|
|
|
|
|
|
|
|
|
if content_type != "application/gzip":
|
|
|
|
|
|
self._download_xml_is_active = False
|
2023-05-13 13:31:42 +03:00
|
|
|
|
raise ValueError("{} {} {}".format(translate("Download XML file error."),
|
|
|
|
|
|
translate("Unsupported file type:"),
|
2019-06-08 15:45:41 +03:00
|
|
|
|
content_type))
|
|
|
|
|
|
|
|
|
|
|
|
file_name = os.path.basename(url)
|
|
|
|
|
|
data_path = self._epg_dat_path_entry.get_text()
|
|
|
|
|
|
|
|
|
|
|
|
with open(data_path + file_name, "wb") as tfp:
|
|
|
|
|
|
bs = 1024 * 8
|
|
|
|
|
|
size = -1
|
|
|
|
|
|
read = 0
|
|
|
|
|
|
b_num = 0
|
|
|
|
|
|
if "content-length" in headers:
|
|
|
|
|
|
size = int(headers["Content-Length"])
|
|
|
|
|
|
|
|
|
|
|
|
while self._download_xml_is_active:
|
|
|
|
|
|
block = fp.read(bs)
|
|
|
|
|
|
if not block:
|
|
|
|
|
|
break
|
|
|
|
|
|
read += len(block)
|
|
|
|
|
|
tfp.write(block)
|
|
|
|
|
|
b_num += 1
|
|
|
|
|
|
self.update_download_progress(b_num * bs / size)
|
|
|
|
|
|
yield True
|
|
|
|
|
|
|
|
|
|
|
|
path = tfp.name.rstrip(".gz")
|
|
|
|
|
|
except (HTTPError, URLError) as e:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
raise ValueError(f"{translate('Download XML file error.')} {e}")
|
2019-06-08 15:45:41 +03:00
|
|
|
|
else:
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(path, "wb") as f_out:
|
|
|
|
|
|
with gzip.open(tfp.name, "rb") as f:
|
|
|
|
|
|
shutil.copyfileobj(f, f_out)
|
|
|
|
|
|
os.remove(tfp.name)
|
|
|
|
|
|
except Exception as e:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
raise ValueError(f"{translate('Unpacking data error.')} {e}")
|
2019-06-08 15:45:41 +03:00
|
|
|
|
finally:
|
|
|
|
|
|
self._download_xml_is_active = False
|
|
|
|
|
|
self.update_active_header_elements(True)
|
2019-04-30 14:17:45 +03:00
|
|
|
|
|
2019-05-04 20:13:57 +03:00
|
|
|
|
try:
|
2019-06-08 15:45:41 +03:00
|
|
|
|
s_refs, info = ChannelsParser.get_refs_from_xml(path)
|
|
|
|
|
|
yield True
|
2019-05-04 20:13:57 +03:00
|
|
|
|
except Exception as e:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
raise ValueError(f"{translate('XML parsing error:')} {e}")
|
2019-04-27 19:05:37 +03:00
|
|
|
|
else:
|
2023-02-14 17:36:43 +03:00
|
|
|
|
refs = refs or {}
|
2022-01-24 16:39:59 +03:00
|
|
|
|
factor = self._app.DEL_FACTOR / 4
|
2023-02-14 17:36:43 +03:00
|
|
|
|
|
2022-01-24 16:39:59 +03:00
|
|
|
|
for index, srv in enumerate(s_refs):
|
2023-02-14 17:36:43 +03:00
|
|
|
|
ref_data = srv.data.split(":")
|
|
|
|
|
|
ref = ":".join(ref_data[3:6])
|
|
|
|
|
|
if ref in refs:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
data = ":".join(ref_data[3:7])
|
|
|
|
|
|
pos, ch_id = srv.num
|
|
|
|
|
|
pos = pos or " "
|
2023-02-17 20:17:17 +03:00
|
|
|
|
self._services_model.append((srv.name, pos, data, "_".join(ref_data).rstrip("_"), ch_id))
|
2023-02-14 17:36:43 +03:00
|
|
|
|
|
2022-01-24 16:39:59 +03:00
|
|
|
|
if index % factor == 0:
|
|
|
|
|
|
yield True
|
|
|
|
|
|
|
2019-05-04 20:13:57 +03:00
|
|
|
|
self.update_source_info(info)
|
2019-06-08 15:45:41 +03:00
|
|
|
|
self.update_source_count_info()
|
|
|
|
|
|
yield True
|
2019-04-24 20:27:47 +03:00
|
|
|
|
|
2021-10-31 13:34:48 +03:00
|
|
|
|
def on_key_press(self, view, event):
|
2019-05-01 13:11:19 +03:00
|
|
|
|
""" Handling keystrokes """
|
|
|
|
|
|
key_code = event.hardware_keycode
|
|
|
|
|
|
if not KeyboardKey.value_exist(key_code):
|
|
|
|
|
|
return
|
|
|
|
|
|
key = KeyboardKey(key_code)
|
|
|
|
|
|
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
|
|
|
|
|
|
|
|
|
|
|
if ctrl and key is KeyboardKey.C:
|
|
|
|
|
|
self.on_copy_ref()
|
|
|
|
|
|
elif ctrl and key is KeyboardKey.V:
|
|
|
|
|
|
self.on_assign_ref()
|
|
|
|
|
|
|
2022-01-24 16:39:59 +03:00
|
|
|
|
def on_bq_cursor_changed(self, view):
|
2023-10-06 22:00:20 +03:00
|
|
|
|
if self._filter_bar.get_visible() and self._filter_auto_button.get_active():
|
2022-01-24 16:39:59 +03:00
|
|
|
|
path, column = view.get_cursor()
|
|
|
|
|
|
model = view.get_model()
|
|
|
|
|
|
if path:
|
|
|
|
|
|
self._filter_entry.set_text(model[path][Column.FAV_SERVICE] or "")
|
|
|
|
|
|
|
2023-02-16 14:42:20 +03:00
|
|
|
|
def on_source_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
|
|
|
|
|
|
result = view.get_dest_row_at_pos(x, y)
|
|
|
|
|
|
if not result:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
path, pos = result
|
|
|
|
|
|
ch_id = view.get_model()[path][-1]
|
|
|
|
|
|
if not ch_id:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
if self._refs_source is RefsSource.XML:
|
|
|
|
|
|
text = f"ID = {ch_id}"
|
|
|
|
|
|
else:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
text = f"{translate('Service reference')}: {ch_id.rstrip('.png')}"
|
2023-02-16 14:42:20 +03:00
|
|
|
|
|
|
|
|
|
|
tooltip.set_text(text)
|
|
|
|
|
|
view.set_tooltip_row(tooltip, path)
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2019-04-22 20:25:19 +03:00
|
|
|
|
@run_idle
|
2019-04-21 01:18:54 +03:00
|
|
|
|
def on_save_to_xml(self, item):
|
2019-12-13 13:31:07 +03:00
|
|
|
|
response = show_dialog(DialogType.CHOOSER, self._dialog, settings=self._settings)
|
2019-05-07 00:04:53 +03:00
|
|
|
|
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2019-04-22 20:25:19 +03:00
|
|
|
|
services = []
|
|
|
|
|
|
iptv_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value)
|
|
|
|
|
|
for r in self._bouquet_model:
|
|
|
|
|
|
srv_type = r[Column.FAV_TYPE]
|
|
|
|
|
|
if srv_type in iptv_types:
|
|
|
|
|
|
srv = BouquetService(name=r[Column.FAV_SERVICE],
|
|
|
|
|
|
type=BqServiceType(srv_type),
|
|
|
|
|
|
data=r[Column.FAV_ID],
|
|
|
|
|
|
num=r[Column.FAV_NUM])
|
|
|
|
|
|
services.append(srv)
|
|
|
|
|
|
|
2019-06-04 13:31:54 +03:00
|
|
|
|
ChannelsParser.write_refs_to_xml("{}{}.xml".format(response, self._bouquet_name), services)
|
2023-05-13 13:31:42 +03:00
|
|
|
|
self.show_info_message(translate("Done!"), Gtk.MessageType.INFO)
|
2019-04-21 01:18:54 +03:00
|
|
|
|
|
2019-05-07 00:04:53 +03:00
|
|
|
|
@run_idle
|
2019-04-21 01:18:54 +03:00
|
|
|
|
def on_auto_configuration(self, item):
|
2019-05-04 23:54:58 +03:00
|
|
|
|
""" Simple mapping of services by name. """
|
2019-05-05 11:26:11 +03:00
|
|
|
|
use_cyrillic = locale.getdefaultlocale()[0] in ("ru_RU", "be_BY", "uk_UA", "sr_RS")
|
2019-05-04 23:54:58 +03:00
|
|
|
|
tr = None
|
|
|
|
|
|
if use_cyrillic:
|
2019-05-07 00:04:53 +03:00
|
|
|
|
# may be not entirely correct
|
|
|
|
|
|
symbols = (u"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯІÏҐЎЈЂЉЊЋЏTB",
|
|
|
|
|
|
u"ABVGDEEJZIJKLMNOPRSTUFHZCSS_Y_EUAIEGUEDLNCJTV")
|
2019-05-04 23:54:58 +03:00
|
|
|
|
tr = {ord(k): ord(v) for k, v in zip(*symbols)}
|
|
|
|
|
|
|
|
|
|
|
|
source = {}
|
2023-01-28 17:55:19 +03:00
|
|
|
|
for row in self._source_view.get_model():
|
2019-05-04 23:54:58 +03:00
|
|
|
|
name = re.sub("\\W+", "", str(row[0])).upper()
|
|
|
|
|
|
name = name.translate(tr) if use_cyrillic else name
|
2021-10-31 13:34:48 +03:00
|
|
|
|
source[name] = row
|
2019-05-04 23:54:58 +03:00
|
|
|
|
|
2019-04-21 01:18:54 +03:00
|
|
|
|
success_count = 0
|
2019-05-07 00:04:53 +03:00
|
|
|
|
not_founded = {}
|
2019-04-21 01:18:54 +03:00
|
|
|
|
|
2019-04-22 00:12:04 +03:00
|
|
|
|
for r in self._bouquet_model:
|
2019-04-30 14:17:45 +03:00
|
|
|
|
if r[Column.FAV_TYPE] != BqServiceType.IPTV.value:
|
|
|
|
|
|
continue
|
2019-05-04 23:54:58 +03:00
|
|
|
|
name = re.sub("\\W+", "", str(r[Column.FAV_SERVICE])).upper()
|
|
|
|
|
|
if use_cyrillic:
|
|
|
|
|
|
name = name.translate(tr)
|
2019-05-07 00:04:53 +03:00
|
|
|
|
ref = source.get(name, None) # Not [pop], because the list may contain duplicates or similar names!
|
2019-04-21 01:18:54 +03:00
|
|
|
|
if ref:
|
2019-04-24 20:27:47 +03:00
|
|
|
|
self.assign_data(r, ref, True)
|
2019-04-21 01:18:54 +03:00
|
|
|
|
success_count += 1
|
2019-05-07 00:04:53 +03:00
|
|
|
|
else:
|
|
|
|
|
|
not_founded[name] = r
|
|
|
|
|
|
# Additional attempt to search in the remaining elements
|
|
|
|
|
|
for n in not_founded:
|
|
|
|
|
|
for k in source:
|
2019-05-07 17:22:18 +03:00
|
|
|
|
if k.startswith(n):
|
2019-05-07 00:04:53 +03:00
|
|
|
|
self.assign_data(not_founded[n], source[k], True)
|
|
|
|
|
|
success_count += 1
|
|
|
|
|
|
break
|
2019-04-21 01:18:54 +03:00
|
|
|
|
|
2019-04-30 14:17:45 +03:00
|
|
|
|
self.update_epg_count()
|
2023-05-13 13:31:42 +03:00
|
|
|
|
self.show_info_message("{} {} {}".format(translate("Done!"),
|
|
|
|
|
|
translate("Count of successfully configured services:"),
|
2019-06-08 16:04:47 +03:00
|
|
|
|
success_count), Gtk.MessageType.INFO)
|
2019-04-21 01:18:54 +03:00
|
|
|
|
|
2022-07-27 00:03:28 +03:00
|
|
|
|
def assign_refs(self, model, paths, data):
|
|
|
|
|
|
[self.assign_data(model[p], data) for p in paths]
|
|
|
|
|
|
self.update_epg_count()
|
|
|
|
|
|
|
2021-10-31 13:34:48 +03:00
|
|
|
|
def assign_data(self, row, data, show_error=False):
|
2019-04-30 14:17:45 +03:00
|
|
|
|
if row[Column.FAV_TYPE] != BqServiceType.IPTV.value:
|
2019-04-24 20:27:47 +03:00
|
|
|
|
if not show_error:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
self.show_info_message(translate("Not allowed in this context!"), Gtk.MessageType.ERROR)
|
2019-04-24 20:27:47 +03:00
|
|
|
|
return
|
2019-04-27 19:05:37 +03:00
|
|
|
|
|
2019-04-21 21:48:47 +03:00
|
|
|
|
fav_id = row[Column.FAV_ID]
|
|
|
|
|
|
fav_id_data = fav_id.split(":")
|
2023-02-16 14:42:20 +03:00
|
|
|
|
fav_id_data[3:7] = data[-3].split(":")
|
2022-11-19 00:20:59 +03:00
|
|
|
|
|
2023-02-16 14:42:20 +03:00
|
|
|
|
if data[-2]:
|
2023-02-17 20:17:17 +03:00
|
|
|
|
row[Column.FAV_POS] = data[-2]
|
2023-02-16 14:42:20 +03:00
|
|
|
|
p_data = data[-2].split("_")
|
2022-11-19 00:20:59 +03:00
|
|
|
|
if p_data:
|
|
|
|
|
|
fav_id_data[2] = p_data[2]
|
|
|
|
|
|
|
2019-04-21 21:48:47 +03:00
|
|
|
|
new_fav_id = ":".join(fav_id_data)
|
2022-11-19 00:20:59 +03:00
|
|
|
|
row[Column.FAV_ID] = new_fav_id
|
|
|
|
|
|
row[Column.FAV_LOCKED] = EPG_ICON
|
|
|
|
|
|
|
|
|
|
|
|
pos = f"({data[1] if self._refs_source is RefsSource.SERVICES else 'XML'})"
|
2023-05-13 13:31:42 +03:00
|
|
|
|
src = f"{translate('EPG source')}: {(GLib.markup_escape_text(data[0] or ''))} {pos}"
|
|
|
|
|
|
row[Column.FAV_TOOLTIP] = f"{translate('Service reference')}: {':'.join(fav_id_data[:10])}\n{src}"
|
2019-04-21 21:48:47 +03:00
|
|
|
|
|
2022-01-24 16:39:59 +03:00
|
|
|
|
def on_filter_toggled(self, button):
|
2022-01-24 19:17:11 +03:00
|
|
|
|
self._filter_bar.set_visible(button.get_active())
|
2023-01-21 15:06:27 +03:00
|
|
|
|
if button.get_active():
|
|
|
|
|
|
self._sat_positions = {r[1] for r in self._services_model}
|
|
|
|
|
|
update_filter_sat_positions(self._sat_pos_filter_model, self._sat_positions)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._sat_positions = None
|
|
|
|
|
|
self._filter_entry.set_text("") if self._filter_entry.get_text() else self.on_filter_changed()
|
|
|
|
|
|
|
|
|
|
|
|
def on_filter_satellite_toggled(self, toggle, path):
|
|
|
|
|
|
update_toggle_model(self._sat_pos_filter_model, path, toggle)
|
|
|
|
|
|
self._sat_positions.clear()
|
|
|
|
|
|
self._sat_positions.update({r[0] for r in self._sat_pos_filter_model if r[1]})
|
|
|
|
|
|
self.on_filter_changed()
|
2019-04-21 01:18:54 +03:00
|
|
|
|
|
2023-01-21 15:06:27 +03:00
|
|
|
|
@run_with_delay(2)
|
|
|
|
|
|
def on_filter_changed(self, entry=None):
|
2019-04-21 01:18:54 +03:00
|
|
|
|
self._services_filter_model.refilter()
|
|
|
|
|
|
|
|
|
|
|
|
def services_filter_function(self, model, itr, data):
|
|
|
|
|
|
txt = self._filter_entry.get_text().upper()
|
2023-01-21 15:06:27 +03:00
|
|
|
|
pos = model.get_value(itr, 1)
|
2023-02-14 17:36:43 +03:00
|
|
|
|
pos = self._sat_positions is None or pos in self._sat_positions
|
2023-01-21 15:06:27 +03:00
|
|
|
|
return model is None or model == "None" or (txt in model.get_value(itr, 0).upper() and pos)
|
2019-04-21 01:18:54 +03:00
|
|
|
|
|
2019-04-18 23:05:19 +03:00
|
|
|
|
def on_info_bar_close(self, bar=None, resp=None):
|
|
|
|
|
|
self._info_bar.set_visible(False)
|
|
|
|
|
|
|
2019-05-01 13:11:19 +03:00
|
|
|
|
def on_copy_ref(self, item=None):
|
2019-04-24 21:53:01 +03:00
|
|
|
|
model, paths = self._source_view.get_selection().get_selected_rows()
|
|
|
|
|
|
self._current_ref.clear()
|
2019-05-01 13:11:19 +03:00
|
|
|
|
if paths:
|
2021-10-31 13:34:48 +03:00
|
|
|
|
self._current_ref.append(model[paths][:])
|
2019-04-24 21:53:01 +03:00
|
|
|
|
|
2019-05-01 13:11:19 +03:00
|
|
|
|
def on_assign_ref(self, item=None):
|
2019-04-24 21:53:01 +03:00
|
|
|
|
if self._current_ref:
|
|
|
|
|
|
model, paths = self._bouquet_view.get_selection().get_selected_rows()
|
2022-07-27 00:03:28 +03:00
|
|
|
|
self.assign_refs(model, paths, self._current_ref.pop())
|
2019-04-24 21:53:01 +03:00
|
|
|
|
|
2019-05-07 17:22:18 +03:00
|
|
|
|
@run_idle
|
|
|
|
|
|
def on_reset(self, item):
|
|
|
|
|
|
model, paths = self._bouquet_view.get_selection().get_selected_rows()
|
|
|
|
|
|
if paths:
|
|
|
|
|
|
row = self._bouquet_model[paths]
|
|
|
|
|
|
self.reset_row_data(row)
|
2019-05-07 22:08:04 +03:00
|
|
|
|
self.update_epg_count()
|
2019-05-07 17:22:18 +03:00
|
|
|
|
|
|
|
|
|
|
@run_idle
|
|
|
|
|
|
def on_list_reset(self, item):
|
|
|
|
|
|
list(map(self.reset_row_data, self._bouquet_model))
|
2019-05-07 22:08:04 +03:00
|
|
|
|
self.update_epg_count()
|
2019-05-07 17:22:18 +03:00
|
|
|
|
|
|
|
|
|
|
def reset_row_data(self, row):
|
2022-11-19 00:20:59 +03:00
|
|
|
|
row[Column.FAV_LOCKED], row[Column.FAV_TOOLTIP], row[Column.FAV_POS] = None, None, None
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2019-04-25 00:18:49 +03:00
|
|
|
|
@run_idle
|
|
|
|
|
|
def show_info_message(self, text, message_type):
|
|
|
|
|
|
self._info_bar.set_visible(True)
|
|
|
|
|
|
self._info_bar.set_message_type(message_type)
|
|
|
|
|
|
self._message_label.set_text(text)
|
|
|
|
|
|
|
2019-04-30 14:17:45 +03:00
|
|
|
|
@run_idle
|
|
|
|
|
|
def update_source_info(self, info):
|
|
|
|
|
|
lines = info.split("\n")
|
|
|
|
|
|
self._source_info_label.set_text(lines[0] if lines else "")
|
|
|
|
|
|
|
2019-06-08 15:45:41 +03:00
|
|
|
|
@run_idle
|
|
|
|
|
|
def update_source_count_info(self):
|
|
|
|
|
|
source_count = len(self._services_model)
|
|
|
|
|
|
self._source_count_label.set_text(str(source_count))
|
|
|
|
|
|
if self._enable_dat_filter and source_count == 0:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
msg = translate("Current epg.dat file does not contains references for the services of this bouquet!")
|
2019-06-08 15:45:41 +03:00
|
|
|
|
self.show_info_message(msg, Gtk.MessageType.WARNING)
|
|
|
|
|
|
|
2019-04-30 14:17:45 +03:00
|
|
|
|
@run_idle
|
|
|
|
|
|
def update_epg_count(self):
|
|
|
|
|
|
count = len(list((filter(None, [r[Column.FAV_LOCKED] for r in self._bouquet_model]))))
|
|
|
|
|
|
self._bouquet_epg_count_label.set_text(str(count))
|
|
|
|
|
|
|
2019-06-08 15:45:41 +03:00
|
|
|
|
@run_idle
|
|
|
|
|
|
def update_active_header_elements(self, state):
|
2022-04-03 19:08:08 +03:00
|
|
|
|
self._left_action_box.set_sensitive(state)
|
2019-06-08 15:45:41 +03:00
|
|
|
|
self._xml_download_progress_bar.set_visible(not state)
|
|
|
|
|
|
self._source_info_label.set_text("" if state else "Downloading XML:")
|
|
|
|
|
|
|
|
|
|
|
|
@run_idle
|
|
|
|
|
|
def update_download_progress(self, value):
|
|
|
|
|
|
self._xml_download_progress_bar.set_fraction(value)
|
|
|
|
|
|
|
2019-04-25 00:18:49 +03:00
|
|
|
|
def on_bouquet_popup_menu(self, menu, event):
|
|
|
|
|
|
self._assign_ref_popup_item.set_sensitive(self._current_ref)
|
|
|
|
|
|
on_popup_menu(menu, event)
|
|
|
|
|
|
|
2019-04-22 00:12:04 +03:00
|
|
|
|
# ***************** Drag-and-drop *********************#
|
2019-04-24 21:53:01 +03:00
|
|
|
|
|
2019-04-22 00:12:04 +03:00
|
|
|
|
def init_drag_and_drop(self):
|
2021-10-31 13:34:48 +03:00
|
|
|
|
""" Enable drag-and-drop. """
|
2019-04-22 00:12:04 +03:00
|
|
|
|
target = []
|
|
|
|
|
|
self._source_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY)
|
|
|
|
|
|
self._source_view.drag_source_add_text_targets()
|
|
|
|
|
|
self._bouquet_view.enable_model_drag_dest(target, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
|
|
|
|
|
|
self._bouquet_view.drag_dest_add_text_targets()
|
|
|
|
|
|
|
|
|
|
|
|
def on_drag_begin(self, view, context):
|
|
|
|
|
|
""" Selects a row under the cursor in the view at the dragging beginning. """
|
|
|
|
|
|
selection = view.get_selection()
|
|
|
|
|
|
if selection.count_selected_rows() > 1:
|
|
|
|
|
|
view.do_toggle_cursor_row(view)
|
|
|
|
|
|
|
2021-10-31 13:34:48 +03:00
|
|
|
|
def on_drag_data_get(self, view, drag_context, data, info, time):
|
2019-04-22 00:12:04 +03:00
|
|
|
|
model, paths = view.get_selection().get_selected_rows()
|
|
|
|
|
|
if paths:
|
2021-10-31 13:34:48 +03:00
|
|
|
|
s_data = model[paths][:]
|
2022-11-11 23:38:06 +03:00
|
|
|
|
if all(s_data[:-1]):
|
2021-10-31 13:34:48 +03:00
|
|
|
|
data.set_text("::::".join(s_data), -1)
|
|
|
|
|
|
else:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
self.show_info_message(translate("Source error!"), Gtk.MessageType.ERROR)
|
2019-04-22 00:12:04 +03:00
|
|
|
|
|
2021-10-31 13:34:48 +03:00
|
|
|
|
def on_drag_data_received(self, view, drag_context, x, y, data, info, time):
|
2019-04-22 00:12:04 +03:00
|
|
|
|
path, pos = view.get_dest_row_at_pos(x, y)
|
|
|
|
|
|
model = view.get_model()
|
2021-10-31 13:34:48 +03:00
|
|
|
|
data = data.get_text()
|
|
|
|
|
|
if data:
|
2022-07-27 00:03:28 +03:00
|
|
|
|
data = data.split("::::")
|
|
|
|
|
|
self.assign_refs(model, path, data)
|
2019-04-22 00:12:04 +03:00
|
|
|
|
return False
|
|
|
|
|
|
|
2019-04-25 00:18:49 +03:00
|
|
|
|
# ***************** Options *********************#
|
|
|
|
|
|
|
2019-04-27 19:05:37 +03:00
|
|
|
|
def init_options(self):
|
2021-08-30 15:04:15 +03:00
|
|
|
|
epg_dat_path = "{}epg{}".format(self._settings.profile_data_path, SEP)
|
2019-04-30 14:17:45 +03:00
|
|
|
|
self._epg_dat_path_entry.set_text(epg_dat_path)
|
|
|
|
|
|
default_epg_data_stb_path = "/etc/enigma2"
|
2020-04-02 16:50:58 +03:00
|
|
|
|
epg_options = self._settings.epg_options
|
2019-04-27 19:05:37 +03:00
|
|
|
|
if epg_options:
|
|
|
|
|
|
self._refs_source = RefsSource.XML if epg_options.get("xml_source", False) else RefsSource.SERVICES
|
|
|
|
|
|
self._xml_radiobutton.set_active(self._refs_source is RefsSource.XML)
|
|
|
|
|
|
self._use_web_source = epg_options.get("use_web_source", False)
|
|
|
|
|
|
self._use_web_source_switch.set_active(self._use_web_source)
|
|
|
|
|
|
self._url_to_xml_entry.set_text(epg_options.get("url_to_xml", ""))
|
|
|
|
|
|
self._enable_dat_filter = epg_options.get("enable_filtering", False)
|
|
|
|
|
|
self._enable_filtering_switch.set_active(self._enable_dat_filter)
|
2019-04-30 14:17:45 +03:00
|
|
|
|
epg_dat_path = epg_options.get("epg_dat_path", epg_dat_path)
|
|
|
|
|
|
self._epg_dat_path_entry.set_text(epg_dat_path)
|
|
|
|
|
|
self._epg_dat_stb_path_entry.set_text(epg_options.get("epg_dat_stb_path", default_epg_data_stb_path))
|
2019-05-04 20:13:57 +03:00
|
|
|
|
self._update_epg_data_on_start = epg_options.get("epg_data_update_on_start", False)
|
|
|
|
|
|
self._update_on_start_switch.set_active(self._update_epg_data_on_start)
|
2019-04-27 19:05:37 +03:00
|
|
|
|
local_xml_path = epg_options.get("local_path_to_xml", None)
|
|
|
|
|
|
if local_xml_path:
|
|
|
|
|
|
self._xml_chooser_button.set_filename(local_xml_path)
|
2019-04-30 14:17:45 +03:00
|
|
|
|
os.makedirs(os.path.dirname(self._epg_dat_path_entry.get_text()), exist_ok=True)
|
2019-04-27 19:05:37 +03:00
|
|
|
|
|
2019-04-30 14:17:45 +03:00
|
|
|
|
def on_options_save(self, item=None):
|
2020-04-02 16:50:58 +03:00
|
|
|
|
self._settings.epg_options = {"xml_source": self._xml_radiobutton.get_active(),
|
|
|
|
|
|
"use_web_source": self._use_web_source_switch.get_active(),
|
|
|
|
|
|
"local_path_to_xml": self._xml_chooser_button.get_filename(),
|
|
|
|
|
|
"url_to_xml": self._url_to_xml_entry.get_text(),
|
|
|
|
|
|
"enable_filtering": self._enable_filtering_switch.get_active(),
|
|
|
|
|
|
"epg_dat_path": self._epg_dat_path_entry.get_text(),
|
|
|
|
|
|
"epg_dat_stb_path": self._epg_dat_stb_path_entry.get_text(),
|
|
|
|
|
|
"epg_data_update_on_start": self._update_on_start_switch.get_active()}
|
2019-04-27 19:05:37 +03:00
|
|
|
|
|
2019-04-22 20:25:19 +03:00
|
|
|
|
def on_resize(self, window):
|
2019-12-13 13:31:07 +03:00
|
|
|
|
if self._settings:
|
|
|
|
|
|
self._settings.add("epg_tool_window_size", window.get_size())
|
2019-04-22 20:25:19 +03:00
|
|
|
|
|
2019-04-25 00:18:49 +03:00
|
|
|
|
def on_names_source_changed(self, button):
|
2019-04-27 19:05:37 +03:00
|
|
|
|
self._refs_source = RefsSource.XML if button.get_active() else RefsSource.SERVICES
|
|
|
|
|
|
self._names_source_box.set_sensitive(button.get_active())
|
2019-04-24 21:53:01 +03:00
|
|
|
|
|
2019-04-27 19:05:37 +03:00
|
|
|
|
def on_enable_filtering_switch(self, switch, state):
|
|
|
|
|
|
self._epg_dat_source_box.set_sensitive(state)
|
2019-05-04 20:13:57 +03:00
|
|
|
|
self._update_on_start_switch.set_active(False if not state else self._update_epg_data_on_start)
|
2019-04-26 22:07:21 +03:00
|
|
|
|
|
|
|
|
|
|
def on_update_on_start_switch(self, switch, state):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def on_use_web_source_switch(self, switch, state):
|
|
|
|
|
|
self._web_source_box.set_sensitive(state)
|
2019-04-27 19:05:37 +03:00
|
|
|
|
self._xml_chooser_button.set_sensitive(not state)
|
2019-04-26 22:07:21 +03:00
|
|
|
|
|
2019-04-30 14:17:45 +03:00
|
|
|
|
def on_field_icon_press(self, entry, icon, event_button):
|
2019-12-13 13:31:07 +03:00
|
|
|
|
update_entry_data(entry, self._dialog, self._settings)
|
2019-04-30 14:17:45 +03:00
|
|
|
|
|
2019-05-04 20:13:57 +03:00
|
|
|
|
# ***************** Downloads *********************#
|
|
|
|
|
|
|
2022-01-24 16:39:59 +03:00
|
|
|
|
def on_epg_dat_downloaded(self, app, value):
|
|
|
|
|
|
gen = self.init_data()
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
|
|
|
|
|
|
2021-02-08 14:59:56 +03:00
|
|
|
|
@run_task
|
2019-05-04 20:13:57 +03:00
|
|
|
|
def download_epg_from_stb(self):
|
|
|
|
|
|
""" Download the epg.dat file via ftp from the receiver. """
|
2022-01-24 16:39:59 +03:00
|
|
|
|
try:
|
|
|
|
|
|
download_data(settings=self._settings, download_type=DownloadType.EPG, callback=print)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
GLib.idle_add(self.show_info_message, f"Download epg.dat file error: {e}", Gtk.MessageType.ERROR)
|
|
|
|
|
|
else:
|
|
|
|
|
|
GLib.idle_add(self._app.emit, "epg-dat-downloaded", None)
|
2019-05-04 20:13:57 +03:00
|
|
|
|
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
pass
|