= Upmpdcli Mediaserver notes A Media Server instance is created by the main upmpdcli process if a call to ContentDirectory::mediaServerNeeded() returns true, which occurs if the root directory of the Content Directory would not be empty (see further for how this is determined). Depending on the command line option, the Media Server can be as a separate device/process (the default, which also allows to run a Media Server only, with no Renderer device), or as an embedded device of the main Media Renderer device. Very few Control Points can handle the latter approach, and the code was kept mostly because we can. == ContentDirectory.cxx This file contains the libupnpp callbacks, and the root directory creation code, together with plugin activation and dispatch code. === Root directory creation It was originally thought that this ContentDirectory.cxx would see several different provider modules, so that it would be natural that it builds the root directory (with top entries for each of the modules). What happened finally is that there is only one provider module, plgwithslave.cxx, so that the root directory creation code, which has knowledge of its internals should probably moved to plgwithslave.cxx: if other modules were added, we'd need an interface so that they each can provide their entries for the root dir. As it is, things can stay this way. ContentDirectory::makerootdir(), which should be in plgwithslave, uses the plgwithslave plugin names to create root entries. The plugin names are currently used in quite a few places. For a module named 'plgname': - As the name of a subdirectory under cdplugins, used to enumerate the plugins on startup. - As the name of the main plugin Python module, named cdplugins/plgname/plgname-app.py - As an element of the object ids belonging to this module (all beginning with 0$plgname$) - As an element of the resource URIs generated by the module (all beginning with /plgname). On startup makerootdir() walks the cdplugins directory (skipping pycommon). For each subdirectory, it decides that the module is configured if there is a variable named 'plgnameuser' in the upmpdcli configuration file (so even plugins without users should have such a variable). If the plugin is configured, makerootdir() creates an entry named Plgname (capitalized), with objid 0$plgname$ === Plugin activation and dispatch When the browse method is called for an object Id beginning with 0$plgname$, the code looks up (Internal::pluginforapp(), and possibly creates (Internal::pluginfactory()) the plugin. Currently, this is always a PlgWithSlave object, created with 2 parameters: the service name (which it will use to find and exec the python module), and a service interface which it can use to request information from the ContentDirectory. The knowledge that all of a plugin's object ids must begin with 0$plgname$ is spread in multiple places in the C++ and Python code. === ContentDirectory service interface ContentDirectory provides a number of service methods to its providers. They are defined in cdplugins/cdplugin.hxx:CDPluginServices, an instance of which is passed to the plugin constructor. See the file for more details. === Notes At the moment, the interface between ContentDirectory and the plgwithslave plugin is a bit of a mess: - There is a lack of interfaces which would be needed if other modules than plgwithslaves were added. - There is too much knowledge of the plgwithslave internals in ContentDirectory. - Both problems are linked. - Probably plgwithslave should provide the root entries, and ContentDirectory would just keep a mapping of object id to plugin. But, things are ok, even if a bit difficult to understand (hence this doc), as long as all providers are modules under plgwithslave. libupnp addr and port: these are retrieved in the ContentDirectory init, and the plugins get it through service interface calls (getupnpaddr/port()) The host part is then communicated to the subprocesses through the environment (UPMPD_HTTPHOSTPORT), but the port part is not used: the microhttpd port is used instead. The port part would have been used if we had been using the libupnp miniserver, which we don't because microhttpd can do redirects. The uprcl local media server plugin does not use UPMPD_HTTPHOSTPORT at all, because it uses its own HTTP server (started on uprclhostport) == HTTP server The resource URLs are initially generated by the plugins, and possibly transformed when they are actually requested. There are 3 possibilities for the actual HTTP server: - The libupnp miniserver (unused at this time) - An internal microhttpd instance (only doing redirects at this time) - An external server. The 3 streaming service modules (tidal/qobuz/gmusic) currently work in the same way: - They use the port supplied from plgwithslave which is the one on which the microhttpd instance is listening. The URIs are like http://host:port/someplugin/xxx.mpr?trackId=tid - The microhttpd request method only does redirects. When it gets a connection, it parses the above url, passes it to the appropriate plugin (as per the path), which provides the actual streaming service URL, to which the client is redirected. An hypothetical client which would not handle redirects would be out of luck. - No configuration data is required (the microhttpd port could be configured with plgmicrohttpport, default 49149). The miniserver port value which the plgwithslave constructor receives and stores in upnpport is not used, as the miniserver itself The local media server plugin uses a separate http service, which began as a completely external server (e.g. an Apache server), but is now more conveniently the embedded Python server. The URLS which are generated in the directory resource records point directly to the HTTP server host/port, there is no redirection through the microhttpd server (this is the big difference with the streaming service plugins. The paths are in three parts: - The plugin name as prefix - A mapping between Recoll (actual) paths and paths as seen by the http server (document_root equivalent) - The real tail The translation routine is now part of the Python request handler, but would need to be restored if we went back to using an external server. The following configuration data is used: The host and port for the HTTP server. This is used in generated URLs and as parameter to start the Python server: uprclhost = 192.168.4.4:8080 Paths translation between the Recoll file paths (as used for topdirs) and the paths relative to the HTTP server document root, e.g.: uprclpaths = /y/av/mp3:/mp3 The path map was really useful with Apache, much less so with the internal server, but has been kept as a way to define the file system area allowed for access: any path not in the map will be rejected. In the first implementation, the external HTTP server had to be separately configured to serve the media directories. The internal server uses the config data.