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