diff --git a/app/picons/picons.py b/app/picons/picons.py index 0b32d0a6..72d33e47 100644 --- a/app/picons/picons.py +++ b/app/picons/picons.py @@ -1,12 +1,16 @@ import os import shutil +from collections import namedtuple from html.parser import HTMLParser from app.properties import Profile +Provider = namedtuple("Provider", ["logo", "name", "url", "on_id", "selected"]) +Picon = namedtuple("Picon", ["ref", "ssid", "v_pid"]) + class PiconsParser(HTMLParser): - """ Parser for html page. (https://www.lyngsat.com/) """ + """ Parser for package html page. (https://www.lyngsat.com/packages/*provider-name*.html) """ def __init__(self, entities=False, separator=' '): @@ -80,5 +84,74 @@ class PiconsParser(HTMLParser): return "{}.png".format(ssid) +class ProviderParser(HTMLParser): + """ Parser for satellite html page. (https://www.lyngsat.com/*sat-name*.html) """ + + def __init__(self, entities=False, separator=' '): + + HTMLParser.__init__(self) + + self._ON_ID_BLACK_LIST = ("65535", "?", "0", "1") + self._parse_html_entities = entities + self._separator = separator + self._is_td = False + self._is_th = False + self._is_provider = False + self._current_row = [] + self._current_cell = [] + self.rows = [] + self._ids = set() + + def handle_starttag(self, tag, attrs): + if tag == 'td': + self._is_td = True + if tag == 'tr': + self._is_th = True + if tag == "img": + if attrs[0][1].startswith("logo/"): + self._current_row.append(attrs[0][1]) + if tag == "a": + if "https://www.lyngsat.com/packages/" in attrs[0][1]: + self._current_row.append(attrs[0][1]) + + def handle_data(self, data): + """ Save content to a cell """ + if self._is_td or self._is_th: + self._current_cell.append(data.strip()) + + def handle_endtag(self, tag): + if tag == 'td': + self._is_td = False + elif tag == 'tr': + self._is_th = False + + if tag in ('td', 'th'): + final_cell = self._separator.join(self._current_cell).strip() + self._current_row.append(final_cell) + self._current_cell = [] + elif tag == 'tr': + row = self._current_row + if len(row) == 12: + on_id, sep, tid = str(row[-2]).partition("-") + if tid and on_id not in self._ON_ID_BLACK_LIST and on_id not in self._ids: + self.rows.append(row) + self._ids.add(on_id) + self._current_row = [] + + def error(self, message): + pass + + +def parse_providers(open_path): + with open(open_path, encoding="utf-8", errors="replace") as f: + parser = ProviderParser() + parser.reset() + parser.feed(f.read()) + rows = parser.rows + + if rows: + return [Provider(logo=r[2], name=r[5], url=r[6], on_id=r[-2], selected=True) for r in rows] + + if __name__ == "__main__": pass diff --git a/app/ui/picons_dialog.glade b/app/ui/picons_dialog.glade index 8672fe8b..95f7cb89 100644 --- a/app/ui/picons_dialog.glade +++ b/app/ui/picons_dialog.glade @@ -2,6 +2,21 @@ + + + + + + + + + + + + + + + 480 True @@ -56,143 +71,6 @@ False vertical 2 - - - True - False - Satellite url (www.lyngsat.com): - 0.019999999552965164 - - - False - True - 0 - - - - - True - True - network-workgroup-symbolic - False - https://www.lyngsat.com/packages/'your-provider'.html - url - - - False - True - 1 - - - - - True - False - - - True - False - - - - - - True - True - - - - - True - False - center - True - Load providers - True - gtk-goto-bottom - - - True - True - - - - - True - False - - - - - - True - True - - - - - - True - True - 2 - - - - - 120 - True - True - out - - - True - True - - - - - - Provider - - - - - - - - - - - Select - - - - - Url - - - - - - - True - True - 3 - - - - - True - False - - - False - True - 4 - - True @@ -246,7 +124,7 @@ False True - 5 + 0 @@ -259,7 +137,7 @@ False True - 6 + 1 @@ -273,20 +151,7 @@ False True - 7 - - - - - True - False - 2 - 2 - - - False - True - 8 + 2 @@ -349,7 +214,127 @@ False True - 9 + 3 + + + + + True + False + + + False + True + 4 + + + + + True + False + Satellite url (www.lyngsat.com): + 0.019999999552965164 + + + False + True + 5 + + + + + True + True + network-workgroup-symbolic + False + https://www.lyngsat.com/*satellite*.html + url + + + + False + True + 6 + + + + + 150 + True + True + out + + + True + True + providers_list_store + + + + + + 15 + Providers + True + 0.5 + + + + 0 + + + + + + 1 + + + + + + + False + Url + + + + 2 + + + + + + + False + ONID + + + + 3 + + + + + + + Selected + + + + + + 4 + + + + + + + + + True + True + 7 @@ -368,11 +353,11 @@ True False + False True False - True Cancel True gtk-cancel @@ -384,11 +369,23 @@ - + True False + + + False + False + + + + + True + False + False + Receive picons for providers True - Receive + Receive picons True go-bottom @@ -398,6 +395,23 @@ True + + + True + False + False + Load satellite providers. + True + Load providers + True + network-server-symbolic + + + + False + True + + True @@ -411,8 +425,8 @@ True + False False - True Send True go-top diff --git a/app/ui/picons_dialog.py b/app/ui/picons_dialog.py index 9e4f937f..4ed0f6e6 100644 --- a/app/ui/picons_dialog.py +++ b/app/ui/picons_dialog.py @@ -1,12 +1,13 @@ +import re import subprocess import tempfile import time -from gi.repository import GLib +from gi.repository import GLib, GdkPixbuf from app.commons import run_idle, run_task -from app.picons.picons import PiconsParser -from . import Gtk, UI_RESOURCES_PATH +from app.picons.picons import PiconsParser, parse_providers +from . import Gtk, Gdk, UI_RESOURCES_PATH from .main_helper import update_entry_data @@ -14,21 +15,27 @@ class PiconsDialog: def __init__(self, transient, options): self._TMP_DIR = tempfile.gettempdir() + "/" self._BASE_URL = "www.lyngsat.com/packages/" + self._PATTERN = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html$") self._current_process = None self._picons_path = options.get("picons_dir_path", "") 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, "on_info_bar_close": self.on_info_bar_close, - "on_picons_dir_open": self.on_picons_dir_open} + "on_picons_dir_open": self.on_picons_dir_open, + "on_selected_toggled": self.on_selected_toggled, + "on_url_changed": self.on_url_changed} builder = Gtk.Builder() - builder.add_objects_from_file(UI_RESOURCES_PATH + "picons_dialog.glade", ("picons_dialog", "receive_image")) + builder.add_objects_from_file(UI_RESOURCES_PATH + "picons_dialog.glade", + ("picons_dialog", "receive_image", "providers_list_store")) builder.connect_signals(handlers) self._dialog = builder.get_object("picons_dialog") self._dialog.set_transient_for(transient) + self._providers_tree_view = builder.get_object("providers_tree_view") self._expander = builder.get_object("expander") self._text_view = builder.get_object("text_view") self._info_bar = builder.get_object("info_bar") @@ -39,6 +46,12 @@ class PiconsDialog: self._info_bar = builder.get_object("info_bar") self._info_bar = builder.get_object("info_bar") self._message_label = builder.get_object("info_bar_message_label") + self._load_providers_tool_button = builder.get_object("load_providers_tool_button") + # 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) self._ip_entry.set_text(options.get("host", "")) self._picons_entry.set_text(options.get("picons_path", "")) @@ -48,6 +61,36 @@ class PiconsDialog: self._dialog.run() self._dialog.destroy() + @run_idle + def on_load_providers(self, item): + self._expander.set_expanded(True) + url = self._url_entry.get_text() + self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer) + self.append_providers(url) + + @run_task + def append_providers(self, url): + model = self._providers_tree_view.get_model() + model.clear() + self._current_process.wait() + providers = parse_providers(self._TMP_DIR + url[url.find("w"):]) + if providers: + for p in providers: + logo = self.get_pixbuf(p[0]) + model.append((logo, p.name, p.url, p.on_id, p.selected)) + + def get_pixbuf(self, img_url): + # image = Gtk.Image() + # image.set_from_file(self._TMP_DIR + "www.lyngsat.com/" + img_url) + # image.size_allocate_with_baseline() + # return image.get_pixbuf() + return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=self._TMP_DIR + "www.lyngsat.com/" + img_url, + width=48, height=48, preserve_aspect_ratio=True) + @run_idle def on_receive(self, item): self.start_download() @@ -55,18 +98,18 @@ class PiconsDialog: def start_download(self): self._expander.set_expanded(True) self.show_info_message("Please, wait...", Gtk.MessageType.INFO) - url = "https://" + self._BASE_URL + "NTV-Plus.html" + url = self._url_entry.get_text() self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer) - self.batch_rename() + self.batch_rename(url) @run_task - def batch_rename(self): + def batch_rename(self, url): self._current_process.wait() - path = self._TMP_DIR + self._BASE_URL + "NTV-Plus.html" + path = self._TMP_DIR + self._BASE_URL + url[url.rfind("/") + 1:] PiconsParser.parse(path, self._picons_path, self._TMP_DIR) self.show_info_message("Done", Gtk.MessageType.INFO) @@ -114,6 +157,15 @@ class PiconsDialog: def on_picons_dir_open(self, entry, icon, event_button): update_entry_data(entry, self._dialog, options={"data_dir_path": self._picons_path}) + def on_selected_toggled(self, toggle, path): + model = self._providers_tree_view.get_model() + model.set_value(model.get_iter(path), 4, not toggle.get_active()) + + 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_tool_button.set_sensitive(suit if suit else False) + if __name__ == "__main__": pass