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