def init_host(self, device, service_id, configid = None): """ Initializes a host service. :param device: Host device this UPnP service is added to :param service_id: Unique UPnP service ID :param configid: UPnP configId for the host device :return: (bool) Returns true if initialization was successful. :since: v0.2.00 """ self.configid = configid self.host_service = True self.service_id = service_id self.udn = device.get_udn() self.url_base = "{0}{1}/".format(device.get_url_base(), Link.encode_query_value(service_id)) self.url_control = "{0}control".format(self.url_base) self.url_event_control = "{0}eventsub".format(self.url_base) self.url_scpd = "{0}xml".format(self.url_base) self._init_host_actions(device) self._init_host_variables(device) Hook.call("dNG.pas.upnp.Service.initHost", device = device, service = self) Hook.register_weakref("dNG.pas.upnp.Gena.onRegistered", self._on_gena_registration) return ((len(self.actions) + len(self.variables)) > 0)
def _init_content(self): """ Initializes the content of a container. :return: (bool) True if successful :since: v0.2.00 """ if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}._init_content()- (#echo(__LINE__)#)", self, context = "pas_upnp") _return = False if (self.content is None): with self._lock: # Thread safety _return = Abstract._init_content(self) if (not _return): if (self.resource_id == "0"): Hook.call("dNG.pas.upnp.Resource.getRootResourceClientContent", container = self) if (len(self.content) == 0): Hook.call("dNG.pas.upnp.Resource.getRootResourceContent", container = self) _return = True # # # # return _return
def delete(params, last_return = None): """ Called for "dNG.pas.user.Profile.delete" :param params: Parameter specified :param last_return: The return value from the last hook called. :return: (mixed) Return value :since: v0.2.00 """ # pylint: disable=star-args if ("username" not in params): raise ValueException("Missing required argument") else: user_profile_class = NamedLoader.get_class("dNG.data.user.Profile") try: user_profile = user_profile_class.load_username(params['username']) Hook.call("dNG.pas.user.Profile.onDelete", user_profile_id = user_profile.get_id()) user_profile.delete() except NothingMatchedException: pass # return last_return
def run(self): """ Worker loop :since: v0.2.00 """ # pylint: disable=broad-except mainloop = None with GlibThread._lock: if (self.mainloop is None): mainloop = GLib.MainLoop() self.mainloop = mainloop # # if (mainloop is not None): if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}.run()- (#echo(__LINE__)#)", self, context = "pas_gapi_core") try: mainloop.run() except Exception as handled_exception: LogLine.error(handled_exception, context = "pas_gapi_core") except KeyboardInterrupt: Hook.call("dNG.pas.Status.stop") finally: self.stop()
def _init_content(self): """ Initializes the content of a container. :return: (bool) True if successful :since: v0.2.00 """ if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}._init_content()- (#echo(__LINE__)#)", self, context = "pas_upnp") _return = False if (self.content is None): with self._lock: # Thread safety Resource._init_content(self) _type = self.get_type() if (_type is not None and _type & Abstract.TYPE_CDS_ITEM == Abstract.TYPE_CDS_ITEM ): Hook.call("dNG.pas.upnp.Resource.getItemResourceClientContent", item = self) if (len(self.content) == 0): Hook.call("dNG.pas.upnp.Resource.getItemResourceContent", item = self) _return = True # # # return _return
def execute_unsubscribe(self): """ Action for "request" :since: v0.2.00 """ self.response.init() self.response.set_header("Date", RfcBasics.get_rfc5322_datetime(time())) if (not isinstance(self.request, HttpUpnpRequest)): raise UpnpException("pas_http_core_400") upnp_service = self.request.get_upnp_service() if (not isinstance(upnp_service, AbstractService)): raise UpnpException("pas_http_core_400", 401) Hook.call("dNG.pas.http.l10n.upnp.Events.init") gena_sid = self.request.get_header("SID") upnp_service.set_client_settings(self.get_client_settings()) if (gena_sid is None): raise UpnpException("pas_http_core_400", 400) gena = Gena.get_instance() usn = upnp_service.get_usn() if (not gena.deregister(usn, gena_sid)): raise UpnpException("pas_http_core_404", 412) self.response.set_raw_data("")
def delete(params, last_return=None): """ Called for "dNG.pas.user.Profile.delete" :param params: Parameter specified :param last_return: The return value from the last hook called. :return: (mixed) Return value :since: v0.2.00 """ # pylint: disable=star-args if ("username" not in params): raise ValueException("Missing required argument") else: user_profile_class = NamedLoader.get_class("dNG.data.user.Profile") try: user_profile = user_profile_class.load_username(params['username']) Hook.call("dNG.pas.user.Profile.onDelete", user_profile_id=user_profile.get_id()) user_profile.delete() except NothingMatchedException: pass # return last_return
def _on_shutdown(self): """ Callback for shutdown. :since: v0.2.00 """ Hook.call("dNG.pas.Status.onShutdown") if (self.cache_instance is not None): self.cache_instance.disable() Hook.free()
def _on_run(self, args): """ Callback for execution. :param args: Parsed command line arguments :since: v0.2.00 """ # pylint: disable=attribute-defined-outside-init Settings.read_file("{0}/settings/pas_global.json".format(Settings.get("path_data"))) Settings.read_file("{0}/settings/pas_core.json".format(Settings.get("path_data")), True) Settings.read_file("{0}/settings/pas_tasks_daemon.json".format(Settings.get("path_data")), True) if (args.additional_settings is not None): Settings.read_file(args.additional_settings, True) if (not Settings.is_defined("pas_tasks_daemon_listener_address")): raise IOException("No listener address defined for the TasksDaemon") if (args.reload_plugins): client = BusClient("pas_tasks_daemon") client.request("dNG.pas.Plugins.reload") elif (args.stop): client = BusClient("pas_tasks_daemon") pid = client.request("dNG.pas.Status.getOSPid") client.request("dNG.pas.Status.stop") self._wait_for_os_pid(pid) else: self.cache_instance = NamedLoader.get_singleton("dNG.data.cache.Content", False) if (self.cache_instance is not None): Settings.set_cache_instance(self.cache_instance) self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) if (self.log_handler is not None): Hook.set_log_handler(self.log_handler) NamedLoader.set_log_handler(self.log_handler) # Hook.load("tasks") Hook.register("dNG.pas.Status.getOSPid", self.get_os_pid) Hook.register("dNG.pas.Status.getTimeStarted", self.get_time_started) Hook.register("dNG.pas.Status.getUptime", self.get_uptime) Hook.register("dNG.pas.Status.stop", self.stop) self.server = BusServer("pas_tasks_daemon") self._set_time_started(time()) if (self.log_handler is not None): self.log_handler.info("TasksDaemon starts listening", context = "pas_tasks") Hook.call("dNG.pas.Status.onStartup") Hook.call("dNG.pas.tasks.Daemon.onStartup") self.set_mainloop(self.server.run)
def _on_run(self, args): """ Callback for execution. :param args: Parsed command line arguments :since: v0.2.00 """ Settings.read_file("{0}/settings/pas_global.json".format(Settings.get("path_data"))) Settings.read_file("{0}/settings/pas_core.json".format(Settings.get("path_data")), True) Settings.read_file("{0}/settings/pas_http.json".format(Settings.get("path_data")), True) if (args.additional_settings is not None): Settings.read_file(args.additional_settings, True) if (args.reload_plugins): client = BusClient("pas_http_bus") client.request("dNG.pas.Plugins.reload") elif (args.stop): client = BusClient("pas_http_bus") pid = client.request("dNG.pas.Status.getOSPid") client.request("dNG.pas.Status.stop") self._wait_for_os_pid(pid) else: self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) if (self.log_handler is not None): Hook.set_log_handler(self.log_handler) NamedLoader.set_log_handler(self.log_handler) # self.cache_instance = NamedLoader.get_singleton("dNG.data.cache.Content", False) if (self.cache_instance is not None): Settings.set_cache_instance(self.cache_instance) Hook.load("http") Hook.register("dNG.pas.Status.getOSPid", self.get_os_pid) Hook.register("dNG.pas.Status.getTimeStarted", self.get_time_started) Hook.register("dNG.pas.Status.getUptime", self.get_uptime) Hook.register("dNG.pas.Status.stop", self.stop) self._set_time_started(time()) http_server = _HttpServer.get_instance() self.server = BusServer("pas_http_bus") if (http_server is not None): Hook.register("dNG.pas.Status.onStartup", http_server.start) Hook.register("dNG.pas.Status.onShutdown", http_server.stop) if (self.log_handler is not None): self.log_handler.info("pas.http starts listening", context = "pas_http_site") Hook.call("dNG.pas.Status.onStartup") self.set_mainloop(self.server.run)
def execute_subscribe(self): """ Action for "request" :since: v0.2.00 """ self.response.init() self.response.set_header("Date", RfcBasics.get_rfc5322_datetime(time())) if (not isinstance(self.request, HttpUpnpRequest)): raise UpnpException("pas_http_core_400") upnp_service = self.request.get_upnp_service() if (not isinstance(upnp_service, AbstractService)): raise UpnpException("pas_http_core_400", 401) Hook.call("dNG.pas.http.l10n.upnp.Events.init") callback_value = self.request.get_header("Callback") client_settings = self.get_client_settings() gena_sid = self.request.get_header("SID") upnp_service.set_client_settings(client_settings) if ((callback_value is None or self.request.get_header("NT") != "upnp:event") and gena_sid is None ): raise UpnpException("pas_http_core_400", 400) gena = Gena.get_instance() timeout = self.request.get_header("Timeout") re_result = (None if (timeout is None) else re.match("^Second-(\\d+)$", timeout)) if (re_result is None): timeout = int(client_settings.get("upnp_subscription_timeout", 1800)) else: timeout = int(re_result.group(1)) usn = upnp_service.get_usn() if (gena_sid is None): gena_variables = self.request.get_header("StateVar") gena_sid = gena.register(usn, callback_value, timeout, variables = gena_variables) if (gena_sid is None): raise UpnpException("pas_http_core_404", 412) self.response.set_header("SID", gena_sid) self.response.set_header("Timeout", "Second-{0:d}".format(timeout)) if (gena_variables != ""): self.response.set_header("Accepted-StateVar", gena_variables) self.response.set_raw_data("") else: result = gena.reregister(usn, gena_sid, timeout) if (result == False): raise UpnpException("pas_http_core_404", 412) self.response.set_header("SID", gena_sid) self.response.set_header("Timeout", "Second-{0:d}".format(timeout)) self.response.set_raw_data("")
def stop(self, params = None, last_return = None): """ Stop the server :param params: Parameter specified :param last_return: The return value from the last hook called. :return: (mixed) Return value :since: v1.0.0 """ Hook.call("dNG.pas.http.Server.onShutdown", server = self) return last_return
def init_cds_id(self, _id, client_user_agent = None, deleted = False): """ Initialize a UPnP resource by CDS ID. :param _id: UPnP CDS ID :param client_user_agent: Client user agent :param update_id: UPnP UpdateID value :param deleted: True to include deleted resources :return: (bool) Returns true if initialization was successful. :since: v0.2.00 """ if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}.init_cds_id({1})- (#echo(__LINE__)#)", self, _id, context = "pas_upnp") _return = Abstract.init_cds_id(self, _id, client_user_agent, deleted) if (_id == "0"): self.name = L10n.get("pas_upnp_container_root") self.type = RootContainer.TYPE_CDS_CONTAINER search_segments = Hook.call("dNG.pas.upnp.Resource.getSearchSegments", id = _id) self.searchable = (type(search_segments) is list and len(search_segments) > 0) else: _return = True return _return
def init_cds_id(self, _id, client_user_agent = None, deleted = False): """ Initialize a UPnP resource by CDS ID. :param _id: UPnP CDS ID :param client_user_agent: Client user agent :param deleted: True to include deleted resources :return: (bool) Returns true if initialization was successful. :since: v0.2.00 """ Abstract.init_cds_id(self, _id, client_user_agent, deleted) _return = (self.resource_id is not None) if (_return): url_elements = urlsplit(self.resource_id) url_path_elements = url_elements.path[1:].split("/", 1) hook_id = url_path_elements[0] hook_params = (dict(parse_qsl(unquote(url_path_elements[1]), keep_blank_values = True)) if (len(url_path_elements) == 2) else { } ) resource_data = Hook.call("mp.upnp.HookResource.getResourceData", id = hook_id, **hook_params) if (self.init(resource_data)): self.hook_id = hook_id self.hook_params = hook_params else: _return = False # return _return
def handle_upnp_stream_request(request, virtual_config): """ Handles a UPnP stream related HTTP request. :param request: Originating request instance :param virtual_config: Virtual path configuration :return: (object) Request object if valid :since: v0.2.00 """ if (not isinstance(request, AbstractHttpRequest)): raise TranslatableException("pas_http_core_400", 400) user_agent = request.get_header("User-Agent") stream_path = request.get_dsd("upnp_path") client_settings = ClientSettings(user_agent) if (client_settings.get("upnp_stream_path_use_filter", False)): stream_path_filtered = Hook.call("dNG.pas.http.HttpUpnpRequest.filterStreamPath", path = stream_path, user_agent = user_agent) if (stream_path_filtered is not None): stream_path = stream_path_filtered # if (stream_path is not None): stream_path = unquote(stream_path) _return = PredefinedHttpRequest() _return.set_module("upnp") _return.set_service("stream") _return.set_action("resource") _return.set_dsd("urid", stream_path) return _return
def call_task(request, virtual_config): """ Called for requests with the path prefix "/tasks/". :param request: Originating request instance :param virtual_config: Virtual path configuration :return: (object) Request object if valid :since: v0.2.00 """ _return = None tid = request.get_dsd("tid") with ExceptionLogTrap("pas_http_site"): _return = (None if (tid is None) else Hook.call("dNG.pas.Tasks.call", client = request.get_client_host(), tid = tid) ) # if (_return is None): LogLine.warning("pas.Tasks.call refused TID '{0}'", tid, context = "pas_http_site") _return = handle_task_result_none() # return _return
def start(self, params = None, last_return = None): """ Start the server :param params: Parameter specified :param last_return: The return value from the last hook called. :return: (mixed) Return value :since: v1.0.0 """ self._configure() Thread.start(self) Hook.call("dNG.pas.http.Server.onStartup", server = self) return self
def _get_segments(self): """ Returns the search segment instances. :return: (list) List of search segments; None if not registered :since: v0.2.00 """ return (Hook.call("dNG.pas.upnp.Resource.getSearchSegments", criteria_definition = self.criteria_definition ) if (self.root_resource is None) else Hook.call("dNG.pas.upnp.Resource.getSearchSegments", id = self.root_resource.get_resource_id(), criteria_definition = self.criteria_definition ) )
def _init_theme_renderer(self): """ Set up theme framework if renderer has not already been initialized. :since: v1.0.0 """ if (self._theme_renderer is None): if (self._log_handler is not None): self._log_handler.debug("#echo(__FILEPATH__)# -{0!r}._init_theme_renderer()- (#echo(__LINE__)#)", self, context = "pas_http_core") Settings.read_file("{0}/settings/pas_http_theme.json".format(Settings.get("path_data"))) if (self._theme is None): theme = AbstractHttpRequest.get_instance().get_parameter("theme") if (theme is not None): self._theme = theme # theme = (Hook.call("dNG.pas.http.Theme.checkCandidates", theme = self._theme) if (Settings.get("pas_http_theme_plugins_supported", True)) else None ) if (theme is not None): theme = re.sub("\\W", "", theme) self._theme_renderer = NamedLoader.get_instance("dNG.data.xhtml.theme.Renderer") self._theme_renderer.theme = (self._theme if (theme is None) else theme) self._theme_active = self._theme_renderer.theme Settings.set("x_pas_http_theme", self.theme) self.theme_active_base_path = path.join(Settings.get("path_data"), "assets", "themes", self.theme_active ) self._theme_renderer.log_handler = self._log_handler self._theme_renderer.theme = self.theme_active self._theme_renderer.theme_subtype = self.theme_subtype if (self._description is not None): self._theme_renderer.description = self._description if (self._canonical_url is not None): self._theme_renderer.canonical_url = self.canonical_url for css_file in self.css_files_cache: self.add_css_file(css_file) self.css_files_cache = [ ] for js_file in self.js_files_cache: self.add_js_file(js_file) self.js_files_cache = [ ] for theme_css_file in self.theme_css_files_cache: self.add_theme_css_file(theme_css_file) self.theme_css_files_cache = [ ] for webc_file in self.webc_files_cache: self.add_webc_file(webc_file) self.webc_files_cache = [ ]
def get_user_agent_settings(user_agent): """ Returns the user agent specific client settings dictionary. :param user_agent: User agent :return: (dict) User agent specific client settings; None on error :since: v0.2.00 """ _return = { } settings = None user_agent = Binary.str(user_agent) if (type(user_agent) is str): settings = Hook.call("dNG.pas.upnp.Client.getUserAgentSettings", user_agent = user_agent) if (not isinstance(settings, dict)): identifier = ClientSettings.get_user_agent_identifiers(user_agent) settings_file_name = "{0}.json".format(identifier) settings = JsonFileContent.read(path.join(Settings.get("path_data"), "upnp", "user_agents", settings_file_name ) ) if (settings is None): log_line = "pas.upnp.ClientSettings reporting: No client settings found for user agent '{0}' with identifier '{1}'" if (Settings.get("pas_upnp_log_missing_user_agent", False)): LogLine.warning(log_line, user_agent, identifier, context = "pas_upnp") else: LogLine.debug(log_line, user_agent, identifier, context = "pas_upnp") # # # if (settings is not None): if ("client_file" in settings): base_settings = JsonFileContent.read(path.join(Settings.get("path_data"), "upnp", "user_agents", InputFilter.filter_file_path(settings['client_file']) ) ) if (type(base_settings) is dict): _return.update(base_settings) del(settings['client_file']) # _return.update(settings) # return _return
def run_apply_schema(self, args): """ Callback for execution. :since: v1.0.0 """ self.output_info("Loading database entities ...") with Connection.get_instance() as connection: Hook.call("dNG.pas.Database.loadAll") self.output_info("Applying schema ...") with HookContext("dNG.pas.Database.applySchema"), TransactionContext(): Abstract().metadata.create_all(connection.get_bind()) # # self.output_info("Process completed")
def execute_request(self): """ Action for "request" :since: v0.2.00 """ if (not isinstance(self.request, HttpUpnpRequest)): raise UpnpException("pas_http_core_400") upnp_service = self.request.get_upnp_service() if (not isinstance(upnp_service, AbstractService)): raise UpnpException("pas_http_core_400", 401) Hook.call("dNG.pas.http.l10n.upnp.Control.init") soap_request = self.request.get_soap_request() upnp_service.set_client_settings(self.get_client_settings()) if (soap_request is None): raise UpnpException("pas_http_core_500") self.response.init() self.response.handle_result(soap_request['urn'], soap_request['action'], upnp_service.handle_soap_call(soap_request['action'], soap_request['arguments']))
def deregister(self, usn, sid): """ Removes the subscription identified by the given SID. :param usn: UPnP USN :param sid: UPnP SID :return: (bool) True if successful :since: v0.2.00 """ if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}.deregister({1}, {2})- (#echo(__LINE__)#)", self, usn, sid, context = "pas_upnp") _return = False service_name = Gena.strip_usn_version(usn) if (service_name in self.subscriptions): with self.lock: # Thread safety if (service_name in self.subscriptions and sid in self.subscriptions[service_name]): del(self.subscriptions[service_name][sid]) if (len(self.subscriptions[service_name]) < 1): del(self.subscriptions[service_name]) for position in range(len(self.timeouts) - 1, -1, -1): timeout_entry = self.timeouts[position] if (timeout_entry['sid'] == sid): self.timeouts.pop(position) # _return = True # # if (_return): if (self.log_handler is not None): self.log_handler.debug("{0!r} removes subscription '{1}' for '{2}'", self, sid, usn, context = "pas_upnp") Hook.call("dNG.pas.upnp.Gena.onUnregistered", usn = usn, sid = sid) # # return _return
def is_defined(self, key): """ Checks if a given key is a defined setting. :param key: Settings key :return: (bool) True if defined :since: v0.2.00 """ _return = True if (Hook.call(self.hook, **self.params) is None): _return = (key in self.settings) return _return
def _get_feature_list(self, hook_class): """ Returns the list of supported UPnP ContentDirectory features. :return: (str) FeatureList XML document :since: v0.2.00 """ xml_resource = XmlResource() xml_resource.set_cdata_encoding(False) xml_resource.add_node("Features", attributes = { "xmlns": "urn:schemas-upnp-org:av:avs", "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation": "urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd" } ) xml_resource.set_cached_node("Features") Hook.call("{0}.getFeatures".format(hook_class), xml_resource = xml_resource) return "<?xml version='1.0' encoding='UTF-8' ?>{0}".format(xml_resource.export_cache(True))
def stop(self, params = None, last_return = None): """ Stops all UPnP listeners and deregisters itself. :param params: Parameter specified :param last_return: The return value from the last hook called. :since: v0.2.00 """ Hook.call("dNG.pas.upnp.ControlPoint.onShutdown") AbstractTimed.stop(self) with self.lock: self._delete_usns(self.usns.copy()) self.usns = { } listeners_multicast = self.listeners_multicast.copy() for ip in listeners_multicast: self._deactivate_multicast_listener(ip) # return last_return
def call_hook(self, hook, json_arguments): """ Calls the given hook and returns the result. :return: (mixed) Data returned by the called hook :since: v0.2.00 """ # pylint: disable=star-args json_resource = JsonResource() arguments = ({ } if (json_arguments.strip() == "") else json_resource.json_to_data(json_arguments)) result = Hook.call(hook, **arguments) return json_resource.data_to_json(result)
def get_sort_capabilities(self): """ Returns the UPnP sort capabilities. :return: (str) UPnP sort capabilities :since: v0.2.00 """ didl_fields = Hook.call("dNG.pas.upnp.Resource.getSortableDidlFields", client_user_agent = self.client_user_agent) if (type(didl_fields) != list or len(didl_fields) < 1): didl_fields = None if (didl_fields is None): _return = "" else: _return = ",".join(InputFilter.filter_unique_list(didl_fields)) return _return
def _configure(self): """ Configures the server :since: v1.0.0 """ site_version = Settings.get("pas_http_site_version", "") if (site_version == ""): site_version = "#echo(pasHttpCoreIVersion)#" Settings.set("pas_http_site_version", site_version) # if (Link.is_preferred_defined()): Settings.set("x_pas_http_base_url", Link.get_preferred().build_url(Link.TYPE_ABSOLUTE_URL | Link.TYPE_BASE_PATH)) else: Settings.set("x_pas_http_base_url", None) Settings.set("x_pas_http_session_uuid", "") Settings.set("x_pas_http_path_assets_versioned", "/data/assets/{0}".format(site_version)) VirtualRoute.set("/data/assets/{0}/".format(site_version), { "s": "cache", "path_parameter_key": "dfile" }) VirtualRoute.set("/data/assets/", { "s": "cache", "path_parameter_key": "dfile" }) Hook.call("dNG.pas.http.Server.onConfigured", server = self)
def get(self, key = None, default = None): """ Returns the value with the specified key. :param key: Settings key :param default: Default value if not set :return: (mixed) Value :since: v0.2.00 """ _return = Hook.call(self.hook, **self.params) if (_return is None): _return = self.settings.get(key) if (_return is None and default is not None): _return = default return _return
def _get_custom_type_class(self, type_class): """ Returns a user agent specific UPnP resource type class. :return: (str) UPnP resource type class; None if not defined :since: v0.2.00 """ _return = (None if (self.client_user_agent is None) else Hook.call("dNG.pas.upnp.Resource.getTypeClass", type_class = type_class, client_user_agent = self.client_user_agent ) ) return (type_class if (_return is None) else _return)