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 __init__(self, timeout_retries = 5): """ Constructor __init__(Abstract) :param timeout_retries: Retries before timing out :since: v0.2.00 """ SupportsMixin.__init__(self) self.io_chunk_size = 65536 """ IO chunk size """ self._lock = ThreadLock() """ Thread safety lock """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ Retries before timing out """ self.stream_size = -1 """ Requested stream size """ self.timeout_retries = (5 if (timeout_retries is None) else timeout_retries) """
def _init_host_protocol_info(self): """ Initializes the client settings UPnP host based protocol info dictionary if the UPnP host has been previously defined. :since: v0.2.00 """ if (self.host is not None and self.host_protocol_info_dict is None): with self._lock: # Thread safety if (self.host_protocol_info_dict is None): self.host_protocol_info_dict = { } control_point = NamedLoader.get_singleton("dNG.net.upnp.ControlPoint") connection_manager = None device = control_point.get_rootdevice_for_host(self.host, "MediaRenderer") if (device is not None): connection_manager = device.get_service("ConnectionManager") if (connection_manager is not None and connection_manager.is_action_supported("GetProtocolInfo")): connection_manager_proxy = connection_manager.get_proxy() protocol_info = connection_manager_proxy.GetProtocolInfo() supported_list = protocol_info.get("Source", "").split(",") self.host_protocol_info_dict['upnp_protocol_source_supported_list'] = supported_list supported_list = protocol_info.get("Sink", "").split(",") self.host_protocol_info_dict['upnp_protocol_sink_supported_list'] = supported_list
def get_class(is_not_implemented_class_aware = False): """ Returns the persistent tasks implementation class based on the configuration set. :param is_not_implemented_class_aware: True to return "dNG.runtime.NotImplementedClass" instead of None :return: (object) Tasks implementation class; None if not available :since: v0.2.00 """ _return = None persistent_tasks = Persistent.get_executing_instance() if (isinstance(persistent_tasks, AbstractPersistent) and persistent_tasks.is_executing_daemon()): _return = persistent_tasks else: persistent_tasks_proxy = NamedLoader.get_singleton("dNG.data.tasks.PersistentProxy") if (isinstance(persistent_tasks_proxy, AbstractPersistentProxy) and persistent_tasks_proxy.is_available() ): _return = persistent_tasks_proxy # if (_return is None and is_not_implemented_class_aware): _return = NotImplementedClass return _return
def __init__(self): """ Constructor __init__(Resources) :since: v0.2.00 """ ClientSettingsMixin.__init__(self) SupportsMixin.__init__(self) self.criteria_definition = None """ UPnP search criteria definition instance """ self.executed = False """ True if search has been executed and results are ready """ self.limit = 50 """ UPnP resource search results limit """ self.limit_max = int(Settings.get("pas_upnp_resource_search_limit_max", 50)) """ UPnP resource search results limit """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.offset = 0 """ UPnP resource search results offset """ self.resources = [ ] """ UPnP resources matching the criteria definition """ self.total = 0 """ UPnP resource search results count from all segments """ self.root_resource = None """ UPnP root resource for searching matches """ self.sort_tuples = [ ] """ Sort list to be applied """ self.supported_features['sortable'] = self._supports_sortable
def __init__(self): """ Constructor __init__(Service) :since: v0.2.00 """ IdentifierMixin.__init__(self) SpecMixin.__init__(self) self.actions = None """ Service actions defined in the SCPD """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.name = None """ UPnP service name """ self.service_id = None """ UPnP serviceId value """ self.url_base = None """ HTTP base URL """ self.url_control = None """ UPnP controlURL value """ self.url_event_control = None """ UPnP eventSubURL value """ self.url_scpd = None """ UPnP SCPDURL value """ self.variables = None """
def __init__(self): """ Constructor __init__(Abstract) :since: v0.2.00 """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.request = None """ Request instance """ self.response = None """
def __init__(self): """ Constructor __init__(Handler) :since: v0.2.00 """ Thread.__init__(self) self.active_id = -1 """ Queue ID """ self.address = None """ Address of the received data """ self.address_family = None """ Address family of the received data """ self.data = Binary.BYTES_TYPE() """ Data buffer """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.server = None """ Server instance """ self.socket = None """ Socket instance """ self.timeout = int(Settings.get("pas_global_server_socket_data_timeout", 0)) """ Request timeout value """ if (self.timeout < 1): self.timeout = int(Settings.get("pas_global_socket_data_timeout", 30))
def __init__(self): """ Constructor __init__(Gena) :since: v0.2.00 """ AbstractTimed.__init__(self) self.subscriptions = { } """ Active subscriptions """ self.timeouts = [ ] """ Active subscriptions """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False)
def __init__(self, target, port = 1900, source_port = None): """ Constructor __init__(AbstractSsdp) :since: v0.2.00 """ self.ssdp_family = None """ SSDP target family """ self.ssdp_host = None """ SSDP target host """ RawClient.__init__(self, "ssdp://{0}:{1:d}/*".format(target, port)) self.ipv4_broadcast_interface = Settings.get("pas_upnp_ssdp_ipv4_broadcast_interface", None) """ IPv4 TTL for SSDP messages. """ self.ipv4_udp_ttl = int(Settings.get("pas_upnp_ssdp_ipv4_udp_ttl", 2)) """ IPv4 TTL for SSDP messages. """ self.ipv6_udp_hops = int(Settings.get("pas_upnp_ssdp_ipv6_udp_hops", 2)) """ IPv6 hops for SSDP messages. """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.source_port = (0 if (source_port is None) else source_port) """ Sets a specific source port for messages sent. """ if (self.log_handler is not None): self.set_event_handler(self.log_handler) if (self.path == "/*"): self.path = "*"
def __init__(self): """ Constructor __init__(Parser) :since: v0.2.00 """ AbstractTagParser.__init__(self) self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.xml_node_path = None """ XML node path containing value nodes """ self.xml_parser = None """
def __init__(self): """ Constructor __init__(GlibThread) :since: v0.2.00 """ Thread.__init__(self) self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.mainloop = None """ Active mainloop instance """ Hook.register_weakref("dNG.pas.Status.onShutdown", self.stop)
def __init__(self): """ Constructor __init__(AbstractLrtHook) :since: v0.2.00 """ AbstractHook.__init__(self) self.context_id = "dNG.pas.tasks.Context" """ Default timeout for an activated task """ self.independent_scheduling = False """ Usually queues are filled and executed as long as new tasks of the same type arrive. Set this variable to true to reschedule these tasks independently. """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.max_retry_delay = Settings.get("pas_global_tasks_lrt_retry_delay_max", 120) """ Maximum delay for rescheduled tasks """ self.min_retry_delay = Settings.get("pas_global_tasks_lrt_retry_delay_min", 10) """ Minimum delay for rescheduled tasks """ self.params = None """ Task parameters """ self.tid = None """
def __init__(self): """ Constructor __init__(ControlPoint) :since: v0.2.00 """ AbstractTimed.__init__(self) self.bootid = 0 """ UPnP bootId value (bootid.upnp.org); nextbootid.upnp.org += 1 """ self.configid = 0 """ UPnP configId value (configid.upnp.org) """ self.devices = { } """ List of devices with its services """ self.gena = None """ UPnP GENA manager """ self.http_host = None """ HTTP Accept-Language value """ self.http_language = (L10n.get("lang_rfc_region") if (L10n.is_defined("lang_rfc_region")) else None) """ HTTP Accept-Language value """ self.http_port = None """ HTTP Accept-Language value """ self.listener_ipv4 = None """ Unicast IPv4 listener """ self.listener_port = int(Settings.get("pas_upnp_device_port", 1900)) """ Unicast port in the range 49152-65535 (searchport.upnp.org) """ self.listeners_multicast = { } """ Multicast listeners """ self.listeners_multicast_ipv4 = 0 """ Number of IPv4 multicast listeners """ self.listeners_multicast_ipv6 = 0 """ Number of IPv6 multicast listeners """ self.managed_devices = { } """ List of managed devices """ self.rootdevices = [ ] """ List of UPnP root devices """ self.tasks = [ ] """ List of tasks (e.g. timed out services) to run """ self.upnp_desc = { } """ Received UPnP descriptions """ self.upnp_desc_unread = { } """ Unread UPnP description URLs """ self.usns = { } """ List of devices with its services """ Settings.read_file("{0}/settings/pas_upnp.json".format(Settings.get("path_data"))) self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) if (self.http_language is None): system_language = getlocale()[0] http_language = (Settings.get("core_lang", "en_US") if (system_language is None or system_language == "c") else system_language ) http_language = http_language.replace("_", "") http_language = re.sub("\\W", "", http_language) else: http_language = self.http_language.replace("_", "") if (Settings.is_defined("core_lang_{0}".format(http_language))): http_language = Settings.get("core_lang_{0}".format(http_language)) elif (Settings.is_defined("core_lang_{0}".format(http_language[:2]))): http_language = Settings.get("core_lang_{0}".format(http_language[:2])) lang_iso_domain = http_language[:2] if (len(http_language) > 2): self.http_language = "{0}-{1}".format(http_language[:2], http_language[2:]) else: self.http_language = http_language self.http_language += ", {0}".format(lang_iso_domain) if (lang_iso_domain != "en"): self.http_language += ", en-US, en"
def __init__(self, listener_socket, active_handler, threads_active = 5, queue_handler = None, threads_queued = 10, thread_stopping_hook = None): """ Constructor __init__(Dispatcher) :param listener_socket: Listener socket :param active_handler: Thread to be used for activated connections :param threads_active: Allowed simultaneous threads :param queue_handler: Thread to be used for queued connections :param threads_queued: Allowed queued threads :param thread_stopping_hook: Thread stopping hook definition :since: v0.2.00 """ asyncore.dispatcher.__init__(self, sock = listener_socket) self.active = False """ Listener state """ self.active_handler = (active_handler if (issubclass(active_handler, Handler)) else None) """ Active queue handler """ self.actives = None """ Active counter """ self.actives_list = [ ] """ Active queue """ self.listener_handle_connections = (listener_socket.type & socket.SOCK_STREAM == socket.SOCK_STREAM) """ Listener socket """ self.listener_socket = listener_socket """ Listener socket """ self.listener_startup_timeout = 45 """ Listener startup timeout """ self.local = None """ Local data handle """ self._lock = InstanceLock() """ Thread safety lock """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.queue_handler = (queue_handler if (isinstance(queue_handler, Handler)) else None) """ Passive queue handler """ self.queue_max = threads_queued """ Passive queue maximum """ self.stopping_hook = ("" if (thread_stopping_hook is None) else thread_stopping_hook) """ Stopping hook definition """ self.thread = None """ Thread if started and active """ self.waiting = 0 """ Thread safety lock """ self.actives = BoundedSemaphore(threads_active if (self.listener_handle_connections) else 1)
def _thread_run(self): """ Active conversation :since: v0.2.00 """ if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}._thread_run()- (#echo(__LINE__)#)", self, context = "pas_upnp") ssdp_data = self.get_data(65535) headers = (None if (ssdp_data == "") else HttpClient.get_headers(ssdp_data)) ssdp_request = None if (headers is not None and "@http" in headers): ssdp_request_data = headers['@http'].split(" ", 2) if (len(ssdp_request_data) > 2 and ssdp_request_data[2].startswith("HTTP/")): ssdp_request = ssdp_request_data[0].upper() ssdp_request_path = ssdp_request_data[1] http_version = float(ssdp_request_data[2].split("/", 1)[1]) # # if (ssdp_request == "NOTIFY" and ssdp_request_path == "*" and "NT" in headers and "NTS" in headers and "USN" in headers): bootid = (int(headers['BOOTID.UPNP.ORG']) if ("BOOTID.UPNP.ORG" in headers) else None) configid = (int(headers['CONFIGID.UPNP.ORG']) if ("CONFIGID.UPNP.ORG" in headers) else None) control_point = NamedLoader.get_singleton("dNG.net.upnp.ControlPoint") user_agent = headers.get("SERVER") client_settings = ClientSettings(user_agent) if (client_settings.get("ssdp_notify_use_filter", False)): headers_filtered = Hook.call("dNG.pas.upnp.SsdpRequest.filterHeaders", headers = headers, user_agent = user_agent) if (headers_filtered is not None): headers = headers_filtered # if (headers['NTS'] == "ssdp:alive" or headers['NTS'] == "ssdp:update"): if ("CACHE-CONTROL" in headers and "LOCATION" in headers and "SERVER" in headers): bootid_old = None if (headers['NTS'] == "ssdp:update"): bootid = (int(headers['NEXTBOOTID.UPNP.ORG']) if ("NEXTBOOTID.UPNP.ORG" in headers) else None) bootid_old = (int(headers['BOOTID.UPNP.ORG']) if ("BOOTID.UPNP.ORG" in headers) else None) # re_result = SsdpRequest.RE_HEADER_MAX_AGE.search(headers['CACHE-CONTROL']) unicast_port = (int(headers['SEARCHPORT.UPNP.ORG']) if ("SEARCHPORT.UPNP.ORG" in headers) else None) if (re_result is not None): control_point.update_usn(headers['SERVER'], headers['USN'], bootid, bootid_old, configid, int(re_result.group(2)), unicast_port, http_version, headers['LOCATION'], headers) elif (self.log_handler is not None): self.log_handler.debug("{0!r} ignored broken NOTIFY CACHE-CONTROL '{1}'", self, headers['CACHE-CONTROL'], context = "pas_upnp") elif (self.log_handler is not None): self.log_handler.debug("{0!r} ignored incomplete NOTIFY {1!r}", self, headers, context = "pas_upnp") elif (headers['NTS'] == "ssdp:byebye"): control_point.delete_usn(headers['USN'], bootid, configid, headers) elif (self.log_handler is not None): self.log_handler.debug("{0!r} received unknown NOTIFY {1!r}", self, headers, context = "pas_upnp") elif (ssdp_request == "M-SEARCH" and ssdp_request_path == "*" and "MAN" in headers and headers['MAN'].strip("\"") == "ssdp:discover" and "ST" in headers): wait_timeout = (int(headers['MX']) if ("MX" in headers) else 1) if (wait_timeout > 5): wait_timeout = 5 ssdp_search_class = NamedLoader.get_class("dNG.net.upnp.SsdpSearch") ssdp_search_class.handle_request(self.address, wait_timeout, headers['ST'], headers)
def __init__(self, app_config_prefix = "pas_bus"): """ Constructor __init__(Client) :since: v0.3.00 """ self.connected = False """ Connection ready flag """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.socket = None """ Socket instance """ self.timeout = int(Settings.get("pas_bus_socket_data_timeout", 0)) """ Request timeout value """ if (self.timeout < 1): self.timeout = int(Settings.get("pas_global_client_socket_data_timeout", 0)) if (self.timeout < 1): self.timeout = int(Settings.get("pas_global_socket_data_timeout", 30)) listener_address = Settings.get("{0}_listener_address".format(app_config_prefix)) listener_mode = Settings.get("{0}_listener_mode".format(app_config_prefix)) if (listener_mode == "ipv6"): listener_mode = socket.AF_INET6 elif (listener_mode == "ipv4"): listener_mode = socket.AF_INET try: if (listener_mode is None or listener_mode == "unixsocket"): listener_mode = socket.AF_UNIX if (listener_address is None): listener_address = "/tmp/dNG.pas.socket" elif (listener_address is None): listener_address = "localhost:8135" except AttributeError: listener_mode = socket.AF_INET listener_address = "localhost:8135" # re_result = re.search("^(.+):(\\d+)$", listener_address) if (re_result is None): listener_host = listener_address listener_port = None else: listener_host = re_result.group(1) listener_port = int(re_result.group(2)) # listener_host = Binary.str(listener_host) if ((listener_mode == socket.AF_INET) or (listener_mode == socket.AF_INET6)): if (self.log_handler is not None): self.log_handler.debug("{0!r} connects to '{1}:{2:d}'", self, listener_host, listener_port, context = "pas_bus") listener_data = ( listener_host, listener_port ) elif (listener_mode == socket.AF_UNIX): if (self.log_handler is not None): self.log_handler.debug("{0!r} connects to '{1}'", self, listener_host, context = "pas_bus") listener_data = path.normpath(listener_host) # self.socket = socket.socket(listener_mode, socket.SOCK_STREAM) self.socket.settimeout(self.timeout) self.socket.connect(listener_data) self.connected = True
def __init__(self): """ Constructor __init__(Gstreamer) :since: v0.2.00 """ Abstract.__init__(self) CallbackContextMixin.__init__(self) self.discovery_timeout = 10 """ Processing may take some time. Wait for this amount of seconds. """ self._instance_lock = InstanceLock() """ Thread safety lock """ self.pipeline = None """ GStreamer pipeline in use """ self._glib_mainloop = None """ GObject mainloop """ self.local = local() """ Local data handle """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.metadata = None """ Cached metadata instance """ self.source_url = None """ GStreamer source URI """ Settings.read_file("{0}/settings/pas_gapi_gstreamer.json".format(Settings.get("path_data"))) Settings.read_file("{0}/settings/pas_gapi_gstreamer_caps.json".format(Settings.get("path_data"))) Settings.read_file("{0}/settings/pas_gapi_gstreamer_mimetypes.json".format(Settings.get("path_data"))) with Gstreamer._lock: gst_debug_enabled = Settings.get("pas_gapi_gstreamer_debug_enabled", False) if (Gstreamer.debug_mode != gst_debug_enabled): Gst.debug_set_default_threshold(Gst.DebugLevel.DEBUG if (gst_debug_enabled) else Gst.DebugLevel.NONE) Gstreamer.debug_mode = gst_debug_enabled # # discovery_timeout = float(Settings.get("pas_gapi_gstreamer_discovery_timeout", 0)) if (discovery_timeout > 0): self.discovery_timeout = discovery_timeout self.start()
def __init__(self): """ Constructor __init__(Resource) :since: v0.2.00 """ ClientSettingsMixin.__init__(self) SupportsMixin.__init__(self) self.content = None """ UPnP resource content cache """ self.content_limit = None """ UPnP resource content limit """ self.content_offset = 0 """ UPnP resource content offset """ self.deleted = False """ True if underlying resource has been deleted """ self.didl_fields = None """ UPnP resource DIDL fields to be returned """ self.didl_res_protocol = None """ UPnP resource DIDL protocolInfo value """ self._lock = ThreadLock() """ Thread safety lock """ self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False) """ The LogHandler is called whenever debug messages should be logged or errors happened. """ self.mimeclass = None """ UPnP resource mime class """ self.mimetype = None """ UPnP resource mime type """ self.name = None """ UPnP resource name """ self.parent_resource_id = None """ UPnP resource parent ID """ self.resource_id = None """ UPnP resource ID """ self.searchable = False """ True if the UPnP resource provides the search method """ self.size = None """ UPnP resource size in bytes """ self.sort_criteria = [ ] """ UPnP resource sort criteria requested """ self.source = None """ UPnP resource source or creator """ self.symlink_target_id = None """ UPnP resource symlinked ID """ self.timestamp = -1 """ UPnP resource's timestamp """ self.type = None """ UPnP resource type """ self.type_name = None """ UPnP resource type name """ self.updatable = False """ True if the resource is writable """ self.supported_features['vfs_url'] = self._supports_vfs_url