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