From 5353af94acfb7a0de9eada3bd77340efcb5765e1 Mon Sep 17 00:00:00 2001 From: DYefremov Date: Wed, 12 May 2021 14:59:55 +0300 Subject: [PATCH] added support for saving single bouquets --- app/eparser/__init__.py | 36 ++++++++++++ app/ui/main_app_window.py | 112 +++++++++++++++++++++++++------------- app/ui/main_window.glade | 10 ++++ 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/app/eparser/__init__.py b/app/eparser/__init__.py index dad92073..7f9057f1 100644 --- a/app/eparser/__init__.py +++ b/app/eparser/__init__.py @@ -1,3 +1,30 @@ +# -*- coding: utf-8 -*- +# +# The MIT License (MIT) +# +# Copyright (c) 2018-2021 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 +# + from app.commons import run_task from app.settings import SettingsType from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid @@ -32,6 +59,15 @@ def get_bouquets(path, s_type): return get_neutrino_bouquets(path) +def write_bouquet(path, bq, s_type): + if s_type is SettingsType.ENIGMA_2: + writer = BouquetsWriter(path, None) + writer.write_bouquet(path + "userbouquet.{}.{}".format(bq.name, bq.type), bq.name, bq.services) + elif s_type is SettingsType.NEUTRINO_MP: + from .neutrino.bouquets import write_bouquet + write_bouquet(path, bq) + + @run_task def write_bouquets(path, bouquets, s_type, force_bq_names=False): if s_type is SettingsType.ENIGMA_2: diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py index 6ff8b4f9..3180b72f 100644 --- a/app/ui/main_app_window.py +++ b/app/ui/main_app_window.py @@ -39,7 +39,7 @@ from gi.repository import GLib, Gio from app.commons import run_idle, log, run_task, run_with_delay, init_logger from app.connections import (HttpAPI, download_data, DownloadType, upload_data, test_http, TestException, HttpApiException, STC_XML_FILE) -from app.eparser import get_blacklist, write_blacklist +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 @@ -154,6 +154,7 @@ class Application(Gtk.Application): "on_model_changed": self.on_model_changed, "on_import_yt_list": self.on_import_yt_list, "on_import_m3u": self.on_import_m3u, + "on_bouquet_export": self.on_bouquet_export, "on_export_to_m3u": self.on_export_to_m3u, "on_import_bouquet": self.on_import_bouquet, "on_import_bouquets": self.on_import_bouquets, @@ -1710,19 +1711,7 @@ class Application(Gtk.Application): bqs = [] num_of_children = model.iter_n_children(itr) for num in range(num_of_children): - bq_itr = model.iter_nth_child(itr, num) - bq_name, locked, hidden, bq_type = model.get(bq_itr, Column.BQ_NAME, Column.BQ_LOCKED, - Column.BQ_HIDDEN, Column.BQ_TYPE) - bq_id = "{}:{}".format(bq_name, bq_type) - favs = self._bouquets[bq_id] - ex_s = self._extra_bouquets.get(bq_id, None) - bq_s = list(filter(None, [self._services.get(f_id, None) for f_id in favs])) - - if profile is SettingsType.ENIGMA_2: - bq_s = self.get_enigma_bq_services(bq_s, ex_s) - - bq = Bouquet(bq_name, bq_type, bq_s, locked, hidden, self._bq_file.get(bq_id, None)) - bqs.append(bq) + bqs.append(self.get_bouquet(model.iter_nth_child(itr, num), model)) if len(b_path) == 1: bouquets.append(Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), bqs if bqs else [])) @@ -1747,6 +1736,20 @@ class Application(Gtk.Application): if callback: callback() + def get_bouquet(self, itr, model): + """ Constructs and returns Bouquet class instance. """ + bq_name, locked, hidden, bq_type = model.get(itr, Column.BQ_NAME, Column.BQ_LOCKED, Column.BQ_HIDDEN, + Column.BQ_TYPE) + bq_id = "{}:{}".format(bq_name, bq_type) + favs = self._bouquets[bq_id] + ex_s = self._extra_bouquets.get(bq_id, None) + bq_s = list(filter(None, [self._services.get(f_id, None) for f_id in favs])) + + if self._s_type is SettingsType.ENIGMA_2: + bq_s = self.get_enigma_bq_services(bq_s, ex_s) + + return Bouquet(bq_name, bq_type, bq_s, locked, hidden, self._bq_file.get(bq_id, None)) + def get_enigma_bq_services(self, services, ext_services): """ Preparing a list of services for the Enigma2 bouquet. """ s_list = [] @@ -2224,7 +2227,7 @@ class Application(Gtk.Application): bq = self._bouquets.get(self._bq_selected) EpgDialog(self._main_window, self._settings, self._services, bq, self._fav_model, self._current_bq_name).show() - # ***************** Import ********************# + # ***************** Import ******************** # def on_import_yt_list(self, action, value=None): """ Import playlist from YouTube """ @@ -2256,30 +2259,6 @@ class Application(Gtk.Application): gen = self.update_bouquet_services(self._fav_model, None, self._bq_selected) GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW) - @run_idle - def on_export_to_m3u(self, action, value=None): - i_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value) - bq_services = [BouquetService(r[Column.FAV_SERVICE], - BqServiceType(r[Column.FAV_TYPE]), - r[Column.FAV_ID], - r[Column.FAV_NUM]) for r in self._fav_model if r[Column.FAV_TYPE] in i_types] - - if not any(s.type is BqServiceType.IPTV for s in bq_services): - self.show_error_dialog("This list does not contains IPTV streams!") - return - - response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings) - if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): - return - - try: - bq = Bouquet(self._current_bq_name, None, bq_services, None, None) - export_to_m3u(response, bq, self._s_type) - except Exception as e: - self.show_error_dialog(str(e)) - else: - show_dialog(DialogType.INFO, self._main_window, "Done!") - def on_import_data(self, path): msg = "Combine with the current data?" if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window, @@ -2361,6 +2340,61 @@ class Application(Gtk.Application): yield True self._wait_dialog.hide() + # ***************** Export ******************** # + + def on_bouquet_export(self, item=None): + """ Exports single bouquet to file. """ + bq_selected = self.check_bouquet_selection() + if not bq_selected: + return + + model, paths = self._bouquets_view.get_selection().get_selected_rows() + if len(paths) > 1: + self.show_error_dialog("Please, select only one bouquet!") + return + + response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings, + buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) + if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): + return + + try: + itr = model.get_iter(paths) + bq = self.get_bouquet(itr, model) + if self._s_type is SettingsType.NEUTRINO_MP: + bq = Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), [bq]) + response += bq.name + write_bouquet(response, bq, self._s_type) + except OSError as e: + self.show_error_dialog(str(e)) + else: + show_dialog(DialogType.INFO, self._main_window, "Done!") + + @run_idle + def on_export_to_m3u(self, action, value=None): + i_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value) + bq_services = [BouquetService(r[Column.FAV_SERVICE], + BqServiceType(r[Column.FAV_TYPE]), + r[Column.FAV_ID], + r[Column.FAV_NUM]) for r in self._fav_model if r[Column.FAV_TYPE] in i_types] + + if not any(s.type is BqServiceType.IPTV for s in bq_services): + self.show_error_dialog("This list does not contains IPTV streams!") + return + + response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings, + buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) + if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): + return + + try: + bq = Bouquet(self._current_bq_name, None, bq_services, None, None) + export_to_m3u(response, bq, self._s_type) + except Exception as e: + self.show_error_dialog(str(e)) + else: + show_dialog(DialogType.INFO, self._main_window, "Done!") + # ***************** Backup ******************** # def on_backup_tool_show(self, action, value=None): diff --git a/app/ui/main_window.glade b/app/ui/main_window.glade index 8484e6ed..73fc031d 100644 --- a/app/ui/main_window.glade +++ b/app/ui/main_window.glade @@ -358,6 +358,16 @@ Author: Dmitriy Yefremov + + + gtk-save-as + True + False + True + True + + + True