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