diff options
author | Jens Georg <mail@jensge.org> | 2022-04-30 12:09:57 +0000 |
---|---|---|
committer | Jens Georg <mail@jensge.org> | 2022-04-30 12:09:57 +0000 |
commit | 5b4656072bfb507d8b97c0b62eb16e9750e31996 (patch) | |
tree | bb364b33c25cd3c7ebfb68b3d968ef5fd202c063 /src | |
parent | 2bbdbb1b47ad3a7e6cd710ca15ceb5b287d1e7d1 (diff) | |
download | rygel-5b4656072bfb507d8b97c0b62eb16e9750e31996.tar.gz |
Port Rygel binary to GApplication
Diffstat (limited to 'src')
-rw-r--r-- | src/librygel-core/rygel-cmdline-config.vala | 296 | ||||
-rw-r--r-- | src/rygel/application.vala | 315 | ||||
-rw-r--r-- | src/rygel/meson.build | 3 |
3 files changed, 404 insertions, 210 deletions
diff --git a/src/librygel-core/rygel-cmdline-config.vala b/src/librygel-core/rygel-cmdline-config.vala index d16996fb..6c41b9d9 100644 --- a/src/librygel-core/rygel-cmdline-config.vala +++ b/src/librygel-core/rygel-cmdline-config.vala @@ -36,70 +36,42 @@ public errordomain Rygel.CmdlineConfigError { * Manages configuration from Commandline arguments. */ public class Rygel.CmdlineConfig : GLib.Object, Configuration { - [CCode (array_length = false, array_null_terminated = true)] - private static string[] ifaces; - private static int port; - - private static bool no_transcoding; - - private static bool disallow_upload; - private static bool disallow_deletion; - - private static string log_levels; - - private static string plugin_path; - private static string engine_path; - - private static bool version; - - private static string config_file; - - private static bool shutdown; - private static bool replace; - - [CCode (array_length = false, array_null_terminated = true)] - private static string[] disabled_plugins; - [CCode (array_length = false, array_null_terminated = true)] - private static string[] plugin_titles; - [CCode (array_length = false, array_null_terminated = true)] - private static string[] plugin_options; + private VariantDict options; // Our singleton private static CmdlineConfig config; // Command-line options - const OptionEntry[] OPTIONS = { - { "version", 0, 0, OptionArg.NONE, ref version, + public const OptionEntry[] OPTIONS = { + { "version", 'v', 0, OptionArg.NONE, null, N_("Display version number"), null }, - { "network-interface", 'n', 0, OptionArg.STRING_ARRAY, ref ifaces, + { "network-interface", 'n', 0, OptionArg.STRING_ARRAY, null, N_("Network Interfaces"), "INTERFACE" }, - { "port", 'p', 0, OptionArg.INT, ref port, + { "port", 'p', 0, OptionArg.INT, null, N_("Port"), "PORT" }, - { "disable-transcoding", 't', 0, OptionArg.NONE, ref no_transcoding, + { "disable-transcoding", 't', 0, OptionArg.NONE, null, N_("Disable transcoding"), null }, { "disallow-upload", 'U', 0, OptionArg.NONE, - ref disallow_upload, N_("Disallow upload"), null }, + null, N_("Disallow upload"), null }, { "disallow-deletion", 'D', 0, OptionArg.NONE, - ref disallow_deletion, N_ ("Disallow deletion"), null }, - { "log-level", 'g', 0, OptionArg.STRING, ref log_levels, + null, N_ ("Disallow deletion"), null }, + { "log-level", 'g', 0, OptionArg.STRING, null, N_ ("Comma-separated list of domain:level pairs. See rygel(1) for details") }, - { "plugin-path", 'u', 0, OptionArg.STRING, ref plugin_path, + { "plugin-path", 'u', 0, OptionArg.STRING, null, N_ ("Plugin Path"), "PLUGIN_PATH" }, - { "engine-path", 'e', 0, OptionArg.STRING, ref engine_path, + { "engine-path", 'e', 0, OptionArg.STRING, null, N_ ("Engine Path"), "ENGINE_PATH" }, { "disable-plugin", 'd', 0, OptionArg.STRING_ARRAY, - ref disabled_plugins, + null, N_ ("Disable plugin"), "PluginName" }, - { "title", 'i', 0, OptionArg.STRING_ARRAY, ref plugin_titles, + { "title", 'i', 0, OptionArg.STRING_ARRAY, null, N_ ("Set plugin titles"), "PluginName:TITLE" }, - { "plugin-option", 'o', 0, OptionArg.STRING_ARRAY, ref plugin_options, + { "plugin-option", 'o', 0, OptionArg.STRING_ARRAY, null, N_ ("Set plugin options"), "PluginName:OPTION:VALUE1[,VALUE2,..]" }, - { "config", 'c', 0, OptionArg.FILENAME, ref config_file, + { "config", 'c', 0, OptionArg.FILENAME, null, N_ ("Use configuration file instead of user configuration"), "FILE" }, - { "shutdown", 's', 0, OptionArg.NONE, ref shutdown, + { "shutdown", 's', 0, OptionArg.NONE, null, N_ ("Shut down remote Rygel reference"), null }, - { "replace", 'r', 0, OptionArg.NONE, ref replace, - N_ ("Replace currently running instance of Rygel"), null }, { null } }; @@ -111,62 +83,18 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { return config; } - public static void parse_args (ref unowned string[] args) - throws CmdlineConfigError.VERSION_ONLY, - OptionError { - var parameter_string = "- " + BuildConfig.PACKAGE_NAME; - var opt_context = new OptionContext (parameter_string); - opt_context.set_help_enabled (true); - opt_context.set_ignore_unknown_options (true); - opt_context.add_main_entries (OPTIONS, null); - - try { - opt_context.parse (ref args); - } catch (OptionError.BAD_VALUE err) { - stdout.printf (opt_context.get_help (true, null)); - - throw new CmdlineConfigError.VERSION_ONLY (""); - } - - if (version) { - stdout.printf ("%s\n", BuildConfig.PACKAGE_STRING); - - throw new CmdlineConfigError.VERSION_ONLY (""); - } - - if (shutdown || replace) { - try { - print (_("Shutting down remote Rygel instance\n")); - DBusInterface rygel = Bus.get_proxy_sync - (BusType.SESSION, - DBusInterface.SERVICE_NAME, - DBusInterface.OBJECT_PATH, - DBusProxyFlags.DO_NOT_LOAD_PROPERTIES); - rygel.shutdown (); - } catch (Error error) { - warning (_("Failed to shut down other Rygel instance: %s"), - error.message); - - } - - // If user wanted to shut down, just exit. - if (shutdown) { - throw new CmdlineConfigError.VERSION_ONLY (""); - } - } + public void set_options (VariantDict args) { + this.options = args; } public string get_interface () throws GLib.Error { - if (ifaces == null) { - throw new ConfigurationError.NO_VALUE_SET (_("No value available")); - } - - return ifaces[0]; + return get_interfaces ()[0]; } [CCode (array_length=false, array_null_terminated = true)] public string[] get_interfaces () throws GLib.Error { - if (ifaces == null) { + string[] ifaces = null; + if (!this.options.lookup ("network-interface", "^as", out ifaces)) { throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } @@ -174,7 +102,8 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { } public int get_port () throws GLib.Error { - if (port <= 0) { + int port = 0; + if (!this.options.lookup ("port", "i", out port)) { throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } @@ -182,7 +111,8 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { } public bool get_transcoding () throws GLib.Error { - if (!no_transcoding) { + bool val; + if (!this.options.lookup ("disable-transcoding", "b", out val)) { throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } else { return false; @@ -190,7 +120,8 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { } public bool get_allow_upload () throws GLib.Error { - if (!disallow_upload) { + bool val; + if (!this.options.lookup ("disable-transcoding", "b", out val)) { throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } else { return false; @@ -198,7 +129,8 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { } public bool get_allow_deletion () throws GLib.Error { - if (!disallow_deletion) { + bool val; + if (!this.options.lookup ("disable-transcoding", "b", out val)) { throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } else { return false; @@ -206,7 +138,8 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { } public string get_log_levels () throws GLib.Error { - if (log_levels == null) { + unowned string log_levels = null; + if (!options.lookup ("log-level", "&s", out log_levels)) { throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } @@ -214,7 +147,8 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { } public string get_plugin_path () throws GLib.Error { - if (plugin_path == null) { + unowned string plugin_path = null; + if (!options.lookup ("plugin-path", "&s", out plugin_path)) { throw new ConfigurationError.NO_VALUE_SET ("No value available"); } @@ -222,11 +156,12 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { } public string get_engine_path () throws GLib.Error { - if (engine_path == null) { + unowned string engine_path = null; + if (!options.lookup ("engine-path", "&s", out engine_path)) { throw new ConfigurationError.NO_VALUE_SET ("No value available"); } - return plugin_path; + return engine_path; } public string get_media_engine () throws GLib.Error { @@ -234,43 +169,47 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { throw new ConfigurationError.NO_VALUE_SET ("No value available"); } - public bool get_enabled (string section) throws GLib.Error { - var disabled = false; - foreach (var plugin in disabled_plugins) { - if (plugin == section) { - disabled = true; - break; - } - } - if (disabled) { - return false; - } else { + // Work-around to make vala aware of the null-termination + [CCode (array_length=false, array_null_terminated = true)] + private string[] get_string_list_from_options (string key) throws GLib.Error { + string[] disabled_plugins = null; + + if (!options.lookup (key, "^as", out disabled_plugins)) { throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } + + return disabled_plugins; + } + + public bool get_enabled (string section) throws GLib.Error { + foreach (var plugin in get_string_list_from_options ("disable-plugin")) { + print ("Checking %s against %s\n", section, plugin); + if (section == plugin) + return false; + } + + throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } public string get_title (string section) throws GLib.Error { - string title = null; - foreach (var plugin_title in plugin_titles) { - var tokens = plugin_title.split (":", 2); + var plugin_titles = this.get_string_list_from_options ("plugin-title"); + + foreach (var entry in plugin_titles) { + var tokens = entry.split (":", 2); if (tokens[0] != null && tokens[1] != null && tokens[0] == section) { - title = tokens[1]; - break; + return tokens[1]; } } - if (title != null) { - return title; - } else { - throw new ConfigurationError.NO_VALUE_SET (_("No value available")); - } + throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } public string get_config_file () throws GLib.Error { - if (config_file == null) { + unowned string config_file = null; + if (!options.lookup ("config", "&s", out config_file)) { throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } @@ -293,7 +232,8 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { // FIXME: How to handle them? public string get_string (string section, string key) throws GLib.Error { - string value = null; + var plugin_options = this.get_string_list_from_options ("plugin-option"); + foreach (var option in plugin_options) { var tokens = option.split (":", 3); if (tokens[0] != null && @@ -301,42 +241,22 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { tokens[2] != null && tokens[0] == section && tokens[1] == key) { - value = tokens[2]; - break; + return tokens[2]; } } - if (value != null) { - return value; - } else { - throw new ConfigurationError.NO_VALUE_SET (_("No value available")); - } + throw new ConfigurationError.NO_VALUE_SET (_("No value available")); } public Gee.ArrayList<string> get_string_list (string section, string key) throws GLib.Error { - ArrayList<string> value = null; - foreach (var option in plugin_options) { - var tokens = option.split (":", 3); - if (tokens[0] != null && - tokens[1] != null && - tokens[2] != null && - tokens[0] == section && - tokens[1] == key) { - value = new ArrayList<string> (); - foreach (var val_token in tokens[2].split (",", -1)) { - value.add (val_token); - } - break; - } + var val = new ArrayList<string> (); + foreach (var val_token in this.get_string (section, key).split (",", -1)) { + val.add (val_token); } - if (value != null) { - return value; - } else { - throw new ConfigurationError.NO_VALUE_SET (_("No value available")); - } + return val; } public int get_int (string section, @@ -344,78 +264,38 @@ public class Rygel.CmdlineConfig : GLib.Object, Configuration { int min, int max) throws GLib.Error { - int value = 0; - bool value_set = false; - foreach (var option in plugin_options) { - var tokens = option.split (":", 3); - if (tokens[0] != null && - tokens[1] != null && - tokens[2] != null && - tokens[0] == section && - tokens[1] == key) { - value = int.parse (tokens[2]); - if (value >= min && value <= max) { - value_set = true; - } - break; - } + int result; + + if (!int.try_parse (this.get_string (section, key), out result)) { + throw new ConfigurationError.VALUE_OUT_OF_RANGE (_("No value available")); } - if (value_set) { - return value; - } else { - throw new ConfigurationError.NO_VALUE_SET (_("No value available")); + if (result < min || result > max) { + throw new ConfigurationError.VALUE_OUT_OF_RANGE (_("No value available")); } + + return result; } public Gee.ArrayList<int> get_int_list (string section, string key) - throws GLib.Error { - ArrayList<int> value = null; - foreach (var option in plugin_options) { - var tokens = option.split (":", 3); - if (tokens[0] != null && - tokens[1] != null && - tokens[2] != null && - tokens[0] == section && - tokens[1] == key) { - value = new ArrayList<int> (); - foreach (var val_token in tokens[2].split (",", -1)) { - value.add (int.parse (val_token)); - } - break; + throws GLib.Error { + var val = new ArrayList<int> (); + foreach (var val_token in this.get_string (section, key).split (",", -1)) { + int result; + if (!int.try_parse (val_token, out result)) { + throw new ConfigurationError.VALUE_OUT_OF_RANGE (_("No value available")); } - } - if (value != null) { - return value; - } else { - throw new ConfigurationError.NO_VALUE_SET (_("No value available")); + val.add (result); } + + return val; } public bool get_bool (string section, string key) throws GLib.Error { - bool value = false; - bool value_set = false; - foreach (var option in plugin_options) { - var tokens = option.split (":", 3); - if (tokens[0] != null && - tokens[1] != null && - tokens[2] != null && - tokens[0] == section && - tokens[1] == key) { - value = bool.parse (tokens[2]); - value_set = true; - break; - } - } - - if (value_set) { - return value; - } else { - throw new ConfigurationError.NO_VALUE_SET (_("No value available")); - } + return bool.parse (this.get_string (section, key)); } } diff --git a/src/rygel/application.vala b/src/rygel/application.vala new file mode 100644 index 00000000..1f30d2a8 --- /dev/null +++ b/src/rygel/application.vala @@ -0,0 +1,315 @@ +using Gee; +using GUPnP; + +public class Rygel.Application : GLib.Application { + // Default time to wait for plugins showing up + private static int PLUGIN_TIMEOUT = 5; + + private PluginLoader plugin_loader; + private ContextManager context_manager; + private ArrayList <RootDeviceFactory> factories; + private ArrayList <RootDevice> root_devices; + + private Configuration config; + private LogHandler log_handler; + private Acl acl; + + private bool activation_pending = false; + + public Application() { + Object(application_id : "org.gnome.Rygel", + flags : ApplicationFlags.HANDLES_COMMAND_LINE | + ApplicationFlags.ALLOW_REPLACEMENT); + + this.add_main_option_entries (CmdlineConfig.OPTIONS); + + Unix.signal_add (ProcessSignal.INT, () => { this.release (); return false; }); + Unix.signal_add (ProcessSignal.TERM, () => { this.release (); return false; }); + Unix.signal_add (ProcessSignal.HUP, () => { this.release (); return false; }); + } + + public override int handle_local_options (VariantDict options) { + int count; + if (options.lookup ("version", "b", out count)) { + print ("%s\n", BuildConfig.PACKAGE_STRING); + + return 0; + } + + // Further options to be handled remotely + return -1; + } + + public override int command_line (GLib.ApplicationCommandLine command_line) { + var options = command_line.get_options_dict (); + if (options.contains ("shutdown")) { + release (); + + return 0; + } + + CmdlineConfig.get_default ().set_options (options); + + activate (); + + return -1; + } + + private void register_default_configurations () { + + var cmdline_config = CmdlineConfig.get_default (); + + MetaConfig.register_configuration (cmdline_config); + MetaConfig.register_configuration (EnvironmentConfig.get_default ()); + + try { + var config_file = cmdline_config.get_config_file (); + var user_config = new UserConfig (config_file); + MetaConfig.register_configuration (user_config); + } catch (Error error) { + try { + var user_config = UserConfig.get_default (); + MetaConfig.register_configuration (user_config); + } catch (Error err) { + warning (_("Failed to load user configuration: %s"), err.message); + } + } + } + + private void run_everything () { + this.register_default_configurations (); + + this.log_handler = LogHandler.get_default (); + this.config = MetaConfig.get_default (); + this.plugin_loader = new PluginLoader (); + this.root_devices = new ArrayList <RootDevice> (); + this.factories = new ArrayList <RootDeviceFactory> (); + this.acl = new Acl (); + + this.plugin_loader.plugin_available.connect (this.on_plugin_loaded); + this.context_manager = this.create_context_manager (); + this.plugin_loader.load_modules (); + this.activation_pending = false; + + var timeout = PLUGIN_TIMEOUT; + try { + var config = MetaConfig.get_default (); + timeout = config.get_int ("plugin", + "TIMEOUT", + 0, + int.MAX); + } catch (Error error) {}; + + if (timeout == 0) { + debug ("Plugin timeout disabled..."); + + return; + } + + Timeout.add_seconds (timeout, () => { + if (this.plugin_loader.list_plugins ().size == 0) { + warning (ngettext ("No plugins found in %d second; giving up…", + "No plugins found in %d seconds; giving up…", + PLUGIN_TIMEOUT), + PLUGIN_TIMEOUT); + this.release (); + } + + return false; + }); + } + + public override void activate () { + base.activate (); + if (this.context_manager == null || this.activation_pending) { + hold (); + if (ApplicationFlags.REPLACE in this.flags) { + this.activation_pending = true; + // Delay context manager creation to give the other instance a chance to + // give up the socket + Timeout.add_seconds (1, () => { this.run_everything (); return false; }); + } else { + this.run_everything (); + } + } + } + + public override void startup () { + base.startup (); + + message (_("Rygel v%s starting…"), BuildConfig.PACKAGE_VERSION); + } + + public override void shutdown () { + this.root_devices = null; + base.shutdown (); + } + + public override bool name_lost () { + this.root_devices = null; + this.release (); + + return true; + } + + private void on_plugin_loaded (PluginLoader plugin_loader, + Plugin plugin) { + var iterator = this.factories.iterator (); + while (iterator.next ()) { + this.create_device.begin (plugin, iterator.get ()); + } + } + + private async void create_device (Plugin plugin, + RootDeviceFactory factory) { + // The call to factory.create(), although synchronous spins the + // the mainloop and therefore might in turn triger some of the signal + // handlers here that modify one of the lists that we might be iterating + // while this function is called. Modification of an ArrayList while + // iterating it is currently unsuppored and leads to a crash and that is + // why defer to mainloop here. + Idle.add (create_device.callback); + yield; + + try { + var device = factory.create (plugin); + + device.available = plugin.active; + + // Due to pure evilness of unix sinals this might actually happen + // if someone shuts down rygel while the call-back is running, + // leading to a crash on shutdown + if (this.root_devices != null) { + this.root_devices.add (device); + + plugin.notify["active"].connect (this.on_plugin_active_notify); + } + } catch (GLib.Error error) { + warning (_("Failed to create RootDevice for %s. Reason: %s"), + plugin.name, + error.message); + } + } + + private void on_plugin_active_notify (Object obj, + ParamSpec spec) { + if (unlikely (this.root_devices == null)) { + return; + } + + var plugin = obj as Plugin; + + foreach (var device in this.root_devices) { + if (device.resource_factory == plugin) { + device.available = plugin.active; + } + } + } + + + private ContextManager create_context_manager () { + int port = 0; + bool ipv6 = false; + + try { + port = this.config.get_port (); + } catch (GLib.Error err) {} + + try { + ipv6 = this.config.get_bool ("general", "ipv6"); + } catch (GLib.Error err) { + debug ("No ipv6 config key found, using default %s", ipv6.to_string ()); + } + + // INVALID means "all" + var family = GLib.SocketFamily.INVALID; + if (!ipv6) { + family = GLib.SocketFamily.IPV4; + } + + var manager = ContextManager.create_full (GSSDP.UDAVersion.VERSION_1_0, + family, + port); + + manager.context_available.connect (this.on_context_available); + manager.context_unavailable.connect (this.on_context_unavailable); + + return manager; + } + + private void on_context_available (GUPnP.ContextManager manager, + GUPnP.Context context) { + string[] ifaces = null; + + debug ("New network %s (%s) context available. IP: %s", + context.network, + context.interface, + context.host_ip); + + context.acl = this.acl; + + try { + ifaces = this.config.get_interfaces (); + } catch (GLib.Error err) { + } + + if (ifaces == null || + context.interface in ifaces || + context.network in ifaces || + context.host_ip in ifaces) { + try { + var factory = new RootDeviceFactory (context); + this.factories.add (factory); + + var iterator = this.plugin_loader.list_plugins ().iterator (); + while (iterator.next ()) { + this.create_device.begin (iterator.get (), factory); + } + } catch (GLib.Error err) { + warning (_("Failed to create root device factory: %s"), + err.message); + } + } else { + debug ("Ignoring network %s (%s) context.", + context.network, + context.interface); + context.active = false; + } + } + + private void on_context_unavailable (GUPnP.ContextManager manager, + GUPnP.Context context) { + debug ("Network %s (%s) context now unavailable. IP: %s", + context.network, + context.interface, + context.host_ip); + + var factory_iter = this.factories.iterator (); + while (factory_iter.next ()) { + if (context == factory_iter.get ().context) { + factory_iter.remove (); + } + } + + var device_iter = this.root_devices.iterator (); + while (device_iter.next ()) { + if (context == device_iter.get ().context) { + device_iter.remove (); + } + } + } + + public static int main(string[] args) { + Environment.set_application_name (_(BuildConfig.PACKAGE_NAME)); + + Intl.setlocale (LocaleCategory.ALL, ""); + Intl.bindtextdomain (BuildConfig.GETTEXT_PACKAGE, + BuildConfig.LOCALEDIR); + Intl.bind_textdomain_codeset (BuildConfig.GETTEXT_PACKAGE, "UTF-8"); + Intl.textdomain (BuildConfig.GETTEXT_PACKAGE); + + Rygel.Application app = new Rygel.Application (); + + return app.run (args); + } +} diff --git a/src/rygel/meson.build b/src/rygel/meson.build index a4b55079..b7f91e20 100644 --- a/src/rygel/meson.build +++ b/src/rygel/meson.build @@ -1,7 +1,6 @@ rygel_sources = [ 'rygel-acl.vala', - 'rygel-dbus-service.vala', - 'rygel-main.vala' + 'application.vala' ] executable('rygel', rygel_sources, |