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