diff --git a/app/connections.py b/app/connections.py
index bdce6cac..cad6ba94 100644
--- a/app/connections.py
+++ b/app/connections.py
@@ -1,18 +1,20 @@
-import json
import os
+import re
import socket
import time
import urllib
+import xml.etree.ElementTree as ETree
from enum import Enum
from ftplib import FTP, error_perm
from http.client import RemoteDisconnected
from telnetlib import Telnet
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
-from urllib.request import urlopen, HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener, install_opener
+from urllib.request import urlopen, HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, \
+ build_opener, install_opener, Request
-from app.commons import log
-from app.settings import Profile
+from app.commons import log, run_task
+from app.settings import SettingsType
_BQ_FILES_LIST = ("tv", "radio", # enigma 2
"myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
@@ -35,21 +37,28 @@ class DownloadType(Enum):
class HttpRequestType(Enum):
ZAP = "zap?sRef="
INFO = "about"
- SIGNAL = "tunersignal"
- STREAM = "streamcurrentm3u"
- STATUS = "statusinfo"
+ SIGNAL = "signal"
+ STREAM = "stream.m3u?ref="
+ STREAM_CURRENT = "streamcurrent.m3u"
+ CURRENT = "getcurrent"
PLAY = "mediaplayerplay?file=4097:0:1:0:0:0:0:0:0:0:"
+ TEST = None
+ TOKEN = "session"
class TestException(Exception):
pass
+class HttpApiException(Exception):
+ pass
+
+
def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
- save_path = settings.data_dir_path
+ save_path = settings.data_local_path
os.makedirs(os.path.dirname(save_path), exist_ok=True)
files = []
# bouquets
@@ -94,15 +103,16 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False,
callback=print, done_callback=None, use_http=False):
- profile = settings.profile
- data_path = settings.data_dir_path
+ s_type = settings.setting_type
+ data_path = settings.data_local_path
host = settings.host
- base_url = "http://{}:{}/api/".format(host, settings.http_port)
+ base_url = "http{}://{}:{}".format("s" if settings.http_use_ssl else "", host, settings.http_port)
+ url = "{}/web/".format(base_url)
tn, ht = None, None # telnet, http
try:
- if profile is Profile.ENIGMA_2 and use_http:
- ht = http(settings.http_user, settings.http_password, base_url, callback)
+ if s_type is SettingsType.ENIGMA_2 and use_http:
+ ht = http(settings.http_user, settings.http_password, base_url, callback, settings.http_use_ssl)
next(ht)
message = ""
if download_type is DownloadType.BOUQUETS:
@@ -113,12 +123,11 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
message = "Satellites.xml file will be updated!"
params = urlencode({"text": message, "type": 2, "timeout": 5})
- url = base_url + "message?{}".format(params)
- ht.send(url)
+ ht.send((url + "message?{}".format(params), "Sending info message... "))
if download_type is DownloadType.ALL:
time.sleep(5)
- ht.send(base_url + "/powerstate?newstate=0")
+ ht.send((url + "powerstate?newstate=0", "Toggle Standby "))
time.sleep(2)
else:
# telnet
@@ -139,7 +148,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
if download_type is DownloadType.SATELLITES:
upload_xml(ftp, data_path, sat_xml_path, _SAT_XML_FILE, callback)
- if profile is Profile.NEUTRINO_MP and download_type is DownloadType.WEBTV:
+ if s_type is SettingsType.NEUTRINO_MP and download_type is DownloadType.WEBTV:
upload_xml(ftp, data_path, sat_xml_path, _WEBTV_XML_FILE, callback)
if download_type is DownloadType.BOUQUETS:
@@ -148,7 +157,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
if download_type is DownloadType.ALL:
upload_xml(ftp, data_path, sat_xml_path, _SAT_XML_FILE, callback)
- if profile is Profile.NEUTRINO_MP:
+ if s_type is SettingsType.NEUTRINO_MP:
upload_xml(ftp, data_path, sat_xml_path, _WEBTV_XML_FILE, callback)
ftp.cwd(services_path)
@@ -156,17 +165,17 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
upload_files(ftp, data_path, _DATA_FILES_LIST, callback)
if download_type is DownloadType.PICONS:
- upload_picons(ftp, settings.picons_dir_path, settings.picons_path, callback)
+ upload_picons(ftp, settings.picons_local_path, settings.picons_path, callback)
if tn and not use_http:
# resume enigma or restart neutrino
- tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
+ tn.send("init 3" if s_type is SettingsType.ENIGMA_2 else "init 6")
elif ht and use_http:
if download_type is DownloadType.BOUQUETS:
- ht.send(base_url + "/servicelistreload?mode=2")
+ ht.send((url + "servicelistreload?mode=2", "Reloading Userbouquets."))
elif download_type is DownloadType.ALL:
- ht.send(base_url + "/servicelistreload?mode=0")
- ht.send(base_url + "/powerstate?newstate=4")
+ ht.send((url + "servicelistreload?mode=0", "Reloading lamedb and Userbouquets."))
+ ht.send((url + "powerstate?newstate=4", "Wakeup from Standby."))
if done_callback is not None:
done_callback()
@@ -238,14 +247,14 @@ def send_file(file_name, path, ftp, callback):
callback("Uploading file: {}. Status: {}\n".format(file_name, str(ftp.storbinary("STOR " + file_name, f))))
-def http(user, password, url, callback):
- init_auth(user, password, url)
+def http(user, password, url, callback, use_ssl=False):
+ init_auth(user, password, url, use_ssl)
+ data = get_post_data(url, password, url)
+
while True:
- url = yield
- with urlopen(url, timeout=5) as f:
- msg = json.loads(f.read().decode("utf-8")).get("message", None)
- if msg:
- callback("HTTP: {}\n".format(msg))
+ url, message = yield
+ resp = get_response(HttpRequestType.TEST, url, data).get("e2statetext", None)
+ callback("HTTP: {} {}\n".format(message, "Successful." if resp and message else ""))
def telnet(host, port=23, user="", password="", timeout=5):
@@ -276,38 +285,102 @@ def telnet(host, port=23, user="", password="", timeout=5):
# ***************** HTTP API *******************#
class HttpAPI:
+ __MAX_WORKERS = 4
- def __init__(self, host, port, user, password):
- self._base_url = "http://{}:{}/api/".format(host, port)
- init_auth(user, password, self._base_url)
+ def __init__(self, settings):
+ self._settings = settings
+ self._shutdown = False
+ self._session_id = 0
+ self._base_url = None
+ self._data = None
+ self.init()
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
- self._executor = PoolExecutor(max_workers=2)
+ self._executor = PoolExecutor(max_workers=self.__MAX_WORKERS)
def send(self, req_type, ref, callback=print):
+ if self._shutdown:
+ return
+
url = self._base_url + req_type.value
- if req_type is HttpRequestType.ZAP:
+ if req_type is HttpRequestType.ZAP or req_type is HttpRequestType.STREAM:
url += urllib.parse.quote(ref)
elif req_type is HttpRequestType.PLAY:
url += urllib.parse.quote(ref).replace("%3A", "%253A")
- future = self._executor.submit(get_json, req_type, url)
+ future = self._executor.submit(get_response, req_type, url, self._data)
future.add_done_callback(lambda f: callback(f.result()))
+ @run_task
+ def init(self):
+ user, password = self._settings.http_user, self._settings.http_password
+ use_ssl = self._settings.http_use_ssl
+ url = "http{}://{}:{}".format("s" if use_ssl else "", self._settings.host, self._settings.http_port)
+ self._base_url = "{}/web/".format(url)
+ init_auth(user, password, url, use_ssl)
+ url = "{}/web/{}".format(url, HttpRequestType.TOKEN.value)
+ s_id = get_session_id(user, password, url)
+ if s_id != "0":
+ self._data = urllib.parse.urlencode({"user": user, "password": password, "sessionid": s_id}).encode("utf-8")
+
+ @run_task
def close(self):
- self._executor.shutdown(False)
+ self._shutdown = True
+ self._executor.shutdown()
-def get_json(req_type, url):
+def get_response(req_type, url, data=None):
try:
- with urlopen(url, timeout=10) as f:
- if req_type is HttpRequestType.STREAM:
+ with urlopen(Request(url, data=data), timeout=10) as f:
+ if req_type is HttpRequestType.STREAM or req_type is HttpRequestType.STREAM_CURRENT:
return f.read().decode("utf-8")
+ elif req_type is HttpRequestType.CURRENT:
+ for el in ETree.fromstring(f.read().decode("utf-8")).iter("e2event"):
+ return {el.tag: el.text for el in el.iter()} # return first[current] event from the list
else:
- return json.loads(f.read().decode("utf-8"))
- except (URLError, HTTPError):
- pass
+ return {el.tag: el.text for el in ETree.fromstring(f.read().decode("utf-8")).iter()}
+ except HTTPError as e:
+ if req_type is HttpRequestType.TEST:
+ raise e
+ return {"error_code": e.code}
+ except (URLError, RemoteDisconnected, ConnectionResetError) as e:
+ if req_type is HttpRequestType.TEST:
+ raise e
+ except ETree.ParseError as e:
+ log("Parsing response error: {}".format(e))
+
+ return {"error_code": -1}
+
+
+def init_auth(user, password, url, use_ssl=False):
+ """ Init authentication """
+ pass_mgr = HTTPPasswordMgrWithDefaultRealm()
+ pass_mgr.add_password(None, url, user, password)
+ auth_handler = HTTPBasicAuthHandler(pass_mgr)
+
+ if use_ssl:
+ import ssl
+ from urllib.request import HTTPSHandler
+
+ opener = build_opener(auth_handler, HTTPSHandler(context=ssl._create_unverified_context()))
+ else:
+ opener = build_opener(auth_handler)
+
+ install_opener(opener)
+
+
+def get_session_id(user, password, url):
+ data = urllib.parse.urlencode(dict(user=user, password=password)).encode("utf-8")
+ return get_response(HttpRequestType.TOKEN, url, data=data).get("e2sessionid", "0")
+
+
+def get_post_data(base_url, password, user):
+ s_id = get_session_id(user, password, "{}/web/{}".format(base_url, HttpRequestType.TOKEN.value))
+ data = None
+ if s_id != "0":
+ data = urllib.parse.urlencode({"user": user, "password": password, "sessionid": s_id}).encode("utf-8")
+ return data
# ***************** Connections testing *******************#
@@ -320,36 +393,30 @@ def test_ftp(host, port, user, password, timeout=5):
raise TestException(e)
-def test_http(host, port, user, password, timeout=5, skip_message=False):
- try:
- params = urlencode({"text": "Connection test", "type": 2, "timeout": timeout})
- params = "statusinfo" if skip_message else "message?{}".format(params)
- url = "http://{}:{}/api/{}".format(host, port, params)
- # authentication
- init_auth(user, password, url)
+def test_http(host, port, user, password, timeout=5, use_ssl=False, skip_message=False):
+ params = urlencode({"text": "Connection test", "type": 2, "timeout": timeout})
+ params = "statusinfo" if skip_message else "message?{}".format(params)
+ base_url = "http{}://{}:{}".format("s" if use_ssl else "", host, port)
+ # authentication
+ init_auth(user, password, base_url, use_ssl)
+ data = get_post_data(base_url, password, user)
- with urlopen(url, timeout=5) as f:
- return json.loads(f.read().decode("utf-8")).get("message", "")
+ try:
+ return get_response(HttpRequestType.TEST, "{}/web/{}".format(base_url, params), data).get("e2statetext", "")
except (RemoteDisconnected, URLError, HTTPError) as e:
raise TestException(e)
-def init_auth(user, password, url):
- """ Init authentication """
- pass_mgr = HTTPPasswordMgrWithDefaultRealm()
- pass_mgr.add_password(None, url, user, password)
- auth_handler = HTTPBasicAuthHandler(pass_mgr)
- opener = build_opener(auth_handler)
- install_opener(opener)
-
-
def test_telnet(host, port, user, password, timeout=5):
try:
gen = telnet_test(host, port, user, password, timeout)
res = next(gen)
- print(res)
- res = next(gen)
- return res
+ msg = str(res, encoding="utf8").strip()
+ log(msg)
+ next(gen)
+ if re.search("password", msg, re.IGNORECASE):
+ raise TestException(msg)
+ return msg
except (socket.timeout, OSError) as e:
raise TestException(e)
@@ -358,14 +425,14 @@ def telnet_test(host, port, user, password, timeout):
tn = Telnet(host=host, port=port, timeout=timeout)
time.sleep(1)
tn.read_until(b"login: ", timeout=2)
- tn.write(user.encode("utf-8") + b"\n")
+ tn.write(user.encode("utf-8") + b"\r")
time.sleep(timeout)
tn.read_until(b"Password: ", timeout=2)
- tn.write(password.encode("utf-8") + b"\n")
+ tn.write(password.encode("utf-8") + b"\r")
time.sleep(timeout)
yield tn.read_very_eager()
tn.close()
- yield "Done!"
+ yield
if __name__ == "__main__":
diff --git a/app/eparser/__init__.py b/app/eparser/__init__.py
index fff031a5..d514d26b 100644
--- a/app/eparser/__init__.py
+++ b/app/eparser/__init__.py
@@ -1,5 +1,5 @@
from app.commons import run_task
-from app.settings import Profile
+from app.settings import SettingsType
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid
from .enigma.blacklist import get_blacklist, write_blacklist
from .enigma.bouquets import get_bouquets as get_enigma_bouquets, write_bouquets as write_enigma_bouquets, to_bouquet_id
@@ -10,33 +10,33 @@ from .neutrino.services import get_services as get_neutrino_services, write_serv
from .satxml import get_satellites, write_satellites
-def get_services(data_path, profile, format_version):
- if profile is Profile.ENIGMA_2:
+def get_services(data_path, s_type, format_version):
+ if s_type is SettingsType.ENIGMA_2:
return get_enigma_services(data_path, format_version)
- elif profile is Profile.NEUTRINO_MP:
+ elif s_type is SettingsType.NEUTRINO_MP:
return get_neutrino_services(data_path)
@run_task
-def write_services(path, channels, profile, format_version):
- if profile is Profile.ENIGMA_2:
+def write_services(path, channels, s_type, format_version):
+ if s_type is SettingsType.ENIGMA_2:
write_enigma_services(path, channels, format_version)
- elif profile is Profile.NEUTRINO_MP:
+ elif s_type is SettingsType.NEUTRINO_MP:
write_neutrino_services(path, channels)
-def get_bouquets(path, profile):
- if profile is Profile.ENIGMA_2:
+def get_bouquets(path, s_type):
+ if s_type is SettingsType.ENIGMA_2:
return get_enigma_bouquets(path)
- elif profile is Profile.NEUTRINO_MP:
+ elif s_type is SettingsType.NEUTRINO_MP:
return get_neutrino_bouquets(path)
@run_task
-def write_bouquets(path, bouquets, profile):
- if profile is Profile.ENIGMA_2:
+def write_bouquets(path, bouquets, s_type):
+ if s_type is SettingsType.ENIGMA_2:
write_enigma_bouquets(path, bouquets)
- elif profile is Profile.NEUTRINO_MP:
+ elif s_type is SettingsType.NEUTRINO_MP:
write_neutrino_bouquets(path, bouquets)
diff --git a/app/eparser/iptv.py b/app/eparser/iptv.py
index b47a8967..55451b5e 100644
--- a/app/eparser/iptv.py
+++ b/app/eparser/iptv.py
@@ -3,7 +3,7 @@ import re
import urllib.request
from enum import Enum
-from app.settings import Profile
+from app.settings import SettingsType
from app.ui.uicommons import IPTV_ICON
from .ecommons import BqServiceType, Service
@@ -20,18 +20,18 @@ class StreamType(Enum):
NONE_REC_2 = "5002"
-def parse_m3u(path, profile):
+def parse_m3u(path, s_type):
with open(path) as file:
aggr = [None] * 10
services = []
groups = set()
counter = 0
name = None
-
+
for line in file.readlines():
if line.startswith("#EXTINF"):
name = line[1 + line.index(","):].strip()
- elif line.startswith("#EXTGRP") and profile is Profile.ENIGMA_2:
+ elif line.startswith("#EXTGRP") and s_type is SettingsType.ENIGMA_2:
grp_name = line.strip("#EXTGRP:").strip()
if grp_name not in groups:
groups.add(grp_name)
@@ -41,7 +41,7 @@ def parse_m3u(path, profile):
services.append(mr)
elif not line.startswith("#"):
url = line.strip()
- fav_id = get_fav_id(url, name, profile)
+ fav_id = get_fav_id(url, name, s_type)
if name and url:
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None)
services.append(srv)
@@ -49,8 +49,8 @@ def parse_m3u(path, profile):
return services
-def export_to_m3u(path, bouquet, profile):
- pattern = re.compile(".*:(http.*):.*") if profile is Profile.ENIGMA_2 else re.compile("(http.*?)::::.*")
+def export_to_m3u(path, bouquet, s_type):
+ pattern = re.compile(".*:(http.*):.*") if s_type is SettingsType.ENIGMA_2 else re.compile("(http.*?)::::.*")
lines = ["#EXTM3U\n"]
current_grp = None
@@ -72,13 +72,13 @@ def export_to_m3u(path, bouquet, profile):
file.writelines(lines)
-def get_fav_id(url, service_name, profile):
+def get_fav_id(url, service_name, s_type):
""" Returns fav id depending on the profile. """
- if profile is Profile.ENIGMA_2:
+ if s_type is SettingsType.ENIGMA_2:
url = urllib.request.quote(url)
stream_type = StreamType.NONE_TS.value
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, 1, 0, 0, 0, 0, url, service_name, service_name, None)
- elif profile is Profile.NEUTRINO_MP:
+ elif s_type is SettingsType.NEUTRINO_MP:
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)
diff --git a/app/settings.py b/app/settings.py
index da318f37..23cfc58f 100644
--- a/app/settings.py
+++ b/app/settings.py
@@ -1,9 +1,11 @@
+import copy
import json
+import locale
import os
+from enum import Enum, IntEnum
+from pathlib import Path
from pprint import pformat
from textwrap import dedent
-from enum import Enum
-from pathlib import Path
HOME_PATH = str(Path.home())
CONFIG_PATH = HOME_PATH + "/.config/demon-editor/"
@@ -11,19 +13,94 @@ CONFIG_FILE = CONFIG_PATH + "config.json"
DATA_PATH = HOME_PATH + "/data/"
-class Profile(Enum):
+class Defaults(Enum):
+ """ Default program settings """
+ DEFAULT_PROFILE = "default"
+ BACKUP_BEFORE_DOWNLOADING = True
+ BACKUP_BEFORE_SAVE = True
+ V5_SUPPORT = False
+ HTTP_API_SUPPORT = False
+ ENABLE_YT_DL = False
+ ENABLE_SEND_TO = False
+ USE_COLORS = True
+ NEW_COLOR = "rgb(255,230,204)"
+ EXTRA_COLOR = "rgb(179,230,204)"
+ FAV_CLICK_MODE = 0
+
+
+def get_default_settings(profile_name="default"):
+ def_settings = SettingsType.ENIGMA_2.get_default_settings()
+ set_local_paths(def_settings, profile_name)
+
+ return {
+ "version": 1,
+ "default_profile": Defaults.DEFAULT_PROFILE.value,
+ "profiles": {profile_name: def_settings},
+ "v5_support": Defaults.V5_SUPPORT.value,
+ "http_api_support": Defaults.HTTP_API_SUPPORT.value,
+ "enable_yt_dl": Defaults.ENABLE_YT_DL.value,
+ "enable_send_to": Defaults.ENABLE_SEND_TO.value,
+ "use_colors": Defaults.USE_COLORS.value,
+ "new_color": Defaults.NEW_COLOR.value,
+ "extra_color": Defaults.EXTRA_COLOR.value,
+ "fav_click_mode": Defaults.FAV_CLICK_MODE.value
+ }
+
+
+def set_local_paths(settings, profile_name):
+ settings["data_local_path"] = "{}{}/".format(settings["data_local_path"], profile_name)
+ settings["picons_local_path"] = "{}{}/".format(settings["picons_local_path"], profile_name)
+ settings["backup_local_path"] = "{}{}/".format(settings["backup_local_path"], profile_name)
+
+
+class SettingsType(IntEnum):
""" Profiles for settings """
- ENIGMA_2 = "0"
- NEUTRINO_MP = "1"
+ ENIGMA_2 = 0
+ NEUTRINO_MP = 1
+
+ def get_default_settings(self):
+ """ Returns default settings for current type """
+ if self is self.ENIGMA_2:
+ return {"setting_type": self.value,
+ "host": "127.0.0.1", "port": "21", "user": "root", "password": "root", "timeout": 5,
+ "http_user": "root", "http_password": "", "http_port": "80",
+ "http_timeout": 5, "http_use_ssl": False,
+ "telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 5,
+ "services_path": "/etc/enigma2/", "user_bouquet_path": "/etc/enigma2/",
+ "satellites_xml_path": "/etc/tuxbox/", "data_local_path": DATA_PATH + "enigma2/",
+ "picons_path": "/usr/share/enigma2/picon/",
+ "picons_local_path": DATA_PATH + "enigma2/picons/",
+ "backup_local_path": DATA_PATH + "enigma2/backup/"}
+ elif self is self.NEUTRINO_MP:
+ return {"setting_type": self,
+ "host": "127.0.0.1", "port": "21", "user": "root", "password": "root", "timeout": 5,
+ "http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2, "http_use_ssl": False,
+ "telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 1,
+ "services_path": "/var/tuxbox/config/zapit/", "user_bouquet_path": "/var/tuxbox/config/zapit/",
+ "satellites_xml_path": "/var/tuxbox/config/", "data_local_path": DATA_PATH + "neutrino/",
+ "picons_path": "/usr/share/tuxbox/neutrino/icons/logo/",
+ "picons_local_path": DATA_PATH + "neutrino/picons/",
+ "backup_local_path": DATA_PATH + "neutrino/backup/"}
+
+
+class SettingsException(Exception):
+ pass
class Settings:
__INSTANCE = None
+ __VERSION = 1
- def __init__(self):
- self._config = get_config()
- self._current_profile = Profile(self._config.get("profile"))
- self._current_profile_options = self._config.get(self._current_profile.value)
+ def __init__(self, ext_settings=None):
+ settings = ext_settings or get_settings()
+
+ if self.__VERSION > settings.get("version", 0):
+ raise SettingsException("Outdated version of the settings format!")
+
+ self._settings = settings
+ self._current_profile = self._settings.get("default_profile", "default")
+ self._profiles = self._settings.get("profiles", {"default": SettingsType.ENIGMA_2.get_default_settings()})
+ self._cp_settings = self._profiles.get(self._current_profile) # Current profile settings
def __str__(self):
return dedent(""" Current profile: {}
@@ -32,8 +109,8 @@ class Settings:
Full config:
{}
""").format(self._current_profile,
- pformat(self._current_profile_options),
- pformat(self._config))
+ pformat(self._cp_settings),
+ pformat(self._settings))
@classmethod
def get_instance(cls):
@@ -42,325 +119,350 @@ class Settings:
return cls.__INSTANCE
def save(self):
- write_config(self._config)
+ write_settings(self._settings)
def reset(self, force_write=False):
- def_settings = get_default_settings()
- for p in Profile:
- current = self._config.get(p.value)
- default = def_settings.get(p.value)
- for k in default:
- current[k] = default.get(k)
+ for k, v in self.setting_type.get_default_settings().items():
+ self._cp_settings[k] = v
+ set_local_paths(self._cp_settings, self._current_profile)
if force_write:
- write_config(get_default_settings())
+ self.save()
+
+ @staticmethod
+ def reset_to_default():
+ write_settings(get_default_settings())
+
+ def get_default(self, p_name):
+ """ Returns default value for current settings type """
+ return self.setting_type.get_default_settings().get(p_name)
def add(self, name, value):
""" Adds extra options """
- self._config[name] = value
+ self._settings[name] = value
def get(self, name):
- """ Returns extra options """
- return self._config.get(name, None)
-
- def get_default(self, name):
- """ Returns default value of the option """
- return get_default_settings().get(self._current_profile.value).get(name)
+ """ Returns extra options or None """
+ return self._settings.get(name, None)
@property
- def presets(self):
- raise NotImplementedError
+ def settings(self):
+ """ Returns copy of the current settings! """
+ return copy.deepcopy(self._settings)
- @presets.setter
- def presets(self, name):
- raise NotImplementedError
+ @settings.setter
+ def settings(self, value):
+ """ Sets copy of the settings! """
+ self._settings = copy.deepcopy(value)
@property
- def profile(self):
+ def current_profile(self):
return self._current_profile
- @profile.setter
- def profile(self, prf):
- self._current_profile = prf
- self._config["profile"] = prf.value
- self._current_profile_options = self._config.get(prf.value)
+ @current_profile.setter
+ def current_profile(self, value):
+ self._current_profile = value
+ self._cp_settings = self._profiles.get(self._current_profile)
+
+ @property
+ def default_profile(self):
+ return self._settings.get("default_profile", "default")
+
+ @default_profile.setter
+ def default_profile(self, value):
+ self._settings["default_profile"] = value
+
+ @property
+ def profiles(self):
+ return self._profiles
+
+ @profiles.setter
+ def profiles(self, ps):
+ self._profiles = ps
+ self._settings["profiles"] = self._profiles
+
+ @property
+ def setting_type(self):
+ return SettingsType(self._cp_settings.get("setting_type", SettingsType.ENIGMA_2.value))
+
+ @setting_type.setter
+ def setting_type(self, s_type):
+ self._cp_settings["setting_type"] = s_type.value
+
+ @property
+ def language(self):
+ return self._settings.get("language", locale.getlocale()[0] or "en_US")
+
+ @language.setter
+ def language(self, value):
+ self._settings["language"] = value
+
+ @property
+ def load_last_config(self):
+ return self._settings.get("load_last_config", False)
+
+ @load_last_config.setter
+ def load_last_config(self, value):
+ self._settings["load_last_config"] = value
@property
def host(self):
- return self._current_profile_options.get("host", self.get_default("host"))
+ return self._cp_settings.get("host", self.get_default("host"))
@host.setter
def host(self, value):
- self._current_profile_options["host"] = value
+ self._cp_settings["host"] = value
@property
def port(self):
- return self._current_profile_options.get("port", self.get_default("port"))
+ return self._cp_settings.get("port", self.get_default("port"))
@port.setter
def port(self, value):
- self._current_profile_options["port"] = value
+ self._cp_settings["port"] = value
@property
def user(self):
- return self._current_profile_options.get("user", self.get_default("user"))
+ return self._cp_settings.get("user", self.get_default("user"))
@user.setter
def user(self, value):
- self._current_profile_options["user"] = value
+ self._cp_settings["user"] = value
@property
def password(self):
- return self._current_profile_options.get("password", self.get_default("password"))
+ return self._cp_settings.get("password", self.get_default("password"))
@password.setter
def password(self, value):
- self._current_profile_options["password"] = value
+ self._cp_settings["password"] = value
@property
def http_user(self):
- return self._current_profile_options.get("http_user", self.get_default("http_user"))
+ return self._cp_settings.get("http_user", self.get_default("http_user"))
@http_user.setter
def http_user(self, value):
- self._current_profile_options["http_user"] = value
+ self._cp_settings["http_user"] = value
@property
def http_password(self):
- return self._current_profile_options.get("http_password", self.get_default("http_password"))
+ return self._cp_settings.get("http_password", self.get_default("http_password"))
@http_password.setter
def http_password(self, value):
- self._current_profile_options["http_password"] = value
+ self._cp_settings["http_password"] = value
@property
def http_port(self):
- return self._current_profile_options.get("http_port", self.get_default("http_port"))
+ return self._cp_settings.get("http_port", self.get_default("http_port"))
@http_port.setter
def http_port(self, value):
- self._current_profile_options["http_port"] = value
+ self._cp_settings["http_port"] = value
@property
def http_timeout(self):
- return self._current_profile_options.get("http_timeout", self.get_default("http_timeout"))
+ return self._cp_settings.get("http_timeout", self.get_default("http_timeout"))
@http_timeout.setter
def http_timeout(self, value):
- self._current_profile_options["http_timeout"] = value
+ self._cp_settings["http_timeout"] = value
+
+ @property
+ def http_use_ssl(self):
+ return self._cp_settings.get("http_use_ssl", self.get_default("http_use_ssl"))
+
+ @http_use_ssl.setter
+ def http_use_ssl(self, value):
+ self._cp_settings["http_use_ssl"] = value
@property
def telnet_user(self):
- return self._current_profile_options.get("telnet_user", self.get_default("telnet_user"))
+ return self._cp_settings.get("telnet_user", self.get_default("telnet_user"))
@telnet_user.setter
def telnet_user(self, value):
- self._current_profile_options["telnet_user"] = value
+ self._cp_settings["telnet_user"] = value
@property
def telnet_password(self):
- return self._current_profile_options.get("telnet_password", self.get_default("telnet_password"))
+ return self._cp_settings.get("telnet_password", self.get_default("telnet_password"))
@telnet_password.setter
def telnet_password(self, value):
- self._current_profile_options["telnet_password"] = value
+ self._cp_settings["telnet_password"] = value
@property
def telnet_port(self):
- return self._current_profile_options.get("telnet_port", self.get_default("telnet_port"))
+ return self._cp_settings.get("telnet_port", self.get_default("telnet_port"))
@telnet_port.setter
def telnet_port(self, value):
- self._current_profile_options["telnet_port"] = value
+ self._cp_settings["telnet_port"] = value
@property
def telnet_timeout(self):
- return self._current_profile_options.get("telnet_timeout", self.get_default("telnet_timeout"))
+ return self._cp_settings.get("telnet_timeout", self.get_default("telnet_timeout"))
@telnet_timeout.setter
def telnet_timeout(self, value):
- self._current_profile_options["telnet_timeout"] = value
+ self._cp_settings["telnet_timeout"] = value
@property
def services_path(self):
- return self._current_profile_options.get("services_path", self.get_default("services_path"))
+ return self._cp_settings.get("services_path", self.get_default("services_path"))
@services_path.setter
def services_path(self, value):
- self._current_profile_options["services_path"] = value
+ self._cp_settings["services_path"] = value
@property
def user_bouquet_path(self):
- return self._current_profile_options.get("user_bouquet_path", self.get_default("user_bouquet_path"))
+ return self._cp_settings.get("user_bouquet_path", self.get_default("user_bouquet_path"))
@user_bouquet_path.setter
def user_bouquet_path(self, value):
- self._current_profile_options["user_bouquet_path"] = value
+ self._cp_settings["user_bouquet_path"] = value
@property
def satellites_xml_path(self):
- return self._current_profile_options.get("satellites_xml_path", self.get_default("satellites_xml_path"))
+ return self._cp_settings.get("satellites_xml_path", self.get_default("satellites_xml_path"))
@satellites_xml_path.setter
def satellites_xml_path(self, value):
- self._current_profile_options["satellites_xml_path"] = value
+ self._cp_settings["satellites_xml_path"] = value
@property
- def data_dir_path(self):
- return self._current_profile_options.get("data_dir_path", self.get_default("data_dir_path"))
+ def data_local_path(self):
+ return self._cp_settings.get("data_local_path", self.get_default("data_local_path"))
- @data_dir_path.setter
- def data_dir_path(self, value):
- self._current_profile_options["data_dir_path"] = value
+ @data_local_path.setter
+ def data_local_path(self, value):
+ self._cp_settings["data_local_path"] = value
@property
def picons_path(self):
- return self._current_profile_options.get("picons_path", self.get_default("picons_path"))
+ return self._cp_settings.get("picons_path", self.get_default("picons_path"))
@picons_path.setter
def picons_path(self, value):
- self._current_profile_options["picons_path"] = value
+ self._cp_settings["picons_path"] = value
@property
- def picons_dir_path(self):
- return self._current_profile_options.get("picons_dir_path", self.get_default("picons_dir_path"))
+ def picons_local_path(self):
+ return self._cp_settings.get("picons_local_path", self.get_default("picons_local_path"))
- @picons_dir_path.setter
- def picons_dir_path(self, value):
- self._current_profile_options["picons_dir_path"] = value
+ @picons_local_path.setter
+ def picons_local_path(self, value):
+ self._cp_settings["picons_local_path"] = value
@property
- def backup_dir_path(self):
- return self._current_profile_options.get("backup_dir_path", self.get_default("backup_dir_path"))
+ def backup_local_path(self):
+ return self._cp_settings.get("backup_local_path", self.get_default("backup_local_path"))
- @backup_dir_path.setter
- def backup_dir_path(self, value):
- self._current_profile_options["backup_dir_path"] = value
+ @backup_local_path.setter
+ def backup_local_path(self, value):
+ self._cp_settings["backup_local_path"] = value
+
+ # ***** Program settings *****
@property
def backup_before_save(self):
- return self._current_profile_options.get("backup_before_save", self.get_default("backup_before_save"))
+ return self._settings.get("backup_before_save", Defaults.BACKUP_BEFORE_SAVE.value)
@backup_before_save.setter
def backup_before_save(self, value):
- self._current_profile_options["backup_before_save"] = value
+ self._settings["backup_before_save"] = value
@property
def backup_before_downloading(self):
- return self._current_profile_options.get("backup_before_downloading",
- self.get_default("backup_before_downloading"))
+ return self._settings.get("backup_before_downloading", Defaults.BACKUP_BEFORE_DOWNLOADING.value)
@backup_before_downloading.setter
def backup_before_downloading(self, value):
- self._current_profile_options["backup_before_downloading"] = value
+ self._settings["backup_before_downloading"] = value
@property
def v5_support(self):
- return self._current_profile_options.get("v5_support", self.get_default("v5_support"))
+ return self._settings.get("v5_support", Defaults.V5_SUPPORT.value)
@v5_support.setter
def v5_support(self, value):
- self._current_profile_options["v5_support"] = value
+ self._settings["v5_support"] = value
@property
def http_api_support(self):
- return self._current_profile_options.get("http_api_support", self.get_default("http_api_support"))
+ return self._settings.get("http_api_support", Defaults.HTTP_API_SUPPORT.value)
@http_api_support.setter
def http_api_support(self, value):
- self._current_profile_options["http_api_support"] = value
+ self._settings["http_api_support"] = value
@property
def enable_yt_dl(self):
- return self._current_profile_options.get("enable_yt_dl", self.get_default("enable_yt_dl"))
+ return self._settings.get("enable_yt_dl", Defaults.ENABLE_YT_DL.value)
@enable_yt_dl.setter
def enable_yt_dl(self, value):
- self._current_profile_options["enable_yt_dl"] = value
+ self._settings["enable_yt_dl"] = value
@property
def enable_send_to(self):
- return self._current_profile_options.get("enable_send_to", self.get_default("enable_send_to"))
+ return self._settings.get("enable_send_to", Defaults.ENABLE_SEND_TO.value)
@enable_send_to.setter
def enable_send_to(self, value):
- self._current_profile_options["enable_send_to"] = value
+ self._settings["enable_send_to"] = value
@property
def use_colors(self):
- return self._current_profile_options.get("use_colors", self.get_default("use_colors"))
+ return self._settings.get("use_colors", Defaults.USE_COLORS.value)
@use_colors.setter
def use_colors(self, value):
- self._current_profile_options["use_colors"] = value
+ self._settings["use_colors"] = value
@property
def new_color(self):
- return self._current_profile_options.get("new_color", self.get_default("new_color"))
+ return self._settings.get("new_color", Defaults.NEW_COLOR.value)
@new_color.setter
def new_color(self, value):
- self._current_profile_options["new_color"] = value
+ self._settings["new_color"] = value
@property
def extra_color(self):
- return self._current_profile_options.get("extra_color", self.get_default("extra_color"))
+ return self._settings.get("extra_color", Defaults.EXTRA_COLOR.value)
@extra_color.setter
def extra_color(self, value):
- self._current_profile_options["extra_color"] = value
+ self._settings["extra_color"] = value
@property
def fav_click_mode(self):
- return self._current_profile_options.get("fav_click_mode", self.get_default("fav_click_mode"))
+ return self._settings.get("fav_click_mode", Defaults.FAV_CLICK_MODE.value)
@fav_click_mode.setter
def fav_click_mode(self, value):
- self._current_profile_options["fav_click_mode"] = value
+ self._settings["fav_click_mode"] = value
-def get_config():
+def get_settings():
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) # create dir if not exist
os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
- write_config(get_default_settings())
+ write_settings(get_default_settings())
with open(CONFIG_FILE, "r") as config_file:
return json.load(config_file)
-def write_config(config):
+def write_settings(config):
with open(CONFIG_FILE, "w") as config_file:
json.dump(config, config_file, indent=" ")
-def get_default_settings():
- return {
- Profile.ENIGMA_2.value: {
- "host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
- "http_user": "root", "http_password": "", "http_port": "80", "http_timeout": 5,
- "telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 5,
- "services_path": "/etc/enigma2/", "user_bouquet_path": "/etc/enigma2/",
- "satellites_xml_path": "/etc/tuxbox/", "data_dir_path": DATA_PATH + "enigma2/",
- "picons_path": "/usr/share/enigma2/picon", "picons_dir_path": DATA_PATH + "enigma2/picons/",
- "backup_dir_path": DATA_PATH + "enigma2/backup/",
- "backup_before_save": True, "backup_before_downloading": True,
- "v5_support": False, "http_api_support": False, "enable_yt_dl": False, "enable_send_to": False,
- "use_colors": True, "new_color": "rgb(255,230,204)", "extra_color": "rgb(179,230,204)",
- "fav_click_mode": 0},
- Profile.NEUTRINO_MP.value: {
- "host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
- "http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2,
- "telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 1,
- "services_path": "/var/tuxbox/config/zapit/", "user_bouquet_path": "/var/tuxbox/config/zapit/",
- "satellites_xml_path": "/var/tuxbox/config/", "data_dir_path": DATA_PATH + "neutrino/",
- "picons_path": "/usr/share/tuxbox/neutrino/icons/logo/", "picons_dir_path": DATA_PATH + "neutrino/picons/",
- "backup_dir_path": DATA_PATH + "neutrino/backup/",
- "backup_before_save": True, "backup_before_downloading": True,
- "fav_click_mode": 0},
- "profile": Profile.ENIGMA_2.value}
-
-
if __name__ == "__main__":
pass
diff --git a/app/tools/media.py b/app/tools/media.py
index 3479b29c..9ef94dca 100644
--- a/app/tools/media.py
+++ b/app/tools/media.py
@@ -1,11 +1,12 @@
import sys
+
from app.commons import run_task, log
class Player:
__VLC_INSTANCE = None
- def __init__(self, rewind_callback, position_callback):
+ def __init__(self, rewind_callback, position_callback, error_callback, playing_callback):
try:
from app.tools import vlc
from app.tools.vlc import EventType
@@ -28,10 +29,19 @@ class Player:
lambda et, p: position_callback(p.get_time()),
self._player)
+ if error_callback:
+ ev_mgr.event_attach(EventType.MediaPlayerEncounteredError,
+ lambda et, p: error_callback(),
+ self._player)
+ if playing_callback:
+ ev_mgr.event_attach(EventType.MediaPlayerPlaying,
+ lambda et, p: playing_callback(),
+ self._player)
+
@classmethod
- def get_instance(cls, rewind_callback=None, position_callback=None):
+ def get_instance(cls, rewind_callback=None, position_callback=None, error_callback=None, playing_callback=None):
if not cls.__VLC_INSTANCE:
- cls.__VLC_INSTANCE = Player(rewind_callback, position_callback)
+ cls.__VLC_INSTANCE = Player(rewind_callback, position_callback, error_callback, playing_callback)
return cls.__VLC_INSTANCE
@run_task
diff --git a/app/tools/picons.py b/app/tools/picons.py
index 87f3d5a4..9e4419bf 100644
--- a/app/tools/picons.py
+++ b/app/tools/picons.py
@@ -7,7 +7,7 @@ from collections import namedtuple
from html.parser import HTMLParser
from app.commons import run_task
-from app.settings import Profile
+from app.settings import SettingsType
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
_NEUTRINO_PICON_KEY = "{:x}{:04x}{:04x}.png"
@@ -79,7 +79,7 @@ class PiconsParser(HTMLParser):
pass
@staticmethod
- def parse(open_path, picons_path, tmp_path, provider, picon_ids, profile=Profile.ENIGMA_2):
+ def parse(open_path, picons_path, tmp_path, provider, picon_ids, s_type=SettingsType.ENIGMA_2):
with open(open_path, encoding="utf-8", errors="replace") as f:
on_id, pos, ssid, single = provider.on_id, provider.pos, provider.ssid, provider.single
neg_pos = pos.endswith("W")
@@ -100,7 +100,7 @@ class PiconsParser(HTMLParser):
namespace = "{:X}{:X}".format(int(pos), int(freq))
else:
namespace = "{:X}0000".format(int(pos))
- name = PiconsParser.format(ssid if single else p.ssid, on_id, namespace, picon_ids, profile)
+ name = PiconsParser.format(ssid if single else p.ssid, on_id, namespace, picon_ids, s_type)
p_name = picons_path + (name if name else os.path.basename(p.ref))
shutil.copyfile(tmp_path + "www.lyngsat.com/" + p.ref.lstrip("."), p_name)
except (TypeError, ValueError) as e:
@@ -109,10 +109,10 @@ class PiconsParser(HTMLParser):
print(msg)
@staticmethod
- def format(ssid, on_id, namespace, picon_ids, profile: Profile):
- if profile is Profile.ENIGMA_2:
+ def format(ssid, on_id, namespace, picon_ids, s_type):
+ if s_type is SettingsType.ENIGMA_2:
return picon_ids.get(_ENIGMA2_PICON_KEY.format(int(ssid), int(on_id), namespace), None)
- elif profile is Profile.NEUTRINO_MP:
+ elif s_type is SettingsType.NEUTRINO_MP:
tr_id = int(ssid[:-2] if len(ssid) < 4 else ssid[:2])
return _NEUTRINO_PICON_KEY.format(tr_id, int(on_id), int(ssid))
else:
@@ -249,12 +249,12 @@ def parse_providers(open_path):
@run_task
-def convert_to(src_path, dest_path, profile, callback, done_callback):
+def convert_to(src_path, dest_path, s_type, callback, done_callback):
""" Converts names format of picons.
Copies resulting files from src to dest and writes state to callback.
"""
- pattern = "/*_0_0_0.png" if profile is Profile.ENIGMA_2 else "/*.png"
+ pattern = "/*_0_0_0.png" if s_type is SettingsType.ENIGMA_2 else "/*.png"
for file in glob.glob(src_path + pattern):
base_name = os.path.basename(file)
pic_data = base_name.rstrip(".png").split("_")
diff --git a/app/ui/backup.py b/app/ui/backup.py
index 7aca7613..5fb857b4 100644
--- a/app/ui/backup.py
+++ b/app/ui/backup.py
@@ -7,7 +7,7 @@ from datetime import datetime
from enum import Enum
from app.commons import run_idle
-from app.settings import Profile
+from app.settings import SettingsType
from app.ui.dialogs import show_dialog, DialogType
from app.ui.main_helper import append_text_to_tview
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey
@@ -36,9 +36,9 @@ class BackupDialog:
builder.connect_signals(handlers)
self._settings = settings
- self._profile = settings.profile
- self._data_path = self._settings.data_dir_path
- self._backup_path = self._settings.backup_dir_path or self._data_path + "backup/"
+ self._s_type = settings.setting_type
+ self._data_path = self._settings.data_local_path
+ self._backup_path = self._settings.backup_local_path or self._data_path + "backup/"
self._open_data_callback = callback
self._dialog_window = builder.get_object("dialog_window")
self._dialog_window.set_transient_for(transient)
@@ -152,7 +152,7 @@ class BackupDialog:
shutil.unpack_archive(full_file_name, self._data_path)
elif restore_type is RestoreType.BOUQUETS:
tmp_dir = tempfile.gettempdir() + "/" + file_name
- cond = (".tv", ".radio") if self._profile is Profile.ENIGMA_2 else "bouquets.xml"
+ cond = (".tv", ".radio") if self._s_type is SettingsType.ENIGMA_2 else "bouquets.xml"
shutil.unpack_archive(full_file_name, tmp_dir)
for file in filter(lambda f: f.endswith(cond), os.listdir(self._data_path)):
os.remove(os.path.join(self._data_path, file))
@@ -192,6 +192,7 @@ def backup_data(path, backup_path, move=True):
"""
backup_path = "{}{}/".format(backup_path, datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
+ os.makedirs(os.path.dirname(path), exist_ok=True)
# backup files in data dir(skipping dirs and satellites.xml)
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
src, dst = os.path.join(path, file), backup_path + file
diff --git a/app/ui/dialogs.glade b/app/ui/dialogs.glade
index a2d3c1c1..24909e47 100644
--- a/app/ui/dialogs.glade
+++ b/app/ui/dialogs.glade
@@ -41,7 +41,7 @@ Author: Dmitriy Yefremov
normal
DemonEditor
0.4.7 Pre-alpha
- 2018-2019 Dmitriy Yefremov
+ 2018-2020 Dmitriy Yefremov
Enigma2 channel and satellites list editor for MacOS.
(Experimental)
@@ -51,7 +51,8 @@ Author: Dmitriy Yefremov
Dmitriy Yefremov
translator-credits
- accessories-text-editor
+ Program logo: <a href="http://ihad.tv"> mfgeg</a>
+ demon-editor
True
mit-x11
diff --git a/app/ui/dialogs.py b/app/ui/dialogs.py
index 20a996e3..1cc238a6 100644
--- a/app/ui/dialogs.py
+++ b/app/ui/dialogs.py
@@ -104,7 +104,7 @@ def get_file_chooser_dialog(transient, text, settings, action_type, file_filter)
if file_filter is not None:
dialog.add_filter(file_filter)
- path = settings.data_dir_path
+ path = settings.data_local_path
dialog.set_current_folder(path)
response = dialog.run()
diff --git a/app/ui/download_dialog.glade b/app/ui/download_dialog.glade
index a441afbd..00116fbf 100644
--- a/app/ui/download_dialog.glade
+++ b/app/ui/download_dialog.glade
@@ -26,7 +26,7 @@ THE SOFTWARE.
Author: Dmitriy Yefremov
-->
-
+
@@ -47,6 +47,7 @@ Author: Dmitriy Yefremov
-
-
-
640
+ 480
False
center
- accessories-text-editor
+ demon-editor
center
DemonEditor
@@ -963,6 +964,9 @@ Author: Dmitriy Yefremov
True
True
+ False
+ 5
+ 5
player_scale_adjustment
False
0
@@ -2207,7 +2211,6 @@ Author: Dmitriy Yefremov
True
False
- 2
False
@@ -2240,7 +2243,7 @@ Author: Dmitriy Yefremov
True
False
- gtk-edit
+ demon-editor
6
@@ -2289,14 +2292,13 @@ Author: Dmitriy Yefremov
- 30
False
False
start
10
- 2
+ 5
True
@@ -2333,62 +2335,56 @@ Author: Dmitriy Yefremov
-
+
True
False
+ False
+ Current IP:
center
- 2
-
-
- True
+ center
+ 1
+ 1
+ 0
+ True
+
+
False
- gtk-connect
+ True
+ baseline
+ baseline
+ 1
+ 1
+ False
+ False
+ 9
+ True
+ False
+ gtk-connect
+
-
- False
- True
- 0
-
-
-
-
- True
- False
- center
- Current IP:
-
-
-
-
-
- False
- True
- 1
-
-
-
-
- True
- False
- 127.0.0.1
-
-
-
-
-
- False
- True
- 2
-
- True
+ False
True
- 5
3
+
+
+ False
+ No connection to the receiver
+ 10
+ 10
+ network-offline
+
+
+ False
+ True
+ end
+ 2
+
+
False
@@ -2477,21 +2473,6 @@ Author: Dmitriy Yefremov
2
-
-
- False
- No connection to the receiver
- 10
- 10
- network-offline
-
-
- False
- True
- end
- 3
-
-
diff --git a/app/ui/picons_downloader.py b/app/ui/picons_downloader.py
index 0ef967f0..6a343d61 100644
--- a/app/ui/picons_downloader.py
+++ b/app/ui/picons_downloader.py
@@ -9,7 +9,7 @@ from gi.repository import GLib, GdkPixbuf
from app.commons import run_idle, run_task
from app.connections import upload_data, DownloadType
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to
-from app.settings import Profile
+from app.settings import SettingsType
from app.tools.satellites import SatellitesParser, SatelliteSource
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, TV_ICON
from .dialogs import show_dialog, DialogType, get_message
@@ -86,13 +86,13 @@ class PiconsDialog:
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
self._settings = settings
- self._profile = settings.profile
+ self._s_type = settings.setting_type
self._ip_entry.set_text(self._settings.host)
self._picons_entry.set_text(self._settings.picons_path)
- self._picons_path = self._settings.picons_dir_path
+ self._picons_path = self._settings.picons_local_path
self._picons_dir_entry.set_text(self._picons_path)
- if not len(self._picon_ids) and self._profile is Profile.ENIGMA_2:
+ if not len(self._picon_ids) and self._s_type is SettingsType.ENIGMA_2:
message = get_message("To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window.")
self.show_info_message(message, Gtk.MessageType.WARNING)
@@ -342,7 +342,7 @@ class PiconsDialog:
self._expander.set_expanded(True)
convert_to(src_path=picons_path,
dest_path=save_path,
- profile=Profile.ENIGMA_2,
+ s_type=SettingsType.ENIGMA_2,
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
@@ -362,10 +362,10 @@ class PiconsDialog:
show_dialog(dialog_type, self._dialog, message)
def get_picons_format(self):
- picon_format = Profile.ENIGMA_2
+ picon_format = SettingsType.ENIGMA_2
if self._neutrino_mp_radio_button.get_active():
- picon_format = Profile.NEUTRINO_MP
+ picon_format = SettingsType.NEUTRINO_MP
return picon_format
diff --git a/app/ui/satellites_dialog.py b/app/ui/satellites_dialog.py
index 80fc0bc0..5da0656e 100644
--- a/app/ui/satellites_dialog.py
+++ b/app/ui/satellites_dialog.py
@@ -25,7 +25,7 @@ class SatellitesDialog:
_aggr = [None for x in range(9)] # aggregate
def __init__(self, transient, settings):
- self._data_path = settings.data_dir_path + "satellites.xml"
+ self._data_path = settings.data_local_path + "satellites.xml"
self._settings = settings
handlers = {"on_open": self.on_open,
diff --git a/app/ui/service_details_dialog.py b/app/ui/service_details_dialog.py
index 0d2cffa0..59ebaedb 100644
--- a/app/ui/service_details_dialog.py
+++ b/app/ui/service_details_dialog.py
@@ -6,7 +6,7 @@ from app.eparser import Service
from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, \
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE, T_MODULATION, C_MODULATION, TrType, \
SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, HIERARCHY, T_FEC
-from app.settings import Profile
+from app.settings import SettingsType
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON, Column, IS_GNOME_SESSION
from .dialogs import show_dialog, DialogType, Action, get_dialogs_string
from .main_helper import get_base_model
@@ -52,10 +52,10 @@ class ServiceDetailsDialog:
self._dialog = builder.get_object("service_details_dialog")
self._dialog.set_transient_for(transient)
- self._profile = settings.profile
+ self._s_type = settings.setting_type
self._tr_type = None
- self._satellites_xml_path = settings.data_dir_path + "satellites.xml"
- self._picons_dir_path = settings.picons_dir_path
+ self._satellites_xml_path = settings.data_local_path + "satellites.xml"
+ self._picons_dir_path = settings.picons_local_path
self._services_view = srv_view
self._fav_view = fav_view
self._action = action
@@ -69,7 +69,7 @@ class ServiceDetailsDialog:
# Patterns
self._DIGIT_PATTERN = re.compile("\\D")
self._NON_EMPTY_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
- self._CAID_PATTERN = re.compile("(?:^[\\s]*$)|(C:[0-9a-z]{4})(,C:[0-9a-z]{4})*")
+ self._CAID_PATTERN = re.compile("(?:^[\\s]*$)|(C:[0-9a-fA-F]{1,4})(,C:[0-9a-fA-F]{1,4})*")
# Buttons
self._apply_button = builder.get_object("apply_button")
self._create_button = builder.get_object("create_button")
@@ -197,7 +197,7 @@ class ServiceDetailsDialog:
self._package_entry.set_text(srv.package)
self._sid_entry.set_text(str(int(srv.ssid, 16)))
# Transponder
- if self._profile is Profile.ENIGMA_2:
+ if self._s_type is SettingsType.ENIGMA_2:
self._tr_type = TrType(srv.transponder_type)
self._freq_entry.set_text(srv.freq)
self._rate_entry.set_text(srv.rate)
@@ -211,10 +211,10 @@ class ServiceDetailsDialog:
else:
self.set_sat_positions(srv.pos)
- if self._profile is Profile.ENIGMA_2:
+ if self._s_type is SettingsType.ENIGMA_2:
self.init_enigma2_service_data(srv)
self.init_enigma2_transponder_data(srv)
- elif self._profile is Profile.NEUTRINO_MP:
+ elif self._s_type is SettingsType.NEUTRINO_MP:
self.init_neutrino_data(srv)
self.init_neutrino_ui_elements()
@@ -484,9 +484,9 @@ class ServiceDetailsDialog:
transponder=transponder)
def get_flags(self):
- if self._profile is Profile.ENIGMA_2:
+ if self._s_type is SettingsType.ENIGMA_2:
return self.get_enigma2_flags()
- elif self._profile is Profile.NEUTRINO_MP:
+ elif self._s_type is SettingsType.NEUTRINO_MP:
return self._old_service.flags_cas
def get_enigma2_flags(self):
@@ -532,12 +532,12 @@ class ServiceDetailsDialog:
net_id, tr_id = int(self._network_id_entry.get_text()), int(self._transponder_id_entry.get_text())
service_type = self._srv_type_entry.get_text()
- if self._profile is Profile.ENIGMA_2:
+ if self._s_type is SettingsType.ENIGMA_2:
namespace = int(self._namespace_entry.get_text())
data_id = self._ENIGMA2_DATA_ID.format(ssid, namespace, tr_id, net_id, service_type, 0)
fav_id = self._ENIGMA2_FAV_ID.format(ssid, tr_id, net_id, namespace)
return fav_id, data_id
- elif self._profile is Profile.NEUTRINO_MP:
+ elif self._s_type is SettingsType.NEUTRINO_MP:
fav_id = self._NEUTRINO_FAV_ID.format(tr_id, net_id, ssid)
return fav_id, self._old_service.data_id
@@ -548,7 +548,7 @@ class ServiceDetailsDialog:
fec = self._fec_combo_box.get_active_id()
system = self._sys_combo_box.get_active_id()
- if self._tr_type is TrType.Satellite or self._profile is Profile.NEUTRINO_MP:
+ if self._tr_type is TrType.Satellite or self._s_type is SettingsType.NEUTRINO_MP:
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
pol = self._pol_combo_box.get_active_id()
@@ -571,7 +571,7 @@ class ServiceDetailsDialog:
inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
srv_sys = "0" # !!!
- if self._profile is Profile.ENIGMA_2:
+ if self._s_type is SettingsType.ENIGMA_2:
dvb_s_tr = self._ENIGMA2_TRANSPONDER_DATA.format("s", freq, rate, pol, fec, sat_pos, inv, srv_sys)
if sys == "DVB-S":
return dvb_s_tr
@@ -585,7 +585,7 @@ class ServiceDetailsDialog:
st_id = self._stream_id_entry.get_text()
pls = ":{}:{}:{}".format(st_id, pls_code, pls_mode) if pls_mode and pls_code and st_id else ""
return "{}:{}:{}:{}:{}{}".format(dvb_s_tr, flag, mod, roll_off, pilot, pls)
- elif self._profile is Profile.NEUTRINO_MP:
+ elif self._s_type is SettingsType.NEUTRINO_MP:
on_id, tr_id = int(self._network_id_entry.get_text()), int(self._transponder_id_entry.get_text())
mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION) if sys == "DVB-S2" else None
srv_sys = None
@@ -682,7 +682,7 @@ class ServiceDetailsDialog:
return True
def update_reference(self, entry, event=None):
- if not self.is_data_correct() or (event is None and self._profile is Profile.NEUTRINO_MP):
+ if not self.is_data_correct() or (event is None and self._s_type is SettingsType.NEUTRINO_MP):
return
self.update_reference_entry()
@@ -691,7 +691,7 @@ class ServiceDetailsDialog:
ssid = int(self._sid_entry.get_text())
tid = int(self._transponder_id_entry.get_text())
nid = int(self._network_id_entry.get_text())
- if self._profile is Profile.ENIGMA_2:
+ if self._s_type is SettingsType.ENIGMA_2:
on_id = int(self._namespace_entry.get_text())
ref = "1:0:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0".format(srv_type, ssid, tid, nid, on_id)
self._reference_entry.set_text(ref)
diff --git a/app/ui/settings_dialog.glade b/app/ui/settings_dialog.glade
index 9f2f65e1..0a7aaf05 100644
--- a/app/ui/settings_dialog.glade
+++ b/app/ui/settings_dialog.glade
@@ -26,13 +26,88 @@ THE SOFTWARE.
Author: Dmitriy Yefremov
-->
-
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ False
+ emblem-default
+
+
1
10
@@ -47,7 +122,6 @@ Author: Dmitriy Yefremov
1
False
- False
True
center-on-parent
True
@@ -55,10 +129,13 @@ Author: Dmitriy Yefremov
True
True
center
+
@@ -98,91 +175,36 @@ Author: Dmitriy Yefremov
2
-
-
+
+
+
+ 3
+
+
+
+
+ True
+ True
+ True
+ Apply profile settings
+
+
+
+ True
+ False
+ gtk-apply
+
+
+
+
+
+ 4
+
@@ -236,7 +258,7 @@ Author: Dmitriy Yefremov
True
False
-
+
+
+ Play stream
+ True
+ True
+ False
+ Play IPTV or other stream in the program(Ctrl + P)
+ True
+ click_mode_disabled_button
3
0
+
+
+ Zap and Play
+ True
+ False
+ True
+ False
+ Switch the channel and watch in the program(Ctrl + W)
+ True
+ click_mode_stream_button
+
+
+ 2
+ 0
+
+
+
+
+ Play
+ True
+ False
+ True
+ False
+ Watch the channel in the program
+ True
+ True
+ click_mode_zap_and_play_button
+
+
+
+ 1
+ 0
+
+
@@ -1525,7 +1967,7 @@ Author: Dmitriy Yefremov
extra
Extra
- 3
+ 2
@@ -1537,7 +1979,7 @@ Author: Dmitriy Yefremov
- False
+ True
True
0
diff --git a/app/ui/settings_dialog.py b/app/ui/settings_dialog.py
index a86d7eb1..00a02485 100644
--- a/app/ui/settings_dialog.py
+++ b/app/ui/settings_dialog.py
@@ -1,10 +1,13 @@
+import os
from enum import Enum
+from pathlib import Path
from app.commons import run_task, run_idle
-from app.connections import test_telnet, test_ftp, TestException, test_http
-from app.settings import Profile
-from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, FavClickMode
-from .main_helper import update_entry_data
+from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
+from app.settings import SettingsType, Settings
+from app.ui.dialogs import show_dialog, DialogType
+from .main_helper import update_entry_data, scroll_to
+from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON
def show_settings_dialog(transient, options):
@@ -19,26 +22,42 @@ class Property(Enum):
class SettingsDialog:
- def __init__(self, transient, settings):
+ def __init__(self, transient, settings: Settings):
handlers = {"on_field_icon_press": self.on_field_icon_press,
- "on_profile_changed": self.on_profile_changed,
+ "on_settings_type_changed": self.on_settings_type_changed,
"on_reset": self.on_reset,
+ "on_response": self.on_response,
"apply_settings": self.apply_settings,
+ "on_apply_profile_settings": self.on_apply_profile_settings,
"on_connection_test": self.on_connection_test,
"on_info_bar_close": self.on_info_bar_close,
"on_set_color_switch_state": self.on_set_color_switch_state,
"on_http_mode_switch_state": self.on_http_mode_switch_state,
"on_yt_dl_switch_state": self.on_yt_dl_switch_state,
- "on_send_to_switch_state": self.on_send_to_switch_state}
+ "on_send_to_switch_state": self.on_send_to_switch_state,
+ "on_profile_add": self.on_profile_add,
+ "on_profile_edit": self.on_profile_edit,
+ "on_profile_remove": self.on_profile_remove,
+ "on_profile_deleted": self.on_profile_deleted,
+ "on_profile_inserted": self.on_profile_inserted,
+ "on_profile_edited": self.on_profile_edited,
+ "on_profile_selected": self.on_profile_selected,
+ "on_profile_set_default": self.on_profile_set_default,
+ "on_lang_changed": self.on_lang_changed,
+ "on_main_settings_visible": self.on_main_settings_visible,
+ "on_network_settings_visible": self.on_network_settings_visible,
+ "on_http_use_ssl_toggled": self.on_http_use_ssl_toggled,
+ "on_click_mode_togged": self.on_click_mode_togged,
+ "on_view_popup_menu": self.on_view_popup_menu}
builder = Gtk.Builder()
- builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "settings_dialog.glade")
builder.connect_signals(handlers)
self._dialog = builder.get_object("settings_dialog")
self._dialog.set_transient_for(transient)
self._header_bar = builder.get_object("header_bar")
+ self._main_stack = builder.get_object("main_stack")
# Network
self._host_field = builder.get_object("host_field")
self._port_field = builder.get_object("port_field")
@@ -47,6 +66,7 @@ class SettingsDialog:
self._http_login_field = builder.get_object("http_login_field")
self._http_password_field = builder.get_object("http_password_field")
self._http_port_field = builder.get_object("http_port_field")
+ self._http_use_ssl_check_button = builder.get_object("http_use_ssl_check_button")
self._telnet_login_field = builder.get_object("telnet_login_field")
self._telnet_password_field = builder.get_object("telnet_password_field")
self._telnet_port_field = builder.get_object("telnet_port_field")
@@ -64,7 +84,7 @@ class SettingsDialog:
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._test_spinner = builder.get_object("test_spinner")
- # Profile
+ # Settings type
self._enigma_radio_button = builder.get_object("enigma_radio_button")
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
self._support_ver5_switch = builder.get_object("support_ver5_switch")
@@ -77,6 +97,7 @@ class SettingsDialog:
self._set_color_switch = builder.get_object("set_color_switch")
self._new_color_button = builder.get_object("new_color_button")
self._extra_color_button = builder.get_object("extra_color_button")
+ self._load_on_startup_switch = builder.get_object("load_on_startup_switch")
# HTTP API
self._support_http_api_switch = builder.get_object("support_http_api_switch")
self._enable_y_dl_switch = builder.get_object("enable_y_dl_switch")
@@ -85,46 +106,77 @@ class SettingsDialog:
self._click_mode_stream_button = builder.get_object("click_mode_stream_button")
self._click_mode_play_button = builder.get_object("click_mode_play_button")
self._click_mode_zap_button = builder.get_object("click_mode_zap_button")
+ self._click_mode_zap_and_play_button = builder.get_object("click_mode_zap_and_play_button")
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_play_button, "sensitive")
+ self._click_mode_zap_button.bind_property("sensitive", self._click_mode_zap_and_play_button, "sensitive")
self._click_mode_zap_button.bind_property("sensitive", self._enable_send_to_switch, "sensitive")
self._enable_send_to_switch.bind_property("sensitive", builder.get_object("enable_send_to_label"), "sensitive")
self._extra_support_grid.bind_property("sensitive", builder.get_object("v5_support_grid"), "sensitive")
+ # Profiles
+ self._profile_view = builder.get_object("profile_tree_view")
+ self._profile_add_button = builder.get_object("profile_add_button")
+ self._profile_remove_button = builder.get_object("profile_remove_button")
+ self._apply_profile_button = builder.get_object("apply_profile_button")
+ self._apply_profile_button.bind_property("visible", builder.get_object("header_separator"), "visible")
+ # Language
+ self._lang_combo_box = builder.get_object("lang_combo_box")
# Settings
- self._settings = settings
- self._active_profile = settings.profile
+ self._ext_settings = settings
+ self._settings = Settings(settings.settings)
+ self._profiles = self._settings.profiles
+ self._s_type = self._settings.setting_type
self.set_settings()
- self.init_ui_elements(self._active_profile)
+ self.init_ui_elements(self._s_type)
+ self.init_profiles()
- def init_ui_elements(self, profile):
- is_enigma_profile = profile is Profile.ENIGMA_2
- self._neutrino_radio_button.set_active(profile is Profile.NEUTRINO_MP)
+ @run_idle
+ def init_ui_elements(self, s_type):
+ is_enigma_profile = s_type is SettingsType.ENIGMA_2
+ self._neutrino_radio_button.set_active(s_type is SettingsType.NEUTRINO_MP)
+ self.update_header_bar()
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(is_enigma_profile)
self._program_frame.set_sensitive(is_enigma_profile)
self._extra_support_grid.set_sensitive(is_enigma_profile)
http_active = self._support_http_api_switch.get_active()
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
+ self._lang_combo_box.set_active_id(self._settings.language)
self.on_info_bar_close() if is_enigma_profile else self.show_info_message(
"The Neutrino has only experimental support. Not all features are supported!", Gtk.MessageType.WARNING)
- def show(self):
- response = self._dialog.run()
- if response == Gtk.ResponseType.OK:
- self.apply_settings()
- self._dialog.destroy()
+ def init_profiles(self):
+ p_def = self._settings.default_profile
+ for p in self._profiles:
+ self._profile_view.get_model().append((p, DEFAULT_ICON if p == p_def else None))
+ self._profile_remove_button.set_sensitive(len(self._profile_view.get_model()) > 1)
- return response
+ def update_header_bar(self):
+ label, sep, st = self._header_bar.get_subtitle().partition(":")
+ if self._s_type is SettingsType.ENIGMA_2:
+ self._header_bar.set_subtitle("{}: {}".format(label, self._enigma_radio_button.get_label()))
+ elif self._s_type is SettingsType.NEUTRINO_MP:
+ self._header_bar.set_subtitle("{}: {}".format(label, self._neutrino_radio_button.get_label()))
+
+ def show(self):
+ self._dialog.run()
+
+ def on_response(self, dialog, resp):
+ if resp == Gtk.ResponseType.OK and not self.apply_settings():
+ return
+
+ self._dialog.destroy()
+ return resp
def on_field_icon_press(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, self._settings)
- def on_profile_changed(self, item):
- profile = Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP
- self._active_profile = profile
- self._settings.profile = profile
- self.set_settings()
+ def on_settings_type_changed(self, item):
+ profile = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
+ self._s_type = profile
+ self._settings.setting_type = profile
+ self.on_reset()
self.init_ui_elements(profile)
- def on_reset(self, item):
+ def on_reset(self, item=None):
self._settings.reset()
self.set_settings()
@@ -136,6 +188,7 @@ class SettingsDialog:
self._http_login_field.set_text(self._settings.http_user)
self._http_password_field.set_text(self._settings.http_password)
self._http_port_field.set_text(self._settings.http_port)
+ self._http_use_ssl_check_button.set_active(self._settings.http_use_ssl)
self._telnet_login_field.set_text(self._settings.telnet_user)
self._telnet_password_field.set_text(self._settings.telnet_password)
self._telnet_port_field.set_text(self._settings.telnet_port)
@@ -144,14 +197,15 @@ class SettingsDialog:
self._user_bouquet_field.set_text(self._settings.user_bouquet_path)
self._satellites_xml_field.set_text(self._settings.satellites_xml_path)
self._picons_field.set_text(self._settings.picons_path)
- self._data_dir_field.set_text(self._settings.data_dir_path)
- self._picons_dir_field.set_text(self._settings.picons_dir_path)
- self._backup_dir_field.set_text(self._settings.backup_dir_path)
+ self._data_dir_field.set_text(self._settings.data_local_path)
+ self._picons_dir_field.set_text(self._settings.picons_local_path)
+ self._backup_dir_field.set_text(self._settings.backup_local_path)
self._before_save_switch.set_active(self._settings.backup_before_save)
self._before_downloading_switch.set_active(self._settings.backup_before_downloading)
self.set_fav_click_mode(self._settings.fav_click_mode)
+ self._load_on_startup_switch.set_active(self._settings.load_last_config)
- if self._active_profile is Profile.ENIGMA_2:
+ if self._s_type is SettingsType.ENIGMA_2:
self._support_ver5_switch.set_active(self._settings.v5_support)
self._support_http_api_switch.set_active(self._settings.http_api_support)
self._enable_y_dl_switch.set_active(self._settings.enable_yt_dl)
@@ -164,9 +218,9 @@ class SettingsDialog:
self._new_color_button.set_rgba(new_rgb)
self._extra_color_button.set_rgba(extra_rgb)
- def apply_settings(self, item=None):
- self._active_profile = Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP
- self._settings.profile = self._active_profile
+ def on_apply_profile_settings(self, item):
+ self._s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
+ self._settings.setting_type = self._s_type
self._settings.host = self._host_field.get_text()
self._settings.port = self._port_field.get_text()
self._settings.user = self._login_field.get_text()
@@ -174,6 +228,7 @@ class SettingsDialog:
self._settings.http_user = self._http_login_field.get_text()
self._settings.http_password = self._http_password_field.get_text()
self._settings.http_port = self._http_port_field.get_text()
+ self._settings.http_use_ssl = self._http_use_ssl_check_button.get_active()
self._settings.telnet_user = self._telnet_login_field.get_text()
self._settings.telnet_password = self._telnet_password_field.get_text()
self._settings.telnet_port = self._telnet_port_field.get_text()
@@ -182,23 +237,33 @@ class SettingsDialog:
self._settings.user_bouquet_path = self._user_bouquet_field.get_text()
self._settings.satellites_xml_path = self._satellites_xml_field.get_text()
self._settings.picons_path = self._picons_field.get_text()
- self._settings.data_dir_path = self._data_dir_field.get_text()
- self._settings.picons_dir_path = self._picons_dir_field.get_text()
- self._settings.backup_dir_path = self._backup_dir_field.get_text()
- self._settings.backup_before_save = self._before_save_switch.get_active()
- self._settings.backup_before_downloading = self._before_downloading_switch.get_active()
- self._settings.fav_click_mode = self.get_fav_click_mode()
+ self._settings.data_local_path = self._data_dir_field.get_text()
+ self._settings.picons_local_path = self._picons_dir_field.get_text()
+ self._settings.backup_local_path = self._backup_dir_field.get_text()
- if self._active_profile is Profile.ENIGMA_2:
- self._settings.use_colors = self._set_color_switch.get_active()
- self._settings.new_color = self._new_color_button.get_rgba().to_string()
- self._settings.extra_color = self._extra_color_button.get_rgba().to_string()
- self._settings.v5_support = self._support_ver5_switch.get_active()
- self._settings.http_api_support = self._support_http_api_switch.get_active()
- self._settings.enable_yt_dl = self._enable_y_dl_switch.get_active()
- self._settings.enable_send_to = self._enable_send_to_switch.get_active()
+ def apply_settings(self, item=None):
+ if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
+ return
- self._settings.save()
+ self._ext_settings.profiles = self._settings.profiles
+ self._ext_settings.backup_before_save = self._before_save_switch.get_active()
+ self._ext_settings.backup_before_downloading = self._before_downloading_switch.get_active()
+ self._ext_settings.fav_click_mode = self.get_fav_click_mode()
+ self._ext_settings.language = self._lang_combo_box.get_active_id()
+ self._ext_settings.load_last_config = self._load_on_startup_switch.get_active()
+
+ if self._s_type is SettingsType.ENIGMA_2:
+ self._ext_settings.use_colors = self._set_color_switch.get_active()
+ self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
+ self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
+ self._ext_settings.v5_support = self._support_ver5_switch.get_active()
+ self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
+ self._ext_settings.enable_yt_dl = self._enable_y_dl_switch.get_active()
+ self._ext_settings.enable_send_to = self._enable_send_to_switch.get_active()
+
+ self._ext_settings.default_profile = list(filter(lambda r: r[1], self._profile_view.get_model()))[0][0]
+ self._ext_settings.save()
+ return True
@run_task
def on_connection_test(self, item):
@@ -216,10 +281,13 @@ class SettingsDialog:
def test_http(self):
user, password = self._http_login_field.get_text(), self._http_password_field.get_text()
host, port = self._host_field.get_text(), self._http_port_field.get_text()
+ use_ssl = self._http_use_ssl_check_button.get_active()
try:
- self.show_info_message(test_http(host, port, user, password), Gtk.MessageType.INFO)
+ self.show_info_message(test_http(host, port, user, password, use_ssl=use_ssl), Gtk.MessageType.INFO)
except TestException as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
+ except HttpApiException as e:
+ self.show_info_message(str(e), Gtk.MessageType.WARNING)
finally:
self.show_spinner(False)
@@ -263,7 +331,9 @@ class SettingsDialog:
def on_http_mode_switch_state(self, switch, state):
self._click_mode_zap_button.set_sensitive(state)
- if self._click_mode_play_button.get_active() or self._click_mode_zap_button.get_active():
+ if any((self._click_mode_play_button.get_active(),
+ self._click_mode_zap_button.get_active(),
+ self._click_mode_zap_and_play_button.get_active())):
self._click_mode_disabled_button.set_active(True)
def on_yt_dl_switch_state(self, switch, state):
@@ -272,6 +342,123 @@ class SettingsDialog:
def on_send_to_switch_state(self, switch, state):
self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
+ def on_profile_add(self, item):
+ model = self._profile_view.get_model()
+ count = 0
+ name = "profile"
+ while name in self._profiles:
+ count += 1
+ name = "profile{}".format(count)
+
+ self._profiles[name] = self._s_type.get_default_settings()
+ model.append((name, None))
+ scroll_to(len(model) - 1, self._profile_view)
+ self.on_profile_selected(self._profile_view)
+ p = name + "/"
+ self._settings.data_local_path += p
+ self._settings.picons_local_path += p
+ self._settings.backup_local_path += p
+ self.on_reset()
+
+ def on_profile_edit(self, item=None):
+ model, paths = self._profile_view.get_selection().get_selected_rows()
+ self._profile_view.set_cursor(paths, self._profile_view.get_column(0), True)
+
+ def on_profile_remove(self, item):
+ model, paths = self._profile_view.get_selection().get_selected_rows()
+ if paths:
+ row = model[paths]
+ is_default = row[1]
+ self._profiles.pop(row[0], None)
+ del model[paths]
+
+ if is_default:
+ model.set_value(model.get_iter_first(), 1, DEFAULT_ICON)
+
+ def on_profile_deleted(self, model, paths):
+ self._profile_remove_button.set_sensitive(len(model) > 1)
+
+ def on_profile_edited(self, render, path, new_value):
+ p_name = render.get_property("text")
+ p_name = self._profiles.pop(p_name, None)
+ if p_name:
+ row = self._profile_view.get_model()[path]
+ row[0] = new_value
+ self._profiles[new_value] = p_name
+
+ if p_name != new_value:
+ self.update_local_paths(new_value)
+ self.on_profile_selected(self._profile_view)
+
+ def update_local_paths(self, p_name, force_rename=False):
+ data_path = self._settings.data_local_path
+ picons_path = self._settings.picons_local_path
+ backup_path = self._settings.backup_local_path
+
+ self._settings.data_local_path = "{}/{}/".format(Path(data_path).parent, p_name)
+ self._settings.picons_local_path = "{}/{}/".format(Path(picons_path).parent, p_name)
+ self._settings.backup_local_path = "{}/{}/".format(Path(backup_path).parent, p_name)
+
+ if force_rename:
+ try:
+ if os.path.isdir(picons_path):
+ os.rename(picons_path, self._settings.picons_local_path)
+ if os.path.isdir(data_path):
+ os.rename(data_path, self._settings.data_local_path)
+ if os.path.isdir(backup_path):
+ os.rename(backup_path, self._settings.backup_local_path)
+ except OSError as e:
+ self.show_info_message(str(e), Gtk.MessageType.ERROR)
+
+ def on_profile_selected(self, view):
+ model, paths = self._profile_view.get_selection().get_selected_rows()
+ if paths:
+ profile = model.get_value(model.get_iter(paths), 0)
+ self._settings.current_profile = profile
+ if self._settings.setting_type is SettingsType.ENIGMA_2:
+ self._enigma_radio_button.activate()
+ else:
+ self._neutrino_radio_button.activate()
+ self.set_settings()
+
+ def on_profile_set_default(self, item):
+ model, paths = self._profile_view.get_selection().get_selected_rows()
+ if paths:
+ itr = model.get_iter(paths)
+ model.foreach(lambda m, p, i: model.set_value(i, 1, None))
+ model.set_value(itr, 1, DEFAULT_ICON)
+ self._settings.default_profile = model.get_value(itr, 0)
+
+ def on_profile_inserted(self, model, path, itr):
+ self._profile_remove_button.set_sensitive(len(model) > 1)
+
+ def on_lang_changed(self, box):
+ if box.get_active_id() != self._settings.language:
+ self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
+
+ def on_main_settings_visible(self, stack, param):
+ self._apply_profile_button.set_visible(stack.get_visible_child_name() == "profiles")
+
+ def on_network_settings_visible(self, stack, param):
+ self._http_use_ssl_check_button.set_visible(Property(stack.get_visible_child_name()) is Property.HTTP)
+
+ def on_http_use_ssl_toggled(self, button):
+ active = button.get_active()
+ self._settings.http_use_ssl = active
+ port = "443" if active else "80"
+ self._http_port_field.set_text(port)
+ self._settings.http_port = port
+
+ def on_click_mode_togged(self, button):
+ if self._main_stack.get_visible_child_name() != "extra":
+ return
+
+ mode = self.get_fav_click_mode()
+ if mode is FavClickMode.PLAY:
+ self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
+ else:
+ self.on_info_bar_close()
+
@run_idle
def set_fav_click_mode(self, mode):
mode = FavClickMode(mode)
@@ -279,17 +466,24 @@ class SettingsDialog:
self._click_mode_stream_button.set_active(mode is FavClickMode.STREAM)
self._click_mode_play_button.set_active(mode is FavClickMode.PLAY)
self._click_mode_zap_button.set_active(mode is FavClickMode.ZAP)
+ self._click_mode_zap_and_play_button.set_active(mode is FavClickMode.ZAP_PLAY)
def get_fav_click_mode(self):
if self._click_mode_zap_button.get_active():
return FavClickMode.ZAP
if self._click_mode_play_button.get_active():
return FavClickMode.PLAY
+ if self._click_mode_zap_and_play_button.get_active():
+ return FavClickMode.ZAP_PLAY
if self._click_mode_stream_button.get_active():
return FavClickMode.STREAM
return FavClickMode.DISABLED
+ def on_view_popup_menu(self, menu, event):
+ if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
+ menu.popup(None, None, None, None, event.button, event.time)
+
if __name__ == "__main__":
pass
diff --git a/app/ui/transmitter.py b/app/ui/transmitter.py
index 16cc7a96..d418f1eb 100644
--- a/app/ui/transmitter.py
+++ b/app/ui/transmitter.py
@@ -87,7 +87,7 @@ class LinksTransmitter:
def on_play(self, res):
""" Play callback """
GLib.idle_add(self._url_entry.set_sensitive, True)
- res = res.get("result", None) if res else res
+ res = res.get("e2state", None) if res else res
self._url_entry.set_name("GtkEntry" if res else "digit-entry")
def on_exit(self, item=None):
diff --git a/app/ui/uicommons.py b/app/ui/uicommons.py
index 97c57a33..ef977f17 100644
--- a/app/ui/uicommons.py
+++ b/app/ui/uicommons.py
@@ -6,6 +6,10 @@ import sys
import gi
from enum import Enum, IntEnum
+import gi
+
+from app.settings import Settings, SettingsException
+
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
@@ -21,12 +25,22 @@ IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
# translation
os.environ["LANG"] = "{}.{}".format(*locale.getlocale())
TEXT_DOMAIN = "demon-editor"
+try:
+ settings = Settings.get_instance()
+except SettingsException:
+ pass
+else:
+ os.environ["LANGUAGE"] = settings.language
+ if UI_RESOURCES_PATH == "app/ui/":
+ locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
LANG_PATH = GTK_PATH + "/share/locale" if GTK_PATH else UI_RESOURCES_PATH + "lang"
gettext.bindtextdomain(TEXT_DOMAIN, LANG_PATH)
if sys.platform != "darwin":
locale.bindtextdomain(TEXT_DOMAIN, LANG_PATH)
theme = Gtk.IconTheme.get_default()
+theme.append_search_path(UI_RESOURCES_PATH + "icons")
+
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
"emblem-readonly", 16, 0) else _IMAGE_MISSING
@@ -36,6 +50,7 @@ HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.lookup_icon("emblem-shared", 16, 0) else None
EPG_ICON = theme.load_icon("gtk-index", 16, 0) if theme.lookup_icon("gtk-index", 16, 0) else None
+DEFAULT_ICON = theme.load_icon("emblem-default", 16, 0) if theme.lookup_icon("emblem-default", 16, 0) else None
class KeyboardKey(Enum):
@@ -94,6 +109,7 @@ class FavClickMode(IntEnum):
STREAM = 1
PLAY = 2
ZAP = 3
+ ZAP_PLAY = 4
class ViewTarget(Enum):
diff --git a/deb/usr/share/demoneditor/start.py b/deb/usr/share/demoneditor/start.py
new file mode 100755
index 00000000..cefbe196
--- /dev/null
+++ b/deb/usr/share/demoneditor/start.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python3
+from app.ui.main_app_window import start_app
+
+start_app()
diff --git a/deb/usr/share/icons/hicolor/96x96/apps/demon-editor.png b/deb/usr/share/icons/hicolor/96x96/apps/demon-editor.png
new file mode 100644
index 00000000..0c560a60
Binary files /dev/null and b/deb/usr/share/icons/hicolor/96x96/apps/demon-editor.png differ
diff --git a/deb/usr/share/icons/hicolor/scalable/apps/demon-editor.svg b/deb/usr/share/icons/hicolor/scalable/apps/demon-editor.svg
new file mode 100644
index 00000000..451891b4
--- /dev/null
+++ b/deb/usr/share/icons/hicolor/scalable/apps/demon-editor.svg
@@ -0,0 +1,634 @@
+
+