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