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