1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "QueryPoseView.h" 37 38 #include <new> 39 40 #include <Catalog.h> 41 #include <Debug.h> 42 #include <Locale.h> 43 #include <NodeMonitor.h> 44 #include <Query.h> 45 #include <Volume.h> 46 #include <VolumeRoster.h> 47 #include <Window.h> 48 49 #include "Attributes.h" 50 #include "AttributeStream.h" 51 #include "AutoLock.h" 52 #include "Commands.h" 53 #include "FindPanel.h" 54 #include "FSUtils.h" 55 #include "MimeTypeList.h" 56 #include "MimeTypes.h" 57 #include "Tracker.h" 58 59 #include <fs_attr.h> 60 61 62 using std::nothrow; 63 64 65 #undef B_TRANSLATION_CONTEXT 66 #define B_TRANSLATION_CONTEXT "QueryPoseView" 67 68 69 // Currently filtering out Trash doesn't node monitor too well - if you 70 // remove an item from the Trash, it doesn't show up in the query result 71 // To do this properly, we would have to node monitor everything BQuery 72 // returns and after a node monitor re-chech if it should be part of 73 // query results and add/remove appropriately. Right now only moving to 74 // Trash is supported 75 76 77 // #pragma mark - BQueryPoseView 78 79 80 BQueryPoseView::BQueryPoseView(Model* model, BRect frame, uint32 resizeMask) 81 : 82 BPoseView(model, frame, kListMode, resizeMask), 83 fShowResultsFromTrash(false), 84 fQueryList(NULL), 85 fQueryListContainer(NULL), 86 fCreateOldPoseList(false) 87 { 88 } 89 90 91 BQueryPoseView::~BQueryPoseView() 92 { 93 delete fQueryListContainer; 94 } 95 96 97 void 98 BQueryPoseView::MessageReceived(BMessage* message) 99 { 100 switch (message->what) { 101 case kFSClipboardChanges: 102 { 103 // poses have always to be updated for the query view 104 UpdatePosesClipboardModeFromClipboard(message); 105 break; 106 } 107 108 default: 109 _inherited::MessageReceived(message); 110 break; 111 } 112 } 113 114 115 void 116 BQueryPoseView::EditQueries() 117 { 118 BMessage message(kEditQuery); 119 message.AddRef("refs", TargetModel()->EntryRef()); 120 BMessenger(kTrackerSignature, -1, 0).SendMessage(&message); 121 } 122 123 124 void 125 BQueryPoseView::SetUpDefaultColumnsIfNeeded() 126 { 127 // in case there were errors getting some columns 128 if (fColumnList->CountItems() != 0) 129 return; 130 131 fColumnList->AddItem(new BColumn(B_TRANSLATE("Name"), kColumnStart, 145, 132 B_ALIGN_LEFT, kAttrStatName, B_STRING_TYPE, true, true)); 133 fColumnList->AddItem(new BColumn(B_TRANSLATE("Location"), 200, 225, 134 B_ALIGN_LEFT, kAttrPath, B_STRING_TYPE, true, false)); 135 fColumnList->AddItem(new BColumn(B_TRANSLATE("Size"), 440, 80, 136 B_ALIGN_RIGHT, kAttrStatSize, B_OFF_T_TYPE, true, false)); 137 fColumnList->AddItem(new BColumn(B_TRANSLATE("Modified"), 535, 150, 138 B_ALIGN_LEFT, kAttrStatModified, B_TIME_TYPE, true, false)); 139 } 140 141 142 void 143 BQueryPoseView::AttachedToWindow() 144 { 145 _inherited::AttachedToWindow(); 146 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 147 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 148 } 149 150 151 void 152 BQueryPoseView::RestoreState(AttributeStreamNode* node) 153 { 154 _inherited::RestoreState(node); 155 fViewState->SetViewMode(kListMode); 156 } 157 158 159 void 160 BQueryPoseView::RestoreState(const BMessage &message) 161 { 162 _inherited::RestoreState(message); 163 fViewState->SetViewMode(kListMode); 164 } 165 166 167 void 168 BQueryPoseView::SavePoseLocations(BRect*) 169 { 170 } 171 172 173 void 174 BQueryPoseView::SetViewMode(uint32) 175 { 176 } 177 178 179 void 180 BQueryPoseView::OpenParent() 181 { 182 } 183 184 185 void 186 BQueryPoseView::Refresh() 187 { 188 PRINT(("refreshing dynamic date query\n")); 189 190 // cause the old AddPosesTask to die 191 fAddPosesThreads.clear(); 192 delete fQueryListContainer; 193 fQueryListContainer = NULL; 194 195 fCreateOldPoseList = true; 196 AddPoses(TargetModel()); 197 TargetModel()->CloseNode(); 198 199 ResetOrigin(); 200 ResetPosePlacementHint(); 201 } 202 203 204 bool 205 BQueryPoseView::ShouldShowPose(const Model* model, const PoseInfo* poseInfo) 206 { 207 // add_poses, etc. filter 208 ASSERT(TargetModel()); 209 210 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 211 if (!fShowResultsFromTrash && tracker != NULL 212 && tracker->InTrashNode(model->EntryRef())) { 213 return false; 214 } 215 216 bool result = _inherited::ShouldShowPose(model, poseInfo); 217 218 PoseList* oldPoseList = fQueryListContainer->OldPoseList(); 219 if (result && oldPoseList != NULL) { 220 // pose will get added - remove it from the old pose list 221 // because it is supposed to be showing 222 BPose* pose = oldPoseList->FindPose(model); 223 if (pose != NULL) 224 oldPoseList->RemoveItem(pose); 225 } 226 227 return result; 228 } 229 230 231 void 232 BQueryPoseView::AddPosesCompleted() 233 { 234 ASSERT(Window()->IsLocked()); 235 236 PoseList* oldPoseList = fQueryListContainer->OldPoseList(); 237 if (oldPoseList != NULL) { 238 int32 count = oldPoseList->CountItems(); 239 for (int32 index = count - 1; index >= 0; index--) { 240 BPose* pose = oldPoseList->ItemAt(index); 241 DeletePose(pose->TargetModel()->NodeRef()); 242 } 243 fQueryListContainer->ClearOldPoseList(); 244 } 245 246 _inherited::AddPosesCompleted(); 247 } 248 249 250 // When using dynamic dates, such as "today", need to refresh the query 251 // window every now and then 252 253 EntryListBase* 254 BQueryPoseView::InitDirentIterator(const entry_ref* ref) 255 { 256 BEntry entry(ref); 257 if (entry.InitCheck() != B_OK) 258 return NULL; 259 260 Model sourceModel(&entry, true); 261 if (sourceModel.InitCheck() != B_OK) 262 return NULL; 263 264 ASSERT(sourceModel.IsQuery()); 265 266 // old pose list is used for finding poses that no longer match a 267 // dynamic date query during a Refresh call 268 PoseList* oldPoseList = NULL; 269 if (fCreateOldPoseList) { 270 oldPoseList = new PoseList(10, false); 271 oldPoseList->AddList(fPoseList); 272 } 273 274 fQueryListContainer = new QueryEntryListCollection(&sourceModel, this, 275 oldPoseList); 276 fCreateOldPoseList = false; 277 278 if (fQueryListContainer->InitCheck() != B_OK) { 279 delete fQueryListContainer; 280 fQueryListContainer = NULL; 281 return NULL; 282 } 283 284 fShowResultsFromTrash = fQueryListContainer->ShowResultsFromTrash(); 285 286 TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_NAME | B_WATCH_STAT 287 | B_WATCH_ATTR, this); 288 289 fQueryList = fQueryListContainer->QueryList(); 290 291 if (fQueryListContainer->DynamicDateQuery()) { 292 // calculate the time to trigger the query refresh - next midnight 293 294 time_t now = time(0); 295 296 time_t nextMidnight = now + 60 * 60 * 24; 297 // move ahead by a day 298 tm timeData; 299 localtime_r(&nextMidnight, &timeData); 300 timeData.tm_sec = 0; 301 timeData.tm_min = 0; 302 timeData.tm_hour = 0; 303 nextMidnight = mktime(&timeData); 304 305 time_t nextHour = now + 60 * 60; 306 // move ahead by a hour 307 localtime_r(&nextHour, &timeData); 308 timeData.tm_sec = 0; 309 timeData.tm_min = 0; 310 nextHour = mktime(&timeData); 311 312 PRINT(("%" B_PRId32 " minutes, %" B_PRId32 " seconds till next hour\n", 313 (nextHour - now) / 60, (nextHour - now) % 60)); 314 315 time_t nextMinute = now + 60; 316 // move ahead by a minute 317 localtime_r(&nextMinute, &timeData); 318 timeData.tm_sec = 0; 319 nextMinute = mktime(&timeData); 320 321 PRINT(("%" B_PRId32 " seconds till next minute\n", nextMinute - now)); 322 323 bigtime_t delta; 324 if (fQueryListContainer->DynamicDateRefreshEveryMinute()) 325 delta = nextMinute - now; 326 else if (fQueryListContainer->DynamicDateRefreshEveryHour()) 327 delta = nextHour - now; 328 else 329 delta = nextMidnight - now; 330 331 #if DEBUG 332 int32 secondsTillMidnight = (nextMidnight - now); 333 int32 minutesTillMidnight = secondsTillMidnight/60; 334 secondsTillMidnight %= 60; 335 int32 hoursTillMidnight = minutesTillMidnight/60; 336 minutesTillMidnight %= 60; 337 338 PRINT(("%" B_PRId32 " hours, %" B_PRId32 " minutes, %" B_PRId32 339 " seconds till midnight\n", hoursTillMidnight, minutesTillMidnight, 340 secondsTillMidnight)); 341 342 int32 refreshInSeconds = delta % 60; 343 int32 refreshInMinutes = delta / 60; 344 int32 refreshInHours = refreshInMinutes / 60; 345 refreshInMinutes %= 60; 346 347 PRINT(("next refresh in %" B_PRId32 " hours, %" B_PRId32 "minutes, %" 348 B_PRId32 " seconds\n", refreshInHours, refreshInMinutes, 349 refreshInSeconds)); 350 #endif 351 352 // bump up to microseconds 353 delta *= 1000000; 354 355 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 356 ThrowOnAssert(tracker != NULL); 357 358 tracker->MainTaskLoop()->RunLater( 359 NewLockingMemberFunctionObject(&BQueryPoseView::Refresh, this), 360 delta); 361 } 362 363 return fQueryListContainer->Clone(); 364 } 365 366 367 uint32 368 BQueryPoseView::WatchNewNodeMask() 369 { 370 return B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR; 371 } 372 373 374 const char* 375 BQueryPoseView::SearchForType() const 376 { 377 if (!fSearchForMimeType.Length()) { 378 BModelOpener opener(TargetModel()); 379 BString buffer; 380 attr_info attrInfo; 381 382 // read the type of files we are looking for 383 status_t status 384 = TargetModel()->Node()->GetAttrInfo(kAttrQueryInitialMime, 385 &attrInfo); 386 if (status == B_OK) { 387 TargetModel()->Node()->ReadAttrString(kAttrQueryInitialMime, 388 &buffer); 389 } 390 391 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 392 if (tracker != NULL && buffer.Length() > 0) { 393 const ShortMimeInfo* info = tracker->MimeTypes()->FindMimeType( 394 buffer.String()); 395 if (info != NULL) 396 fSearchForMimeType = info->InternalName(); 397 } 398 399 if (!fSearchForMimeType.Length()) 400 fSearchForMimeType = B_FILE_MIMETYPE; 401 } 402 403 return fSearchForMimeType.String(); 404 } 405 406 407 bool 408 BQueryPoseView::ActiveOnDevice(dev_t device) const 409 { 410 int32 count = fQueryList->CountItems(); 411 for (int32 index = 0; index < count; index++) { 412 if (fQueryList->ItemAt(index)->TargetDevice() == device) 413 return true; 414 } 415 416 return false; 417 } 418 419 420 // #pragma mark - QueryEntryListCollection 421 422 423 QueryEntryListCollection::QueryEntryListCollection(Model* model, 424 BHandler* target, PoseList* oldPoseList) 425 : 426 fQueryListRep(new QueryListRep(new BObjectList<BQuery>(5, true))) 427 { 428 Rewind(); 429 attr_info info; 430 BQuery query; 431 432 BNode* modelNode = model->Node(); 433 if (modelNode == NULL) { 434 fStatus = B_ERROR; 435 return; 436 } 437 438 // read the actual query string 439 fStatus = modelNode->GetAttrInfo(kAttrQueryString, &info); 440 if (fStatus != B_OK) 441 return; 442 443 BString buffer; 444 if (modelNode->ReadAttr(kAttrQueryString, B_STRING_TYPE, 0, 445 buffer.LockBuffer((int32)info.size), 446 (size_t)info.size) != info.size) { 447 fStatus = B_ERROR; 448 return; 449 } 450 451 buffer.UnlockBuffer(); 452 453 // read the extra options 454 MoreOptionsStruct saveMoreOptions; 455 if (ReadAttr(modelNode, kAttrQueryMoreOptions, 456 kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions, 457 sizeof(MoreOptionsStruct), 458 &MoreOptionsStruct::EndianSwap) != kReadAttrFailed) { 459 fQueryListRep->fShowResultsFromTrash = saveMoreOptions.searchTrash; 460 } 461 462 fStatus = query.SetPredicate(buffer.String()); 463 464 fQueryListRep->fOldPoseList = oldPoseList; 465 fQueryListRep->fDynamicDateQuery = false; 466 467 fQueryListRep->fRefreshEveryHour = false; 468 fQueryListRep->fRefreshEveryMinute = false; 469 470 if (modelNode->ReadAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0, 471 &fQueryListRep->fDynamicDateQuery, 472 sizeof(bool)) != sizeof(bool)) { 473 fQueryListRep->fDynamicDateQuery = false; 474 } 475 476 if (fQueryListRep->fDynamicDateQuery) { 477 // only refresh every minute on debug builds 478 fQueryListRep->fRefreshEveryMinute = buffer.IFindFirst("second") != -1 479 || buffer.IFindFirst("minute") != -1; 480 fQueryListRep->fRefreshEveryHour = fQueryListRep->fRefreshEveryMinute 481 || buffer.IFindFirst("hour") != -1; 482 483 #if !DEBUG 484 // don't refresh every minute unless we are running debug build 485 fQueryListRep->fRefreshEveryMinute = false; 486 #endif 487 } 488 489 if (fStatus != B_OK) 490 return; 491 492 bool searchAllVolumes = true; 493 status_t result = B_OK; 494 495 // get volumes to perform query on 496 if (modelNode->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) { 497 char* buffer = NULL; 498 499 if ((buffer = (char*)malloc((size_t)info.size)) != NULL 500 && modelNode->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0, 501 buffer, (size_t)info.size) == info.size) { 502 BMessage message; 503 if (message.Unflatten(buffer) == B_OK) { 504 for (int32 index = 0; ;index++) { 505 ASSERT(index < 100); 506 BVolume volume; 507 // match a volume with the info embedded in 508 // the message 509 result = MatchArchivedVolume(&volume, &message, index); 510 if (result == B_OK) { 511 // start the query on this volume 512 result = FetchOneQuery(&query, target, 513 fQueryListRep->fQueryList, &volume); 514 if (result != B_OK) 515 continue; 516 517 searchAllVolumes = false; 518 } else if (result != B_DEV_BAD_DRIVE_NUM) { 519 // if B_DEV_BAD_DRIVE_NUM, the volume just isn't 520 // mounted this time around, keep looking for more 521 // if other error, bail 522 break; 523 } 524 } 525 } 526 } 527 528 free(buffer); 529 } 530 531 if (searchAllVolumes) { 532 // no specific volumes embedded in query, search everything 533 BVolumeRoster roster; 534 BVolume volume; 535 536 roster.Rewind(); 537 while (roster.GetNextVolume(&volume) == B_OK) 538 if (volume.IsPersistent() && volume.KnowsQuery()) { 539 result = FetchOneQuery(&query, target, 540 fQueryListRep->fQueryList, &volume); 541 if (result != B_OK) 542 continue; 543 } 544 } 545 546 fStatus = B_OK; 547 548 return; 549 } 550 551 552 status_t 553 QueryEntryListCollection::FetchOneQuery(const BQuery* copyThis, 554 BHandler* target, BObjectList<BQuery>* list, BVolume* volume) 555 { 556 BQuery* query = new (nothrow) BQuery; 557 if (query == NULL) 558 return B_NO_MEMORY; 559 560 // have to fake a copy constructor here because BQuery doesn't have 561 // a copy constructor 562 BString buffer; 563 const_cast<BQuery*>(copyThis)->GetPredicate(&buffer); 564 query->SetPredicate(buffer.String()); 565 566 query->SetTarget(BMessenger(target)); 567 query->SetVolume(volume); 568 569 status_t result = query->Fetch(); 570 if (result != B_OK) { 571 PRINT(("fetch error %s\n", strerror(result))); 572 delete query; 573 return result; 574 } 575 576 list->AddItem(query); 577 578 return B_OK; 579 } 580 581 582 QueryEntryListCollection::~QueryEntryListCollection() 583 { 584 if (fQueryListRep->CloseQueryList()) 585 delete fQueryListRep; 586 } 587 588 589 QueryEntryListCollection* 590 QueryEntryListCollection::Clone() 591 { 592 fQueryListRep->OpenQueryList(); 593 return new QueryEntryListCollection(*this); 594 } 595 596 597 // #pragma mark - QueryEntryListCollection 598 599 600 QueryEntryListCollection::QueryEntryListCollection( 601 const QueryEntryListCollection &cloneThis) 602 : 603 EntryListBase(), 604 fQueryListRep(cloneThis.fQueryListRep) 605 { 606 // only to be used by the Clone routine 607 } 608 609 610 void 611 QueryEntryListCollection::ClearOldPoseList() 612 { 613 delete fQueryListRep->fOldPoseList; 614 fQueryListRep->fOldPoseList = NULL; 615 } 616 617 618 status_t 619 QueryEntryListCollection::GetNextEntry(BEntry* entry, bool traverse) 620 { 621 status_t result = B_ERROR; 622 623 for (int32 count = fQueryListRep->fQueryList->CountItems(); 624 fQueryListRep->fQueryListIndex < count; 625 fQueryListRep->fQueryListIndex++) { 626 result = fQueryListRep->fQueryList-> 627 ItemAt(fQueryListRep->fQueryListIndex)-> 628 GetNextEntry(entry, traverse); 629 if (result == B_OK) 630 break; 631 } 632 633 return result; 634 } 635 636 637 int32 638 QueryEntryListCollection::GetNextDirents(struct dirent* buffer, size_t length, 639 int32 count) 640 { 641 int32 result = 0; 642 643 for (int32 queryCount = fQueryListRep->fQueryList->CountItems(); 644 fQueryListRep->fQueryListIndex < queryCount; 645 fQueryListRep->fQueryListIndex++) { 646 result = fQueryListRep->fQueryList-> 647 ItemAt(fQueryListRep->fQueryListIndex)-> 648 GetNextDirents(buffer, length, count); 649 if (result > 0) 650 break; 651 } 652 653 return result; 654 } 655 656 657 status_t 658 QueryEntryListCollection::GetNextRef(entry_ref* ref) 659 { 660 status_t result = B_ERROR; 661 662 for (int32 count = fQueryListRep->fQueryList->CountItems(); 663 fQueryListRep->fQueryListIndex < count; 664 fQueryListRep->fQueryListIndex++) { 665 666 result = fQueryListRep->fQueryList-> 667 ItemAt(fQueryListRep->fQueryListIndex)->GetNextRef(ref); 668 if (result == B_OK) 669 break; 670 } 671 672 return result; 673 } 674 675 676 status_t 677 QueryEntryListCollection::Rewind() 678 { 679 fQueryListRep->fQueryListIndex = 0; 680 681 return B_OK; 682 } 683 684 685 int32 686 QueryEntryListCollection::CountEntries() 687 { 688 return 0; 689 } 690 691 692 bool 693 QueryEntryListCollection::ShowResultsFromTrash() const 694 { 695 return fQueryListRep->fShowResultsFromTrash; 696 } 697 698 699 bool 700 QueryEntryListCollection::DynamicDateQuery() const 701 { 702 return fQueryListRep->fDynamicDateQuery; 703 } 704 705 706 bool 707 QueryEntryListCollection::DynamicDateRefreshEveryHour() const 708 { 709 return fQueryListRep->fRefreshEveryHour; 710 } 711 712 713 bool 714 QueryEntryListCollection::DynamicDateRefreshEveryMinute() const 715 { 716 return fQueryListRep->fRefreshEveryMinute; 717 } 718