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