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) 81 : 82 BPoseView(model, kListMode), 83 fRefFilter(NULL), 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 (CountColumns() != 0) 129 return; 130 131 AddColumn(new BColumn(B_TRANSLATE("Name"), 145, 132 B_ALIGN_LEFT, kAttrStatName, B_STRING_TYPE, true, true)); 133 AddColumn(new BColumn(B_TRANSLATE("Location"), 225, 134 B_ALIGN_LEFT, kAttrPath, B_STRING_TYPE, true, false)); 135 AddColumn(new BColumn(B_TRANSLATE("Size"), 80, 136 B_ALIGN_RIGHT, kAttrStatSize, B_OFF_T_TYPE, true, false)); 137 AddColumn(new BColumn(B_TRANSLATE("Modified"), 150, 138 B_ALIGN_LEFT, kAttrStatModified, B_TIME_TYPE, true, false)); 139 } 140 141 142 void 143 BQueryPoseView::AttachedToWindow() 144 { 145 _inherited::AttachedToWindow(); 146 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR, B_DARKEN_1_TINT); 147 SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR, B_DARKEN_1_TINT); 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 void 205 BQueryPoseView::AddPosesCompleted() 206 { 207 ASSERT(Window()->IsLocked()); 208 209 PoseList* oldPoseList = fQueryListContainer->OldPoseList(); 210 if (oldPoseList != NULL) { 211 int32 count = oldPoseList->CountItems(); 212 for (int32 index = count - 1; index >= 0; index--) { 213 BPose* pose = oldPoseList->ItemAt(index); 214 DeletePose(pose->TargetModel()->NodeRef()); 215 } 216 fQueryListContainer->ClearOldPoseList(); 217 } 218 219 _inherited::AddPosesCompleted(); 220 } 221 222 223 // When using dynamic dates, such as "today", need to refresh the query 224 // window every now and then 225 226 EntryListBase* 227 BQueryPoseView::InitDirentIterator(const entry_ref* ref) 228 { 229 BEntry entry(ref); 230 if (entry.InitCheck() != B_OK) 231 return NULL; 232 233 Model sourceModel(&entry, true); 234 if (sourceModel.InitCheck() != B_OK) 235 return NULL; 236 237 ASSERT(sourceModel.IsQuery()); 238 239 // old pose list is used for finding poses that no longer match a 240 // dynamic date query during a Refresh call 241 PoseList* oldPoseList = NULL; 242 if (fCreateOldPoseList) { 243 oldPoseList = new PoseList(10, false); 244 oldPoseList->AddList(fPoseList); 245 } 246 247 fQueryListContainer = new QueryEntryListCollection(&sourceModel, this, 248 oldPoseList); 249 fCreateOldPoseList = false; 250 251 if (fQueryListContainer->InitCheck() != B_OK) { 252 delete fQueryListContainer; 253 fQueryListContainer = NULL; 254 return NULL; 255 } 256 257 TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_NAME | B_WATCH_STAT 258 | B_WATCH_ATTR, this); 259 260 fQueryList = fQueryListContainer->QueryList(); 261 262 if (fQueryListContainer->DynamicDateQuery()) { 263 // calculate the time to trigger the query refresh - next midnight 264 265 time_t now = time(0); 266 267 time_t nextMidnight = now + 60 * 60 * 24; 268 // move ahead by a day 269 tm timeData; 270 localtime_r(&nextMidnight, &timeData); 271 timeData.tm_sec = 0; 272 timeData.tm_min = 0; 273 timeData.tm_hour = 0; 274 nextMidnight = mktime(&timeData); 275 276 time_t nextHour = now + 60 * 60; 277 // move ahead by a hour 278 localtime_r(&nextHour, &timeData); 279 timeData.tm_sec = 0; 280 timeData.tm_min = 0; 281 nextHour = mktime(&timeData); 282 283 PRINT(("%" B_PRIdTIME " minutes, %" B_PRIdTIME " seconds till next hour\n", 284 (nextHour - now) / 60, (nextHour - now) % 60)); 285 286 time_t nextMinute = now + 60; 287 // move ahead by a minute 288 localtime_r(&nextMinute, &timeData); 289 timeData.tm_sec = 0; 290 nextMinute = mktime(&timeData); 291 292 PRINT(("%" B_PRIdTIME " seconds till next minute\n", nextMinute - now)); 293 294 bigtime_t delta; 295 if (fQueryListContainer->DynamicDateRefreshEveryMinute()) 296 delta = nextMinute - now; 297 else if (fQueryListContainer->DynamicDateRefreshEveryHour()) 298 delta = nextHour - now; 299 else 300 delta = nextMidnight - now; 301 302 #if DEBUG 303 int32 secondsTillMidnight = (nextMidnight - now); 304 int32 minutesTillMidnight = secondsTillMidnight/60; 305 secondsTillMidnight %= 60; 306 int32 hoursTillMidnight = minutesTillMidnight/60; 307 minutesTillMidnight %= 60; 308 309 PRINT(("%" B_PRId32 " hours, %" B_PRId32 " minutes, %" B_PRId32 310 " seconds till midnight\n", hoursTillMidnight, minutesTillMidnight, 311 secondsTillMidnight)); 312 313 int32 refreshInSeconds = delta % 60; 314 int32 refreshInMinutes = delta / 60; 315 int32 refreshInHours = refreshInMinutes / 60; 316 refreshInMinutes %= 60; 317 318 PRINT(("next refresh in %" B_PRId32 " hours, %" B_PRId32 "minutes, %" 319 B_PRId32 " seconds\n", refreshInHours, refreshInMinutes, 320 refreshInSeconds)); 321 #endif 322 323 // bump up to microseconds 324 delta *= 1000000; 325 326 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 327 ThrowOnAssert(tracker != NULL); 328 329 tracker->MainTaskLoop()->RunLater( 330 NewLockingMemberFunctionObject(&BQueryPoseView::Refresh, this), 331 delta); 332 } 333 334 SetRefFilter(new QueryRefFilter(fQueryListContainer->ShowResultsFromTrash())); 335 336 return fQueryListContainer->Clone(); 337 } 338 339 340 uint32 341 BQueryPoseView::WatchNewNodeMask() 342 { 343 return B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR; 344 } 345 346 347 const char* 348 BQueryPoseView::SearchForType() const 349 { 350 if (!fSearchForMimeType.Length()) { 351 BModelOpener opener(TargetModel()); 352 BString buffer; 353 attr_info attrInfo; 354 355 // read the type of files we are looking for 356 status_t status 357 = TargetModel()->Node()->GetAttrInfo(kAttrQueryInitialMime, 358 &attrInfo); 359 if (status == B_OK) { 360 TargetModel()->Node()->ReadAttrString(kAttrQueryInitialMime, 361 &buffer); 362 } 363 364 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 365 if (tracker != NULL && buffer.Length() > 0) { 366 const ShortMimeInfo* info = tracker->MimeTypes()->FindMimeType( 367 buffer.String()); 368 if (info != NULL) 369 fSearchForMimeType = info->InternalName(); 370 } 371 372 if (!fSearchForMimeType.Length()) 373 fSearchForMimeType = B_FILE_MIMETYPE; 374 } 375 376 return fSearchForMimeType.String(); 377 } 378 379 380 bool 381 BQueryPoseView::ActiveOnDevice(dev_t device) const 382 { 383 int32 count = fQueryList->CountItems(); 384 for (int32 index = 0; index < count; index++) { 385 if (fQueryList->ItemAt(index)->TargetDevice() == device) 386 return true; 387 } 388 389 return false; 390 } 391 392 393 // #pragma mark - QueryRefFilter 394 395 396 QueryRefFilter::QueryRefFilter(bool showResultsFromTrash) 397 : 398 fShowResultsFromTrash(showResultsFromTrash) 399 { 400 } 401 402 403 bool 404 QueryRefFilter::Filter(const entry_ref* ref, BNode* node, stat_beos* st, 405 const char* filetype) 406 { 407 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 408 return !(!fShowResultsFromTrash && tracker != NULL 409 && tracker->InTrashNode(ref)); 410 } 411 412 413 // #pragma mark - QueryEntryListCollection 414 415 416 QueryEntryListCollection::QueryEntryListCollection(Model* model, 417 BHandler* target, PoseList* oldPoseList) 418 : 419 fQueryListRep(new QueryListRep(new BObjectList<BQuery>(5, true))) 420 { 421 Rewind(); 422 attr_info info; 423 BQuery query; 424 425 BNode* modelNode = model->Node(); 426 if (modelNode == NULL) { 427 fStatus = B_ERROR; 428 return; 429 } 430 431 // read the actual query string 432 fStatus = modelNode->GetAttrInfo(kAttrQueryString, &info); 433 if (fStatus != B_OK) 434 return; 435 436 BString buffer; 437 if (modelNode->ReadAttr(kAttrQueryString, B_STRING_TYPE, 0, 438 buffer.LockBuffer((int32)info.size), 439 (size_t)info.size) != info.size) { 440 fStatus = B_ERROR; 441 return; 442 } 443 444 buffer.UnlockBuffer(); 445 446 // read the extra options 447 MoreOptionsStruct saveMoreOptions; 448 if (ReadAttr(modelNode, kAttrQueryMoreOptions, 449 kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions, 450 sizeof(MoreOptionsStruct), 451 &MoreOptionsStruct::EndianSwap) != kReadAttrFailed) { 452 fQueryListRep->fShowResultsFromTrash = saveMoreOptions.searchTrash; 453 } 454 455 fStatus = query.SetPredicate(buffer.String()); 456 457 fQueryListRep->fOldPoseList = oldPoseList; 458 fQueryListRep->fDynamicDateQuery = false; 459 460 fQueryListRep->fRefreshEveryHour = false; 461 fQueryListRep->fRefreshEveryMinute = false; 462 463 if (modelNode->ReadAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0, 464 &fQueryListRep->fDynamicDateQuery, 465 sizeof(bool)) != sizeof(bool)) { 466 fQueryListRep->fDynamicDateQuery = false; 467 } 468 469 if (fQueryListRep->fDynamicDateQuery) { 470 // only refresh every minute on debug builds 471 fQueryListRep->fRefreshEveryMinute = buffer.IFindFirst("second") != -1 472 || buffer.IFindFirst("minute") != -1; 473 fQueryListRep->fRefreshEveryHour = fQueryListRep->fRefreshEveryMinute 474 || buffer.IFindFirst("hour") != -1; 475 476 #if !DEBUG 477 // don't refresh every minute unless we are running debug build 478 fQueryListRep->fRefreshEveryMinute = false; 479 #endif 480 } 481 482 if (fStatus != B_OK) 483 return; 484 485 bool searchAllVolumes = true; 486 status_t result = B_OK; 487 488 // get volumes to perform query on 489 if (modelNode->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) { 490 char* buffer = NULL; 491 492 if ((buffer = (char*)malloc((size_t)info.size)) != NULL 493 && modelNode->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0, 494 buffer, (size_t)info.size) == info.size) { 495 BMessage message; 496 if (message.Unflatten(buffer) == B_OK) { 497 for (int32 index = 0; ;index++) { 498 ASSERT(index < 100); 499 BVolume volume; 500 // match a volume with the info embedded in 501 // the message 502 result = MatchArchivedVolume(&volume, &message, index); 503 if (result == B_OK) { 504 // start the query on this volume 505 result = FetchOneQuery(&query, target, 506 fQueryListRep->fQueryList, &volume); 507 if (result != B_OK) 508 continue; 509 510 searchAllVolumes = false; 511 } else if (result != B_DEV_BAD_DRIVE_NUM) { 512 // if B_DEV_BAD_DRIVE_NUM, the volume just isn't 513 // mounted this time around, keep looking for more 514 // if other error, bail 515 break; 516 } 517 } 518 } 519 } 520 521 free(buffer); 522 } 523 524 if (searchAllVolumes) { 525 // no specific volumes embedded in query, search everything 526 BVolumeRoster roster; 527 BVolume volume; 528 529 roster.Rewind(); 530 while (roster.GetNextVolume(&volume) == B_OK) 531 if (volume.IsPersistent() && volume.KnowsQuery()) { 532 result = FetchOneQuery(&query, target, 533 fQueryListRep->fQueryList, &volume); 534 if (result != B_OK) 535 continue; 536 } 537 } 538 539 fStatus = B_OK; 540 541 return; 542 } 543 544 545 status_t 546 QueryEntryListCollection::FetchOneQuery(const BQuery* copyThis, 547 BHandler* target, BObjectList<BQuery>* list, BVolume* volume) 548 { 549 BQuery* query = new (nothrow) BQuery; 550 if (query == NULL) 551 return B_NO_MEMORY; 552 553 // have to fake a copy constructor here because BQuery doesn't have 554 // a copy constructor 555 BString buffer; 556 const_cast<BQuery*>(copyThis)->GetPredicate(&buffer); 557 query->SetPredicate(buffer.String()); 558 559 query->SetTarget(BMessenger(target)); 560 query->SetVolume(volume); 561 562 status_t result = query->Fetch(); 563 if (result != B_OK) { 564 PRINT(("fetch error %s\n", strerror(result))); 565 delete query; 566 return result; 567 } 568 569 list->AddItem(query); 570 571 return B_OK; 572 } 573 574 575 QueryEntryListCollection::~QueryEntryListCollection() 576 { 577 if (fQueryListRep->CloseQueryList()) 578 delete fQueryListRep; 579 } 580 581 582 QueryEntryListCollection* 583 QueryEntryListCollection::Clone() 584 { 585 fQueryListRep->OpenQueryList(); 586 return new QueryEntryListCollection(*this); 587 } 588 589 590 // #pragma mark - QueryEntryListCollection 591 592 593 QueryEntryListCollection::QueryEntryListCollection( 594 const QueryEntryListCollection &cloneThis) 595 : 596 EntryListBase(), 597 fQueryListRep(cloneThis.fQueryListRep) 598 { 599 // only to be used by the Clone routine 600 } 601 602 603 void 604 QueryEntryListCollection::ClearOldPoseList() 605 { 606 delete fQueryListRep->fOldPoseList; 607 fQueryListRep->fOldPoseList = NULL; 608 } 609 610 611 status_t 612 QueryEntryListCollection::GetNextEntry(BEntry* entry, bool traverse) 613 { 614 status_t result = B_ERROR; 615 616 for (int32 count = fQueryListRep->fQueryList->CountItems(); 617 fQueryListRep->fQueryListIndex < count; 618 fQueryListRep->fQueryListIndex++) { 619 result = fQueryListRep->fQueryList-> 620 ItemAt(fQueryListRep->fQueryListIndex)-> 621 GetNextEntry(entry, traverse); 622 if (result == B_OK) 623 break; 624 } 625 626 return result; 627 } 628 629 630 int32 631 QueryEntryListCollection::GetNextDirents(struct dirent* buffer, size_t length, 632 int32 count) 633 { 634 int32 result = 0; 635 636 for (int32 queryCount = fQueryListRep->fQueryList->CountItems(); 637 fQueryListRep->fQueryListIndex < queryCount; 638 fQueryListRep->fQueryListIndex++) { 639 result = fQueryListRep->fQueryList-> 640 ItemAt(fQueryListRep->fQueryListIndex)-> 641 GetNextDirents(buffer, length, count); 642 if (result > 0) 643 break; 644 } 645 646 return result; 647 } 648 649 650 status_t 651 QueryEntryListCollection::GetNextRef(entry_ref* ref) 652 { 653 status_t result = B_ERROR; 654 655 for (int32 count = fQueryListRep->fQueryList->CountItems(); 656 fQueryListRep->fQueryListIndex < count; 657 fQueryListRep->fQueryListIndex++) { 658 659 result = fQueryListRep->fQueryList-> 660 ItemAt(fQueryListRep->fQueryListIndex)->GetNextRef(ref); 661 if (result == B_OK) 662 break; 663 } 664 665 return result; 666 } 667 668 669 status_t 670 QueryEntryListCollection::Rewind() 671 { 672 fQueryListRep->fQueryListIndex = 0; 673 674 return B_OK; 675 } 676 677 678 int32 679 QueryEntryListCollection::CountEntries() 680 { 681 return 0; 682 } 683 684 685 bool 686 QueryEntryListCollection::ShowResultsFromTrash() const 687 { 688 return fQueryListRep->fShowResultsFromTrash; 689 } 690 691 692 bool 693 QueryEntryListCollection::DynamicDateQuery() const 694 { 695 return fQueryListRep->fDynamicDateQuery; 696 } 697 698 699 bool 700 QueryEntryListCollection::DynamicDateRefreshEveryHour() const 701 { 702 return fQueryListRep->fRefreshEveryHour; 703 } 704 705 706 bool 707 QueryEntryListCollection::DynamicDateRefreshEveryMinute() const 708 { 709 return fQueryListRep->fRefreshEveryMinute; 710 } 711