diff options
Diffstat (limited to 'src/Main.cxx')
-rw-r--r-- | src/Main.cxx | 453 |
1 files changed, 325 insertions, 128 deletions
diff --git a/src/Main.cxx b/src/Main.cxx index b45e2c3ae..26d4e7ae4 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,67 +23,87 @@ #include "CommandLine.hxx" #include "PlaylistFile.hxx" #include "PlaylistGlobal.hxx" -#include "UpdateGlue.hxx" #include "MusicChunk.hxx" #include "StateFile.hxx" #include "PlayerThread.hxx" #include "Mapper.hxx" -#include "DatabaseGlue.hxx" -#include "DatabaseSimple.hxx" #include "Permission.hxx" #include "Listen.hxx" -#include "Client.hxx" -#include "ClientList.hxx" +#include "client/Client.hxx" +#include "client/ClientList.hxx" #include "command/AllCommands.hxx" #include "Partition.hxx" -#include "Volume.hxx" -#include "OutputAll.hxx" #include "tag/TagConfig.hxx" #include "ReplayGainConfig.hxx" #include "Idle.hxx" -#include "SignalHandlers.hxx" #include "Log.hxx" #include "LogInit.hxx" #include "GlobalEvents.hxx" -#include "InputInit.hxx" +#include "input/Init.hxx" #include "event/Loop.hxx" #include "IOThread.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Config.hxx" -#include "PlaylistRegistry.hxx" -#include "ZeroconfGlue.hxx" -#include "DecoderList.hxx" +#include "playlist/PlaylistRegistry.hxx" +#include "zeroconf/ZeroconfGlue.hxx" +#include "decoder/DecoderList.hxx" #include "AudioConfig.hxx" -#include "pcm/PcmResample.hxx" -#include "Daemon.hxx" +#include "pcm/PcmConvert.hxx" +#include "unix/SignalHandlers.hxx" +#include "unix/Daemon.hxx" #include "system/FatalError.hxx" +#include "util/UriUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "thread/Id.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" -#include "ConfigDefaults.hxx" -#include "ConfigOption.hxx" +#include "thread/Slack.hxx" +#include "lib/icu/Init.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigDefaults.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigError.hxx" #include "Stats.hxx" +#ifdef ENABLE_DATABASE +#include "db/update/Service.hxx" +#include "db/Configured.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/plugins/simple/SimpleDatabasePlugin.hxx" +#include "storage/Configured.hxx" +#include "storage/CompositeStorage.hxx" #ifdef ENABLE_INOTIFY -#include "InotifyUpdate.hxx" +#include "db/update/InotifyUpdate.hxx" +#endif +#endif + +#ifdef ENABLE_NEIGHBOR_PLUGINS +#include "neighbor/Glue.hxx" #endif #ifdef ENABLE_SQLITE -#include "StickerDatabase.hxx" +#include "sticker/StickerDatabase.hxx" #endif #ifdef ENABLE_ARCHIVE -#include "ArchiveList.hxx" +#include "archive/ArchiveList.hxx" +#endif + +#ifdef ANDROID +#include "java/Global.hxx" +#include "java/File.hxx" +#include "android/Environment.hxx" +#include "android/Context.hxx" +#include "fs/StandardDirectory.hxx" +#include "fs/FileSystem.hxx" +#include "org_musicpd_Bridge.h" #endif +#ifdef HAVE_GLIB #include <glib.h> +#endif -#include <unistd.h> #include <stdlib.h> -#include <errno.h> -#include <string.h> #ifdef HAVE_LOCALE_H #include <locale.h> @@ -94,18 +114,27 @@ #include <ws2tcpip.h> #endif +#ifdef __BLOCKS__ +#include <dispatch/dispatch.h> +#endif + +#include <limits.h> + static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096; static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10; static constexpr Domain main_domain("main"); -ThreadId main_thread; -EventLoop *main_loop; +#ifdef ANDROID +Context *context; +#endif Instance *instance; static StateFile *state_file; +#ifndef ANDROID + static bool glue_daemonize_init(const struct options *options, Error &error) { @@ -123,28 +152,33 @@ glue_daemonize_init(const struct options *options, Error &error) return true; } +#endif + static bool glue_mapper_init(Error &error) { - auto music_dir = config_get_path(CONF_MUSIC_DIR, error); - if (music_dir.IsNull() && error.IsDefined()) - return false; - auto playlist_dir = config_get_path(CONF_PLAYLIST_DIR, error); if (playlist_dir.IsNull() && error.IsDefined()) return false; - if (music_dir.IsNull()) { - const char *path = - g_get_user_special_dir(G_USER_DIRECTORY_MUSIC); - if (path != nullptr) { - music_dir = AllocatedPath::FromUTF8(path, error); - if (music_dir.IsNull()) - return false; - } - } + mapper_init(std::move(playlist_dir)); + return true; +} + +#ifdef ENABLE_DATABASE - mapper_init(std::move(music_dir), std::move(playlist_dir)); +static bool +InitStorage(Error &error) +{ + Storage *storage = CreateConfiguredStorage(io_thread_get(), error); + if (storage == nullptr) + return !error.IsDefined(); + + assert(!error.IsDefined()); + + CompositeStorage *composite = new CompositeStorage(); + instance->storage = composite; + composite->Mount("", storage); return true; } @@ -156,50 +190,60 @@ glue_mapper_init(Error &error) static bool glue_db_init_and_load(void) { - const struct config_param *param = config_get_param(CONF_DATABASE); - const struct config_param *path = config_get_param(CONF_DB_FILE); + Error error; + instance->database = + CreateConfiguredDatabase(*instance->event_loop, *instance, + error); + if (instance->database == nullptr) { + if (error.IsDefined()) + FatalError(error); + else + return true; + } - if (param != nullptr && path != nullptr) - LogWarning(main_domain, - "Found both 'database' and 'db_file' setting - ignoring the latter"); + if (instance->database->GetPlugin().flags & DatabasePlugin::FLAG_REQUIRE_STORAGE) { + if (!InitStorage(error)) + FatalError(error); - if (!mapper_has_music_directory()) { - if (param != nullptr) - LogDefault(main_domain, + if (instance->storage == nullptr) { + delete instance->database; + instance->database = nullptr; + LogDefault(config_domain, "Found database setting without " "music_directory - disabling database"); - if (path != nullptr) - LogDefault(main_domain, - "Found db_file setting without " - "music_directory - disabling database"); - return true; - } - - struct config_param *allocated = nullptr; - - if (param == nullptr && path != nullptr) { - allocated = new config_param("database", path->line); - allocated->AddBlockParam("path", path->value.c_str(), - path->line); - param = allocated; + return true; + } + } else { + if (IsStorageConfigured()) + LogDefault(config_domain, + "Ignoring the storage configuration " + "because the database does not need it"); } - if (param == nullptr) - return true; - - Error error; - if (!DatabaseGlobalInit(*param, error)) + if (!instance->database->Open(error)) FatalError(error); - delete allocated; + if (!instance->database->IsPlugin(simple_db_plugin)) + return true; - if (!DatabaseGlobalOpen(error)) - FatalError(error); + SimpleDatabase &db = *(SimpleDatabase *)instance->database; + instance->update = new UpdateService(*instance->event_loop, db, + static_cast<CompositeStorage &>(*instance->storage), + *instance); /* run database update after daemonization? */ - return !db_is_simple() || db_exists(); + return db.FileExists(); } +static bool +InitDatabaseAndStorage() +{ + const bool create_db = !glue_db_init_and_load(); + return create_db; +} + +#endif + /** * Configure and initialize the sticker subsystem. */ @@ -224,11 +268,27 @@ static bool glue_state_file_init(Error &error) { auto path_fs = config_get_path(CONF_STATE_FILE, error); - if (path_fs.IsNull()) - return !error.IsDefined(); + if (path_fs.IsNull()) { + if (error.IsDefined()) + return false; - state_file = new StateFile(std::move(path_fs), - *instance->partition, *main_loop); +#ifdef ANDROID + const auto cache_dir = GetUserCacheDir(); + if (cache_dir.IsNull()) + return true; + + path_fs = AllocatedPath::Build(cache_dir, "state"); +#else + return true; +#endif + } + + unsigned interval = config_get_unsigned(CONF_STATE_FILE_INTERVAL, + StateFile::DEFAULT_INTERVAL); + + state_file = new StateFile(std::move(path_fs), interval, + *instance->partition, + *instance->event_loop); state_file->Read(); return true; } @@ -240,9 +300,8 @@ static void winsock_init(void) { #ifdef WIN32 WSADATA sockinfo; - int retval; - retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); + int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); if(retval != 0) FormatFatalError("Attempt to open Winsock2 failed; error code %d", retval); @@ -260,14 +319,11 @@ static void initialize_decoder_and_player(void) { const struct config_param *param; - char *test; - size_t buffer_size; - float perc; - unsigned buffered_chunks; - unsigned buffered_before_play; + size_t buffer_size; param = config_get_param(CONF_AUDIO_BUFFER_SIZE); if (param != nullptr) { + char *test; long tmp = strtol(param->value.c_str(), &test, 10); if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) FormatFatalError("buffer size \"%s\" is not a " @@ -279,14 +335,16 @@ initialize_decoder_and_player(void) buffer_size *= 1024; - buffered_chunks = buffer_size / CHUNK_SIZE; + const unsigned buffered_chunks = buffer_size / CHUNK_SIZE; if (buffered_chunks >= 1 << 15) FormatFatalError("buffer size \"%lu\" is too big", (unsigned long)buffer_size); + float perc; param = config_get_param(CONF_BUFFER_BEFORE_PLAY); if (param != nullptr) { + char *test; perc = strtod(param->value.c_str(), &test); if (*test != '%' || perc < 0 || perc > 100) { FormatFatalError("buffered before play \"%s\" is not " @@ -297,7 +355,7 @@ initialize_decoder_and_player(void) } else perc = DEFAULT_BUFFER_BEFORE_PLAY; - buffered_before_play = (perc / 100) * buffered_chunks; + unsigned buffered_before_play = (perc / 100) * buffered_chunks; if (buffered_before_play > buffered_chunks) buffered_before_play = buffered_chunks; @@ -336,11 +394,13 @@ idle_event_emitted(void) static void shutdown_event_emitted(void) { - main_loop->Break(); + instance->event_loop->Break(); } #endif +#ifndef ANDROID + int main(int argc, char *argv[]) { #ifdef WIN32 @@ -350,14 +410,19 @@ int main(int argc, char *argv[]) #endif } +#endif + +static int mpd_main_after_fork(struct options); + +#ifdef ANDROID +static inline +#endif int mpd_main(int argc, char *argv[]) { struct options options; - clock_t start; - bool create_db; Error error; - bool success; +#ifndef ANDROID daemonize_close_stdin(); #ifdef HAVE_LOCALE_H @@ -365,19 +430,43 @@ int mpd_main(int argc, char *argv[]) setlocale(LC_CTYPE,""); #endif +#ifdef HAVE_GLIB g_set_application_name("Music Player Daemon"); #if !GLIB_CHECK_VERSION(2,32,0) /* enable GLib's thread safety code */ g_thread_init(nullptr); #endif +#endif +#endif + + if (!IcuInit(error)) { + LogError(error); + return EXIT_FAILURE; + } - io_thread_init(); winsock_init(); + io_thread_init(); config_global_init(); - success = parse_cmdline(argc, argv, &options, error); - if (!success) { +#ifdef ANDROID + (void)argc; + (void)argv; + + { + const auto sdcard = Environment::getExternalStorageDirectory(); + if (!sdcard.IsNull()) { + const auto config_path = + AllocatedPath::Build(sdcard, "mpd.conf"); + if (FileExists(config_path) && + !ReadConfigFile(config_path, error)) { + LogError(error); + return EXIT_FAILURE; + } + } + } +#else + if (!parse_cmdline(argc, argv, &options, error)) { LogError(error); return EXIT_FAILURE; } @@ -386,6 +475,7 @@ int mpd_main(int argc, char *argv[]) LogError(error); return EXIT_FAILURE; } +#endif stats_global_init(); TagLoadConfig(); @@ -395,23 +485,60 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } - main_thread = ThreadId::GetCurrent(); - main_loop = new EventLoop(EventLoop::Default()); - instance = new Instance(); + instance->event_loop = new EventLoop(); + +#ifdef ENABLE_NEIGHBOR_PLUGINS + instance->neighbors = new NeighborGlue(); + if (!instance->neighbors->Init(io_thread_get(), *instance, error)) { + LogError(error); + return EXIT_FAILURE; + } + + if (instance->neighbors->IsEmpty()) { + delete instance->neighbors; + instance->neighbors = nullptr; + } +#endif const unsigned max_clients = config_get_positive(CONF_MAX_CONN, 10); instance->client_list = new ClientList(max_clients); - success = listen_global_init(error); - if (!success) { + initialize_decoder_and_player(); + + if (!listen_global_init(*instance->event_loop, *instance->partition, + error)) { LogError(error); return EXIT_FAILURE; } +#ifndef ANDROID daemonize_set_user(); + daemonize_begin(options.daemon); +#endif + +#ifdef __BLOCKS__ + /* Runs the OS X native event loop in the main thread, and runs + the rest of mpd_main on a new thread. This lets CoreAudio receive + route change notifications (e.g. plugging or unplugging headphones). + All hardware output on OS X ultimately uses CoreAudio internally. + This must be run after forking; if dispatch is called before forking, + the child process will have a broken internal dispatch state. */ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + exit(mpd_main_after_fork(options)); + }); + dispatch_main(); + return EXIT_FAILURE; // unreachable, because dispatch_main never returns +#else + return mpd_main_after_fork(options); +#endif +} - GlobalEvents::Initialize(*main_loop); +static int mpd_main_after_fork(struct options options) +{ + Error error; + + GlobalEvents::Initialize(*instance->event_loop); GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted); #ifdef WIN32 GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted); @@ -431,23 +558,23 @@ int mpd_main(int argc, char *argv[]) archive_plugin_init_all(); #endif - if (!pcm_resample_global_init(error)) { + if (!pcm_convert_global_init(error)) { LogError(error); return EXIT_FAILURE; } decoder_plugin_init_all(); - update_global_init(); - create_db = !glue_db_init_and_load(); +#ifdef ENABLE_DATABASE + const bool create_db = InitDatabaseAndStorage(); +#endif glue_sticker_init(); command_init(); - initialize_decoder_and_player(); - volume_init(); initAudioConfig(); - audio_output_all_init(instance->partition->pc); + instance->partition->outputs.Configure(*instance->event_loop, + instance->partition->pc); client_manager_init(); replay_gain_global_init(); @@ -458,43 +585,59 @@ int mpd_main(int argc, char *argv[]) playlist_list_global_init(); - daemonize(options.daemon); +#ifndef ANDROID + daemonize_commit(); setup_log_output(options.log_stderr); - SignalHandlersInit(*main_loop); + SignalHandlersInit(*instance->event_loop); +#endif io_thread_start(); - ZeroconfInit(*main_loop); +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (instance->neighbors != nullptr && + !instance->neighbors->Open(error)) + FatalError(error); +#endif + + ZeroconfInit(*instance->event_loop); - player_create(instance->partition->pc); + StartPlayerThread(instance->partition->pc); +#ifdef ENABLE_DATABASE if (create_db) { /* the database failed to load: recreate the database */ - unsigned job = update_enqueue("", true); + unsigned job = instance->update->Enqueue("", true); if (job == 0) FatalError("directory update failed"); } +#endif if (!glue_state_file_init(error)) { - g_printerr("%s\n", error.GetMessage()); + LogError(error); return EXIT_FAILURE; } - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(instance->partition->playlist.queue.random)); + instance->partition->outputs.SetReplayGainMode(replay_gain_get_real_mode(instance->partition->playlist.queue.random)); - success = config_get_bool(CONF_AUTO_UPDATE, false); +#ifdef ENABLE_DATABASE + if (config_get_bool(CONF_AUTO_UPDATE, false)) { #ifdef ENABLE_INOTIFY - if (success && mapper_has_music_directory()) - mpd_inotify_init(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, - G_MAXUINT)); + if (instance->storage != nullptr && + instance->update != nullptr) + mpd_inotify_init(*instance->event_loop, + *instance->storage, + *instance->update, + config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, + INT_MAX)); #else - if (success) FormatWarning(main_domain, "inotify: auto_update was disabled. enable during compilation phase"); #endif + } +#endif config_global_check(); @@ -506,8 +649,12 @@ int mpd_main(int argc, char *argv[]) win32_app_started(); #endif + /* the MPD frontend does not care about timer slack; set it to + a huge value to allow the kernel to reduce CPU wakeups */ + SetThreadTimerSlackMS(100); + /* run the main loop */ - main_loop->Run(); + instance->event_loop->Run(); #ifdef WIN32 win32_app_stopping(); @@ -515,8 +662,11 @@ int mpd_main(int argc, char *argv[]) /* cleanup */ -#ifdef ENABLE_INOTIFY +#if defined(ENABLE_DATABASE) && defined(ENABLE_INOTIFY) mpd_inotify_finish(); + + if (instance->update != nullptr) + instance->update->CancelAllAsync(); #endif if (state_file != nullptr) { @@ -529,11 +679,23 @@ int mpd_main(int argc, char *argv[]) listen_global_finish(); delete instance->client_list; - start = clock(); - DatabaseGlobalDeinit(); - FormatDebug(main_domain, - "db_finish took %f seconds", - ((float)(clock()-start))/CLOCKS_PER_SEC); +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (instance->neighbors != nullptr) { + instance->neighbors->Close(); + delete instance->neighbors; + } +#endif + +#ifdef ENABLE_DATABASE + delete instance->update; + + if (instance->database != nullptr) { + instance->database->Close(); + delete instance->database; + } + + delete instance->storage; +#endif #ifdef ENABLE_SQLITE sticker_global_finish(); @@ -543,27 +705,62 @@ int mpd_main(int argc, char *argv[]) playlist_list_global_finish(); input_stream_global_finish(); - audio_output_all_finish(); - volume_finish(); + +#ifdef ENABLE_DATABASE mapper_finish(); +#endif + delete instance->partition; command_finish(); - update_global_finish(); decoder_plugin_deinit_all(); #ifdef ENABLE_ARCHIVE archive_plugin_deinit_all(); #endif config_global_finish(); - stats_global_finish(); io_thread_deinit(); +#ifndef ANDROID SignalHandlersFinish(); +#endif + delete instance->event_loop; delete instance; - delete main_loop; + instance = nullptr; +#ifndef ANDROID daemonize_finish(); +#endif #ifdef WIN32 WSACleanup(); #endif + IcuFinish(); + log_deinit(); return EXIT_SUCCESS; } + +#ifdef ANDROID + +gcc_visibility_default +JNIEXPORT void JNICALL +Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context) +{ + Java::Init(env); + Java::File::Initialise(env); + Environment::Initialise(env); + + context = new Context(env, _context); + + mpd_main(0, nullptr); + + delete context; + Environment::Deinitialise(env); +} + +gcc_visibility_default +JNIEXPORT void JNICALL +Java_org_musicpd_Bridge_shutdown(JNIEnv *, jclass) +{ + if (instance != nullptr) + instance->event_loop->Break(); +} + +#endif |