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