xref: /haiku/src/kits/storage/PathMonitor.cpp (revision 5115ca085884f7b604a3d607688f0ca20fb7cf57)
1 /*
2  * Copyright 2007, Haiku Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  */
8 
9 
10 #include <PathMonitor.h>
11 
12 #include <Application.h>
13 #include <Directory.h>
14 #include <Entry.h>
15 #include <Handler.h>
16 #include <Looper.h>
17 #include <Path.h>
18 
19 #include <set>
20 
21 using namespace BPrivate;
22 using namespace std;
23 
24 
25 #define WATCH_NODE_FLAG_MASK	0x00ff
26 
27 typedef set<node_ref> DirectorySet;
28 
29 namespace BPrivate {
30 
31 class PathHandler : public BHandler {
32 	public:
33 		PathHandler(const char* path, uint32 flags, BMessenger target);
34 		virtual ~PathHandler();
35 
36 		status_t InitCheck() const;
37 		void SetTarget(BMessenger target);
38 		void Quit();
39 
40 		virtual void MessageReceived(BMessage* message);
41 
42 	private:
43 		bool _IsContained(const node_ref& nodeRef) const;
44 		bool _IsContained(BEntry& entry) const;
45 		bool _HasDirectory(const node_ref& nodeRef) const;
46 		void _NotifyTarget(BMessage* message) const;
47 		status_t _AddDirectory(BEntry& entry);
48 		status_t _RemoveDirectory(const node_ref& nodeRef);
49 		status_t _RemoveDirectory(BEntry& entry);
50 
51 		BPath			fPath;
52 		int32			fPathLength;
53 		BMessenger		fTarget;
54 		uint32			fFlags;
55 		status_t		fStatus;
56 		bool			fOwnsLooper;
57 		DirectorySet	fDirectories;
58 };
59 
60 }
61 
62 
63 static status_t
64 set_entry(node_ref& nodeRef, const char* name, BEntry& entry)
65 {
66 	entry_ref ref;
67 	ref.device = nodeRef.device;
68 	ref.directory = nodeRef.node;
69 
70 	status_t status = ref.set_name(name);
71 	if (status != B_OK)
72 		return status;
73 
74 	return entry.SetTo(&ref, true);
75 }
76 
77 
78 bool
79 operator<(const node_ref& a, const node_ref& b)
80 {
81 	if (a.device < b.device)
82 		return true;
83 	if (a.device == b.device && a.node < b.node)
84 		return true;
85 
86 	return false;
87 }
88 
89 
90 //	#pragma mark -
91 
92 
93 PathHandler::PathHandler(const char* path, uint32 flags, BMessenger target)
94 	: BHandler(path),
95 	fPath(path, NULL, true),
96 	fTarget(target),
97 	fFlags(flags),
98 	fOwnsLooper(false)
99 {
100 	fPathLength = strlen(fPath.Path());
101 
102 	BPath first(path);
103 	node_ref nodeRef;
104 
105 	while (true) {
106 		// try to find the first part of the path that exists
107 		BDirectory directory;
108 		fStatus = directory.SetTo(first.Path());
109 		if (fStatus == B_OK) {
110 			fStatus = directory.GetNodeRef(&nodeRef);
111 			if (fStatus == B_OK)
112 				break;
113 		}
114 
115 		if (first.GetParent(&first) != B_OK) {
116 			fStatus = B_ERROR;
117 			break;
118 		}
119 	}
120 
121 	if (fStatus < B_OK)
122 		return;
123 
124 	if (be_app != NULL) {
125 		be_app->AddHandler(this);
126 	} else {
127 		// TODO: only have a single global looper!
128 		BLooper* looper = new BLooper("PathMonitor looper");
129 		looper->Run();
130 		looper->AddHandler(this);
131 		fOwnsLooper = true;
132 	}
133 
134 	fStatus = watch_node(&nodeRef, flags & WATCH_NODE_FLAG_MASK, this);
135 }
136 
137 
138 PathHandler::~PathHandler()
139 {
140 }
141 
142 
143 status_t
144 PathHandler::InitCheck() const
145 {
146 	return fStatus;
147 }
148 
149 
150 void
151 PathHandler::SetTarget(BMessenger target)
152 {
153 	fTarget = target;
154 }
155 
156 
157 void
158 PathHandler::Quit()
159 {
160 	BMessenger me(this);
161 	me.SendMessage(B_QUIT_REQUESTED);
162 }
163 
164 
165 void
166 PathHandler::MessageReceived(BMessage* message)
167 {
168 	switch (message->what) {
169 		case B_NODE_MONITOR:
170 		{
171 			int32 opcode;
172 			if (message->FindInt32("opcode", &opcode) != B_OK)
173 				return;
174 
175 			switch (opcode) {
176 				case B_ENTRY_CREATED:
177 				{
178 					const char* name;
179 					node_ref nodeRef;
180 					if (message->FindInt32("device", &nodeRef.device) != B_OK
181 						|| message->FindInt64("directory", &nodeRef.node) != B_OK
182 						|| message->FindString("name", &name) != B_OK)
183 						break;
184 
185 					BEntry entry;
186 					if (set_entry(nodeRef, name, entry) != B_OK)
187 						break;
188 
189 					bool notify = true;
190 
191 					if (entry.IsDirectory()) {
192 						// a new directory to watch for us
193 						if (_AddDirectory(entry) != B_OK
194 							|| (fFlags & B_WATCH_FILES_ONLY) != 0)
195 							notify = false;
196 					}
197 
198 					if (notify && _IsContained(entry)) {
199 						message->AddBool("added", true);
200 						_NotifyTarget(message);
201 					}
202 					break;
203 				}
204 
205 				case B_ENTRY_MOVED:
206 				{
207 					// has the entry been moved into a monitored directory or has
208 					// it been removed from one?
209 					const char* name;
210 					node_ref nodeRef;
211 					uint64 fromNode;
212 					uint64 node;
213 					if (message->FindInt32("device", &nodeRef.device) != B_OK
214 						|| message->FindInt64("to directory", &nodeRef.node) != B_OK
215 						|| message->FindInt64("from directory", (int64 *)&fromNode) != B_OK
216 						|| message->FindInt64("node", (int64 *)&node) != B_OK
217 						|| message->FindString("name", &name) != B_OK)
218 						break;
219 
220 					BEntry entry;
221 					if (set_entry(nodeRef, name, entry) != B_OK)
222 						break;
223 
224 					bool wasAdded = false;
225 					bool wasRemoved = false;
226 					bool notify = true;
227 
228 					if (_HasDirectory(nodeRef)) {
229 						// something has been added to our watched directories
230 
231 						// test, if the source directory is one of ours as well
232 						nodeRef.node = fromNode;
233 						bool hasFromDirectory = _HasDirectory(nodeRef);
234 
235 						if (entry.IsDirectory()) {
236 							if (!hasFromDirectory) {
237 								// there is a new directory to watch for us
238 								_AddDirectory(entry);
239 								wasAdded = true;
240 							}
241 							if ((fFlags & B_WATCH_FILES_ONLY) != 0)
242 								notify = false;
243 						} else  if (!hasFromDirectory) {
244 							// file has been added
245 							wasAdded = true;
246 						}
247 					} else {
248 						// and entry has been removed from our directories
249 						wasRemoved = true;
250 
251 						if (entry.IsDirectory()) {
252 							_RemoveDirectory(entry);
253 							if ((fFlags & B_WATCH_FILES_ONLY) != 0)
254 								notify = false;
255 						}
256 					}
257 
258 					if (notify && _IsContained(entry)) {
259 						if (wasAdded)
260 							message->AddBool("added", true);
261 						if (wasRemoved)
262 							message->AddBool("removed", true);
263 
264 						_NotifyTarget(message);
265 					}
266 					break;
267 				}
268 
269 				case B_ENTRY_REMOVED:
270 				{
271 					node_ref nodeRef;
272 					uint64 directoryNode;
273 					if (message->FindInt32("device", &nodeRef.device) != B_OK
274 						|| message->FindInt64("directory", (int64 *)&directoryNode) != B_OK
275 						|| message->FindInt64("node", &nodeRef.node) != B_OK)
276 						break;
277 
278 					bool notify = true;
279 
280 					if (_HasDirectory(nodeRef)) {
281 						// the directory has been removed, so we remove it as well
282 						_RemoveDirectory(nodeRef);
283 						if ((fFlags & B_WATCH_FILES_ONLY) != 0)
284 							notify = false;
285 					}
286 
287 					nodeRef.node = directoryNode;
288 					if (notify && _IsContained(nodeRef)) {
289 						message->AddBool("removed", true);
290 						_NotifyTarget(message);
291 					}
292 					break;
293 				}
294 
295 				default:
296 					_NotifyTarget(message);
297 					break;
298 			}
299 			break;
300 		}
301 
302 		case B_QUIT_REQUESTED:
303 		{
304 			BLooper* looper = Looper();
305 
306 			stop_watching(this);
307 			looper->RemoveHandler(this);
308 			delete this;
309 
310 			if (fOwnsLooper)
311 				looper->Quit();
312 			return;
313 		}
314 
315 		default:
316 			BHandler::MessageReceived(message);
317 			break;
318 	}
319 }
320 
321 
322 bool
323 PathHandler::_IsContained(const node_ref& nodeRef) const
324 {
325 	BDirectory directory(&nodeRef);
326 	if (directory.InitCheck() != B_OK)
327 		return false;
328 
329 	BEntry entry;
330 	if (directory.GetEntry(&entry) != B_OK)
331 		return false;
332 
333 	return _IsContained(entry);
334 }
335 
336 
337 bool
338 PathHandler::_IsContained(BEntry& entry) const
339 {
340 	BPath path;
341 	if (entry.GetPath(&path) != B_OK)
342 		return false;
343 
344 	return strncmp(path.Path(), fPath.Path(), fPathLength) == 0;
345 }
346 
347 
348 bool
349 PathHandler::_HasDirectory(const node_ref& nodeRef) const
350 {
351 	DirectorySet::const_iterator iterator = fDirectories.find(nodeRef);
352 	return iterator != fDirectories.end();
353 }
354 
355 
356 void
357 PathHandler::_NotifyTarget(BMessage* message) const
358 {
359 	BMessage update(*message);
360 	update.what = B_PATH_MONITOR;
361 	fTarget.SendMessage(&update);
362 }
363 
364 
365 status_t
366 PathHandler::_AddDirectory(BEntry& entry)
367 {
368 	node_ref nodeRef;
369 	status_t status = entry.GetNodeRef(&nodeRef);
370 	if (status != B_OK)
371 		return status;
372 
373 	// check if we are already know this directory
374 
375 	if (_HasDirectory(nodeRef))
376 		return B_OK;
377 
378 	uint32 flags;
379 	if (_IsContained(entry))
380 		flags = fFlags & WATCH_NODE_FLAG_MASK;
381 	else
382 		flags = B_WATCH_DIRECTORY;
383 
384 	status = watch_node(&nodeRef, flags, this);
385 	if (status != B_OK)
386 		return status;
387 
388 	fDirectories.insert(nodeRef);
389 
390 #if 0
391 	BEntry parent;
392 	if (entry.GetParent(&parent) == B_OK
393 		&& !_IsContained(parent)) {
394 		// TODO: remove parent from watched directories
395 	}
396 #endif
397 	return B_OK;
398 }
399 
400 
401 status_t
402 PathHandler::_RemoveDirectory(const node_ref& nodeRef)
403 {
404 	DirectorySet::iterator iterator = fDirectories.find(nodeRef);
405 	if (iterator == fDirectories.end())
406 		return B_ENTRY_NOT_FOUND;
407 
408 	fDirectories.erase(iterator);
409 	return B_OK;
410 }
411 
412 
413 status_t
414 PathHandler::_RemoveDirectory(BEntry& entry)
415 {
416 	node_ref nodeRef;
417 	status_t status = entry.GetNodeRef(&nodeRef);
418 	if (status != B_OK)
419 		return status;
420 
421 	return _RemoveDirectory(nodeRef);
422 }
423 
424 
425 //	#pragma mark -
426 
427 
428 BPathMonitor::BPathMonitor()
429 	:
430 	fHandler(NULL),
431 	fStatus(B_NO_INIT)
432 {
433 }
434 
435 
436 BPathMonitor::BPathMonitor(const char* path, uint32 flags, BMessenger target)
437 	:
438 	fHandler(NULL),
439 	fStatus(B_NO_INIT)
440 {
441 	SetTo(path, flags, target);
442 }
443 
444 
445 BPathMonitor::~BPathMonitor()
446 {
447 	Unset();
448 }
449 
450 
451 status_t
452 BPathMonitor::InitCheck() const
453 {
454 	return fStatus;
455 }
456 
457 
458 status_t
459 BPathMonitor::SetTo(const char* path, uint32 flags, BMessenger target)
460 {
461 	Unset();
462 
463 	fHandler = new PathHandler(path, flags, target);
464 	status_t status = fHandler->InitCheck();
465 	if (status < B_OK)
466 		Unset();
467 
468 	return fStatus = status;
469 }
470 
471 
472 status_t
473 BPathMonitor::SetTarget(BMessenger target)
474 {
475 	if (fStatus < B_OK)
476 		return B_NO_INIT;
477 
478 	fHandler->SetTarget(target);
479 	return B_OK;
480 }
481 
482 
483 void
484 BPathMonitor::Unset()
485 {
486 	if (fHandler != NULL) {
487 		fHandler->Quit();
488 		fHandler = NULL;
489 	}
490 	fStatus = B_NO_INIT;
491 }
492 
493 //}	// namespace BPrivate
494