text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "dbus/exported_object.h"
  
  #include "base/bind.h"
  #include "base/logging.h"
  #include "base/memory/ref_counted.h"
  #include "base/message_loop.h"
  #include "base/metrics/histogram.h"
  #include "base/threading/thread_restrictions.h"
  #include "base/time.h"
  #include "dbus/bus.h"
  #include "dbus/message.h"
  #include "dbus/object_path.h"
  #include "dbus/scoped_dbus_error.h"
  
  namespace dbus {
  
  namespace {
  
  // Used for success ratio histograms. 1 for success, 0 for failure.
  const int kSuccessRatioHistogramMaxValue = 2;
  
  // Gets the absolute method name by concatenating the interface name and
  // the method name. Used for building keys for method_table_ in
  // ExportedObject.
  std::string GetAbsoluteMethodName(
      const std::string& interface_name,
      const std::string& method_name) {
    return interface_name + "." + method_name;
  }
  
  }  // namespace
  
  ExportedObject::ExportedObject(Bus* bus,
                                 const ObjectPath& object_path)
      : bus_(bus),
        object_path_(object_path),
        object_is_registered_(false) {
  }
  
  ExportedObject::~ExportedObject() {
    DCHECK(!object_is_registered_);
  }
  
  bool ExportedObject::ExportMethodAndBlock(
      const std::string& interface_name,
      const std::string& method_name,
      MethodCallCallback method_call_callback) {
    bus_->AssertOnDBusThread();
  
    // Check if the method is already exported.
    const std::string absolute_method_name =
        GetAbsoluteMethodName(interface_name, method_name);
    if (method_table_.find(absolute_method_name) != method_table_.end()) {
      LOG(ERROR) << absolute_method_name << " is already exported";
      return false;
    }
  
    if (!bus_->Connect())
      return false;
    if (!bus_->SetUpAsyncOperations())
      return false;
    if (!Register())
      return false;
  
    // Add the method callback to the method table.
    method_table_[absolute_method_name] = method_call_callback;
  
    return true;
  }
  
  void ExportedObject::ExportMethod(const std::string& interface_name,
                                    const std::string& method_name,
                                    MethodCallCallback method_call_callback,
                                    OnExportedCallback on_exported_calback) {
    bus_->AssertOnOriginThread();
  
    base::Closure task = base::Bind(&ExportedObject::ExportMethodInternal,
                                    this,
                                    interface_name,
                                    method_name,
                                    method_call_callback,
                                    on_exported_calback);
    bus_->PostTaskToDBusThread(FROM_HERE, task);
  }
  
  void ExportedObject::SendSignal(Signal* signal) {
    // For signals, the object path should be set to the path to the sender
    // object, which is this exported object here.
    CHECK(signal->SetPath(object_path_));
  
    // Increment the reference count so we can safely reference the
    // underlying signal message until the signal sending is complete. This
    // will be unref'ed in SendSignalInternal().
    DBusMessage* signal_message = signal->raw_message();
    dbus_message_ref(signal_message);
  
    const base::TimeTicks start_time = base::TimeTicks::Now();
    bus_->PostTaskToDBusThread(FROM_HERE,
                               base::Bind(&ExportedObject::SendSignalInternal,
                                          this,
                                          start_time,
                                          signal_message));
  }
  
  void ExportedObject::Unregister() {
    bus_->AssertOnDBusThread();
  
    if (!object_is_registered_)
      return;
  
    bus_->UnregisterObjectPath(object_path_);
    object_is_registered_ = false;
  }
  
  void ExportedObject::ExportMethodInternal(
      const std::string& interface_name,
      const std::string& method_name,
      MethodCallCallback method_call_callback,
      OnExportedCallback on_exported_calback) {
    bus_->AssertOnDBusThread();
  
    const bool success = ExportMethodAndBlock(interface_name,
                                              method_name,
                                              method_call_callback);
    bus_->PostTaskToOriginThread(FROM_HERE,
                                 base::Bind(&ExportedObject::OnExported,
                                            this,
                                            on_exported_calback,
                                            interface_name,
                                            method_name,
                                            success));
  }
  
  void ExportedObject::OnExported(OnExportedCallback on_exported_callback,
                                  const std::string& interface_name,
                                  const std::string& method_name,
                                  bool success) {
    bus_->AssertOnOriginThread();
  
    on_exported_callback.Run(interface_name, method_name, success);
  }
  
  void ExportedObject::SendSignalInternal(base::TimeTicks start_time,
                                          DBusMessage* signal_message) {
    uint32 serial = 0;
    bus_->Send(signal_message, &serial);
    dbus_message_unref(signal_message);
    // Record time spent to send the the signal. This is not accurate as the
    // signal will actually be sent from the next run of the message loop,
    // but we can at least tell the number of signals sent.
    UMA_HISTOGRAM_TIMES("DBus.SignalSendTime",
                        base::TimeTicks::Now() - start_time);
  }
  
  bool ExportedObject::Register() {
    bus_->AssertOnDBusThread();
  
    if (object_is_registered_)
      return true;
  
    ScopedDBusError error;
  
    DBusObjectPathVTable vtable = {};
    vtable.message_function = &ExportedObject::HandleMessageThunk;
    vtable.unregister_function = &ExportedObject::OnUnregisteredThunk;
    const bool success = bus_->TryRegisterObjectPath(object_path_,
                                                     &vtable,
                                                     this,
                                                     error.get());
    if (!success) {
      LOG(ERROR) << "Failed to register the object: " << object_path_.value()
                 << ": " << (error.is_set() ? error.message() : "");
      return false;
    }
  
    object_is_registered_ = true;
    return true;
  }
  
  DBusHandlerResult ExportedObject::HandleMessage(
      DBusConnection* connection,
      DBusMessage* raw_message) {
    bus_->AssertOnDBusThread();
    DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message));
  
    // raw_message will be unrefed on exit of the function. Increment the
    // reference so we can use it in MethodCall.
    dbus_message_ref(raw_message);
    scoped_ptr<MethodCall> method_call(
        MethodCall::FromRawMessage(raw_message));
    const std::string interface = method_call->GetInterface();
    const std::string member = method_call->GetMember();
  
    if (interface.empty()) {
      // We don't support method calls without interface.
      LOG(WARNING) << "Interface is missing: " << method_call->ToString();
      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }
  
    // Check if we know about the method.
    const std::string absolute_method_name = GetAbsoluteMethodName(
        interface, member);
    MethodTable::const_iterator iter = method_table_.find(absolute_method_name);
    if (iter == method_table_.end()) {
      // Don't know about the method.
      LOG(WARNING) << "Unknown method: " << method_call->ToString();
      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }
  
    const base::TimeTicks start_time = base::TimeTicks::Now();
    if (bus_->HasDBusThread()) {
      // Post a task to run the method in the origin thread.
      bus_->PostTaskToOriginThread(FROM_HERE,
                                   base::Bind(&ExportedObject::RunMethod,
                                              this,
                                              iter->second,
                                              base::Passed(&method_call),
                                              start_time));
    } else {
      // If the D-Bus thread is not used, just call the method directly.
      MethodCall* method = method_call.get();
      iter->second.Run(method,
                       base::Bind(&ExportedObject::SendResponse,
                                  this,
                                  start_time,
                                  base::Passed(&method_call)));
    }
  
    // It's valid to say HANDLED here, and send a method response at a later
    // time from OnMethodCompleted() asynchronously.
    return DBUS_HANDLER_RESULT_HANDLED;
  }
  
  void ExportedObject::RunMethod(MethodCallCallback method_call_callback,
                                 scoped_ptr<MethodCall> method_call,
                                 base::TimeTicks start_time) {
    bus_->AssertOnOriginThread();
    MethodCall* method = method_call.get();
    method_call_callback.Run(method,
                             base::Bind(&ExportedObject::SendResponse,
                                        this,
                                        start_time,
                                        base::Passed(&method_call)));
  }
  
  void ExportedObject::SendResponse(base::TimeTicks start_time,
                                    scoped_ptr<MethodCall> method_call,
                                    scoped_ptr<Response> response) {
    DCHECK(method_call);
    if (bus_->HasDBusThread()) {
      bus_->PostTaskToDBusThread(FROM_HERE,
                                 base::Bind(&ExportedObject::OnMethodCompleted,
                                            this,
                                            base::Passed(&method_call),
                                            base::Passed(&response),
                                            start_time));
    } else {
      OnMethodCompleted(method_call.Pass(), response.Pass(), start_time);
    }
  }
  
  void ExportedObject::OnMethodCompleted(scoped_ptr<MethodCall> method_call,
                                         scoped_ptr<Response> response,
                                         base::TimeTicks start_time) {
    bus_->AssertOnDBusThread();
  
    // Record if the method call is successful, or not. 1 if successful.
    UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess",
                              response ? 1 : 0,
                              kSuccessRatioHistogramMaxValue);
  
    // Check if the bus is still connected. If the method takes long to
    // complete, the bus may be shut down meanwhile.
    if (!bus_->is_connected())
      return;
  
    if (!response) {
      // Something bad happened in the method call.
      scoped_ptr<ErrorResponse> error_response(
          ErrorResponse::FromMethodCall(
              method_call.get(),
              DBUS_ERROR_FAILED,
              "error occurred in " + method_call->GetMember()));
      bus_->Send(error_response->raw_message(), NULL);
      return;
    }
  
    // The method call was successful.
    bus_->Send(response->raw_message(), NULL);
  
    // Record time spent to handle the the method call. Don't include failures.
    UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
                        base::TimeTicks::Now() - start_time);
  }
  
  void ExportedObject::OnUnregistered(DBusConnection* connection) {
  }
  
  DBusHandlerResult ExportedObject::HandleMessageThunk(
      DBusConnection* connection,
      DBusMessage* raw_message,
      void* user_data) {
    ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
    return self->HandleMessage(connection, raw_message);
  }
  
  void ExportedObject::OnUnregisteredThunk(DBusConnection *connection,
                                           void* user_data) {
    ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
    return self->OnUnregistered(connection);
  }
  
  }  // namespace dbus
  