diff --git a/app/tools/picons.py b/app/tools/picons.py
index 7fce4992..a56cafe0 100644
--- a/app/tools/picons.py
+++ b/app/tools/picons.py
@@ -1,7 +1,36 @@
+# -*- 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
+#
+
+
import glob
import os
import re
import shutil
+import subprocess
from collections import namedtuple
from html.parser import HTMLParser
@@ -18,6 +47,166 @@ Provider = namedtuple("Provider", ["logo", "name", "pos", "url", "on_id", "ssid"
Picon = namedtuple("Picon", ["ref", "ssid"])
+class PiconsError(Exception):
+ pass
+
+
+class PiconsCzDownloader:
+ """ The main class for loading picons from the https://picon.cz/ source (by ChocholouĊĦek). """
+
+ _PERM_URL = "https://picon.cz/download/7337"
+ _BASE_URL = "https://picon.cz/download/"
+ _BASE_LOGO_URL = "https://picon.cz/picon/0/"
+ _HEADER = {"User-Agent": "DemonEditor/1.0.8", "Referer": ""}
+ _LINK_PATTERN = re.compile(r"((.*)-\d+x\d+)-(.*)_by_chocholousek.7z$")
+ _FILE_PATTERN = re.compile(b"\\s+(1_.*\\.png).*")
+
+ def __init__(self, picon_ids=set(), appender=log):
+ self._perm_links = {}
+ self._providers = {}
+ self._provider_logos = {}
+ self._picon_ids = picon_ids
+ self._appender = appender
+
+ def init(self):
+ """ Initializes dict with values: download_id -> perm link and provider data. """
+ if self._perm_links:
+ return
+
+ self._HEADER["Referer"] = self._PERM_URL
+
+ with requests.get(url=self._PERM_URL, headers=self._HEADER, stream=True) as request:
+ if request.reason == "OK":
+ logo_map = self.get_logos_map()
+ name_map = self.get_name_map()
+
+ for line in request.iter_lines():
+ l_id, perm_link = line.decode(encoding="utf-8", errors="ignore").split(maxsplit=1)
+ self._perm_links[str(l_id)] = str(perm_link)
+ data = re.match(self._LINK_PATTERN, perm_link)
+ if data:
+ sat_pos = data.group(3)
+ # Logo url.
+ logo = logo_map.get(data.group(2), None)
+ l_name = name_map.get(sat_pos, None) or sat_pos.replace(".", "")
+ logo_url = "{}{}/{}.png".format(self._BASE_LOGO_URL, logo, l_name) if logo else None
+
+ prv = Provider(None, data.group(1), sat_pos, self._BASE_URL + l_id, l_id, logo_url, None, False)
+ if sat_pos in self._providers:
+ self._providers[sat_pos].append(prv)
+ else:
+ self._providers[sat_pos] = [prv]
+ else:
+ log("{} [get permalinks] error: {}".format(self.__class__.__name__, request.reason))
+ raise PiconsError(request.reason)
+
+ @property
+ def providers(self):
+ return self._providers
+
+ def get_sat_providers(self, url):
+ return self._providers.get(url, [])
+
+ def download(self, provider, picons_path):
+ self._HEADER["Referer"] = provider.url
+ with requests.get(url=provider.url, headers=self._HEADER, stream=True) as request:
+ if request.reason == "OK":
+ dest = "{}{}.7z".format(picons_path, provider.on_id)
+ self._appender("Downloading: {}\n".format(provider.url))
+ with open(dest, mode="bw") as f:
+ for data in request.iter_content(chunk_size=1024):
+ f.write(data)
+ self._appender("Extracting: {}\n".format(provider.on_id))
+ self.extract(dest, picons_path)
+ else:
+ log("{} [download] error: {}".format(self.__class__.__name__, request.reason))
+
+ def extract(self, src, dest):
+ """ Extracts 7z archives. """
+ # TODO: think about https://github.com/miurahr/py7zr
+ cmd = ["7zr", "l", src]
+ try:
+ out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+ if err:
+ log("{} [extract] error: {}".format(self.__class__.__name__, err))
+ raise PiconsError(err)
+ except OSError as e:
+ log("{} [extract] error: {}".format(self.__class__.__name__, e))
+ raise PiconsError(e)
+
+ to_extract = []
+ for o in re.finditer(self._FILE_PATTERN, out):
+ p_id = o.group(1).decode("utf-8", errors="ignore")
+ if p_id in self._picon_ids:
+ to_extract.append(p_id)
+
+ cmd = ["7zr", "e", src, "-o{}".format(dest), "-y", "-r"]
+ cmd.extend(to_extract)
+ try:
+ out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+ if err:
+ log("{} [extract] error: {}".format(self.__class__.__name__, err))
+ raise PiconsError(err)
+ else:
+ if os.path.isfile(src):
+ os.remove(src)
+ except OSError as e:
+ log(e)
+ raise PiconsError(e)
+
+ def get_logo_data(self, url):
+ """ Returns the logo data if present. """
+ return self._provider_logos.get(url, None)
+
+ def get_provider_logo(self, url):
+ """ Retrieves package logo. """
+ # Getting package logo.
+ logo = self._provider_logos.get(url, None)
+ if logo:
+ return logo
+
+ with requests.get(url=url, stream=True) as logo_request:
+ if logo_request.reason == "OK":
+ data = logo_request.content
+ self._provider_logos[url] = data
+ return data
+ else:
+ log("Downloading package logo error: {}".format(logo_request.reason))
+
+ def get_logos_map(self):
+ return {"piconblack": "b50",
+ "picontransparent": "t50",
+ "piconwhite": "w50",
+ "piconmirrorglass": "mr100",
+ "piconnoName": "n100",
+ "piconsrhd": "srhd100",
+ "piconfreezeframe": "ff220",
+ "piconfreezewhite": "fw100",
+ "piconpoolrainbow": "r100",
+ "piconsimpleblack": "s220",
+ "piconjustblack": "jb220",
+ "picondirtypaper": "dp220",
+ "picongray": "g400",
+ "piconmonochrom": "m220",
+ "picontransparentwhite": "tw100",
+ "picontransparentdark": "td220",
+ "piconoled": "o96",
+ "piconblack80": "b50",
+ "piconblack3d": "b50"
+ }
+
+ def get_name_map(self):
+ return {"antiksat": "ANTIK",
+ "digiczsk": "DIGI",
+ "DTTitaly": "picon_trs-it",
+ "dvbtCZSK": "picon_trs",
+ "PolandDTT": "picon_trs-pl",
+ "freeSAT": "UPC DIRECT",
+ "orangesat": "ORANGE TV",
+ "skylink": "M7 GROUP",
+ }
+
+
class PiconsParser(HTMLParser):
""" Parser for package html page. (https://www.lyngsat.com/packages/*provider-name*.html) """
_BASE_URL = "https://www.lyngsat.com"
diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py
index 34c41a3a..2ffbaa01 100644
--- a/app/ui/main_app_window.py
+++ b/app/ui/main_app_window.py
@@ -1,3 +1,31 @@
+# -*- 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
+#
+
+
import os
import sys
from contextlib import suppress
@@ -1996,7 +2024,8 @@ class Application(Gtk.Application):
alt_servs = srv.transponder
if alt_servs:
alt_srv = self._services.get(alt_servs[0].data, None)
- picon = self._picons.get(alt_srv.picon_id, None) if srv else None
+ if alt_srv:
+ picon = self._picons.get(alt_srv.picon_id, None) if srv else None
self._fav_model.append((0 if is_marker else num, srv.coded, ex_srv_name if ex_srv_name else srv.service,
srv.locked, srv.hide, srv_type, srv.pos, srv.fav_id,
@@ -2437,6 +2466,7 @@ class Application(Gtk.Application):
dialog = ImportDialog(self._main_window, path, self._settings, self._services.keys(), append)
dialog.import_data() if force else dialog.show()
+ self.update_picons()
def append_imported_data(self, bouquets, services, callback=None):
try:
diff --git a/app/ui/picons_manager.glade b/app/ui/picons_manager.glade
index 4222558a..abaccbfc 100644
--- a/app/ui/picons_manager.glade
+++ b/app/ui/picons_manager.glade
@@ -54,12 +54,6 @@ Author: Dmitriy Yefremov
Falseemblem-important-symbolic
-