added basic support for downloading picons from picon.cz

This commit is contained in:
DYefremov
2021-05-11 00:18:27 +03:00
parent 73a1f2ccef
commit a99c1e00b9
4 changed files with 529 additions and 133 deletions

View File

@@ -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"

View File

@@ -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
@@ -1853,6 +1881,7 @@ class Application(Gtk.Application):
alt_servs = srv.transponder
if alt_servs:
alt_srv = self._services.get(alt_servs[0].data, 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,
@@ -2294,6 +2323,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:

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2020 Dmitriy Yefremov
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
@@ -28,9 +28,10 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="picons_dest_list_store">
<columns>
@@ -228,8 +229,20 @@ Author: Dmitriy Yefremov
<object class="GtkHeaderBar" id="header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkComboBoxText" id="download_source_button">
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Source:</property>
<property name="active">0</property>
<items>
<item id="piconcz" translatable="yes">Picon.cz</item>
<item id="lyngsat" translatable="yes">LyngSat</item>
</items>
<signal name="changed" handler="on_download_source_changed" swapped="no"/>
</object>
</child>
<child>
<object class="GtkButton" id="cancel_button">
<property name="can_focus">False</property>
@@ -246,35 +259,15 @@ Author: Dmitriy Yefremov
</child>
<accelerator key="z" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="header_download_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="load_providers_button">
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Load providers</property>
<property name="halign">center</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_load_providers" swapped="no"/>
<child>
<object class="GtkImage" id="load_providers_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-server-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="receive_button">
<property name="sensitive">False</property>
@@ -398,27 +391,6 @@ Author: Dmitriy Yefremov
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="info_check_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Details</property>
<property name="draw_indicator">False</property>
<child>
<object class="GtkImage" id="info_check_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-info</property>
</object>
</child>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="filter_button">
<property name="visible">True</property>
@@ -441,6 +413,27 @@ Author: Dmitriy Yefremov
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="info_check_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Details</property>
<property name="draw_indicator">False</property>
<child>
<object class="GtkImage" id="info_check_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-info</property>
</object>
</child>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">5</property>
</packing>
</child>
</object>
</child>
<child>
@@ -995,7 +988,7 @@ Author: Dmitriy Yefremov
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="satellites_box">
<property name="width_request">200</property>
<property name="width_request">250</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">2</property>
@@ -1042,6 +1035,48 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="satellite_filter_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">5</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkCheckButton" id="satellite_filter_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<property name="image_position">right</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_satellite_filter_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="satellite_filter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Filter</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -1127,10 +1162,11 @@ Author: Dmitriy Yefremov
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel">
<object class="GtkLabel" id="provider_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Satellite url:</property>
<property name="halign">start</property>
<property name="margin_left">10</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1138,22 +1174,6 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">network-workgroup-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">https://www.lyngsat.com/*satellite*.html</property>
<property name="input_purpose">url</property>
<signal name="changed" handler="on_url_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="providers_scrolled_window">
<property name="height_request">150</property>
@@ -1167,7 +1187,9 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="model">providers_list_store</property>
<property name="search_column">1</property>
<property name="tooltip_column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="providers_popup_menu" swapped="no"/>
<signal name="query-tooltip" handler="on_providers_view_query_tooltip" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
@@ -1175,11 +1197,14 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="provider_column">
<property name="spacing">15</property>
<property name="title" translatable="yes">Providers</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="logo_cellrendererpixbuf"/>
<object class="GtkCellRendererPixbuf" id="logo_cellrendererpixbuf">
<property name="xpad">5</property>
</object>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
@@ -1261,6 +1286,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="selected_column">
<property name="title" translatable="yes">Selected</property>
<property name="sort_column_id">7</property>
<child>
<object class="GtkCellRendererToggle" id="cellrenderer_toggle">
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>

View File

@@ -1,7 +1,37 @@
# -*- 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 concurrent.futures
import os
import re
import shutil
import tempfile
from enum import Enum
from pathlib import Path
from urllib.parse import urlparse, unquote
@@ -10,7 +40,8 @@ from gi.repository import GLib, GdkPixbuf, Gio
from app.commons import run_idle, run_task, run_with_delay
from app.connections import upload_data, DownloadType, download_data, remove_picons
from app.settings import SettingsType, Settings
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to, download_picon
from app.tools.picons import (PiconsParser, parse_providers, Provider, convert_to, download_picon, PiconsCzDownloader,
PiconsError)
from app.tools.satellites import SatellitesParser, SatelliteSource
from .dialogs import show_dialog, DialogType, get_message, get_builder
from .main_helper import (update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model, set_picon,
@@ -19,6 +50,10 @@ from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey
class PiconsDialog:
class DownloadSource(Enum):
LYNG_SAT = "lyngsat"
PICON_CZ = "piconcz"
def __init__(self, transient, settings, picon_ids, sat_positions, app):
self._picon_ids = picon_ids
self._sat_positions = sat_positions
@@ -33,9 +68,13 @@ class PiconsDialog:
self._filter_binding = None
self._services = None
self._current_picon_info = None
# Downloader
self._sats = None
self._sat_names = None
self._download_src = self.DownloadSource.PICON_CZ
self._picon_cz_downloader = None
handlers = {"on_receive": self.on_receive,
"on_load_providers": self.on_load_providers,
"on_cancel": self.on_cancel,
"on_close": self.on_close,
"on_send": self.on_send,
@@ -64,7 +103,10 @@ class PiconsDialog:
"on_selective_remove": self.on_selective_remove,
"on_local_remove": self.on_local_remove,
"on_picons_dest_view_realize": self.on_picons_dest_view_realize,
"on_download_source_changed": self.on_download_source_changed,
"on_satellites_view_realize": self.on_satellites_view_realize,
"on_satellite_filter_toggled": self.on_satellite_filter_toggled,
"on_providers_view_query_tooltip": self.on_providers_view_query_tooltip,
"on_satellite_selection": self.on_satellite_selection,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
@@ -100,7 +142,6 @@ class PiconsDialog:
self._picons_filter_entry = builder.get_object("picons_filter_entry")
self._ip_entry = builder.get_object("ip_entry")
self._picons_entry = builder.get_object("picons_entry")
self._url_entry = builder.get_object("url_entry")
self._picons_dir_entry = builder.get_object("picons_dir_entry")
self._info_bar = builder.get_object("info_bar")
self._info_bar = builder.get_object("info_bar")
@@ -108,7 +149,7 @@ class PiconsDialog:
self._info_check_button = builder.get_object("info_check_button")
self._picon_info_image = builder.get_object("picon_info_image")
self._picon_info_label = builder.get_object("picon_info_label")
self._load_providers_button = builder.get_object("load_providers_button")
self._download_source_button = builder.get_object("download_source_button")
self._receive_button = builder.get_object("receive_button")
self._convert_button = builder.get_object("convert_button")
self._enigma2_path_button = builder.get_object("enigma2_path_button")
@@ -123,12 +164,16 @@ class PiconsDialog:
self._resize_220_132_radio_button = builder.get_object("resize_220_132_radio_button")
self._resize_100_60_radio_button = builder.get_object("resize_100_60_radio_button")
self._satellite_label = builder.get_object("satellite_label")
self._provider_header_label = builder.get_object("provider_header_label")
self._satellite_filter_button = builder.get_object("satellite_filter_button")
self._header_download_box = builder.get_object("header_download_box")
self._satellite_label.bind_property("visible", builder.get_object("loading_data_label"), "visible", 4)
self._satellite_label.bind_property("visible", builder.get_object("loading_data_spinner"), "visible", 4)
self._satellite_label.bind_property("visible", self._download_source_button, "sensitive")
self._satellite_label.bind_property("visible", self._satellites_view, "sensitive")
self._cancel_button.bind_property("visible", self._header_download_box, "visible", 4)
self._convert_button.bind_property("visible", self._header_download_box, "visible", 4)
self._load_providers_button.bind_property("visible", self._receive_button, "visible")
self._download_source_button.bind_property("visible", self._receive_button, "visible")
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
self._explorer_src_path_button.bind_property("sensitive", builder.get_object("picons_view_sw"), "sensitive")
self._filter_button.bind_property("active", builder.get_object("filter_service_box"), "visible")
@@ -143,11 +188,7 @@ class PiconsDialog:
self._info_check_button.bind_property("active", explorer_info_bar, "visible")
# Init drag-and-drop
self.init_drag_and_drop()
# Style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
# Settings
self._settings = settings
self._s_type = settings.setting_type
self._ip_entry.set_text(self._settings.host)
@@ -464,72 +505,158 @@ class PiconsDialog:
# ******************** Downloader ************************* #
def on_download_source_changed(self, button):
self._download_src = self.DownloadSource(button.get_active_id())
self.set_providers_header()
GLib.idle_add(self._providers_view.get_model().clear)
self.init_satellites(self._satellites_view)
def on_satellites_view_realize(self, view):
self.set_providers_header()
self.get_satellites(view)
def on_satellite_filter_toggled(self, button):
self.init_satellites(self._satellites_view)
def on_providers_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
if self._download_src is self.DownloadSource.LYNG_SAT:
return False
dest = view.get_dest_row_at_pos(x, y)
if not dest:
return False
path, pos = dest
model = view.get_model()
itr = model.get_iter(path)
logo_url = model.get_value(itr, 5)
if logo_url:
pix_data = self._picon_cz_downloader.get_logo_data(logo_url)
if pix_data:
pix = self.get_pixbuf(pix_data)
model.set_value(itr, 0, pix if pix else TV_ICON)
size = self._settings.tooltip_logo_size
tooltip.set_icon(self.get_pixbuf(pix_data, size, size))
else:
self.update_logo_data(itr, model, logo_url)
tooltip.set_text(model.get_value(itr, 1))
view.set_tooltip_row(tooltip, path)
return True
@run_task
def update_logo_data(self, itr, model, url):
pix_data = self._picon_cz_downloader.get_provider_logo(url)
if pix_data:
pix = self.get_pixbuf(pix_data)
GLib.idle_add(model.set_value, itr, 0, pix if pix else TV_ICON)
@run_idle
def set_providers_header(self):
msg = "{} [{}]"
if self._download_src is self.DownloadSource.PICON_CZ:
msg = msg.format(get_message("Package"), "https://picon.cz (by Chocholoušek)")
elif self._download_src is self.DownloadSource.LYNG_SAT:
msg = msg.format(get_message("Providers"), "https://www.lyngsat.com")
else:
msg = ""
self._provider_header_label.set_text(msg)
@run_task
def get_satellites(self, view):
sats = SatellitesParser().get_satellites_list(SatelliteSource.LYNGSAT)
if not sats:
self._sats = SatellitesParser().get_satellites_list(SatelliteSource.LYNGSAT)
if not self._sats:
self.show_info_message("Getting satellites list error!", Gtk.MessageType.ERROR)
self._sat_names = {s[1]: s[0] for s in self._sats} # position -> satellite name
self._picon_cz_downloader = PiconsCzDownloader(self._picon_ids, self.append_output)
self.init_satellites(view)
@run_task
def init_satellites(self, view):
sats = self._sats
if self._download_src is self.DownloadSource.PICON_CZ:
try:
self._picon_cz_downloader.init()
except PiconsError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
providers = self._picon_cz_downloader.providers
sats = ((self._sat_names.get(p, p), p, None, p, False) for p in providers)
gen = self.append_satellites(view.get_model(), sats)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def append_satellites(self, model, sats):
is_filter = self._satellite_filter_button.get_active()
model.clear()
try:
for sat in sats:
for sat in sorted(sats):
pos = sat[1]
name = "{} ({})".format(sat[0], pos)
if not self._terminate and model:
if pos in self._sat_positions:
if is_filter and pos not in self._sat_positions:
continue
yield model.append((name, sat[3], pos))
finally:
self._satellite_label.show()
def on_satellite_selection(self, view, path, column):
model = view.get_model()
self._url_entry.set_text(model.get(model.get_iter(path), 1)[0])
def on_load_providers(self, item):
self.on_info_bar_close()
model = self._providers_view.get_model()
model.clear()
self.get_providers(model)
self._satellite_label.set_visible(False)
self.get_providers(view.get_model()[path][1], model)
@run_task
def get_providers(self, model):
providers = parse_providers(self._url_entry.get_text())
if providers:
self.append_providers(providers, model)
def get_providers(self, url, model):
if self._download_src is self.DownloadSource.LYNG_SAT:
providers = parse_providers(url)
elif self._download_src is self.DownloadSource.PICON_CZ:
providers = self._picon_cz_downloader.get_sat_providers(url)
else:
return
self.append_providers(providers or [], model)
@run_idle
def append_providers(self, providers, model):
if self._download_src is self.DownloadSource.LYNG_SAT:
for p in providers:
model.append((self.get_pixbuf(p[0]) if p[0] else TV_ICON, *p[1:]))
self.update_receive_button_state()
model.append(p._replace(logo=self.get_pixbuf(p.logo) if p.logo else TV_ICON))
elif self._download_src is self.DownloadSource.PICON_CZ:
for p in providers:
logo_data = self._picon_cz_downloader.get_logo_data(p.ssid)
model.append(p._replace(logo=self.get_pixbuf(logo_data) if logo_data else TV_ICON))
def get_pixbuf(self, img_data):
self.update_receive_button_state()
GLib.idle_add(self._satellite_label.set_visible, True)
def get_pixbuf(self, img_data, w=48, h=32):
if img_data:
f = Gio.MemoryInputStream.new_from_data(img_data)
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, 48, 32, True, None)
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, w, h, True, None)
def on_receive(self, item):
self._cancel_button.show()
self.start_download()
@run_task
def start_download(self):
if self._is_downloading:
self.show_dialog("The task is already running!", DialogType.ERROR)
return
providers = self.get_selected_providers()
if self._download_src is self.DownloadSource.PICON_CZ and len(providers) > 1:
self.show_dialog("Please, select only one item!", DialogType.ERROR)
return
self._cancel_button.show()
self.start_download(providers)
@run_task
def start_download(self, providers):
self._is_downloading = True
GLib.idle_add(self._expander.set_expanded, True)
providers = self.get_selected_providers()
for prv in providers:
if not self._POS_PATTERN.match(prv[2]):
if self._download_src is self.DownloadSource.LYNG_SAT and not self._POS_PATTERN.match(prv[2]):
self.show_info_message(
get_message("Specify the correct position value for the provider!"), Gtk.MessageType.ERROR)
scroll_to(prv.path, self._providers_view)
@@ -538,33 +665,13 @@ class PiconsDialog:
try:
picons_path = self._picons_dir_entry.get_text()
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
picons = []
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
providers = (Provider(*p) for p in providers)
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
# Getting links to picons.
futures = {executor.submit(self.process_provider, Provider(*p), picons_path): p for p in providers}
for future in concurrent.futures.as_completed(futures):
if not self._is_downloading:
executor.shutdown()
return
pic = future.result()
if pic:
picons.extend(pic)
# Getting picon images.
futures = {executor.submit(download_picon, *pic, self.append_output): pic for pic in picons}
done, not_done = concurrent.futures.wait(futures, timeout=0)
while self._is_downloading and not_done:
done, not_done = concurrent.futures.wait(not_done, timeout=5)
for future in not_done:
future.cancel()
concurrent.futures.wait(not_done)
if self._download_src is self.DownloadSource.LYNG_SAT:
self.get_picons_for_lyngsat(picons_path, providers)
elif self._download_src is self.DownloadSource.PICON_CZ:
self.get_picons_for_picon_cz(picons_path, providers)
if not self._is_downloading:
return
@@ -577,6 +684,50 @@ class PiconsDialog:
GLib.idle_add(self._cancel_button.hide)
self._is_downloading = False
def get_picons_for_lyngsat(self, path, providers):
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
picons = []
# Getting links to picons.
futures = {executor.submit(self.process_provider, p, path): p for p in providers}
for future in concurrent.futures.as_completed(futures):
if not self._is_downloading:
executor.shutdown()
return
pic = future.result()
if pic:
picons.extend(pic)
# Getting picon images.
futures = {executor.submit(download_picon, *pic, self.append_output): pic for pic in picons}
done, not_done = concurrent.futures.wait(futures, timeout=0)
while self._is_downloading and not_done:
done, not_done = concurrent.futures.wait(not_done, timeout=5)
for future in not_done:
future.cancel()
concurrent.futures.wait(not_done)
def get_picons_for_picon_cz(self, path, providers):
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
futures = {executor.submit(self._picon_cz_downloader.download, p, path): p for p in providers}
for future in concurrent.futures.as_completed(futures):
if not self._is_downloading:
executor.shutdown()
return
try:
future.result()
except PiconsError as e:
self.append_output("Error: {}\n".format(str(e)))
done, not_done = concurrent.futures.wait(futures, timeout=0)
while self._is_downloading and not_done:
done, not_done = concurrent.futures.wait(not_done, timeout=5)
for future in not_done:
future.cancel()
concurrent.futures.wait(not_done)
def process_provider(self, prv, picons_path):
self.append_output("Getting links to picons for: {}.\n".format(prv.name))
return PiconsParser.parse(prv, picons_path, self._picon_ids, self.get_picons_format())
@@ -781,7 +932,7 @@ class PiconsDialog:
def on_url_changed(self, entry):
suit = self._PATTERN.search(entry.get_text())
entry.set_name("GtkEntry" if suit else "digit-entry")
self._load_providers_button.set_sensitive(suit if suit else False)
self._download_source_button.set_sensitive(suit if suit else False)
def on_position_edited(self, render, path, value):
model = self._providers_view.get_model()
@@ -791,7 +942,7 @@ class PiconsDialog:
def on_visible_page(self, stack: Gtk.Stack, param):
name = stack.get_visible_child_name()
self._convert_button.set_visible(name == "converter")
self._load_providers_button.set_visible(name == "downloader")
self._download_source_button.set_visible(name == "downloader")
is_explorer = name == "explorer"
self._filter_button.set_visible(is_explorer)
if is_explorer: