From a7c854bcd45f1746ba24677ac28328e52cc76d0a Mon Sep 17 00:00:00 2001 From: deathbybandaid Date: Fri, 29 Jan 2021 15:29:05 -0500 Subject: [PATCH] Rebuild Plugin System and seperate core functionality into Plugins --- data/internal_config/epg.json | 2 +- data/internal_config/fhdhr.json | 32 +- data/internal_config/main.json | 7 +- data/internal_config/rmg.json | 9 - fHDHR/__init__.py | 15 +- fHDHR/cli/run.py | 32 +- fHDHR/config/__init__.py | 137 ++++----- fHDHR/db/__init__.py | 284 +++++------------- fHDHR/device/__init__.py | 23 +- fHDHR/device/channels/__init__.py | 203 ++++++++----- fHDHR/device/channels/chan_ident.py | 23 +- fHDHR/device/channels/channel.py | 62 ++-- fHDHR/device/cluster.py | 158 ---------- fHDHR/device/epg/__init__.py | 111 +++---- fHDHR/device/epg/blocks.py | 9 +- fHDHR/device/ssdp/__init__.py | 63 ++-- fHDHR/device/ssdp/rmg_ssdp.py | 49 --- fHDHR/device/ssdp/ssdp_detect.py | 16 - fHDHR/device/tuners/__init__.py | 116 ++++--- fHDHR/device/tuners/stream/__init__.py | 8 +- .../tuners/stream/direct_m3u8_stream.py | 13 +- fHDHR/device/tuners/tuner.py | 38 ++- fHDHR/origins/__init__.py | 45 +++ fHDHR/originwrapper/__init__.py | 62 ---- .../originwrapper/origin_channels_standin.py | 11 - fHDHR/originwrapper/origin_epg_standin.py | 8 - fHDHR/plugins/__init__.py | 250 +++++++++++++++ fHDHR_web/__init__.py | 99 +++--- fHDHR_web/api/__init__.py | 6 +- fHDHR_web/api/channels.py | 60 ++-- fHDHR_web/api/debug.py | 13 +- fHDHR_web/api/epg.py | 14 +- fHDHR_web/api/images.py | 2 +- fHDHR_web/api/m3u.py | 32 +- fHDHR_web/api/plugins.py | 30 ++ fHDHR_web/api/root_url.py | 20 +- fHDHR_web/api/settings.py | 10 +- fHDHR_web/api/startup_tasks.py | 7 +- fHDHR_web/api/tuners.py | 95 ++++-- fHDHR_web/api/w3u.py | 34 ++- fHDHR_web/api/xmltv.py | 10 +- fHDHR_web/files/__init__.py | 2 - fHDHR_web/files/device_xml.py | 19 -- fHDHR_web/hdhr/lineup_json.py | 46 --- fHDHR_web/pages/__init__.py | 4 - fHDHR_web/pages/channels_editor_html.py | 14 +- fHDHR_web/pages/channels_html.py | 15 +- fHDHR_web/pages/cluster_html.py | 52 ---- fHDHR_web/pages/diagnostics_html.py | 5 +- fHDHR_web/pages/guide_html.py | 11 +- fHDHR_web/pages/index_html.py | 8 +- fHDHR_web/pages/settings_html.py | 8 +- fHDHR_web/pages/tools_html.py | 18 -- fHDHR_web/pages/tuners_html.py | 47 ++- fHDHR_web/pages/version_html.py | 15 +- fHDHR_web/pages/xmltv_html.py | 3 +- fHDHR_web/rmg/device_xml.py | 58 ---- fHDHR_web/rmg/devices_devicekey_channels.py | 47 --- fHDHR_web/rmg/devices_devicekey_media.py | 31 -- fHDHR_web/rmg/devices_discover.py | 49 --- fHDHR_web/rmg/devices_probe.py | 54 ---- fHDHR_web/templates/base.html | 15 +- fHDHR_web/templates/channels.html | 10 +- fHDHR_web/templates/guide.html | 4 +- fHDHR_web/templates/settings.html | 7 +- fHDHR_web/templates/tuners.html | 81 +++-- fHDHR_web/templates/version.html | 3 +- fHDHR_web/templates/xmltv.html | 12 +- fHDHR_web/web_ui_conf.json | 6 - main.py | 3 +- plugins/__init__.py | 70 ----- plugins/fHDHR_plugin_epg_tvtv/__init__.py | 65 ++-- plugins/fHDHR_plugin_epg_tvtv/plugin.json | 5 + plugins/fHDHR_plugin_epg_zap2it/__init__.py | 81 +++-- plugins/fHDHR_plugin_epg_zap2it/plugin.json | 5 + .../cluster_conf.json | 14 + .../interface/__init__.py | 157 ++++++++++ .../interface/plugin.json | 3 + .../plugin.json | 5 + .../ssdp/__init__.py | 70 +++++ .../ssdp/plugin.json | 3 + .../web/__init__.py | 14 + .../web}/cluster.html | 0 .../web/cluster_api.py | 28 +- .../web/cluster_device_xml.py | 46 +++ .../web/cluster_html.py | 68 +++++ .../web/plugin.json | 3 + .../hdhr_conf.json | 39 +++ .../fHDHR_plugin_interface_hdhr/plugin.json | 5 + .../ssdp/__init__.py | 22 +- .../ssdp/plugin.json | 3 + .../web}/__init__.py | 5 +- .../fHDHR_plugin_interface_hdhr/web}/auto.py | 21 +- .../web}/device_xml.py | 24 +- .../web}/discover_json.py | 26 +- .../web/lineup_json.py | 61 ++++ .../web}/lineup_post.py | 20 +- .../web}/lineup_status_json.py | 29 +- .../web}/lineup_xml.py | 43 ++- .../web/plugin.json | 3 + .../fHDHR_plugin_interface_hdhr/web}/tuner.py | 23 +- .../fHDHR_plugin_interface_rmg/plugin.json | 5 + .../fHDHR_plugin_interface_rmg/rmg_conf.json | 24 ++ .../ssdp/__init__.py | 49 +++ .../ssdp/plugin.json | 3 + .../web}/__init__.py | 6 +- .../web/device_xml.py | 63 ++++ .../web}/devices_devicekey.py | 32 +- .../web/devices_devicekey_channels.py | 59 ++++ .../web/devices_devicekey_media.py | 43 +++ .../web}/devices_devicekey_networks.py | 9 +- .../web}/devices_devicekey_prefs.py | 5 +- .../web}/devices_devicekey_scan.py | 13 +- .../web}/devices_devicekey_scanners.py | 8 +- .../web/devices_discover.py | 57 ++++ .../web/devices_probe.py | 63 ++++ .../web/plugin.json | 3 + .../web}/rmg_ident_xml.py | 20 +- .../fHDHR_plugin_origin_NextPVR/__init__.py | 8 - .../{origin/origin_epg.py => epg/__init__.py} | 30 +- .../epg/plugin.json | 3 + .../nextpvr_conf.json | 31 ++ .../origin/__init__.py | 116 ++++++- .../origin/origin_channels.py | 64 ---- .../origin/origin_conf.json | 67 ----- .../origin/origin_service.py | 55 ---- .../origin/origin_web/__init__.py | 12 - .../origin/origin_web/origin_api.py | 16 - .../origin/origin_web/origin_html.py | 30 -- .../origin/plugin.json | 3 + .../fHDHR_plugin_origin_NextPVR/plugin.json | 5 + .../web/__init__.py | 11 + .../{origin/origin_web => web}/origin.html | 2 +- .../web/origin_html.py | 34 +++ .../web/plugin.json | 3 + .../fHDHR_plugin_stream_ffmpeg/__init__.py | 63 ++-- .../fHDHR_plugin_stream_ffmpeg/plugin.json | 5 + plugins/fHDHR_plugin_stream_vlc/__init__.py | 63 ++-- plugins/fHDHR_plugin_stream_vlc/plugin.json | 5 + plugins/fHDHR_plugin_web_devtools/__init__.py | 12 + .../fHDHR_plugin_web_devtools/devtools.html | 4 +- .../fHDHR_plugin_web_devtools/devtools_api.py | 15 +- .../devtools_html.py | 26 ++ plugins/fHDHR_plugin_web_devtools/plugin.json | 5 + requirements.txt | 1 + 145 files changed, 2759 insertions(+), 2218 deletions(-) delete mode 100644 data/internal_config/rmg.json delete mode 100644 fHDHR/device/cluster.py delete mode 100644 fHDHR/device/ssdp/rmg_ssdp.py delete mode 100644 fHDHR/device/ssdp/ssdp_detect.py create mode 100644 fHDHR/origins/__init__.py delete mode 100644 fHDHR/originwrapper/__init__.py delete mode 100644 fHDHR/originwrapper/origin_channels_standin.py delete mode 100644 fHDHR/originwrapper/origin_epg_standin.py create mode 100644 fHDHR/plugins/__init__.py create mode 100644 fHDHR_web/api/plugins.py delete mode 100644 fHDHR_web/files/device_xml.py delete mode 100644 fHDHR_web/hdhr/lineup_json.py delete mode 100644 fHDHR_web/pages/cluster_html.py delete mode 100644 fHDHR_web/pages/tools_html.py delete mode 100644 fHDHR_web/rmg/device_xml.py delete mode 100644 fHDHR_web/rmg/devices_devicekey_channels.py delete mode 100644 fHDHR_web/rmg/devices_devicekey_media.py delete mode 100644 fHDHR_web/rmg/devices_discover.py delete mode 100644 fHDHR_web/rmg/devices_probe.py delete mode 100644 plugins/__init__.py create mode 100644 plugins/fHDHR_plugin_epg_tvtv/plugin.json create mode 100644 plugins/fHDHR_plugin_epg_zap2it/plugin.json create mode 100644 plugins/fHDHR_plugin_interface_cluster/cluster_conf.json create mode 100644 plugins/fHDHR_plugin_interface_cluster/interface/__init__.py create mode 100644 plugins/fHDHR_plugin_interface_cluster/interface/plugin.json create mode 100644 plugins/fHDHR_plugin_interface_cluster/plugin.json create mode 100644 plugins/fHDHR_plugin_interface_cluster/ssdp/__init__.py create mode 100644 plugins/fHDHR_plugin_interface_cluster/ssdp/plugin.json create mode 100644 plugins/fHDHR_plugin_interface_cluster/web/__init__.py rename {fHDHR_web/templates => plugins/fHDHR_plugin_interface_cluster/web}/cluster.html (100%) rename fHDHR_web/api/cluster.py => plugins/fHDHR_plugin_interface_cluster/web/cluster_api.py (56%) create mode 100644 plugins/fHDHR_plugin_interface_cluster/web/cluster_device_xml.py create mode 100644 plugins/fHDHR_plugin_interface_cluster/web/cluster_html.py create mode 100644 plugins/fHDHR_plugin_interface_cluster/web/plugin.json create mode 100644 plugins/fHDHR_plugin_interface_hdhr/hdhr_conf.json create mode 100644 plugins/fHDHR_plugin_interface_hdhr/plugin.json rename fHDHR/device/ssdp/hdhr_ssdp.py => plugins/fHDHR_plugin_interface_hdhr/ssdp/__init__.py (73%) create mode 100644 plugins/fHDHR_plugin_interface_hdhr/ssdp/plugin.json rename {fHDHR_web/hdhr => plugins/fHDHR_plugin_interface_hdhr/web}/__init__.py (86%) rename {fHDHR_web/hdhr => plugins/fHDHR_plugin_interface_hdhr/web}/auto.py (60%) rename {fHDHR_web/hdhr => plugins/fHDHR_plugin_interface_hdhr/web}/device_xml.py (62%) rename {fHDHR_web/hdhr => plugins/fHDHR_plugin_interface_hdhr/web}/discover_json.py (53%) create mode 100644 plugins/fHDHR_plugin_interface_hdhr/web/lineup_json.py rename {fHDHR_web/hdhr => plugins/fHDHR_plugin_interface_hdhr/web}/lineup_post.py (78%) rename {fHDHR_web/hdhr => plugins/fHDHR_plugin_interface_hdhr/web}/lineup_status_json.py (56%) rename {fHDHR_web/hdhr => plugins/fHDHR_plugin_interface_hdhr/web}/lineup_xml.py (51%) create mode 100644 plugins/fHDHR_plugin_interface_hdhr/web/plugin.json rename {fHDHR_web/hdhr => plugins/fHDHR_plugin_interface_hdhr/web}/tuner.py (59%) create mode 100644 plugins/fHDHR_plugin_interface_rmg/plugin.json create mode 100644 plugins/fHDHR_plugin_interface_rmg/rmg_conf.json create mode 100644 plugins/fHDHR_plugin_interface_rmg/ssdp/__init__.py create mode 100644 plugins/fHDHR_plugin_interface_rmg/ssdp/plugin.json rename {fHDHR_web/rmg => plugins/fHDHR_plugin_interface_rmg/web}/__init__.py (92%) create mode 100644 plugins/fHDHR_plugin_interface_rmg/web/device_xml.py rename {fHDHR_web/rmg => plugins/fHDHR_plugin_interface_rmg/web}/devices_devicekey.py (70%) create mode 100644 plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_channels.py create mode 100644 plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_media.py rename {fHDHR_web/rmg => plugins/fHDHR_plugin_interface_rmg/web}/devices_devicekey_networks.py (73%) rename {fHDHR_web/rmg => plugins/fHDHR_plugin_interface_rmg/web}/devices_devicekey_prefs.py (72%) rename {fHDHR_web/rmg => plugins/fHDHR_plugin_interface_rmg/web}/devices_devicekey_scan.py (81%) rename {fHDHR_web/rmg => plugins/fHDHR_plugin_interface_rmg/web}/devices_devicekey_scanners.py (85%) create mode 100644 plugins/fHDHR_plugin_interface_rmg/web/devices_discover.py create mode 100644 plugins/fHDHR_plugin_interface_rmg/web/devices_probe.py create mode 100644 plugins/fHDHR_plugin_interface_rmg/web/plugin.json rename {fHDHR_web/rmg => plugins/fHDHR_plugin_interface_rmg/web}/rmg_ident_xml.py (58%) delete mode 100644 plugins/fHDHR_plugin_origin_NextPVR/__init__.py rename plugins/fHDHR_plugin_origin_NextPVR/{origin/origin_epg.py => epg/__init__.py} (81%) create mode 100644 plugins/fHDHR_plugin_origin_NextPVR/epg/plugin.json create mode 100644 plugins/fHDHR_plugin_origin_NextPVR/nextpvr_conf.json delete mode 100644 plugins/fHDHR_plugin_origin_NextPVR/origin/origin_channels.py delete mode 100644 plugins/fHDHR_plugin_origin_NextPVR/origin/origin_conf.json delete mode 100644 plugins/fHDHR_plugin_origin_NextPVR/origin/origin_service.py delete mode 100644 plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/__init__.py delete mode 100644 plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin_api.py delete mode 100644 plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin_html.py create mode 100644 plugins/fHDHR_plugin_origin_NextPVR/origin/plugin.json create mode 100644 plugins/fHDHR_plugin_origin_NextPVR/plugin.json create mode 100644 plugins/fHDHR_plugin_origin_NextPVR/web/__init__.py rename plugins/fHDHR_plugin_origin_NextPVR/{origin/origin_web => web}/origin.html (80%) create mode 100644 plugins/fHDHR_plugin_origin_NextPVR/web/origin_html.py create mode 100644 plugins/fHDHR_plugin_origin_NextPVR/web/plugin.json create mode 100644 plugins/fHDHR_plugin_stream_ffmpeg/plugin.json create mode 100644 plugins/fHDHR_plugin_stream_vlc/plugin.json create mode 100644 plugins/fHDHR_plugin_web_devtools/__init__.py rename fHDHR_web/templates/tools.html => plugins/fHDHR_plugin_web_devtools/devtools.html (72%) rename fHDHR_web/api/tools.py => plugins/fHDHR_plugin_web_devtools/devtools_api.py (77%) create mode 100644 plugins/fHDHR_plugin_web_devtools/devtools_html.py create mode 100644 plugins/fHDHR_plugin_web_devtools/plugin.json diff --git a/data/internal_config/epg.json b/data/internal_config/epg.json index acf7eb1..d7ff86a 100644 --- a/data/internal_config/epg.json +++ b/data/internal_config/epg.json @@ -6,7 +6,7 @@ "config_web": true }, "method":{ - "value": "blocks", + "value": "none", "config_file": true, "config_web": true }, diff --git a/data/internal_config/fhdhr.json b/data/internal_config/fhdhr.json index dad0ada..4871698 100644 --- a/data/internal_config/fhdhr.json +++ b/data/internal_config/fhdhr.json @@ -15,26 +15,6 @@ "config_file": true, "config_web": true }, - "reporting_manufacturer":{ - "value": "BoronDust", - "config_file": true, - "config_web": true - }, - "reporting_model":{ - "value": "fHDHR", - "config_file": true, - "config_web": true - }, - "reporting_firmware_ver":{ - "value": "20201001", - "config_file": true, - "config_web": true - }, - "reporting_tuner_type":{ - "value": "Antenna", - "config_file": true, - "config_web": true - }, "device_auth":{ "value": "fHDHR", "config_file": true, @@ -54,16 +34,6 @@ "value": "fHDHR", "config_file": true, "config_web": true - }, - "tuner_count":{ - "value": 4, - "config_file": true, - "config_web": true - }, - "reporting_firmware_name":{ - "value": "fHDHR", - "config_file": true, - "config_web": true - } + } } } diff --git a/data/internal_config/main.json b/data/internal_config/main.json index 4d1a43b..8c5e666 100644 --- a/data/internal_config/main.json +++ b/data/internal_config/main.json @@ -14,12 +14,7 @@ "value": "fHDHR", "config_file": false, "config_web": false - }, - "dictpopname":{ - "value": "fHDHR", - "config_file": false, - "config_web": false - }, + }, "reponame":{ "value": "fHDHR", "config_file": false, diff --git a/data/internal_config/rmg.json b/data/internal_config/rmg.json deleted file mode 100644 index bf088aa..0000000 --- a/data/internal_config/rmg.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "rmg":{ - "enabled":{ - "value": true, - "config_file": true, - "config_web": false - } - } -} diff --git a/fHDHR/__init__.py b/fHDHR/__init__.py index 22a3ab3..d462bf8 100644 --- a/fHDHR/__init__.py +++ b/fHDHR/__init__.py @@ -1,25 +1,28 @@ # coding=utf-8 -from .originwrapper import OriginServiceWrapper from .device import fHDHR_Device from .api import fHDHR_API_URLs import fHDHR.tools - fHDHR_VERSION = "v0.6.0-beta" class fHDHR_INT_OBJ(): - def __init__(self, settings, logger, db): + def __init__(self, settings, logger, db, plugins): self.version = fHDHR_VERSION self.config = settings self.logger = logger self.db = db + self.plugins = plugins self.web = fHDHR.tools.WebReq() + for plugin_name in list(self.plugins.plugins.keys()): + self.plugins.plugins[plugin_name].plugin_utils.web = self.web self.api = fHDHR_API_URLs(settings, self.web) + for plugin_name in list(self.plugins.plugins.keys()): + self.plugins.plugins[plugin_name].plugin_utils.api = self.api self.threads = {} @@ -27,11 +30,11 @@ class fHDHR_INT_OBJ(): class fHDHR_OBJ(): def __init__(self, settings, logger, db, plugins): - self.fhdhr = fHDHR_INT_OBJ(settings, logger, db) + self.fhdhr = fHDHR_INT_OBJ(settings, logger, db, plugins) - self.originwrapper = OriginServiceWrapper(self.fhdhr, plugins.origin) + self.fhdhr.origins = fHDHR.origins.Origins(self.fhdhr) - self.device = fHDHR_Device(self.fhdhr, self.originwrapper, plugins) + self.device = fHDHR_Device(self.fhdhr, self.fhdhr.origins) def __getattr__(self, name): ''' will only get called for undefined attributes ''' diff --git a/fHDHR/cli/run.py b/fHDHR/cli/run.py index e1cb82b..74d3dd2 100644 --- a/fHDHR/cli/run.py +++ b/fHDHR/cli/run.py @@ -7,6 +7,8 @@ from fHDHR import fHDHR_VERSION, fHDHR_OBJ import fHDHR.exceptions import fHDHR.config import fHDHR.logger +import fHDHR.plugins +import fHDHR.origins from fHDHR.db import fHDHRdb ERR_CODE = 1 @@ -25,10 +27,10 @@ def build_args_parser(): return parser.parse_args() -def get_configuration(args, script_dir, plugins, fHDHR_web): +def get_configuration(args, script_dir, fHDHR_web): if not os.path.isfile(args.cfg): raise fHDHR.exceptions.ConfigurationNotFound(filename=args.cfg) - return fHDHR.config.Config(args.cfg, script_dir, plugins, fHDHR_web) + return fHDHR.config.Config(args.cfg, script_dir, fHDHR_web) def run(settings, logger, db, script_dir, fHDHR_web, plugins): @@ -64,23 +66,41 @@ def run(settings, logger, db, script_dir, fHDHR_web, plugins): return ERR_CODE -def start(args, script_dir, fHDHR_web, plugins): +def start(args, script_dir, fHDHR_web): """Get Configuration for fHDHR and start""" try: - settings = get_configuration(args, script_dir, plugins, fHDHR_web) + settings = get_configuration(args, script_dir, fHDHR_web) except fHDHR.exceptions.ConfigurationError as e: print(e) return ERR_CODE_NO_RESTART + # Find Plugins and import their default configs + plugins = fHDHR.plugins.PluginsHandler(settings) + + # Apply User Configuration + settings.user_config() + settings.config_verification() + + # Setup Logging logger = fHDHR.logger.Logger(settings) + # Setup Database db = fHDHRdb(settings) + # Setup Plugins + plugins.load_plugins(logger, db) + plugins.setup() + settings.config_verification_plugins() + + if not len([x for x in list(plugins.plugins.keys()) if plugins.plugins[x].type == "origin"]): + print("No Origin Plugins found.") + return ERR_CODE + return run(settings, logger, db, script_dir, fHDHR_web, plugins) -def main(script_dir, fHDHR_web, plugins): +def main(script_dir, fHDHR_web): """fHDHR run script entry point""" print("Loading fHDHR %s" % fHDHR_VERSION) @@ -89,7 +109,7 @@ def main(script_dir, fHDHR_web, plugins): try: args = build_args_parser() while True: - returned_code = start(args, script_dir, fHDHR_web, plugins) + returned_code = start(args, script_dir, fHDHR_web) if returned_code not in ["restart"]: return returned_code except KeyboardInterrupt: diff --git a/fHDHR/config/__init__.py b/fHDHR/config/__init__.py index f624c35..143dd0c 100644 --- a/fHDHR/config/__init__.py +++ b/fHDHR/config/__init__.py @@ -13,29 +13,28 @@ from fHDHR.tools import isint, isfloat, is_arithmetic, is_docker class Config(): - def __init__(self, filename, script_dir, plugins, fHDHR_web): - self.plugins = plugins + def __init__(self, filename, script_dir, fHDHR_web): self.fHDHR_web = fHDHR_web self.internal = {} self.conf_default = {} self.dict = {} + self.internal["versions"] = {} self.config_file = filename self.core_setup(script_dir) - self.plugins_setup() - self.user_config() - self.config_verification() def core_setup(self, script_dir): data_dir = pathlib.Path(script_dir).joinpath('data') + internal_plugins_dir = pathlib.Path(script_dir).joinpath('plugins') fHDHR_web_dir = pathlib.Path(script_dir).joinpath('fHDHR_web') www_dir = pathlib.Path(fHDHR_web_dir).joinpath('www_dir') self.internal["paths"] = { "script_dir": script_dir, "data_dir": data_dir, + "plugins_dir": [internal_plugins_dir], "cache_dir": pathlib.Path(data_dir).joinpath('cache'), "internal_config": pathlib.Path(data_dir).joinpath('internal_config'), "fHDHR_web_dir": fHDHR_web_dir, @@ -54,68 +53,61 @@ class Config(): if str(file_item_path).endswith("_conf.json"): self.read_json_config(file_item_path) - self.dict["epg"]["valid_methods"] = ["origin", "blocks", None] - - self.dict["streaming"]["valid_methods"] = ["direct"] + self.dict["epg"]["valid_methods"] = {None: {}} + self.dict["origins"] = {} + self.dict["origins"]["valid_methods"] = {} + self.dict["streaming"]["valid_methods"] = {"direct": {}} + self.dict["plugin_web_paths"] = {} self.load_versions() - def plugins_setup(self): + def register_web_path(self, name, path, plugin_dict_name): + self.dict["plugin_web_paths"][name.lower()] = { + "name": name, + "namespace": name.lower(), + "path": path, + "plugin": plugin_dict_name + } - # Load Origin Paths - origin_dir = [self.plugins.plugin_dict[x]["PATH"] for x in list(self.plugins.plugin_dict.keys()) if self.plugins.plugin_dict[x]["TYPE"] == "origin"][0] - self.internal["paths"]["origin"] = origin_dir - self.internal["paths"]["origin_web"] = pathlib.Path(origin_dir).joinpath('origin_web') + def register_valid_origin_method(self, method_item): + self.dict["origins"]["valid_methods"][method_item.lower()] = { + "name": method_item, + "namespace": method_item.lower(), + } - # Load Plugin Conf - for dir_type in ["alt_epg", "origin", "alt_stream"]: - if dir_type == "origin": - dir_tops = [self.internal["paths"]["origin"]] - elif dir_type in ["alt_stream", "alt_epg"]: - dir_tops = [self.plugins.plugin_dict[x]["PATH"] for x in list(self.plugins.plugin_dict.keys()) if self.plugins.plugin_dict[x]["TYPE"] == dir_type] - for top_dir in dir_tops: - for file_item in os.listdir(top_dir): - file_item_path = pathlib.Path(top_dir).joinpath(file_item) - if file_item_path.is_dir(): - for sub_file_item in os.listdir(file_item_path): - sub_file_item_path = pathlib.Path(file_item_path).joinpath(sub_file_item) - if str(sub_file_item_path).endswith("_conf.json"): - self.read_json_config(sub_file_item_path) - else: - if str(file_item_path).endswith("_conf.json"): - self.read_json_config(file_item_path) + def register_valid_streaming_method(self, method_item, plugin_dict_name): + self.dict["streaming"]["valid_methods"][method_item.lower()] = { + "name": method_item, + "namespace": method_item.lower(), + "plugin": plugin_dict_name + } - # Rename the Origin conf section - self.dict["origin"] = self.dict.pop(self.dict["main"]["dictpopname"]) + def register_valid_epg_method(self, method_item, plugin_dict_name): + self.dict["epg"]["valid_methods"][method_item.lower()] = { + "name": method_item, + "namespace": method_item.lower(), + "plugin": plugin_dict_name + } - # Get Pltuin Version - for plugin_item in list(self.plugins.plugin_dict.keys()): - self.internal["versions"][plugin_item] = self.plugins.plugin_dict[plugin_item]["VERSION"] + def register_version(self, item_name, item_version, item_type): + self.internal["versions"][item_name] = { + "name": item_name, + "version": item_version, + "type": item_type + } - # Run Plugin Setup Checks - for plugin_item in list(self.plugins.plugin_dict.keys()): - try: - eval("self.plugins.%s_Setup(self)" % self.plugins.plugin_dict[plugin_item]["NAME"].upper()) - except AttributeError: - pass - - self.dict["epg"]["valid_methods"].extend([self.plugins.plugin_dict[x]["NAME"] for x in list(self.plugins.plugin_dict.keys()) if self.plugins.plugin_dict[x]["TYPE"] == "alt_epg"]) - self.dict["streaming"]["valid_methods"].extend([self.plugins.plugin_dict[x]["NAME"] for x in list(self.plugins.plugin_dict.keys()) if self.plugins.plugin_dict[x]["TYPE"] == "alt_stream"]) - - def register_version(self, item_name, item_version): - self.internal["versions"][item_name] = item_version + def import_conf_json(self, file_item_path): + self.read_json_config(file_item_path) def load_versions(self): - self.internal["versions"] = {} + self.register_version("fHDHR", fHDHR_VERSION, "fHDHR") + self.register_version("fHDHR_web", self.fHDHR_web.fHDHR_web_VERSION, "fHDHR") - self.internal["versions"]["fHDHR"] = fHDHR_VERSION - self.internal["versions"]["fHDHR_web"] = self.fHDHR_web.fHDHR_web_VERSION - - self.internal["versions"]["Python"] = sys.version + self.register_version("Python", sys.version, "env") opersystem = platform.system() - self.internal["versions"]["Operating System"] = opersystem + self.register_version("Operating System", opersystem, "env") if opersystem in ["Linux", "Darwin"]: # Linux/Mac if os.getuid() == 0 or os.geteuid() == 0: @@ -128,23 +120,19 @@ class Config(): print("Uncommon Operating System, use at your own risk.") isdocker = is_docker() - self.internal["versions"]["Docker"] = isdocker + self.register_version("Docker", isdocker, "env") def user_config(self): print("Loading Configuration File: %s" % self.config_file) self.read_ini_config(self.config_file) - def config_verification(self): - + def config_verification_plugins(self): required_missing = {} # create dict and combine items for config_section in list(self.conf_default.keys()): for config_item in list(self.conf_default[config_section].keys()): if self.conf_default[config_section][config_item]["required"]: - config_section_name = config_section - if config_section == self.dict["main"]["dictpopname"]: - config_section_name = "origin" - if not self.dict[config_section_name][config_item]: + if not self.dict[config_section][config_item]: if config_section not in list(required_missing.keys()): required_missing[config_section] = [] required_missing[config_section].append(config_item) @@ -156,19 +144,25 @@ class Config(): self.dict["epg"]["method"] = [self.dict["epg"]["method"]] epg_methods = [] for epg_method in self.dict["epg"]["method"]: - if epg_method == self.dict["main"]["dictpopname"] or epg_method == "origin": - epg_methods.append("origin") - elif epg_method in ["None"]: - raise fHDHR.exceptions.ConfigurationError("Invalid EPG Method. Exiting...") - elif epg_method in self.dict["epg"]["valid_methods"]: + if epg_method in list(self.dict["epg"]["valid_methods"].keys()): + epg_methods.append(epg_method) + elif epg_method in list(self.dict["origins"]["valid_methods"].keys()): epg_methods.append(epg_method) else: raise fHDHR.exceptions.ConfigurationError("Invalid EPG Method. Exiting...") - self.dict["epg"]["def_method"] = self.dict["epg"]["method"][0] + if self.dict["epg"]["method"]: + self.dict["epg"]["def_method"] = self.dict["epg"]["method"][0] + else: + self.dict["epg"]["def_method"] = None + + if self.dict["streaming"]["method"] not in self.dict["streaming"]["valid_methods"]: + raise fHDHR.exceptions.ConfigurationError("Invalid stream type. Exiting...") + + def config_verification(self): if not self.dict["main"]["uuid"]: self.dict["main"]["uuid"] = ''.join(random.choice("hijklmnopqrstuvwxyz") for i in range(8)) - self.write('main', 'uuid', self.dict["main"]["uuid"]) + self.write('uuid', self.dict["main"]["uuid"], 'main') if self.dict["main"]["cache_dir"]: if not pathlib.Path(self.dict["main"]["cache_dir"]).is_dir(): @@ -183,9 +177,6 @@ class Config(): self.dict["database"]["path"] = pathlib.Path(cache_dir).joinpath('fhdhr.db') - if self.dict["streaming"]["method"] not in self.dict["streaming"]["valid_methods"]: - raise fHDHR.exceptions.ConfigurationError("Invalid stream type. Exiting...") - if not self.dict["fhdhr"]["discovery_address"] and self.dict["fhdhr"]["address"] != "0.0.0.0": self.dict["fhdhr"]["discovery_address"] = self.dict["fhdhr"]["address"] if not self.dict["fhdhr"]["discovery_address"] or self.dict["fhdhr"]["discovery_address"] == "0.0.0.0": @@ -263,11 +254,9 @@ class Config(): import_val = False if import_val: - if each_section == self.dict["main"]["dictpopname"]: - each_section = "origin" self.dict[each_section.lower()][each_key.lower()] = each_val - def write(self, section, key, value): + def write(self, key, value, section): if not value: value = None @@ -284,8 +273,6 @@ class Config(): elif isinstance(value, list): ",".join(value) - if section == self.dict["main"]["dictpopname"]: - section = "origin" self.dict[section][key] = value config_handler = configparser.ConfigParser() diff --git a/fHDHR/db/__init__.py b/fHDHR/db/__init__.py index 86fd530..58ced71 100644 --- a/fHDHR/db/__init__.py +++ b/fHDHR/db/__init__.py @@ -32,28 +32,10 @@ MYSQL_TABLE_ARGS = {'mysql_engine': 'InnoDB', 'mysql_collate': 'utf8mb4_unicode_ci'} -class ChannelValues(BASE): - __tablename__ = 'channel_values' +class PluginValues(BASE): + __tablename__ = 'plugin_values' __table_args__ = MYSQL_TABLE_ARGS - channel = Column(String(255), primary_key=True) - namespace = Column(String(255), primary_key=True) - key = Column(String(255), primary_key=True) - value = Column(Text()) - - -class ProgramValues(BASE): - __tablename__ = 'program_values' - __table_args__ = MYSQL_TABLE_ARGS - program = Column(String(255), primary_key=True) - namespace = Column(String(255), primary_key=True) - key = Column(String(255), primary_key=True) - value = Column(Text()) - - -class CacheValues(BASE): - __tablename__ = 'cache_values' - __table_args__ = MYSQL_TABLE_ARGS - cacheitem = Column(String(255), primary_key=True) + pluginitem = Column(String(255), primary_key=True) namespace = Column(String(255), primary_key=True) key = Column(String(255), primary_key=True) value = Column(Text()) @@ -148,198 +130,6 @@ class fHDHRdb(object): def get_uri(self): return self.url - # Channel Values - - def set_channel_value(self, channel, key, value, namespace='default'): - channel = channel.lower() - value = json.dumps(value, ensure_ascii=False) - session = self.ssession() - try: - result = session.query(ChannelValues) \ - .filter(ChannelValues.channel == channel)\ - .filter(ChannelValues.namespace == namespace)\ - .filter(ChannelValues.key == key) \ - .one_or_none() - # ChannelValues exists, update - if result: - result.value = value - session.commit() - # DNE - Insert - else: - new_channelvalue = ChannelValues(channel=channel, namespace=namespace, key=key, value=value) - session.add(new_channelvalue) - session.commit() - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - - def get_channel_value(self, channel, key, namespace='default'): - channel = channel.lower() - session = self.ssession() - try: - result = session.query(ChannelValues) \ - .filter(ChannelValues.channel == channel)\ - .filter(ChannelValues.namespace == namespace)\ - .filter(ChannelValues.key == key) \ - .one_or_none() - if result is not None: - result = result.value - return _deserialize(result) - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - - def delete_channel_value(self, channel, key, namespace='default'): - channel = channel.lower() - session = self.ssession() - try: - result = session.query(ChannelValues) \ - .filter(ChannelValues.channel == channel)\ - .filter(ChannelValues.namespace == namespace)\ - .filter(ChannelValues.key == key) \ - .one_or_none() - # ChannelValues exists, delete - if result: - session.delete(result) - session.commit() - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - - # Program Values - - def set_program_value(self, program, key, value, namespace='default'): - program = program.lower() - value = json.dumps(value, ensure_ascii=False) - session = self.ssession() - try: - result = session.query(ProgramValues) \ - .filter(ProgramValues.program == program)\ - .filter(ProgramValues.namespace == namespace)\ - .filter(ProgramValues.key == key) \ - .one_or_none() - # ProgramValue exists, update - if result: - result.value = value - session.commit() - # DNE - Insert - else: - new_programvalue = ProgramValues(program=program, namespace=namespace, key=key, value=value) - session.add(new_programvalue) - session.commit() - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - - def get_program_value(self, program, key, namespace='default'): - program = program.lower() - session = self.ssession() - try: - result = session.query(ProgramValues) \ - .filter(ProgramValues.program == program)\ - .filter(ProgramValues.namespace == namespace)\ - .filter(ProgramValues.key == key) \ - .one_or_none() - if result is not None: - result = result.value - return _deserialize(result) - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - - def delete_program_value(self, program, key, namespace='default'): - program = program.lower() - session = self.ssession() - try: - result = session.query(ProgramValues) \ - .filter(ProgramValues.program == program)\ - .filter(ProgramValues.namespace == namespace)\ - .filter(ProgramValues.key == key) \ - .one_or_none() - # ProgramValue exists, delete - if result: - session.delete(result) - session.commit() - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - - # Cache Values - - def set_cacheitem_value(self, cacheitem, key, value, namespace='default'): - cacheitem = cacheitem.lower() - value = json.dumps(value, ensure_ascii=False) - session = self.ssession() - try: - result = session.query(CacheValues) \ - .filter(CacheValues.cacheitem == cacheitem)\ - .filter(CacheValues.namespace == namespace)\ - .filter(CacheValues.key == key) \ - .one_or_none() - # ProgramValue exists, update - if result: - result.value = value - session.commit() - # DNE - Insert - else: - new_cacheitemvalue = CacheValues(cacheitem=cacheitem, namespace=namespace, key=key, value=value) - session.add(new_cacheitemvalue) - session.commit() - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - - def get_cacheitem_value(self, cacheitem, key, namespace='default'): - cacheitem = cacheitem.lower() - session = self.ssession() - try: - result = session.query(CacheValues) \ - .filter(CacheValues.cacheitem == cacheitem)\ - .filter(CacheValues.namespace == namespace)\ - .filter(CacheValues.key == key) \ - .one_or_none() - if result is not None: - result = result.value - return _deserialize(result) - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - - def delete_cacheitem_value(self, cacheitem, key, namespace='default'): - cacheitem = cacheitem.lower() - session = self.ssession() - try: - result = session.query(CacheValues) \ - .filter(CacheValues.cacheitem == cacheitem)\ - .filter(CacheValues.namespace == namespace)\ - .filter(CacheValues.key == key) \ - .one_or_none() - # ProgramValue exists, delete - if result: - session.delete(result) - session.commit() - except SQLAlchemyError: - session.rollback() - raise - finally: - session.close() - # fHDHR Values def set_fhdhr_value(self, item, key, value, namespace='default'): @@ -358,8 +148,8 @@ class fHDHRdb(object): session.commit() # DNE - Insert else: - new_cacheitemvalue = fHDHRValues(item=item, namespace=namespace, key=key, value=value) - session.add(new_cacheitemvalue) + new_pluginitemvalue = fHDHRValues(item=item, namespace=namespace, key=key, value=value) + session.add(new_pluginitemvalue) session.commit() except SQLAlchemyError: session.rollback() @@ -403,3 +193,67 @@ class fHDHRdb(object): raise finally: session.close() + + # Plugin Values + + def set_plugin_value(self, pluginitem, key, value, namespace='default'): + pluginitem = pluginitem.lower() + value = json.dumps(value, ensure_ascii=False) + session = self.ssession() + try: + result = session.query(PluginValues) \ + .filter(PluginValues.pluginitem == pluginitem)\ + .filter(PluginValues.namespace == namespace)\ + .filter(PluginValues.key == key) \ + .one_or_none() + # ProgramValue exists, update + if result: + result.value = value + session.commit() + # DNE - Insert + else: + new_pluginitemvalue = PluginValues(pluginitem=pluginitem, namespace=namespace, key=key, value=value) + session.add(new_pluginitemvalue) + session.commit() + except SQLAlchemyError: + session.rollback() + raise + finally: + session.close() + + def get_plugin_value(self, pluginitem, key, namespace='default'): + pluginitem = pluginitem.lower() + session = self.ssession() + try: + result = session.query(PluginValues) \ + .filter(PluginValues.pluginitem == pluginitem)\ + .filter(PluginValues.namespace == namespace)\ + .filter(PluginValues.key == key) \ + .one_or_none() + if result is not None: + result = result.value + return _deserialize(result) + except SQLAlchemyError: + session.rollback() + raise + finally: + session.close() + + def delete_plugin_value(self, pluginitem, key, namespace='default'): + pluginitem = pluginitem.lower() + session = self.ssession() + try: + result = session.query(PluginValues) \ + .filter(PluginValues.pluginitem == pluginitem)\ + .filter(PluginValues.namespace == namespace)\ + .filter(PluginValues.key == key) \ + .one_or_none() + # ProgramValue exists, delete + if result: + session.delete(result) + session.commit() + except SQLAlchemyError: + session.rollback() + raise + finally: + session.close() diff --git a/fHDHR/device/__init__.py b/fHDHR/device/__init__.py index 874ef39..ada2e3a 100644 --- a/fHDHR/device/__init__.py +++ b/fHDHR/device/__init__.py @@ -3,21 +3,32 @@ from .epg import EPG from .tuners import Tuners from .images import imageHandler from .ssdp import SSDPServer -from .cluster import fHDHR_Cluster class fHDHR_Device(): - def __init__(self, fhdhr, originwrapper, plugins): + def __init__(self, fhdhr, origins): + self.fhdhr = fhdhr - self.channels = Channels(fhdhr, originwrapper) + self.channels = Channels(fhdhr, origins) - self.epg = EPG(fhdhr, self.channels, originwrapper, plugins) + self.epg = EPG(fhdhr, self.channels, origins) - self.tuners = Tuners(fhdhr, self.epg, self.channels, plugins) + self.tuners = Tuners(fhdhr, self.epg, self.channels) self.images = imageHandler(fhdhr, self.epg) self.ssdp = SSDPServer(fhdhr) - self.cluster = fHDHR_Cluster(fhdhr, self.ssdp) + self.interfaces = {} + + for plugin_name in list(self.fhdhr.plugins.plugins.keys()): + if self.fhdhr.plugins.plugins[plugin_name].manifest["type"] == "interface": + method = self.fhdhr.plugins.plugins[plugin_name].name.lower() + plugin_utils = self.fhdhr.plugins.plugins[plugin_name].plugin_utils + plugin_utils.channels = self.channels + plugin_utils.epg = self.epg + plugin_utils.tuners = self.tuners + plugin_utils.images = self.images + plugin_utils.ssdp = self.ssdp + self.interfaces[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(fhdhr, plugin_utils) diff --git a/fHDHR/device/channels/__init__.py b/fHDHR/device/channels/__init__.py index 113ef43..f3c25b7 100644 --- a/fHDHR/device/channels/__init__.py +++ b/fHDHR/device/channels/__init__.py @@ -8,57 +8,111 @@ from .chan_ident import Channel_IDs class Channels(): - def __init__(self, fhdhr, originwrapper): + def __init__(self, fhdhr, origins): self.fhdhr = fhdhr - self.origin = originwrapper + self.origins = origins - self.id_system = Channel_IDs(fhdhr) + self.id_system = Channel_IDs(fhdhr, origins) self.list = {} + for origin in list(self.origins.origins_dict.keys()): + self.list[origin] = {} self.get_db_channels() - def get_channel_obj(self, keyfind, valfind): - if keyfind == "number": - return next(self.list[fhdhr_id] for fhdhr_id in [x["id"] for x in self.get_channels()] if self.list[fhdhr_id].number == valfind) or None + def get_channel_obj(self, keyfind, valfind, origin=None): + if origin: + origin = origin.lower() + if keyfind == "number": + matches = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys()) if self.list[origin][x].number == valfind] + else: + matches = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys()) if self.list[origin][x].dict[keyfind] == valfind] + if len(matches): + return self.list[origin][matches[0]] else: - return next(self.list[fhdhr_id] for fhdhr_id in [x["id"] for x in self.get_channels()] if self.list[fhdhr_id].dict[keyfind] == valfind) or None + matches = [] + for origin in list(self.list.keys()): + if keyfind == "number": + matches = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys()) if self.list[origin][x].number == valfind] + else: + matches = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys()) if self.list[origin][x].dict[keyfind] == valfind] + if len(matches): + return self.list[origin][matches[0]] + if len(matches): + return self.list[origin][matches[0]] + return None - def get_channel_list(self, keyfind): - if keyfind == "number": - return [self.list[x].number for x in [x["id"] for x in self.get_channels()]] + def get_channel_list(self, keyfind, origin=None): + if origin: + if keyfind == "number": + return [self.list[origin][x].number for x in [x["id"] for x in self.get_channels(origin)]] + else: + return [self.list[origin][x].dict[keyfind] for x in [x["id"] for x in self.get_channels(origin)]] else: - return [self.list[x].dict[keyfind] for x in [x["id"] for x in self.get_channels()]] + matches = [] + for origin in list(self.list.keys()): + if keyfind == "number": + next_match = [self.list[origin][x].number for x in [x["id"] for x in self.get_channels(origin)]] + else: + next_match = [self.list[origin][x].dict[keyfind] for x in [x["id"] for x in self.get_channels(origin)]] + if len(next_match): + matches.append(next_match) + return matches[0] - def set_channel_status(self, keyfind, valfind, updatedict): - self.get_channel_obj(keyfind, valfind).set_status(updatedict) + def get_channel_dict(self, keyfind, valfind, origin=None): + chan_obj = self.get_channel_obj(keyfind, valfind, origin) + if chan_obj: + return chan_obj.dict + return None - def set_channel_enablement_all(self, enablement): - for fhdhr_id in [x["id"] for x in self.get_channels()]: - self.list[fhdhr_id].set_enablement(enablement) + def set_channel_status(self, keyfind, valfind, updatedict, origin): + self.get_channel_obj(keyfind, valfind, origin).set_status(updatedict) - def set_channel_enablement(self, keyfind, valfind, enablement): - self.get_channel_obj(keyfind, valfind).set_enablement(enablement) + def set_channel_enablement_all(self, enablement, origin): + for fhdhr_id in [x["id"] for x in self.get_channels(origin)]: + self.list[fhdhr_id].set_enablement(enablement, origin) - def set_channel_favorite(self, keyfind, valfind, enablement): - self.get_channel_obj(keyfind, valfind).set_favorite(enablement) + def set_channel_enablement(self, keyfind, valfind, enablement, origin): + self.get_channel_obj(keyfind, valfind, origin).set_enablement(enablement) - def get_db_channels(self): - self.fhdhr.logger.info("Checking for Channel information stored in the database.") - channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or [] - if len(channel_ids): - self.fhdhr.logger.info("Found %s existing channels in the database." % str(len(channel_ids))) - for channel_id in channel_ids: - channel_obj = Channel(self.fhdhr, self.id_system, channel_id=channel_id) - channel_id = channel_obj.dict["id"] - self.list[channel_id] = channel_obj + def set_channel_favorite(self, keyfind, valfind, enablement, origin): + self.get_channel_obj(keyfind, valfind, origin).set_favorite(enablement) - def save_db_channels(self): - channel_ids = [x["id"] for x in self.get_channels()] - self.fhdhr.db.set_fhdhr_value("channels", "list", channel_ids) + def get_db_channels(self, origin=None): - def get_channels(self, forceupdate=False): + if not origin: + origins_list = list(self.list.keys()) + else: + origins_list = origin.lower() + + if isinstance(origins_list, str): + origins_list = [origins_list] + + for origin in origins_list: + self.fhdhr.logger.info("Checking for %s Channel information stored in the database." % origin) + channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "list", origin) or [] + if len(channel_ids): + self.fhdhr.logger.info("Found %s existing channels in the database." % str(len(channel_ids))) + for channel_id in channel_ids: + channel_obj = Channel(self.fhdhr, self.id_system, origin=origin, channel_id=channel_id) + channel_id = channel_obj.dict["id"] + self.list[origin][channel_id] = channel_obj + + def save_db_channels(self, origin=None): + if not origin: + origins_list = list(self.list.keys()) + else: + origins_list = origin.lower() + + if isinstance(origins_list, str): + origins_list = [origins_list] + + for origin in origins_list: + channel_ids = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys())] + self.fhdhr.db.set_fhdhr_value("channels", "list", channel_ids, origin) + + def get_channels(self, origin=None, forceupdate=False): """Pull Channels from origin. Output a list. @@ -66,53 +120,64 @@ class Channels(): Don't pull more often than 12 hours. """ - if not len(list(self.list.keys())): - self.get_db_channels() + if not origin: + origins_list = list(self.list.keys()) + else: + origins_list = origin.lower().lower() - if not forceupdate: - return [self.list[x].dict for x in list(self.list.keys())] + if isinstance(origins_list, str): + origins_list = [origins_list] - channel_origin_id_list = [str(self.list[x].dict["origin_id"]) for x in list(self.list.keys())] + return_chan_list = [] + for origin in origins_list: - self.fhdhr.logger.info("Performing Channel Scan.") + if not len(list(self.list[origin].keys())): + self.get_db_channels(origin=origin) - channel_dict_list = self.origin.get_channels() - self.fhdhr.logger.info("Found %s channels for %s." % (len(channel_dict_list), self.fhdhr.config.dict["main"]["servicename"])) + if not forceupdate: + return_chan_list.extend([self.list[origin][x].dict for x in list(self.list[origin].keys())]) - self.fhdhr.logger.info("Performing Channel Import, This can take some time, Please wait.") - - newchan = 0 - chan_scan_start = time.time() - for channel_info in channel_dict_list: - - chan_existing = str(channel_info["id"]) in channel_origin_id_list - - if chan_existing: - channel_obj = self.get_channel_obj("origin_id", channel_info["id"]) else: - channel_obj = Channel(self.fhdhr, self.id_system, origin_id=channel_info["id"]) - channel_id = channel_obj.dict["id"] - channel_obj.basics(channel_info) - if not chan_existing: - self.list[channel_id] = channel_obj - newchan += 1 + channel_origin_id_list = [str(self.list[origin][x].dict["origin_id"]) for x in list(self.list[origin].keys())] - self.fhdhr.logger.info("Channel Import took %s" % humanized_time(time.time() - chan_scan_start)) + self.fhdhr.logger.info("Performing Channel Scan for %s." % origin) - if not newchan: - newchan = "no" - self.fhdhr.logger.info("Found %s NEW channels." % newchan) + channel_dict_list = self.origins.origins_dict[origin].get_channels() + self.fhdhr.logger.info("Found %s channels for %s." % (len(channel_dict_list), origin)) - self.fhdhr.logger.info("Total Channel Count: %s" % len(self.list.keys())) - self.save_db_channels() + self.fhdhr.logger.info("Performing Channel Import, This can take some time, Please wait.") - self.fhdhr.db.set_fhdhr_value("channels", "scanned_time", time.time()) + newchan = 0 + chan_scan_start = time.time() + for channel_info in channel_dict_list: - return [self.list[x].dict for x in list(self.list.keys())] + chan_existing = str(channel_info["id"]) in channel_origin_id_list - def get_channel_stream(self, stream_args): - return self.origin.get_channel_stream(self.get_channel_dict("number", stream_args["channel"]), stream_args) + if chan_existing: + channel_obj = self.get_channel_obj("origin_id", channel_info["id"], origin) + else: + channel_obj = Channel(self.fhdhr, self.id_system, origin, origin_id=channel_info["id"]) - def get_channel_dict(self, keyfind, valfind): - return self.get_channel_obj(keyfind, valfind).dict + channel_id = channel_obj.dict["id"] + channel_obj.basics(channel_info) + if not chan_existing: + self.list[origin][channel_id] = channel_obj + newchan += 1 + + self.fhdhr.logger.info("%s Channel Import took %s" % (origin, humanized_time(time.time() - chan_scan_start))) + + if not newchan: + newchan = "no" + self.fhdhr.logger.info("Found %s NEW channels for %s." % (newchan, origin)) + + self.fhdhr.logger.info("Total %s Channel Count: %s" % (origin, len(self.list[origin].keys()))) + self.save_db_channels(origin=origin) + + self.fhdhr.db.set_fhdhr_value("channels", "scanned_time", time.time(), origin) + return_chan_list.extend([self.list[origin][x].dict for x in list(self.list[origin].keys())]) + + return return_chan_list + + def get_channel_stream(self, stream_args, origin): + return self.origins.origins_dict[origin].get_channel_stream(self.get_channel_dict("number", stream_args["channel"]), stream_args) diff --git a/fHDHR/device/channels/chan_ident.py b/fHDHR/device/channels/chan_ident.py index face34c..ce98ba8 100644 --- a/fHDHR/device/channels/chan_ident.py +++ b/fHDHR/device/channels/chan_ident.py @@ -2,31 +2,32 @@ import uuid class Channel_IDs(): - def __init__(self, fhdhr): + def __init__(self, fhdhr, origins): self.fhdhr = fhdhr + self.origins = origins - def get(self, origin_id): - existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or [] - existing_channel_info = [self.fhdhr.db.get_channel_value(channel_id, "dict") or {} for channel_id in existing_ids] + def get(self, origin_id, origin): + existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list", origin) or [] + existing_channel_info = [self.fhdhr.db.get_fhdhr_value(channel_id, "dict", origin) or {} for channel_id in existing_ids] for existing_channel in existing_channel_info: if existing_channel["origin_id"] == origin_id: return existing_channel["id"] - return self.assign() + return self.assign(origin) - def assign(self): - existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or [] + def assign(self, origin): + existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list", origin) or [] channel_id = None while not channel_id: unique_id = str(uuid.uuid4()) if str(unique_id) not in existing_ids: channel_id = str(unique_id) existing_ids.append(channel_id) - self.fhdhr.db.set_fhdhr_value("channels", "list", existing_ids) + self.fhdhr.db.set_fhdhr_value("channels", "list", existing_ids, origin) return channel_id - def get_number(self, channel_id): - existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or [] - existing_channel_info = [self.fhdhr.db.get_channel_value(channel_id, "dict") or {} for channel_id in existing_ids] + def get_number(self, channel_id, origin): + existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list", origin) or [] + existing_channel_info = [self.fhdhr.db.get_fhdhr_value(channel_id, "dict", origin) or {} for channel_id in existing_ids] cnumber = [existing_channel["number"] for existing_channel in existing_channel_info if existing_channel["id"] == channel_id] or None if cnumber: return cnumber diff --git a/fHDHR/device/channels/channel.py b/fHDHR/device/channels/channel.py index debc7e8..ae9e11f 100644 --- a/fHDHR/device/channels/channel.py +++ b/fHDHR/device/channels/channel.py @@ -3,22 +3,23 @@ import time class Channel(): - def __init__(self, fhdhr, id_system, origin_id=None, channel_id=None): + def __init__(self, fhdhr, id_system, origin, origin_id=None, channel_id=None): self.fhdhr = fhdhr + self.origin = origin self.id_system = id_system if not channel_id: if origin_id: - channel_id = id_system.get(origin_id) + channel_id = id_system.get(origin_id, origin) else: - channel_id = id_system.assign() + channel_id = id_system.assign(origin) self.channel_id = channel_id - self.dict = self.fhdhr.db.get_channel_value(str(channel_id), "dict") or self.default_dict + self.dict = self.fhdhr.db.get_fhdhr_value(str(channel_id), "dict", self.origin) or self.default_dict self.verify_dict() - self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict) + self.fhdhr.db.set_fhdhr_value(self.dict["id"], "dict", self.dict, self.origin) @property def number(self): @@ -96,9 +97,9 @@ class Channel(): self.dict["tags"] = self.dict["origin_tags"] if "number" not in list(channel_info.keys()): - channel_info["number"] = self.id_system.get_number(channel_info["id"]) + channel_info["number"] = self.id_system.get_number(channel_info["id"], self.origin) elif not channel_info["number"]: - channel_info["number"] = self.id_system.get_number(channel_info["id"]) + channel_info["number"] = self.id_system.get_number(channel_info["id"], self.origin) self.dict["origin_number"] = str(channel_info["number"]) if not self.dict["number"]: self.dict["number"] = self.dict["origin_number"].split(".")[0] @@ -128,7 +129,7 @@ class Channel(): if "created" not in list(self.dict.keys()): self.dict["created"] = time.time() - self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict) + self.fhdhr.db.set_fhdhr_value(self.dict["id"], "dict", self.dict, self.origin) @property def default_dict(self): @@ -144,64 +145,37 @@ class Channel(): } def destroy(self): - self.fhdhr.db.delete_channel_value(self.dict["id"], "dict") + self.fhdhr.db.delete_fhdhr_value(self.dict["id"], "dict", self.origin) channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or [] if self.dict["id"] in channel_ids: channel_ids.remove(self.dict["id"]) - self.fhdhr.db.set_fhdhr_value("channels", "list", channel_ids) + self.fhdhr.db.set_fhdhr_value("channels", "list", channel_ids, self.origin) def set_status(self, updatedict): for key in list(updatedict.keys()): if key == "number": updatedict[key] = str(updatedict[key]) self.dict[key] = updatedict[key] - self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict) - - @property - def lineup_dict(self): - return { - 'GuideNumber': self.number, - 'GuideName': self.dict['name'], - 'Tags': ",".join(self.dict['tags']), - 'URL': self.hdhr_stream_url, - 'HD': self.dict["HD"], - "Favorite": self.dict["favorite"], - } + self.fhdhr.db.set_fhdhr_value(self.dict["id"], "dict", self.dict, self.origin) @property def generic_image_url(self): return "/api/images?method=generate&type=channel&message=%s" % self.number - @property - def hdhr_stream_url(self): - return '/auto/%s' % self.hdhr_stream_ident - - @property - def hdhr_stream_ident(self): - return 'v%s' % self.number - - @property - def rmg_stream_url(self): - return "/devices/%s/media/%s" % (self.fhdhr.config.dict["main"]["uuid"], self.rmg_stream_ident) - - @property - def rmg_stream_ident(self): - return "id://%s" % self.number - @property def api_stream_url(self): - return '/api/tuners?method=%s&channel=%s' % (self.fhdhr.config.dict["streaming"]["method"], self.number) + return '/api/tuners?method=%s&channel=%s&origin=%s' % (self.fhdhr.config.dict["streaming"]["method"], self.dict["id"], self.origin) @property - def m3u_url(self): - return '/api/m3u?method=get&channel=%s' % self.number + def api_m3u_url(self): + return '/api/m3u?method=get&channel=%s&origin=%s' % (self.dict["id"], self.origin) def set_favorite(self, enablement): if enablement == "+": self.dict["favorite"] = 1 - elif enablement == "+": + elif enablement == "-": self.dict["favorite"] = 0 - self.fhdhr.db.set_channel_value(self.dict["id"], "info", self.dict) + self.fhdhr.db.set_fhdhr_value(self.dict["id"], "info", self.dict, self.origin) def set_enablement(self, enablement): if enablement == "disable": @@ -213,7 +187,7 @@ class Channel(): self.dict["enabled"] = False else: self.dict["enabled"] = True - self.fhdhr.db.set_channel_value(self.dict["id"], "info", self.dict) + self.fhdhr.db.set_fhdhr_value(self.dict["id"], "info", self.dict, self.origin) def __getattr__(self, name): ''' will only get called for undefined attributes ''' diff --git a/fHDHR/device/cluster.py b/fHDHR/device/cluster.py deleted file mode 100644 index cbddd2c..0000000 --- a/fHDHR/device/cluster.py +++ /dev/null @@ -1,158 +0,0 @@ -from collections import OrderedDict - - -class fHDHR_Cluster(): - - def __init__(self, fhdhr, ssdp): - self.fhdhr = fhdhr - - self.ssdp = ssdp - - self.friendlyname = self.fhdhr.config.dict["fhdhr"]["friendlyname"] - - if self.fhdhr.config.dict["fhdhr"]["discovery_address"]: - self.startup_sync() - - def cluster(self): - return self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster() - - def get_cluster_dicts_web(self): - fhdhr_list = self.cluster() - locations = [] - for location in list(fhdhr_list.keys()): - item_dict = { - "base_url": fhdhr_list[location]["base_url"], - "name": fhdhr_list[location]["name"] - } - if item_dict["base_url"] != self.fhdhr.api.base: - locations.append(item_dict) - if len(locations): - locations = sorted(locations, key=lambda i: i['name']) - return locations - else: - return None - - def get_list(self): - cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster() - return_dict = {} - for location in list(cluster.keys()): - if location != self.fhdhr.api.base: - return_dict[location] = { - "Joined": True - } - - detected_list = self.ssdp.detect_method.get() - for location in detected_list: - if location not in list(cluster.keys()): - return_dict[location] = { - "Joined": False - } - return_dict = OrderedDict(sorted(return_dict.items())) - return return_dict - - def default_cluster(self): - defdict = {} - defdict[self.fhdhr.api.base] = { - "base_url": self.fhdhr.api.base, - "name": self.friendlyname - } - return defdict - - def startup_sync(self): - self.fhdhr.logger.info("Syncronizing with Cluster.") - cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster() - if not len(list(cluster.keys())): - self.fhdhr.logger.info("No Cluster Found.") - else: - self.fhdhr.logger.info("Found %s clustered services." % str(len(list(cluster.keys())))) - for location in list(cluster.keys()): - if location != self.fhdhr.api.base: - self.fhdhr.logger.debug("Checking Cluster Syncronization information from %s." % location) - sync_url = "%s/api/cluster?method=get" % location - try: - sync_open = self.fhdhr.web.session.get(sync_url) - retrieved_cluster = sync_open.json() - if self.fhdhr.api.base not in list(retrieved_cluster.keys()): - return self.leave() - except self.fhdhr.web.exceptions.ConnectionError: - self.fhdhr.logger.error("Unreachable: %s" % location) - - def leave(self): - self.fhdhr.logger.info("Leaving cluster.") - self.fhdhr.db.set_fhdhr_value("cluster", "dict", self.default_cluster()) - - def disconnect(self): - cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster() - for location in list(cluster.keys()): - if location != self.fhdhr.api.base: - self.fhdhr.logger.info("Informing %s that I am departing the Cluster." % location) - sync_url = "%s/api/cluster?method=del&location=%s" % (location, self.fhdhr.api.base) - try: - self.fhdhr.web.session.get(sync_url) - except self.fhdhr.web.exceptions.ConnectionError: - self.fhdhr.logger.error("Unreachable: %s" % location) - self.leave() - - def sync(self, location): - sync_url = "%s/api/cluster?method=get" % location - try: - sync_open = self.fhdhr.web.session.get(sync_url) - self.fhdhr.db.set_fhdhr_value("cluster", "dict", sync_open.json()) - except self.fhdhr.web.exceptions.ConnectionError: - self.fhdhr.logger.error("Unreachable: %s" % location) - - def push_sync(self): - cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster() - for location in list(cluster.keys()): - if location != self.fhdhr.api.base: - sync_url = "%s/api/cluster?method=sync&location=%s" % (location, self.fhdhr.api.base_quoted) - try: - self.fhdhr.web.session.get(sync_url) - except self.fhdhr.web.exceptions.ConnectionError: - self.fhdhr.logger.error("Unreachable: %s" % location) - - def add(self, location): - cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster() - if location not in list(cluster.keys()): - self.fhdhr.logger.info("Adding %s to cluster." % location) - cluster[location] = {"base_url": location} - - location_info_url = "%s/hdhr/discover.json" % location - try: - location_info_req = self.fhdhr.web.session.get(location_info_url) - except self.fhdhr.web.exceptions.ConnectionError: - self.fhdhr.logger.error("Unreachable: %s" % location) - del cluster[location] - self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster) - return - location_info = location_info_req.json() - cluster[location]["name"] = location_info["FriendlyName"] - - cluster_info_url = "%s/api/cluster?method=get" % location - try: - cluster_info_req = self.fhdhr.web.session.get(cluster_info_url) - except self.fhdhr.web.exceptions.ConnectionError: - self.fhdhr.logger.error("Unreachable: %s" % location) - del cluster[location] - self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster) - return - cluster_info = cluster_info_req.json() - for cluster_key in list(cluster_info.keys()): - if cluster_key not in list(cluster.keys()): - cluster[cluster_key] = cluster_info[cluster_key] - - self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster) - self.push_sync() - - def remove(self, location): - cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster() - if location in list(cluster.keys()): - self.fhdhr.logger.info("Removing %s from cluster." % location) - del cluster[location] - sync_url = "%s/api/cluster?method=leave" % location - try: - self.fhdhr.web.session.get(sync_url) - except self.fhdhr.web.exceptions.ConnectionError: - self.fhdhr.logger.error("Unreachable: %s" % location) - self.push_sync() - self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster) diff --git a/fHDHR/device/epg/__init__.py b/fHDHR/device/epg/__init__.py index e08cd24..9a8ddd1 100644 --- a/fHDHR/device/epg/__init__.py +++ b/fHDHR/device/epg/__init__.py @@ -9,23 +9,19 @@ from .blocks import blocksEPG class EPG(): - def __init__(self, fhdhr, channels, originwrapper, plugins): + def __init__(self, fhdhr, channels, origins): self.fhdhr = fhdhr - self.origin = originwrapper + self.origins = origins self.channels = channels - self.plugins = plugins self.epgdict = {} - self.epg_methods = self.fhdhr.config.dict["epg"]["method"] - self.valid_epg_methods = [x for x in self.fhdhr.config.dict["epg"]["valid_methods"] if x and x not in [None, "None"]] + self.epg_methods = self.fhdhr.config.dict["epg"]["method"] or [] + self.valid_epg_methods = [x for x in list(self.fhdhr.config.dict["epg"]["valid_methods"].keys()) if x and x not in [None, "None"]] - self.blocks = blocksEPG(self.fhdhr, self.channels) - self.epg_handling = { - "origin": self.origin, - "blocks": self.blocks, - } + self.blocks = blocksEPG(self.fhdhr, self.channels, self.origins, None) + self.epg_handling = {} self.epg_method_selfadd() self.def_method = self.fhdhr.config.dict["epg"]["def_method"] @@ -44,16 +40,14 @@ class EPG(): def clear_epg_cache(self, method=None): if not method: + if not self.def_method: + return + if method not in self.valid_epg_methods: + if not self.def_method: + return method = self.def_method - if (method == self.fhdhr.config.dict["main"]["dictpopname"] or - method not in self.fhdhr.config.dict["epg"]["valid_methods"]): - method = "origin" - epgtypename = method - if method in [self.fhdhr.config.dict["main"]["dictpopname"], "origin"]: - epgtypename = self.fhdhr.config.dict["main"]["dictpopname"] - - self.fhdhr.logger.info("Clearing %s EPG cache." % epgtypename) + self.fhdhr.logger.info("Clearing %s EPG cache." % method) if hasattr(self.epg_handling[method], 'clear_cache'): self.epg_handling[method].clear_cache() @@ -66,11 +60,17 @@ class EPG(): def whats_on_now(self, channel_number, method=None, chan_obj=None, chan_dict=None): nowtime = time.time() epgdict = self.get_epg(method) - try: - listings = epgdict[channel_number]["listing"] - except KeyError: - listings = [] - for listing in listings: + if channel_number not in list(epgdict.keys()): + epgdict[channel_number] = { + "callsign": "", + "name": "", + "number": str(channel_number), + "id": "", + "thumbnail": "", + "listing": [] + } + + for listing in epgdict[channel_number]["listing"]: for time_item in ["time_start", "time_end"]: time_value = listing[time_item] if str(time_value).endswith("+00:00"): @@ -90,16 +90,19 @@ class EPG(): def whats_on_allchans(self, method=None): if not method: + if not self.def_method: + return + method = self.def_method + if method not in self.valid_epg_methods: + if not self.def_method: + return method = self.def_method - if (method == self.fhdhr.config.dict["main"]["dictpopname"] or - method not in self.fhdhr.config.dict["epg"]["valid_methods"]): - method = "origin" channel_guide_dict = {} epgdict = self.get_epg(method) epgdict = epgdict.copy() for c in list(epgdict.keys()): - if method in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: + if method in [origin for origin in list(self.origins.origins_dict.keys())]: chan_obj = self.channels.get_channel_obj("origin_id", epgdict[c]["id"]) channel_number = chan_obj.number epgdict[channel_number] = epgdict.pop(c) @@ -119,10 +122,13 @@ class EPG(): def get_epg(self, method=None): if not method: + if not self.def_method: + return + method = self.def_method + if method not in self.valid_epg_methods: + if not self.def_method: + return method = self.def_method - if (method == self.fhdhr.config.dict["main"]["dictpopname"] or - method not in self.fhdhr.config.dict["epg"]["valid_methods"]): - method = "origin" if method in list(self.epgdict.keys()): return self.epgdict[method] @@ -153,28 +159,29 @@ class EPG(): return next(item for item in event_list if item["id"] == event_id) or None def epg_method_selfadd(self): - new_epgtype_list = [self.plugins.plugin_dict[x]["NAME"] for x in list(self.plugins.plugin_dict.keys()) if self.plugins.plugin_dict[x]["TYPE"] == "alt_epg"] - for method in new_epgtype_list: - self.epg_handling[method] = eval("self.plugins.%sEPG(self.fhdhr, self.channels)" % method) + for plugin_name in list(self.fhdhr.plugins.plugins.keys()): + if self.fhdhr.plugins.plugins[plugin_name].type == "alt_epg": + method = self.fhdhr.plugins.plugins[plugin_name].name.lower() + self.epg_handling[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(self.channels, self.fhdhr.plugins.plugins[plugin_name].plugin_utils) + for origin in list(self.origins.origins_dict.keys()): + if origin.lower() not in list(self.epg_handling.keys()): + self.epg_handling[origin.lower()] = blocksEPG(self.fhdhr, self.channels, self.origins, origin) + self.fhdhr.config.register_valid_epg_method(origin, "Blocks") + self.valid_epg_methods.append(origin.lower()) def update(self, method=None): - if (not method or - method not in self.fhdhr.config.dict["epg"]["valid_methods"]): + if not method: + if not self.def_method: + return + method = self.def_method + if method not in self.valid_epg_methods: + if not self.def_method: + return method = self.def_method - if method == self.fhdhr.config.dict["main"]["dictpopname"]: - method = "origin" - - epgtypename = method - if method in [self.fhdhr.config.dict["main"]["dictpopname"], "origin"]: - epgtypename = self.fhdhr.config.dict["main"]["dictpopname"] - - self.fhdhr.logger.info("Updating %s EPG cache." % epgtypename) - if method == 'origin': - programguide = self.epg_handling['origin'].update_epg(self.channels) - else: - programguide = self.epg_handling[method].update_epg() + self.fhdhr.logger.info("Updating %s EPG cache." % method) + programguide = self.epg_handling[method].update_epg() # sort the channel listings by time stamp for cnum in list(programguide.keys()): @@ -190,7 +197,7 @@ class EPG(): clean_prog_guide[cnum] = programguide[cnum].copy() clean_prog_guide[cnum]["listing"] = [] - if method in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: + if method in [origin for origin in list(self.origins.origins_dict.keys())]: chan_obj = self.channels.get_channel_obj("origin_id", programguide[cnum]["id"]) else: chan_obj = None @@ -251,10 +258,10 @@ class EPG(): programguide = clean_prog_guide.copy() # if a stock method, generate Blocks EPG for missing channels - if method in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: + if method in [origin for origin in list(self.origins.origins_dict.keys())]: timestamps = self.blocks.timestamps - for fhdhr_id in [x["id"] for x in self.channels.get_channels()]: - chan_obj = self.channels.list[fhdhr_id] + for fhdhr_id in [x["id"] for x in self.channels.get_channels(method)]: + chan_obj = self.channels.get_channel_obj("id", fhdhr_id, method) if str(chan_obj.number) not in list(programguide.keys()): programguide[str(chan_obj.number)] = chan_obj.epgdict clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_obj=chan_obj) @@ -285,7 +292,7 @@ class EPG(): self.epgdict[method] = sorted_chan_guide self.fhdhr.db.set_fhdhr_value("epg_dict", method, programguide) self.fhdhr.db.set_fhdhr_value("update_time", method, time.time()) - self.fhdhr.logger.info("Wrote %s EPG cache. %s Programs for %s Channels" % (epgtypename, total_programs, total_channels)) + self.fhdhr.logger.info("Wrote %s EPG cache. %s Programs for %s Channels" % (method, total_programs, total_channels)) def start(self): self.fhdhr.logger.info("EPG Update Thread Starting") diff --git a/fHDHR/device/epg/blocks.py b/fHDHR/device/epg/blocks.py index 33ae70d..52e8247 100644 --- a/fHDHR/device/epg/blocks.py +++ b/fHDHR/device/epg/blocks.py @@ -3,18 +3,19 @@ import datetime class blocksEPG(): - def __init__(self, fhdhr, channels): + def __init__(self, fhdhr, channels, origins, origin): self.fhdhr = fhdhr - self.channels = channels + self.origins = origins + self.origin = origin def update_epg(self): programguide = {} timestamps = self.timestamps - for fhdhr_id in [x["id"] for x in self.channels.get_channels()]: - chan_obj = self.channels.list[fhdhr_id] + for fhdhr_id in [x["id"] for x in self.channels.get_channels(self.origin)]: + chan_obj = self.channels.get_channel_obj("id", fhdhr_id, self.origin) if str(chan_obj.number) not in list(programguide.keys()): programguide[str(chan_obj.number)] = chan_obj.epgdict diff --git a/fHDHR/device/ssdp/__init__.py b/fHDHR/device/ssdp/__init__.py index 7c70400..c44a3ba 100644 --- a/fHDHR/device/ssdp/__init__.py +++ b/fHDHR/device/ssdp/__init__.py @@ -4,22 +4,20 @@ import struct import time import threading -from .ssdp_detect import fHDHR_Detect -from .rmg_ssdp import RMG_SSDP -from .hdhr_ssdp import HDHR_SSDP - class SSDPServer(): def __init__(self, fhdhr): self.fhdhr = fhdhr - self.detect_method = fHDHR_Detect(fhdhr) - - self.fhdhr.threads["ssdp"] = threading.Thread(target=self.run) + self.ssdp_handling = {} + self.methods = [x for x in list(self.fhdhr.plugins.plugins.keys()) if self.fhdhr.plugins.plugins[x].type == "ssdp"] if (self.fhdhr.config.dict["fhdhr"]["discovery_address"] and - self.fhdhr.config.dict["ssdp"]["enabled"]): + self.fhdhr.config.dict["ssdp"]["enabled"] and + len(self.methods)): + + self.fhdhr.threads["ssdp"] = threading.Thread(target=self.run) self.setup_ssdp() self.sock.bind((self.bind_address, 1900)) @@ -29,12 +27,18 @@ class SSDPServer(): self.max_age = int(fhdhr.config.dict["ssdp"]["max_age"]) self.age_time = None - self.rmg_ssdp = RMG_SSDP(fhdhr, self.broadcast_ip, self.max_age) - self.hdhr_ssdp = HDHR_SSDP(fhdhr, self.broadcast_ip, self.max_age) + self.ssdp_method_selfadd() self.do_alive() self.m_search() + def ssdp_method_selfadd(self): + for plugin_name in list(self.fhdhr.plugins.plugins.keys()): + if self.fhdhr.plugins.plugins[plugin_name].type == "ssdp": + method = self.fhdhr.plugins.plugins[plugin_name].name.lower() + plugin_utils = self.fhdhr.plugins.plugins[plugin_name].plugin_utils + self.ssdp_handling[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(self.fhdhr, plugin_utils, self.broadcast_ip, self.max_age) + def start(self): self.fhdhr.logger.info("SSDP Server Starting") self.fhdhr.threads["ssdp"].start() @@ -62,21 +66,22 @@ class SSDPServer(): if send_alive: self.fhdhr.logger.info("Sending Alive message to network.") - self.do_notify(self.broadcase_address_tuple) + self.do_notify(self.broadcast_address_tuple) self.age_time = time.time() def do_notify(self, address): notify_list = [] - - hdhr_notify = self.hdhr_ssdp.get() - notify_list.append(hdhr_notify) - - if self.fhdhr.config.dict["rmg"]["enabled"]: - rmg_notify = self.rmg_ssdp.get() - notify_list.append(rmg_notify) + for ssdp_handler in list(self.ssdp_handling.keys()): + if self.ssdp_handling[ssdp_handler].enabled and hasattr(self.ssdp_handling[ssdp_handler], 'notify'): + notify_data = self.ssdp_handling[ssdp_handler].notify + if isinstance(notify_data, list): + notify_list.extend(notify_data) + else: + notify_list.append(notify_data) for notifydata in notify_list: + notifydata = notifydata.encode("utf-8") self.fhdhr.logger.debug("Created {}".format(notifydata)) try: @@ -103,6 +108,10 @@ class SSDPServer(): headers = [x.split(':', 1) for x in lines] headers = dict(map(lambda x: (x[0].lower(), x[1]), headers)) + for ssdp_handler in list(self.ssdp_handling.keys()): + if self.ssdp_handling[ssdp_handler].enabled and hasattr(self.ssdp_handling[ssdp_handler], 'on_recv'): + self.ssdp_handling[ssdp_handler].on_recv(headers, cmd, list(self.ssdp_handling.keys())) + if cmd[0] == 'M-SEARCH' and cmd[1] == '*': # SSDP discovery self.fhdhr.logger.debug("Received qualifying M-SEARCH from {}".format(address)) @@ -110,26 +119,14 @@ class SSDPServer(): self.do_notify(address) - elif cmd[0] == 'NOTIFY' and cmd[1] == '*': - # SSDP presence + if cmd[0] == 'NOTIFY' and cmd[1] == '*': self.fhdhr.logger.debug("NOTIFY data: {}".format(headers)) - try: - if headers["server"].startswith("fHDHR"): - savelocation = headers["location"].split("/device.xml")[0] - if savelocation.endswith("/hdhr"): - savelocation = savelocation.replace("/hdhr", '') - elif savelocation.endswith("/rmg"): - savelocation = savelocation.replace("/rmg", '') - if savelocation != self.fhdhr.api.base: - self.detect_method.set(savelocation) - except KeyError: - return else: self.fhdhr.logger.debug('Unknown SSDP command %s %s' % (cmd[0], cmd[1])) def m_search(self): data = self.msearch_payload - self.sock.sendto(data, self.broadcase_address_tuple) + self.sock.sendto(data, self.broadcast_address_tuple) def create_msearch_payload(self): @@ -176,7 +173,7 @@ class SSDPServer(): if self.proto == "ipv4": self.af_type = socket.AF_INET self.broadcast_ip = "239.255.255.250" - self.broadcase_address_tuple = (self.broadcast_ip, 1900) + self.broadcast_address_tuple = (self.broadcast_ip, 1900) self.bind_address = "0.0.0.0" elif self.proto == "ipv6": self.af_type = socket.AF_INET6 diff --git a/fHDHR/device/ssdp/rmg_ssdp.py b/fHDHR/device/ssdp/rmg_ssdp.py deleted file mode 100644 index 5b5c64f..0000000 --- a/fHDHR/device/ssdp/rmg_ssdp.py +++ /dev/null @@ -1,49 +0,0 @@ - - -class RMG_SSDP(): - - def __init__(self, fhdhr, broadcast_ip, max_age): - self.fhdhr = fhdhr - - self.ssdp_content = None - - self.broadcast_ip = broadcast_ip - self.device_xml_path = '/rmg/device.xml' - - self.cable_schema = "urn:schemas-opencable-com:service:Security:1" - self.ota_schema = "urn:schemas-upnp-org:device-1-0" - - if self.fhdhr.config.dict["fhdhr"]["reporting_tuner_type"].lower() == "antenna": - self.schema = self.ota_schema - elif self.fhdhr.config.dict["fhdhr"]["reporting_tuner_type"].lower() == "cable": - self.schema = self.cable_schema - else: - self.schema = self.ota_schema - - self.max_age = max_age - - def get(self): - if self.ssdp_content: - return self.ssdp_content.encode("utf-8") - - data = '' - data_command = "NOTIFY * HTTP/1.1" - - data_dict = { - "HOST": "%s:%s" % ("239.255.255.250", 1900), - "NT": self.schema, - "NTS": "ssdp:alive", - "USN": 'uuid:%s::%s' % (self.fhdhr.config.dict["main"]["uuid"], self.schema), - "SERVER": 'fHDHR/%s UPnP/1.0' % self.fhdhr.version, - "LOCATION": "%s%s" % (self.fhdhr.api.base, self.device_xml_path), - "AL": "%s%s" % (self.fhdhr.api.base, self.device_xml_path), - "Cache-Control:max-age=": self.max_age - } - - data += "%s\r\n" % data_command - for data_key in list(data_dict.keys()): - data += "%s:%s\r\n" % (data_key, data_dict[data_key]) - data += "\r\n" - - self.ssdp_content = data - return data.encode("utf-8") diff --git a/fHDHR/device/ssdp/ssdp_detect.py b/fHDHR/device/ssdp/ssdp_detect.py deleted file mode 100644 index e9800b5..0000000 --- a/fHDHR/device/ssdp/ssdp_detect.py +++ /dev/null @@ -1,16 +0,0 @@ - - -class fHDHR_Detect(): - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - self.fhdhr.db.delete_fhdhr_value("ssdp_detect", "list") - - def set(self, location): - detect_list = self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or [] - if location not in detect_list: - detect_list.append(location) - self.fhdhr.db.set_fhdhr_value("ssdp_detect", "list", detect_list) - - def get(self): - return self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or [] diff --git a/fHDHR/device/tuners/__init__.py b/fHDHR/device/tuners/__init__.py index 720641d..d67b3f2 100644 --- a/fHDHR/device/tuners/__init__.py +++ b/fHDHR/device/tuners/__init__.py @@ -7,93 +7,117 @@ from .tuner import Tuner class Tuners(): - def __init__(self, fhdhr, epg, channels, plugins): + def __init__(self, fhdhr, epg, channels): self.fhdhr = fhdhr self.channels = channels - self.plugins = plugins self.epg = epg - self.max_tuners = int(self.fhdhr.config.dict["fhdhr"]["tuner_count"]) self.tuners = {} + for origin in list(self.fhdhr.origins.origins_dict.keys()): + self.tuners[origin] = {} - self.fhdhr.logger.info("Creating %s tuners." % str(self.max_tuners)) + max_tuners = int(self.fhdhr.origins.origins_dict[origin].tuners) - for i in range(0, self.max_tuners): - self.tuners[str(i)] = Tuner(fhdhr, i, epg, plugins) + self.fhdhr.logger.info("Creating %s tuners for %s." % (max_tuners, origin)) - def get_available_tuner(self): - return next(tunernum for tunernum in list(self.tuners.keys()) if not self.tuners[tunernum].tuner_lock.locked()) or None + for i in range(0, max_tuners): + self.tuners[origin][str(i)] = Tuner(fhdhr, i, epg, origin) - def get_scanning_tuner(self): - return next(tunernum for tunernum in list(self.tuners.keys()) if self.tuners[tunernum].status["status"] == "Scanning") or None + self.alt_stream_handlers = {} - def stop_tuner_scan(self): - tunernum = self.get_scanning_tuner() + def alt_stream_methods_selfadd(self): + for plugin_name in list(self.fhdhr.plugins.plugins.keys()): + if self.fhdhr.plugins.plugins[plugin_name].type == "alt_stream": + method = self.fhdhr.plugins.plugins[plugin_name].name + self.alt_stream_handlers[method] = self.fhdhr.plugins.plugins[plugin_name] + + def get_available_tuner(self, origin): + return next(tunernum for tunernum in list(self.tuners[origin].keys()) if not self.tuners[origin][tunernum].tuner_lock.locked()) or None + + def get_scanning_tuner(self, origin): + return next(tunernum for tunernum in list(self.tuners[origin].keys()) if self.tuners[origin][tunernum].status["status"] == "Scanning") or None + + def stop_tuner_scan(self, origin): + tunernum = self.get_scanning_tuner(origin) if tunernum: - self.tuners[str(tunernum)].close() + self.tuners[origin][str(tunernum)].close() - def tuner_scan(self): + def tuner_scan(self, origin="all"): """Temporarily use a tuner for a scan""" - if not self.available_tuner_count(): - raise TunerError("805 - All Tuners In Use") - tunernumber = self.get_available_tuner() - self.tuners[str(tunernumber)].channel_scan() + if origin == "all": + origins = list(self.tuners.keys()) + else: + origins = [origin] - if not tunernumber: - raise TunerError("805 - All Tuners In Use") + for origin in origins: - def tuner_grab(self, tuner_number, channel_number): + if not self.available_tuner_count(origin): + raise TunerError("805 - All Tuners In Use") - if str(tuner_number) not in list(self.tuners.keys()): - self.fhdhr.logger.error("Tuner %s does not exist." % str(tuner_number)) + tunernumber = self.get_available_tuner(origin) + self.tuners[str(tunernumber)].channel_scan(origin) + + if not tunernumber: + raise TunerError("805 - All Tuners In Use") + + def tuner_grab(self, tuner_number, origin, channel_number): + + if str(tuner_number) not in list(self.tuners[origin].keys()): + self.fhdhr.logger.error("Tuner %s does not exist for %s." % (tuner_number, origin)) raise TunerError("806 - Tune Failed") # TunerError will raise if unavailable - self.tuners[str(tuner_number)].grab(channel_number) + self.tuners[origin][str(tuner_number)].grab(origin, channel_number) return tuner_number - def first_available(self, channel_number, dograb=True): + def first_available(self, origin, channel_number, dograb=True): - if not self.available_tuner_count(): + if not self.available_tuner_count(origin): raise TunerError("805 - All Tuners In Use") - tunernumber = self.get_available_tuner() + tunernumber = self.get_available_tuner(origin) if not tunernumber: raise TunerError("805 - All Tuners In Use") else: - self.tuners[str(tunernumber)].grab(channel_number) + self.tuners[origin][str(tunernumber)].grab(origin, channel_number) return tunernumber - def tuner_close(self, tunernum): - self.tuners[str(tunernum)].close() + def tuner_close(self, tunernum, origin): + self.tuners[origin][str(tunernum)].close() - def status(self): + def status(self, origin=None): all_status = {} - for tunernum in list(self.tuners.keys()): - all_status[tunernum] = self.tuners[str(tunernum)].get_status() + if origin: + for tunernum in list(self.tuners[origin].keys()): + all_status[tunernum] = self.tuners[origin][str(tunernum)].get_status() + else: + for origin in list(self.tuners.keys()): + all_status[origin] = {} + for tunernum in list(self.tuners[origin].keys()): + all_status[origin][tunernum] = self.tuners[origin][str(tunernum)].get_status() return all_status - def available_tuner_count(self): + def available_tuner_count(self, origin): available_tuners = 0 - for tunernum in list(self.tuners.keys()): - if not self.tuners[str(tunernum)].tuner_lock.locked(): + for tunernum in list(self.tuners[origin].keys()): + if not self.tuners[origin][str(tunernum)].tuner_lock.locked(): available_tuners += 1 return available_tuners - def inuse_tuner_count(self): + def inuse_tuner_count(self, origin): inuse_tuners = 0 - for tunernum in list(self.tuners.keys()): - if self.tuners[str(tunernum)].tuner_lock.locked(): + for tunernum in list(self.tuners[origin].keys()): + if self.tuners[origin][str(tunernum)].tuner_lock.locked(): inuse_tuners += 1 return inuse_tuners def get_stream_info(self, stream_args): - stream_info = self.channels.get_channel_stream(stream_args) + stream_info = self.channels.get_channel_stream(stream_args, stream_args["origin"]) if not stream_info: raise TunerError("806 - Tune Failed") @@ -143,10 +167,14 @@ class Tuners(): while True: self.fhdhr.logger.info("Opening m3u8 for reading %s" % m3u8_url) - if stream_args["stream_info"]["headers"]: - videoUrlM3u = m3u8.load(m3u8_url, headers=stream_args["stream_info"]["headers"]) - else: - videoUrlM3u = m3u8.load(m3u8_url) + try: + if stream_args["stream_info"]["headers"]: + videoUrlM3u = m3u8.load(m3u8_url, headers=stream_args["stream_info"]["headers"]) + else: + videoUrlM3u = m3u8.load(m3u8_url) + except Exception as e: + self.fhdhr.logger.info("m3u8 load error: %s" % e) + return m3u8_url if len(videoUrlM3u.playlists): self.fhdhr.logger.info("%s m3u8 varients found" % len(videoUrlM3u.playlists)) diff --git a/fHDHR/device/tuners/stream/__init__.py b/fHDHR/device/tuners/stream/__init__.py index a145f56..8df96ab 100644 --- a/fHDHR/device/tuners/stream/__init__.py +++ b/fHDHR/device/tuners/stream/__init__.py @@ -6,20 +6,18 @@ from .direct_m3u8_stream import Direct_M3U8_Stream class Stream(): - def __init__(self, fhdhr, stream_args, tuner, plugins): + def __init__(self, fhdhr, stream_args, tuner): self.fhdhr = fhdhr self.stream_args = stream_args - self.plugins = plugins - if stream_args["method"] == "direct": if self.stream_args["true_content_type"].startswith(tuple(["application/", "text/"])): self.method = Direct_M3U8_Stream(fhdhr, stream_args, tuner) else: self.method = Direct_Stream(fhdhr, stream_args, tuner) else: - - self.method = eval("self.plugins.%s_Stream(fhdhr, stream_args, tuner)" % stream_args["method"].upper()) + plugin_name = self.fhdhr.config.dict["streaming"]["valid_methods"][stream_args["method"]]["plugin"] + self.method = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(self.fhdhr.plugins.plugins[plugin_name].plugin_utils, stream_args, tuner) def get(self): return self.method.get() diff --git a/fHDHR/device/tuners/stream/direct_m3u8_stream.py b/fHDHR/device/tuners/stream/direct_m3u8_stream.py index c438061..4572828 100644 --- a/fHDHR/device/tuners/stream/direct_m3u8_stream.py +++ b/fHDHR/device/tuners/stream/direct_m3u8_stream.py @@ -34,10 +34,15 @@ class Direct_M3U8_Stream(): while self.tuner.tuner_lock.locked(): - if self.stream_args["stream_info"]["headers"]: - playlist = m3u8.load(self.stream_args["stream_info"]["url"], headers=self.stream_args["stream_info"]["headers"]) - else: - playlist = m3u8.load(self.stream_args["stream_info"]["url"]) + try: + if self.stream_args["stream_info"]["headers"]: + playlist = m3u8.load(self.stream_args["stream_info"]["url"], headers=self.stream_args["stream_info"]["headers"]) + else: + playlist = m3u8.load(self.stream_args["stream_info"]["url"]) + except Exception as e: + self.fhdhr.logger.info("Connection Closed: %s" % e) + self.tuner.close() + return None segments = playlist.segments diff --git a/fHDHR/device/tuners/tuner.py b/fHDHR/device/tuners/tuner.py index e93f60b..798f0f5 100644 --- a/fHDHR/device/tuners/tuner.py +++ b/fHDHR/device/tuners/tuner.py @@ -8,22 +8,22 @@ from .stream import Stream class Tuner(): - def __init__(self, fhdhr, inum, epg, plugins): + def __init__(self, fhdhr, inum, epg, origin): self.fhdhr = fhdhr - self.plugins = plugins self.number = inum + self.origin = origin self.epg = epg self.tuner_lock = threading.Lock() self.set_off_status() self.chanscan_url = "/api/channels?method=scan" - self.close_url = "/api/tuners?method=close&tuner=%s" % str(self.number) + self.close_url = "/api/tuners?method=close&tuner=%s&origin=%s" % (self.number, self.origin) - def channel_scan(self, grabbed=False): + def channel_scan(self, origin, grabbed=False): if self.tuner_lock.locked() and not grabbed: - self.fhdhr.logger.error("Tuner #%s is not available." % str(self.number)) + self.fhdhr.logger.error("%s Tuner #%s is not available." % (self.origin, self.number)) raise TunerError("804 - Tuner In Use") if self.status["status"] == "Scanning": @@ -33,14 +33,16 @@ class Tuner(): if not grabbed: self.tuner_lock.acquire() self.status["status"] = "Scanning" - self.fhdhr.logger.info("Tuner #%s Performing Channel Scan." % str(self.number)) + self.status["origin"] = origin + self.status["time_start"] = datetime.datetime.utcnow() + self.fhdhr.logger.info("Tuner #%s Performing Channel Scan for %s origin." % (self.number, origin)) - chanscan = threading.Thread(target=self.runscan) + chanscan = threading.Thread(target=self.runscan, args=(origin,)) chanscan.start() - def runscan(self): - self.fhdhr.api.get(self.chanscan_url) - self.fhdhr.logger.info("Requested Channel Scan Complete.") + def runscan(self, origin): + self.fhdhr.api.get("%s&origin=%s" % (self.chanscan_url, origin)) + self.fhdhr.logger.info("Requested Channel Scan for %s origin Complete." % origin) self.close() self.fhdhr.api.get(self.close_url) @@ -48,13 +50,15 @@ class Tuner(): if "downloaded" in list(self.status.keys()): self.status["downloaded"] += bytes_count - def grab(self, channel_number): + def grab(self, origin, channel_number): if self.tuner_lock.locked(): self.fhdhr.logger.error("Tuner #%s is not available." % self.number) raise TunerError("804 - Tuner In Use") self.tuner_lock.acquire() self.status["status"] = "Acquired" + self.status["origin"] = origin self.status["channel"] = channel_number + self.status["time_start"] = datetime.datetime.utcnow() self.fhdhr.logger.info("Tuner #%s Acquired." % str(self.number)) def close(self): @@ -65,19 +69,22 @@ class Tuner(): def get_status(self): current_status = self.status.copy() - if current_status["status"] == "Active": - current_status["Play Time"] = str( + current_status["epg"] = {} + if current_status["status"] in ["Acquired", "Active", "Scanning"]: + current_status["running_time"] = str( humanized_time( int((datetime.datetime.utcnow() - current_status["time_start"]).total_seconds()))) current_status["time_start"] = str(current_status["time_start"]) - current_status["epg"] = self.epg.whats_on_now(current_status["channel"]) + if current_status["status"] in ["Active"]: + if current_status["origin"] in self.epg.epg_methods: + current_status["epg"] = self.epg.whats_on_now(current_status["channel"], method=current_status["origin"]) return current_status def set_off_status(self): self.status = {"status": "Inactive"} def get_stream(self, stream_args, tuner): - stream = Stream(self.fhdhr, stream_args, tuner, self.plugins) + stream = Stream(self.fhdhr, stream_args, tuner) return stream.get() def set_status(self, stream_args): @@ -88,6 +95,7 @@ class Tuner(): "clients_id": [], "method": stream_args["method"], "accessed": [stream_args["accessed"]], + "origin": stream_args["origin"], "channel": stream_args["channel"], "proxied_url": stream_args["stream_info"]["url"], "time_start": datetime.datetime.utcnow(), diff --git a/fHDHR/origins/__init__.py b/fHDHR/origins/__init__.py new file mode 100644 index 0000000..b960cab --- /dev/null +++ b/fHDHR/origins/__init__.py @@ -0,0 +1,45 @@ + +import fHDHR.exceptions + + +class Origin_StandIN(): + def __init__(self): + self.setup_success = False + + def get_channels(self): + return [] + + def get_channel_stream(self, chandict, stream_args): + return None + + +class Origins(): + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + self.origins_dict = {} + self.origin_selfadd() + for plugin_name in list(self.fhdhr.plugins.plugins.keys()): + if self.fhdhr.plugins.plugins[plugin_name].manifest["tagged_mod"] and self.fhdhr.plugins.plugins[plugin_name].manifest["tagged_mod_type"] == "origin": + self.fhdhr.plugins.plugins[plugin_name].plugin_utils.origin = self.origins_dict[self.fhdhr.plugins.plugins[plugin_name].manifest["tagged_mod"].lower()] + + @property + def valid_origins(self): + return [origin for origin in list(self.origins_dict.keys())] + + def origin_selfadd(self): + for plugin_name in list(self.fhdhr.plugins.plugins.keys()): + if self.fhdhr.plugins.plugins[plugin_name].type == "origin": + method = self.fhdhr.plugins.plugins[plugin_name].name.lower() + try: + plugin_utils = self.fhdhr.plugins.plugins[plugin_name].plugin_utils + self.origins_dict[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(plugin_utils) + self.fhdhr.logger.info("%s Setup Success" % method) + self.origins_dict[method].setup_success = True + except fHDHR.exceptions.OriginSetupError as e: + self.fhdhr.logger.error(e) + self.origins_dict[method] = Origin_StandIN() + + if not hasattr(self.origins_dict[method], 'tuners'): + self.origins_dict[method].tuners = 4 diff --git a/fHDHR/originwrapper/__init__.py b/fHDHR/originwrapper/__init__.py deleted file mode 100644 index 0af25c4..0000000 --- a/fHDHR/originwrapper/__init__.py +++ /dev/null @@ -1,62 +0,0 @@ -from .origin_channels_standin import OriginChannels_StandIN -from .origin_epg_standin import OriginEPG_StandIN - -import fHDHR.exceptions - - -class OriginServiceWrapper(): - - def __init__(self, fhdhr, origin): - self.fhdhr = fhdhr - self.origin = origin - - self.servicename = fhdhr.config.dict["main"]["servicename"] - - self.setup_success = None - self.setup() - - def setup(self): - - if self.origin: - - try: - self.originservice = self.origin.OriginService(self.fhdhr) - self.setup_success = True - self.fhdhr.logger.info("%s Setup Success" % self.servicename) - except fHDHR.exceptions.OriginSetupError as e: - self.originservice = None - self.fhdhr.logger.error(e) - self.setup_success = False - - if self.setup_success: - self.channels = self.origin.OriginChannels(self.fhdhr, self.originservice) - self.epg = self.origin.OriginEPG(self.fhdhr) - else: - self.channels = OriginChannels_StandIN() - self.epg = OriginEPG_StandIN() - else: - self.originservice = None - self.channels = OriginChannels_StandIN() - self.epg = OriginEPG_StandIN() - - def get_channels(self): - return self.channels.get_channels() - - def get_channel_stream(self, chandict, stream_args): - return self.channels.get_channel_stream(chandict, stream_args) - - def update_epg(self, channels): - return self.epg.update_epg(channels) - - def __getattr__(self, name): - ''' will only get called for undefined attributes ''' - if hasattr(self.fhdhr, name): - return eval("self.fhdhr.%s" % name) - if hasattr(self.originservice, name): - return eval("self.originservice.%s" % name) - elif hasattr(self.channels, name): - return eval("self.channels.%s" % name) - elif hasattr(self.epg, name): - return eval("self.epg.%s" % name) - else: - raise AttributeError(name) diff --git a/fHDHR/originwrapper/origin_channels_standin.py b/fHDHR/originwrapper/origin_channels_standin.py deleted file mode 100644 index 06bc2b4..0000000 --- a/fHDHR/originwrapper/origin_channels_standin.py +++ /dev/null @@ -1,11 +0,0 @@ - - -class OriginChannels_StandIN(): - def __init__(self): - pass - - def get_channels(self): - return [] - - def get_channel_stream(self, chandict, stream_args): - return None diff --git a/fHDHR/originwrapper/origin_epg_standin.py b/fHDHR/originwrapper/origin_epg_standin.py deleted file mode 100644 index a0830a6..0000000 --- a/fHDHR/originwrapper/origin_epg_standin.py +++ /dev/null @@ -1,8 +0,0 @@ - - -class OriginEPG_StandIN(): - def __init__(self): - pass - - def update_epg(self, channels): - return {} diff --git a/fHDHR/plugins/__init__.py b/fHDHR/plugins/__init__.py new file mode 100644 index 0000000..9706528 --- /dev/null +++ b/fHDHR/plugins/__init__.py @@ -0,0 +1,250 @@ +import os +import imp +import json + + +class Plugin_DB(): + def __init__(self, db, name): + self._db = db + self.name = name + self.namespace = name.lower() + + # fhdhr + def set_fhdhr_value(self, pluginitem, key, value, namespace="default"): + print("%s plugin is not allowed write access to fhdhr db namespaces." % self.name) + return + + def get_fhdhr_value(self, pluginitem, key, namespace="default"): + return self._db.get_fhdhr_value(pluginitem, key, namespace=namespace.lower()) + + def delete_fhdhr_value(self, pluginitem, key, namespace="default"): + print("%s plugin is not allowed write access to fhdhr db namespaces." % self.name) + return + + # Plugin + def set_plugin_value(self, pluginitem, key, value, namespace=None): + if not namespace: + namespace = self.namespace + elif namespace.lower() != self.namespace: + print("%s plugin is not allowed write access to %s db namespace." % (self.name, namespace)) + return + return self._db.set_plugin_value(pluginitem, key, value, namespace=self.namespace) + + def get_plugin_value(self, pluginitem, key, namespace=None): + if not namespace: + namespace = self.namespace + return self._db.get_plugin_value(pluginitem, key, namespace=namespace.lower()) + + def delete_plugin_value(self, pluginitem, key, namespace=None): + if not namespace: + namespace = self.namespace + elif namespace.lower() != self.namespace: + print("%s plugin is not allowed write access to %s db namespace." % (self.name, namespace)) + return + return self._db.delete_plugin_value(pluginitem, key, namespace=self.namespace) + + +class Plugin_Config(): + def __init__(self, config, name): + self._config = config + self.name = name + self.namespace = name.lower() + + @property + def dict(self): + return self._config.dict.copy() + + @property + def internal(self): + return self._config.internal.copy() + + @property + def conf_default(self): + return self._config.conf_default.copy() + + def write(self, key, value, namespace=None): + if not namespace: + namespace = self.namespace + elif str(namespace).lower() != self.namespace: + print("%s plugin is not allowed write access to fhdhr config namespaces." % self.name) + return + return self._config.write(key, value, self.namespace) + + +class Plugin_Utils(): + + def __init__(self, config, logger, db, plugin_name, plugin_manifest, modname): + self.config = Plugin_Config(config, plugin_manifest["name"]) + self.db = Plugin_DB(db, plugin_manifest["name"]) + self.logger = logger + self.namespace = plugin_manifest["name"].lower() + self.plugin_name = plugin_name + self.plugin_manifest = plugin_manifest + self.origin = None + + +class Plugin(): + + def __init__(self, config, logger, db, plugin_name, plugin_path, plugin_conf, plugin_manifest): + self.config = config + self.db = db + self.logger = logger + + # Gather Info about Plugin + self.plugin_name = plugin_name + self.modname = os.path.basename(plugin_path) + self.path = plugin_path + self.module_type = imp.PKG_DIRECTORY + self.multi_plugin = (self.plugin_name != self.modname) + self.default_conf = plugin_conf + self.manifest = plugin_manifest + + if self.multi_plugin: + self.plugin_dict_name = "%s_%s" % (plugin_name, self.modname) + else: + self.plugin_dict_name = plugin_name + + self.plugin_utils = Plugin_Utils(config, logger, db, plugin_name, plugin_manifest, self.modname) + + # Load the module + self._module = self._load() + + def setup(self): + + if self.type == "alt_epg": + self.config.register_valid_epg_method(self.name, self.plugin_dict_name) + elif self.type == "alt_stream": + self.config.register_valid_streaming_method(self.name, self.plugin_dict_name) + elif self.type == "web": + self.config.register_web_path(self.manifest["name"], self.path, self.plugin_dict_name) + + if self.has_setup(): + self._module.setup(self) + + def has_setup(self): + return hasattr(self._module, 'setup') + + def _load(self): + description = ('', '', self.module_type) + mod = imp.load_module(self.plugin_dict_name, None, self.path, description) + return mod + + @property + def name(self): + return self.manifest["name"] + + @property + def version(self): + return self.manifest["version"] + + @property + def type(self): + return self.manifest["type"] + + def __getattr__(self, name): + ''' will only get called for undefined attributes ''' + if name == "Plugin_OBJ": + return self._module.Plugin_OBJ + + +class PluginsHandler(): + + def __init__(self, settings): + self.config = settings + + self.plugins = {} + + self.found_plugins = [] + self.found_plugins_conf = [] + self.list_plugins() + + def setup(self): + for plugin_name in list(self.plugins.keys()): + self.plugins[plugin_name].setup() + + def load_plugin_configs(self): + for file_item_path in self.found_plugins_conf: + self.config.import_conf_json(file_item_path) + + def list_plugins(self): + for directory in self.config.internal["paths"]["plugins_dir"]: + + base = os.path.abspath(directory) + for filename in os.listdir(base): + abspath = os.path.join(base, filename) + + if os.path.isdir(abspath): + + plugin_conf = [] + for subfilename in os.listdir(abspath): + subabspath = os.path.join(abspath, subfilename) + if subfilename.endswith("_conf.json"): + plugin_conf.append(subabspath) + self.found_plugins_conf.append(subabspath) + + # Plugin/multi-plugin must have a basic manifest json + conffilepath = os.path.join(abspath, 'plugin.json') + if os.path.isfile(conffilepath): + plugin_manifest = json.load(open(conffilepath, 'r')) + + for plugin_man_item in ["name", "version", "type"]: + if plugin_man_item not in list(plugin_manifest.keys()): + plugin_manifest[plugin_man_item] = None + + self.config.register_version(os.path.basename(filename), plugin_manifest["version"], "plugin") + + if plugin_manifest["type"] == "origin": + self.config.register_valid_origin_method(plugin_manifest["name"]) + + plugin_import_print_string = "Found %s type plugin: %s %s. " % (plugin_manifest["type"], plugin_manifest["name"], plugin_manifest["version"]) + + # Warn for multiple origins + if plugin_manifest["type"] == "origin" and len([plugin_name for plugin_name, plugin_path, plugin_conf, plugin_manifest in self.found_plugins if plugin_manifest["type"] == "origin"]): + plugin_import_print_string += " ImportWarning: Only one Origin Allowed." + + if not any(plugin_manifest[plugin_item] for plugin_item in ["name", "version", "type"]): + plugin_import_print_string += " ImportWarning: Missing PLUGIN_* Value." + else: + + # Single Plugin + if os.path.isfile(os.path.join(abspath, '__init__.py')): + plugin_manifest["tagged_mod"] = None + plugin_manifest["tagged_mod_type"] = None + self.found_plugins.append((os.path.basename(filename), abspath, plugin_conf, plugin_manifest)) + + else: + + # Multi-Plugin + for subfilename in os.listdir(abspath): + subabspath = os.path.join(abspath, subfilename) + + if os.path.isdir(subabspath): + + subconffilepath = os.path.join(subabspath, 'plugin.json') + if os.path.isfile(subconffilepath): + subplugin_manifest = json.load(open(subconffilepath, 'r')) + + for subplugin_man_item in ["name", "version", "type"]: + if subplugin_man_item not in list(subplugin_manifest.keys()): + subplugin_manifest[subplugin_man_item] = plugin_manifest[subplugin_man_item] + else: + subplugin_manifest = plugin_manifest + + subplugin_manifest["tagged_mod"] = None + subplugin_manifest["tagged_mod_type"] = None + if plugin_manifest["type"] != subplugin_manifest["type"]: + subplugin_manifest["tagged_mod"] = plugin_manifest["name"] + subplugin_manifest["tagged_mod_type"] = plugin_manifest["type"] + + if os.path.isfile(os.path.join(subabspath, '__init__.py')): + self.found_plugins.append((os.path.basename(filename), subabspath, plugin_conf, subplugin_manifest)) + + print(plugin_import_print_string) + self.load_plugin_configs() + + def load_plugins(self, logger, db): + self.logger = logger + self.db = db + for plugin_name, plugin_path, plugin_conf, plugin_manifest in self.found_plugins: + plugin_item = Plugin(self.config, self.logger, self.db, plugin_name, plugin_path, plugin_conf, plugin_manifest) + self.plugins[plugin_item.plugin_dict_name] = plugin_item diff --git a/fHDHR_web/__init__.py b/fHDHR_web/__init__.py index 4a1faa1..b346691 100644 --- a/fHDHR_web/__init__.py +++ b/fHDHR_web/__init__.py @@ -6,8 +6,6 @@ import uuid from .pages import fHDHR_Pages from .files import fHDHR_Files from .brython import fHDHR_Brython -from .hdhr import fHDHR_HDHR -from .rmg import fHDHR_RMG from .api import fHDHR_API @@ -36,33 +34,16 @@ class fHDHR_HTTP_Server(): self.route_list = {} - self.fhdhr.logger.info("Loading HTTP Pages Endpoints.") - self.pages = fHDHR_Pages(fhdhr) - self.add_endpoints(self.pages, "pages") + self.endpoints_obj = {} + self.endpoints_obj["pages"] = fHDHR_Pages(fhdhr) + self.endpoints_obj["files"] = fHDHR_Files(fhdhr) + self.endpoints_obj["brython"] = fHDHR_Brython(fhdhr) + self.endpoints_obj["api"] = fHDHR_API(fhdhr) - self.fhdhr.logger.info("Loading HTTP Files Endpoints.") - self.files = fHDHR_Files(fhdhr) - self.add_endpoints(self.files, "files") - - self.fhdhr.logger.info("Loading HTTP Brython Endpoints.") - self.brython = fHDHR_Brython(fhdhr) - self.add_endpoints(self.brython, "brython") - - self.fhdhr.logger.info("Loading HTTP HDHR Endpoints.") - self.hdhr = fHDHR_HDHR(fhdhr) - self.add_endpoints(self.hdhr, "hdhr") - - self.fhdhr.logger.info("Loading HTTP RMG Endpoints.") - self.rmg = fHDHR_RMG(fhdhr) - self.add_endpoints(self.rmg, "rmg") - - self.fhdhr.logger.info("Loading HTTP API Endpoints.") - self.api = fHDHR_API(fhdhr) - self.add_endpoints(self.api, "api") - - self.fhdhr.logger.info("Loading HTTP Origin Endpoints.") - self.origin_endpoints = self.fhdhr.originwrapper.origin.origin_web.fHDHR_Origin_Web(fhdhr) - self.add_endpoints(self.origin_endpoints, "origin_endpoints") + self.selfadd_web_plugins() + for endpoint_type in list(self.endpoints_obj.keys()): + self.fhdhr.logger.info("Loading HTTP %s Endpoints." % endpoint_type) + self.add_endpoints(endpoint_type) self.fhdhr.app.before_request(self.before_request) self.fhdhr.app.after_request(self.after_request) @@ -70,6 +51,16 @@ class fHDHR_HTTP_Server(): self.fhdhr.threads["flask"] = threading.Thread(target=self.run) + def selfadd_web_plugins(self): + for plugin_name in list(self.fhdhr.plugins.plugins.keys()): + if self.fhdhr.plugins.plugins[plugin_name].type == "web": + method = self.fhdhr.plugins.plugins[plugin_name].name.lower() + plugin_utils = self.fhdhr.plugins.plugins[plugin_name].plugin_utils + try: + self.endpoints_obj[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(self.fhdhr, plugin_utils) + except Exception as e: + print(e) + def start(self): self.fhdhr.logger.info("Flask HTTP Thread Starting") self.fhdhr.threads["flask"].start() @@ -87,6 +78,8 @@ class fHDHR_HTTP_Server(): session["instance_id"] = self.instance_id session["route_list"] = self.route_list + session["user_agent"] = request.headers.get('User-Agent') + session["is_internal_api"] = self.detect_internal_api(request) if session["is_internal_api"]: self.fhdhr.logger.debug("Client is using internal API call.") @@ -153,49 +146,57 @@ class fHDHR_HTTP_Server(): else: return False - def add_endpoints(self, index_list, index_name): + def add_endpoints(self, index_name): - if index_name not in list(self.route_list.keys()): - self.route_list[index_name] = {} - - item_list = [x for x in dir(index_list) if self.isapath(x)] + item_list = [x for x in dir(self.endpoints_obj[index_name]) if self.isapath(x)] + endpoint_main = self.endpoints_obj[index_name] + endpoint_main.fhdhr.version # dummy line for item in item_list: - endpoints = eval("self.%s.%s.%s" % (index_name, item, "endpoints")) + endpoints = eval("endpoint_main.%s.%s" % (item, "endpoints")) if isinstance(endpoints, str): endpoints = [endpoints] - handler = eval("self.%s.%s" % (index_name, item)) - endpoint_name = eval("self.%s.%s.%s" % (index_name, item, "endpoint_name")) + handler = eval("endpoint_main.%s" % item) + endpoint_name = eval("endpoint_main.%s.%s" % (item, "endpoint_name")) try: - endpoint_methods = eval("self.%s.%s.%s" % (index_name, item, "endpoint_methods")) + endpoint_methods = eval("endpoint_main.%s.%s" % (item, "endpoint_methods")) except AttributeError: endpoint_methods = ['GET'] try: - endpoint_access_level = eval("self.%s.%s.%s" % (index_name, item, "endpoint_access_level")) + endpoint_access_level = eval("endpoint_main.%s.%s" % (item, "endpoint_access_level")) except AttributeError: endpoint_access_level = 0 try: - pretty_name = eval("self.%s.%s.%s" % (index_name, item, "pretty_name")) + pretty_name = eval("endpoint_main.%s.%s" % (item, "pretty_name")) except AttributeError: pretty_name = endpoint_name try: - endpoint_default_parameters = eval("self.%s.%s.%s" % (index_name, item, "endpoint_default_parameters")) + endpoint_category = eval("endpoint_main.%s.%s" % (item, "endpoint_category")) + except AttributeError: + endpoint_category = index_name + + try: + endpoint_default_parameters = eval("endpoint_main.%s.%s" % (item, "endpoint_default_parameters")) except AttributeError: endpoint_default_parameters = {} self.fhdhr.logger.debug("Adding endpoint %s available at %s with %s methods." % (endpoint_name, ",".join(endpoints), ",".join(endpoint_methods))) - if endpoint_name not in list(self.route_list[index_name].keys()): - self.route_list[index_name][endpoint_name] = {} - self.route_list[index_name][endpoint_name]["name"] = endpoint_name - self.route_list[index_name][endpoint_name]["endpoints"] = endpoints - self.route_list[index_name][endpoint_name]["endpoint_methods"] = endpoint_methods - self.route_list[index_name][endpoint_name]["endpoint_access_level"] = endpoint_access_level - self.route_list[index_name][endpoint_name]["endpoint_default_parameters"] = endpoint_default_parameters - self.route_list[index_name][endpoint_name]["pretty_name"] = pretty_name + if endpoint_category not in list(self.route_list.keys()): + self.route_list[endpoint_category] = {} + + if endpoint_name not in list(self.route_list[endpoint_category].keys()): + self.route_list[endpoint_category][endpoint_name] = {} + self.route_list[endpoint_category][endpoint_name]["name"] = endpoint_name + self.route_list[endpoint_category][endpoint_name]["endpoints"] = endpoints + self.route_list[endpoint_category][endpoint_name]["endpoint_methods"] = endpoint_methods + self.route_list[endpoint_category][endpoint_name]["endpoint_access_level"] = endpoint_access_level + self.route_list[endpoint_category][endpoint_name]["endpoint_default_parameters"] = endpoint_default_parameters + self.route_list[endpoint_category][endpoint_name]["pretty_name"] = pretty_name + self.route_list[endpoint_category][endpoint_name]["endpoint_category"] = endpoint_category for endpoint in endpoints: self.add_endpoint(endpoint=endpoint, @@ -204,7 +205,7 @@ class fHDHR_HTTP_Server(): methods=endpoint_methods) def isapath(self, item): - not_a_page_list = ["fhdhr"] + not_a_page_list = ["fhdhr", "plugin_utils"] if item in not_a_page_list: return False elif item.startswith("__") and item.endswith("__"): diff --git a/fHDHR_web/api/__init__.py b/fHDHR_web/api/__init__.py index 6cf57c9..db8f6e6 100644 --- a/fHDHR_web/api/__init__.py +++ b/fHDHR_web/api/__init__.py @@ -2,7 +2,6 @@ from .root_url import Root_URL from .startup_tasks import Startup_Tasks -from .cluster import Cluster from .settings import Settings from .channels import Channels from .xmltv import xmlTV @@ -11,7 +10,7 @@ from .w3u import W3U from .epg import EPG from .tuners import Tuners from .debug import Debug_JSON -from .tools import API_Tools +from .plugins import Plugins_JSON from .route_list import Route_List @@ -26,7 +25,6 @@ class fHDHR_API(): self.root_url = Root_URL(fhdhr) self.startup_tasks = Startup_Tasks(fhdhr) - self.cluster = Cluster(fhdhr) self.settings = Settings(fhdhr) self.channels = Channels(fhdhr) self.xmltv = xmlTV(fhdhr) @@ -35,7 +33,7 @@ class fHDHR_API(): self.epg = EPG(fhdhr) self.tuners = Tuners(fhdhr) self.debug = Debug_JSON(fhdhr) - self.tools = API_Tools(fhdhr) + self.plugins = Plugins_JSON(fhdhr) self.route_list = Route_List(fhdhr) diff --git a/fHDHR_web/api/channels.py b/fHDHR_web/api/channels.py index 978d0cc..5ab1595 100644 --- a/fHDHR_web/api/channels.py +++ b/fHDHR_web/api/channels.py @@ -24,22 +24,38 @@ class Channels(): method = request.args.get('method', default=None, type=str) redirect_url = request.args.get('redirect', default=None, type=str) + origin_methods = self.fhdhr.origins.valid_origins + origin = request.args.get('origin', default=None, type=str) + if origin not in origin_methods: + return "%s Invalid channels origin" % origin + if method == "get": channels_info = {} - for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels()]: - channel_obj = self.fhdhr.device.channels.list[fhdhr_id] - channel_dict = channel_obj.dict.copy() - channel_dict["m3u_url"] = channel_obj.m3u_url - channel_dict["stream_url"] = channel_obj.api_stream_url - channels_info[channel_obj.number] = channel_dict + if not origin: + origin_list = origin_methods + else: + origin_list = [origin] - # Sort the channels - sorted_channel_list = channel_sort(list(channels_info.keys())) - sorted_chan_guide = [] - for channel in sorted_channel_list: - sorted_chan_guide.append(channels_info[channel]) + for origin_item in origin_list: - channels_info_json = json.dumps(sorted_chan_guide, indent=4) + channels_info[origin_item] = {} + + for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin=origin_item)]: + channel_obj = self.fhdhr.device.channels.list[origin_item][fhdhr_id] + channel_dict = channel_obj.dict.copy() + channel_dict["m3u_url"] = channel_obj.api_m3u_url + channel_dict["stream_url"] = channel_obj.api_stream_url + channels_info[origin_item][channel_obj.number] = channel_dict + + # Sort the channels + sorted_channel_list = channel_sort(list(channels_info[origin_item].keys())) + sorted_chan_guide = [] + for channel in sorted_channel_list: + sorted_chan_guide.append(channels_info[origin_item][channel]) + + channels_info[origin_item] = sorted_chan_guide + + channels_info_json = json.dumps(channels_info, indent=4) return Response(status=200, response=channels_info_json, @@ -59,18 +75,18 @@ class Channels(): channel_method = channel[0] channel_number = channel[1:] - if str(channel_number) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]: + if str(channel_number) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]: response = Response("Not Found", status=404) response.headers["X-fHDHR-Error"] = "801 - Unknown Channel" self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) abort(response) if channel_method == "+": - self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method) + self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method, origin) elif channel_method == "-": - self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method) + self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method, origin) elif channel_method == "x": - self.fhdhr.device.channels.set_channel_enablement("number", channel_number, "toggle") + self.fhdhr.device.channels.set_channel_enablement("number", channel_number, "toggle", origin) else: self.fhdhr.logger.warning("Unknown favorite command %s" % request.args['favorite']) @@ -79,14 +95,14 @@ class Channels(): elif method in ["enable", "disable"]: channel = request.args.get('channel', default=None, type=str) if channel == "all": - self.fhdhr.device.channels.set_channel_enablement_all(method) - elif not channel or str(channel) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]: + self.fhdhr.device.channels.set_channel_enablement_all(method, origin) + elif not channel or str(channel) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]: if redirect_url: return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Failed" % method))) else: return "%s Falied" % method else: - self.fhdhr.device.channels.set_channel_enablement("number", channel, method) + self.fhdhr.device.channels.set_channel_enablement("number", channel, method, origin) elif method == "update": channel_id = request.form.get('id', None) @@ -111,7 +127,7 @@ class Channels(): updatedict[key] = confvalue elif key in ["favorite", "HD"]: updatedict[key] = int(request.form.get(key)) - self.fhdhr.device.channels.set_channel_status("id", channel_id, updatedict) + self.fhdhr.device.channels.set_channel_status("id", channel_id, updatedict, origin) elif method == "modify": channels_list = json.loads(request.form.get('channels', [])) @@ -139,10 +155,10 @@ class Channels(): updatedict[key] = int(channel[key]) else: channel_id = str(channel[key]) - self.fhdhr.device.channels.set_channel_status("id", channel_id, updatedict) + self.fhdhr.device.channels.set_channel_status("id", channel_id, updatedict, origin) elif method == "scan": - self.fhdhr.device.channels.get_channels(forceupdate=True) + self.fhdhr.device.channels.get_channels(forceupdate=True, origin=origin) else: return "Invalid Method" diff --git a/fHDHR_web/api/debug.py b/fHDHR_web/api/debug.py index 7ca9df7..e90f6e1 100644 --- a/fHDHR_web/api/debug.py +++ b/fHDHR_web/api/debug.py @@ -19,11 +19,16 @@ class Debug_JSON(): debugjson = { "base_url": base_url, - "total channels": len(self.fhdhr.device.channels.list), - "tuner status": self.fhdhr.device.tuners.status(), } - cluster_json = json.dumps(debugjson, indent=4) + + for origin in list(self.fhdhr.origins.origins_dict.keys()): + debugjson[origin] = { + "tuner status": self.fhdhr.device.tuners.status(origin), + "total channels": len(list(self.fhdhr.device.channels.list[origin].keys())) + } + + debug_json = json.dumps(debugjson, indent=4) return Response(status=200, - response=cluster_json, + response=debug_json, mimetype='application/json') diff --git a/fHDHR_web/api/epg.py b/fHDHR_web/api/epg.py index 87953b7..68961a1 100644 --- a/fHDHR_web/api/epg.py +++ b/fHDHR_web/api/epg.py @@ -23,18 +23,18 @@ class EPG(): method = request.args.get('method', default="get", type=str) source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["def_method"], type=str) - if source not in self.fhdhr.config.dict["epg"]["valid_methods"]: - return "%s Invalid xmltv method" % source + if source not in list(self.fhdhr.config.dict["epg"]["valid_methods"].keys()): + return "%s Invalid epg method" % source redirect_url = request.args.get('redirect', default=None, type=str) if method == "get": epgdict = self.fhdhr.device.epg.get_epg(source) - if source in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: + if source in self.fhdhr.origins.valid_origins: epgdict = epgdict.copy() for c in list(epgdict.keys()): - chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"]) + chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"], source) epgdict[chan_obj.number] = epgdict.pop(c) epgdict[chan_obj.number]["name"] = chan_obj.dict["name"] epgdict[chan_obj.number]["callsign"] = chan_obj.dict["callsign"] @@ -93,14 +93,14 @@ class EPG(): else: chan_dict["listing_%s" % time_item] = str(datetime.datetime.fromtimestamp(sorted_chan_guide[channel]["listing"][0][time_item])) - if source in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: - chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", sorted_chan_guide[channel]["id"]) + if source in self.fhdhr.origins.valid_origins: + chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", sorted_chan_guide[channel]["id"], source) chan_dict["name"] = chan_obj.dict["name"] chan_dict["number"] = chan_obj.number chan_dict["chan_thumbnail"] = chan_obj.thumbnail chan_dict["enabled"] = chan_obj.dict["enabled"] - chan_dict["m3u_url"] = chan_obj.m3u_url + chan_dict["m3u_url"] = chan_obj.api_m3u_url chan_dict["listing_thumbnail"] = chan_dict["listing_thumbnail"] or chan_obj.thumbnail else: diff --git a/fHDHR_web/api/images.py b/fHDHR_web/api/images.py index 5c515a5..a7bd0a1 100644 --- a/fHDHR_web/api/images.py +++ b/fHDHR_web/api/images.py @@ -31,7 +31,7 @@ class Images(): elif method == "get": source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["method"], type=str) - if source in self.fhdhr.config.dict["epg"]["valid_methods"]: + if source in list(self.fhdhr.config.dict["epg"]["valid_methods"].keys()): image_type = request.args.get('type', default="content", type=str) if image_type in ["content", "channel"]: image_id = request.args.get('id', default=None, type=str) diff --git a/fHDHR_web/api/m3u.py b/fHDHR_web/api/m3u.py index 17ca875..deffe8a 100644 --- a/fHDHR_web/api/m3u.py +++ b/fHDHR_web/api/m3u.py @@ -26,6 +26,11 @@ class M3U(): if method == "get": + origin_methods = self.fhdhr.origins.valid_origins + origin = request.args.get('origin', default=None, type=str) + if origin and origin not in origin_methods: + return "%s Invalid channels origin" % origin + FORMAT_DESCRIPTOR = "#EXTM3U" RECORD_MARKER = "#EXTINF" @@ -37,14 +42,29 @@ class M3U(): channel_items = [] - if channel == "all": - fileName = "channels.m3u" - for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels()]: - channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + if origin: + if channel == "all": + fileName = "channels.m3u" + for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin) + if channel_obj.enabled: + channel_items.append(channel_obj) + elif str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("number", channel, origin) + fileName = "%s.m3u" % channel_obj.number if channel_obj.enabled: channel_items.append(channel_obj) - elif str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]: - channel_obj = self.fhdhr.device.channels.get_channel_obj("number", channel) + else: + return "Channel Disabled" + elif channel != "all" and str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id", origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", channel, origin) + fileName = "%s.m3u" % channel_obj.number + if channel_obj.enabled: + channel_items.append(channel_obj) + else: + return "Channel Disabled" + elif not origin and channel != "all" and str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id")]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", channel) fileName = "%s.m3u" % channel_obj.number if channel_obj.enabled: channel_items.append(channel_obj) diff --git a/fHDHR_web/api/plugins.py b/fHDHR_web/api/plugins.py new file mode 100644 index 0000000..bb9eabb --- /dev/null +++ b/fHDHR_web/api/plugins.py @@ -0,0 +1,30 @@ +from flask import Response +import json + + +class Plugins_JSON(): + endpoints = ["/api/plugins"] + endpoint_name = "api_plugins" + endpoint_methods = ["GET", "POST"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + + pluginsjson = {} + + for plugin in list(self.fhdhr.plugins.plugins.keys()): + pluginsjson[plugin] = { + "name": plugin, + "manifest": self.fhdhr.plugins.plugins[plugin].manifest + } + + plugins_json = json.dumps(pluginsjson, indent=4) + + return Response(status=200, + response=plugins_json, + mimetype='application/json') diff --git a/fHDHR_web/api/root_url.py b/fHDHR_web/api/root_url.py index acbedaf..d8ee889 100644 --- a/fHDHR_web/api/root_url.py +++ b/fHDHR_web/api/root_url.py @@ -1,4 +1,4 @@ -from flask import redirect, request, session +from flask import redirect class Root_URL(): @@ -13,20 +13,4 @@ class Root_URL(): return self.get(*args) def get(self, *args): - - user_agent = request.headers.get('User-Agent') - - # Client Devices Discovering Device Information - if not user_agent or session["is_plexmediaserver"]: - - # Plex Remote Media Grabber redirect - if self.fhdhr.config.dict["rmg"]["enabled"] and session["is_plexmediaserver"]: - return redirect("/rmg") - - # Client Device is looking for HDHR type device - else: - return redirect("/hdhr/device.xml") - - # Anything Else is likely a Web Browser - else: - return redirect("/index") + return redirect("/index") diff --git a/fHDHR_web/api/settings.py b/fHDHR_web/api/settings.py index 5763152..c9ec827 100644 --- a/fHDHR_web/api/settings.py +++ b/fHDHR_web/api/settings.py @@ -30,11 +30,8 @@ class Settings(): web_settings_dict[config_section] = {} for config_item in list(self.fhdhr.config.conf_default[config_section].keys()): - real_config_section = config_section - if config_section == self.fhdhr.config.dict["main"]["dictpopname"]: - real_config_section = "origin" web_settings_dict[config_section][config_item] = { - "value": self.fhdhr.config.dict[real_config_section][config_item], + "value": self.fhdhr.config.dict[config_section][config_item], } if self.fhdhr.config.conf_default[config_section][config_item]["config_web_hidden"]: web_settings_dict[config_section][config_item]["value"] = "***********" @@ -56,10 +53,7 @@ class Settings(): else: return "%s Falied" % method - if config_section == "origin": - config_section = self.fhdhr.config.dict["main"]["dictpopname"] - - self.fhdhr.config.write(config_section, config_name, config_value) + self.fhdhr.config.write(config_name, config_value, config_section) elif method == "restart": restart_thread = threading.Thread(target=self.restart_thread) diff --git a/fHDHR_web/api/startup_tasks.py b/fHDHR_web/api/startup_tasks.py index ef3a3ea..cd9b613 100644 --- a/fHDHR_web/api/startup_tasks.py +++ b/fHDHR_web/api/startup_tasks.py @@ -16,6 +16,8 @@ class Startup_Tasks(): def get(self, *args): + self.fhdhr.logger.info("Running Startup Tasks.") + # Hit Channel Update API haseverscanned = self.fhdhr.db.get_fhdhr_value("channels", "scanned_time") updatechannels = False @@ -25,10 +27,13 @@ class Startup_Tasks(): updatechannels = True if updatechannels: - self.fhdhr.api.get(self.channel_update_url) + for origin in list(self.fhdhr.origins.origins_dict.keys()): + self.fhdhr.api.get("%s&origin=%s" % (self.channel_update_url, origin)) # Hit EPG Update API for epg_method in self.fhdhr.device.epg.epg_methods: self.fhdhr.api.get("%s&source=%s" % (self.epg_update_url, epg_method)) + self.fhdhr.logger.info("Startup Tasks Complete.") + return "Success" diff --git a/fHDHR_web/api/tuners.py b/fHDHR_web/api/tuners.py index adc34d6..0448611 100644 --- a/fHDHR_web/api/tuners.py +++ b/fHDHR_web/api/tuners.py @@ -31,25 +31,48 @@ class Tuners(): redirect_url = request.args.get('redirect', default=None, type=str) - if method in self.fhdhr.config.dict["streaming"]["valid_methods"]: + origin_methods = self.fhdhr.origins.valid_origins + origin = request.args.get('origin', default=None, type=str) + if origin and origin not in origin_methods: + return "%s Invalid channels origin" % origin + + if method in list(self.fhdhr.config.dict["streaming"]["valid_methods"].keys()): channel_number = request.args.get('channel', None, type=str) if not channel_number: return "Missing Channel" - if str(channel_number) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]: - response = Response("Not Found", status=404) - response.headers["X-fHDHR-Error"] = "801 - Unknown Channel" - self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) - abort(response) + if origin: - channel_dict = self.fhdhr.device.channels.get_channel_dict("number", channel_number) - if not channel_dict["enabled"]: + if str(channel_number) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]: + chan_obj = self.fhdhr.device.channels.get_channel_obj("number", channel_number, origin) + elif str(channel_number) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id", origin)]: + chan_obj = self.fhdhr.device.channels.get_channel_obj("id", channel_number, origin) + else: + response = Response("Not Found", status=404) + response.headers["X-fHDHR-Error"] = "801 - Unknown Channel" + self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) + abort(response) + + else: + + if str(channel_number) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id")]: + chan_obj = self.fhdhr.device.channels.get_channel_obj("id", channel_number) + else: + response = Response("Not Found", status=404) + response.headers["X-fHDHR-Error"] = "801 - Unknown Channel" + self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) + abort(response) + + if not chan_obj.dict["enabled"]: response = Response("Service Unavailable", status=503) response.headers["X-fHDHR-Error"] = str("806 - Tune Failed") self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) abort(response) + origin = chan_obj.origin + channel_number = chan_obj.number + duration = request.args.get('duration', default=0, type=int) transcode_quality = request.args.get('transcode', default=None, type=str) @@ -62,6 +85,7 @@ class Tuners(): stream_args = { "channel": channel_number, + "origin": origin, "method": method, "duration": duration, "origin_quality": self.fhdhr.config.dict["streaming"]["origin_quality"], @@ -73,9 +97,9 @@ class Tuners(): try: if not tuner_number: - tunernum = self.fhdhr.device.tuners.first_available(channel_number) + tunernum = self.fhdhr.device.tuners.first_available(origin, channel_number) else: - tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, channel_number) + tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, origin, channel_number) except TunerError as e: self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s" % (stream_args["method"], str(stream_args["channel"]), str(e))) @@ -84,20 +108,20 @@ class Tuners(): self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) abort(response) - tuner = self.fhdhr.device.tuners.tuners[str(tunernum)] + tuner = self.fhdhr.device.tuners.tuners[origin][str(tunernum)] try: stream_args = self.fhdhr.device.tuners.get_stream_info(stream_args) except TunerError as e: - self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s" - % (stream_args["method"], str(stream_args["channel"]), str(e))) + self.fhdhr.logger.info("A %s stream request for %s channel %s was rejected due to %s" + % (origin, stream_args["method"], str(stream_args["channel"]), str(e))) response = Response("Service Unavailable", status=503) response.headers["X-fHDHR-Error"] = str(e) self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) tuner.close() abort(response) - self.fhdhr.logger.info("Tuner #%s to be used for stream." % tunernum) + self.fhdhr.logger.info("%s Tuner #%s to be used for stream." % (origin, tunernum)) tuner.set_status(stream_args) session["tuner_used"] = tunernum @@ -105,31 +129,50 @@ class Tuners(): elif method == "close": - if not tuner_number or str(tuner_number) not in list(self.fhdhr.device.tuners.tuners.keys()): + if not origin: + return "Missing Origin" + + if not tuner_number or str(tuner_number) not in list(self.fhdhr.device.tuners.tuners[origin].keys()): return "%s Invalid tuner" % str(tuner_number) session["tuner_used"] = tuner_number - tuner = self.fhdhr.device.tuners.tuners[str(tuner_number)] + tuner = self.fhdhr.device.tuners.tuners[origin][str(tuner_number)] tuner.close() elif method == "scan": - if not tuner_number: - tunernum = self.fhdhr.device.tuners.first_available(None) + if not origin: + for origin in list(self.fhdhr.device.tuners.tuners.keys()): + if not tuner_number: + tunernum = self.fhdhr.device.tuners.first_available(origin, None) + else: + tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, origin, None) + tuner = self.fhdhr.device.tuners.tuners[origin][str(tunernum)] + tuner.channel_scan(origin=origin, grabbed=False) else: - tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, None) - tuner = self.fhdhr.device.tuners.tuners[str(tunernum)] - tuner.channel_scan(grabbed=True) + if not tuner_number: + tunernum = self.fhdhr.device.tuners.first_available(origin, None) + else: + tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, origin, None) + tuner = self.fhdhr.device.tuners.tuners[origin][str(tunernum)] + tuner.channel_scan(origin=origin, grabbed=True) elif method == "status": - if not tuner_number: - tuner_status = self.fhdhr.device.tuners.status() - elif str(tuner_number) in list(self.fhdhr.device.tuners.tuners.keys()): - tuner_status = self.fhdhr.device.tuners.tuners[str(tuner_number)].get_status() + if not origin: + if not tuner_number: + tuner_status = self.fhdhr.device.tuners.status() + else: + tuner_status = ["Invalid Tuner %s" % tuner_number] else: - tuner_status = ["Invalid Tuner %s" % tuner_number] + + if not tuner_number: + tuner_status = self.fhdhr.device.tuners.status(origin) + elif str(tuner_number) in list(self.fhdhr.device.tuners.tuners[origin].keys()): + tuner_status = self.fhdhr.device.tuners.tuners[origin][str(tuner_number)].get_status() + else: + tuner_status = ["Invalid Tuner %s" % tuner_number] tuner_status_json = json.dumps(tuner_status, indent=4) diff --git a/fHDHR_web/api/w3u.py b/fHDHR_web/api/w3u.py index b553c4d..083057f 100644 --- a/fHDHR_web/api/w3u.py +++ b/fHDHR_web/api/w3u.py @@ -26,6 +26,11 @@ class W3U(): if method == "get": + origin_methods = self.fhdhr.origins.valid_origins + origin = request.args.get('origin', default=None, type=str) + if origin and origin not in origin_methods: + return "%s Invalid channels origin" % origin + channel_info_m3u = { "name": self.fhdhr.config.dict["fhdhr"]["friendlyname"], "image": '%s/favicon.ico' % base_url, @@ -35,15 +40,30 @@ class W3U(): channel_items = [] - if channel == "all": - fileName = "channels.w3u" - for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels()]: - channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + if origin: + if channel == "all": + fileName = "channels.m3u" + for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin) + if channel_obj.enabled: + channel_items.append(channel_obj) + elif str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("number", channel, origin) + fileName = "%s.m3u" % channel_obj.number if channel_obj.enabled: channel_items.append(channel_obj) - elif str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]: - channel_obj = self.fhdhr.device.channels.get_channel_obj("number", channel) - fileName = "%s.w3u" % channel_obj.number + else: + return "Channel Disabled" + elif channel != "all" and str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id", origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", channel, origin) + fileName = "%s.m3u" % channel_obj.number + if channel_obj.enabled: + channel_items.append(channel_obj) + else: + return "Channel Disabled" + elif not origin and channel != "all" and str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id")]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", channel) + fileName = "%s.m3u" % channel_obj.number if channel_obj.enabled: channel_items.append(channel_obj) else: diff --git a/fHDHR_web/api/xmltv.py b/fHDHR_web/api/xmltv.py index dc76773..a023238 100644 --- a/fHDHR_web/api/xmltv.py +++ b/fHDHR_web/api/xmltv.py @@ -38,7 +38,7 @@ class xmlTV(): method = request.args.get('method', default="get", type=str) source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["def_method"], type=str) - if source not in self.fhdhr.config.dict["epg"]["valid_methods"]: + if source not in list(self.fhdhr.config.dict["epg"]["valid_methods"].keys()): return "%s Invalid xmltv method" % source redirect_url = request.args.get('redirect', default=None, type=str) @@ -47,10 +47,10 @@ class xmlTV(): epgdict = self.fhdhr.device.epg.get_epg(source) - if source in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: + if source in self.fhdhr.origins.valid_origins: epgdict = epgdict.copy() for c in list(epgdict.keys()): - chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"]) + chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"], source) epgdict[chan_obj.number] = epgdict.pop(c) epgdict[chan_obj.number]["name"] = chan_obj.dict["name"] epgdict[chan_obj.number]["callsign"] = chan_obj.dict["callsign"] @@ -113,9 +113,9 @@ class xmlTV(): out = self.xmltv_headers() - if source in ["origin", "blocks", self.fhdhr.config.dict["main"]["dictpopname"]]: + if source in self.fhdhr.origins.valid_origins: for c in list(epgdict.keys()): - chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"]) + chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"], source) epgdict[chan_obj.number] = epgdict.pop(c) epgdict[chan_obj.number]["name"] = chan_obj.dict["name"] epgdict[chan_obj.number]["callsign"] = chan_obj.dict["callsign"] diff --git a/fHDHR_web/files/__init__.py b/fHDHR_web/files/__init__.py index 905566b..ad6908b 100644 --- a/fHDHR_web/files/__init__.py +++ b/fHDHR_web/files/__init__.py @@ -2,7 +2,6 @@ from .favicon_ico import Favicon_ICO from .style_css import Style_CSS -from .device_xml import Device_XML class fHDHR_Files(): @@ -12,4 +11,3 @@ class fHDHR_Files(): self.favicon = Favicon_ICO(fhdhr) self.style = Style_CSS(fhdhr) - self.device_xml = Device_XML(fhdhr) diff --git a/fHDHR_web/files/device_xml.py b/fHDHR_web/files/device_xml.py deleted file mode 100644 index 031bf35..0000000 --- a/fHDHR_web/files/device_xml.py +++ /dev/null @@ -1,19 +0,0 @@ -from flask import redirect, session - - -class Device_XML(): - endpoints = ["/device.xml"] - endpoint_name = "file_device_xml" - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - - if self.fhdhr.config.dict["rmg"]["enabled"] and session["is_plexmediaserver"]: - return redirect("/rmg/device.xml") - else: - return redirect("/hdhr/device.xml") diff --git a/fHDHR_web/hdhr/lineup_json.py b/fHDHR_web/hdhr/lineup_json.py deleted file mode 100644 index f448657..0000000 --- a/fHDHR_web/hdhr/lineup_json.py +++ /dev/null @@ -1,46 +0,0 @@ -from flask import Response, request -import json - -from fHDHR.tools import channel_sort - - -class Lineup_JSON(): - endpoints = ["/lineup.json", "/hdhr/lineup.json"] - endpoint_name = "hdhr_lineup_json" - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - - base_url = request.url_root[:-1] - - show = request.args.get('show', default="all", type=str) - - channelslist = {} - for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels()]: - channel_obj = self.fhdhr.device.channels.list[fhdhr_id] - if channel_obj.enabled or show == "found": - lineup_dict = channel_obj.lineup_dict - lineup_dict["URL"] = "%s%s" % (base_url, lineup_dict["URL"]) - if show == "found" and channel_obj.enabled: - lineup_dict["Enabled"] = 1 - elif show == "found" and not channel_obj.enabled: - lineup_dict["Enabled"] = 0 - - channelslist[channel_obj.number] = lineup_dict - - # Sort the channels - sorted_channel_list = channel_sort(list(channelslist.keys())) - sorted_chan_guide = [] - for channel in sorted_channel_list: - sorted_chan_guide.append(channelslist[channel]) - - lineup_json = json.dumps(sorted_chan_guide, indent=4) - - return Response(status=200, - response=lineup_json, - mimetype='application/json') diff --git a/fHDHR_web/pages/__init__.py b/fHDHR_web/pages/__init__.py index 7cf10ca..ee99705 100644 --- a/fHDHR_web/pages/__init__.py +++ b/fHDHR_web/pages/__init__.py @@ -3,14 +3,12 @@ from .index_html import Index_HTML from .channels_html import Channels_HTML from .guide_html import Guide_HTML -from .cluster_html import Cluster_HTML from .tuners_html import Tuners_HTML from .xmltv_html import xmlTV_HTML from .version_html import Version_HTML from .diagnostics_html import Diagnostics_HTML from .settings_html import Settings_HTML from .channels_editor_html import Channels_Editor_HTML -from .tools_html import Tools_HTML class fHDHR_Pages(): @@ -22,10 +20,8 @@ class fHDHR_Pages(): self.channels_html = Channels_HTML(fhdhr) self.channels_editor_html = Channels_Editor_HTML(fhdhr) self.guide_html = Guide_HTML(fhdhr) - self.cluster_html = Cluster_HTML(fhdhr) self.tuners_html = Tuners_HTML(fhdhr) self.xmltv_html = xmlTV_HTML(fhdhr) self.version_html = Version_HTML(fhdhr) self.diagnostics_html = Diagnostics_HTML(fhdhr) self.settings_html = Settings_HTML(fhdhr) - self.tools_html = Tools_HTML(fhdhr) diff --git a/fHDHR_web/pages/channels_editor_html.py b/fHDHR_web/pages/channels_editor_html.py index 5b875ab..3292584 100644 --- a/fHDHR_web/pages/channels_editor_html.py +++ b/fHDHR_web/pages/channels_editor_html.py @@ -7,6 +7,7 @@ class Channels_Editor_HTML(): endpoints = ["/channels_editor", "/channels_editor.html"] endpoint_name = "page_channels_editor_html" endpoint_access_level = 2 + endpoint_category = "tool_pages" pretty_name = "Channels Editor" def __init__(self, fhdhr): @@ -17,14 +18,19 @@ class Channels_Editor_HTML(): def get(self, *args): + origin = request.args.get('source', default=self.fhdhr.device.epg.def_method, type=str) + origin_methods = self.fhdhr.origins.valid_origins + if origin not in origin_methods: + origin = origin_methods[0] + channelslist = {} - for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels()]: - channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin) channel_dict = channel_obj.dict.copy() channel_dict["number"] = channel_obj.number channel_dict["chan_thumbnail"] = channel_obj.thumbnail - channel_dict["m3u_url"] = channel_obj.m3u_url + channel_dict["m3u_url"] = channel_obj.api_m3u_url channelslist[channel_dict["number"]] = channel_dict @@ -34,4 +40,4 @@ class Channels_Editor_HTML(): for channel in sorted_channel_list: sorted_chan_guide.append(channelslist[channel]) - return render_template('channels_editor.html', session=session, request=request, fhdhr=self.fhdhr, channelslist=sorted_chan_guide, list=list) + return render_template('channels_editor.html', request=request, session=session, fhdhr=self.fhdhr, channelslist=sorted_chan_guide, origin=origin, origin_methods=origin_methods, list=list) diff --git a/fHDHR_web/pages/channels_html.py b/fHDHR_web/pages/channels_html.py index f36f59a..784e721 100644 --- a/fHDHR_web/pages/channels_html.py +++ b/fHDHR_web/pages/channels_html.py @@ -17,19 +17,24 @@ class Channels_HTML(): def get(self, *args): + origin = request.args.get('source', default=self.fhdhr.device.epg.def_method, type=str) + origin_methods = self.fhdhr.origins.valid_origins + if origin not in origin_methods: + origin = origin_methods[0] + channels_dict = { - "Total Channels": len(self.fhdhr.device.channels.get_channels()), + "Total Channels": len(self.fhdhr.device.channels.get_channels(origin)), "Enabled": 0 } channelslist = {} - for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels()]: - channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin) channel_dict = channel_obj.dict.copy() channel_dict["number"] = channel_obj.number channel_dict["chan_thumbnail"] = channel_obj.thumbnail - channel_dict["m3u_url"] = channel_obj.m3u_url + channel_dict["m3u_url"] = channel_obj.api_m3u_url channelslist[channel_dict["number"]] = channel_dict if channel_dict["enabled"]: @@ -41,4 +46,4 @@ class Channels_HTML(): for channel in sorted_channel_list: sorted_chan_guide.append(channelslist[channel]) - return render_template('channels.html', session=session, request=request, fhdhr=self.fhdhr, channelslist=sorted_chan_guide, channels_dict=channels_dict, list=list) + return render_template('channels.html', request=request, session=session, fhdhr=self.fhdhr, channelslist=sorted_chan_guide, channels_dict=channels_dict, origin=origin, origin_methods=origin_methods, list=list) diff --git a/fHDHR_web/pages/cluster_html.py b/fHDHR_web/pages/cluster_html.py deleted file mode 100644 index 4b846e0..0000000 --- a/fHDHR_web/pages/cluster_html.py +++ /dev/null @@ -1,52 +0,0 @@ -from flask import request, render_template, session -import urllib.parse - - -class Cluster_HTML(): - endpoints = ["/cluster", "/cluster.html"] - endpoint_name = "page_cluster_html" - endpoint_access_level = 1 - pretty_name = "Cluster/SSDP" - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - self.location_dict = { - "name": self.fhdhr.config.dict["fhdhr"]["friendlyname"], - "location": self.fhdhr.api.base, - "joined": "N/A", - "url_query": self.fhdhr.api.base_quoted - } - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - - locations_list = [] - - if self.fhdhr.config.dict["fhdhr"]["discovery_address"]: - - locations_list.append(self.location_dict) - - fhdhr_list = self.fhdhr.device.cluster.get_list() - for location in list(fhdhr_list.keys()): - - if location in list(self.fhdhr.device.cluster.cluster().keys()): - location_name = self.fhdhr.device.cluster.cluster()[location]["name"] - else: - try: - location_info_url = "%s/discover.json" % location - location_info_req = self.fhdhr.web.session.get(location_info_url) - location_info = location_info_req.json() - location_name = location_info["FriendlyName"] - except self.fhdhr.web.exceptions.ConnectionError: - self.fhdhr.logger.error("Unreachable: %s" % location) - location_dict = { - "name": location_name, - "location": location, - "joined": str(fhdhr_list[location]["Joined"]), - "url_query": urllib.parse.quote(location) - } - locations_list.append(location_dict) - - return render_template('cluster.html', session=session, request=request, fhdhr=self.fhdhr, locations_list=locations_list) diff --git a/fHDHR_web/pages/diagnostics_html.py b/fHDHR_web/pages/diagnostics_html.py index c16b1bf..1f9f02a 100644 --- a/fHDHR_web/pages/diagnostics_html.py +++ b/fHDHR_web/pages/diagnostics_html.py @@ -5,6 +5,7 @@ class Diagnostics_HTML(): endpoints = ["/diagnostics", "/diagnostics.html"] endpoint_name = "page_diagnostics_html" endpoint_access_level = 2 + endpoint_category = "tool_pages" pretty_name = "Diagnostics" def __init__(self, fhdhr): @@ -20,7 +21,7 @@ class Diagnostics_HTML(): button_dict = {} for route_group in list(session["route_list"].keys()): - if route_group not in ["pages", "brython", "files"]: + if route_group not in ["pages", "brython", "files", "tool_pages"]: button_dict[route_group] = [] for route_item in list(session["route_list"][route_group].keys()): if not session["route_list"][route_group][route_item]["name"].startswith("page_"): @@ -46,4 +47,4 @@ class Diagnostics_HTML(): curr_button_dict["button"] = False button_dict[route_group].append(curr_button_dict) - return render_template('diagnostics.html', session=session, request=request, fhdhr=self.fhdhr, button_dict=button_dict, list=list) + return render_template('diagnostics.html', request=request, session=session, fhdhr=self.fhdhr, button_dict=button_dict, list=list) diff --git a/fHDHR_web/pages/guide_html.py b/fHDHR_web/pages/guide_html.py index 5f687b6..5fa8a51 100644 --- a/fHDHR_web/pages/guide_html.py +++ b/fHDHR_web/pages/guide_html.py @@ -27,6 +27,9 @@ class Guide_HTML(): if source not in epg_methods: source = self.fhdhr.device.epg.def_method + if not source: + return render_template('guide.html', request=request, session=session, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list, epg_methods=epg_methods, source=source, list=list) + whatson = self.fhdhr.device.epg.whats_on_allchans(source) # Sort the channels @@ -60,14 +63,14 @@ class Guide_HTML(): else: chan_dict["listing_%s" % time_item] = str(datetime.datetime.fromtimestamp(sorted_chan_guide[channel]["listing"][0][time_item])) - if source in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: - chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", sorted_chan_guide[channel]["id"]) + if source in self.fhdhr.origins.valid_origins: + chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", sorted_chan_guide[channel]["id"], source) chan_dict["name"] = chan_obj.dict["name"] chan_dict["number"] = chan_obj.number chan_dict["chan_thumbnail"] = chan_obj.thumbnail chan_dict["enabled"] = chan_obj.dict["enabled"] - chan_dict["m3u_url"] = chan_obj.m3u_url + chan_dict["m3u_url"] = chan_obj.api_m3u_url chan_dict["listing_thumbnail"] = chan_dict["listing_thumbnail"] or chan_obj.thumbnail else: @@ -78,4 +81,4 @@ class Guide_HTML(): chan_guide_list.append(chan_dict) - return render_template('guide.html', session=session, request=request, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list, epg_methods=epg_methods, source=source) + return render_template('guide.html', request=request, session=session, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list, epg_methods=epg_methods, source=source, list=list) diff --git a/fHDHR_web/pages/index_html.py b/fHDHR_web/pages/index_html.py index 6fd6740..c584ada 100644 --- a/fHDHR_web/pages/index_html.py +++ b/fHDHR_web/pages/index_html.py @@ -15,15 +15,13 @@ class Index_HTML(): def get(self, *args): - tuners_in_use = self.fhdhr.device.tuners.inuse_tuner_count() - max_tuners = self.fhdhr.device.tuners.max_tuners + origin = self.fhdhr.origins.valid_origins[0] fhdhr_status_dict = { "Script Directory": str(self.fhdhr.config.internal["paths"]["script_dir"]), "Config File": str(self.fhdhr.config.config_file), "Cache Path": str(self.fhdhr.config.internal["paths"]["cache_dir"]), - "Total Channels": len(self.fhdhr.device.channels.list), - "Tuner Usage": ("%s/%s" % (str(tuners_in_use), str(max_tuners))), + "Total Channels": len(list(self.fhdhr.device.channels.list[origin].keys())), } - return render_template('index.html', session=session, request=request, fhdhr=self.fhdhr, fhdhr_status_dict=fhdhr_status_dict, list=list) + return render_template('index.html', request=request, session=session, fhdhr=self.fhdhr, fhdhr_status_dict=fhdhr_status_dict, list=list) diff --git a/fHDHR_web/pages/settings_html.py b/fHDHR_web/pages/settings_html.py index 0d34715..794cd88 100644 --- a/fHDHR_web/pages/settings_html.py +++ b/fHDHR_web/pages/settings_html.py @@ -5,6 +5,7 @@ class Settings_HTML(): endpoints = ["/settings", "/settings.html"] endpoint_name = "page_settings_html" endpoint_access_level = 1 + endpoint_category = "tool_pages" pretty_name = "Settings" def __init__(self, fhdhr): @@ -21,15 +22,12 @@ class Settings_HTML(): for config_item in list(self.fhdhr.config.conf_default[config_section].keys()): if self.fhdhr.config.conf_default[config_section][config_item]["config_web"]: - real_config_section = config_section - if config_section == self.fhdhr.config.dict["main"]["dictpopname"]: - real_config_section = "origin" web_settings_dict[config_section][config_item] = { - "value": self.fhdhr.config.dict[real_config_section][config_item], + "value": self.fhdhr.config.dict[config_section][config_item], "value_default": self.fhdhr.config.conf_default[config_section][config_item]["value"], "hide": self.fhdhr.config.conf_default[config_section][config_item]["config_web_hidden"] } if not len(web_settings_dict[config_section].keys()): del web_settings_dict[config_section] - return render_template('settings.html', session=session, request=request, fhdhr=self.fhdhr, web_settings_dict=web_settings_dict, list=list) + return render_template('settings.html', request=request, session=session, fhdhr=self.fhdhr, web_settings_dict=web_settings_dict, list=list) diff --git a/fHDHR_web/pages/tools_html.py b/fHDHR_web/pages/tools_html.py deleted file mode 100644 index 72b9b9e..0000000 --- a/fHDHR_web/pages/tools_html.py +++ /dev/null @@ -1,18 +0,0 @@ -from flask import request, render_template, session - - -class Tools_HTML(): - endpoints = ["/tools", "/tools.html"] - endpoint_name = "tools_html" - endpoint_access_level = 2 - pretty_name = "Tools" - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - - return render_template('tools.html', session=session, request=request, fhdhr=self.fhdhr) diff --git a/fHDHR_web/pages/tuners_html.py b/fHDHR_web/pages/tuners_html.py index 99978b9..9a251b6 100644 --- a/fHDHR_web/pages/tuners_html.py +++ b/fHDHR_web/pages/tuners_html.py @@ -7,6 +7,7 @@ class Tuners_HTML(): endpoints = ["/tuners", "/tuners.html"] endpoint_name = "page_streams_html" endpoint_access_level = 0 + endpoint_category = "tool_pages" pretty_name = "Tuners" def __init__(self, fhdhr): @@ -17,22 +18,36 @@ class Tuners_HTML(): def get(self, *args): - tuner_list = [] + tuner_status_dict = {} + tuner_status = self.fhdhr.device.tuners.status() - tuner_scanning = 0 - for tuner in list(tuner_status.keys()): - tuner_dict = { - "number": str(tuner), - "status": str(tuner_status[tuner]["status"]), - } - if tuner_status[tuner]["status"] == "Active": - tuner_dict["channel_number"] = tuner_status[tuner]["channel"] - tuner_dict["method"] = tuner_status[tuner]["method"] - tuner_dict["play_duration"] = str(tuner_status[tuner]["Play Time"]) - tuner_dict["downloaded"] = humanized_filesize(tuner_status[tuner]["downloaded"]) - elif tuner_status[tuner]["status"] == "Scanning": - tuner_scanning += 1 + for origin in list(tuner_status.keys()): + tuner_status_dict[origin] = {} + tuner_status_dict[origin]["scan_count"] = 0 + tuner_status_dict[origin]["status_list"] = [] + for tuner in list(tuner_status[origin].keys()): + if tuner_status[origin][tuner]["status"] == "Scanning": + tuner_status_dict[origin]["scan_count"] += 1 - tuner_list.append(tuner_dict) + tuner_dict = { + "number": str(tuner), + "status": str(tuner_status[origin][tuner]["status"]), + "origin": "N/A", + "channel_number": "N/A", + "method": "N/A", + "running_time": "N/A", + "downloaded": "N/A", + } - return render_template('tuners.html', session=session, request=request, fhdhr=self.fhdhr, tuner_list=tuner_list, tuner_scanning=tuner_scanning) + if tuner_status[origin][tuner]["status"] in ["Active", "Acquired", "Scanning"]: + tuner_dict["origin"] = tuner_status[origin][tuner]["origin"] + tuner_dict["channel_number"] = tuner_status[origin][tuner]["channel"] or "N/A" + tuner_dict["running_time"] = str(tuner_status[origin][tuner]["running_time"]) + + if tuner_status[origin][tuner]["status"] in "Active": + tuner_dict["method"] = tuner_status[origin][tuner]["method"] + tuner_dict["downloaded"] = humanized_filesize(tuner_status[origin][tuner]["downloaded"]) + + tuner_status_dict[origin]["status_list"].append(tuner_dict) + + return render_template('tuners.html', request=request, session=session, fhdhr=self.fhdhr, tuner_status_dict=tuner_status_dict, list=list) diff --git a/fHDHR_web/pages/version_html.py b/fHDHR_web/pages/version_html.py index dc1cfdf..1a4092e 100644 --- a/fHDHR_web/pages/version_html.py +++ b/fHDHR_web/pages/version_html.py @@ -5,6 +5,7 @@ class Version_HTML(): endpoints = ["/version", "/version.html"] endpoint_name = "page_version_html" endpoint_access_level = 1 + endpoint_category = "tool_pages" pretty_name = "Version" def __init__(self, fhdhr): @@ -14,7 +15,19 @@ class Version_HTML(): return self.get(*args) def get(self, *args): + version_dict = {} for key in list(self.fhdhr.config.internal["versions"].keys()): version_dict[key] = self.fhdhr.config.internal["versions"][key] - return render_template('version.html', session=session, request=request, fhdhr=self.fhdhr, version_dict=version_dict, list=list) + + # Sort the Version Info + sorted_version_list = sorted(version_dict, key=lambda i: (version_dict[i]['type'], version_dict[i]['name'])) + sorted_version_dict = { + "fHDHR": version_dict["fHDHR"], + "fHDHR_web": version_dict["fHDHR_web"] + } + for version_item in sorted_version_list: + if version_item not in ["fHDHR", "fHDHR_web"]: + sorted_version_dict[version_item] = version_dict[version_item] + + return render_template('version.html', request=request, session=session, fhdhr=self.fhdhr, version_dict=sorted_version_dict, list=list) diff --git a/fHDHR_web/pages/xmltv_html.py b/fHDHR_web/pages/xmltv_html.py index 32e0bd6..b1e06e9 100644 --- a/fHDHR_web/pages/xmltv_html.py +++ b/fHDHR_web/pages/xmltv_html.py @@ -5,6 +5,7 @@ class xmlTV_HTML(): endpoints = ["/xmltv", "/xmltv.html"] endpoint_name = "page_xmltv_html" endpoint_access_level = 1 + endpoint_category = "tool_pages" pretty_name = "xmltv" def __init__(self, fhdhr): @@ -15,4 +16,4 @@ class xmlTV_HTML(): def get(self, *args): - return render_template('xmltv.html', session=session, request=request, fhdhr=self.fhdhr) + return render_template('xmltv.html', request=request, session=session, fhdhr=self.fhdhr, list=list) diff --git a/fHDHR_web/rmg/device_xml.py b/fHDHR_web/rmg/device_xml.py deleted file mode 100644 index 8740bc9..0000000 --- a/fHDHR_web/rmg/device_xml.py +++ /dev/null @@ -1,58 +0,0 @@ -from flask import Response, request -from io import BytesIO -import xml.etree.ElementTree - -from fHDHR.tools import sub_el - - -class RMG_Device_XML(): - endpoints = ["/rmg/device.xml"] - endpoint_name = "rmg_device_xml" - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - """Device.xml referenced from SSDP""" - - base_url = request.url_root[:-1] - - out = xml.etree.ElementTree.Element('root') - out.set('xmlns', "urn:schemas-upnp-org:device-1-0") - - specVersion_out = sub_el(out, 'specVersion') - sub_el(specVersion_out, 'major', "1") - sub_el(specVersion_out, 'minor', "0") - - device_out = sub_el(out, 'device') - - sub_el(device_out, 'deviceType', "urn:plex-tv:device:Media:1") - - sub_el(device_out, 'friendlyName', self.fhdhr.config.dict["fhdhr"]["friendlyname"]) - sub_el(device_out, 'manufacturer', self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"]) - sub_el(device_out, 'manufacturerURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"]) - sub_el(device_out, 'modelName', self.fhdhr.config.dict["fhdhr"]["reporting_model"]) - sub_el(device_out, 'modelNumber', self.fhdhr.config.internal["versions"]["fHDHR"]) - - sub_el(device_out, 'modelDescription', self.fhdhr.config.dict["fhdhr"]["friendlyname"]) - sub_el(device_out, 'modelURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"]) - - serviceList_out = sub_el(device_out, 'serviceList') - service_out = sub_el(serviceList_out, 'service') - sub_el(out, 'URLBase', "%s" % base_url) - sub_el(service_out, 'serviceType', "urn:plex-tv:service:MediaGrabber:1") - sub_el(service_out, 'serviceId', "urn:plex-tv:serviceId:MediaGrabber") - - sub_el(device_out, 'UDN', "uuid:%s" % self.fhdhr.config.dict["main"]["uuid"]) - - fakefile = BytesIO() - fakefile.write(b'\n') - fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) - device_xml = fakefile.getvalue() - - return Response(status=200, - response=device_xml, - mimetype='application/xml') diff --git a/fHDHR_web/rmg/devices_devicekey_channels.py b/fHDHR_web/rmg/devices_devicekey_channels.py deleted file mode 100644 index 443ab06..0000000 --- a/fHDHR_web/rmg/devices_devicekey_channels.py +++ /dev/null @@ -1,47 +0,0 @@ -from flask import Response -from io import BytesIO -import xml.etree.ElementTree - -from fHDHR.tools import sub_el - - -class RMG_Devices_DeviceKey_Channels(): - endpoints = ["/devices//channels", "/rmg/devices//channels"] - endpoint_name = "rmg_devices_devicekey_channels" - endpoint_methods = ["GET"] - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, devicekey, *args): - return self.get(devicekey, *args) - - def get(self, devicekey, *args): - """Returns the current channels.""" - - out = xml.etree.ElementTree.Element('MediaContainer') - if devicekey == self.fhdhr.config.dict["main"]["uuid"]: - out.set('size', str(len(self.fhdhr.device.channels.list))) - for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels()]: - channel_obj = self.fhdhr.device.channels.list[fhdhr_id] - if channel_obj.enabled: - sub_el(out, 'Channel', - drm="0", - channelIdentifier=channel_obj.rmg_stream_ident, - name=channel_obj.dict["name"], - origin=channel_obj.dict["callsign"], - number=str(channel_obj.number), - type="tv", - # TODO param - signalStrength="100", - signalQuality="100", - ) - - fakefile = BytesIO() - fakefile.write(b'\n') - fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) - device_xml = fakefile.getvalue() - - return Response(status=200, - response=device_xml, - mimetype='application/xml') diff --git a/fHDHR_web/rmg/devices_devicekey_media.py b/fHDHR_web/rmg/devices_devicekey_media.py deleted file mode 100644 index 9e91ebe..0000000 --- a/fHDHR_web/rmg/devices_devicekey_media.py +++ /dev/null @@ -1,31 +0,0 @@ -from flask import request, redirect -import urllib.parse - - -class RMG_Devices_DeviceKey_Media(): - endpoints = ["/devices//media/", "/rmg/devices//media/"] - endpoint_name = "rmg_devices_devicekey_media" - endpoint_methods = ["GET"] - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, devicekey, channel, *args): - return self.get(devicekey, channel, *args) - - def get(self, devicekey, channel, *args): - - param = request.args.get('method', default=None, type=str) - self.fhdhr.logger.debug("param:%s" % param) - - method = self.fhdhr.config.dict["streaming"]["method"] - - redirect_url = "/api/tuners?method=%s" % (method) - - if str(channel).startswith('id://'): - channel = str(channel).replace('id://', '') - redirect_url += "&channel=%s" % str(channel) - - redirect_url += "&accessed=%s" % urllib.parse.quote(request.url) - - return redirect(redirect_url) diff --git a/fHDHR_web/rmg/devices_discover.py b/fHDHR_web/rmg/devices_discover.py deleted file mode 100644 index 82daca1..0000000 --- a/fHDHR_web/rmg/devices_discover.py +++ /dev/null @@ -1,49 +0,0 @@ -from flask import Response, request -from io import BytesIO -import xml.etree.ElementTree - -from fHDHR.tools import sub_el - - -class RMG_Devices_Discover(): - endpoints = ["/devices/discover", "/rmg/devices/discover"] - endpoint_name = "rmg_devices_discover" - endpoint_methods = ["GET", "POST"] - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - """This endpoint requests the grabber attempt to discover any devices it can, and it returns zero or more devices.""" - - base_url = request.url_root[:-1] - - out = xml.etree.ElementTree.Element('MediaContainer') - out.set('size', "1") - sub_el(out, 'Device', - key=self.fhdhr.config.dict["main"]["uuid"], - make=self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"], - model=self.fhdhr.config.dict["fhdhr"]["reporting_model"], - modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"], - protocol="livetv", - status="alive", - title=self.fhdhr.config.dict["fhdhr"]["friendlyname"], - tuners=str(self.fhdhr.config.dict["fhdhr"]["tuner_count"]), - uri=base_url, - uuid="device://tv.plex.grabbers.fHDHR/%s" % self.fhdhr.config.dict["main"]["uuid"], - thumb="favicon.ico", - interface='network' - # TODO add preferences - ) - - fakefile = BytesIO() - fakefile.write(b'\n') - fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) - device_xml = fakefile.getvalue() - - return Response(status=200, - response=device_xml, - mimetype='application/xml') diff --git a/fHDHR_web/rmg/devices_probe.py b/fHDHR_web/rmg/devices_probe.py deleted file mode 100644 index 6ed4c39..0000000 --- a/fHDHR_web/rmg/devices_probe.py +++ /dev/null @@ -1,54 +0,0 @@ -from flask import Response, request -from io import BytesIO -import xml.etree.ElementTree - -from fHDHR.tools import sub_el - - -class RMG_Devices_Probe(): - endpoints = ["/devices/probe", "/rmg/devices/probe"] - endpoint_name = "rmg_devices_probe" - endpoint_methods = ["GET", "POST"] - endpoint_default_parameters = { - "uri": "" - } - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - """Probes a specific URI for a network device, and returns a device, if it exists at the given URI.""" - - base_url = request.url_root[:-1] - - uri = request.args.get('uri', default=None, type=str) - - out = xml.etree.ElementTree.Element('MediaContainer') - out.set('size', "1") - if uri == base_url: - sub_el(out, 'Device', - key=self.fhdhr.config.dict["main"]["uuid"], - make=self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"], - model=self.fhdhr.config.dict["fhdhr"]["reporting_model"], - modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"], - protocol="livetv", - status="alive", - title=self.fhdhr.config.dict["fhdhr"]["friendlyname"], - tuners=str(self.fhdhr.config.dict["fhdhr"]["tuner_count"]), - uri=base_url, - uuid="device://tv.plex.grabbers.fHDHR/%s" % self.fhdhr.config.dict["main"]["uuid"], - thumb="favicon.ico", - interface='network' - ) - - fakefile = BytesIO() - fakefile.write(b'\n') - fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) - device_xml = fakefile.getvalue() - - return Response(status=200, - response=device_xml, - mimetype='application/xml') diff --git a/fHDHR_web/templates/base.html b/fHDHR_web/templates/base.html index 6f0df0a..64e9b31 100644 --- a/fHDHR_web/templates/base.html +++ b/fHDHR_web/templates/base.html @@ -28,7 +28,6 @@
- {% for page_dict in session["route_list"]["pages"] %} {% if session["route_list"]["pages"][page_dict]["name"] != "page_index_html" and fhdhr.config.dict["web_ui"]["access_level"] >= session["route_list"]["pages"][page_dict]["endpoint_access_level"] %} @@ -64,17 +63,11 @@
- {% if fhdhr.config.dict["web_ui"]["cluster_bar"] %} - {% set locations = fhdhr.device.cluster.get_cluster_dicts_web() %} - {% if locations %} -
- {% for location in locations %} - - {% endfor %} -
-
+ {% for page_dict in session["route_list"]["tool_pages"] %} + {% if session["route_list"]["tool_pages"][page_dict]["name"] != "page_index_html" and fhdhr.config.dict["web_ui"]["access_level"] >= session["route_list"]["tool_pages"][page_dict]["endpoint_access_level"] %} + {% endif %} - {% endif %} + {% endfor %} {% block content %}{% endblock %} diff --git a/fHDHR_web/templates/channels.html b/fHDHR_web/templates/channels.html index 706d3ae..b36a47f 100644 --- a/fHDHR_web/templates/channels.html +++ b/fHDHR_web/templates/channels.html @@ -4,8 +4,14 @@

{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Channels

+

+ {% for origin in origin_methods %} + + {% endfor %} +

+
- +

Note: This may take some time.


@@ -25,7 +31,7 @@
- +

diff --git a/fHDHR_web/templates/guide.html b/fHDHR_web/templates/guide.html index 213fabf..3882ecc 100644 --- a/fHDHR_web/templates/guide.html +++ b/fHDHR_web/templates/guide.html @@ -14,7 +14,7 @@ - {% if source in ["blocks", "origin", fhdhr.config.dict["main"]["dictpopname"]] %} + {% if source in fhdhr.origins.valid_origins %} {% endif %} @@ -32,7 +32,7 @@ {% for chan_dict in chan_guide_list %} - {% if source in ["blocks", "origin", fhdhr.config.dict["main"]["dictpopname"]] %} + {% if source in fhdhr.origins.valid_origins %} + + + + + + + + + + + {% endfor %} + +
PlayChannel Name
{% if chan_dict["enabled"] %} Play diff --git a/fHDHR_web/templates/settings.html b/fHDHR_web/templates/settings.html index dbc206f..80514b2 100644 --- a/fHDHR_web/templates/settings.html +++ b/fHDHR_web/templates/settings.html @@ -14,11 +14,8 @@ {% for config_section in list(web_settings_dict.keys()) %} - {% if config_section == "origin" %} -

{{ fhdhr.config.dict["main"]["dictpopname"] }}

- {% else %} -

{{ config_section }}

- {% endif %} +

{{ config_section }}

+
diff --git a/fHDHR_web/templates/tuners.html b/fHDHR_web/templates/tuners.html index 34e86c1..2c98f3b 100644 --- a/fHDHR_web/templates/tuners.html +++ b/fHDHR_web/templates/tuners.html @@ -4,49 +4,48 @@

fHDHR Streams

-
-
- - - - - - - - - - + {% for origin in list(tuner_status_dict.keys()) %} - {% for tuner_dict in tuner_list %} +

{{ origin }}

+ +
+
TunerStatusChannelMethodTime ActiveTotal DownloadedActions
+ - - - {% if tuner_dict["status"] in ["Active", "Acquired"] %} - - {% else %} - - {% endif %} - {% if tuner_dict["status"] == "Active" %} - - - - {% else %} - - - - {% endif %} - + + + + + + + + - {% endfor %} - -
{{ tuner_dict["number"] }}{{ tuner_dict["status"] }}{{ tuner_dict["channel_number"] }}N/A{{ tuner_dict["method"] }}{{ tuner_dict["play_duration"] }}{{ tuner_dict["downloaded"] }}N/AN/AN/A - {% if tuner_dict["status"] != "Inactive" %} - - {% endif %} - {% if not tuner_scanning and tuner_dict["status"] == "Inactive" %} - - {% endif %} - TunerStatusOriginChannelMethodTime ActiveTotal DownloadedActions
-
+ + {% for tuner_dict in tuner_status_dict[origin]["status_list"] %} +
{{ tuner_dict["number"] }}{{ tuner_dict["status"] }}{{ tuner_dict["origin"] }}{{ tuner_dict["channel_number"] }}{{ tuner_dict["method"] }}{{ tuner_dict["running_time"] }}{{ tuner_dict["downloaded"] }} + {% if tuner_dict["status"] != "Inactive" %} + + {% endif %} + {% if not tuner_status_dict[origin]["scan_count"] and tuner_dict["status"] == "Inactive" %} + + {% endif %} +
+ + + {% endfor %} {% endblock %} diff --git a/fHDHR_web/templates/version.html b/fHDHR_web/templates/version.html index 0307cfb..988ea32 100644 --- a/fHDHR_web/templates/version.html +++ b/fHDHR_web/templates/version.html @@ -10,8 +10,9 @@ {% for key in list(version_dict.keys()) %} + {{ version_dict[key]["type"] }} {{ key }} - {{ version_dict[key] }} + {{ version_dict[key]["version"] }} {% endfor %} diff --git a/fHDHR_web/templates/xmltv.html b/fHDHR_web/templates/xmltv.html index 69bf75e..6e49fee 100644 --- a/fHDHR_web/templates/xmltv.html +++ b/fHDHR_web/templates/xmltv.html @@ -14,16 +14,12 @@ Actions - {% for epg_method in fhdhr.config.dict["epg"]["valid_methods"] %} + {% for epg_method in list(fhdhr.config.dict["epg"]["valid_methods"].keys()) %} {% if epg_method not in [None, "None"] %} - {% set epg_method_name = epg_method %} - {% if epg_method == "origin" %} - {% set epg_method_name = fhdhr.config.dict["main"]["dictpopname"] %} - {% endif %} - {{ epg_method_name }} - {{ epg_method_name }} - {{ epg_method_name }} + {{ epg_method }} + {{ epg_method }} + {{ epg_method }} diff --git a/fHDHR_web/web_ui_conf.json b/fHDHR_web/web_ui_conf.json index 7bacc73..f7dc862 100644 --- a/fHDHR_web/web_ui_conf.json +++ b/fHDHR_web/web_ui_conf.json @@ -10,11 +10,5 @@ "config_file": true, "config_web": true } - , - "cluster_bar":{ - "value": true, - "config_file": true, - "config_web": true - } } } diff --git a/main.py b/main.py index a2c7d81..698a781 100644 --- a/main.py +++ b/main.py @@ -9,9 +9,8 @@ import pathlib from fHDHR.cli import run import fHDHR_web -import plugins SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.abspath(__file__))) if __name__ == '__main__': - sys.exit(run.main(SCRIPT_DIR, fHDHR_web, plugins)) + sys.exit(run.main(SCRIPT_DIR, fHDHR_web)) diff --git a/plugins/__init__.py b/plugins/__init__.py deleted file mode 100644 index 26ffe0b..0000000 --- a/plugins/__init__.py +++ /dev/null @@ -1,70 +0,0 @@ -import os -import sys -import pathlib - -plugins_top_dir = os.path.dirname(__file__) -print("Importing Plugins Prior to Script Run.") -plugin_dict = {} -for entry in os.scandir(plugins_top_dir): - if entry.is_dir() and not entry.is_file() and entry.name[0] != '_': - - curr_dict = {} - plugin_use = True - - # Import - imp_string = "from .%s import *" % entry.name - exec(imp_string) - - for plugin_item in ["NAME", "VERSION", "TYPE"]: - plugin_item_eval_string = "%s.PLUGIN_%s" % (entry.name, plugin_item) - try: - curr_dict[plugin_item] = eval(plugin_item_eval_string) - except AttributeError: - curr_dict[plugin_item] = None - - if curr_dict["TYPE"] == "origin": - curr_dict["PATH"] = pathlib.Path(os.path.dirname(os.path.abspath(__file__))).joinpath(entry.name).joinpath('origin') - elif curr_dict["TYPE"] in ["alt_epg", "alt_stream"]: - curr_dict["PATH"] = pathlib.Path(os.path.dirname(os.path.abspath(__file__))).joinpath(entry.name) - - plugin_import_print_string = "Found %s type plugin: %s %s. " % (curr_dict["TYPE"], curr_dict["NAME"], curr_dict["VERSION"]) - if not any(curr_dict[plugin_item] for plugin_item in ["NAME", "VERSION", "TYPE"]): - plugin_import_print_string += " ImportWarning: Missing PLUGIN_* Value." - plugin_use = False - - elif curr_dict["TYPE"] not in ["origin", "alt_epg", "alt_stream"]: - plugin_use = False - plugin_import_print_string += " ImportWarning: Invalid PLUGIN_TYPE." - - # Only allow a single origin - elif curr_dict["TYPE"] == "origin" and len([x for x in list(plugin_dict.keys()) if plugin_dict[x]["TYPE"] == "origin"]): - plugin_use = False - plugin_import_print_string += " ImportWarning: Only one Origin Allowed." - - if plugin_use: - plugin_import_print_string += " Import Success" - - # add to plugin_dict - print(plugin_import_print_string) - if plugin_use and entry.name not in plugin_dict: - plugin_dict[entry.name] = curr_dict - - # Import Origin - if curr_dict["TYPE"] == "origin": - imp_string = "from .%s import origin" % entry.name - exec(imp_string) - imp_string = "from .%s import %s_Setup" % (entry.name, curr_dict["NAME"].upper()) - try: - exec(imp_string) - except ImportError: - pass - elif curr_dict["TYPE"] == "alt_epg": - imp_string = "from .%s import *" % entry.name - exec(imp_string) - elif curr_dict["TYPE"] == "alt_stream": - imp_string = "from .%s import *" % entry.name - exec(imp_string) - -if not len([x for x in list(plugin_dict.keys()) if plugin_dict[x]["TYPE"] == "origin"]): - print("No Origin Plugin found.") - sys.exit(1) diff --git a/plugins/fHDHR_plugin_epg_tvtv/__init__.py b/plugins/fHDHR_plugin_epg_tvtv/__init__.py index ff519d3..7c0dbe5 100644 --- a/plugins/fHDHR_plugin_epg_tvtv/__init__.py +++ b/plugins/fHDHR_plugin_epg_tvtv/__init__.py @@ -2,30 +2,21 @@ import datetime from fHDHR.exceptions import EPGSetupError -PLUGIN_NAME = "tvtv" -PLUGIN_VERSION = "v0.6.0-beta" -PLUGIN_TYPE = "alt_epg" +class Plugin_OBJ(): -class TVTV_Setup(): - def __init__(self, config): - pass - - -class tvtvEPG(): - - def __init__(self, fhdhr, channels): - self.fhdhr = fhdhr + def __init__(self, channels, plugin_utils): + self.plugin_utils = plugin_utils self.channels = channels @property def postalcode(self): - if self.fhdhr.config.dict["tvtv"]["postalcode"]: - return self.fhdhr.config.dict["tvtv"]["postalcode"] + if self.plugin_utils.config.dict["tvtv"]["postalcode"]: + return self.plugin_utils.config.dict["tvtv"]["postalcode"] try: postalcode_url = 'http://ipinfo.io/json' - postalcode_req = self.fhdhr.web.session.get(postalcode_url) + postalcode_req = self.plugin_utils.web.session.get(postalcode_url) data = postalcode_req.json() postalcode = data["postal"] except Exception as e: @@ -36,9 +27,9 @@ class tvtvEPG(): @property def lineup_id(self): lineup_id_url = "https://www.tvtv.us/tvm/t/tv/v4/lineups?postalCode=%s" % self.postalcode - if self.fhdhr.config.dict["tvtv"]["lineuptype"]: - lineup_id_url += "&lineupType=%s" % self.fhdhr.config.dict["tvtv"]["lineuptype"] - lineup_id_req = self.fhdhr.web.session.get(lineup_id_url) + if self.plugin_utils.config.dict["tvtv"]["lineuptype"]: + lineup_id_url += "&lineupType=%s" % self.plugin_utils.config.dict["tvtv"]["lineuptype"] + lineup_id_req = self.plugin_utils.web.session.get(lineup_id_url) data = lineup_id_req.json() lineup_id = data[0]["lineupID"] return lineup_id @@ -122,43 +113,43 @@ class tvtvEPG(): stoptime = "%s%s" % (datesdict["stop"], "T00%3A00%3A00.000Z") url = "https://www.tvtv.us/tvm/t/tv/v4/lineups/%s/listings/grid?start=%s&end=%s" % (self.lineup_id, starttime, stoptime) self.get_cached_item(str(datesdict["start"]), url) - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "tvtv") or [] - return [self.fhdhr.db.get_cacheitem_value(x, "epg_cache", "tvtv") for x in cache_list] + cache_list = self.plugin_utils.db.get_plugin_value("cache_list", "epg_cache", "tvtv") or [] + return [self.plugin_utils.db.get_plugin_value(x, "epg_cache", "tvtv") for x in cache_list] def get_cached_item(self, cache_key, url): - cacheitem = self.fhdhr.db.get_cacheitem_value(cache_key, "epg_cache", "tvtv") + cacheitem = self.plugin_utils.db.get_plugin_value(cache_key, "epg_cache", "tvtv") if cacheitem: - self.fhdhr.logger.info("FROM CACHE: %s" % cache_key) + self.plugin_utils.logger.info("FROM CACHE: %s" % cache_key) return cacheitem else: - self.fhdhr.logger.info("Fetching: %s" % url) + self.plugin_utils.logger.info("Fetching: %s" % url) try: - resp = self.fhdhr.web.session.get(url) - except self.fhdhr.web.exceptions.HTTPError: - self.fhdhr.logger.info('Got an error! Ignoring it.') + resp = self.plugin_utils.web.session.get(url) + except self.plugin_utils.web.exceptions.HTTPError: + self.plugin_utils.logger.info('Got an error! Ignoring it.') return result = resp.json() - self.fhdhr.db.set_cacheitem_value(cache_key, "epg_cache", result, "tvtv") - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "tvtv") or [] + self.plugin_utils.db.set_plugin_value(cache_key, "epg_cache", result, "tvtv") + cache_list = self.plugin_utils.db.get_plugin_value("cache_list", "epg_cache", "tvtv") or [] cache_list.append(cache_key) - self.fhdhr.db.set_cacheitem_value("cache_list", "epg_cache", cache_list, "tvtv") + self.plugin_utils.db.set_plugin_value("cache_list", "epg_cache", cache_list, "tvtv") def remove_stale_cache(self, todaydate): - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "tvtv") or [] + cache_list = self.plugin_utils.db.get_plugin_value("cache_list", "epg_cache", "tvtv") or [] cache_to_kill = [] for cacheitem in cache_list: cachedate = datetime.datetime.strptime(str(cacheitem), "%Y-%m-%d") todaysdate = datetime.datetime.strptime(str(todaydate), "%Y-%m-%d") if cachedate < todaysdate: cache_to_kill.append(cacheitem) - self.fhdhr.db.delete_cacheitem_value(cacheitem, "epg_cache", "tvtv") - self.fhdhr.logger.info("Removing stale cache: %s" % cacheitem) - self.fhdhr.db.set_cacheitem_value("cache_list", "epg_cache", [x for x in cache_list if x not in cache_to_kill], "tvtv") + self.plugin_utils.db.delete_plugin_value(cacheitem, "epg_cache", "tvtv") + self.plugin_utils.logger.info("Removing stale cache: %s" % cacheitem) + self.plugin_utils.db.set_plugin_value("cache_list", "epg_cache", [x for x in cache_list if x not in cache_to_kill], "tvtv") def clear_cache(self): - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "tvtv") or [] + cache_list = self.plugin_utils.db.get_plugin_value("cache_list", "epg_cache", "tvtv") or [] for cacheitem in cache_list: - self.fhdhr.db.delete_cacheitem_value(cacheitem, "epg_cache", "tvtv") - self.fhdhr.logger.info("Removing cache: %s" % str(cacheitem)) - self.fhdhr.db.delete_cacheitem_value("cache_list", "epg_cache", "tvtv") + self.plugin_utils.db.delete_plugin_value(cacheitem, "epg_cache", "tvtv") + self.plugin_utils.logger.info("Removing cache: %s" % str(cacheitem)) + self.plugin_utils.db.delete_plugin_value("cache_list", "epg_cache", "tvtv") diff --git a/plugins/fHDHR_plugin_epg_tvtv/plugin.json b/plugins/fHDHR_plugin_epg_tvtv/plugin.json new file mode 100644 index 0000000..1b9186a --- /dev/null +++ b/plugins/fHDHR_plugin_epg_tvtv/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"tvtv", + "version":"v0.6.0-beta", + "type":"alt_epg" +} diff --git a/plugins/fHDHR_plugin_epg_zap2it/__init__.py b/plugins/fHDHR_plugin_epg_zap2it/__init__.py index c74ab50..17ac077 100644 --- a/plugins/fHDHR_plugin_epg_zap2it/__init__.py +++ b/plugins/fHDHR_plugin_epg_zap2it/__init__.py @@ -4,30 +4,21 @@ import urllib.parse from fHDHR.tools import xmldictmaker from fHDHR.exceptions import EPGSetupError -PLUGIN_NAME = "zap2it" -PLUGIN_VERSION = "v0.6.0-beta" -PLUGIN_TYPE = "alt_epg" +class Plugin_OBJ(): -class ZAP2IT_Setup(): - def __init__(self, config): - pass - - -class zap2itEPG(): - - def __init__(self, fhdhr, channels): - self.fhdhr = fhdhr + def __init__(self, channels, plugin_utils): + self.plugin_utils = plugin_utils self.channels = channels @property def postalcode(self): - if self.fhdhr.config.dict["zap2it"]["postalcode"]: - return self.fhdhr.config.dict["zap2it"]["postalcode"] + if self.plugin_utils.config.dict["zap2it"]["postalcode"]: + return self.plugin_utils.config.dict["zap2it"]["postalcode"] try: postalcode_url = 'http://ipinfo.io/json' - postalcode_req = self.fhdhr.web.session.get(postalcode_url) + postalcode_req = self.plugin_utils.web.session.get(postalcode_url) data = postalcode_req.json() postalcode = data["postal"] except Exception as e: @@ -41,12 +32,12 @@ class zap2itEPG(): # Start time parameter is now rounded down to nearest `zap_timespan`, in s. zap_time = datetime.datetime.utcnow().timestamp() self.remove_stale_cache(zap_time) - zap_time_window = int(self.fhdhr.config.dict["zap2it"]["timespan"]) * 3600 + zap_time_window = int(self.plugin_utils.config.dict["zap2it"]["timespan"]) * 3600 zap_time = int(zap_time - (zap_time % zap_time_window)) # Fetch data in `zap_timespan` chunks. i_times = [] - for i in range(int(7 * 24 / int(self.fhdhr.config.dict["zap2it"]["timespan"]))): + for i in range(int(7 * 24 / int(self.plugin_utils.config.dict["zap2it"]["timespan"]))): i_times.append(zap_time + (i * zap_time_window)) cached_items = self.get_cached(i_times) @@ -120,18 +111,18 @@ class zap2itEPG(): for i_time in i_times: parameters = { - 'aid': self.fhdhr.config.dict["zap2it"]['affiliate_id'], - 'country': self.fhdhr.config.dict["zap2it"]['country'], - 'device': self.fhdhr.config.dict["zap2it"]['device'], - 'headendId': self.fhdhr.config.dict["zap2it"]['headendid'], + 'aid': self.plugin_utils.config.dict["zap2it"]['affiliate_id'], + 'country': self.plugin_utils.config.dict["zap2it"]['country'], + 'device': self.plugin_utils.config.dict["zap2it"]['device'], + 'headendId': self.plugin_utils.config.dict["zap2it"]['headendid'], 'isoverride': "true", - 'languagecode': self.fhdhr.config.dict["zap2it"]['languagecode'], + 'languagecode': self.plugin_utils.config.dict["zap2it"]['languagecode'], 'pref': 'm,p', - 'timespan': self.fhdhr.config.dict["zap2it"]['timespan'], - 'timezone': self.fhdhr.config.dict["zap2it"]['timezone'], - 'userId': self.fhdhr.config.dict["zap2it"]['userid'], + 'timespan': self.plugin_utils.config.dict["zap2it"]['timespan'], + 'timezone': self.plugin_utils.config.dict["zap2it"]['timezone'], + 'userId': self.plugin_utils.config.dict["zap2it"]['userid'], 'postalCode': str(self.postalcode), - 'lineupId': '%s-%s-DEFAULT' % (self.fhdhr.config.dict["zap2it"]['country'], self.fhdhr.config.dict["zap2it"]['device']), + 'lineupId': '%s-%s-DEFAULT' % (self.plugin_utils.config.dict["zap2it"]['country'], self.plugin_utils.config.dict["zap2it"]['device']), 'time': i_time, 'Activity_ID': 1, 'FromPage': "TV%20Guide", @@ -140,43 +131,43 @@ class zap2itEPG(): url = 'https://tvlistings.zap2it.com/api/grid?' url += urllib.parse.urlencode(parameters) self.get_cached_item(str(i_time), url) - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "zap2it") or [] - return [self.fhdhr.db.get_cacheitem_value(x, "epg_cache", "zap2it") for x in cache_list] + cache_list = self.plugin_utils.db.get_plugin_value("cache_list", "epg_cache", "zap2it") or [] + return [self.plugin_utils.db.get_plugin_value(x, "epg_cache", "zap2it") for x in cache_list] def get_cached_item(self, cache_key, url): - cacheitem = self.fhdhr.db.get_cacheitem_value(cache_key, "epg_cache", "zap2it") + cacheitem = self.plugin_utils.db.get_plugin_value(cache_key, "epg_cache", "zap2it") if cacheitem: - self.fhdhr.logger.info("FROM CACHE: %s" % cache_key) + self.plugin_utils.logger.info("FROM CACHE: %s" % cache_key) return cacheitem else: - self.fhdhr.logger.info("Fetching: %s" % url) + self.plugin_utils.logger.info("Fetching: %s" % url) try: - resp = self.fhdhr.web.session.get(url) - except self.fhdhr.web.exceptions.HTTPError: - self.fhdhr.logger.info('Got an error! Ignoring it.') + resp = self.plugin_utils.web.session.get(url) + except self.plugin_utils.web.exceptions.HTTPError: + self.plugin_utils.logger.info('Got an error! Ignoring it.') return result = resp.json() - self.fhdhr.db.set_cacheitem_value(cache_key, "epg_cache", result, "zap2it") - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "zap2it") or [] + self.plugin_utils.db.set_plugin_value(cache_key, "epg_cache", result, "zap2it") + cache_list = self.plugin_utils.db.get_plugin_value("cache_list", "epg_cache", "zap2it") or [] cache_list.append(cache_key) - self.fhdhr.db.set_cacheitem_value("cache_list", "epg_cache", cache_list, "zap2it") + self.plugin_utils.db.set_plugin_value("cache_list", "epg_cache", cache_list, "zap2it") def remove_stale_cache(self, zap_time): - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "zap2it") or [] + cache_list = self.plugin_utils.db.get_plugin_value("cache_list", "epg_cache", "zap2it") or [] cache_to_kill = [] for cacheitem in cache_list: cachedate = int(cacheitem) if cachedate < zap_time: cache_to_kill.append(cacheitem) - self.fhdhr.db.delete_cacheitem_value(cacheitem, "epg_cache", "zap2it") - self.fhdhr.logger.info("Removing stale cache: %s" % cacheitem) - self.fhdhr.db.set_cacheitem_value("cache_list", "epg_cache", [x for x in cache_list if x not in cache_to_kill], "zap2it") + self.plugin_utils.db.delete_plugin_value(cacheitem, "epg_cache", "zap2it") + self.plugin_utils.logger.info("Removing stale cache: %s" % cacheitem) + self.plugin_utils.db.set_plugin_value("cache_list", "epg_cache", [x for x in cache_list if x not in cache_to_kill], "zap2it") def clear_cache(self): - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "zap2it") or [] + cache_list = self.plugin_utils.db.get_plugin_value("cache_list", "epg_cache", "zap2it") or [] for cacheitem in cache_list: - self.fhdhr.db.delete_cacheitem_value(cacheitem, "epg_cache", "zap2it") - self.fhdhr.logger.info("Removing cache: %s" % cacheitem) - self.fhdhr.db.delete_cacheitem_value("cache_list", "epg_cache", "zap2it") + self.plugin_utils.db.delete_plugin_value(cacheitem, "epg_cache", "zap2it") + self.plugin_utils.logger.info("Removing cache: %s" % cacheitem) + self.plugin_utils.db.delete_plugin_value("cache_list", "epg_cache", "zap2it") diff --git a/plugins/fHDHR_plugin_epg_zap2it/plugin.json b/plugins/fHDHR_plugin_epg_zap2it/plugin.json new file mode 100644 index 0000000..75f9ccc --- /dev/null +++ b/plugins/fHDHR_plugin_epg_zap2it/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"zap2it", + "version":"v0.6.0-beta", + "type":"alt_epg" +} diff --git a/plugins/fHDHR_plugin_interface_cluster/cluster_conf.json b/plugins/fHDHR_plugin_interface_cluster/cluster_conf.json new file mode 100644 index 0000000..3576220 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/cluster_conf.json @@ -0,0 +1,14 @@ +{ + "cluster":{ + "enabled":{ + "value": true, + "config_file": true, + "config_web": true + }, + "friendlyname":{ + "value": "none", + "config_file": true, + "config_web": true + } + } +} diff --git a/plugins/fHDHR_plugin_interface_cluster/interface/__init__.py b/plugins/fHDHR_plugin_interface_cluster/interface/__init__.py new file mode 100644 index 0000000..dafa1d3 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/interface/__init__.py @@ -0,0 +1,157 @@ +from collections import OrderedDict + + +class Plugin_OBJ(): + + def __init__(self, fhdhr, plugin_utils): + self.fhdhr = fhdhr + self.plugin_utils = plugin_utils + + self.friendlyname = self.fhdhr.config.dict["cluster"]["friendlyname"] or "%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], self.fhdhr.origins.valid_origins[0]) + + if self.plugin_utils.config.dict["fhdhr"]["discovery_address"]: + self.startup_sync() + + def cluster(self): + return self.plugin_utils.db.get_plugin_value("cluster", "dict") or self.default_cluster() + + def get_cluster_dicts_web(self): + fhdhr_list = self.cluster() + locations = [] + for location in list(fhdhr_list.keys()): + item_dict = { + "base_url": fhdhr_list[location]["base_url"], + "name": fhdhr_list[location]["name"] + } + if item_dict["base_url"] != self.plugin_utils.api.base: + locations.append(item_dict) + if len(locations): + locations = sorted(locations, key=lambda i: i['name']) + return locations + else: + return None + + def get_list(self): + cluster = self.plugin_utils.db.get_plugin_value("cluster", "dict") or self.default_cluster() + return_dict = {} + for location in list(cluster.keys()): + if location != self.plugin_utils.api.base: + return_dict[location] = { + "Joined": True + } + + detected_list = self.plugin_utils.ssdp.ssdp_handling[self.plugin_utils.namespace].detect_method.get() + for location in detected_list: + if location not in list(cluster.keys()): + return_dict[location] = { + "Joined": False + } + return_dict = OrderedDict(sorted(return_dict.items())) + return return_dict + + def default_cluster(self): + defdict = {} + defdict[self.plugin_utils.api.base] = { + "base_url": self.plugin_utils.api.base, + "name": self.friendlyname + } + return defdict + + def startup_sync(self): + self.plugin_utils.logger.info("Syncronizing with Cluster.") + cluster = self.plugin_utils.db.get_plugin_value("cluster", "dict") or self.default_cluster() + if not len(list(cluster.keys())): + self.plugin_utils.logger.info("No Cluster Found.") + else: + self.plugin_utils.logger.info("Found %s clustered services." % str(len(list(cluster.keys())))) + for location in list(cluster.keys()): + if location != self.plugin_utils.api.base: + self.plugin_utils.logger.debug("Checking Cluster Syncronization information from %s." % location) + sync_url = "%s/api/cluster?method=get" % location + try: + sync_open = self.plugin_utils.web.session.get(sync_url) + retrieved_cluster = sync_open.json() + if self.plugin_utils.api.base not in list(retrieved_cluster.keys()): + return self.leave() + except self.plugin_utils.web.exceptions.ConnectionError: + self.plugin_utils.logger.error("Unreachable: %s" % location) + + def leave(self): + self.plugin_utils.logger.info("Leaving cluster.") + self.plugin_utils.db.set_plugin_value("cluster", "dict", self.default_cluster()) + + def disconnect(self): + cluster = self.plugin_utils.db.get_plugin_value("cluster", "dict") or self.default_cluster() + for location in list(cluster.keys()): + if location != self.plugin_utils.api.base: + self.plugin_utils.logger.info("Informing %s that I am departing the Cluster." % location) + sync_url = "%s/api/cluster?method=del&location=%s" % (location, self.plugin_utils.api.base) + try: + self.plugin_utils.web.session.get(sync_url) + except self.plugin_utils.web.exceptions.ConnectionError: + self.plugin_utils.logger.error("Unreachable: %s" % location) + self.leave() + + def sync(self, location): + sync_url = "%s/api/cluster?method=get" % location + try: + sync_open = self.plugin_utils.web.session.get(sync_url) + self.plugin_utils.db.set_plugin_value("cluster", "dict", sync_open.json()) + except self.plugin_utils.web.exceptions.ConnectionError: + self.plugin_utils.logger.error("Unreachable: %s" % location) + + def push_sync(self): + cluster = self.plugin_utils.db.get_plugin_value("cluster", "dict") or self.default_cluster() + for location in list(cluster.keys()): + if location != self.plugin_utils.api.base: + sync_url = "%s/api/cluster?method=sync&location=%s" % (location, self.plugin_utils.api.base_quoted) + try: + self.plugin_utils.web.session.get(sync_url) + except self.plugin_utils.web.exceptions.ConnectionError: + self.plugin_utils.logger.error("Unreachable: %s" % location) + + def add(self, location): + cluster = self.plugin_utils.db.get_fhdhr_value("cluster", "dict") or self.default_cluster() + if location not in list(cluster.keys()): + self.plugin_utils.logger.info("Adding %s to cluster." % location) + cluster[location] = {"base_url": location} + + location_info_url = "%s/api/cluster?method=ident" % location + try: + location_info_req = self.plugin_utils.web.session.get(location_info_url) + except self.plugin_utils.web.exceptions.ConnectionError: + self.plugin_utils.logger.error("Unreachable: %s" % location) + del cluster[location] + self.plugin_utils.db.set_plugin_value("cluster", "dict", cluster) + return + location_info = location_info_req.json() + cluster[location]["name"] = location_info["name"] + + cluster_info_url = "%s/api/cluster?method=get" % location + try: + cluster_info_req = self.plugin_utils.web.session.get(cluster_info_url) + except self.plugin_utils.web.exceptions.ConnectionError: + self.plugin_utils.logger.error("Unreachable: %s" % location) + del cluster[location] + self.plugin_utils.db.set_plugin_value("cluster", "dict", cluster) + return + cluster_info = cluster_info_req.json() + for cluster_key in list(cluster_info.keys()): + if cluster_key not in list(cluster.keys()): + cluster[cluster_key] = cluster_info[cluster_key] + + self.plugin_utils.db.set_plugin_value("cluster", "dict", cluster) + self.push_sync() + + def remove(self, location): + cluster = self.plugin_utils.db.get_plugin_value("cluster", "dict") or self.default_cluster() + if location in list(cluster.keys()): + self.plugin_utils.logger.info("Removing %s from cluster." % location) + del cluster[location] + sync_url = "%s/api/cluster?method=leave" % location + try: + self.plugin_utils.web.session.get(sync_url) + except self.plugin_utils.web.exceptions.ConnectionError: + self.plugin_utils.logger.error("Unreachable: %s" % location) + self.push_sync() + self.plugin_utils.db.set_plugin_value("cluster", "dict", cluster) diff --git a/plugins/fHDHR_plugin_interface_cluster/interface/plugin.json b/plugins/fHDHR_plugin_interface_cluster/interface/plugin.json new file mode 100644 index 0000000..72356bf --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/interface/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"interface" +} diff --git a/plugins/fHDHR_plugin_interface_cluster/plugin.json b/plugins/fHDHR_plugin_interface_cluster/plugin.json new file mode 100644 index 0000000..93786d6 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"Cluster", + "version":"v0.6.0-beta", + "type":"interface" +} diff --git a/plugins/fHDHR_plugin_interface_cluster/ssdp/__init__.py b/plugins/fHDHR_plugin_interface_cluster/ssdp/__init__.py new file mode 100644 index 0000000..fad134a --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/ssdp/__init__.py @@ -0,0 +1,70 @@ + +class fHDHR_Detect(): + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + self.fhdhr.db.delete_fhdhr_value("ssdp_detect", "list") + + def set(self, location): + detect_list = self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or [] + if location not in detect_list: + detect_list.append(location) + self.fhdhr.db.set_fhdhr_value("ssdp_detect", "list", detect_list) + + def get(self): + return self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or [] + + +class Plugin_OBJ(): + + def __init__(self, fhdhr, plugin_utils, broadcast_ip, max_age): + self.fhdhr = fhdhr + + self.detect_method = fHDHR_Detect(fhdhr) + + self.broadcast_ip = broadcast_ip + self.device_xml_path = '/cluster/device.xml' + self.schema = "upnp:rootdevice" + + self.max_age = max_age + + @property + def enabled(self): + return self.fhdhr.config.dict["cluster"]["enabled"] + + @property + def notify(self): + + data = '' + data_command = "NOTIFY * HTTP/1.1" + + data_dict = { + "HOST": "%s:%d" % ("239.255.255.250", 1900), + "NTS": "ssdp:alive", + "USN": 'uuid:%s::%s' % (self.fhdhr.config.dict["main"]["uuid"], self.schema), + "LOCATION": "%s%s" % (self.fhdhr.api.base, self.device_xml_path), + "EXT": '', + "SERVER": 'fHDHR/%s UPnP/1.0' % self.fhdhr.version, + "Cache-Control:max-age=": self.max_age, + "NT": self.schema, + } + + data += "%s\r\n" % data_command + for data_key in list(data_dict.keys()): + data += "%s:%s\r\n" % (data_key, data_dict[data_key]) + data += "\r\n" + + return data + + def on_recv(self, headers, cmd, ssdp_handling): + if cmd[0] == 'NOTIFY' and cmd[1] == '*': + try: + if headers["server"].startswith("fHDHR"): + if headers["location"].endswith("/device.xml"): + savelocation = headers["location"].split("/device.xml")[0] + if savelocation.endswith("/cluster"): + savelocation = savelocation.replace("/cluster", '') + if savelocation != self.fhdhr.api.base: + self.detect_method.set(savelocation) + except KeyError: + return diff --git a/plugins/fHDHR_plugin_interface_cluster/ssdp/plugin.json b/plugins/fHDHR_plugin_interface_cluster/ssdp/plugin.json new file mode 100644 index 0000000..e71555c --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/ssdp/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"ssdp" +} diff --git a/plugins/fHDHR_plugin_interface_cluster/web/__init__.py b/plugins/fHDHR_plugin_interface_cluster/web/__init__.py new file mode 100644 index 0000000..ed6018e --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/web/__init__.py @@ -0,0 +1,14 @@ +from .cluster_api import Cluster_API +from .cluster_html import Cluster_HTML +from .cluster_device_xml import Cluster_Device_XML + + +class Plugin_OBJ(): + + def __init__(self, fhdhr, plugin_utils): + self.fhdhr = fhdhr + self.plugin_utils = plugin_utils + + self.cluster_api = Cluster_API(fhdhr, plugin_utils) + self.cluster_html = Cluster_HTML(fhdhr, plugin_utils) + self.cluster_device_xml = Cluster_Device_XML(fhdhr, plugin_utils) diff --git a/fHDHR_web/templates/cluster.html b/plugins/fHDHR_plugin_interface_cluster/web/cluster.html similarity index 100% rename from fHDHR_web/templates/cluster.html rename to plugins/fHDHR_plugin_interface_cluster/web/cluster.html diff --git a/fHDHR_web/api/cluster.py b/plugins/fHDHR_plugin_interface_cluster/web/cluster_api.py similarity index 56% rename from fHDHR_web/api/cluster.py rename to plugins/fHDHR_plugin_interface_cluster/web/cluster_api.py index ce78710..6bc93d4 100644 --- a/fHDHR_web/api/cluster.py +++ b/plugins/fHDHR_plugin_interface_cluster/web/cluster_api.py @@ -3,7 +3,7 @@ import urllib.parse import json -class Cluster(): +class Cluster_API(): endpoints = ["/api/cluster"] endpoint_name = "api_cluster" endpoint_methods = ["GET", "POST"] @@ -11,8 +11,9 @@ class Cluster(): "method": "get" } - def __init__(self, fhdhr): + def __init__(self, fhdhr, plugin_utils): self.fhdhr = fhdhr + self.plugin_utils = plugin_utils def __call__(self, *args): return self.get(*args) @@ -24,7 +25,18 @@ class Cluster(): redirect_url = request.args.get('redirect', default=None, type=str) if method == "get": - jsoncluster = self.fhdhr.device.cluster.cluster() + jsoncluster = self.fhdhr.device.interfaces[self.plugin_utils.namespace].cluster() + cluster_json = json.dumps(jsoncluster, indent=4) + + return Response(status=200, + response=cluster_json, + mimetype='application/json') + + elif method == "ident": + jsoncluster = { + "name": self.fhdhr.config.dict["cluster"]["friendlyname"] or "%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], self.fhdhr.origins.valid_origins[0]) + } + cluster_json = json.dumps(jsoncluster, indent=4) return Response(status=200, @@ -35,17 +47,17 @@ class Cluster(): self.fhdhr.device.ssdp.m_search() elif method == 'add': - self.fhdhr.device.cluster.add(location) + self.fhdhr.device.interfaces[self.plugin_utils.namespace].add(location) elif method == 'del': - self.fhdhr.device.cluster.remove(location) + self.fhdhr.device.interfaces[self.plugin_utils.namespace].remove(location) elif method == 'sync': - self.fhdhr.device.cluster.sync(location) + self.fhdhr.device.interfaces[self.plugin_utils.namespace].sync(location) elif method == 'leave': - self.fhdhr.device.cluster.leave() + self.fhdhr.device.interfaces[self.plugin_utils.namespace].leave() elif method == 'disconnect': - self.fhdhr.device.cluster.disconnect() + self.fhdhr.device.interfaces[self.plugin_utils.namespace].disconnect() elif method == 'alive': self.fhdhr.device.ssdp.do_alive(forcealive=True) diff --git a/plugins/fHDHR_plugin_interface_cluster/web/cluster_device_xml.py b/plugins/fHDHR_plugin_interface_cluster/web/cluster_device_xml.py new file mode 100644 index 0000000..574de6e --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/web/cluster_device_xml.py @@ -0,0 +1,46 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class Cluster_Device_XML(): + endpoints = ["/cluster/device.xml"] + endpoint_name = "cluster_device_xml" + + def __init__(self, fhdhr, plugin_utils): + self.fhdhr = fhdhr + self.plugin_utils = plugin_utils + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + """Device.xml referenced from SSDP""" + + base_url = request.url_root[:-1] + + out = xml.etree.ElementTree.Element('root') + out.set('xmlns', "upnp:rootdevice") + + sub_el(out, 'URLBase', "%s" % base_url) + + specVersion_out = sub_el(out, 'specVersion') + sub_el(specVersion_out, 'major', "1") + sub_el(specVersion_out, 'minor', "0") + + device_out = sub_el(out, 'device') + + sub_el(device_out, 'deviceType', "upnp:rootdevice") + sub_el(device_out, 'friendlyName', self.fhdhr.config.dict["fhdhr"]["friendlyname"]) + sub_el(device_out, 'UDN', "uuid:%s" % self.fhdhr.config.dict["main"]["uuid"]) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/plugins/fHDHR_plugin_interface_cluster/web/cluster_html.py b/plugins/fHDHR_plugin_interface_cluster/web/cluster_html.py new file mode 100644 index 0000000..e5e3ab2 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/web/cluster_html.py @@ -0,0 +1,68 @@ +from flask import request, render_template_string, session +import urllib.parse +from simplejson.errors import JSONDecodeError +import pathlib +from io import StringIO + + +class Cluster_HTML(): + endpoints = ["/cluster", "/cluster.html"] + endpoint_name = "page_cluster_html" + endpoint_access_level = 1 + endpoint_category = "tool_pages" + pretty_name = "Cluster/SSDP" + + def __init__(self, fhdhr, plugin_utils): + self.fhdhr = fhdhr + self.plugin_utils = plugin_utils + self.location_dict = { + "name": self.fhdhr.config.dict["cluster"]["friendlyname"] or "%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], self.fhdhr.origins.valid_origins[0]), + "location": self.fhdhr.api.base, + "joined": "N/A", + "url_query": self.fhdhr.api.base_quoted + } + + self.template_file = pathlib.Path(plugin_utils.config.dict["plugin_web_paths"][plugin_utils.namespace]["path"]).joinpath('cluster.html') + self.template = StringIO() + self.template.write(open(self.template_file).read()) + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + + locations_list = [] + + if self.fhdhr.config.dict["fhdhr"]["discovery_address"]: + + locations_list.append(self.location_dict) + + fhdhr_list = self.fhdhr.device.interfaces[self.plugin_utils.namespace].get_list() + for location in list(fhdhr_list.keys()): + + if location in list(self.fhdhr.device.interfaces[self.plugin_utils.namespace].cluster().keys()): + location_name = self.fhdhr.device.interfaces[self.plugin_utils.namespace].cluster()[location]["name"] + else: + try: + location_info_url = "%s/api/cluster?method=ident" % location + location_info_req = self.fhdhr.web.session.get(location_info_url) + location_info = location_info_req.json() + except self.fhdhr.web.exceptions.ConnectionError: + self.fhdhr.logger.error("Unreachable: %s" % location) + location_info = None + except JSONDecodeError: + self.fhdhr.logger.error("Unreachable: %s" % location) + location_info = None + if not location_info: + location_info = {"name": "N/A"} + location_name = location_info["name"] + + location_dict = { + "name": location_name, + "location": location, + "joined": str(fhdhr_list[location]["Joined"]), + "url_query": urllib.parse.quote(location) + } + locations_list.append(location_dict) + + return render_template_string(self.template.getvalue(), request=request, session=session, fhdhr=self.fhdhr, locations_list=locations_list) diff --git a/plugins/fHDHR_plugin_interface_cluster/web/plugin.json b/plugins/fHDHR_plugin_interface_cluster/web/plugin.json new file mode 100644 index 0000000..0a5a6e1 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_cluster/web/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"web" +} diff --git a/plugins/fHDHR_plugin_interface_hdhr/hdhr_conf.json b/plugins/fHDHR_plugin_interface_hdhr/hdhr_conf.json new file mode 100644 index 0000000..3a8dc65 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_hdhr/hdhr_conf.json @@ -0,0 +1,39 @@ +{ + "hdhr":{ + "enabled":{ + "value": true, + "config_file": true, + "config_web": true + }, + "source":{ + "value": "none", + "config_file": true, + "config_web": true + }, + "reporting_manufacturer":{ + "value": "BoronDust", + "config_file": true, + "config_web": true + }, + "reporting_model":{ + "value": "fHDHR", + "config_file": true, + "config_web": true + }, + "reporting_firmware_ver":{ + "value": "20201001", + "config_file": true, + "config_web": true + }, + "reporting_tuner_type":{ + "value": "Antenna", + "config_file": true, + "config_web": true + }, + "reporting_firmware_name":{ + "value": "fHDHR", + "config_file": true, + "config_web": true + } + } +} diff --git a/plugins/fHDHR_plugin_interface_hdhr/plugin.json b/plugins/fHDHR_plugin_interface_hdhr/plugin.json new file mode 100644 index 0000000..438fba7 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_hdhr/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"HDHR", + "version":"v0.6.0-beta", + "type":"interface" +} diff --git a/fHDHR/device/ssdp/hdhr_ssdp.py b/plugins/fHDHR_plugin_interface_hdhr/ssdp/__init__.py similarity index 73% rename from fHDHR/device/ssdp/hdhr_ssdp.py rename to plugins/fHDHR_plugin_interface_hdhr/ssdp/__init__.py index 37ccd4b..b2562ce 100644 --- a/fHDHR/device/ssdp/hdhr_ssdp.py +++ b/plugins/fHDHR_plugin_interface_hdhr/ssdp/__init__.py @@ -1,30 +1,31 @@ -class HDHR_SSDP(): +class Plugin_OBJ(): - def __init__(self, fhdhr, broadcast_ip, max_age): + def __init__(self, fhdhr, plugin_utils, broadcast_ip, max_age): self.fhdhr = fhdhr - self.ssdp_content = None - self.broadcast_ip = broadcast_ip self.device_xml_path = '/hdhr/device.xml' self.cable_schema = "urn:schemas-opencable-com:service:Security:1" self.ota_schema = "urn:schemas-upnp-org:device:MediaServer:1" - if self.fhdhr.config.dict["fhdhr"]["reporting_tuner_type"].lower() == "antenna": + if self.fhdhr.config.dict["hdhr"]["reporting_tuner_type"].lower() == "antenna": self.schema = self.ota_schema - elif self.fhdhr.config.dict["fhdhr"]["reporting_tuner_type"].lower() == "cable": + elif self.fhdhr.config.dict["hdhr"]["reporting_tuner_type"].lower() == "cable": self.schema = self.cable_schema else: self.schema = self.ota_schema self.max_age = max_age - def get(self): - if self.ssdp_content: - return self.ssdp_content.encode("utf-8") + @property + def enabled(self): + return self.fhdhr.config.dict["hdhr"]["enabled"] + + @property + def notify(self): data = '' data_command = "NOTIFY * HTTP/1.1" @@ -45,5 +46,4 @@ class HDHR_SSDP(): data += "%s:%s\r\n" % (data_key, data_dict[data_key]) data += "\r\n" - self.ssdp_content = data - return data.encode("utf-8") + return data diff --git a/plugins/fHDHR_plugin_interface_hdhr/ssdp/plugin.json b/plugins/fHDHR_plugin_interface_hdhr/ssdp/plugin.json new file mode 100644 index 0000000..e71555c --- /dev/null +++ b/plugins/fHDHR_plugin_interface_hdhr/ssdp/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"ssdp" +} diff --git a/fHDHR_web/hdhr/__init__.py b/plugins/fHDHR_plugin_interface_hdhr/web/__init__.py similarity index 86% rename from fHDHR_web/hdhr/__init__.py rename to plugins/fHDHR_plugin_interface_hdhr/web/__init__.py index a545826..c3987e9 100644 --- a/fHDHR_web/hdhr/__init__.py +++ b/plugins/fHDHR_plugin_interface_hdhr/web/__init__.py @@ -12,10 +12,11 @@ from .auto import Auto from .tuner import Tuner -class fHDHR_HDHR(): +class Plugin_OBJ(): - def __init__(self, fhdhr): + def __init__(self, fhdhr, plugin_utils): self.fhdhr = fhdhr + self.plugin_utils = plugin_utils self.lineup_post = Lineup_Post(fhdhr) diff --git a/fHDHR_web/hdhr/auto.py b/plugins/fHDHR_plugin_interface_hdhr/web/auto.py similarity index 60% rename from fHDHR_web/hdhr/auto.py rename to plugins/fHDHR_plugin_interface_hdhr/web/auto.py index 196d31e..4aad57a 100644 --- a/fHDHR_web/hdhr/auto.py +++ b/plugins/fHDHR_plugin_interface_hdhr/web/auto.py @@ -3,34 +3,43 @@ import urllib.parse class Auto(): - endpoints = ['/auto/', '/hdhr/auto/'] + endpoints = ['/hdhr/auto/'] endpoint_name = "hdhr_auto" def __init__(self, fhdhr): self.fhdhr = fhdhr + @property + def source(self): + return self.fhdhr.config.dict["hdhr"]["source"] or self.fhdhr.origins.valid_origins[0] + def __call__(self, channel, *args): return self.get(channel, *args) def get(self, channel, *args): - method = request.args.get('method', default=self.fhdhr.config.dict["streaming"]["method"], type=str) + origin = self.source - redirect_url = "/api/tuners?method=%s" % (method) + redirect_url = "/api/tuners?method=%s" % (self.fhdhr.config.dict["streaming"]["method"]) if channel.startswith("v"): channel_number = channel.replace('v', '') elif channel.startswith("ch"): channel_freq = channel.replace('ch', '').split("-")[0] - subchannel = 0 + subchannel = None if "-" in channel: subchannel = channel.replace('ch', '').split("-")[1] - self.fhdhr.logger.error("Not Implemented %s-%s" % (str(channel_freq), str(subchannel))) - abort(501, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel))) + if subchannel: + self.fhdhr.logger.error("Not Implemented %s-%s" % (channel_freq, subchannel)) + abort(501, "Not Implemented %s-%s" % (channel_freq, subchannel)) + else: + self.fhdhr.logger.error("Not Implemented %s" % (channel_freq, subchannel)) + abort(501, "Not Implemented %s" % channel_freq) else: channel_number = channel redirect_url += "&channel=%s" % str(channel_number) + redirect_url += "&origin=%s" % str(origin) duration = request.args.get('duration', default=0, type=int) if duration: diff --git a/fHDHR_web/hdhr/device_xml.py b/plugins/fHDHR_plugin_interface_hdhr/web/device_xml.py similarity index 62% rename from fHDHR_web/hdhr/device_xml.py rename to plugins/fHDHR_plugin_interface_hdhr/web/device_xml.py index 1da639a..772815b 100644 --- a/fHDHR_web/hdhr/device_xml.py +++ b/plugins/fHDHR_plugin_interface_hdhr/web/device_xml.py @@ -6,12 +6,16 @@ from fHDHR.tools import sub_el class HDHR_Device_XML(): - endpoints = ["/hdhr/device.xml"] + endpoints = ["/hdhr", "/hdhr/", "/hdhr/device.xml"] endpoint_name = "hdhr_device_xml" def __init__(self, fhdhr): self.fhdhr = fhdhr + @property + def source(self): + return self.fhdhr.config.dict["hdhr"]["source"] or self.fhdhr.origins.valid_origins[0] + def __call__(self, *args): return self.get(*args) @@ -20,10 +24,14 @@ class HDHR_Device_XML(): base_url = request.url_root[:-1] + origin = self.source + origin_plugin_name = self.fhdhr.origins.origins_dict[origin].plugin_utils.plugin_name + origin_plugin_version = self.fhdhr.origins.origins_dict[origin].plugin_utils.plugin_manifest["version"] + out = xml.etree.ElementTree.Element('root') out.set('xmlns', "urn:schemas-upnp-org:device-1-0") - sub_el(out, 'URLBase', "%s" % base_url) + sub_el(out, 'URLBase', "%s/hdhr" % base_url) specVersion_out = sub_el(out, 'specVersion') sub_el(specVersion_out, 'major', "1") @@ -33,15 +41,15 @@ class HDHR_Device_XML(): sub_el(device_out, 'deviceType', "urn:schemas-upnp-org:device:MediaServer:1") - sub_el(device_out, 'friendlyName', self.fhdhr.config.dict["fhdhr"]["friendlyname"]) - sub_el(device_out, 'manufacturer', self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"]) - sub_el(device_out, 'manufacturerURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"]) - sub_el(device_out, 'modelName', self.fhdhr.config.dict["fhdhr"]["reporting_model"]) - sub_el(device_out, 'modelNumber', self.fhdhr.config.internal["versions"]["fHDHR"]) + sub_el(device_out, 'friendlyName', "%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin)) + sub_el(device_out, 'manufacturer', self.fhdhr.config.dict["hdhr"]["reporting_manufacturer"]) + sub_el(device_out, 'manufacturerURL', "https://github.com/fHDHR/%s" % origin_plugin_name) + sub_el(device_out, 'modelName', self.fhdhr.config.dict["hdhr"]["reporting_model"]) + sub_el(device_out, 'modelNumber', origin_plugin_version) sub_el(device_out, 'serialNumber') - sub_el(device_out, 'UDN', "uuid:%s" % self.fhdhr.config.dict["main"]["uuid"]) + sub_el(device_out, 'UDN', "uuid:%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin)) fakefile = BytesIO() fakefile.write(b'\n') diff --git a/fHDHR_web/hdhr/discover_json.py b/plugins/fHDHR_plugin_interface_hdhr/web/discover_json.py similarity index 53% rename from fHDHR_web/hdhr/discover_json.py rename to plugins/fHDHR_plugin_interface_hdhr/web/discover_json.py index a31326a..f77020a 100644 --- a/fHDHR_web/hdhr/discover_json.py +++ b/plugins/fHDHR_plugin_interface_hdhr/web/discover_json.py @@ -3,12 +3,16 @@ import json class Discover_JSON(): - endpoints = ["/discover.json", "/hdhr/discover.json"] + endpoints = ["/hdhr/discover.json"] endpoint_name = "hdhr_discover_json" def __init__(self, fhdhr): self.fhdhr = fhdhr + @property + def source(self): + return self.fhdhr.config.dict["hdhr"]["source"] or self.fhdhr.origins.valid_origins[0] + def __call__(self, *args): return self.get(*args) @@ -16,17 +20,19 @@ class Discover_JSON(): base_url = request.url_root[:-1] + origin = self.source + jsondiscover = { - "FriendlyName": self.fhdhr.config.dict["fhdhr"]["friendlyname"], - "Manufacturer": self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"], - "ModelNumber": self.fhdhr.config.dict["fhdhr"]["reporting_model"], - "FirmwareName": self.fhdhr.config.dict["fhdhr"]["reporting_firmware_name"], - "TunerCount": self.fhdhr.config.dict["fhdhr"]["tuner_count"], - "FirmwareVersion": self.fhdhr.config.dict["fhdhr"]["reporting_firmware_ver"], - "DeviceID": self.fhdhr.config.dict["main"]["uuid"], + "FriendlyName": "%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin), + "Manufacturer": self.fhdhr.config.dict["hdhr"]["reporting_manufacturer"], + "ModelNumber": self.fhdhr.config.dict["hdhr"]["reporting_model"], + "FirmwareName": self.fhdhr.config.dict["hdhr"]["reporting_firmware_name"], + "TunerCount": self.fhdhr.origins.origins_dict[origin].tuners, + "FirmwareVersion": self.fhdhr.config.dict["hdhr"]["reporting_firmware_ver"], + "DeviceID": "%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin), "DeviceAuth": self.fhdhr.config.dict["fhdhr"]["device_auth"], - "BaseURL": "%s" % base_url, - "LineupURL": "%s/lineup.json" % base_url + "BaseURL": "%s/hdhr" % base_url, + "LineupURL": "%s/hdhr/lineup.json" % base_url } discover_json = json.dumps(jsondiscover, indent=4) diff --git a/plugins/fHDHR_plugin_interface_hdhr/web/lineup_json.py b/plugins/fHDHR_plugin_interface_hdhr/web/lineup_json.py new file mode 100644 index 0000000..500a697 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_hdhr/web/lineup_json.py @@ -0,0 +1,61 @@ +from flask import Response, request +import json + +from fHDHR.tools import channel_sort + + +class Lineup_JSON(): + endpoints = ["/hdhr/lineup.json"] + endpoint_name = "hdhr_lineup_json" + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + @property + def source(self): + return self.fhdhr.config.dict["hdhr"]["source"] or self.fhdhr.origins.valid_origins[0] + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + + base_url = request.url_root[:-1] + + show = request.args.get('show', default="all", type=str) + + origin = self.source + + channelslist = {} + sorted_chan_guide = [] + for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin) + if channel_obj.enabled: + channelslist[channel_obj.number] = channel_obj + + # Sort the channels + sorted_channel_list = channel_sort(list(channelslist.keys())) + for channel in sorted_channel_list: + + channel_obj = channelslist[channel] + lineup_dict = { + 'GuideNumber': channel_obj.number, + 'GuideName': channel_obj.dict['name'], + 'Tags': ",".join(channel_obj.dict['tags']), + 'URL': '/hdhr/auto/v' % channel_obj.number, + 'HD': channel_obj.dict["HD"], + "Favorite": channel_obj.dict["favorite"], + } + lineup_dict["URL"] = "%s%s" % (base_url, lineup_dict["URL"]) + if show == "found" and channel_obj.enabled: + lineup_dict["Enabled"] = 1 + elif show == "found" and not channel_obj.enabled: + lineup_dict["Enabled"] = 0 + + sorted_chan_guide.append(lineup_dict) + + lineup_json = json.dumps(sorted_chan_guide, indent=4) + + return Response(status=200, + response=lineup_json, + mimetype='application/json') diff --git a/fHDHR_web/hdhr/lineup_post.py b/plugins/fHDHR_plugin_interface_hdhr/web/lineup_post.py similarity index 78% rename from fHDHR_web/hdhr/lineup_post.py rename to plugins/fHDHR_plugin_interface_hdhr/web/lineup_post.py index 6a2e8fb..a5b5ac4 100644 --- a/fHDHR_web/hdhr/lineup_post.py +++ b/plugins/fHDHR_plugin_interface_hdhr/web/lineup_post.py @@ -4,29 +4,35 @@ from fHDHR.exceptions import TunerError class Lineup_Post(): - endpoints = ["/lineup.post", "/hdhr/lineup.post"] + endpoints = ["/hdhr/lineup.post"] endpoint_name = "hdhr_lineup_post" endpoint_methods = ["POST"] def __init__(self, fhdhr): self.fhdhr = fhdhr + @property + def source(self): + return self.fhdhr.config.dict["hdhr"]["source"] or self.fhdhr.origins.valid_origins[0] + def __call__(self, *args): return self.get(*args) def get(self, *args): + origin = self.source + if 'scan' in list(request.args.keys()): if request.args['scan'] == 'start': try: - self.fhdhr.device.tuners.tuner_scan() + self.fhdhr.device.tuners.tuner_scan(origin) except TunerError as e: self.fhdhr.logger.info(str(e)) return Response(status=200, mimetype='text/html') elif request.args['scan'] == 'abort': - self.fhdhr.device.tuners.stop_tuner_scan() + self.fhdhr.device.tuners.stop_tuner_scan(origin) return Response(status=200, mimetype='text/html') else: @@ -39,18 +45,18 @@ class Lineup_Post(): channel_method = request.args['favorite'][0] channel_number = request.args['favorite'][1:] - if str(channel_number) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]: + if str(channel_number) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]: response = Response("Not Found", status=404) response.headers["X-fHDHR-Error"] = "801 - Unknown Channel" self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) abort(response) if channel_method == "+": - self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method) + self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method, origin) elif channel_method == "-": - self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method) + self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method, origin) elif channel_method == "x": - self.fhdhr.device.channels.set_channel_enablement("number", channel_number, "toggle") + self.fhdhr.device.channels.set_channel_enablement("number", channel_number, "toggle", origin) else: self.fhdhr.logger.warning("Unknown favorite command %s" % request.args['favorite']) diff --git a/fHDHR_web/hdhr/lineup_status_json.py b/plugins/fHDHR_plugin_interface_hdhr/web/lineup_status_json.py similarity index 56% rename from fHDHR_web/hdhr/lineup_status_json.py rename to plugins/fHDHR_plugin_interface_hdhr/web/lineup_status_json.py index 7c0abf5..2189ea5 100644 --- a/fHDHR_web/hdhr/lineup_status_json.py +++ b/plugins/fHDHR_plugin_interface_hdhr/web/lineup_status_json.py @@ -3,27 +3,35 @@ import json class Lineup_Status_JSON(): - endpoints = ["/lineup_status.json", "/hdhr/lineup_status.json"] + endpoints = ["/hdhr/lineup_status.json"] endpoint_name = "hdhr_lineup_status_json" def __init__(self, fhdhr): self.fhdhr = fhdhr + @property + def source(self): + return self.fhdhr.config.dict["hdhr"]["source"] or self.fhdhr.origins.valid_origins[0] + def __call__(self, *args): return self.get(*args) def get(self, *args): - tuner_status = self.fhdhr.device.tuners.status() + origin = self.source + + tuner_status = self.fhdhr.device.tuners.status(origin) tuners_scanning = 0 for tuner_number in list(tuner_status.keys()): if tuner_status[tuner_number]["status"] == "Scanning": tuners_scanning += 1 + channel_count = len(list(self.fhdhr.device.channels.list[origin].keys())) + if tuners_scanning: - jsonlineup = self.scan_in_progress() - elif not len(self.fhdhr.device.channels.list): - jsonlineup = self.scan_in_progress() + jsonlineup = self.scan_in_progress(origin) + elif not channel_count: + jsonlineup = self.scan_in_progress(origin) else: jsonlineup = self.not_scanning() lineup_json = json.dumps(jsonlineup, indent=4) @@ -32,11 +40,14 @@ class Lineup_Status_JSON(): response=lineup_json, mimetype='application/json') - def scan_in_progress(self): + def scan_in_progress(self, origin): + + channel_count = len(list(self.fhdhr.device.channels.list[origin].keys())) + jsonlineup = { "ScanInProgress": "true", "Progress": 99, - "Found": len(self.fhdhr.device.channels.list) + "Found": channel_count } return jsonlineup @@ -44,7 +55,7 @@ class Lineup_Status_JSON(): jsonlineup = { "ScanInProgress": "false", "ScanPossible": "true", - "Source": self.fhdhr.config.dict["fhdhr"]["reporting_tuner_type"], - "SourceList": [self.fhdhr.config.dict["fhdhr"]["reporting_tuner_type"]], + "Source": self.fhdhr.config.dict["hdhr"]["reporting_tuner_type"], + "SourceList": [self.fhdhr.config.dict["hdhr"]["reporting_tuner_type"]], } return jsonlineup diff --git a/fHDHR_web/hdhr/lineup_xml.py b/plugins/fHDHR_plugin_interface_hdhr/web/lineup_xml.py similarity index 51% rename from fHDHR_web/hdhr/lineup_xml.py rename to plugins/fHDHR_plugin_interface_hdhr/web/lineup_xml.py index edaf48a..dfb694c 100644 --- a/fHDHR_web/hdhr/lineup_xml.py +++ b/plugins/fHDHR_plugin_interface_hdhr/web/lineup_xml.py @@ -6,12 +6,16 @@ from fHDHR.tools import channel_sort, sub_el class Lineup_XML(): - endpoints = ["/lineup.xml", "/hdhr/lineup.xml"] + endpoints = ["/hdhr/lineup.xml"] endpoint_name = "hdhr_lineup_xml" def __init__(self, fhdhr): self.fhdhr = fhdhr + @property + def source(self): + return self.fhdhr.config.dict["hdhr"]["source"] or self.fhdhr.origins.valid_origins[0] + def __call__(self, *args): return self.get(*args) @@ -21,24 +25,35 @@ class Lineup_XML(): show = request.args.get('show', default="all", type=str) - channelslist = {} - for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels()]: - channel_obj = self.fhdhr.device.channels.list[fhdhr_id] - if channel_obj.enabled or show == "found": - lineup_dict = channel_obj.lineup_dict - lineup_dict["URL"] = "%s%s" % (base_url, lineup_dict["URL"]) - if show == "found" and channel_obj.enabled: - lineup_dict["Enabled"] = 1 - elif show == "found" and not channel_obj.enabled: - lineup_dict["Enabled"] = 0 + origin = self.source - channelslist[channel_obj.number] = lineup_dict + channelslist = {} + sorted_chan_guide = [] + for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin) + if channel_obj.enabled: + channelslist[channel_obj.number] = channel_obj # Sort the channels sorted_channel_list = channel_sort(list(channelslist.keys())) - sorted_chan_guide = [] for channel in sorted_channel_list: - sorted_chan_guide.append(channelslist[channel]) + + channel_obj = channelslist[channel] + lineup_dict = { + 'GuideNumber': channel_obj.number, + 'GuideName': channel_obj.dict['name'], + 'Tags': ",".join(channel_obj.dict['tags']), + 'URL': '/hdhr/auto/v' % channel_obj.number, + 'HD': channel_obj.dict["HD"], + "Favorite": channel_obj.dict["favorite"], + } + lineup_dict["URL"] = "%s%s" % (base_url, lineup_dict["URL"]) + if show == "found" and channel_obj.enabled: + lineup_dict["Enabled"] = 1 + elif show == "found" and not channel_obj.enabled: + lineup_dict["Enabled"] = 0 + + sorted_chan_guide.append(lineup_dict) out = xml.etree.ElementTree.Element('Lineup') for lineup_dict in sorted_chan_guide: diff --git a/plugins/fHDHR_plugin_interface_hdhr/web/plugin.json b/plugins/fHDHR_plugin_interface_hdhr/web/plugin.json new file mode 100644 index 0000000..0a5a6e1 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_hdhr/web/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"web" +} diff --git a/fHDHR_web/hdhr/tuner.py b/plugins/fHDHR_plugin_interface_hdhr/web/tuner.py similarity index 59% rename from fHDHR_web/hdhr/tuner.py rename to plugins/fHDHR_plugin_interface_hdhr/web/tuner.py index 6a6245a..e77fd9b 100644 --- a/fHDHR_web/hdhr/tuner.py +++ b/plugins/fHDHR_plugin_interface_hdhr/web/tuner.py @@ -3,36 +3,45 @@ import urllib.parse class Tuner(): - endpoints = ['/tuner/', '/hdhr/tuner/'] + endpoints = ['/hdhr/tuner/'] endpoint_name = "hdhr_tuner" def __init__(self, fhdhr): self.fhdhr = fhdhr + @property + def source(self): + return self.fhdhr.config.dict["hdhr"]["source"] or self.fhdhr.origins.valid_origins[0] + def __call__(self, tuner_number, channel, *args): return self.get(tuner_number, channel, *args) def get(self, tuner_number, channel, *args): - method = request.args.get('method', default=self.fhdhr.config.dict["streaming"]["method"], type=str) + origin = self.source - redirect_url = "/api/tuners?method=%s" % (method) + redirect_url = "/api/tuners?method=%s" % (self.fhdhr.config.dict["streaming"]["method"]) - redirect_url += "&tuner=%s" % str(tuner_number) + redirect_url += "&tuner=%s" % (tuner_number) if channel.startswith("v"): channel_number = channel.replace('v', '') elif channel.startswith("ch"): channel_freq = channel.replace('ch', '').split("-")[0] - subchannel = 0 + subchannel = None if "-" in channel: subchannel = channel.replace('ch', '').split("-")[1] - self.fhdhr.logger.error("Not Implemented %s-%s" % (str(channel_freq), str(subchannel))) - abort(501, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel))) + if subchannel: + self.fhdhr.logger.error("Not Implemented %s-%s" % (channel_freq, subchannel)) + abort(501, "Not Implemented %s-%s" % (channel_freq, subchannel)) + else: + self.fhdhr.logger.error("Not Implemented %s" % (channel_freq, subchannel)) + abort(501, "Not Implemented %s" % channel_freq) else: channel_number = channel redirect_url += "&channel=%s" % str(channel_number) + redirect_url += "&origin=%s" % str(origin) duration = request.args.get('duration', default=0, type=int) if duration: diff --git a/plugins/fHDHR_plugin_interface_rmg/plugin.json b/plugins/fHDHR_plugin_interface_rmg/plugin.json new file mode 100644 index 0000000..8abcf71 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"RMG", + "version":"v0.6.0-beta", + "type":"interface" +} diff --git a/plugins/fHDHR_plugin_interface_rmg/rmg_conf.json b/plugins/fHDHR_plugin_interface_rmg/rmg_conf.json new file mode 100644 index 0000000..666aff9 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/rmg_conf.json @@ -0,0 +1,24 @@ +{ + "rmg":{ + "enabled":{ + "value": true, + "config_file": true, + "config_web": true + }, + "reporting_manufacturer":{ + "value": "BoronDust", + "config_file": true, + "config_web": true + }, + "reporting_model":{ + "value": "fHDHR", + "config_file": true, + "config_web": true + }, + "reporting_tuner_type":{ + "value": "Antenna", + "config_file": true, + "config_web": true + } + } +} diff --git a/plugins/fHDHR_plugin_interface_rmg/ssdp/__init__.py b/plugins/fHDHR_plugin_interface_rmg/ssdp/__init__.py new file mode 100644 index 0000000..7c2027f --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/ssdp/__init__.py @@ -0,0 +1,49 @@ + + +class Plugin_OBJ(): + + def __init__(self, fhdhr, plugin_utils, broadcast_ip, max_age): + self.fhdhr = fhdhr + + self.broadcast_ip = broadcast_ip + + self.schema = "urn:plex-tv:service:MediaGrabber:1" + + self.max_age = max_age + + def create_ssdp_content(self, origin): + data = '' + data_command = "NOTIFY * HTTP/1.1" + + device_xml_path = "/rmg/%s%s/device.xml" % (self.fhdhr.config.dict["main"]["uuid"], origin) + + data_dict = { + "HOST": "%s:%s" % ("239.255.255.250", 1900), + "NT": self.schema, + "NTS": "ssdp:alive", + "USN": 'uuid:%s%s::%s' % (self.fhdhr.config.dict["main"]["uuid"], origin, self.schema), + "SERVER": 'fHDHR/%s UPnP/1.0' % self.fhdhr.version, + "LOCATION": "%s%s" % (self.fhdhr.api.base, device_xml_path), + "AL": "%s%s" % (self.fhdhr.api.base, device_xml_path), + "Cache-Control:max-age=": self.max_age + } + + data += "%s\r\n" % data_command + for data_key in list(data_dict.keys()): + data += "%s:%s\r\n" % (data_key, data_dict[data_key]) + data += "\r\n" + + self.ssdp_content = data + return data + + @property + def enabled(self): + return self.fhdhr.config.dict["rmg"]["enabled"] + + @property + def notify(self): + ssdp_content = [] + for origin in list(self.ssdp_content.keys()): + data = self.create_ssdp_content(origin) + ssdp_content.append(data) + return ssdp_content diff --git a/plugins/fHDHR_plugin_interface_rmg/ssdp/plugin.json b/plugins/fHDHR_plugin_interface_rmg/ssdp/plugin.json new file mode 100644 index 0000000..e71555c --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/ssdp/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"ssdp" +} diff --git a/fHDHR_web/rmg/__init__.py b/plugins/fHDHR_plugin_interface_rmg/web/__init__.py similarity index 92% rename from fHDHR_web/rmg/__init__.py rename to plugins/fHDHR_plugin_interface_rmg/web/__init__.py index bccf4e0..8bbe2f2 100644 --- a/fHDHR_web/rmg/__init__.py +++ b/plugins/fHDHR_plugin_interface_rmg/web/__init__.py @@ -1,4 +1,5 @@ + from .rmg_ident_xml import RMG_Ident_XML from .device_xml import RMG_Device_XML from .devices_discover import RMG_Devices_Discover @@ -12,10 +13,11 @@ from .devices_devicekey_prefs import RMG_Devices_DeviceKey_Prefs from .devices_devicekey_media import RMG_Devices_DeviceKey_Media -class fHDHR_RMG(): +class Plugin_OBJ(): - def __init__(self, fhdhr): + def __init__(self, fhdhr, plugin_utils): self.fhdhr = fhdhr + self.plugin_utils = plugin_utils self.rmg_ident_xml = RMG_Ident_XML(fhdhr) self.device_xml = RMG_Device_XML(fhdhr) diff --git a/plugins/fHDHR_plugin_interface_rmg/web/device_xml.py b/plugins/fHDHR_plugin_interface_rmg/web/device_xml.py new file mode 100644 index 0000000..59d83a4 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/web/device_xml.py @@ -0,0 +1,63 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Device_XML(): + endpoints = ["/rmg//device.xml"] + endpoint_name = "rmg_device_xml" + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, devicekey, *args): + """Device.xml referenced from SSDP""" + + base_url = request.url_root[:-1] + + out = xml.etree.ElementTree.Element('root') + out.set('xmlns', "urn:schemas-upnp-org:device-1-0") + + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] + origin_plugin_name = self.fhdhr.origins.origins_dict[origin].plugin_utils.plugin_name + origin_plugin_version = self.fhdhr.origins.origins_dict[origin].plugin_utils.plugin_manifest["version"] + + specVersion_out = sub_el(out, 'specVersion') + sub_el(specVersion_out, 'major', "1") + sub_el(specVersion_out, 'minor', "0") + + device_out = sub_el(out, 'device') + + sub_el(device_out, 'deviceType', "urn:plex-tv:device:Media:1") + + sub_el(device_out, 'friendlyName', "%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin)) + sub_el(device_out, 'manufacturer', self.fhdhr.config.dict["rmg"]["reporting_manufacturer"]) + sub_el(device_out, 'manufacturerURL', "https://github.com/fHDHR/%s" % origin_plugin_name) + sub_el(device_out, 'modelName', self.fhdhr.config.dict["rmg"]["reporting_model"]) + sub_el(device_out, 'modelNumber', origin_plugin_version) + + sub_el(device_out, 'modelDescription', "%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin)) + sub_el(device_out, 'modelURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"]) + + serviceList_out = sub_el(device_out, 'serviceList') + service_out = sub_el(serviceList_out, 'service') + sub_el(out, 'URLBase', "%s/rmg/%s%s" % (base_url, self.fhdhr.config.dict["main"]["uuid"], origin)) + sub_el(service_out, 'serviceType', "urn:plex-tv:service:MediaGrabber:1") + sub_el(service_out, 'serviceId', "urn:plex-tv:serviceId:MediaGrabber") + + sub_el(device_out, 'UDN', "uuid:%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin)) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR_web/rmg/devices_devicekey.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey.py similarity index 70% rename from fHDHR_web/rmg/devices_devicekey.py rename to plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey.py index 697b6bc..eb9979e 100644 --- a/fHDHR_web/rmg/devices_devicekey.py +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey.py @@ -6,7 +6,7 @@ from fHDHR.tools import sub_el class RMG_Devices_DeviceKey(): - endpoints = ["/devices/", "/rmg/devices/"] + endpoints = ["/rmg/devices/"] endpoint_name = "rmg_devices_devicekey" endpoint_methods = ["GET"] @@ -22,22 +22,30 @@ class RMG_Devices_DeviceKey(): base_url = request.url_root[:-1] out = xml.etree.ElementTree.Element('MediaContainer') - if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] out.set('size', "1") + + if self.fhdhr.origins.origins_dict[origin].setup_success: + alive_status = "alive" + else: + alive_status = "dead" + device_out = sub_el(out, 'Device', - key=self.fhdhr.config.dict["main"]["uuid"], - make=self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"], - model=self.fhdhr.config.dict["fhdhr"]["reporting_model"], + key="%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin), + make=self.fhdhr.config.dict["rmg"]["reporting_manufacturer"], + model=self.fhdhr.config.dict["rmg"]["reporting_model"], modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"], protocol="livetv", - status="alive", - title=self.fhdhr.config.dict["fhdhr"]["friendlyname"], - tuners=str(self.fhdhr.config.dict["fhdhr"]["tuner_count"]), - uri=base_url, - uuid="device://tv.plex.grabbers.fHDHR/%s" % self.fhdhr.config.dict["main"]["uuid"], + status=alive_status, + title="%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin), + tuners=str(self.fhdhr.origins.origins_dict[origin].tuners), + uri="%s/rmg/%s%s" % (base_url, self.fhdhr.config.dict["main"]["uuid"], origin), + uuid="device://tv.plex.grabbers.fHDHR/%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin), ) - tuner_status = self.fhdhr.device.tuners.status() + tuner_status = self.fhdhr.device.tuners.status(origin) for tuner_number in list(tuner_status.keys()): tuner_dict = tuner_status[tuner_number] @@ -67,7 +75,7 @@ class RMG_Devices_DeviceKey(): index=tuner_number, status="scanning", progress="99", - channelsFound=str(len(self.fhdhr.device.channels.list)), + channelsFound=str(len(list(self.fhdhr.device.channels.list[origin].keys()))), ) # TODO networksScanned diff --git a/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_channels.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_channels.py new file mode 100644 index 0000000..545f1a8 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_channels.py @@ -0,0 +1,59 @@ +from flask import Response +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el, channel_sort + + +class RMG_Devices_DeviceKey_Channels(): + endpoints = ["/rmg/devices//channels"] + endpoint_name = "rmg_devices_devicekey_channels" + endpoint_methods = ["GET"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, *args): + return self.get(devicekey, *args) + + def get(self, devicekey, *args): + """Returns the current channels.""" + + out = xml.etree.ElementTree.Element('MediaContainer') + + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] + out.set('size', str(len(list(self.fhdhr.device.channels.list[origin].keys())))) + + channelslist = {} + for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]: + channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin) + if channel_obj.enabled: + channelslist[channel_obj.number] = channel_obj + + # Sort the channels + sorted_channel_list = channel_sort(list(channelslist.keys())) + for channel in sorted_channel_list: + + channel_obj = channelslist[channel] + + sub_el(out, 'Channel', + drm="0", + channelIdentifier="id://%s" % channel_obj.number, + name=channel_obj.dict["name"], + origin=channel_obj.dict["callsign"], + number=str(channel_obj.number), + type="tv", + # TODO param + signalStrength="100", + signalQuality="100", + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_media.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_media.py new file mode 100644 index 0000000..0d49f50 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_media.py @@ -0,0 +1,43 @@ +from flask import request, redirect, abort, Response +import urllib.parse + + +class RMG_Devices_DeviceKey_Media(): + endpoints = ["/rmg/devices//media/"] + endpoint_name = "rmg_devices_devicekey_media" + endpoint_methods = ["GET"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, channel, *args): + return self.get(devicekey, channel, *args) + + def get(self, devicekey, channel, *args): + + param = request.args.get('method', default=None, type=str) + self.fhdhr.logger.debug("param:%s" % param) + + if not devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + response = Response("Not Found", status=404) + response.headers["X-fHDHR-Error"] = "801 - Unknown devicekey" + self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) + abort(response) + + method = self.fhdhr.config.dict["streaming"]["method"] + redirect_url = "/api/tuners?method=%s" % (method) + + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] + redirect_url += "&origin=%s" % (origin) + + if str(channel).startswith('id://'): + channel = str(channel).replace('id://', '') + elif channel.startswith("triplet://"): + channel_tuple = channel.replace('triplet://', '').split(":") + self.fhdhr.logger.error("Not Implemented %s" % ":".join(channel_tuple)) + abort(501, "Not Implemented %s" % ":".join(channel_tuple)) + redirect_url += "&channel=%s" % (channel) + + redirect_url += "&accessed=%s" % urllib.parse.quote(request.url) + + return redirect(redirect_url) diff --git a/fHDHR_web/rmg/devices_devicekey_networks.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_networks.py similarity index 73% rename from fHDHR_web/rmg/devices_devicekey_networks.py rename to plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_networks.py index c635965..7ef6fe7 100644 --- a/fHDHR_web/rmg/devices_devicekey_networks.py +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_networks.py @@ -6,7 +6,7 @@ from fHDHR.tools import sub_el class RMG_Devices_DeviceKey_Networks(): - endpoints = ["/devices//networks", "/rmg/devices//networks"] + endpoints = ["/rmg/devices//networks"] endpoint_name = "rmg_devices_devicekey_networks" endpoint_methods = ["GET"] @@ -20,12 +20,13 @@ class RMG_Devices_DeviceKey_Networks(): """In some cases, channel scanning is a two-step process, where the first stage consists of scanning for networks (this is called "fast scan").""" out = xml.etree.ElementTree.Element('MediaContainer') - if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] out.set('size', "1") sub_el(out, 'Network', - key="1", - title="fHDHR" + key="%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin), + title="%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin), ) fakefile = BytesIO() diff --git a/fHDHR_web/rmg/devices_devicekey_prefs.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_prefs.py similarity index 72% rename from fHDHR_web/rmg/devices_devicekey_prefs.py rename to plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_prefs.py index 90a1e06..db7bfec 100644 --- a/fHDHR_web/rmg/devices_devicekey_prefs.py +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_prefs.py @@ -2,7 +2,7 @@ from flask import Response class RMG_Devices_DeviceKey_Prefs(): - endpoints = ["/devices//prefs", "/rmg/devices//prefs"] + endpoints = ["/rmg/devices//prefs"] endpoint_name = "rmg_devices_devicekey_prefs" endpoint_methods = ["GET", "PUT"] @@ -15,4 +15,7 @@ class RMG_Devices_DeviceKey_Prefs(): def get(self, devicekey, *args): """Prefs sent back from Plex in Key-Pair format""" + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + return Response(status=200) + return Response(status=200) diff --git a/fHDHR_web/rmg/devices_devicekey_scan.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_scan.py similarity index 81% rename from fHDHR_web/rmg/devices_devicekey_scan.py rename to plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_scan.py index f7d3aea..41d0216 100644 --- a/fHDHR_web/rmg/devices_devicekey_scan.py +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_scan.py @@ -4,7 +4,7 @@ import xml.etree.ElementTree class RMG_Devices_DeviceKey_Scan(): - endpoints = ["/devices//scan", "/rmg/devices//scan"] + endpoints = ["/rmg/devices//scan"] endpoint_name = "rmg_devices_devicekey_scan" endpoint_methods = ["GET", "POST", "DELETE"] @@ -26,9 +26,11 @@ class RMG_Devices_DeviceKey_Scan(): self.fhdhr.logger.debug("Scan Requested network:%s, source:%s, provider:%s" % (network, source, provider)) out = xml.etree.ElementTree.Element('MediaContainer') - if devicekey == self.fhdhr.config.dict["main"]["uuid"]: - tuner_status = self.fhdhr.device.tuners.status() + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] + + tuner_status = self.fhdhr.device.tuners.status(origin) tuner_scanning = 0 for tuner in list(tuner_status.keys()): if tuner_status[tuner]["status"] == "Scanning": @@ -53,9 +55,10 @@ class RMG_Devices_DeviceKey_Scan(): elif request.method in ["DELETE"]: out = xml.etree.ElementTree.Element('MediaContainer') - if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] - self.fhdhr.device.tuners.stop_tuner_scan() + self.fhdhr.device.tuners.stop_tuner_scan(origin) out.set('status', "0") out.set('message', "Scan Aborted") diff --git a/fHDHR_web/rmg/devices_devicekey_scanners.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_scanners.py similarity index 85% rename from fHDHR_web/rmg/devices_devicekey_scanners.py rename to plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_scanners.py index 4a2d4ec..a0042db 100644 --- a/fHDHR_web/rmg/devices_devicekey_scanners.py +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_devicekey_scanners.py @@ -6,7 +6,7 @@ from fHDHR.tools import sub_el class RMG_Devices_DeviceKey_Scanners(): - endpoints = ["/devices//scanners", "/rmg/devices//scanners"] + endpoints = ["/rmg/devices//scanners"] endpoint_name = "rmg_devices_devicekey_scanners" endpoint_methods = ["GET"] @@ -23,7 +23,9 @@ class RMG_Devices_DeviceKey_Scanners(): # 0 (atsc), 1 (cqam), 2 (dvb-s), 3 (iptv), 4 (virtual), 5 (dvb-t), 6 (dvb-c), 7 (isdbt) out = xml.etree.ElementTree.Element('MediaContainer') - if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] + if method == "0": out.set('size', "1") out.set('simultaneousScanners', "1") @@ -35,7 +37,7 @@ class RMG_Devices_DeviceKey_Scanners(): sub_el(scanner_out, 'Setting', id="provider", type="text", - enumValues=self.fhdhr.config.dict["main"]["servicename"] + enumValues=origin ) fakefile = BytesIO() diff --git a/plugins/fHDHR_plugin_interface_rmg/web/devices_discover.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_discover.py new file mode 100644 index 0000000..966e386 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_discover.py @@ -0,0 +1,57 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Devices_Discover(): + endpoints = ["/rmg/devices/discover"] + endpoint_name = "rmg_devices_discover" + endpoint_methods = ["GET", "POST"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + """This endpoint requests the grabber attempt to discover any devices it can, and it returns zero or more devices.""" + + base_url = request.url_root[:-1] + + out = xml.etree.ElementTree.Element('MediaContainer') + out.set('size', str(len(list(self.fhdhr.origins.origins_dict.keys())))) + + for origin in list(self.fhdhr.origins.origins_dict.keys()): + + if self.fhdhr.origins.origins_dict[origin].setup_success: + alive_status = "alive" + else: + alive_status = "dead" + + sub_el(out, 'Device', + key="%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin), + make=self.fhdhr.config.dict["rmg"]["reporting_manufacturer"], + model=self.fhdhr.config.dict["rmg"]["reporting_model"], + modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"], + protocol="livetv", + status=alive_status, + title="%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin), + tuners=str(self.fhdhr.origins.origins_dict[origin].tuners), + uri="%s/rmg/%s%s" % (base_url, self.fhdhr.config.dict["main"]["uuid"], origin), + uuid="device://tv.plex.grabbers.fHDHR/%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin), + thumb="favicon.ico", + interface='network' + # TODO add preferences + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/plugins/fHDHR_plugin_interface_rmg/web/devices_probe.py b/plugins/fHDHR_plugin_interface_rmg/web/devices_probe.py new file mode 100644 index 0000000..b8eac84 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/web/devices_probe.py @@ -0,0 +1,63 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Devices_Probe(): + endpoints = ["/rmg/devices/probe"] + endpoint_name = "rmg_devices_probe" + endpoint_methods = ["GET", "POST"] + endpoint_default_parameters = { + "uri": "" + } + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + """Probes a specific URI for a network device, and returns a device, if it exists at the given URI.""" + + base_url = request.url_root[:-1] + + uri = request.args.get('uri', default=None, type=str) + + out = xml.etree.ElementTree.Element('MediaContainer') + out.set('size', str(len(list(self.fhdhr.origins.origins_dict.keys())))) + + for origin in list(self.fhdhr.origins.origins_dict.keys()): + + if uri == "%s/rmg/%s%s" % (base_url, self.fhdhr.config.dict["main"]["uuid"], origin): + + if self.fhdhr.origins.origins_dict[origin].setup_success: + alive_status = "alive" + else: + alive_status = "dead" + + sub_el(out, 'Device', + key="%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin), + make=self.fhdhr.config.dict["rmg"]["reporting_manufacturer"], + model=self.fhdhr.config.dict["rmg"]["reporting_model"], + modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"], + protocol="livetv", + status=alive_status, + title="%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin), + tuners=str(self.fhdhr.origins.origins_dict[origin].tuners), + uri="%s/rmg/%s%s" % (base_url, self.fhdhr.config.dict["main"]["uuid"], origin), + uuid="device://tv.plex.grabbers.fHDHR/%s%s" % (self.fhdhr.config.dict["main"]["uuid"], origin), + thumb="favicon.ico", + interface='network' + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/plugins/fHDHR_plugin_interface_rmg/web/plugin.json b/plugins/fHDHR_plugin_interface_rmg/web/plugin.json new file mode 100644 index 0000000..0a5a6e1 --- /dev/null +++ b/plugins/fHDHR_plugin_interface_rmg/web/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"web" +} diff --git a/fHDHR_web/rmg/rmg_ident_xml.py b/plugins/fHDHR_plugin_interface_rmg/web/rmg_ident_xml.py similarity index 58% rename from fHDHR_web/rmg/rmg_ident_xml.py rename to plugins/fHDHR_plugin_interface_rmg/web/rmg_ident_xml.py index 8b5749b..c950d16 100644 --- a/fHDHR_web/rmg/rmg_ident_xml.py +++ b/plugins/fHDHR_plugin_interface_rmg/web/rmg_ident_xml.py @@ -6,7 +6,7 @@ from fHDHR.tools import sub_el class RMG_Ident_XML(): - endpoints = ["/rmg", "/rmg/"] + endpoints = ["/rmg//"] endpoint_name = "rmg_ident_xml" def __init__(self, fhdhr): @@ -15,18 +15,22 @@ class RMG_Ident_XML(): def __call__(self, *args): return self.get(*args) - def get(self, *args): + def get(self, devicekey, *args): + """Device.xml referenced from SSDP""" """Provides general information about the media grabber""" base_url = request.url_root[:-1] out = xml.etree.ElementTree.Element('MediaContainer') - sub_el(out, 'MediaGrabber', - identifier="tv.plex.grabbers.fHDHR", - title=str(self.fhdhr.config.dict["fhdhr"]["friendlyname"]), - protocols="livetv", - icon="%s/favicon.ico" % base_url - ) + if devicekey.startswith(self.fhdhr.config.dict["main"]["uuid"]): + origin = devicekey.split(self.fhdhr.config.dict["main"]["uuid"])[-1] + + sub_el(out, 'MediaGrabber', + identifier="tv.plex.grabbers.fHDHR.%s" % origin, + title="%s %s" % (self.fhdhr.config.dict["fhdhr"]["friendlyname"], origin), + protocols="livetv", + icon="%s/favicon.ico" % base_url + ) fakefile = BytesIO() fakefile.write(b'\n') diff --git a/plugins/fHDHR_plugin_origin_NextPVR/__init__.py b/plugins/fHDHR_plugin_origin_NextPVR/__init__.py deleted file mode 100644 index 454c779..0000000 --- a/plugins/fHDHR_plugin_origin_NextPVR/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -PLUGIN_NAME = "NextPVR" -PLUGIN_VERSION = "v0.6.0-beta" -PLUGIN_TYPE = "origin" - - -class NEXTPVR_Setup(): - def __init__(self, config): - pass diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_epg.py b/plugins/fHDHR_plugin_origin_NextPVR/epg/__init__.py similarity index 81% rename from plugins/fHDHR_plugin_origin_NextPVR/origin/origin_epg.py rename to plugins/fHDHR_plugin_origin_NextPVR/epg/__init__.py index 32a16cb..92d5f79 100644 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_epg.py +++ b/plugins/fHDHR_plugin_origin_NextPVR/epg/__init__.py @@ -3,17 +3,21 @@ import xmltodict import fHDHR.tools -class OriginEPG(): +class Plugin_OBJ(): - def __init__(self, fhdhr): - self.fhdhr = fhdhr + def __init__(self, channels, plugin_utils): + self.plugin_utils = plugin_utils + + self.channels = channels + + self.origin = plugin_utils.origin def get_content_thumbnail(self, content_id): item_thumb_url = ("%s%s:%s/service?method=channel.show.artwork&sid=%s&event_id=%s" % - ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", - self.fhdhr.config.dict["origin"]["address"], - str(self.fhdhr.config.dict["origin"]["port"]), - self.fhdhr.config.dict["origin"]["sid"], + ("https://" if self.fhdhr.config.dict["nextpvr"]["ssl"] else "http://", + self.fhdhr.config.dict["nextpvr"]["address"], + str(self.fhdhr.config.dict["nextpvr"]["port"]), + self.fhdhr.config.dict["nextpvr"]["sid"], str(content_id) )) return item_thumb_url @@ -21,20 +25,20 @@ class OriginEPG(): def duration_nextpvr_minutes(self, starttime, endtime): return ((int(endtime) - int(starttime))/1000/60) - def update_epg(self, fhdhr_channels): + def update_epg(self): programguide = {} - for fhdhr_id in list(fhdhr_channels.list.keys()): - chan_obj = fhdhr_channels.list[fhdhr_id] + for fhdhr_id in list(self.channels.list.keys()): + chan_obj = self.channels.list[fhdhr_id] if str(chan_obj.number) not in list(programguide.keys()): programguide[str(chan_obj.number)] = chan_obj.epgdict epg_url = ('%s%s:%s/service?method=channel.listings&channel_id=%s' % - ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", - self.fhdhr.config.dict["origin"]["address"], - str(self.fhdhr.config.dict["origin"]["port"]), + ("https://" if self.fhdhr.config.dict["nextpvr"]["ssl"] else "http://", + self.fhdhr.config.dict["nextpvr"]["address"], + str(self.fhdhr.config.dict["nextpvr"]["port"]), str(chan_obj.dict["origin_id"]), )) epg_req = self.fhdhr.web.session.get(epg_url) diff --git a/plugins/fHDHR_plugin_origin_NextPVR/epg/plugin.json b/plugins/fHDHR_plugin_origin_NextPVR/epg/plugin.json new file mode 100644 index 0000000..2dbec65 --- /dev/null +++ b/plugins/fHDHR_plugin_origin_NextPVR/epg/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"alt_epg" +} diff --git a/plugins/fHDHR_plugin_origin_NextPVR/nextpvr_conf.json b/plugins/fHDHR_plugin_origin_NextPVR/nextpvr_conf.json new file mode 100644 index 0000000..beab095 --- /dev/null +++ b/plugins/fHDHR_plugin_origin_NextPVR/nextpvr_conf.json @@ -0,0 +1,31 @@ +{ + "nextpvr":{ + "address":{ + "value": "localhost", + "config_file": true, + "config_web": true + }, + "port":{ + "value": 8866, + "config_file": true, + "config_web": true + }, + "ssl":{ + "value": false, + "config_file": true, + "config_web": true + }, + "pin":{ + "value": "none", + "config_file": true, + "config_web": true, + "config_web_hidden": true, + "required": true + }, + "sid":{ + "value": "none", + "config_file": true, + "config_web": false + } + } +} diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/__init__.py b/plugins/fHDHR_plugin_origin_NextPVR/origin/__init__.py index 2b5e61e..bf76fe0 100644 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/__init__.py +++ b/plugins/fHDHR_plugin_origin_NextPVR/origin/__init__.py @@ -1,5 +1,111 @@ -# pylama:ignore=W0401,W0611 -from .origin_service import * -from .origin_channels import * -from .origin_epg import * -from .origin_web import * +import xmltodict +import json +import hashlib + +import fHDHR.tools +import fHDHR.exceptions + + +class Plugin_OBJ(): + + def __init__(self, plugin_utils): + self.plugin_utils = plugin_utils + + self.nextpvr_address = ('%s%s:%s' % + ("https://" if self.plugin_utils.config.dict["nextpvr"]["ssl"] else "http://", + self.plugin_utils.config.dict["nextpvr"]["address"], + str(self.plugin_utils.config.dict["nextpvr"]["port"]), + )) + + self.login() + + def login(self): + self.plugin_utils.logger.info("Logging into NextPVR") + self.sid = self.get_sid() + if not self.sid: + raise fHDHR.exceptions.OriginSetupError("NextPVR Login Failed") + else: + self.plugin_utils.logger.info("NextPVR Login Success") + self.plugin_utils.config.write(self.plugin_utils.config.dict["main"]["dictpopname"], 'sid', self.sid) + + def get_sid(self): + if self.plugin_utils.config.dict["nextpvr"]["sid"]: + return self.plugin_utils.config.dict["nextpvr"]["sid"] + + initiate_url = '%s/service?method=session.initiate&ver=1.0&device=fhdhr' % self.nextpvr_address + + initiate_req = self.plugin_utils.web.session.get(initiate_url) + initiate_dict = xmltodict.parse(initiate_req.content) + + sid = initiate_dict['rsp']['sid'] + salt = initiate_dict['rsp']['salt'] + md5PIN = hashlib.md5(str(self.plugin_utils.config.dict["nextpvr"]['pin']).encode('utf-8')).hexdigest() + string = ':%s:%s' % (md5PIN, salt) + clientKey = hashlib.md5(string.encode('utf-8')).hexdigest() + + login_url = ('%s/service?method=session.login&sid=%s&md5=%s' % + (self.nextpvr_address, sid, clientKey)) + login_req = self.plugin_utils.web.session.get(login_url) + login_dict = xmltodict.parse(login_req.content) + + loginsuccess = None + if login_dict['rsp']['@stat'] == "ok": + if login_dict['rsp']['allow_watch'] == "true": + loginsuccess = sid + + return loginsuccess + + def get_channel_thumbnail(self, channel_id): + channel_thumb_url = ("%s%s:%s/service?method=channel.icon&channel_id=%s" % + ("https://" if self.plugin_utils.config.dict["nextpvr"]["ssl"] else "http://", + self.plugin_utils.config.dict["nextpvr"]["address"], + str(self.plugin_utils.config.dict["nextpvr"]["port"]), + str(channel_id) + )) + return channel_thumb_url + + def get_channels(self): + + data_url = ('%s%s:%s/service?method=channel.list&sid=%s' % + ("https://" if self.plugin_utils.config.dict["nextpvr"]["ssl"] else "http://", + self.plugin_utils.config.dict["nextpvr"]["address"], + str(self.plugin_utils.config.dict["nextpvr"]["port"]), + self.sid + )) + + data_req = self.plugin_utils.web.session.get(data_url) + data_dict = xmltodict.parse(data_req.content) + + if 'channels' not in list(data_dict['rsp'].keys()): + self.plugin_utils.logger.error("Could not retrieve channel list") + return [] + + channel_o_list = data_dict['rsp']['channels']['channel'] + + channel_list = [] + for c in channel_o_list: + dString = json.dumps(c) + channel_dict = eval(dString) + + clean_station_item = { + "name": channel_dict["name"], + "callsign": channel_dict["name"], + "number": channel_dict["formatted-number"], + "id": channel_dict["id"], + "thumbnail": self.get_channel_thumbnail(channel_dict["id"]) + } + channel_list.append(clean_station_item) + return channel_list + + def get_channel_stream(self, chandict, stream_args): + streamurl = ('%s%s:%s/live?channel_id=%s&client=%s' % + ("https://" if self.plugin_utils.config.dict["nextpvr"]["ssl"] else "http://", + self.plugin_utils.config.dict["nextpvr"]["address"], + str(self.plugin_utils.config.dict["nextpvr"]["port"]), + str(chandict["origin_id"]), + "fhdhr_%s" % chandict["origin_number"], + )) + + stream_info = {"url": streamurl} + + return stream_info diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_channels.py b/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_channels.py deleted file mode 100644 index aa4d52a..0000000 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_channels.py +++ /dev/null @@ -1,64 +0,0 @@ -import xmltodict -import json - - -class OriginChannels(): - - def __init__(self, fhdhr, origin): - self.fhdhr = fhdhr - self.origin = origin - - def get_channel_thumbnail(self, channel_id): - channel_thumb_url = ("%s%s:%s/service?method=channel.icon&channel_id=%s" % - ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", - self.fhdhr.config.dict["origin"]["address"], - str(self.fhdhr.config.dict["origin"]["port"]), - str(channel_id) - )) - return channel_thumb_url - - def get_channels(self): - - data_url = ('%s%s:%s/service?method=channel.list&sid=%s' % - ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", - self.fhdhr.config.dict["origin"]["address"], - str(self.fhdhr.config.dict["origin"]["port"]), - self.origin.sid - )) - - data_req = self.fhdhr.web.session.get(data_url) - data_dict = xmltodict.parse(data_req.content) - - if 'channels' not in list(data_dict['rsp'].keys()): - self.fhdhr.logger.error("Could not retrieve channel list") - return [] - - channel_o_list = data_dict['rsp']['channels']['channel'] - - channel_list = [] - for c in channel_o_list: - dString = json.dumps(c) - channel_dict = eval(dString) - - clean_station_item = { - "name": channel_dict["name"], - "callsign": channel_dict["name"], - "number": channel_dict["formatted-number"], - "id": channel_dict["id"], - "thumbnail": self.get_channel_thumbnail(channel_dict["id"]) - } - channel_list.append(clean_station_item) - return channel_list - - def get_channel_stream(self, chandict, stream_args): - streamurl = ('%s%s:%s/live?channel_id=%s&client=%s' % - ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", - self.fhdhr.config.dict["origin"]["address"], - str(self.fhdhr.config.dict["origin"]["port"]), - str(chandict["origin_id"]), - "fhdhr_%s" % chandict["origin_number"], - )) - - stream_info = {"url": streamurl} - - return stream_info diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_conf.json b/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_conf.json deleted file mode 100644 index 8839a6a..0000000 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_conf.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "main":{ - "servicename":{ - "value": "NextPVR", - "config_file": false, - "config_web": false - }, - "dictpopname":{ - "value": "nextpvr", - "config_file": false, - "config_web": false - }, - "reponame":{ - "value": "fHDHR_NextPVR", - "config_file": false, - "config_web": false - } - }, - "fhdhr":{ - "friendlyname":{ - "value": "fHDHR-NextPVR", - "config_file": true, - "config_web": true - }, - "reporting_firmware_name":{ - "value": "fHDHR_NextPVR", - "config_file": true, - "config_web": true - } - }, - "epg":{ - "method":{ - "value": "origin", - "config_file": true, - "config_web": true - } - }, - "nextpvr":{ - "address":{ - "value": "localhost", - "config_file": true, - "config_web": true - }, - "port":{ - "value": 8866, - "config_file": true, - "config_web": true - }, - "ssl":{ - "value": false, - "config_file": true, - "config_web": true - }, - "pin":{ - "value": "none", - "config_file": true, - "config_web": true, - "config_web_hidden": true, - "required": true - }, - "sid":{ - "value": "none", - "config_file": true, - "config_web": false - } - } -} diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_service.py b/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_service.py deleted file mode 100644 index ac222e0..0000000 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_service.py +++ /dev/null @@ -1,55 +0,0 @@ -import xmltodict -import hashlib - -import fHDHR.tools -import fHDHR.exceptions - - -class OriginService(): - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - self.nextpvr_address = ('%s%s:%s' % - ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", - self.fhdhr.config.dict["origin"]["address"], - str(self.fhdhr.config.dict["origin"]["port"]), - )) - - self.login() - - def login(self): - self.fhdhr.logger.info("Logging into NextPVR") - self.sid = self.get_sid() - if not self.sid: - raise fHDHR.exceptions.OriginSetupError("NextPVR Login Failed") - else: - self.fhdhr.logger.info("NextPVR Login Success") - self.fhdhr.config.write(self.fhdhr.config.dict["main"]["dictpopname"], 'sid', self.sid) - - def get_sid(self): - if self.fhdhr.config.dict["origin"]["sid"]: - return self.fhdhr.config.dict["origin"]["sid"] - - initiate_url = '%s/service?method=session.initiate&ver=1.0&device=fhdhr' % self.nextpvr_address - - initiate_req = self.fhdhr.web.session.get(initiate_url) - initiate_dict = xmltodict.parse(initiate_req.content) - - sid = initiate_dict['rsp']['sid'] - salt = initiate_dict['rsp']['salt'] - md5PIN = hashlib.md5(str(self.fhdhr.config.dict["origin"]['pin']).encode('utf-8')).hexdigest() - string = ':%s:%s' % (md5PIN, salt) - clientKey = hashlib.md5(string.encode('utf-8')).hexdigest() - - login_url = ('%s/service?method=session.login&sid=%s&md5=%s' % - (self.nextpvr_address, sid, clientKey)) - login_req = self.fhdhr.web.session.get(login_url) - login_dict = xmltodict.parse(login_req.content) - - loginsuccess = None - if login_dict['rsp']['@stat'] == "ok": - if login_dict['rsp']['allow_watch'] == "true": - loginsuccess = sid - - return loginsuccess diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/__init__.py b/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/__init__.py deleted file mode 100644 index 4b6008e..0000000 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ - -from .origin_api import Origin_API -from .origin_html import Origin_HTML - - -class fHDHR_Origin_Web(): - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - self.origin_api = Origin_API(fhdhr) - self.origin_html = Origin_HTML(fhdhr) diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin_api.py b/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin_api.py deleted file mode 100644 index a23494e..0000000 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin_api.py +++ /dev/null @@ -1,16 +0,0 @@ - - -class Origin_API(): - endpoints = ["/api/origin"] - endpoint_name = "api_origin" - endpoint_methods = ["GET", "POST"] - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - - return "Success" diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin_html.py b/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin_html.py deleted file mode 100644 index c6e9632..0000000 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin_html.py +++ /dev/null @@ -1,30 +0,0 @@ -from flask import request, render_template_string -import pathlib -from io import StringIO - - -class Origin_HTML(): - endpoints = ["/origin", "/origin.html"] - endpoint_name = "page_origin_html" - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - self.template_file = pathlib.Path(self.fhdhr.config.internal["paths"]["origin_web"]).joinpath('origin.html') - self.template = StringIO() - self.template.write(open(self.template_file).read()) - - def __call__(self, *args): - return self.get(*args) - - def get(self, *args): - - if self.fhdhr.originwrapper.setup_success: - origin_status_dict = { - "Setup": "Success", - "Address": self.fhdhr.originwrapper.originservice.nextpvr_address, - "Total Channels": len(self.fhdhr.device.channels.list) - } - else: - origin_status_dict = {"Setup": "Failed"} - return render_template_string(self.template.getvalue(), request=request, fhdhr=self.fhdhr, origin_status_dict=origin_status_dict, list=list) diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/plugin.json b/plugins/fHDHR_plugin_origin_NextPVR/origin/plugin.json new file mode 100644 index 0000000..d4aa78b --- /dev/null +++ b/plugins/fHDHR_plugin_origin_NextPVR/origin/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"origin" +} diff --git a/plugins/fHDHR_plugin_origin_NextPVR/plugin.json b/plugins/fHDHR_plugin_origin_NextPVR/plugin.json new file mode 100644 index 0000000..1debee7 --- /dev/null +++ b/plugins/fHDHR_plugin_origin_NextPVR/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"NextPVR", + "version":"v0.6.0-beta", + "type":"origin" +} diff --git a/plugins/fHDHR_plugin_origin_NextPVR/web/__init__.py b/plugins/fHDHR_plugin_origin_NextPVR/web/__init__.py new file mode 100644 index 0000000..e75770e --- /dev/null +++ b/plugins/fHDHR_plugin_origin_NextPVR/web/__init__.py @@ -0,0 +1,11 @@ + +from .origin_html import Origin_HTML + + +class Plugin_OBJ(): + + def __init__(self, fhdhr, plugin_utils): + self.fhdhr = fhdhr + self.plugin_utils = plugin_utils + + self.origin_html = Origin_HTML(fhdhr, plugin_utils) diff --git a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin.html b/plugins/fHDHR_plugin_origin_NextPVR/web/origin.html similarity index 80% rename from plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin.html rename to plugins/fHDHR_plugin_origin_NextPVR/web/origin.html index aa08202..6e680c5 100644 --- a/plugins/fHDHR_plugin_origin_NextPVR/origin/origin_web/origin.html +++ b/plugins/fHDHR_plugin_origin_NextPVR/web/origin.html @@ -2,7 +2,7 @@ {% block content %} -

