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