From 960541b56a8d60ac56892faa63215b8b7afe5abd Mon Sep 17 00:00:00 2001 From: DYefremov Date: Wed, 31 Jul 2024 22:46:04 +0300 Subject: [PATCH] add basic streamrelay support (#199) * marking services used with streamrelay in the fav list with an additional icon --- app/connections.py | 2 +- app/eparser/enigma/streamrelay.py | 76 +++++++++++++++++++++++++++++++ app/ui/backup.py | 6 ++- app/ui/ftp.py | 22 ++++----- app/ui/main.py | 8 +++- app/ui/uicommons.py | 2 + 6 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 app/eparser/enigma/streamrelay.py diff --git a/app/connections.py b/app/connections.py index 04321a1f..0a191864 100644 --- a/app/connections.py +++ b/app/connections.py @@ -48,7 +48,7 @@ from app.settings import SettingsType BQ_FILES_LIST = ("tv", "radio", # Enigma2. "services.xml", "myservices.xml", "bouquets.xml", "ubouquets.xml") # Neutrino. -DATA_FILES_LIST = ("lamedb", "lamedb5", "blacklist", "whitelist",) +DATA_FILES_LIST = ("lamedb", "lamedb5", "blacklist", "whitelist", "whitelist_streamrelay") STC_XML_FILE = ("satellites.xml", "terrestrial.xml", "cables.xml") WEB_TV_XML_FILE = ("webtv.xml", "webtv_usr.xml") diff --git a/app/eparser/enigma/streamrelay.py b/app/eparser/enigma/streamrelay.py new file mode 100644 index 00000000..a716eec3 --- /dev/null +++ b/app/eparser/enigma/streamrelay.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# +# The MIT License (MIT) +# +# Copyright (c) 2024 Dmitriy Yefremov +# +# 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 +# + + +""" Additional module to use stream relay functionality. + + Reads/Writes 'whitelist_streamrelay' file. + """ +import os.path +from contextlib import suppress + +from app.commons import log + +_FILE_NAME = "whitelist_streamrelay" + + +class StreamRelay(dict): + """ Class to hold/process service references used by a stream relay. """ + + def refresh(self, path): + self.clear() + f_path = f"{path}{_FILE_NAME}" + if os.path.isfile(f_path): + log("Updating stream relay cache...") + with suppress(FileNotFoundError): + with open(f"{path}{_FILE_NAME}", "r", encoding="utf-8") as file: + refs = filter(None, (x.rstrip(":\n") for x in file.readlines())) + self.update(self.get_ref_data(ref) for ref in refs) + + def get_ref_data(self, ref): + """ Returns tuple from FAV ID and ref or ref and None for comments. """ + data = ref.split(":") + if len(data) == 10: + return f"{data[3]}:{data[4]}:{data[5]}:{data[6]}", ref + return ref, None + + def save(self, path): + """ Saves current refs to a file. + + If no refs is present, delites current relay file. + """ + f_name = f"{path}{_FILE_NAME}_" + if len(self): + with open(f_name, "w", encoding="utf-8") as file: + file.writelines([f"{v if v else k}{':' if v else ''}\n\n" for k, v in self.items()]) + else: + if os.path.exists(f_name): + os.remove(f_name) + + +if __name__ == "__main__": + pass diff --git a/app/ui/backup.py b/app/ui/backup.py index 81020bf8..3b20fcf5 100644 --- a/app/ui/backup.py +++ b/app/ui/backup.py @@ -43,7 +43,9 @@ from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Heade KEEP_DATA = {"satellites.xml", "terrestrial.xml", - "cables.xml"} + "cables.xml", + "whitelist", + "whitelist_streamrelay"} class RestoreType(Enum): @@ -264,7 +266,7 @@ def restore_data(src, dst): def clear_data_path(path): """ Clearing data at the specified path excluding *.xml file. """ - for file in filter(lambda f: not f.endswith(".xml") and os.path.isfile(os.path.join(path, f)), os.listdir(path)): + for file in filter(lambda f: f not in KEEP_DATA and os.path.isfile(os.path.join(path, f)), os.listdir(path)): os.remove(os.path.join(path, file)) diff --git a/app/ui/ftp.py b/app/ui/ftp.py index b1178822..79304ac1 100644 --- a/app/ui/ftp.py +++ b/app/ui/ftp.py @@ -2,7 +2,7 @@ # # The MIT License (MIT) # -# Copyright (c) 2018-2023 Dmitriy Yefremov +# Copyright (c) 2018-2024 Dmitriy Yefremov # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -45,7 +45,7 @@ from app.connections import UtfFTP from app.settings import IS_LINUX, IS_DARWIN, IS_WIN, SEP, USE_HEADER_BAR from app.ui.dialogs import show_dialog, DialogType, get_builder, translate from app.ui.main_helper import on_popup_menu -from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Page +from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Page, LINK_ICON, FOLDER_ICON File = namedtuple("File", ["icon", "name", "size", "date", "attr", "extra"]) @@ -296,12 +296,6 @@ class FtpClientBox(Gtk.HBox): # Force Ctrl self._ftp_view.connect("key-press-event", self._app.force_ctrl) self._file_view.connect("key-press-event", self._app.force_ctrl) - # Icons - theme = Gtk.IconTheme.get_default() - folder_icon = "folder-symbolic" if settings.is_darwin else "folder" - self._folder_icon = theme.load_icon(folder_icon, 16, 0) if theme.lookup_icon(folder_icon, 16, 0) else None - self._link_icon = theme.load_icon("emblem-symbolic-link", 16, 0) if theme.lookup_icon("emblem-symbolic-link", - 16, 0) else None # Initialization self.init_drag_and_drop() self.init_ftp() @@ -377,10 +371,10 @@ class FtpClientBox(Gtk.HBox): icon = None if is_dir: r_size = self.FOLDER - icon = self._folder_icon + icon = FOLDER_ICON elif p.is_symlink(): r_size = self.LINK - icon = self._link_icon + icon = LINK_ICON else: r_size = get_size_from_bytes(size) @@ -401,10 +395,10 @@ class FtpClientBox(Gtk.HBox): icon = None if is_dir: r_size = self.FOLDER - icon = self._folder_icon + icon = FOLDER_ICON elif is_link: r_size = self.LINK - icon = self._link_icon + icon = LINK_ICON else: r_size = get_size_from_bytes(size) @@ -675,7 +669,7 @@ class FtpClientBox(Gtk.HBox): log(e) self._app.show_error_message(str(e)) else: - itr = self._file_model.append(File(self._folder_icon, path.name, self.FOLDER, "", str(path.resolve()), "0")) + itr = self._file_model.append(File(FOLDER_ICON, path.name, self.FOLDER, "", str(path.resolve()), "0")) renderer.set_property("editable", True) self._file_view.set_cursor(self._file_model.get_path(itr), self._file_view.get_column(0), True) @@ -695,7 +689,7 @@ class FtpClientBox(Gtk.HBox): log(e) else: if resp == f"{cur_path}/{name}": - itr = self._ftp_model.append(File(self._folder_icon, name, self.FOLDER, "", "drwxr-xr-x", "0")) + itr = self._ftp_model.append(File(FOLDER_ICON, name, self.FOLDER, "", "drwxr-xr-x", "0")) renderer.set_property("editable", True) self._ftp_view.set_cursor(self._ftp_model.get_path(itr), self._ftp_view.get_column(0), True) diff --git a/app/ui/main.py b/app/ui/main.py index f1d4cc3b..34c611a6 100644 --- a/app/ui/main.py +++ b/app/ui/main.py @@ -45,6 +45,7 @@ from app.eparser import get_blacklist, write_blacklist, write_bouquet from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service from app.eparser.ecommons import CAS, Flag, BouquetService from app.eparser.enigma.bouquets import BqServiceType +from app.eparser.enigma.streamrelay import StreamRelay from app.eparser.iptv import export_to_m3u, StreamType from app.eparser.neutrino.bouquets import BqType from app.settings import (SettingsType, Settings, SettingsException, SettingsReadException, IS_DARWIN, IS_LINUX, @@ -70,7 +71,7 @@ from .search import SearchProvider from .service_details_dialog import ServiceDetailsDialog, Action from .settings_dialog import SettingsDialog from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column, - MOD_MASK, APP_FONT, Page, HeaderBar) + MOD_MASK, APP_FONT, Page, HeaderBar, LINK_ICON) from .xml.dialogs import ServicesUpdateDialog from .xml.edit import SatellitesTool @@ -255,6 +256,7 @@ class Application(Gtk.Application): # For bouquets with different names of services in bouquet and main list self._extra_bouquets = {} self._blacklist = set() + self._stream_relay = StreamRelay() self._current_bq_name = None self._bq_selected = "" # Current selected bouquet self._select_enabled = True # Multiple selection @@ -2330,6 +2332,7 @@ class Application(Gtk.Application): prf = self._s_type black_list = get_blacklist(data_path) + self._stream_relay.refresh(data_path) bouquets = get_bouquets(data_path, prf) yield True services = get_services(data_path, prf, self.get_format_version() if prf is SettingsType.ENIGMA_2 else 0) @@ -2777,13 +2780,14 @@ class Application(Gtk.Application): ex_srv_name = ex_services.get(srv_id) if srv: background = self._EXTRA_COLOR if self._use_colors and ex_srv_name else None + coded = LINK_ICON if srv_id in self._stream_relay else srv.coded srv_type = srv.service_type is_marker = srv_type in self.MARKER_TYPES if not is_marker: num += 1 - self._fav_model.append((0 if is_marker else num, srv.coded, ex_srv_name if ex_srv_name else srv.service, + self._fav_model.append((0 if is_marker else num, coded, ex_srv_name if ex_srv_name else srv.service, srv.locked, srv.hide, srv_type, srv.pos, srv.fav_id, None, None, background)) diff --git a/app/ui/uicommons.py b/app/ui/uicommons.py index 1c1d8ec2..042a5c2d 100644 --- a/app/ui/uicommons.py +++ b/app/ui/uicommons.py @@ -119,6 +119,8 @@ LOCKED_ICON = get_icon("changes-prevent-symbolic", 16, _IMAGE_MISSING) HIDE_ICON = get_icon("go-jump", 16, _IMAGE_MISSING) TV_ICON = get_icon("tv-symbolic", 16, _IMAGE_MISSING) IPTV_ICON = get_icon("emblem-shared", 16, _IMAGE_MISSING) +LINK_ICON = get_icon("emblem-symbolic-link", 16, _IMAGE_MISSING) +FOLDER_ICON = get_icon("folder-symbolic" if IS_DARWIN else "folder", 16, _IMAGE_MISSING) EPG_ICON = get_icon("gtk-index", 16, _IMAGE_MISSING) DEFAULT_ICON = get_icon("emblem-default", 16, get_icon("emblem-default-symbolic", 16, _IMAGE_MISSING))