{{ fhdhr.config.dict["main"]["servicename"] }} Status

+

{{ origin }} Status

diff --git a/plugins/fHDHR_plugin_origin_NextPVR/web/origin_html.py b/plugins/fHDHR_plugin_origin_NextPVR/web/origin_html.py new file mode 100644 index 0000000..6c4b152 --- /dev/null +++ b/plugins/fHDHR_plugin_origin_NextPVR/web/origin_html.py @@ -0,0 +1,34 @@ +from flask import request, render_template_string, session +import pathlib +from io import StringIO + + +class Origin_HTML(): + endpoints = ["/nextpvr", "/nextpvr.html"] + endpoint_name = "page_nextpvr_html" + endpoint_category = "pages" + pretty_name = "NextPVR" + + def __init__(self, fhdhr, plugin_utils): + self.fhdhr = fhdhr + self.plugin_utils = plugin_utils + + self.origin = plugin_utils.origin + + self.template_file = pathlib.Path(fhdhr.config.dict["plugin_web_paths"][plugin_utils.namespace]["path"]).joinpath('origin.html') + self.template = StringIO() + self.template.write(open(self.template_file).read()) + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + + if self.origin.setup_success: + origin_status_dict = { + "Setup": "Success", + "Address": self.origin.nextpvr_address, + } + else: + origin_status_dict = {"Setup": "Failed"} + return render_template_string(self.template.getvalue(), request=request, session=session, fhdhr=self.fhdhr, origin_status_dict=origin_status_dict, list=list, origin=self.plugin_utils.namespace) diff --git a/plugins/fHDHR_plugin_origin_NextPVR/web/plugin.json b/plugins/fHDHR_plugin_origin_NextPVR/web/plugin.json new file mode 100644 index 0000000..0a5a6e1 --- /dev/null +++ b/plugins/fHDHR_plugin_origin_NextPVR/web/plugin.json @@ -0,0 +1,3 @@ +{ + "type":"web" +} diff --git a/plugins/fHDHR_plugin_stream_ffmpeg/__init__.py b/plugins/fHDHR_plugin_stream_ffmpeg/__init__.py index 0e676e8..9d510a0 100644 --- a/plugins/fHDHR_plugin_stream_ffmpeg/__init__.py +++ b/plugins/fHDHR_plugin_stream_ffmpeg/__init__.py @@ -1,41 +1,34 @@ import sys import subprocess -# from fHDHR.exceptions import TunerError -PLUGIN_NAME = "ffmpeg" -PLUGIN_VERSION = "v0.6.0-beta" -PLUGIN_TYPE = "alt_stream" +def setup(plugin): + try: + ffmpeg_command = [plugin.config.dict["ffmpeg"]["path"], + "-version", + "pipe:stdout" + ] + + ffmpeg_proc = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE) + ffmpeg_version = ffmpeg_proc.stdout.read() + ffmpeg_proc.terminate() + ffmpeg_proc.communicate() + ffmpeg_proc.kill() + ffmpeg_version = ffmpeg_version.decode().split("version ")[1].split(" ")[0] + except FileNotFoundError: + ffmpeg_version = "Missing" + plugin.logger.warning("Failed to find ffmpeg.") + plugin.config.register_version("ffmpeg", ffmpeg_version, "env") -class FFMPEG_Setup(): - def __init__(self, config): - try: - ffmpeg_command = [config.dict["ffmpeg"]["path"], - "-version", - "pipe:stdout" - ] +class Plugin_OBJ(): - ffmpeg_proc = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE) - ffmpeg_version = ffmpeg_proc.stdout.read() - ffmpeg_proc.terminate() - ffmpeg_proc.communicate() - ffmpeg_proc.kill() - ffmpeg_version = ffmpeg_version.decode().split("version ")[1].split(" ")[0] - except FileNotFoundError: - ffmpeg_version = "Missing" - print("Failed to find ffmpeg.") - config.register_version("ffmpeg", ffmpeg_version) - - -class FFMPEG_Stream(): - - def __init__(self, fhdhr, stream_args, tuner): - self.fhdhr = fhdhr + def __init__(self, plugin_utils, stream_args, tuner): + self.plugin_utils = plugin_utils self.stream_args = stream_args self.tuner = tuner - self.bytes_per_read = int(self.fhdhr.config.dict["streaming"]["bytes_per_read"]) + self.bytes_per_read = int(plugin_utils.config.dict["streaming"]["bytes_per_read"]) self.ffmpeg_command = self.ffmpeg_command_assemble(stream_args) def get(self): @@ -53,17 +46,17 @@ class FFMPEG_Stream(): yield chunk chunk_size = int(sys.getsizeof(chunk)) self.tuner.add_downloaded_size(chunk_size) - self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed") + self.plugin_utils.logger.info("Connection Closed: Tuner Lock Removed") except GeneratorExit: - self.fhdhr.logger.info("Connection Closed.") + self.plugin_utils.logger.info("Connection Closed.") except Exception as e: - self.fhdhr.logger.info("Connection Closed: %s" % e) + self.plugin_utils.logger.info("Connection Closed: %s" % e) finally: ffmpeg_proc.terminate() ffmpeg_proc.communicate() ffmpeg_proc.kill() - self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed") + self.plugin_utils.logger.info("Connection Closed: Tuner Lock Removed") self.tuner.close() # raise TunerError("806 - Tune Failed") @@ -71,7 +64,7 @@ class FFMPEG_Stream(): def ffmpeg_command_assemble(self, stream_args): ffmpeg_command = [ - self.fhdhr.config.dict["ffmpeg"]["path"], + self.plugin_utils.config.dict["ffmpeg"]["path"], "-i", stream_args["stream_info"]["url"], ] ffmpeg_command.extend(self.ffmpeg_headers(stream_args)) @@ -112,7 +105,7 @@ class FFMPEG_Stream(): def ffmpeg_loglevel(self): ffmpeg_command = [] - log_level = self.fhdhr.config.dict["logging"]["level"].lower() + log_level = self.plugin_utils.config.dict["logging"]["level"].lower() loglevel_dict = { "debug": "debug", @@ -129,7 +122,7 @@ class FFMPEG_Stream(): def transcode_profiles(self, stream_args): if stream_args["transcode_quality"]: - self.fhdhr.logger.info("Client requested a %s transcode for stream." % stream_args["transcode_quality"]) + self.plugin_utils.logger.info("Client requested a %s transcode for stream." % stream_args["transcode_quality"]) ffmpeg_command = [] diff --git a/plugins/fHDHR_plugin_stream_ffmpeg/plugin.json b/plugins/fHDHR_plugin_stream_ffmpeg/plugin.json new file mode 100644 index 0000000..a456406 --- /dev/null +++ b/plugins/fHDHR_plugin_stream_ffmpeg/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"ffmpeg", + "version":"v0.6.0-beta", + "type":"alt_stream" +} diff --git a/plugins/fHDHR_plugin_stream_vlc/__init__.py b/plugins/fHDHR_plugin_stream_vlc/__init__.py index 1d541cd..13d90f2 100644 --- a/plugins/fHDHR_plugin_stream_vlc/__init__.py +++ b/plugins/fHDHR_plugin_stream_vlc/__init__.py @@ -1,41 +1,34 @@ import sys import subprocess -# from fHDHR.exceptions import TunerError -PLUGIN_NAME = "vlc" -PLUGIN_VERSION = "v0.6.0-beta" -PLUGIN_TYPE = "alt_stream" +def setup(plugin): + try: + vlc_command = [plugin.config.dict["vlc"]["path"], + "--version", + "pipe:stdout" + ] + + vlc_proc = subprocess.Popen(vlc_command, stdout=subprocess.PIPE) + vlc_version = vlc_proc.stdout.read() + vlc_proc.terminate() + vlc_proc.communicate() + vlc_proc.kill() + vlc_version = vlc_version.decode().split("version ")[1].split('\n')[0] + except FileNotFoundError: + vlc_version = "Missing" + plugin.logger.warning("Failed to find vlc.") + plugin.config.register_version("vlc", vlc_version, "env") -class VLC_Setup(): - def __init__(self, config): - try: - vlc_command = [config.dict["vlc"]["path"], - "--version", - "pipe:stdout" - ] +class Plugin_OBJ(): - vlc_proc = subprocess.Popen(vlc_command, stdout=subprocess.PIPE) - vlc_version = vlc_proc.stdout.read() - vlc_proc.terminate() - vlc_proc.communicate() - vlc_proc.kill() - vlc_version = vlc_version.decode().split("version ")[1].split('\n')[0] - except FileNotFoundError: - vlc_version = "Missing" - print("Failed to find vlc.") - config.register_version("vlc", vlc_version) - - -class VLC_Stream(): - - def __init__(self, fhdhr, stream_args, tuner): - self.fhdhr = fhdhr + def __init__(self, plugin_utils, stream_args, tuner): + self.plugin_utils = plugin_utils self.stream_args = stream_args self.tuner = tuner - self.bytes_per_read = int(self.fhdhr.config.dict["streaming"]["bytes_per_read"]) + self.bytes_per_read = int(self.plugin_utils.config.dict["streaming"]["bytes_per_read"]) self.vlc_command = self.vlc_command_assemble(stream_args) def get(self): @@ -54,17 +47,17 @@ class VLC_Stream(): yield chunk chunk_size = int(sys.getsizeof(chunk)) self.tuner.add_downloaded_size(chunk_size) - self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed") + self.plugin_utils.logger.info("Connection Closed: Tuner Lock Removed") except GeneratorExit: - self.fhdhr.logger.info("Connection Closed.") + self.plugin_utils.logger.info("Connection Closed.") except Exception as e: - self.fhdhr.logger.info("Connection Closed: %s" % e) + self.plugin_utils.logger.info("Connection Closed: %s" % e) finally: vlc_proc.terminate() vlc_proc.communicate() vlc_proc.kill() - self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed") + self.plugin_utils.logger.info("Connection Closed: Tuner Lock Removed") self.tuner.close() # raise TunerError("806 - Tune Failed") @@ -72,7 +65,7 @@ class VLC_Stream(): def vlc_command_assemble(self, stream_args): vlc_command = [ - self.fhdhr.config.dict["vlc"]["path"], + self.plugin_utils.config.dict["vlc"]["path"], "-I", "dummy", stream_args["stream_info"]["url"], ] # vlc_command.extend(self.vlc_headers(stream_args)) @@ -94,7 +87,7 @@ class VLC_Stream(): def vlc_loglevel(self): vlc_command = [] - log_level = self.fhdhr.config.dict["logging"]["level"].lower() + log_level = self.plugin_utils.config.dict["logging"]["level"].lower() loglevel_dict = { "debug": "3", @@ -113,7 +106,7 @@ class VLC_Stream(): vlc_command = [] if stream_args["transcode_quality"]: - self.fhdhr.logger.info("Client requested a %s transcode for stream." % stream_args["transcode_quality"]) + self.plugin_utils.logger.info("Client requested a %s transcode for stream." % stream_args["transcode_quality"]) transcode_dict = {} if not stream_args["transcode_quality"] or stream_args["transcode_quality"] == "heavy": diff --git a/plugins/fHDHR_plugin_stream_vlc/plugin.json b/plugins/fHDHR_plugin_stream_vlc/plugin.json new file mode 100644 index 0000000..2aa7464 --- /dev/null +++ b/plugins/fHDHR_plugin_stream_vlc/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"vlc", + "version":"v0.6.0-beta", + "type":"alt_stream" +} diff --git a/plugins/fHDHR_plugin_web_devtools/__init__.py b/plugins/fHDHR_plugin_web_devtools/__init__.py new file mode 100644 index 0000000..963d074 --- /dev/null +++ b/plugins/fHDHR_plugin_web_devtools/__init__.py @@ -0,0 +1,12 @@ +from .devtools_html import DevTools_HTML +from .devtools_api import DevTools_API + + +class Plugin_OBJ(): + + def __init__(self, fhdhr, plugin_utils): + self.fhdhr = fhdhr + self.plugin_utils = plugin_utils + + self.devtools_html = DevTools_HTML(fhdhr, plugin_utils) + self.devtools_api = DevTools_API(fhdhr, plugin_utils) diff --git a/fHDHR_web/templates/tools.html b/plugins/fHDHR_plugin_web_devtools/devtools.html similarity index 72% rename from fHDHR_web/templates/tools.html rename to plugins/fHDHR_plugin_web_devtools/devtools.html index de7281e..ac9b1ca 100644 --- a/fHDHR_web/templates/tools.html +++ b/plugins/fHDHR_plugin_web_devtools/devtools.html @@ -2,9 +2,9 @@ {% block content %} -

