xref: /haiku/src/kits/tracker/QueryPoseView.cpp (revision 56430ad8002b8fd1ac69b590e9cc130de6d9e852)
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)
81 	:
82 	BPoseView(model, kListMode),
83 	fRefFilter(NULL),
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 	SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR, B_DARKEN_1_TINT);
147 	SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR, B_DARKEN_1_TINT);
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 void
205 BQueryPoseView::AddPosesCompleted()
206 {
207 	ASSERT(Window()->IsLocked());
208 
209 	PoseList* oldPoseList = fQueryListContainer->OldPoseList();
210 	if (oldPoseList != NULL) {
211 		int32 count = oldPoseList->CountItems();
212 		for (int32 index = count - 1; index >= 0; index--) {
213 			BPose* pose = oldPoseList->ItemAt(index);
214 			DeletePose(pose->TargetModel()->NodeRef());
215 		}
216 		fQueryListContainer->ClearOldPoseList();
217 	}
218 
219 	_inherited::AddPosesCompleted();
220 }
221 
222 
223 // When using dynamic dates, such as "today", need to refresh the query
224 // window every now and then
225 
226 EntryListBase*
227 BQueryPoseView::InitDirentIterator(const entry_ref* ref)
228 {
229 	BEntry entry(ref);
230 	if (entry.InitCheck() != B_OK)
231 		return NULL;
232 
233 	Model sourceModel(&entry, true);
234 	if (sourceModel.InitCheck() != B_OK)
235 		return NULL;
236 
237 	ASSERT(sourceModel.IsQuery());
238 
239 	// old pose list is used for finding poses that no longer match a
240 	// dynamic date query during a Refresh call
241 	PoseList* oldPoseList = NULL;
242 	if (fCreateOldPoseList) {
243 		oldPoseList = new PoseList(10, false);
244 		oldPoseList->AddList(fPoseList);
245 	}
246 
247 	fQueryListContainer = new QueryEntryListCollection(&sourceModel, this,
248 		oldPoseList);
249 	fCreateOldPoseList = false;
250 
251 	if (fQueryListContainer->InitCheck() != B_OK) {
252 		delete fQueryListContainer;
253 		fQueryListContainer = NULL;
254 		return NULL;
255 	}
256 
257 	TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_NAME | B_WATCH_STAT
258 		| B_WATCH_ATTR, this);
259 
260 	fQueryList = fQueryListContainer->QueryList();
261 
262 	if (fQueryListContainer->DynamicDateQuery()) {
263 		// calculate the time to trigger the query refresh - next midnight
264 
265 		time_t now = time(0);
266 
267 		time_t nextMidnight = now + 60 * 60 * 24;
268 			// move ahead by a day
269 		tm timeData;
270 		localtime_r(&nextMidnight, &timeData);
271 		timeData.tm_sec = 0;
272 		timeData.tm_min = 0;
273 		timeData.tm_hour = 0;
274 		nextMidnight = mktime(&timeData);
275 
276 		time_t nextHour = now + 60 * 60;
277 			// move ahead by a hour
278 		localtime_r(&nextHour, &timeData);
279 		timeData.tm_sec = 0;
280 		timeData.tm_min = 0;
281 		nextHour = mktime(&timeData);
282 
283 		PRINT(("%" B_PRId32 " minutes, %" B_PRId32 " seconds till next hour\n",
284 			(nextHour - now) / 60, (nextHour - now) % 60));
285 
286 		time_t nextMinute = now + 60;
287 			// move ahead by a minute
288 		localtime_r(&nextMinute, &timeData);
289 		timeData.tm_sec = 0;
290 		nextMinute = mktime(&timeData);
291 
292 		PRINT(("%" B_PRId32 " seconds till next minute\n", nextMinute - now));
293 
294 		bigtime_t delta;
295 		if (fQueryListContainer->DynamicDateRefreshEveryMinute())
296 			delta = nextMinute - now;
297 		else if (fQueryListContainer->DynamicDateRefreshEveryHour())
298 			delta = nextHour - now;
299 		else
300 			delta = nextMidnight - now;
301 
302 #if DEBUG
303 		int32 secondsTillMidnight = (nextMidnight - now);
304 		int32 minutesTillMidnight = secondsTillMidnight/60;
305 		secondsTillMidnight %= 60;
306 		int32 hoursTillMidnight = minutesTillMidnight/60;
307 		minutesTillMidnight %= 60;
308 
309 		PRINT(("%" B_PRId32 " hours, %" B_PRId32 " minutes, %" B_PRId32
310 			" seconds till midnight\n", hoursTillMidnight, minutesTillMidnight,
311 			secondsTillMidnight));
312 
313 		int32 refreshInSeconds = delta % 60;
314 		int32 refreshInMinutes = delta / 60;
315 		int32 refreshInHours = refreshInMinutes / 60;
316 		refreshInMinutes %= 60;
317 
318 		PRINT(("next refresh in %" B_PRId32 " hours, %" B_PRId32 "minutes, %"
319 			B_PRId32 " seconds\n", refreshInHours, refreshInMinutes,
320 			refreshInSeconds));
321 #endif
322 
323 		// bump up to microseconds
324 		delta *= 1000000;
325 
326 		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
327 		ThrowOnAssert(tracker != NULL);
328 
329 		tracker->MainTaskLoop()->RunLater(
330 			NewLockingMemberFunctionObject(&BQueryPoseView::Refresh, this),
331 			delta);
332 	}
333 
334 	SetRefFilter(new QueryRefFilter(fQueryListContainer->ShowResultsFromTrash()));
335 
336 	return fQueryListContainer->Clone();
337 }
338 
339 
340 uint32
341 BQueryPoseView::WatchNewNodeMask()
342 {
343 	return B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR;
344 }
345 
346 
347 const char*
348 BQueryPoseView::SearchForType() const
349 {
350 	if (!fSearchForMimeType.Length()) {
351 		BModelOpener opener(TargetModel());
352 		BString buffer;
353 		attr_info attrInfo;
354 
355 		// read the type of files we are looking for
356 		status_t status
357 			= TargetModel()->Node()->GetAttrInfo(kAttrQueryInitialMime,
358 				&attrInfo);
359 		if (status == B_OK) {
360 			TargetModel()->Node()->ReadAttrString(kAttrQueryInitialMime,
361 				&buffer);
362 		}
363 
364 		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
365 		if (tracker != NULL && buffer.Length() > 0) {
366 			const ShortMimeInfo* info = tracker->MimeTypes()->FindMimeType(
367 				buffer.String());
368 			if (info != NULL)
369 				fSearchForMimeType = info->InternalName();
370 		}
371 
372 		if (!fSearchForMimeType.Length())
373 			fSearchForMimeType = B_FILE_MIMETYPE;
374 	}
375 
376 	return fSearchForMimeType.String();
377 }
378 
379 
380 bool
381 BQueryPoseView::ActiveOnDevice(dev_t device) const
382 {
383 	int32 count = fQueryList->CountItems();
384 	for (int32 index = 0; index < count; index++) {
385 		if (fQueryList->ItemAt(index)->TargetDevice() == device)
386 			return true;
387 	}
388 
389 	return false;
390 }
391 
392 
393 //	#pragma mark - QueryRefFilter
394 
395 
396 QueryRefFilter::QueryRefFilter(bool showResultsFromTrash)
397 	:
398 	fShowResultsFromTrash(showResultsFromTrash)
399 {
400 }
401 
402 
403 bool
404 QueryRefFilter::Filter(const entry_ref* ref, BNode* node, stat_beos* st,
405 	const char* filetype)
406 {
407 	TTracker* tracker = dynamic_cast<TTracker*>(be_app);
408 	return !(!fShowResultsFromTrash && tracker != NULL
409 		&& tracker->InTrashNode(ref));
410 }
411 
412 
413 //	#pragma mark - QueryEntryListCollection
414 
415 
416 QueryEntryListCollection::QueryEntryListCollection(Model* model,
417 	BHandler* target, PoseList* oldPoseList)
418 	:
419 	fQueryListRep(new QueryListRep(new BObjectList<BQuery>(5, true)))
420 {
421 	Rewind();
422 	attr_info info;
423 	BQuery query;
424 
425 	BNode* modelNode = model->Node();
426 	if (modelNode == NULL) {
427 		fStatus = B_ERROR;
428 		return;
429 	}
430 
431 	// read the actual query string
432 	fStatus = modelNode->GetAttrInfo(kAttrQueryString, &info);
433 	if (fStatus != B_OK)
434 		return;
435 
436 	BString buffer;
437 	if (modelNode->ReadAttr(kAttrQueryString, B_STRING_TYPE, 0,
438 		buffer.LockBuffer((int32)info.size),
439 			(size_t)info.size) != info.size) {
440 		fStatus = B_ERROR;
441 		return;
442 	}
443 
444 	buffer.UnlockBuffer();
445 
446 	// read the extra options
447 	MoreOptionsStruct saveMoreOptions;
448 	if (ReadAttr(modelNode, kAttrQueryMoreOptions,
449 			kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions,
450 			sizeof(MoreOptionsStruct),
451 			&MoreOptionsStruct::EndianSwap) != kReadAttrFailed) {
452 		fQueryListRep->fShowResultsFromTrash = saveMoreOptions.searchTrash;
453 	}
454 
455 	fStatus = query.SetPredicate(buffer.String());
456 
457 	fQueryListRep->fOldPoseList = oldPoseList;
458 	fQueryListRep->fDynamicDateQuery = false;
459 
460 	fQueryListRep->fRefreshEveryHour = false;
461 	fQueryListRep->fRefreshEveryMinute = false;
462 
463 	if (modelNode->ReadAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0,
464 			&fQueryListRep->fDynamicDateQuery,
465 			sizeof(bool)) != sizeof(bool)) {
466 		fQueryListRep->fDynamicDateQuery = false;
467 	}
468 
469 	if (fQueryListRep->fDynamicDateQuery) {
470 		// only refresh every minute on debug builds
471 		fQueryListRep->fRefreshEveryMinute = buffer.IFindFirst("second") != -1
472 			|| buffer.IFindFirst("minute") != -1;
473 		fQueryListRep->fRefreshEveryHour = fQueryListRep->fRefreshEveryMinute
474 			|| buffer.IFindFirst("hour") != -1;
475 
476 #if !DEBUG
477 		// don't refresh every minute unless we are running debug build
478 		fQueryListRep->fRefreshEveryMinute = false;
479 #endif
480 	}
481 
482 	if (fStatus != B_OK)
483 		return;
484 
485 	bool searchAllVolumes = true;
486 	status_t result = B_OK;
487 
488 	// get volumes to perform query on
489 	if (modelNode->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) {
490 		char* buffer = NULL;
491 
492 		if ((buffer = (char*)malloc((size_t)info.size)) != NULL
493 			&& modelNode->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0,
494 				buffer, (size_t)info.size) == info.size) {
495 			BMessage message;
496 			if (message.Unflatten(buffer) == B_OK) {
497 				for (int32 index = 0; ;index++) {
498 					ASSERT(index < 100);
499 					BVolume volume;
500 						// match a volume with the info embedded in
501 						// the message
502 					result = MatchArchivedVolume(&volume, &message, index);
503 					if (result == B_OK) {
504 						// start the query on this volume
505 						result = FetchOneQuery(&query, target,
506 							fQueryListRep->fQueryList, &volume);
507 						if (result != B_OK)
508 							continue;
509 
510 						searchAllVolumes = false;
511 					} else if (result != B_DEV_BAD_DRIVE_NUM) {
512 						// if B_DEV_BAD_DRIVE_NUM, the volume just isn't
513 						// mounted this time around, keep looking for more
514 						// if other error, bail
515 						break;
516 					}
517 				}
518 			}
519 		}
520 
521 		free(buffer);
522 	}
523 
524 	if (searchAllVolumes) {
525 		// no specific volumes embedded in query, search everything
526 		BVolumeRoster roster;
527 		BVolume volume;
528 
529 		roster.Rewind();
530 		while (roster.GetNextVolume(&volume) == B_OK)
531 			if (volume.IsPersistent() && volume.KnowsQuery()) {
532 				result = FetchOneQuery(&query, target,
533 					fQueryListRep->fQueryList, &volume);
534 				if (result != B_OK)
535 					continue;
536 			}
537 	}
538 
539 	fStatus = B_OK;
540 
541 	return;
542 }
543 
544 
545 status_t
546 QueryEntryListCollection::FetchOneQuery(const BQuery* copyThis,
547 	BHandler* target, BObjectList<BQuery>* list, BVolume* volume)
548 {
549 	BQuery* query = new (nothrow) BQuery;
550 	if (query == NULL)
551 		return B_NO_MEMORY;
552 
553 	// have to fake a copy constructor here because BQuery doesn't have
554 	// a copy constructor
555 	BString buffer;
556 	const_cast<BQuery*>(copyThis)->GetPredicate(&buffer);
557 	query->SetPredicate(buffer.String());
558 
559 	query->SetTarget(BMessenger(target));
560 	query->SetVolume(volume);
561 
562 	status_t result = query->Fetch();
563 	if (result != B_OK) {
564 		PRINT(("fetch error %s\n", strerror(result)));
565 		delete query;
566 		return result;
567 	}
568 
569 	list->AddItem(query);
570 
571 	return B_OK;
572 }
573 
574 
575 QueryEntryListCollection::~QueryEntryListCollection()
576 {
577 	if (fQueryListRep->CloseQueryList())
578 		delete fQueryListRep;
579 }
580 
581 
582 QueryEntryListCollection*
583 QueryEntryListCollection::Clone()
584 {
585 	fQueryListRep->OpenQueryList();
586 	return new QueryEntryListCollection(*this);
587 }
588 
589 
590 //	#pragma mark - QueryEntryListCollection
591 
592 
593 QueryEntryListCollection::QueryEntryListCollection(
594 	const QueryEntryListCollection &cloneThis)
595 	:
596 	EntryListBase(),
597 	fQueryListRep(cloneThis.fQueryListRep)
598 {
599 	// only to be used by the Clone routine
600 }
601 
602 
603 void
604 QueryEntryListCollection::ClearOldPoseList()
605 {
606 	delete fQueryListRep->fOldPoseList;
607 	fQueryListRep->fOldPoseList = NULL;
608 }
609 
610 
611 status_t
612 QueryEntryListCollection::GetNextEntry(BEntry* entry, bool traverse)
613 {
614 	status_t result = B_ERROR;
615 
616 	for (int32 count = fQueryListRep->fQueryList->CountItems();
617 		fQueryListRep->fQueryListIndex < count;
618 		fQueryListRep->fQueryListIndex++) {
619 		result = fQueryListRep->fQueryList->
620 			ItemAt(fQueryListRep->fQueryListIndex)->
621 				GetNextEntry(entry, traverse);
622 		if (result == B_OK)
623 			break;
624 	}
625 
626 	return result;
627 }
628 
629 
630 int32
631 QueryEntryListCollection::GetNextDirents(struct dirent* buffer, size_t length,
632 	int32 count)
633 {
634 	int32 result = 0;
635 
636 	for (int32 queryCount = fQueryListRep->fQueryList->CountItems();
637 			fQueryListRep->fQueryListIndex < queryCount;
638 			fQueryListRep->fQueryListIndex++) {
639 		result = fQueryListRep->fQueryList->
640 			ItemAt(fQueryListRep->fQueryListIndex)->
641 				GetNextDirents(buffer, length, count);
642 		if (result > 0)
643 			break;
644 	}
645 
646 	return result;
647 }
648 
649 
650 status_t
651 QueryEntryListCollection::GetNextRef(entry_ref* ref)
652 {
653 	status_t result = B_ERROR;
654 
655 	for (int32 count = fQueryListRep->fQueryList->CountItems();
656 		fQueryListRep->fQueryListIndex < count;
657 		fQueryListRep->fQueryListIndex++) {
658 
659 		result = fQueryListRep->fQueryList->
660 			ItemAt(fQueryListRep->fQueryListIndex)->GetNextRef(ref);
661 		if (result == B_OK)
662 			break;
663 	}
664 
665 	return result;
666 }
667 
668 
669 status_t
670 QueryEntryListCollection::Rewind()
671 {
672 	fQueryListRep->fQueryListIndex = 0;
673 
674 	return B_OK;
675 }
676 
677 
678 int32
679 QueryEntryListCollection::CountEntries()
680 {
681 	return 0;
682 }
683 
684 
685 bool
686 QueryEntryListCollection::ShowResultsFromTrash() const
687 {
688 	return fQueryListRep->fShowResultsFromTrash;
689 }
690 
691 
692 bool
693 QueryEntryListCollection::DynamicDateQuery() const
694 {
695 	return fQueryListRep->fDynamicDateQuery;
696 }
697 
698 
699 bool
700 QueryEntryListCollection::DynamicDateRefreshEveryHour() const
701 {
702 	return fQueryListRep->fRefreshEveryHour;
703 }
704 
705 
706 bool
707 QueryEntryListCollection::DynamicDateRefreshEveryMinute() const
708 {
709 	return fQueryListRep->fRefreshEveryMinute;
710 }
711