XMMS2
src/xmms/main.c
Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2011 XMMS2 Team
00003  *
00004  *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2.1 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  */
00016 
00017 /**
00018  * @mainpage
00019  * @image html pixmaps/xmms2-128.png
00020  */
00021 
00022 /** @file
00023  * This file controls the XMMS2 main loop.
00024  */
00025 
00026 #include <locale.h>
00027 #include <glib.h>
00028 
00029 #include "xmms_configuration.h"
00030 #include "xmmsc/xmmsc_util.h"
00031 #include "xmmspriv/xmms_plugin.h"
00032 #include "xmmspriv/xmms_config.h"
00033 #include "xmmspriv/xmms_playlist.h"
00034 #include "xmmspriv/xmms_collection.h"
00035 #include "xmmspriv/xmms_signal.h"
00036 #include "xmmspriv/xmms_symlink.h"
00037 #include "xmmspriv/xmms_checkroot.h"
00038 #include "xmmspriv/xmms_thread_name.h"
00039 #include "xmmspriv/xmms_medialib.h"
00040 #include "xmmspriv/xmms_output.h"
00041 #include "xmmspriv/xmms_ipc.h"
00042 #include "xmmspriv/xmms_log.h"
00043 #include "xmmspriv/xmms_sqlite.h"
00044 #include "xmmspriv/xmms_xform.h"
00045 #include "xmmspriv/xmms_bindata.h"
00046 #include "xmmspriv/xmms_utils.h"
00047 #include "xmmspriv/xmms_visualization.h"
00048 
00049 #include <stdio.h>
00050 #include <stdlib.h>
00051 #include <string.h>
00052 #include <unistd.h>
00053 #include <signal.h>
00054 #include <sys/stat.h>
00055 #include <fcntl.h>
00056 
00057 /*
00058  * Forward declarations of the methods in the main object
00059  */
00060 static void xmms_main_client_quit (xmms_object_t *object, xmms_error_t *error);
00061 static GTree *xmms_main_client_stats (xmms_object_t *object, xmms_error_t *error);
00062 static GList *xmms_main_client_list_plugins (xmms_object_t *main, gint32 type, xmms_error_t *err);
00063 static void xmms_main_client_hello (xmms_object_t *object, gint protocolver, const gchar *client, xmms_error_t *error);
00064 static void install_scripts (const gchar *into_dir);
00065 static void spawn_script_setup (gpointer data);
00066 static xmms_xform_object_t *xform_obj;
00067 static xmms_bindata_t *bindata_obj;
00068 
00069 #include "main_ipc.c"
00070 
00071 /** @defgroup XMMSServer XMMSServer
00072   * @brief look at this if you want to code inside the server.
00073   * The XMMS2 project is split into a server and a multiple clients.
00074   * This documents the server part.
00075   */
00076 
00077 /**
00078   * @defgroup Main Main
00079   * @ingroup XMMSServer
00080   * @brief main object
00081   * @{
00082   */
00083 
00084 
00085 /**
00086  * Main object, when this is unreffed, XMMS2 is quiting.
00087  */
00088 struct xmms_main_St {
00089     xmms_object_t object;
00090     xmms_output_t *output;
00091     xmms_visualization_t *vis;
00092     time_t starttime;
00093 };
00094 
00095 typedef struct xmms_main_St xmms_main_t;
00096 
00097 /** This is the mainloop of the xmms2 server */
00098 static GMainLoop *mainloop;
00099 
00100 /** The path of the configfile */
00101 static gchar *conffile = NULL;
00102 
00103 /**
00104  * This returns the main stats for the server
00105  */
00106 static GTree *
00107 xmms_main_client_stats (xmms_object_t *object, xmms_error_t *error)
00108 {
00109     GTree *ret;
00110     gint starttime;
00111 
00112     ret = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
00113                            NULL, (GDestroyNotify) xmmsv_unref);
00114 
00115     starttime = ((xmms_main_t*)object)->starttime;
00116 
00117     g_tree_insert (ret, (gpointer) "version",
00118                    xmmsv_new_string (XMMS_VERSION));
00119     g_tree_insert (ret, (gpointer) "uptime",
00120                    xmmsv_new_int (time (NULL) - starttime));
00121 
00122     return ret;
00123 }
00124 
00125 static gboolean
00126 xmms_main_client_list_foreach (xmms_plugin_t *plugin, gpointer data)
00127 {
00128     xmmsv_t *dict;
00129     GList **list = data;
00130 
00131     dict = xmmsv_build_dict (
00132             XMMSV_DICT_ENTRY_STR ("name", xmms_plugin_name_get (plugin)),
00133             XMMSV_DICT_ENTRY_STR ("shortname", xmms_plugin_shortname_get (plugin)),
00134             XMMSV_DICT_ENTRY_STR ("version", xmms_plugin_version_get (plugin)),
00135             XMMSV_DICT_ENTRY_STR ("description", xmms_plugin_description_get (plugin)),
00136             XMMSV_DICT_ENTRY_INT ("type", xmms_plugin_type_get (plugin)),
00137             XMMSV_DICT_END);
00138 
00139     *list = g_list_prepend (*list, dict);
00140 
00141     return TRUE;
00142 }
00143 
00144 static GList *
00145 xmms_main_client_list_plugins (xmms_object_t *main, gint32 type, xmms_error_t *err)
00146 {
00147     GList *list = NULL;
00148     xmms_plugin_foreach (type, xmms_main_client_list_foreach, &list);
00149     return list;
00150 }
00151 
00152 
00153 /**
00154  * @internal Execute all programs or scripts in a directory. Used when starting
00155  * up and shutting down the daemon.
00156  *
00157  * @param[in] scriptdir Directory to search for executable programs/scripts.
00158  * started.
00159  * @param     arg1 value passed to executed scripts as argument 1. This makes
00160  * it possible to handle start and stop in one script
00161  */
00162 static void
00163 do_scriptdir (const gchar *scriptdir, const gchar *arg1)
00164 {
00165     GError *err = NULL;
00166     GDir *dir;
00167     const gchar *f;
00168     gchar *argv[3] = {NULL, NULL, NULL};
00169 
00170     XMMS_DBG ("Running scripts in %s", scriptdir);
00171     if (!g_file_test (scriptdir, G_FILE_TEST_IS_DIR)) {
00172         g_mkdir_with_parents (scriptdir, 0755);
00173         install_scripts (scriptdir);
00174     }
00175 
00176     dir = g_dir_open (scriptdir, 0, &err);
00177     if (!dir) {
00178         xmms_log_error ("Could not open script dir '%s' error: %s", scriptdir, err->message);
00179         return;
00180     }
00181 
00182     argv[1] = g_strdup (arg1);
00183     while ((f = g_dir_read_name (dir))) {
00184         argv[0] = g_strdup_printf ("%s/%s", scriptdir, f);
00185         if (g_file_test (argv[0], G_FILE_TEST_IS_EXECUTABLE)) {
00186             if (!g_spawn_async (g_get_home_dir (), argv, NULL, 0,
00187                                 spawn_script_setup, NULL, NULL, &err)) {
00188                 xmms_log_error ("Could not run script '%s', error: %s",
00189                                 argv[0], err->message);
00190             }
00191         }
00192         g_free (argv[0]);
00193     }
00194     g_free (argv[1]);
00195 
00196     g_dir_close (dir);
00197 
00198 }
00199 
00200 /**
00201  * @internal Setup function for processes spawned by do_scriptdir
00202  */
00203 static void
00204 spawn_script_setup (gpointer data)
00205 {
00206     xmms_signal_restore ();
00207 }
00208 
00209 /**
00210  * @internal Load the xmms2d configuration file. Creates the config directory
00211  * if needed.
00212  */
00213 static void
00214 load_config (void)
00215 {
00216     gchar configdir[XMMS_PATH_MAX];
00217 
00218     if (!conffile) {
00219         conffile = XMMS_BUILD_PATH ("xmms2.conf");
00220     }
00221 
00222     g_assert (strlen (conffile) <= XMMS_MAX_CONFIGFILE_LEN);
00223 
00224     if (!xmms_userconfdir_get (configdir, sizeof (configdir))) {
00225         xmms_log_error ("Could not get path to config dir");
00226     } else if (!g_file_test (configdir, G_FILE_TEST_IS_DIR)) {
00227         g_mkdir_with_parents (configdir, 0755);
00228     }
00229 
00230     xmms_config_init (conffile);
00231 }
00232 
00233 /**
00234  * @internal Switch to using another output plugin
00235  * @param object An object
00236  * @param data The name of the output plugin to switch to
00237  * @param userdata The #xmms_main_t object
00238  */
00239 static void
00240 change_output (xmms_object_t *object, xmmsv_t *_data, gpointer userdata)
00241 {
00242     xmms_output_plugin_t *plugin;
00243     xmms_main_t *mainobj = (xmms_main_t*)userdata;
00244     const gchar *outname;
00245 
00246     if (!mainobj->output)
00247         return;
00248 
00249     outname = xmms_config_property_get_string ((xmms_config_property_t *) object);
00250 
00251     xmms_log_info ("Switching to output %s", outname);
00252 
00253     plugin = (xmms_output_plugin_t *)xmms_plugin_find (XMMS_PLUGIN_TYPE_OUTPUT, outname);
00254     if (!plugin) {
00255         xmms_log_error ("Baaaaad output plugin, try to change the output.plugin config variable to something useful");
00256     } else {
00257         if (!xmms_output_plugin_switch (mainobj->output, plugin)) {
00258             xmms_log_error ("Baaaaad output plugin, try to change the output.plugin config variable to something useful");
00259         }
00260     }
00261 }
00262 
00263 /**
00264  * @internal Destroy the main object
00265  * @param[in] object The object to destroy
00266  */
00267 static void
00268 xmms_main_destroy (xmms_object_t *object)
00269 {
00270     xmms_main_t *mainobj = (xmms_main_t *) object;
00271     xmms_object_cmd_arg_t arg;
00272     xmms_config_property_t *cv;
00273 
00274     cv = xmms_config_lookup ("core.shutdownpath");
00275     do_scriptdir (xmms_config_property_get_string (cv), "stop");
00276 
00277     /* stop output */
00278     xmms_object_cmd_arg_init (&arg);
00279     arg.args = xmmsv_new_list ();
00280     xmms_object_cmd_call (XMMS_OBJECT (mainobj->output),
00281                           XMMS_IPC_CMD_STOP, &arg);
00282     xmmsv_unref (arg.args);
00283 
00284     g_usleep (G_USEC_PER_SEC); /* wait for the output thread to end */
00285 
00286     xmms_object_unref (mainobj->vis);
00287     xmms_object_unref (mainobj->output);
00288 
00289     xmms_object_unref (xform_obj);
00290 
00291     xmms_config_save ();
00292 
00293     xmms_config_shutdown ();
00294 
00295     xmms_plugin_shutdown ();
00296 
00297     xmms_main_unregister_ipc_commands ();
00298 
00299     xmms_ipc_shutdown ();
00300 
00301     xmms_log_shutdown ();
00302 }
00303 
00304 /**
00305  * @internal Function to respond to the 'hello' sent from clients on connect
00306  */
00307 static void
00308 xmms_main_client_hello (xmms_object_t *object, gint protocolver, const gchar *client, xmms_error_t *error)
00309 {
00310     if (protocolver != XMMS_IPC_PROTOCOL_VERSION) {
00311         xmms_log_info ("Client '%s' with bad protocol version (%d, not %d) connected", client, protocolver, XMMS_IPC_PROTOCOL_VERSION);
00312         xmms_error_set (error, XMMS_ERROR_INVAL, "Bad protocol version");
00313         return;
00314     }
00315     XMMS_DBG ("Client '%s' connected", client);
00316 }
00317 
00318 static gboolean
00319 kill_server (gpointer object) {
00320     xmms_object_emit_f (XMMS_OBJECT (object),
00321                         XMMS_IPC_SIGNAL_QUIT,
00322                         XMMSV_TYPE_INT32,
00323                         time (NULL)-((xmms_main_t*)object)->starttime);
00324 
00325     xmms_object_unref (object);
00326 
00327     exit (EXIT_SUCCESS);
00328 }
00329 
00330 
00331 /**
00332  * @internal Function to respond to the 'quit' command sent from a client
00333  */
00334 static void
00335 xmms_main_client_quit (xmms_object_t *object, xmms_error_t *error)
00336 {
00337     /*
00338      * to be able to return from this method
00339      * we add a timeout that will kill the server
00340      * very "ugly"
00341      */
00342     g_timeout_add (1, kill_server, object);
00343 }
00344 
00345 static void
00346 install_scripts (const gchar *into_dir)
00347 {
00348     GDir *dir;
00349     GError *err = NULL;
00350     gchar path[XMMS_PATH_MAX];
00351     const gchar *f;
00352     gchar *s;
00353 
00354     s = strrchr (into_dir, G_DIR_SEPARATOR);
00355     if (!s)
00356         return;
00357 
00358     s++;
00359 
00360     g_snprintf (path, XMMS_PATH_MAX, "%s/scripts/%s", SHAREDDIR, s);
00361     xmms_log_info ("Installing scripts from %s", path);
00362     dir = g_dir_open (path, 0, &err);
00363     if (!dir) {
00364         xmms_log_error ("Global script directory not found");
00365         return;
00366     }
00367 
00368     while ((f = g_dir_read_name (dir))) {
00369         gchar *source = g_strdup_printf ("%s/%s", path, f);
00370         gchar *dest = g_strdup_printf ("%s/%s", into_dir, f);
00371         if (!xmms_symlink_file (source, dest)) {
00372             g_free (source);
00373             g_free (dest);
00374             break;
00375         }
00376         g_free (source);
00377         g_free (dest);
00378     }
00379 
00380     g_dir_close (dir);
00381 }
00382 
00383 /**
00384  * Just print version and quit
00385  */
00386 static void
00387 print_version (void)
00388 {
00389     printf ("XMMS2 version " XMMS_VERSION "\n");
00390     printf ("Copyright (C) 2003-2011 XMMS2 Team\n");
00391     printf ("This is free software; see the source for copying conditions.\n");
00392     printf ("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n");
00393     printf ("PARTICULAR PURPOSE.\n");
00394     printf (" Using glib version %d.%d.%d (compiled against "
00395             G_STRINGIFY (GLIB_MAJOR_VERSION) "."
00396             G_STRINGIFY (GLIB_MINOR_VERSION) "."
00397             G_STRINGIFY (GLIB_MICRO_VERSION) ")\n",
00398             glib_major_version,
00399             glib_minor_version,
00400             glib_micro_version);
00401     xmms_sqlite_print_version ();
00402 
00403     exit (EXIT_SUCCESS);
00404 }
00405 
00406 /**
00407  * The xmms2 daemon main initialisation function
00408  */
00409 int
00410 main (int argc, char **argv)
00411 {
00412     xmms_output_plugin_t *o_plugin;
00413     xmms_config_property_t *cv;
00414     xmms_main_t *mainobj;
00415     int loglevel = 1;
00416     xmms_playlist_t *playlist;
00417     gchar default_path[XMMS_PATH_MAX + 16], *tmp;
00418     gboolean verbose = FALSE;
00419     gboolean quiet = FALSE;
00420     gboolean version = FALSE;
00421     gboolean runasroot = FALSE;
00422     gboolean showhelp = FALSE;
00423     const gchar *outname = NULL;
00424     const gchar *ipcpath = NULL;
00425     gchar *ppath = NULL;
00426     int status_fd = -1;
00427     GOptionContext *context = NULL;
00428     GError *error = NULL;
00429 
00430     setlocale (LC_ALL, "");
00431 
00432     /**
00433      * The options that the server accepts.
00434      */
00435     GOptionEntry opts[] = {
00436         {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Increase verbosity", NULL},
00437         {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Decrease verbosity", NULL},
00438         {"version", 'V', 0, G_OPTION_ARG_NONE, &version, "Print version", NULL},
00439         {"output", 'o', 0, G_OPTION_ARG_STRING, &outname, "Use 'x' as output plugin", "<x>"},
00440         {"ipc-socket", 'i', 0, G_OPTION_ARG_FILENAME, &ipcpath, "Listen to socket 'url'", "<url>"},
00441         {"plugindir", 'p', 0, G_OPTION_ARG_FILENAME, &ppath, "Search for plugins in directory 'foo'", "<foo>"},
00442         {"conf", 'c', 0, G_OPTION_ARG_FILENAME, &conffile, "Specify alternate configuration file", "<file>"},
00443         {"status-fd", 's', 0, G_OPTION_ARG_INT, &status_fd, "Specify a filedescriptor to write to when started", "fd"},
00444         {"yes-run-as-root", 0, 0, G_OPTION_ARG_NONE, &runasroot, "Give me enough rope to shoot myself in the foot", NULL},
00445         {"show-help", 'h', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &showhelp, "Use --help or -? instead", NULL},
00446         {NULL}
00447     };
00448 
00449     /** Check that we are running against the correct glib version */
00450     if (glib_major_version != GLIB_MAJOR_VERSION ||
00451         glib_minor_version < GLIB_MINOR_VERSION) {
00452         g_print ("xmms2d is build against version %d.%d,\n"
00453                  "but is (runtime) linked against %d.%d.\n"
00454                  "Refusing to start.\n",
00455                  GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION,
00456                  glib_major_version, glib_minor_version);
00457         exit (EXIT_FAILURE);
00458     }
00459 
00460     xmms_signal_block ();
00461 
00462     context = g_option_context_new ("- XMMS2 Daemon");
00463     g_option_context_add_main_entries (context, opts, NULL);
00464     if (!g_option_context_parse (context, &argc, &argv, &error) || error) {
00465         g_print ("Error parsing options: %s\n", error->message);
00466         g_clear_error (&error);
00467         exit (EXIT_FAILURE);
00468     }
00469     if (showhelp) {
00470 #if GLIB_CHECK_VERSION(2,14,0)
00471         g_print ("%s", g_option_context_get_help (context, TRUE, NULL));
00472         exit (EXIT_SUCCESS);
00473 #else
00474         g_print ("Please use --help or -? for help\n");
00475         exit (EXIT_FAILURE);
00476 #endif
00477     }
00478     g_option_context_free (context);
00479 
00480     if (argc != 1) {
00481         g_print ("There were unknown options, aborting!\n");
00482         exit (EXIT_FAILURE);
00483     }
00484 
00485     if (xmms_checkroot ()) {
00486         if (runasroot) {
00487             g_print ("***************************************\n");
00488             g_print ("Warning! You are running XMMS2D as root, this is a bad idea!\nBut I'll allow it since you asked nicely.\n");
00489             g_print ("***************************************\n\n");
00490         } else {
00491             g_print ("PLEASE DON'T RUN XMMS2D AS ROOT!\n\n(if you really must, read the help)\n");
00492             exit (EXIT_FAILURE);
00493         }
00494     }
00495 
00496     if (verbose) {
00497         loglevel++;
00498     } else if (quiet) {
00499         loglevel--;
00500     }
00501 
00502     if (version) {
00503         print_version ();
00504     }
00505 
00506     g_thread_init (NULL);
00507 
00508     g_random_set_seed (time (NULL));
00509 
00510     xmms_log_init (loglevel);
00511     xmms_ipc_init ();
00512 
00513     load_config ();
00514 
00515     cv = xmms_config_property_register ("core.logtsfmt",
00516                                         "%H:%M:%S ",
00517                                         NULL, NULL);
00518 
00519     xmms_log_set_format (xmms_config_property_get_string (cv));
00520 
00521     xmms_fallback_ipcpath_get (default_path, sizeof (default_path));
00522 
00523     cv = xmms_config_property_register ("core.ipcsocket",
00524                                         default_path,
00525                                         on_config_ipcsocket_change,
00526                                         NULL);
00527 
00528     if (!ipcpath) {
00529         /*
00530          * if not ipcpath is specifed on the cmd line we
00531          * grab it from the config
00532          */
00533         ipcpath = xmms_config_property_get_string (cv);
00534     }
00535 
00536     if (!xmms_ipc_setup_server (ipcpath)) {
00537         xmms_ipc_shutdown ();
00538         xmms_log_fatal ("IPC failed to init!");
00539     }
00540 
00541     if (!xmms_plugin_init (ppath)) {
00542         return 1;
00543     }
00544 
00545     playlist = xmms_playlist_init ();
00546     xform_obj = xmms_xform_object_init ();
00547     bindata_obj = xmms_bindata_init ();
00548 
00549     mainobj = xmms_object_new (xmms_main_t, xmms_main_destroy);
00550 
00551     /* find output plugin. */
00552     cv = xmms_config_property_register ("output.plugin",
00553                                         XMMS_OUTPUT_DEFAULT,
00554                                         change_output, mainobj);
00555 
00556     if (outname) {
00557         xmms_config_property_set_data (cv, outname);
00558     }
00559 
00560     outname = xmms_config_property_get_string (cv);
00561     xmms_log_info ("Using output plugin: %s", outname);
00562     o_plugin = (xmms_output_plugin_t *)xmms_plugin_find (XMMS_PLUGIN_TYPE_OUTPUT, outname);
00563     if (!o_plugin) {
00564         xmms_log_error ("Baaaaad output plugin, try to change the"
00565                         "output.plugin config variable to something useful");
00566     }
00567 
00568     mainobj->output = xmms_output_new (o_plugin, playlist);
00569     if (!mainobj->output) {
00570         xmms_log_fatal ("Failed to create output object!");
00571     }
00572 
00573     mainobj->vis = xmms_visualization_new (mainobj->output);
00574 
00575     if (status_fd != -1) {
00576         write (status_fd, "+", 1);
00577     }
00578 
00579     xmms_signal_init (XMMS_OBJECT (mainobj));
00580 
00581     xmms_main_register_ipc_commands (XMMS_OBJECT (mainobj));
00582 
00583     /* Save the time we started in order to count uptime */
00584     mainobj->starttime = time (NULL);
00585 
00586     /* Dirty hack to tell XMMS_PATH a valid path */
00587     g_strlcpy (default_path, ipcpath, sizeof (default_path));
00588 
00589     tmp = strchr (default_path, ';');
00590     if (tmp) {
00591         *tmp = '\0';
00592     }
00593 
00594     g_setenv ("XMMS_PATH", default_path, TRUE);
00595 
00596     /* Also put the full path for clients that understands */
00597     g_setenv("XMMS_PATH_FULL", ipcpath, TRUE);
00598 
00599     tmp = XMMS_BUILD_PATH ("shutdown.d");
00600     cv = xmms_config_property_register ("core.shutdownpath",
00601                                         tmp, NULL, NULL);
00602     g_free (tmp);
00603 
00604     tmp = XMMS_BUILD_PATH ("startup.d");
00605     cv = xmms_config_property_register ("core.startuppath",
00606                                         tmp, NULL, NULL);
00607     g_free (tmp);
00608 
00609     /* Startup dir */
00610     do_scriptdir (xmms_config_property_get_string (cv), "start");
00611 
00612     mainloop = g_main_loop_new (NULL, FALSE);
00613 
00614     g_main_loop_run (mainloop);
00615 
00616     return 0;
00617 }
00618 
00619 /** @} */