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" | |
12 #include "base/logging.h" | 7 #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" | |
19 | 8 |
20 namespace multi_process_notification { | 9 namespace multi_process_notification { |
21 | 10 |
22 const char* kNotifyPath = "/modules/notify"; | 11 bool Post(const std::string& name, Domain domain) { |
23 const char* kNotifyInterface = "org.chromium.Notify"; | 12 // TODO(dmaclach): Implement |
24 const char* kNotifySignalName = "Notify"; | 13 NOTIMPLEMENTED(); |
| 14 return false; |
| 15 } |
25 | 16 |
26 // A simple thread to run the DBus main loop. | |
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. | |
78 class ListenerImpl { | 17 class ListenerImpl { |
79 public: | 18 public: |
80 ListenerImpl(const std::string& name, | 19 ListenerImpl(const std::string& name, |
81 Domain domain, | 20 Domain domain, |
82 Listener::Delegate* delegate); | 21 Listener::Delegate* delegate); |
83 ~ListenerImpl(); | |
84 | 22 |
85 bool Start(MessageLoop* io_loop_to_listen_on); | 23 bool Start(MessageLoop* io_loop_to_listen_on); |
86 void OnListen(const std::string& name); | |
87 | 24 |
88 std::string name() const { return name_; } | 25 std::string name() const { return name_; } |
89 Domain domain() const { return domain_; } | 26 Domain domain() const { return domain_; } |
90 | 27 |
91 private: | 28 private: |
92 void StartListener(); | |
93 | |
94 std::string name_; | 29 std::string name_; |
95 Domain domain_; | 30 Domain domain_; |
96 Listener::Delegate* delegate_; | 31 Listener::Delegate* delegate_; |
97 | 32 |
98 DBusError error_; | |
99 DBusConnection* connection_; | |
100 | |
101 scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; | |
102 | |
103 DISALLOW_COPY_AND_ASSIGN(ListenerImpl); | 33 DISALLOW_COPY_AND_ASSIGN(ListenerImpl); |
104 }; | 34 }; |
105 | 35 |
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 | |
287 ListenerImpl::ListenerImpl(const std::string& name, | 36 ListenerImpl::ListenerImpl(const std::string& name, |
288 Domain domain, | 37 Domain domain, |
289 Listener::Delegate* delegate) | 38 Listener::Delegate* delegate) |
290 : name_(name), | 39 : name_(name), domain_(domain), delegate_(delegate) { |
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 } | |
302 } | 40 } |
303 | 41 |
304 bool ListenerImpl::Start(MessageLoop* io_loop_to_listen_on) { | 42 bool ListenerImpl::Start(MessageLoop* io_loop_to_listen_on) { |
305 if (io_loop_to_listen_on->type() != MessageLoop::TYPE_IO) { | 43 // TODO(dmaclach): Implement |
306 DLOG(ERROR) << "io_loop_to_listen_on must be TYPE_IO"; | 44 NOTIMPLEMENTED(); |
307 return false; | 45 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 } | |
343 } | 46 } |
344 | 47 |
345 Listener::Listener(const std::string& name, | 48 Listener::Listener(const std::string& name, |
346 Domain domain, | 49 Domain domain, |
347 Listener::Delegate* delegate) | 50 Listener::Delegate* delegate) |
348 : impl_(new ListenerImpl(name, domain, delegate)) { | 51 : impl_(new ListenerImpl(name, domain, delegate)) { |
349 } | 52 } |
350 | 53 |
351 Listener::~Listener() { | 54 Listener::~Listener() { |
352 } | 55 } |
353 | 56 |
354 bool Listener::Start(MessageLoop* io_loop_to_listen_on) { | 57 bool Listener::Start(MessageLoop* io_loop_to_listen_on) { |
355 return impl_->Start(io_loop_to_listen_on); | 58 return impl_->Start(io_loop_to_listen_on); |
356 } | 59 } |
357 | 60 |
358 std::string Listener::name() const { | 61 std::string Listener::name() const { |
359 return impl_->name(); | 62 return impl_->name(); |
360 } | 63 } |
361 | 64 |
362 Domain Listener::domain() const { | 65 Domain Listener::domain() const { |
363 return impl_->domain(); | 66 return impl_->domain(); |
364 } | 67 } |
365 | 68 |
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 | |
397 } // namespace multi_process_notification | 69 } // namespace multi_process_notification |
398 | |
399 DISABLE_RUNNABLE_METHOD_REFCOUNT(multi_process_notification::ListenerImpl); | |
OLD | NEW |