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