1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All 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
62using 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
80BQueryPoseView::BQueryPoseView(Model* model)
81	:
82	BPoseView(model, kListMode),
83	fRefFilter(NULL),
84	fQueryList(NULL),
85	fQueryListContainer(NULL),
86	fCreateOldPoseList(false)
87{
88}
89
90
91BQueryPoseView::~BQueryPoseView()
92{
93	delete fQueryListContainer;
94}
95
96
97void
98BQueryPoseView::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
115void
116BQueryPoseView::EditQueries()
117{
118	BMessage message(kEditQuery);
119	message.AddRef("refs", TargetModel()->EntryRef());
120	BMessenger(kTrackerSignature, -1, 0).SendMessage(&message);
121}
122
123
124void
125BQueryPoseView::SetupDefaultColumnsIfNeeded()
126{
127	// in case there were errors getting some columns
128	if (CountColumns() != 0)
129		return;
130
131	AddColumn(new BColumn(B_TRANSLATE("Name"), 145,
132		B_ALIGN_LEFT, kAttrStatName, B_STRING_TYPE, true, true));
133	AddColumn(new BColumn(B_TRANSLATE("Location"), 225,
134		B_ALIGN_LEFT, kAttrPath, B_STRING_TYPE, true, false));
135	AddColumn(new BColumn(B_TRANSLATE("Size"), 80,
136		B_ALIGN_RIGHT, kAttrStatSize, B_OFF_T_TYPE, true, false));
137	AddColumn(new BColumn(B_TRANSLATE("Modified"), 150,
138		B_ALIGN_LEFT, kAttrStatModified, B_TIME_TYPE, true, false));
139}
140
141
142void
143BQueryPoseView::RestoreState(AttributeStreamNode* node)
144{
145	_inherited::RestoreState(node);
146	fViewState->SetViewMode(kListMode);
147}
148
149
150void
151BQueryPoseView::RestoreState(const BMessage &message)
152{
153	_inherited::RestoreState(message);
154	fViewState->SetViewMode(kListMode);
155}
156
157
158void
159BQueryPoseView::SavePoseLocations(BRect*)
160{
161}
162
163
164void
165BQueryPoseView::SetViewMode(uint32)
166{
167}
168
169
170void
171BQueryPoseView::OpenParent()
172{
173}
174
175
176void
177BQueryPoseView::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
195void
196BQueryPoseView::AddPosesCompleted()
197{
198	ASSERT(Window()->IsLocked());
199
200	PoseList* oldPoseList = fQueryListContainer->OldPoseList();
201	if (oldPoseList != NULL) {
202		int32 count = oldPoseList->CountItems();
203		for (int32 index = count - 1; index >= 0; index--) {
204			BPose* pose = oldPoseList->ItemAt(index);
205			DeletePose(pose->TargetModel()->NodeRef());
206		}
207		fQueryListContainer->ClearOldPoseList();
208	}
209
210	_inherited::AddPosesCompleted();
211}
212
213
214// When using dynamic dates, such as "today", need to refresh the query
215// window every now and then
216
217EntryListBase*
218BQueryPoseView::InitDirentIterator(const entry_ref* ref)
219{
220	BEntry entry(ref);
221	if (entry.InitCheck() != B_OK)
222		return NULL;
223
224	Model sourceModel(&entry, true);
225	if (sourceModel.InitCheck() != B_OK)
226		return NULL;
227
228	ASSERT(sourceModel.IsQuery());
229
230	// old pose list is used for finding poses that no longer match a
231	// dynamic date query during a Refresh call
232	PoseList* oldPoseList = NULL;
233	if (fCreateOldPoseList) {
234		oldPoseList = new PoseList(10, false);
235		oldPoseList->AddList(fPoseList);
236	}
237
238	fQueryListContainer = new QueryEntryListCollection(&sourceModel, this,
239		oldPoseList);
240	fCreateOldPoseList = false;
241
242	if (fQueryListContainer->InitCheck() != B_OK) {
243		delete fQueryListContainer;
244		fQueryListContainer = NULL;
245		return NULL;
246	}
247
248	TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_NAME | B_WATCH_STAT
249		| B_WATCH_ATTR, this);
250
251	fQueryList = fQueryListContainer->QueryList();
252
253	if (fQueryListContainer->DynamicDateQuery()) {
254		// calculate the time to trigger the query refresh - next midnight
255
256		time_t now = time(0);
257
258		time_t nextMidnight = now + 60 * 60 * 24;
259			// move ahead by a day
260		tm timeData;
261		localtime_r(&nextMidnight, &timeData);
262		timeData.tm_sec = 0;
263		timeData.tm_min = 0;
264		timeData.tm_hour = 0;
265		nextMidnight = mktime(&timeData);
266
267		time_t nextHour = now + 60 * 60;
268			// move ahead by a hour
269		localtime_r(&nextHour, &timeData);
270		timeData.tm_sec = 0;
271		timeData.tm_min = 0;
272		nextHour = mktime(&timeData);
273
274		PRINT(("%" B_PRIdTIME " minutes, %" B_PRIdTIME " seconds till next hour\n",
275			(nextHour - now) / 60, (nextHour - now) % 60));
276
277		time_t nextMinute = now + 60;
278			// move ahead by a minute
279		localtime_r(&nextMinute, &timeData);
280		timeData.tm_sec = 0;
281		nextMinute = mktime(&timeData);
282
283		PRINT(("%" B_PRIdTIME " seconds till next minute\n", nextMinute - now));
284
285		bigtime_t delta;
286		if (fQueryListContainer->DynamicDateRefreshEveryMinute())
287			delta = nextMinute - now;
288		else if (fQueryListContainer->DynamicDateRefreshEveryHour())
289			delta = nextHour - now;
290		else
291			delta = nextMidnight - now;
292
293#if DEBUG
294		int32 secondsTillMidnight = (nextMidnight - now);
295		int32 minutesTillMidnight = secondsTillMidnight/60;
296		secondsTillMidnight %= 60;
297		int32 hoursTillMidnight = minutesTillMidnight/60;
298		minutesTillMidnight %= 60;
299
300		PRINT(("%" B_PRId32 " hours, %" B_PRId32 " minutes, %" B_PRId32
301			" seconds till midnight\n", hoursTillMidnight, minutesTillMidnight,
302			secondsTillMidnight));
303
304		int32 refreshInSeconds = delta % 60;
305		int32 refreshInMinutes = delta / 60;
306		int32 refreshInHours = refreshInMinutes / 60;
307		refreshInMinutes %= 60;
308
309		PRINT(("next refresh in %" B_PRId32 " hours, %" B_PRId32 "minutes, %"
310			B_PRId32 " seconds\n", refreshInHours, refreshInMinutes,
311			refreshInSeconds));
312#endif
313
314		// bump up to microseconds
315		delta *= 1000000;
316
317		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
318		ThrowOnAssert(tracker != NULL);
319
320		tracker->MainTaskLoop()->RunLater(
321			NewLockingMemberFunctionObject(&BQueryPoseView::Refresh, this),
322			delta);
323	}
324
325	SetRefFilter(new QueryRefFilter(fQueryListContainer->ShowResultsFromTrash()));
326
327	return fQueryListContainer->Clone();
328}
329
330
331uint32
332BQueryPoseView::WatchNewNodeMask()
333{
334	return B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR;
335}
336
337
338const char*
339BQueryPoseView::SearchForType() const
340{
341	if (!fSearchForMimeType.Length()) {
342		BModelOpener opener(TargetModel());
343		BString buffer;
344		attr_info attrInfo;
345
346		// read the type of files we are looking for
347		status_t status
348			= TargetModel()->Node()->GetAttrInfo(kAttrQueryInitialMime,
349				&attrInfo);
350		if (status == B_OK) {
351			TargetModel()->Node()->ReadAttrString(kAttrQueryInitialMime,
352				&buffer);
353		}
354
355		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
356		if (tracker != NULL && buffer.Length() > 0) {
357			const ShortMimeInfo* info = tracker->MimeTypes()->FindMimeType(
358				buffer.String());
359			if (info != NULL)
360				fSearchForMimeType = info->InternalName();
361		}
362
363		if (!fSearchForMimeType.Length())
364			fSearchForMimeType = B_FILE_MIMETYPE;
365	}
366
367	return fSearchForMimeType.String();
368}
369
370
371bool
372BQueryPoseView::ActiveOnDevice(dev_t device) const
373{
374	int32 count = fQueryList->CountItems();
375	for (int32 index = 0; index < count; index++) {
376		if (fQueryList->ItemAt(index)->TargetDevice() == device)
377			return true;
378	}
379
380	return false;
381}
382
383
384//	#pragma mark - QueryRefFilter
385
386
387QueryRefFilter::QueryRefFilter(bool showResultsFromTrash)
388	:
389	fShowResultsFromTrash(showResultsFromTrash)
390{
391}
392
393
394bool
395QueryRefFilter::Filter(const entry_ref* ref, BNode* node, stat_beos* st,
396	const char* filetype)
397{
398	TTracker* tracker = dynamic_cast<TTracker*>(be_app);
399	return !(!fShowResultsFromTrash && tracker != NULL
400		&& tracker->InTrashNode(ref));
401}
402
403
404//	#pragma mark - QueryEntryListCollection
405
406
407QueryEntryListCollection::QueryEntryListCollection(Model* model,
408	BHandler* target, PoseList* oldPoseList)
409	:
410	fQueryListRep(new QueryListRep(new BObjectList<BQuery>(5, true)))
411{
412	Rewind();
413	attr_info info;
414	BQuery query;
415
416	BNode* modelNode = model->Node();
417	if (modelNode == NULL) {
418		fStatus = B_ERROR;
419		return;
420	}
421
422	// read the actual query string
423	fStatus = modelNode->GetAttrInfo(kAttrQueryString, &info);
424	if (fStatus != B_OK)
425		return;
426
427	BString buffer;
428	if (modelNode->ReadAttr(kAttrQueryString, B_STRING_TYPE, 0,
429		buffer.LockBuffer((int32)info.size),
430			(size_t)info.size) != info.size) {
431		fStatus = B_ERROR;
432		return;
433	}
434
435	buffer.UnlockBuffer();
436
437	// read the extra options
438	MoreOptionsStruct saveMoreOptions;
439	if (ReadAttr(modelNode, kAttrQueryMoreOptions,
440			kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions,
441			sizeof(MoreOptionsStruct),
442			&MoreOptionsStruct::EndianSwap) != kReadAttrFailed) {
443		fQueryListRep->fShowResultsFromTrash = saveMoreOptions.searchTrash;
444	}
445
446	fStatus = query.SetPredicate(buffer.String());
447
448	fQueryListRep->fOldPoseList = oldPoseList;
449	fQueryListRep->fDynamicDateQuery = false;
450
451	fQueryListRep->fRefreshEveryHour = false;
452	fQueryListRep->fRefreshEveryMinute = false;
453
454	if (modelNode->ReadAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0,
455			&fQueryListRep->fDynamicDateQuery,
456			sizeof(bool)) != sizeof(bool)) {
457		fQueryListRep->fDynamicDateQuery = false;
458	}
459
460	if (fQueryListRep->fDynamicDateQuery) {
461		// only refresh every minute on debug builds
462		fQueryListRep->fRefreshEveryMinute = buffer.IFindFirst("second") != -1
463			|| buffer.IFindFirst("minute") != -1;
464		fQueryListRep->fRefreshEveryHour = fQueryListRep->fRefreshEveryMinute
465			|| buffer.IFindFirst("hour") != -1;
466
467#if !DEBUG
468		// don't refresh every minute unless we are running debug build
469		fQueryListRep->fRefreshEveryMinute = false;
470#endif
471	}
472
473	if (fStatus != B_OK)
474		return;
475
476	bool searchAllVolumes = true;
477	status_t result = B_OK;
478
479	// get volumes to perform query on
480	if (modelNode->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) {
481		char* buffer = NULL;
482
483		if ((buffer = (char*)malloc((size_t)info.size)) != NULL
484			&& modelNode->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0,
485				buffer, (size_t)info.size) == info.size) {
486			BMessage message;
487			if (message.Unflatten(buffer) == B_OK) {
488				for (int32 index = 0; ;index++) {
489					ASSERT(index < 100);
490					BVolume volume;
491						// match a volume with the info embedded in
492						// the message
493					result = MatchArchivedVolume(&volume, &message, index);
494					if (result == B_OK) {
495						// start the query on this volume
496						result = FetchOneQuery(&query, target,
497							fQueryListRep->fQueryList, &volume);
498						if (result != B_OK)
499							continue;
500
501						searchAllVolumes = false;
502					} else if (result != B_DEV_BAD_DRIVE_NUM) {
503						// if B_DEV_BAD_DRIVE_NUM, the volume just isn't
504						// mounted this time around, keep looking for more
505						// if other error, bail
506						break;
507					}
508				}
509			}
510		}
511
512		free(buffer);
513	}
514
515	if (searchAllVolumes) {
516		// no specific volumes embedded in query, search everything
517		BVolumeRoster roster;
518		BVolume volume;
519
520		roster.Rewind();
521		while (roster.GetNextVolume(&volume) == B_OK)
522			if (volume.IsPersistent() && volume.KnowsQuery()) {
523				result = FetchOneQuery(&query, target,
524					fQueryListRep->fQueryList, &volume);
525				if (result != B_OK)
526					continue;
527			}
528	}
529
530	fStatus = B_OK;
531
532	return;
533}
534
535
536status_t
537QueryEntryListCollection::FetchOneQuery(const BQuery* copyThis,
538	BHandler* target, BObjectList<BQuery>* list, BVolume* volume)
539{
540	BQuery* query = new (nothrow) BQuery;
541	if (query == NULL)
542		return B_NO_MEMORY;
543
544	// have to fake a copy constructor here because BQuery doesn't have
545	// a copy constructor
546	BString buffer;
547	const_cast<BQuery*>(copyThis)->GetPredicate(&buffer);
548	query->SetPredicate(buffer.String());
549
550	query->SetTarget(BMessenger(target));
551	query->SetVolume(volume);
552
553	status_t result = query->Fetch();
554	if (result != B_OK) {
555		PRINT(("fetch error %s\n", strerror(result)));
556		delete query;
557		return result;
558	}
559
560	list->AddItem(query);
561
562	return B_OK;
563}
564
565
566QueryEntryListCollection::~QueryEntryListCollection()
567{
568	if (fQueryListRep->CloseQueryList())
569		delete fQueryListRep;
570}
571
572
573QueryEntryListCollection*
574QueryEntryListCollection::Clone()
575{
576	fQueryListRep->OpenQueryList();
577	return new QueryEntryListCollection(*this);
578}
579
580
581//	#pragma mark - QueryEntryListCollection
582
583
584QueryEntryListCollection::QueryEntryListCollection(
585	const QueryEntryListCollection &cloneThis)
586	:
587	EntryListBase(),
588	fQueryListRep(cloneThis.fQueryListRep)
589{
590	// only to be used by the Clone routine
591}
592
593
594void
595QueryEntryListCollection::ClearOldPoseList()
596{
597	delete fQueryListRep->fOldPoseList;
598	fQueryListRep->fOldPoseList = NULL;
599}
600
601
602status_t
603QueryEntryListCollection::GetNextEntry(BEntry* entry, bool traverse)
604{
605	status_t result = B_ERROR;
606
607	for (int32 count = fQueryListRep->fQueryList->CountItems();
608		fQueryListRep->fQueryListIndex < count;
609		fQueryListRep->fQueryListIndex++) {
610		result = fQueryListRep->fQueryList->
611			ItemAt(fQueryListRep->fQueryListIndex)->
612				GetNextEntry(entry, traverse);
613		if (result == B_OK)
614			break;
615	}
616
617	return result;
618}
619
620
621int32
622QueryEntryListCollection::GetNextDirents(struct dirent* buffer, size_t length,
623	int32 count)
624{
625	int32 result = 0;
626
627	for (int32 queryCount = fQueryListRep->fQueryList->CountItems();
628			fQueryListRep->fQueryListIndex < queryCount;
629			fQueryListRep->fQueryListIndex++) {
630		result = fQueryListRep->fQueryList->
631			ItemAt(fQueryListRep->fQueryListIndex)->
632				GetNextDirents(buffer, length, count);
633		if (result > 0)
634			break;
635	}
636
637	return result;
638}
639
640
641status_t
642QueryEntryListCollection::GetNextRef(entry_ref* ref)
643{
644	status_t result = B_ERROR;
645
646	for (int32 count = fQueryListRep->fQueryList->CountItems();
647		fQueryListRep->fQueryListIndex < count;
648		fQueryListRep->fQueryListIndex++) {
649
650		result = fQueryListRep->fQueryList->
651			ItemAt(fQueryListRep->fQueryListIndex)->GetNextRef(ref);
652		if (result == B_OK)
653			break;
654	}
655
656	return result;
657}
658
659
660status_t
661QueryEntryListCollection::Rewind()
662{
663	fQueryListRep->fQueryListIndex = 0;
664
665	return B_OK;
666}
667
668
669int32
670QueryEntryListCollection::CountEntries()
671{
672	return 0;
673}
674
675
676bool
677QueryEntryListCollection::ShowResultsFromTrash() const
678{
679	return fQueryListRep->fShowResultsFromTrash;
680}
681
682
683bool
684QueryEntryListCollection::DynamicDateQuery() const
685{
686	return fQueryListRep->fDynamicDateQuery;
687}
688
689
690bool
691QueryEntryListCollection::DynamicDateRefreshEveryHour() const
692{
693	return fQueryListRep->fRefreshEveryHour;
694}
695
696
697bool
698QueryEntryListCollection::DynamicDateRefreshEveryMinute() const
699{
700	return fQueryListRep->fRefreshEveryMinute;
701}
702