blob: 86fea4df12bb5942ea744b1e5d34c635277fa056 [file] [log] [blame]
/*
* libjingle
* Copyright 2004--2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_DBUS_GLIB
#include "talk/base/dbus.h"
#include <dbus/dbus-glib-lowlevel.h>
#include "talk/base/thread.h"
namespace talk_base {
// Avoid static object construction/destruction on startup/shutdown.
static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT;
static LibDBusGlibSymbolTable *g_dbus_symbol = NULL;
// Releases DBus-Glib symbols.
static void ReleaseDBusGlibSymbol() {
if (g_dbus_symbol != NULL) {
delete g_dbus_symbol;
g_dbus_symbol = NULL;
}
}
// Loads DBus-Glib symbols.
static void InitializeDBusGlibSymbol() {
// This is thread safe.
if (NULL == g_dbus_symbol) {
g_dbus_symbol = new LibDBusGlibSymbolTable();
// Loads dbus-glib
if (NULL == g_dbus_symbol || !g_dbus_symbol->Load()) {
LOG(LS_WARNING) << "Failed to load dbus-glib symbol table.";
ReleaseDBusGlibSymbol();
} else {
// Nothing we can do if atexit() failed. Just ignore its returned value.
atexit(ReleaseDBusGlibSymbol);
}
}
}
// Returns a reference to the given late-binded symbol, with the correct type.
#define LATE(sym) LATESYM_GET(LibDBusGlibSymbolTable, \
DBusMonitor::GetDBusGlibSymbolTable(), sym)
// Implementation of class DBusSigMessageData
DBusSigMessageData::DBusSigMessageData(DBusMessage *message)
: TypedMessageData<DBusMessage *>(message) {
LATE(dbus_message_ref)(data());
}
DBusSigMessageData::~DBusSigMessageData() {
LATE(dbus_message_unref)(data());
}
// Implementation of class DBusSigFilter
// Builds a DBus filter string from given DBus path, interface and member.
std::string DBusSigFilter::BuildFilterString(const std::string &path,
const std::string &interface,
const std::string &member) {
std::string ret(DBUS_TYPE "='" DBUS_SIGNAL "'");
if (!path.empty()) {
ret += ("," DBUS_PATH "='");
ret += path;
ret += "'";
}
if (!interface.empty()) {
ret += ("," DBUS_INTERFACE "='");
ret += interface;
ret += "'";
}
if (!member.empty()) {
ret += ("," DBUS_MEMBER "='");
ret += member;
ret += "'";
}
return ret;
}
// Forwards the message to the given instance.
DBusHandlerResult DBusSigFilter::DBusCallback(DBusConnection *dbus_conn,
DBusMessage *message,
void *instance) {
ASSERT(instance);
if (instance) {
return static_cast<DBusSigFilter *>(instance)->Callback(message);
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
// Posts a message to caller thread.
DBusHandlerResult DBusSigFilter::Callback(DBusMessage *message) {
if (caller_thread_) {
caller_thread_->Post(this, DSM_SIGNAL, new DBusSigMessageData(message));
}
// Don't "eat" the message here. Let it pop up.
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
// From MessageHandler.
void DBusSigFilter::OnMessage(Message *message) {
if (message != NULL && DSM_SIGNAL == message->message_id) {
DBusSigMessageData *msg =
static_cast<DBusSigMessageData *>(message->pdata);
if (msg) {
ProcessSignal(msg->data());
delete msg;
}
}
}
// Definition of private class DBusMonitoringThread.
// It creates a worker-thread to listen signals on DBus. The worker-thread will
// be running in a priate GMainLoop forever until either Stop() has been invoked
// or it hits an error.
class DBusMonitor::DBusMonitoringThread : public talk_base::Thread {
public:
explicit DBusMonitoringThread(DBusMonitor *monitor,
GMainContext *context,
GMainLoop *mainloop,
std::vector<DBusSigFilter *> *filter_list)
: monitor_(monitor),
context_(context),
mainloop_(mainloop),
connection_(NULL),
idle_source_(NULL),
filter_list_(filter_list) {
ASSERT(monitor_);
ASSERT(context_);
ASSERT(mainloop_);
ASSERT(filter_list_);
}
// Override virtual method of Thread. Context: worker-thread.
virtual void Run() {
ASSERT(NULL == connection_);
// Setup DBus connection and start monitoring.
monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING);
if (!Setup()) {
LOG(LS_ERROR) << "DBus monitoring setup failed.";
monitor_->OnMonitoringStatusChanged(DMS_FAILED);
CleanUp();
return;
}
monitor_->OnMonitoringStatusChanged(DMS_RUNNING);
LATE(g_main_loop_run)(mainloop_);
monitor_->OnMonitoringStatusChanged(DMS_STOPPED);
// Done normally. Clean up DBus connection.
CleanUp();
return;
}
// Override virtual method of Thread. Context: caller-thread.
virtual void Stop() {
ASSERT(NULL == idle_source_);
// Add an idle source and let the gmainloop quit on idle.
idle_source_ = LATE(g_idle_source_new)();
if (idle_source_) {
LATE(g_source_set_callback)(idle_source_, &Idle, this, NULL);
LATE(g_source_attach)(idle_source_, context_);
} else {
LOG(LS_ERROR) << "g_idle_source_new() failed.";
QuitGMainloop(); // Try to quit anyway.
}
Thread::Stop(); // Wait for the thread.
}
private:
// Registers all DBus filters.
void RegisterAllFilters() {
ASSERT(NULL != LATE(dbus_g_connection_get_connection)(connection_));
for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
it != filter_list_->end(); ++it) {
DBusSigFilter *filter = (*it);
if (!filter) {
LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
continue;
}
LATE(dbus_bus_add_match)(
LATE(dbus_g_connection_get_connection)(connection_),
filter->filter().c_str(), NULL);
if (!LATE(dbus_connection_add_filter)(
LATE(dbus_g_connection_get_connection)(connection_),
&DBusSigFilter::DBusCallback, filter, NULL)) {
LOG(LS_ERROR) << "dbus_connection_add_filter() failed."
<< "Filter: " << filter->filter();
continue;
}
}
}
// Unregisters all DBus filters.
void UnRegisterAllFilters() {
ASSERT(NULL != LATE(dbus_g_connection_get_connection)(connection_));
for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
it != filter_list_->end(); ++it) {
DBusSigFilter *filter = (*it);
if (!filter) {
LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
continue;
}
LATE(dbus_connection_remove_filter)(
LATE(dbus_g_connection_get_connection)(connection_),
&DBusSigFilter::DBusCallback, filter);
}
}
// Sets up the monitoring thread.
bool Setup() {
LATE(g_main_context_push_thread_default)(context_);
// Start connection to dbus.
// If dbus daemon is not running, returns false immediately.
connection_ = LATE(dbus_g_bus_get_private)(monitor_->type_, context_, NULL);
if (NULL == connection_) {
LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection.";
return false;
}
if (NULL == LATE(dbus_g_connection_get_connection)(connection_)) {
LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. "
<< "DBus daemon is probably not running.";
return false;
}
// Application don't exit if DBus daemon die.
LATE(dbus_connection_set_exit_on_disconnect)(
LATE(dbus_g_connection_get_connection)(connection_), FALSE);
// Connect all filters.
RegisterAllFilters();
return true;
}
// Cleans up the monitoring thread.
void CleanUp() {
if (idle_source_) {
// We did an attach() with the GSource, so we need to destroy() it.
LATE(g_source_destroy)(idle_source_);
// We need to unref() the GSource to end the last reference we got.
LATE(g_source_unref)(idle_source_);
idle_source_ = NULL;
}
if (connection_) {
if (LATE(dbus_g_connection_get_connection)(connection_)) {
UnRegisterAllFilters();
LATE(dbus_connection_close)(
LATE(dbus_g_connection_get_connection)(connection_));
}
LATE(dbus_g_connection_unref)(connection_);
connection_ = NULL;
}
LATE(g_main_loop_unref)(mainloop_);
mainloop_ = NULL;
LATE(g_main_context_unref)(context_);
context_ = NULL;
}
// Handles callback on Idle. We only add this source when ready to stop.
static gboolean Idle(gpointer data) {
static_cast<DBusMonitoringThread *>(data)->QuitGMainloop();
return TRUE;
}
// We only hit this when ready to quit.
void QuitGMainloop() {
LATE(g_main_loop_quit)(mainloop_);
}
DBusMonitor *monitor_;
GMainContext *context_;
GMainLoop *mainloop_;
DBusGConnection *connection_;
GSource *idle_source_;
std::vector<DBusSigFilter *> *filter_list_;
};
// Implementation of class DBusMonitor
// Returns DBus-Glib symbol handle. Initialize it first if hasn't.
LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() {
// This is multi-thread safe.
pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol);
return g_dbus_symbol;
};
// Creates an instance of DBusMonitor
DBusMonitor *DBusMonitor::Create(DBusBusType type) {
if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) {
return NULL;
}
return new DBusMonitor(type);
}
DBusMonitor::DBusMonitor(DBusBusType type)
: type_(type),
status_(DMS_NOT_INITIALIZED),
monitoring_thread_(NULL) {
ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION);
}
DBusMonitor::~DBusMonitor() {
StopMonitoring();
}
bool DBusMonitor::AddFilter(DBusSigFilter *filter) {
if (monitoring_thread_) {
return false;
}
if (!filter) {
return false;
}
filter_list_.push_back(filter);
return true;
}
bool DBusMonitor::StartMonitoring() {
if (!monitoring_thread_) {
LATE(g_type_init)();
LATE(g_thread_init)(NULL);
LATE(dbus_g_thread_init)();
GMainContext *context = LATE(g_main_context_new)();
if (NULL == context) {
LOG(LS_ERROR) << "g_main_context_new() failed.";
return false;
}
GMainLoop *mainloop = LATE(g_main_loop_new)(context, FALSE);
if (NULL == mainloop) {
LOG(LS_ERROR) << "g_main_loop_new() failed.";
LATE(g_main_context_unref)(context);
return false;
}
monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop,
&filter_list_);
if (monitoring_thread_ == NULL) {
LOG(LS_ERROR) << "Failed to create DBus monitoring thread.";
LATE(g_main_context_unref)(context);
LATE(g_main_loop_unref)(mainloop);
return false;
}
monitoring_thread_->Start();
}
return true;
}
bool DBusMonitor::StopMonitoring() {
if (monitoring_thread_) {
monitoring_thread_->Stop();
monitoring_thread_ = NULL;
}
return true;
}
DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() {
return status_;
}
void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) {
status_ = status;
}
#undef LATE
} // namespace talk_base
#endif // HAVE_DBUS_GLIB