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