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