OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/multi_process_notification.h" | 5 #include "chrome/browser/multi_process_notification.h" |
6 | 6 |
| 7 #include <dbus/dbus.h> |
| 8 #include <set> |
| 9 |
| 10 #include "base/basictypes.h" |
| 11 #include "base/file_path.h" |
7 #include "base/logging.h" | 12 #include "base/logging.h" |
| 13 #include "base/message_loop_proxy.h" |
| 14 #include "base/path_service.h" |
| 15 #include "base/singleton.h" |
| 16 #include "base/stringprintf.h" |
| 17 #include "base/threading/simple_thread.h" |
| 18 #include "chrome/common/chrome_paths.h" |
8 | 19 |
9 namespace multi_process_notification { | 20 namespace multi_process_notification { |
10 | 21 |
11 bool Post(const std::string& name, Domain domain) { | 22 const char* kNotifyPath = "/modules/notify"; |
12 // TODO(dmaclach): Implement | 23 const char* kNotifyInterface = "org.chromium.Notify"; |
13 NOTIMPLEMENTED(); | 24 const char* kNotifySignalName = "Notify"; |
14 return false; | 25 |
15 } | 26 // A simple thread to run the DBus main loop. |
16 | 27 class ListenerThread : public base::SimpleThread { |
| 28 public: |
| 29 ListenerThread(); |
| 30 |
| 31 bool Init(); |
| 32 |
| 33 bool AddListener(ListenerImpl* listner); |
| 34 bool RemoveListener(ListenerImpl* listner); |
| 35 |
| 36 // SimpleThread overrides |
| 37 virtual void Run(); |
| 38 |
| 39 private: |
| 40 friend struct DefaultSingletonTraits<ListenerThread>; |
| 41 |
| 42 DBusConnection* connection_; |
| 43 |
| 44 // Access to this needs to be protected by ListenerThreadLock::GetLock(). |
| 45 std::set<ListenerImpl*> listeners_; |
| 46 |
| 47 DISALLOW_COPY_AND_ASSIGN(ListenerThread); |
| 48 }; |
| 49 |
| 50 // This is a singleton class that owns the lock for the ListenerThread and |
| 51 // the thread. |
| 52 class ListenerThreadLock { |
| 53 public: |
| 54 static ListenerThreadLock* GetInstance(); |
| 55 |
| 56 // Return a lock that can be used to protect access to the ListenerThread's |
| 57 // member variables. |
| 58 base::Lock& GetLock(); |
| 59 |
| 60 // Return the global ListenerThread, creating/initializing it as needed. |
| 61 // Note that this should not be called if you've taken the lock (using |
| 62 // GetLock()) since this call needs to make use of that lock. |
| 63 ListenerThread* GetThread(); |
| 64 |
| 65 private: |
| 66 ListenerThreadLock(); |
| 67 friend struct DefaultSingletonTraits<ListenerThreadLock>; |
| 68 |
| 69 // This lock is used to protect the thread creation and the thread members. |
| 70 base::Lock thread_lock_; |
| 71 |
| 72 ListenerThread* thread_; |
| 73 |
| 74 DISALLOW_COPY_AND_ASSIGN(ListenerThreadLock); |
| 75 }; |
| 76 |
| 77 // This does all the heavy lifting for Listener class. |
17 class ListenerImpl { | 78 class ListenerImpl { |
18 public: | 79 public: |
19 ListenerImpl(const std::string& name, | 80 ListenerImpl(const std::string& name, |
20 Domain domain, | 81 Domain domain, |
21 Listener::Delegate* delegate); | 82 Listener::Delegate* delegate); |
| 83 ~ListenerImpl(); |
22 | 84 |
23 bool Start(MessageLoop* io_loop_to_listen_on); | 85 bool Start(MessageLoop* io_loop_to_listen_on); |
| 86 void OnListen(const std::string& name); |
24 | 87 |
25 std::string name() const { return name_; } | 88 std::string name() const { return name_; } |
26 Domain domain() const { return domain_; } | 89 Domain domain() const { return domain_; } |
27 | 90 |
28 private: | 91 private: |
| 92 void StartListener(); |
| 93 |
29 std::string name_; | 94 std::string name_; |
30 Domain domain_; | 95 Domain domain_; |
31 Listener::Delegate* delegate_; | 96 Listener::Delegate* delegate_; |
32 | 97 |
| 98 DBusError error_; |
| 99 DBusConnection* connection_; |
| 100 |
| 101 scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; |
| 102 |
33 DISALLOW_COPY_AND_ASSIGN(ListenerImpl); | 103 DISALLOW_COPY_AND_ASSIGN(ListenerImpl); |
34 }; | 104 }; |
35 | 105 |
| 106 // Return the fullname for this signal by taking the user-specified name and |
| 107 // adding a prefix based on the domain. |
| 108 std::string AddDomainPrefixToNotification(const std::string& name, |
| 109 Domain domain) { |
| 110 std::string prefix; |
| 111 switch (domain) { |
| 112 case multi_process_notification::ProfileDomain: { |
| 113 FilePath user_data_dir; |
| 114 if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { |
| 115 NOTREACHED(); |
| 116 } |
| 117 prefix = StringPrintf("user.%u.%s.", getuid(), |
| 118 user_data_dir.value().c_str()); |
| 119 break; |
| 120 } |
| 121 case multi_process_notification::UserDomain: |
| 122 prefix = StringPrintf("user.%u.", getuid()); |
| 123 break; |
| 124 case multi_process_notification::SystemDomain: |
| 125 prefix = ""; |
| 126 break; |
| 127 } |
| 128 return prefix + name; |
| 129 } |
| 130 |
| 131 bool Post(const std::string& name, Domain domain) { |
| 132 DBusError error; |
| 133 dbus_error_init(&error); |
| 134 |
| 135 // Get a connection to the DBus. |
| 136 DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &error); |
| 137 if (dbus_error_is_set(&error)) { |
| 138 LOG(ERROR) << "Failed to create initial dbus connection: " << error.message; |
| 139 dbus_error_free(&error); |
| 140 return false; |
| 141 } |
| 142 if (!connection) { |
| 143 LOG(ERROR) << "Failed to create initial dbus connection"; |
| 144 return false; |
| 145 } |
| 146 |
| 147 std::string fullname = AddDomainPrefixToNotification(name, domain); |
| 148 |
| 149 // Create the Notify signal. |
| 150 bool success = true; |
| 151 DBusMessage* message = dbus_message_new_signal(kNotifyPath, kNotifyInterface, |
| 152 kNotifySignalName); |
| 153 if (!message) { |
| 154 LOG(ERROR) << "Failed to create dbus message for signal: " |
| 155 << kNotifySignalName << " (" << kNotifyInterface << ")"; |
| 156 success = false; |
| 157 } |
| 158 |
| 159 // Add the full signal name as an argument to the Notify signal. |
| 160 if (success) { |
| 161 DBusMessageIter args; |
| 162 const char* cname = fullname.c_str(); |
| 163 dbus_message_iter_init_append(message, &args); |
| 164 if (!dbus_message_iter_append_basic(&args, |
| 165 DBUS_TYPE_STRING, &cname)) { |
| 166 LOG(ERROR) << "Failed to set signal name: " << fullname; |
| 167 success = false; |
| 168 } |
| 169 } |
| 170 |
| 171 // Actually send the signal. |
| 172 if (success) { |
| 173 dbus_uint32_t serial = 0; |
| 174 if (!dbus_connection_send(connection, message, &serial)) { |
| 175 LOG(ERROR) << "Unable to send dbus message for " << fullname; |
| 176 success = false; |
| 177 } |
| 178 } |
| 179 |
| 180 if (success) { |
| 181 dbus_connection_flush(connection); |
| 182 } |
| 183 dbus_message_unref(message); |
| 184 |
| 185 return success; |
| 186 } |
| 187 |
| 188 ListenerThread::ListenerThread() |
| 189 : base::SimpleThread("ListenerThread"), |
| 190 connection_(NULL) { |
| 191 } |
| 192 |
| 193 bool ListenerThread::Init() { |
| 194 DBusError error; |
| 195 dbus_error_init(&error); |
| 196 |
| 197 // Get a connection to the DBus. |
| 198 connection_ = dbus_bus_get(DBUS_BUS_SESSION, &error); |
| 199 if (dbus_error_is_set(&error)) { |
| 200 LOG(ERROR) << "Failed to create initial dbus connection: " << error.message; |
| 201 dbus_error_free(&error); |
| 202 return false; |
| 203 } |
| 204 if (!connection_) { |
| 205 LOG(ERROR) << "Failed to create initial dbus connection"; |
| 206 return false; |
| 207 } |
| 208 |
| 209 // Create matching rule for our signal type. |
| 210 std::string match_rule = StringPrintf("type='signal',interface='%s'", |
| 211 kNotifyInterface); |
| 212 dbus_bus_add_match(connection_, match_rule.c_str(), &error); |
| 213 dbus_connection_flush(connection_); |
| 214 if (dbus_error_is_set(&error)) { |
| 215 LOG(ERROR) << "Failed to add dbus match rule for " |
| 216 << kNotifyInterface << ": " |
| 217 << error.message; |
| 218 dbus_error_free(&error); |
| 219 return false; |
| 220 } |
| 221 |
| 222 return true; |
| 223 } |
| 224 |
| 225 bool ListenerThread::AddListener(ListenerImpl* listener) { |
| 226 base::AutoLock autolock(ListenerThreadLock::GetInstance()->GetLock()); |
| 227 listeners_.insert(listener); |
| 228 return true; |
| 229 } |
| 230 |
| 231 bool ListenerThread::RemoveListener(ListenerImpl* listener) { |
| 232 base::AutoLock autolock(ListenerThreadLock::GetInstance()->GetLock()); |
| 233 listeners_.erase(listener); |
| 234 return true; |
| 235 } |
| 236 |
| 237 void ListenerThread::Run() { |
| 238 while (true) { |
| 239 // Get next available message. |
| 240 // Using -1 for the timeout (instead of 0) would make this a blocking call. |
| 241 // But it would unfortunately block *all* dbus calls on this connection. |
| 242 // Thus, we use a timeout of 0 and a (nano)sleep. |
| 243 // TODO(garykac): Ugh. Fix this to avoid the call to sleep if possible. |
| 244 dbus_connection_read_write(connection_, 0); |
| 245 |
| 246 DBusMessage* message = dbus_connection_pop_message(connection_); |
| 247 if (!message) { |
| 248 struct timespec delay = {0}; |
| 249 delay.tv_sec = 0; |
| 250 delay.tv_nsec = 500 * 1000 * 1000; |
| 251 nanosleep(&delay, NULL); // nanoYuck! |
| 252 continue; |
| 253 } |
| 254 |
| 255 // Process all queued up messages. |
| 256 while (message) { |
| 257 if (dbus_message_is_signal(message, |
| 258 kNotifyInterface, kNotifySignalName)) { |
| 259 // Get user-defined name from the signal arguments. |
| 260 DBusMessageIter args; |
| 261 if (!dbus_message_iter_init(message, &args)) { |
| 262 LOG(ERROR) << "Params missing from dbus signal"; |
| 263 } else if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { |
| 264 LOG(ERROR) << "Dbus signal param is not string type"; |
| 265 } else { |
| 266 const char* name; |
| 267 dbus_message_iter_get_basic(&args, &name); |
| 268 { // Scope for lock |
| 269 base::AutoLock autolock(ListenerThreadLock::GetInstance() |
| 270 ->GetLock()); |
| 271 std::set<ListenerImpl*>::iterator it; |
| 272 // Note that this doesn't scale well to a large number of listeners. |
| 273 // But we should only have a couple active at a time. |
| 274 for (it=listeners_.begin(); it!=listeners_.end(); it++) { |
| 275 (*it)->OnListen(name); |
| 276 } |
| 277 } |
| 278 } |
| 279 } |
| 280 |
| 281 dbus_message_unref(message); |
| 282 message = dbus_connection_pop_message(connection_); |
| 283 } |
| 284 } |
| 285 } |
| 286 |
36 ListenerImpl::ListenerImpl(const std::string& name, | 287 ListenerImpl::ListenerImpl(const std::string& name, |
37 Domain domain, | 288 Domain domain, |
38 Listener::Delegate* delegate) | 289 Listener::Delegate* delegate) |
39 : name_(name), domain_(domain), delegate_(delegate) { | 290 : name_(name), |
| 291 domain_(domain), |
| 292 delegate_(delegate), |
| 293 connection_(NULL) { |
| 294 } |
| 295 |
| 296 ListenerImpl::~ListenerImpl() { |
| 297 ListenerThreadLock* threadlock = ListenerThreadLock::GetInstance(); |
| 298 ListenerThread* thread = threadlock->GetThread(); |
| 299 if (thread) { |
| 300 thread->RemoveListener(this); |
| 301 } |
40 } | 302 } |
41 | 303 |
42 bool ListenerImpl::Start(MessageLoop* io_loop_to_listen_on) { | 304 bool ListenerImpl::Start(MessageLoop* io_loop_to_listen_on) { |
43 // TODO(dmaclach): Implement | 305 if (io_loop_to_listen_on->type() != MessageLoop::TYPE_IO) { |
44 NOTIMPLEMENTED(); | 306 DLOG(ERROR) << "io_loop_to_listen_on must be TYPE_IO"; |
45 return false; | 307 return false; |
| 308 } |
| 309 |
| 310 // Start the listener on the IO thread. |
| 311 message_loop_proxy_ = base::MessageLoopProxy::CreateForCurrentThread(); |
| 312 Task* task = NewRunnableMethod(this, &ListenerImpl::StartListener); |
| 313 io_loop_to_listen_on->PostTask(FROM_HERE, task); |
| 314 return true; |
| 315 } |
| 316 |
| 317 void ListenerImpl::StartListener() { |
| 318 DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); |
| 319 |
| 320 ListenerThreadLock* threadlock = ListenerThreadLock::GetInstance(); |
| 321 ListenerThread* thread = threadlock->GetThread(); |
| 322 |
| 323 // Register ourselves as a listener of signals. |
| 324 bool success = false; |
| 325 if (thread) { |
| 326 success = thread->AddListener(this); |
| 327 } |
| 328 |
| 329 // Send initialization success/fail status to delegate. |
| 330 Task* task = new Listener::ListenerStartedTask(name_, domain_, delegate_, |
| 331 success); |
| 332 CHECK(message_loop_proxy_->PostTask(FROM_HERE, task)); |
| 333 } |
| 334 |
| 335 void ListenerImpl::OnListen(const std::string& name) { |
| 336 // Ignore the signal unless it matches our name. |
| 337 std::string fullname = AddDomainPrefixToNotification(name_, domain_); |
| 338 if (name == fullname) { |
| 339 Task* task = |
| 340 new Listener::NotificationReceivedTask(name_, domain_, delegate_); |
| 341 CHECK(message_loop_proxy_->PostTask(FROM_HERE, task)); |
| 342 } |
46 } | 343 } |
47 | 344 |
48 Listener::Listener(const std::string& name, | 345 Listener::Listener(const std::string& name, |
49 Domain domain, | 346 Domain domain, |
50 Listener::Delegate* delegate) | 347 Listener::Delegate* delegate) |
51 : impl_(new ListenerImpl(name, domain, delegate)) { | 348 : impl_(new ListenerImpl(name, domain, delegate)) { |
52 } | 349 } |
53 | 350 |
54 Listener::~Listener() { | 351 Listener::~Listener() { |
55 } | 352 } |
56 | 353 |
57 bool Listener::Start(MessageLoop* io_loop_to_listen_on) { | 354 bool Listener::Start(MessageLoop* io_loop_to_listen_on) { |
58 return impl_->Start(io_loop_to_listen_on); | 355 return impl_->Start(io_loop_to_listen_on); |
59 } | 356 } |
60 | 357 |
61 std::string Listener::name() const { | 358 std::string Listener::name() const { |
62 return impl_->name(); | 359 return impl_->name(); |
63 } | 360 } |
64 | 361 |
65 Domain Listener::domain() const { | 362 Domain Listener::domain() const { |
66 return impl_->domain(); | 363 return impl_->domain(); |
67 } | 364 } |
68 | 365 |
| 366 ListenerThreadLock* ListenerThreadLock::GetInstance() { |
| 367 return Singleton<ListenerThreadLock>::get(); |
| 368 } |
| 369 |
| 370 ListenerThreadLock::ListenerThreadLock() |
| 371 : thread_(NULL) { |
| 372 } |
| 373 |
| 374 base::Lock& ListenerThreadLock::GetLock() { |
| 375 return thread_lock_; |
| 376 } |
| 377 |
| 378 ListenerThread* ListenerThreadLock::GetThread() { |
| 379 base::AutoLock autolock(thread_lock_); |
| 380 if (!thread_) { |
| 381 thread_ = new ListenerThread(); |
| 382 if (!thread_) { |
| 383 LOG(ERROR) << "Unable to create ListenerThread"; |
| 384 return NULL; |
| 385 } |
| 386 if (thread_->Init()) { |
| 387 thread_->Start(); |
| 388 } else { |
| 389 LOG(ERROR) << "Unable to start dbus ListenerThread"; |
| 390 delete thread_; |
| 391 thread_ = NULL; |
| 392 } |
| 393 } |
| 394 return thread_; |
| 395 } |
| 396 |
69 } // namespace multi_process_notification | 397 } // namespace multi_process_notification |
| 398 |
| 399 DISABLE_RUNNABLE_METHOD_REFCOUNT(multi_process_notification::ListenerImpl); |
OLD | NEW |