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