added basic support for downloading picons from picon.cz

This commit is contained in:
DYefremov
2021-05-15 13:03:17 +03:00
parent c4d2b7747a
commit 06f110c29a
4 changed files with 498 additions and 109 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
@@ -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:

View File

@@ -54,12 +54,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="icon_name">emblem-important-symbolic</property>
</object>
<object class="GtkImage" id="load_providers">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-server-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="picons_dest_list_store">
<columns>
<!-- column-name picon -->
@@ -818,6 +812,24 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</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="halign">center</property>
<property name="active">0</property>
<items>
<item id="piconcz" translatable="yes">Picon.cz</item>
<item id="lyngsat" translatable="yes">LyngSat</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="downloader_frame">
<property name="visible">True</property>
@@ -835,7 +847,7 @@ Author: Dmitriy Yefremov
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="satellites_box">
<property name="width_request">180</property>
<property name="width_request">250</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">2</property>
@@ -882,6 +894,46 @@ 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="column_spacing">2</property>
<child>
<object class="GtkCheckButton" id="satellite_filter_button">
<property name="visible">True</property>
<property name="can_focus">True</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>
</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>
@@ -966,10 +1018,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>
@@ -977,22 +1030,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>
@@ -1225,7 +1262,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -1668,25 +1705,6 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="load_providers_button">
<property name="label" translatable="yes">Load providers</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Load providers</property>
<property name="valign">center</property>
<property name="image">load_providers</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_load_providers" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="receive_button">
<property name="label" translatable="yes">Receive picons</property>

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,13 +142,12 @@ 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._message_label = builder.get_object("info_bar_message_label")
self._info_toggle_button = builder.get_object("info_toggle_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")
@@ -121,11 +162,15 @@ 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._explorer_action_box = builder.get_object("explorer_action_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._download_source_button.bind_property("visible", self._receive_button, "visible")
self._cancel_button.bind_property("visible", builder.get_object("receive_button"), "visible", 4)
self._cancel_button.bind_property("visible", self._load_providers_button, "visible", 4)
self._convert_button.bind_property("visible", self._explorer_action_box, "visible", 4)
downloader_action_box = builder.get_object("downloader_action_box")
self._explorer_action_box.bind_property("visible", downloader_action_box, "visible", 4)
@@ -141,11 +186,7 @@ class PiconsDialog:
self._info_toggle_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)
@@ -465,72 +506,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:
yield model.append((name, sat[3], pos))
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):
for p in providers:
model.append((self.get_pixbuf(p[0]) if p[0] else TV_ICON, *p[1:]))
self.update_receive_button_state()
if self._download_src is self.DownloadSource.LYNG_SAT:
for p in providers:
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)
@@ -539,33 +666,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
@@ -578,6 +685,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())
@@ -782,7 +933,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()
@@ -792,6 +943,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._download_source_button.set_visible(name == "downloader")
is_explorer = name == "explorer"
self._explorer_action_box.set_visible(is_explorer)
if is_explorer: