xref: /haiku/src/kits/tracker/QueryPoseView.cpp (revision 25a7b01d15612846f332751841da3579db313082)
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 #undef B_TRANSLATION_CONTEXT
63 #define B_TRANSLATION_CONTEXT "QueryPoseView"
64 
65 using 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 
74 BQueryPoseView::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 
84 BQueryPoseView::~BQueryPoseView()
85 {
86 	delete fQueryListContainer;
87 }
88 
89 
90 void
91 BQueryPoseView::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 
108 void
109 BQueryPoseView::EditQueries()
110 {
111 	BMessage message(kEditQuery);
112 	message.AddRef("refs", TargetModel()->EntryRef());
113 	BMessenger(kTrackerSignature, -1, 0).SendMessage(&message);
114 }
115 
116 
117 void
118 BQueryPoseView::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 
135 void
136 BQueryPoseView::AttachedToWindow()
137 {
138 	_inherited::AttachedToWindow();
139 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
140 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
141 }
142 
143 
144 void
145 BQueryPoseView::RestoreState(AttributeStreamNode* node)
146 {
147 	_inherited::RestoreState(node);
148 	fViewState->SetViewMode(kListMode);
149 }
150 
151 
152 void
153 BQueryPoseView::RestoreState(const BMessage &message)
154 {
155 	_inherited::RestoreState(message);
156 	fViewState->SetViewMode(kListMode);
157 }
158 
159 
160 void
161 BQueryPoseView::SavePoseLocations(BRect*)
162 {
163 }
164 
165 
166 void
167 BQueryPoseView::SetViewMode(uint32)
168 {
169 }
170 
171 
172 void
173 BQueryPoseView::OpenParent()
174 {
175 }
176 
177 
178 void
179 BQueryPoseView::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 
197 bool
198 BQueryPoseView::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 
221 void
222 BQueryPoseView::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 
243 EntryListBase*
244 BQueryPoseView::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 
356 uint32
357 BQueryPoseView::WatchNewNodeMask()
358 {
359 	return B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR;
360 }
361 
362 
363 const char*
364 BQueryPoseView::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 
398 bool
399 BQueryPoseView::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 
413 QueryEntryListCollection::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 
540 status_t
541 QueryEntryListCollection::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 
570 QueryEntryListCollection::~QueryEntryListCollection()
571 {
572 	if (fQueryListRep->CloseQueryList())
573 		delete fQueryListRep;
574 }
575 
576 
577 QueryEntryListCollection*
578 QueryEntryListCollection::Clone()
579 {
580 	fQueryListRep->OpenQueryList();
581 	return new QueryEntryListCollection(*this);
582 }
583 
584 
585 QueryEntryListCollection::QueryEntryListCollection(
586 	const QueryEntryListCollection &cloneThis)
587 	:	EntryListBase(),
588 		fQueryListRep(cloneThis.fQueryListRep)
589 {
590 	// only to be used by the Clone routine
591 }
592 
593 
594 void
595 QueryEntryListCollection::ClearOldPoseList()
596 {
597 	delete fQueryListRep->fOldPoseList;
598 	fQueryListRep->fOldPoseList = NULL;
599 }
600 
601 
602 status_t
603 QueryEntryListCollection::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 
620 int32
621 QueryEntryListCollection::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 
640 status_t
641 QueryEntryListCollection::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 
659 status_t
660 QueryEntryListCollection::Rewind()
661 {
662 	fQueryListRep->fQueryListIndex = 0;
663 
664 	return B_OK;
665 }
666 
667 
668 int32
669 QueryEntryListCollection::CountEntries()
670 {
671 	return 0;
672 }
673 
674 
675 bool
676 QueryEntryListCollection::ShowResultsFromTrash() const
677 {
678 	return fQueryListRep->fShowResultsFromTrash;
679 }
680 
681 
682 bool
683 QueryEntryListCollection::DynamicDateQuery() const
684 {
685 	return fQueryListRep->fDynamicDateQuery;
686 }
687 
688 
689 bool
690 QueryEntryListCollection::DynamicDateRefreshEveryHour() const
691 {
692 	return fQueryListRep->fRefreshEveryHour;
693 }
694 
695 
696 bool
697 QueryEntryListCollection::DynamicDateRefreshEveryMinute() const
698 {
699 	return fQueryListRep->fRefreshEveryMinute;
700 }
701