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