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 entry; 212 while (directory.GetNextDirents(&entry, sizeof(entry), 1) == 1) { 213 if (strcmp(entry.d_name, ".") != 0 214 && strcmp(entry.d_name, "..") != 0) { 215 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, 216 node_ref(entry.d_dev, entry.d_ino), 217 NotOwningEntryRef(entry.d_pdev, entry.d_pino, 218 entry.d_name), 219 NULL, false); 220 } 221 } 222 return true; 223 } 224 225 // See, if this entry actually becomes visible. If not, we can simply ignore 226 // it. 227 struct stat st; 228 entry_ref visibleEntryRef; 229 if (!_GetEntry(entryRef.name, visibleEntryRef, &st) 230 || visibleEntryRef != entryRef) { 231 return true; 232 } 233 234 // If it is a directory, translate it. 235 VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance(); 236 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 237 238 bool entryTranslated = S_ISDIR(st.st_mode); 239 if (entryTranslated) { 240 if (manager == NULL) 241 return true; 242 243 if (manager->TranslateDirectoryEntry(*TargetModel()->NodeRef(), 244 entryRef, nodeRef) != B_OK) { 245 return true; 246 } 247 } 248 249 // The entry might replace another entry. If it does, we'll fake a removed 250 // message for the old one first. 251 BPose* pose = fPoseList->FindPoseByFileName(entryRef.name); 252 if (pose != NULL) { 253 if (nodeRef == *pose->TargetModel()->NodeRef()) { 254 // apparently not really a new entry -- can happen for 255 // subdirectories 256 return true; 257 } 258 259 // It may be a directory, so tell the manager. 260 if (manager != NULL) 261 manager->DirectoryRemoved(*pose->TargetModel()->NodeRef()); 262 263 managerLocker.Unlock(); 264 265 BMessage removedMessage(B_NODE_MONITOR); 266 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, 267 *pose->TargetModel()->NodeRef(), *pose->TargetModel()->EntryRef()); 268 } else 269 managerLocker.Unlock(); 270 271 return entryTranslated 272 ? (_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, nodeRef, 273 entryRef), true) 274 : _inherited::FSNotification(message); 275 } 276 277 278 bool 279 VirtualDirectoryPoseView::_EntryRemoved(const BMessage* message) 280 { 281 NotOwningEntryRef entryRef; 282 node_ref nodeRef; 283 284 if (message->FindInt32("device", &nodeRef.device) != B_OK 285 || message->FindInt64("node", &nodeRef.node) != B_OK 286 || message->FindInt64("directory", &entryRef.directory) 287 != B_OK 288 || message->FindString("name", (const char**)&entryRef.name) != B_OK) { 289 return true; 290 } 291 entryRef.device = nodeRef.device; 292 293 // It might be our definition file. 294 if (nodeRef == *TargetModel()->NodeRef()) 295 return _inherited::FSNotification(message); 296 297 // It might be one of our directories. 298 BString path; 299 if (message->FindString("path", &path) == B_OK 300 && fDirectoryPaths.HasString(path)) { 301 // Find all poses that stem from that directory and generate an 302 // entry-removed message for each. 303 PoseList poses; 304 for (int32 i = 0; BPose* pose = fPoseList->ItemAt(i); i++) { 305 NotOwningEntryRef poseEntryRef = *pose->TargetModel()->EntryRef(); 306 if (poseEntryRef.DirectoryNodeRef() == nodeRef) 307 poses.AddItem(pose); 308 } 309 310 for (int32 i = 0; BPose* pose = poses.ItemAt(i); i++) { 311 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, 312 *pose->TargetModel()->NodeRef(), 313 *pose->TargetModel()->EntryRef(), NULL, false); 314 } 315 316 return true; 317 } 318 319 // If it is a directory, translate it. 320 entry_ref* actualEntryRef = &entryRef; 321 node_ref* actualNodeRef = &nodeRef; 322 entry_ref definitionEntryRef; 323 node_ref definitionNodeRef; 324 325 VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance(); 326 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 327 328 if (manager != NULL 329 && manager->GetSubDirectoryDefinitionFile(*TargetModel()->NodeRef(), 330 entryRef.name, definitionEntryRef, definitionNodeRef)) { 331 actualEntryRef = &definitionEntryRef; 332 actualNodeRef = &definitionNodeRef; 333 } 334 335 // Check the pose. It might have been an entry that wasn't visible anyway. 336 // In that case we can just ignore the notification. 337 BPose* pose = fPoseList->FindPoseByFileName(actualEntryRef->name); 338 if (pose == NULL || *actualNodeRef != *pose->TargetModel()->NodeRef()) 339 return true; 340 341 // See, if another entry becomes visible, now. 342 struct stat st; 343 entry_ref visibleEntryRef; 344 node_ref visibleNodeRef; 345 if (_GetEntry(actualEntryRef->name, visibleEntryRef, &st)) { 346 // If the new entry is a directory, translate it. 347 visibleNodeRef = node_ref(st.st_dev, st.st_ino); 348 if (S_ISDIR(st.st_mode)) { 349 if (manager == NULL || manager->TranslateDirectoryEntry( 350 *TargetModel()->NodeRef(), visibleEntryRef, visibleNodeRef) 351 != B_OK) { 352 return true; 353 } 354 355 // Effectively nothing changes, when the removed entry was a 356 // directory as well. 357 if (visibleNodeRef == *actualNodeRef) 358 return true; 359 } 360 } 361 362 if (actualEntryRef == &entryRef) { 363 managerLocker.Unlock(); 364 if (_inherited::FSNotification(message)) 365 pendingNodeMonitorCache.Add(message); 366 } else { 367 // tell the manager that the directory has been removed 368 manager->DirectoryRemoved(*actualNodeRef); 369 managerLocker.Unlock(); 370 371 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, *actualNodeRef, 372 *actualEntryRef); 373 } 374 375 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, visibleNodeRef, 376 visibleEntryRef); 377 378 return true; 379 } 380 381 382 bool 383 VirtualDirectoryPoseView::_EntryMoved(const BMessage* message) 384 { 385 NotOwningEntryRef fromEntryRef; 386 NotOwningEntryRef toEntryRef; 387 node_ref nodeRef; 388 389 if (message->FindInt32("node device", &nodeRef.device) != B_OK 390 || message->FindInt64("node", &nodeRef.node) != B_OK 391 || message->FindInt32("device", &fromEntryRef.device) != B_OK 392 || message->FindInt64("from directory", &fromEntryRef.directory) != B_OK 393 || message->FindInt64("to directory", &toEntryRef.directory) != B_OK 394 || message->FindString("from name", (const char**)&fromEntryRef.name) 395 != B_OK 396 || message->FindString("name", (const char**)&toEntryRef.name) 397 != B_OK) { 398 return true; 399 } 400 toEntryRef.device = fromEntryRef.device; 401 402 // TODO: That's the lazy approach. Ideally we'd analyze the situation and 403 // forward a B_ENTRY_MOVED, if possible. There are quite a few cases to 404 // consider, though. 405 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, nodeRef, 406 fromEntryRef, message->GetString("from path", NULL), false); 407 _DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, nodeRef, 408 toEntryRef, message->GetString("path", NULL), false); 409 410 return true; 411 } 412 413 414 bool 415 VirtualDirectoryPoseView::_NodeStatChanged(const BMessage* message) 416 { 417 node_ref nodeRef; 418 if (message->FindInt32("device", &nodeRef.device) != B_OK 419 || message->FindInt64("node", &nodeRef.node) != B_OK) { 420 return true; 421 } 422 423 if (nodeRef == fRootDefinitionFileRef) { 424 if ((message->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) != 0) { 425 VirtualDirectoryManager* manager 426 = VirtualDirectoryManager::Instance(); 427 if (manager != NULL) { 428 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 429 if (!manager->DefinitionFileChanged( 430 *TargetModel()->NodeRef())) { 431 // The definition file no longer exists. Ignore the message 432 // -- we'll get a remove notification soon. 433 return true; 434 } 435 436 bigtime_t fileChangeTime; 437 manager->GetDefinitionFileChangeTime(*TargetModel()->NodeRef(), 438 fileChangeTime); 439 if (fileChangeTime != fFileChangeTime) { 440 _UpdateDirectoryPaths(); 441 managerLocker.Unlock(); 442 Refresh(); 443 // TODO: Refresh() is rather radical. Or rather its 444 // implementation is. Ideally it would just compare the 445 // currently added poses with what a new dir iterator 446 // returns and remove/add poses as needed. 447 } 448 } 449 } 450 451 if (!fIsRoot) 452 return true; 453 } 454 455 return _inherited::FSNotification(message); 456 } 457 458 459 void 460 VirtualDirectoryPoseView::_DispatchEntryCreatedOrRemovedMessage(int32 opcode, 461 const node_ref& nodeRef, const entry_ref& entryRef, const char* path, 462 bool dispatchToSuperClass) 463 { 464 BMessage message(B_NODE_MONITOR); 465 message.AddInt32("opcode", opcode); 466 message.AddInt32("device", nodeRef.device); 467 message.AddInt64("node", nodeRef.node); 468 message.AddInt64("directory", entryRef.directory); 469 message.AddString("name", entryRef.name); 470 if (path != NULL && path[0] != '\0') 471 message.AddString("path", path); 472 bool result = dispatchToSuperClass 473 ? _inherited::FSNotification(&message) 474 : FSNotification(&message); 475 if (!result) 476 pendingNodeMonitorCache.Add(&message); 477 } 478 479 480 bool 481 VirtualDirectoryPoseView::_GetEntry(const char* name, entry_ref& _ref, 482 struct stat* _st) 483 { 484 return VirtualDirectoryManager::GetEntry(fDirectoryPaths, name, &_ref, _st); 485 } 486 487 488 status_t 489 VirtualDirectoryPoseView::_UpdateDirectoryPaths() 490 { 491 VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance(); 492 Model* model = TargetModel(); 493 status_t error = manager->ResolveDirectoryPaths(*model->NodeRef(), 494 *model->EntryRef(), fDirectoryPaths); 495 if (error != B_OK) 496 return error; 497 498 manager->GetDefinitionFileChangeTime(*model->NodeRef(), fFileChangeTime); 499 return B_OK; 500 } 501 502 } // namespace BPrivate 503