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