1 /* 2 * Copyright 2013 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Ingo Weinhold, ingo_weinhold@gmx.de 7 */ 8 9 10 #include "VirtualDirectoryPoseView.h" 11 12 #include <new> 13 14 #include <AutoLocker.h> 15 #include <NotOwningEntryRef.h> 16 #include <PathMonitor.h> 17 #include <storage_support.h> 18 19 #include "Commands.h" 20 #include "Tracker.h" 21 #include "VirtualDirectoryEntryList.h" 22 #include "VirtualDirectoryManager.h" 23 24 25 namespace BPrivate { 26 27 // #pragma mark - VirtualDirectoryPoseView 28 29 30 VirtualDirectoryPoseView::VirtualDirectoryPoseView(Model* model) 31 : 32 BPoseView(model, kListMode), 33 fDirectoryPaths(), 34 fRootDefinitionFileRef(-1, -1), 35 fFileChangeTime(-1), 36 fIsRoot(false) 37 { 38 VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance(); 39 if (manager == NULL) 40 return; 41 42 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 43 if (_UpdateDirectoryPaths() != B_OK) 44 return; 45 46 manager->GetRootDefinitionFile(*model->NodeRef(), fRootDefinitionFileRef); 47 fIsRoot = fRootDefinitionFileRef == *model->NodeRef(); 48 } 49 50 51 VirtualDirectoryPoseView::~VirtualDirectoryPoseView() 52 { 53 } 54 55 56 void 57 VirtualDirectoryPoseView::MessageReceived(BMessage* message) 58 { 59 if (message->WasDropped()) 60 return _inherited::MessageReceived(message); 61 62 switch (message->what) { 63 // ignore all edit operations 64 case B_CUT: 65 case B_PASTE: 66 case kCutMoreSelectionToClipboard: 67 case kDeleteSelection: 68 case kDuplicateSelection: 69 case kMoveSelectionToTrash: 70 case kNewEntryFromTemplate: 71 case kNewFolder: 72 break; 73 74 default: 75 _inherited::MessageReceived(message); 76 break; 77 } 78 } 79 80 81 void 82 VirtualDirectoryPoseView::AttachedToWindow() 83 { 84 _inherited::AttachedToWindow(); 85 AddFilter(new TPoseViewFilter(this)); 86 } 87 88 89 void 90 VirtualDirectoryPoseView::RestoreState(AttributeStreamNode* node) 91 { 92 _inherited::RestoreState(node); 93 fViewState->SetViewMode(kListMode); 94 } 95 96 97 void 98 VirtualDirectoryPoseView::RestoreState(const BMessage& message) 99 { 100 _inherited::RestoreState(message); 101 fViewState->SetViewMode(kListMode); 102 } 103 104 105 void 106 VirtualDirectoryPoseView::SavePoseLocations(BRect* frameIfDesktop) 107 { 108 } 109 110 111 void 112 VirtualDirectoryPoseView::SetViewMode(uint32 newMode) 113 { 114 } 115 116 117 EntryListBase* 118 VirtualDirectoryPoseView::InitDirentIterator(const entry_ref* ref) 119 { 120 if (fRootDefinitionFileRef.node < 0 || *ref != *TargetModel()->EntryRef()) 121 return NULL; 122 123 Model sourceModel(ref, false, true); 124 if (sourceModel.InitCheck() != B_OK) 125 return NULL; 126 127 VirtualDirectoryEntryList* entryList 128 = new(std::nothrow) VirtualDirectoryEntryList( 129 *TargetModel()->NodeRef(), fDirectoryPaths); 130 if (entryList == NULL || entryList->InitCheck() != B_OK) { 131 delete entryList; 132 return NULL; 133 } 134 135 return entryList; 136 } 137 138 139 void 140 VirtualDirectoryPoseView::StartWatching() 141 { 142 // watch the directories 143 int32 count = fDirectoryPaths.CountStrings(); 144 for (int32 i = 0; i < count; i++) { 145 BString path = fDirectoryPaths.StringAt(i); 146 BPathMonitor::StartWatching(path, B_WATCH_DIRECTORY, this); 147 } 148 149 // watch the definition file 150 TTracker::WatchNode(TargetModel()->NodeRef(), 151 B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, this); 152 153 // also watch the root definition file 154 if (!fIsRoot) 155 TTracker::WatchNode(&fRootDefinitionFileRef, B_WATCH_STAT, this); 156 } 157 158 159 void 160 VirtualDirectoryPoseView::StopWatching() 161 { 162 BPathMonitor::StopWatching(this); 163 stop_watching(this); 164 } 165 166 167 bool 168 VirtualDirectoryPoseView::FSNotification(const BMessage* message) 169 { 170 switch (message->GetInt32("opcode", 0)) { 171 case B_ENTRY_CREATED: 172 return _EntryCreated(message); 173 174 case B_ENTRY_REMOVED: 175 return _EntryRemoved(message); 176 177 case B_ENTRY_MOVED: 178 return _EntryMoved(message); 179 180 case B_STAT_CHANGED: 181 return _NodeStatChanged(message); 182 183 default: 184 return _inherited::FSNotification(message); 185 } 186 } 187 188 189 bool 190 VirtualDirectoryPoseView::_EntryCreated(const BMessage* message) 191 { 192 NotOwningEntryRef entryRef; 193 node_ref nodeRef; 194 195 if (message->FindInt32("device", &nodeRef.device) != B_OK 196 || message->FindInt64("node", &nodeRef.node) != B_OK 197 || message->FindInt64("directory", &entryRef.directory) != B_OK 198 || message->FindString("name", (const char**)&entryRef.name) != B_OK) { 199 return true; 200 } 201 entryRef.device = nodeRef.device; 202 203 // It might be one of our directories. 204 BString path; 205 if (message->FindString("path", &path) == B_OK 206 && fDirectoryPaths.HasString(path)) { 207 // Iterate through the directory and generate an entry-created message 208 // for each entry. 209 BDirectory directory; 210 if (directory.SetTo(&nodeRef) != B_OK) 211 return true; 212 213 BPrivate::Storage::LongDirEntry longEntry; 214 struct dirent* entry = longEntry.dirent(); 215 while (directory.GetNextDirents(entry, sizeof(longEntry), 1) == 1) { 216 if (strcmp(entry->d_name, ".") != 0 217 && strcmp(entry->d_name, "..") != 0) { 218 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, 219 node_ref(entry->d_dev, entry->d_ino), 220 NotOwningEntryRef(entry->d_pdev, entry->d_pino, 221 entry->d_name), 222 NULL, false); 223 } 224 } 225 return true; 226 } 227 228 // See, if this entry actually becomes visible. If not, we can simply ignore 229 // it. 230 struct stat st; 231 entry_ref visibleEntryRef; 232 if (!_GetEntry(entryRef.name, visibleEntryRef, &st) 233 || visibleEntryRef != entryRef) { 234 return true; 235 } 236 237 // If it is a directory, translate it. 238 VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance(); 239 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 240 241 bool entryTranslated = S_ISDIR(st.st_mode); 242 if (entryTranslated) { 243 if (manager == NULL) 244 return true; 245 246 if (manager->TranslateDirectoryEntry(*TargetModel()->NodeRef(), 247 entryRef, nodeRef) != B_OK) { 248 return true; 249 } 250 } 251 252 // The entry might replace another entry. If it does, we'll fake a removed 253 // message for the old one first. 254 BPose* pose = fPoseList->FindPoseByFileName(entryRef.name); 255 if (pose != NULL) { 256 if (nodeRef == *pose->TargetModel()->NodeRef()) { 257 // apparently not really a new entry -- can happen for 258 // subdirectories 259 return true; 260 } 261 262 // It may be a directory, so tell the manager. 263 if (manager != NULL) 264 manager->DirectoryRemoved(*pose->TargetModel()->NodeRef()); 265 266 managerLocker.Unlock(); 267 268 BMessage removedMessage(B_NODE_MONITOR); 269 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, 270 *pose->TargetModel()->NodeRef(), *pose->TargetModel()->EntryRef()); 271 } else 272 managerLocker.Unlock(); 273 274 return entryTranslated 275 ? (_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, nodeRef, 276 entryRef), true) 277 : _inherited::FSNotification(message); 278 } 279 280 281 bool 282 VirtualDirectoryPoseView::_EntryRemoved(const BMessage* message) 283 { 284 NotOwningEntryRef entryRef; 285 node_ref nodeRef; 286 287 if (message->FindInt32("device", &nodeRef.device) != B_OK 288 || message->FindInt64("node", &nodeRef.node) != B_OK 289 || message->FindInt64("directory", &entryRef.directory) 290 != B_OK 291 || message->FindString("name", (const char**)&entryRef.name) != B_OK) { 292 return true; 293 } 294 entryRef.device = nodeRef.device; 295 296 // It might be our definition file. 297 if (nodeRef == *TargetModel()->NodeRef()) 298 return _inherited::FSNotification(message); 299 300 // It might be one of our directories. 301 BString path; 302 if (message->FindString("path", &path) == B_OK 303 && fDirectoryPaths.HasString(path)) { 304 // Find all poses that stem from that directory and generate an 305 // entry-removed message for each. 306 PoseList poses; 307 for (int32 i = 0; BPose* pose = fPoseList->ItemAt(i); i++) { 308 NotOwningEntryRef poseEntryRef = *pose->TargetModel()->EntryRef(); 309 if (poseEntryRef.DirectoryNodeRef() == nodeRef) 310 poses.AddItem(pose); 311 } 312 313 for (int32 i = 0; BPose* pose = poses.ItemAt(i); i++) { 314 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, 315 *pose->TargetModel()->NodeRef(), 316 *pose->TargetModel()->EntryRef(), NULL, false); 317 } 318 319 return true; 320 } 321 322 // If it is a directory, translate it. 323 entry_ref* actualEntryRef = &entryRef; 324 node_ref* actualNodeRef = &nodeRef; 325 entry_ref definitionEntryRef; 326 node_ref definitionNodeRef; 327 328 VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance(); 329 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 330 331 if (manager != NULL 332 && manager->GetSubDirectoryDefinitionFile(*TargetModel()->NodeRef(), 333 entryRef.name, definitionEntryRef, definitionNodeRef)) { 334 actualEntryRef = &definitionEntryRef; 335 actualNodeRef = &definitionNodeRef; 336 } 337 338 // Check the pose. It might have been an entry that wasn't visible anyway. 339 // In that case we can just ignore the notification. 340 BPose* pose = fPoseList->FindPoseByFileName(actualEntryRef->name); 341 if (pose == NULL || *actualNodeRef != *pose->TargetModel()->NodeRef()) 342 return true; 343 344 // See, if another entry becomes visible, now. 345 struct stat st; 346 entry_ref visibleEntryRef; 347 node_ref visibleNodeRef; 348 if (_GetEntry(actualEntryRef->name, visibleEntryRef, &st)) { 349 // If the new entry is a directory, translate it. 350 visibleNodeRef = node_ref(st.st_dev, st.st_ino); 351 if (S_ISDIR(st.st_mode)) { 352 if (manager == NULL || manager->TranslateDirectoryEntry( 353 *TargetModel()->NodeRef(), visibleEntryRef, visibleNodeRef) 354 != B_OK) { 355 return true; 356 } 357 358 // Effectively nothing changes, when the removed entry was a 359 // directory as well. 360 if (visibleNodeRef == *actualNodeRef) 361 return true; 362 } 363 } 364 365 if (actualEntryRef == &entryRef) { 366 managerLocker.Unlock(); 367 if (_inherited::FSNotification(message)) 368 pendingNodeMonitorCache.Add(message); 369 } else { 370 // tell the manager that the directory has been removed 371 manager->DirectoryRemoved(*actualNodeRef); 372 managerLocker.Unlock(); 373 374 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, *actualNodeRef, 375 *actualEntryRef); 376 } 377 378 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, visibleNodeRef, 379 visibleEntryRef); 380 381 return true; 382 } 383 384 385 bool 386 VirtualDirectoryPoseView::_EntryMoved(const BMessage* message) 387 { 388 NotOwningEntryRef fromEntryRef; 389 NotOwningEntryRef toEntryRef; 390 node_ref nodeRef; 391 392 if (message->FindInt32("node device", &nodeRef.device) != B_OK 393 || message->FindInt64("node", &nodeRef.node) != B_OK 394 || message->FindInt32("device", &fromEntryRef.device) != B_OK 395 || message->FindInt64("from directory", &fromEntryRef.directory) != B_OK 396 || message->FindInt64("to directory", &toEntryRef.directory) != B_OK 397 || message->FindString("from name", (const char**)&fromEntryRef.name) 398 != B_OK 399 || message->FindString("name", (const char**)&toEntryRef.name) 400 != B_OK) { 401 return true; 402 } 403 toEntryRef.device = fromEntryRef.device; 404 405 // TODO: That's the lazy approach. Ideally we'd analyze the situation and 406 // forward a B_ENTRY_MOVED, if possible. There are quite a few cases to 407 // consider, though. 408 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, nodeRef, 409 fromEntryRef, message->GetString("from path", NULL), false); 410 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, nodeRef, 411 toEntryRef, message->GetString("path", NULL), false); 412 413 return true; 414 } 415 416 417 bool 418 VirtualDirectoryPoseView::_NodeStatChanged(const BMessage* message) 419 { 420 node_ref nodeRef; 421 if (message->FindInt32("device", &nodeRef.device) != B_OK 422 || message->FindInt64("node", &nodeRef.node) != B_OK) { 423 return true; 424 } 425 426 if (nodeRef == fRootDefinitionFileRef) { 427 if ((message->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) != 0) { 428 VirtualDirectoryManager* manager 429 = VirtualDirectoryManager::Instance(); 430 if (manager != NULL) { 431 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 432 if (!manager->DefinitionFileChanged( 433 *TargetModel()->NodeRef())) { 434 // The definition file no longer exists. Ignore the message 435 // -- we'll get a remove notification soon. 436 return true; 437 } 438 439 bigtime_t fileChangeTime; 440 manager->GetDefinitionFileChangeTime(*TargetModel()->NodeRef(), 441 fileChangeTime); 442 if (fileChangeTime != fFileChangeTime) { 443 _UpdateDirectoryPaths(); 444 managerLocker.Unlock(); 445 Refresh(); 446 // TODO: Refresh() is rather radical. Or rather its 447 // implementation is. Ideally it would just compare the 448 // currently added poses with what a new dir iterator 449 // returns and remove/add poses as needed. 450 } 451 } 452 } 453 454 if (!fIsRoot) 455 return true; 456 } 457 458 return _inherited::FSNotification(message); 459 } 460 461 462 void 463 VirtualDirectoryPoseView::_DispatchEntryCreatedOrRemovedMessage(int32 opcode, 464 const node_ref& nodeRef, const entry_ref& entryRef, const char* path, 465 bool dispatchToSuperClass) 466 { 467 BMessage message(B_NODE_MONITOR); 468 message.AddInt32("opcode", opcode); 469 message.AddInt32("device", nodeRef.device); 470 message.AddInt64("node", nodeRef.node); 471 message.AddInt64("directory", entryRef.directory); 472 message.AddString("name", entryRef.name); 473 if (path != NULL && path[0] != '\0') 474 message.AddString("path", path); 475 bool result = dispatchToSuperClass 476 ? _inherited::FSNotification(&message) 477 : FSNotification(&message); 478 if (!result) 479 pendingNodeMonitorCache.Add(&message); 480 } 481 482 483 bool 484 VirtualDirectoryPoseView::_GetEntry(const char* name, entry_ref& _ref, 485 struct stat* _st) 486 { 487 return VirtualDirectoryManager::GetEntry(fDirectoryPaths, name, &_ref, _st); 488 } 489 490 491 status_t 492 VirtualDirectoryPoseView::_UpdateDirectoryPaths() 493 { 494 VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance(); 495 Model* model = TargetModel(); 496 status_t error = manager->ResolveDirectoryPaths(*model->NodeRef(), 497 *model->EntryRef(), fDirectoryPaths); 498 if (error != B_OK) 499 return error; 500 501 manager->GetDefinitionFileChangeTime(*model->NodeRef(), fFileChangeTime); 502 return B_OK; 503 } 504 505 } // namespace BPrivate 506