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 ASSERT(tracker); 357 tracker->MainTaskLoop()->RunLater( 358 NewLockingMemberFunctionObject(&BQueryPoseView::Refresh, this), 359 delta); 360 } 361 362 return fQueryListContainer->Clone(); 363 } 364 365 366 uint32 367 BQueryPoseView::WatchNewNodeMask() 368 { 369 return B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR; 370 } 371 372 373 const char* 374 BQueryPoseView::SearchForType() const 375 { 376 if (!fSearchForMimeType.Length()) { 377 BModelOpener opener(TargetModel()); 378 BString buffer; 379 attr_info attrInfo; 380 381 // read the type of files we are looking for 382 status_t status 383 = TargetModel()->Node()->GetAttrInfo(kAttrQueryInitialMime, 384 &attrInfo); 385 if (status == B_OK) { 386 TargetModel()->Node()->ReadAttrString(kAttrQueryInitialMime, 387 &buffer); 388 } 389 390 if (buffer.Length()) { 391 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 392 if (tracker) { 393 const ShortMimeInfo* info 394 = tracker->MimeTypes()->FindMimeType(buffer.String()); 395 if (info) 396 fSearchForMimeType = info->InternalName(); 397 } 398 } 399 400 if (!fSearchForMimeType.Length()) 401 fSearchForMimeType = B_FILE_MIMETYPE; 402 } 403 404 return fSearchForMimeType.String(); 405 } 406 407 408 bool 409 BQueryPoseView::ActiveOnDevice(dev_t device) const 410 { 411 int32 count = fQueryList->CountItems(); 412 for (int32 index = 0; index < count; index++) { 413 if (fQueryList->ItemAt(index)->TargetDevice() == device) 414 return true; 415 } 416 417 return false; 418 } 419 420 421 // #pragma mark - QueryEntryListCollection 422 423 424 QueryEntryListCollection::QueryEntryListCollection(Model* model, 425 BHandler* target, PoseList* oldPoseList) 426 : 427 fQueryListRep(new QueryListRep(new BObjectList<BQuery>(5, true))) 428 { 429 Rewind(); 430 attr_info info; 431 BQuery query; 432 433 BNode* modelNode = model->Node(); 434 if (modelNode == NULL) { 435 fStatus = B_ERROR; 436 return; 437 } 438 439 // read the actual query string 440 fStatus = modelNode->GetAttrInfo(kAttrQueryString, &info); 441 if (fStatus != B_OK) 442 return; 443 444 BString buffer; 445 if (modelNode->ReadAttr(kAttrQueryString, B_STRING_TYPE, 0, 446 buffer.LockBuffer((int32)info.size), 447 (size_t)info.size) != info.size) { 448 fStatus = B_ERROR; 449 return; 450 } 451 452 buffer.UnlockBuffer(); 453 454 // read the extra options 455 MoreOptionsStruct saveMoreOptions; 456 if (ReadAttr(modelNode, kAttrQueryMoreOptions, 457 kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions, 458 sizeof(MoreOptionsStruct), 459 &MoreOptionsStruct::EndianSwap) != kReadAttrFailed) { 460 fQueryListRep->fShowResultsFromTrash = saveMoreOptions.searchTrash; 461 } 462 463 fStatus = query.SetPredicate(buffer.String()); 464 465 fQueryListRep->fOldPoseList = oldPoseList; 466 fQueryListRep->fDynamicDateQuery = false; 467 468 fQueryListRep->fRefreshEveryHour = false; 469 fQueryListRep->fRefreshEveryMinute = false; 470 471 if (modelNode->ReadAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0, 472 &fQueryListRep->fDynamicDateQuery, 473 sizeof(bool)) != sizeof(bool)) { 474 fQueryListRep->fDynamicDateQuery = false; 475 } 476 477 if (fQueryListRep->fDynamicDateQuery) { 478 // only refresh every minute on debug builds 479 fQueryListRep->fRefreshEveryMinute = buffer.IFindFirst("second") != -1 480 || buffer.IFindFirst("minute") != -1; 481 fQueryListRep->fRefreshEveryHour = fQueryListRep->fRefreshEveryMinute 482 || buffer.IFindFirst("hour") != -1; 483 484 #if !DEBUG 485 // don't refresh every minute unless we are running debug build 486 fQueryListRep->fRefreshEveryMinute = false; 487 #endif 488 } 489 490 if (fStatus != B_OK) 491 return; 492 493 bool searchAllVolumes = true; 494 status_t result = B_OK; 495 496 // get volumes to perform query on 497 if (modelNode->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) { 498 char* buffer = NULL; 499 500 if ((buffer = (char*)malloc((size_t)info.size)) != NULL 501 && modelNode->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0, 502 buffer, (size_t)info.size) == info.size) { 503 BMessage message; 504 if (message.Unflatten(buffer) == B_OK) { 505 for (int32 index = 0; ;index++) { 506 ASSERT(index < 100); 507 BVolume volume; 508 // match a volume with the info embedded in 509 // the message 510 result = MatchArchivedVolume(&volume, &message, index); 511 if (result == B_OK) { 512 // start the query on this volume 513 result = FetchOneQuery(&query, target, 514 fQueryListRep->fQueryList, &volume); 515 if (result != B_OK) 516 continue; 517 518 searchAllVolumes = false; 519 } else if (result != B_DEV_BAD_DRIVE_NUM) { 520 // if B_DEV_BAD_DRIVE_NUM, the volume just isn't 521 // mounted this time around, keep looking for more 522 // if other error, bail 523 break; 524 } 525 } 526 } 527 } 528 529 free(buffer); 530 } 531 532 if (searchAllVolumes) { 533 // no specific volumes embedded in query, search everything 534 BVolumeRoster roster; 535 BVolume volume; 536 537 roster.Rewind(); 538 while (roster.GetNextVolume(&volume) == B_OK) 539 if (volume.IsPersistent() && volume.KnowsQuery()) { 540 result = FetchOneQuery(&query, target, 541 fQueryListRep->fQueryList, &volume); 542 if (result != B_OK) 543 continue; 544 } 545 } 546 547 fStatus = B_OK; 548 549 return; 550 } 551 552 553 status_t 554 QueryEntryListCollection::FetchOneQuery(const BQuery* copyThis, 555 BHandler* target, BObjectList<BQuery>* list, BVolume* volume) 556 { 557 BQuery* query = new (nothrow) BQuery; 558 if (query == NULL) 559 return B_NO_MEMORY; 560 561 // have to fake a copy constructor here because BQuery doesn't have 562 // a copy constructor 563 BString buffer; 564 const_cast<BQuery*>(copyThis)->GetPredicate(&buffer); 565 query->SetPredicate(buffer.String()); 566 567 query->SetTarget(BMessenger(target)); 568 query->SetVolume(volume); 569 570 status_t result = query->Fetch(); 571 if (result != B_OK) { 572 PRINT(("fetch error %s\n", strerror(result))); 573 delete query; 574 return result; 575 } 576 577 list->AddItem(query); 578 579 return B_OK; 580 } 581 582 583 QueryEntryListCollection::~QueryEntryListCollection() 584 { 585 if (fQueryListRep->CloseQueryList()) 586 delete fQueryListRep; 587 } 588 589 590 QueryEntryListCollection* 591 QueryEntryListCollection::Clone() 592 { 593 fQueryListRep->OpenQueryList(); 594 return new QueryEntryListCollection(*this); 595 } 596 597 598 // #pragma mark - QueryEntryListCollection 599 600 601 QueryEntryListCollection::QueryEntryListCollection( 602 const QueryEntryListCollection &cloneThis) 603 : 604 EntryListBase(), 605 fQueryListRep(cloneThis.fQueryListRep) 606 { 607 // only to be used by the Clone routine 608 } 609 610 611 void 612 QueryEntryListCollection::ClearOldPoseList() 613 { 614 delete fQueryListRep->fOldPoseList; 615 fQueryListRep->fOldPoseList = NULL; 616 } 617 618 619 status_t 620 QueryEntryListCollection::GetNextEntry(BEntry* entry, bool traverse) 621 { 622 status_t result = B_ERROR; 623 624 for (int32 count = fQueryListRep->fQueryList->CountItems(); 625 fQueryListRep->fQueryListIndex < count; 626 fQueryListRep->fQueryListIndex++) { 627 result = fQueryListRep->fQueryList-> 628 ItemAt(fQueryListRep->fQueryListIndex)-> 629 GetNextEntry(entry, traverse); 630 if (result == B_OK) 631 break; 632 } 633 634 return result; 635 } 636 637 638 int32 639 QueryEntryListCollection::GetNextDirents(struct dirent* buffer, size_t length, 640 int32 count) 641 { 642 int32 result = 0; 643 644 for (int32 queryCount = fQueryListRep->fQueryList->CountItems(); 645 fQueryListRep->fQueryListIndex < queryCount; 646 fQueryListRep->fQueryListIndex++) { 647 result = fQueryListRep->fQueryList-> 648 ItemAt(fQueryListRep->fQueryListIndex)-> 649 GetNextDirents(buffer, length, count); 650 if (result > 0) 651 break; 652 } 653 654 return result; 655 } 656 657 658 status_t 659 QueryEntryListCollection::GetNextRef(entry_ref* ref) 660 { 661 status_t result = B_ERROR; 662 663 for (int32 count = fQueryListRep->fQueryList->CountItems(); 664 fQueryListRep->fQueryListIndex < count; 665 fQueryListRep->fQueryListIndex++) { 666 667 result = fQueryListRep->fQueryList-> 668 ItemAt(fQueryListRep->fQueryListIndex)->GetNextRef(ref); 669 if (result == B_OK) 670 break; 671 } 672 673 return result; 674 } 675 676 677 status_t 678 QueryEntryListCollection::Rewind() 679 { 680 fQueryListRep->fQueryListIndex = 0; 681 682 return B_OK; 683 } 684 685 686 int32 687 QueryEntryListCollection::CountEntries() 688 { 689 return 0; 690 } 691 692 693 bool 694 QueryEntryListCollection::ShowResultsFromTrash() const 695 { 696 return fQueryListRep->fShowResultsFromTrash; 697 } 698 699 700 bool 701 QueryEntryListCollection::DynamicDateQuery() const 702 { 703 return fQueryListRep->fDynamicDateQuery; 704 } 705 706 707 bool 708 QueryEntryListCollection::DynamicDateRefreshEveryHour() const 709 { 710 return fQueryListRep->fRefreshEveryHour; 711 } 712 713 714 bool 715 QueryEntryListCollection::DynamicDateRefreshEveryMinute() const 716 { 717 return fQueryListRep->fRefreshEveryMinute; 718 } 719