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