xref: /haiku/src/kits/tracker/QueryPoseView.cpp (revision e688bf23d48bfd1216a0cacbdbda5e35a1bcd779)
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(("%ld minutes, %ld 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(("%ld 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(("%ld hours, %ld minutes, %ld seconds till midnight\n",
329 			hoursTillMidnight, minutesTillMidnight, secondsTillMidnight));
330 
331 		int32 refreshInSeconds = delta % 60;
332 		int32 refreshInMinutes = delta / 60;
333 		int32 refreshInHours = refreshInMinutes / 60;
334 		refreshInMinutes %= 60;
335 
336 		PRINT(("next refresh in %ld hours, %ld minutes, %ld seconds\n",
337 			refreshInHours, refreshInMinutes, refreshInSeconds));
338 #endif
339 
340 		// bump up to microseconds
341 		delta *=  1000000;
342 
343 		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
344 		ASSERT(tracker);
345 		tracker->MainTaskLoop()->RunLater(
346 			NewLockingMemberFunctionObject(&BQueryPoseView::Refresh, this),
347 			delta);
348 	}
349 
350 	return fQueryListContainer->Clone();
351 }
352 
353 
354 uint32
355 BQueryPoseView::WatchNewNodeMask()
356 {
357 	return B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR;
358 }
359 
360 
361 const char*
362 BQueryPoseView::SearchForType() const
363 {
364 	if (!fSearchForMimeType.Length()) {
365 		BModelOpener opener(TargetModel());
366 		BString buffer;
367 		attr_info attrInfo;
368 
369 		// read the type of files we are looking for
370 		status_t status
371 			= TargetModel()->Node()->GetAttrInfo(kAttrQueryInitialMime,
372 				&attrInfo);
373 		if (status == B_OK) {
374 			TargetModel()->Node()->ReadAttrString(kAttrQueryInitialMime,
375 				&buffer);
376 		}
377 
378 		if (buffer.Length()) {
379 			TTracker* tracker = dynamic_cast<TTracker*>(be_app);
380 			if (tracker) {
381 				const ShortMimeInfo* info
382 					= tracker->MimeTypes()->FindMimeType(buffer.String());
383 				if (info)
384 					fSearchForMimeType = info->InternalName();
385 			}
386 		}
387 
388 		if (!fSearchForMimeType.Length())
389 			fSearchForMimeType = B_FILE_MIMETYPE;
390 	}
391 
392 	return fSearchForMimeType.String();
393 }
394 
395 
396 bool
397 BQueryPoseView::ActiveOnDevice(dev_t device) const
398 {
399 	int32 count = fQueryList->CountItems();
400 	for (int32 index = 0; index < count; index++)
401 		if (fQueryList->ItemAt(index)->TargetDevice() == device)
402 			return true;
403 
404 	return false;
405 }
406 
407 
408 //	#pragma mark -
409 
410 
411 QueryEntryListCollection::QueryEntryListCollection(Model* model,
412 	BHandler* target, PoseList* oldPoseList)
413 	:	fQueryListRep(new QueryListRep(new BObjectList<BQuery>(5, true)))
414 {
415 	Rewind();
416 	attr_info info;
417 	BQuery query;
418 
419 	if (!model->Node()) {
420 		fStatus = B_ERROR;
421 		return;
422 	}
423 
424 	// read the actual query string
425 	fStatus = model->Node()->GetAttrInfo(kAttrQueryString, &info);
426 	if (fStatus != B_OK)
427 		return;
428 
429 	BString buffer;
430 	if (model->Node()->ReadAttr(kAttrQueryString, B_STRING_TYPE, 0,
431 		buffer.LockBuffer((int32)info.size),
432 			(size_t)info.size) != info.size) {
433 		fStatus = B_ERROR;
434 		return;
435 	}
436 
437 	buffer.UnlockBuffer();
438 
439 	// read the extra options
440 	MoreOptionsStruct saveMoreOptions;
441 	if (ReadAttr(model->Node(), kAttrQueryMoreOptions,
442 			kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions,
443 			sizeof(MoreOptionsStruct),
444 			&MoreOptionsStruct::EndianSwap) != kReadAttrFailed) {
445 		fQueryListRep->fShowResultsFromTrash = saveMoreOptions.searchTrash;
446 	}
447 
448 	fStatus = query.SetPredicate(buffer.String());
449 
450 	fQueryListRep->fOldPoseList = oldPoseList;
451 	fQueryListRep->fDynamicDateQuery = false;
452 
453 	fQueryListRep->fRefreshEveryHour = false;
454 	fQueryListRep->fRefreshEveryMinute = false;
455 
456 	if (model->Node()->ReadAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0,
457 			&fQueryListRep->fDynamicDateQuery,
458 			sizeof(bool)) != sizeof(bool)) {
459 		fQueryListRep->fDynamicDateQuery = false;
460 	}
461 
462 	if (fQueryListRep->fDynamicDateQuery) {
463 		// only refresh every minute on debug builds
464 		fQueryListRep->fRefreshEveryMinute = buffer.IFindFirst("second") != -1
465 			|| buffer.IFindFirst("minute") != -1;
466 		fQueryListRep->fRefreshEveryHour = fQueryListRep->fRefreshEveryMinute
467 			|| buffer.IFindFirst("hour") != -1;
468 
469 #if !DEBUG
470 		// don't refresh every minute unless we are running debug build
471 		fQueryListRep->fRefreshEveryMinute = false;
472 #endif
473 	}
474 
475 	if (fStatus != B_OK)
476 		return;
477 
478 	bool searchAllVolumes = true;
479 	status_t result = B_OK;
480 
481 	// get volumes to perform query on
482 	if (model->Node()->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) {
483 		char* buffer = NULL;
484 
485 		if ((buffer = (char*)malloc((size_t)info.size)) != NULL
486 			&& model->Node()->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0,
487 				buffer, (size_t)info.size) == info.size) {
488 
489 			BMessage message;
490 			if (message.Unflatten(buffer) == B_OK) {
491 				for (int32 index = 0; ;index++) {
492 					ASSERT(index < 100);
493 					BVolume volume;
494 						// match a volume with the info embedded in
495 						// the message
496 					result = MatchArchivedVolume(&volume, &message, index);
497 					if (result == B_OK) {
498 						// start the query on this volume
499 						result = FetchOneQuery(&query, target,
500 							fQueryListRep->fQueryList, &volume);
501 						if (result != B_OK)
502 							continue;
503 
504 						searchAllVolumes = false;
505 					} else if (result != B_DEV_BAD_DRIVE_NUM) {
506 						// if B_DEV_BAD_DRIVE_NUM, the volume just isn't
507 						// mounted this time around, keep looking for more
508 						// if other error, bail
509 						break;
510 					}
511 				}
512 			}
513 		}
514 
515 		free(buffer);
516 	}
517 
518 	if (searchAllVolumes) {
519 		// no specific volumes embedded in query, search everything
520 		BVolumeRoster roster;
521 		BVolume volume;
522 
523 		roster.Rewind();
524 		while (roster.GetNextVolume(&volume) == B_OK)
525 			if (volume.IsPersistent() && volume.KnowsQuery()) {
526 				result = FetchOneQuery(&query, target,
527 					fQueryListRep->fQueryList, &volume);
528 				if (result != B_OK)
529 					continue;
530 			}
531 	}
532 
533 	fStatus = B_OK;
534 	return;
535 }
536 
537 
538 status_t
539 QueryEntryListCollection::FetchOneQuery(const BQuery* copyThis,
540 	BHandler* target, BObjectList<BQuery>* list, BVolume* volume)
541 {
542 	BQuery* query = new (nothrow) BQuery;
543 	if (query == NULL)
544 		return B_NO_MEMORY;
545 
546 	// have to fake a copy constructor here because BQuery doesn't have
547 	// a copy constructor
548 	BString buffer;
549 	const_cast<BQuery*>(copyThis)->GetPredicate(&buffer);
550 	query->SetPredicate(buffer.String());
551 
552 	query->SetTarget(BMessenger(target));
553 	query->SetVolume(volume);
554 
555 	status_t result = query->Fetch();
556 	if (result != B_OK) {
557 		PRINT(("fetch error %s\n", strerror(result)));
558 		delete query;
559 		return result;
560 	}
561 
562 	list->AddItem(query);
563 
564 	return B_OK;
565 }
566 
567 
568 QueryEntryListCollection::~QueryEntryListCollection()
569 {
570 	if (fQueryListRep->CloseQueryList())
571 		delete fQueryListRep;
572 }
573 
574 
575 QueryEntryListCollection*
576 QueryEntryListCollection::Clone()
577 {
578 	fQueryListRep->OpenQueryList();
579 	return new QueryEntryListCollection(*this);
580 }
581 
582 
583 QueryEntryListCollection::QueryEntryListCollection(
584 	const QueryEntryListCollection &cloneThis)
585 	:	EntryListBase(),
586 		fQueryListRep(cloneThis.fQueryListRep)
587 {
588 	// only to be used by the Clone routine
589 }
590 
591 
592 void
593 QueryEntryListCollection::ClearOldPoseList()
594 {
595 	delete fQueryListRep->fOldPoseList;
596 	fQueryListRep->fOldPoseList = NULL;
597 }
598 
599 
600 status_t
601 QueryEntryListCollection::GetNextEntry(BEntry* entry, bool traverse)
602 {
603 	status_t result = B_ERROR;
604 
605 	for (int32 count = fQueryListRep->fQueryList->CountItems();
606 		fQueryListRep->fQueryListIndex < count;
607 		fQueryListRep->fQueryListIndex++) {
608 		result = fQueryListRep->fQueryList->
609 			ItemAt(fQueryListRep->fQueryListIndex)
610 				->GetNextEntry(entry, traverse);
611 		if (result == B_OK)
612 			break;
613 	}
614 	return result;
615 }
616 
617 
618 int32
619 QueryEntryListCollection::GetNextDirents(struct dirent* buffer, size_t length,
620 	int32 count)
621 {
622 	int32 result = 0;
623 
624 	for (int32 queryCount = fQueryListRep->fQueryList->CountItems();
625 		fQueryListRep->fQueryListIndex < queryCount;
626 		fQueryListRep->fQueryListIndex++) {
627 
628 		result = fQueryListRep->fQueryList->
629 			ItemAt(fQueryListRep->fQueryListIndex)->GetNextDirents(buffer,
630 				length, count);
631 		if (result > 0)
632 			break;
633 	}
634 	return result;
635 }
636 
637 
638 status_t
639 QueryEntryListCollection::GetNextRef(entry_ref* ref)
640 {
641 	status_t result = B_ERROR;
642 
643 	for (int32 count = fQueryListRep->fQueryList->CountItems();
644 		fQueryListRep->fQueryListIndex < count;
645 		fQueryListRep->fQueryListIndex++) {
646 
647 		result = fQueryListRep->fQueryList->
648 			ItemAt(fQueryListRep->fQueryListIndex)->GetNextRef(ref);
649 		if (result == B_OK)
650 			break;
651 	}
652 
653 	return result;
654 }
655 
656 
657 status_t
658 QueryEntryListCollection::Rewind()
659 {
660 	fQueryListRep->fQueryListIndex = 0;
661 
662 	return B_OK;
663 }
664 
665 
666 int32
667 QueryEntryListCollection::CountEntries()
668 {
669 	return 0;
670 }
671 
672 
673 bool
674 QueryEntryListCollection::ShowResultsFromTrash() const
675 {
676 	return fQueryListRep->fShowResultsFromTrash;
677 }
678 
679 
680 bool
681 QueryEntryListCollection::DynamicDateQuery() const
682 {
683 	return fQueryListRep->fDynamicDateQuery;
684 }
685 
686 
687 bool
688 QueryEntryListCollection::DynamicDateRefreshEveryHour() const
689 {
690 	return fQueryListRep->fRefreshEveryHour;
691 }
692 
693 
694 bool
695 QueryEntryListCollection::DynamicDateRefreshEveryMinute() const
696 {
697 	return fQueryListRep->fRefreshEveryMinute;
698 }
699