XMMS2
main.c
Go to the documentation of this file.
1 /* XMMS2 - X Music Multiplexer System
2  * Copyright (C) 2003-2009 XMMS2 Team
3  *
4  * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  */
16 
17 /**
18  * @mainpage
19  * @image html pixmaps/xmms2-128.png
20  */
21 
22 /** @file
23  * This file controls the XMMS2 main loop.
24  */
25 
26 #include <locale.h>
27 #include <glib.h>
28 
29 #include "xmms_configuration.h"
30 #include "xmmsc/xmmsc_util.h"
31 #include "xmmspriv/xmms_plugin.h"
32 #include "xmmspriv/xmms_config.h"
33 #include "xmmspriv/xmms_playlist.h"
35 #include "xmmspriv/xmms_signal.h"
36 #include "xmmspriv/xmms_symlink.h"
38 #include "xmmspriv/xmms_medialib.h"
39 #include "xmmspriv/xmms_output.h"
40 #include "xmmspriv/xmms_ipc.h"
41 #include "xmmspriv/xmms_log.h"
42 #include "xmmspriv/xmms_sqlite.h"
43 #include "xmmspriv/xmms_xform.h"
44 #include "xmmspriv/xmms_bindata.h"
45 #include "xmmspriv/xmms_utils.h"
47 
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <signal.h>
53 #include <sys/stat.h>
54 #include <fcntl.h>
55 
56 /*
57  * Forward declarations of the methods in the main object
58  */
59 static void xmms_main_client_quit (xmms_object_t *object, xmms_error_t *error);
60 static GTree *xmms_main_client_stats (xmms_object_t *object, xmms_error_t *error);
61 static GList *xmms_main_client_plugin_list (xmms_object_t *main, gint32 type, xmms_error_t *err);
62 static void xmms_main_client_hello (xmms_object_t *object, gint protocolver, const gchar *client, xmms_error_t *error);
63 static void install_scripts (const gchar *into_dir);
64 static void spawn_script_setup (gpointer data);
65 static xmms_xform_object_t *xform_obj;
66 static xmms_bindata_t *bindata_obj;
67 
68 XMMS_CMD_DEFINE (quit, xmms_main_client_quit, xmms_object_t*, NONE, NONE, NONE);
69 XMMS_CMD_DEFINE (hello, xmms_main_client_hello, xmms_object_t *, NONE, INT32, STRING);
70 XMMS_CMD_DEFINE (stats, xmms_main_client_stats, xmms_object_t *, DICT, NONE, NONE);
71 XMMS_CMD_DEFINE (plugin_list, xmms_main_client_plugin_list, xmms_object_t *, LIST, INT32, NONE);
72 
73 /** @defgroup XMMSServer XMMSServer
74  * @brief look at this if you want to code inside the server.
75  * The XMMS2 project is split into a server and a multiple clients.
76  * This documents the server part.
77  */
78 
79 /**
80  * @defgroup Main Main
81  * @ingroup XMMSServer
82  * @brief main object
83  * @{
84  */
85 
86 
87 /**
88  * Main object, when this is unreffed, XMMS2 is quiting.
89  */
90 struct xmms_main_St {
91  xmms_object_t object;
92  xmms_output_t *output;
94  time_t starttime;
95 };
96 
97 typedef struct xmms_main_St xmms_main_t;
98 
99 /** This is the mainloop of the xmms2 server */
100 static GMainLoop *mainloop;
101 
102 /** The path of the configfile */
103 static gchar *conffile = NULL;
104 
105 /**
106  * This returns the main stats for the server
107  */
108 static GTree *
109 xmms_main_client_stats (xmms_object_t *object, xmms_error_t *error)
110 {
111  GTree *ret;
112  gint starttime;
113 
114  ret = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
115  NULL, (GDestroyNotify) xmmsv_unref);
116 
117  starttime = ((xmms_main_t*)object)->starttime;
118 
119  g_tree_insert (ret, (gpointer) "version",
120  xmmsv_new_string (XMMS_VERSION));
121  g_tree_insert (ret, (gpointer) "uptime",
122  xmmsv_new_int (time (NULL) - starttime));
123 
124  return ret;
125 }
126 
127 static gboolean
128 xmms_main_client_list_foreach (xmms_plugin_t *plugin, gpointer data)
129 {
130  xmmsv_t *dict;
131  GList **list = data;
132 
133  dict = xmmsv_build_dict (
134  XMMSV_DICT_ENTRY_STR ("name", xmms_plugin_name_get (plugin)),
135  XMMSV_DICT_ENTRY_STR ("shortname", xmms_plugin_shortname_get (plugin)),
136  XMMSV_DICT_ENTRY_STR ("version", xmms_plugin_version_get (plugin)),
137  XMMSV_DICT_ENTRY_STR ("description", xmms_plugin_description_get (plugin)),
138  XMMSV_DICT_ENTRY_INT ("type", xmms_plugin_type_get (plugin)),
140 
141  *list = g_list_prepend (*list, dict);
142 
143  return TRUE;
144 }
145 
146 static GList *
147 xmms_main_client_plugin_list (xmms_object_t *main, gint32 type, xmms_error_t *err)
148 {
149  GList *list = NULL;
150  xmms_plugin_foreach (type, xmms_main_client_list_foreach, &list);
151  return list;
152 }
153 
154 
155 /**
156  * @internal Execute all programs or scripts in a directory. Used when starting
157  * up and shutting down the daemon.
158  *
159  * @param[in] scriptdir Directory to search for executable programs/scripts.
160  * started.
161  * @param arg1 value passed to executed scripts as argument 1. This makes
162  * it possible to handle start and stop in one script
163  */
164 static void
165 do_scriptdir (const gchar *scriptdir, const gchar *arg1)
166 {
167  GError *err = NULL;
168  GDir *dir;
169  const gchar *f;
170  gchar *argv[3] = {NULL, NULL, NULL};
171 
172  XMMS_DBG ("Running scripts in %s", scriptdir);
173  if (!g_file_test (scriptdir, G_FILE_TEST_IS_DIR)) {
174  g_mkdir_with_parents (scriptdir, 0755);
175  install_scripts (scriptdir);
176  }
177 
178  dir = g_dir_open (scriptdir, 0, &err);
179  if (!dir) {
180  xmms_log_error ("Could not open script dir '%s' error: %s", scriptdir, err->message);
181  return;
182  }
183 
184  argv[1] = g_strdup (arg1);
185  while ((f = g_dir_read_name (dir))) {
186  argv[0] = g_strdup_printf ("%s/%s", scriptdir, f);
187  if (g_file_test (argv[0], G_FILE_TEST_IS_EXECUTABLE)) {
188  if (!g_spawn_async (g_get_home_dir (), argv, NULL, 0,
189  spawn_script_setup, NULL, NULL, &err)) {
190  xmms_log_error ("Could not run script '%s', error: %s",
191  argv[0], err->message);
192  }
193  }
194  g_free (argv[0]);
195  }
196  g_free (argv[1]);
197 
198  g_dir_close (dir);
199 
200 }
201 
202 /**
203  * @internal Setup function for processes spawned by do_scriptdir
204  */
205 static void
206 spawn_script_setup (gpointer data)
207 {
209 }
210 
211 /**
212  * @internal Load the xmms2d configuration file. Creates the config directory
213  * if needed.
214  */
215 static void
216 load_config (void)
217 {
218  gchar configdir[XMMS_PATH_MAX];
219 
220  if (!conffile) {
221  conffile = XMMS_BUILD_PATH ("xmms2.conf");
222  }
223 
224  g_assert (strlen (conffile) <= XMMS_MAX_CONFIGFILE_LEN);
225 
226  if (!xmms_userconfdir_get (configdir, sizeof (configdir))) {
227  xmms_log_error ("Could not get path to config dir");
228  } else if (!g_file_test (configdir, G_FILE_TEST_IS_DIR)) {
229  g_mkdir_with_parents (configdir, 0755);
230  }
231 
232  xmms_config_init (conffile);
233 }
234 
235 /**
236  * @internal Switch to using another output plugin
237  * @param object An object
238  * @param data The name of the output plugin to switch to
239  * @param userdata The #xmms_main_t object
240  */
241 static void
242 change_output (xmms_object_t *object, xmmsv_t *_data, gpointer userdata)
243 {
244  xmms_output_plugin_t *plugin;
245  xmms_main_t *mainobj = (xmms_main_t*)userdata;
246  const gchar *outname;
247 
248  if (!mainobj->output)
249  return;
250 
252 
253  xmms_log_info ("Switching to output %s", outname);
254 
256  if (!plugin) {
257  xmms_log_error ("Baaaaad output plugin, try to change the output.plugin config variable to something useful");
258  } else {
259  if (!xmms_output_plugin_switch (mainobj->output, plugin)) {
260  xmms_log_error ("Baaaaad output plugin, try to change the output.plugin config variable to something useful");
261  }
262  }
263 }
264 
265 /**
266  * @internal Destroy the main object
267  * @param[in] object The object to destroy
268  */
269 static void
270 xmms_main_destroy (xmms_object_t *object)
271 {
272  xmms_main_t *mainobj = (xmms_main_t *) object;
275 
276  cv = xmms_config_lookup ("core.shutdownpath");
277  do_scriptdir (xmms_config_property_get_string (cv), "stop");
278 
279  /* stop output */
281 
282  xmms_object_cmd_call (XMMS_OBJECT (mainobj->output),
283  XMMS_IPC_CMD_STOP, &arg);
284 
285  g_usleep (G_USEC_PER_SEC); /* wait for the output thread to end */
286 
287  xmms_object_unref (mainobj->vis);
288  xmms_object_unref (mainobj->output);
289 
290  xmms_object_unref (xform_obj);
291 
292  xmms_config_save ();
293 
295 
297 
300 
302 }
303 
304 /**
305  * @internal Function to respond to the 'hello' sent from clients on connect
306  */
307 static void
308 xmms_main_client_hello (xmms_object_t *object, gint protocolver, const gchar *client, xmms_error_t *error)
309 {
310  if (protocolver != XMMS_IPC_PROTOCOL_VERSION) {
311  xmms_log_info ("Client '%s' with bad protocol version (%d, not %d) connected", client, protocolver, XMMS_IPC_PROTOCOL_VERSION);
312  xmms_error_set (error, XMMS_ERROR_INVAL, "Bad protocol version");
313  return;
314  }
315  XMMS_DBG ("Client '%s' connected", client);
316 }
317 
318 static gboolean
319 kill_server (gpointer object) {
323  time (NULL)-((xmms_main_t*)object)->starttime);
324 
325  xmms_object_unref (object);
326 
327  exit (EXIT_SUCCESS);
328 }
329 
330 
331 /**
332  * @internal Function to respond to the 'quit' command sent from a client
333  */
334 static void
335 xmms_main_client_quit (xmms_object_t *object, xmms_error_t *error)
336 {
337  /*
338  * to be able to return from this method
339  * we add a timeout that will kill the server
340  * very "ugly"
341  */
342  g_timeout_add (1, kill_server, object);
343 }
344 
345 static void
346 install_scripts (const gchar *into_dir)
347 {
348  GDir *dir;
349  GError *err = NULL;
350  gchar path[XMMS_PATH_MAX];
351  const gchar *f;
352  gchar *s;
353 
354  s = strrchr (into_dir, G_DIR_SEPARATOR);
355  if (!s)
356  return;
357 
358  s++;
359 
360  g_snprintf (path, XMMS_PATH_MAX, "%s/scripts/%s", SHAREDDIR, s);
361  xmms_log_info ("Installing scripts from %s", path);
362  dir = g_dir_open (path, 0, &err);
363  if (!dir) {
364  xmms_log_error ("Global script directory not found");
365  return;
366  }
367 
368  while ((f = g_dir_read_name (dir))) {
369  gchar *source = g_strdup_printf ("%s/%s", path, f);
370  gchar *dest = g_strdup_printf ("%s/%s", into_dir, f);
371  if (!xmms_symlink_file (source, dest)) {
372  g_free (source);
373  g_free (dest);
374  break;
375  }
376  g_free (source);
377  g_free (dest);
378  }
379 
380  g_dir_close (dir);
381 }
382 
383 /**
384  * Just print version and quit
385  */
386 static void
387 print_version (void)
388 {
389  printf ("XMMS2 version " XMMS_VERSION "\n");
390  printf ("Copyright (C) 2003-2009 XMMS2 Team\n");
391  printf ("This is free software; see the source for copying conditions.\n");
392  printf ("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n");
393  printf ("PARTICULAR PURPOSE.\n");
394  printf (" Using glib version %d.%d.%d (compiled against "
395  G_STRINGIFY (GLIB_MAJOR_VERSION) "."
396  G_STRINGIFY (GLIB_MINOR_VERSION) "."
397  G_STRINGIFY (GLIB_MICRO_VERSION) ")\n",
398  glib_major_version,
399  glib_minor_version,
400  glib_micro_version);
402 
403  exit (EXIT_SUCCESS);
404 }
405 
406 /**
407  * The xmms2 daemon main initialisation function
408  */
409 int
410 main (int argc, char **argv)
411 {
412  xmms_output_plugin_t *o_plugin;
414  xmms_main_t *mainobj;
415  int loglevel = 1;
416  xmms_playlist_t *playlist;
417  gchar default_path[XMMS_PATH_MAX + 16], *tmp;
418  gboolean verbose = FALSE;
419  gboolean quiet = FALSE;
420  gboolean version = FALSE;
421  gboolean nologging = FALSE;
422  gboolean runasroot = FALSE;
423  gboolean showhelp = FALSE;
424  const gchar *outname = NULL;
425  const gchar *ipcpath = NULL;
426  gchar *ppath = NULL;
427  int status_fd = -1;
428  GOptionContext *context = NULL;
429  GError *error = NULL;
430 
431  setlocale (LC_ALL, "");
432 
433  /**
434  * The options that the server accepts.
435  */
436  GOptionEntry opts[] = {
437  {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Increase verbosity", NULL},
438  {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Decrease verbosity", NULL},
439  {"version", 'V', 0, G_OPTION_ARG_NONE, &version, "Print version", NULL},
440  {"no-logging", 'n', 0, G_OPTION_ARG_NONE, &nologging, "Disable logging", NULL},
441  {"output", 'o', 0, G_OPTION_ARG_STRING, &outname, "Use 'x' as output plugin", "<x>"},
442  {"ipc-socket", 'i', 0, G_OPTION_ARG_FILENAME, &ipcpath, "Listen to socket 'url'", "<url>"},
443  {"plugindir", 'p', 0, G_OPTION_ARG_FILENAME, &ppath, "Search for plugins in directory 'foo'", "<foo>"},
444  {"conf", 'c', 0, G_OPTION_ARG_FILENAME, &conffile, "Specify alternate configuration file", "<file>"},
445  {"status-fd", 's', 0, G_OPTION_ARG_INT, &status_fd, "Specify a filedescriptor to write to when started", "fd"},
446  {"yes-run-as-root", 0, 0, G_OPTION_ARG_NONE, &runasroot, "Give me enough rope to shoot myself in the foot", NULL},
447  {"show-help", 'h', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &showhelp, "Use --help or -? instead", NULL},
448  {NULL}
449  };
450 
451  /** Check that we are running against the correct glib version */
452  if (glib_major_version != GLIB_MAJOR_VERSION ||
453  glib_minor_version < GLIB_MINOR_VERSION) {
454  g_print ("xmms2d is build against version %d.%d,\n"
455  "but is (runtime) linked against %d.%d.\n"
456  "Refusing to start.\n",
457  GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION,
458  glib_major_version, glib_minor_version);
459  exit (EXIT_FAILURE);
460  }
461 
463 
464  context = g_option_context_new ("- XMMS2 Daemon");
465  g_option_context_add_main_entries (context, opts, NULL);
466  if (!g_option_context_parse (context, &argc, &argv, &error) || error) {
467  g_print ("Error parsing options: %s\n", error->message);
468  g_clear_error (&error);
469  exit (EXIT_FAILURE);
470  }
471  if (showhelp) {
472 #if GLIB_CHECK_VERSION(2,14,0)
473  g_print ("%s", g_option_context_get_help (context, TRUE, NULL));
474  exit (EXIT_SUCCESS);
475 #else
476  g_print ("Please use --help or -? for help\n");
477  exit (EXIT_FAILURE);
478 #endif
479  }
480  g_option_context_free (context);
481 
482  if (argc != 1) {
483  g_print ("There were unknown options, aborting!\n");
484  exit (EXIT_FAILURE);
485  }
486 
487  if (xmms_checkroot ()) {
488  if (runasroot) {
489  g_print ("***************************************\n");
490  g_print ("Warning! You are running XMMS2D as root, this is a bad idea!\nBut I'll allow it since you asked nicely.\n");
491  g_print ("***************************************\n\n");
492  } else {
493  g_print ("PLEASE DON'T RUN XMMS2D AS ROOT!\n\n(if you really must, read the help)\n");
494  exit (EXIT_FAILURE);
495  }
496  }
497 
498  if (verbose) {
499  loglevel++;
500  } else if (quiet) {
501  loglevel--;
502  }
503 
504  if (version) {
505  print_version ();
506  }
507 
508  g_thread_init (NULL);
509 
510  g_random_set_seed (time (NULL));
511 
512  xmms_log_init (loglevel);
513  xmms_ipc_init ();
514 
515  load_config ();
516 
517  cv = xmms_config_property_register ("core.logtsfmt",
518  "%H:%M:%S ",
519  NULL, NULL);
520 
522 
523  xmms_fallback_ipcpath_get (default_path, sizeof (default_path));
524 
525  cv = xmms_config_property_register ("core.ipcsocket",
526  default_path,
528  NULL);
529 
530  if (!ipcpath) {
531  /*
532  * if not ipcpath is specifed on the cmd line we
533  * grab it from the config
534  */
535  ipcpath = xmms_config_property_get_string (cv);
536  }
537 
538  if (!xmms_ipc_setup_server (ipcpath)) {
540  xmms_log_fatal ("IPC failed to init!");
541  }
542 
543  if (!xmms_plugin_init (ppath)) {
544  return 1;
545  }
546 
547  playlist = xmms_playlist_init ();
548  xform_obj = xmms_xform_object_init ();
549  bindata_obj = xmms_bindata_init ();
550 
551  mainobj = xmms_object_new (xmms_main_t, xmms_main_destroy);
552 
553  /* find output plugin. */
554  cv = xmms_config_property_register ("output.plugin",
555  XMMS_OUTPUT_DEFAULT,
556  change_output, mainobj);
557 
558  if (outname) {
559  xmms_config_property_set_data (cv, outname);
560  }
561 
562  outname = xmms_config_property_get_string (cv);
563  xmms_log_info ("Using output plugin: %s", outname);
565  if (!o_plugin) {
566  xmms_log_error ("Baaaaad output plugin, try to change the"
567  "output.plugin config variable to something useful");
568  }
569 
570  mainobj->output = xmms_output_new (o_plugin, playlist);
571  if (!mainobj->output) {
572  xmms_log_fatal ("Failed to create output object!");
573  }
574 
575  mainobj->vis = xmms_visualization_new (mainobj->output);
576 
577  if (status_fd != -1) {
578  write (status_fd, "+", 1);
579  }
580 
581  xmms_signal_init (XMMS_OBJECT (mainobj));
582 
584  XMMS_OBJECT (mainobj));
585 
588 
589  xmms_object_cmd_add (XMMS_OBJECT (mainobj),
591  XMMS_CMD_FUNC (quit));
592  xmms_object_cmd_add (XMMS_OBJECT (mainobj),
594  XMMS_CMD_FUNC (hello));
595  xmms_object_cmd_add (XMMS_OBJECT (mainobj),
597  XMMS_CMD_FUNC (plugin_list));
598  xmms_object_cmd_add (XMMS_OBJECT (mainobj),
600  XMMS_CMD_FUNC (stats));
601 
602  /* Save the time we started in order to count uptime */
603  mainobj->starttime = time (NULL);
604 
605  /* Dirty hack to tell XMMS_PATH a valid path */
606  g_strlcpy (default_path, ipcpath, sizeof (default_path));
607 
608  tmp = strchr (default_path, ';');
609  if (tmp) {
610  *tmp = '\0';
611  }
612 
613  g_setenv ("XMMS_PATH", default_path, TRUE);
614 
615  /* Also put the full path for clients that understands */
616  g_setenv("XMMS_PATH_FULL", ipcpath, TRUE);
617 
618  tmp = XMMS_BUILD_PATH ("shutdown.d");
619  cv = xmms_config_property_register ("core.shutdownpath",
620  tmp, NULL, NULL);
621  g_free (tmp);
622 
623  tmp = XMMS_BUILD_PATH ("startup.d");
624  cv = xmms_config_property_register ("core.startuppath",
625  tmp, NULL, NULL);
626  g_free (tmp);
627 
628  /* Startup dir */
629  do_scriptdir (xmms_config_property_get_string (cv), "start");
630 
631  mainloop = g_main_loop_new (NULL, FALSE);
632 
633  g_main_loop_run (mainloop);
634 
635  return 0;
636 }
637 
638 /** @} */