{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Tools

+

{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Dev Tools

-
+
diff --git a/fHDHR_web/api/tools.py b/plugins/fHDHR_plugin_web_devtools/devtools_api.py similarity index 77% rename from fHDHR_web/api/tools.py rename to plugins/fHDHR_plugin_web_devtools/devtools_api.py index a65e0ad..7466bba 100644 --- a/fHDHR_web/api/tools.py +++ b/plugins/fHDHR_plugin_web_devtools/devtools_api.py @@ -3,16 +3,17 @@ import urllib.parse import json -class API_Tools(): - endpoints = ["/api/tools"] - endpoint_name = "api_tools" +class DevTools_API(): + endpoints = ["/api/devtools"] + endpoint_name = "api_devtools" endpoint_methods = ["GET", "POST"] endpoint_default_parameters = { "method": "get" } - def __init__(self, fhdhr): + def __init__(self, fhdhr, plugin_utils): self.fhdhr = fhdhr + self.plugin_utils = plugin_utils def __call__(self, *args): return self.get(*args) @@ -36,11 +37,11 @@ class API_Tools(): dirty_json_url = request.form.get('url', None) try: - json_url_req = self.fhdhr.web.session.get(dirty_json_url) + json_url_req = self.plugin_utils.web.session.get(dirty_json_url) json_url_req.raise_for_status() json_resp = json_url_req.json() - except self.fhdhr.web.exceptions.HTTPError as err: - self.fhdhr.logger.error('Error while getting stations: %s' % err) + except self.plugin_utilsplugin_utils.web.exceptions.HTTPError as err: + self.plugin_utils.logger.error('Error while getting stations: %s' % err) json_resp = {"error": 'Error while getting stations: %s' % err} return_json = json.dumps(json_resp, indent=4) diff --git a/plugins/fHDHR_plugin_web_devtools/devtools_html.py b/plugins/fHDHR_plugin_web_devtools/devtools_html.py new file mode 100644 index 0000000..b8cca36 --- /dev/null +++ b/plugins/fHDHR_plugin_web_devtools/devtools_html.py @@ -0,0 +1,26 @@ +from flask import request, render_template_string, session +import pathlib +from io import StringIO + + +class DevTools_HTML(): + endpoints = ["/devtools", "/devtools.html"] + endpoint_name = "page_devtools_html" + endpoint_access_level = 2 + endpoint_category = "tool_pages" + pretty_name = "Dev Tools" + + def __init__(self, fhdhr, plugin_utils): + self.fhdhr = fhdhr + self.plugin_utils = plugin_utils + + self.template_file = pathlib.Path(plugin_utils.config.dict["plugin_web_paths"][plugin_utils.namespace]["path"]).joinpath('devtools.html') + self.template = StringIO() + self.template.write(open(self.template_file).read()) + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + + return render_template_string(self.template.getvalue(), request=request, session=session, fhdhr=self.fhdhr) diff --git a/plugins/fHDHR_plugin_web_devtools/plugin.json b/plugins/fHDHR_plugin_web_devtools/plugin.json new file mode 100644 index 0000000..39a6252 --- /dev/null +++ b/plugins/fHDHR_plugin_web_devtools/plugin.json @@ -0,0 +1,5 @@ +{ + "name":"DevTools", + "version":"v0.6.0-beta", + "type":"web" +} diff --git a/requirements.txt b/requirements.txt index c49cb72..04f99ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ xmltodict sqlalchemy pycryptodome m3u8 +simplejson