1/*
2 * Copyright 2015, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "QueryList.h"
8
9#include <pthread.h>
10
11#include <Autolock.h>
12#include <Debug.h>
13#include <NodeMonitor.h>
14#include <VolumeRoster.h>
15
16
17static BLooper* sQueryLooper = NULL;
18static pthread_once_t sInitOnce = PTHREAD_ONCE_INIT;
19
20
21static void
22initQueryLooper()
23{
24	sQueryLooper = new BLooper("query looper");
25	sQueryLooper->Run();
26}
27
28
29// #pragma mark - QueryListener
30
31
32QueryListener::~QueryListener()
33{
34}
35
36
37// #pragma mark - QueryList
38
39
40QueryList::QueryList()
41	:
42	fQuit(false),
43	fListeners(5, true)
44{
45}
46
47
48QueryList::~QueryList()
49{
50	fQuit = true;
51
52	ThreadVector::const_iterator threadIterator = fFetchThreads.begin();
53	for (; threadIterator != fFetchThreads.end(); threadIterator++) {
54		wait_for_thread(*threadIterator, NULL);
55	}
56
57	QueryVector::iterator queryIterator = fQueries.begin();
58	for (; queryIterator != fQueries.end(); queryIterator++) {
59		delete *queryIterator;
60	}
61}
62
63
64status_t
65QueryList::Init(const char* predicate, BVolume* specificVolume)
66{
67	if (sQueryLooper == NULL)
68		pthread_once(&sInitOnce, &initQueryLooper);
69
70	if (Looper() != NULL)
71		debugger("Init() called twice!");
72
73	sQueryLooper->Lock();
74	sQueryLooper->AddHandler(this);
75	sQueryLooper->Unlock();
76
77	if (specificVolume == NULL) {
78		BVolumeRoster roster;
79		BVolume volume;
80
81		while (roster.GetNextVolume(&volume) == B_OK) {
82			if (volume.KnowsQuery() && volume.KnowsAttr() && volume.KnowsMime())
83				_AddVolume(volume, predicate);
84		}
85	} else
86		_AddVolume(*specificVolume, predicate);
87
88	return B_OK;
89}
90
91
92void
93QueryList::AddListener(QueryListener* listener)
94{
95	BAutolock locker(this);
96
97	// Add all entries that were already retrieved
98	RefMap::const_iterator iterator = fRefs.begin();
99	for (; iterator != fRefs.end(); iterator++) {
100		listener->EntryCreated(*this, iterator->second, iterator->first.node);
101	}
102
103	fListeners.AddItem(listener);
104}
105
106
107void
108QueryList::RemoveListener(QueryListener* listener)
109{
110	BAutolock locker(this);
111	fListeners.RemoveItem(listener, false);
112}
113
114
115void
116QueryList::MessageReceived(BMessage* message)
117{
118	switch (message->what) {
119		case B_QUERY_UPDATE:
120		{
121			int32 opcode = message->GetInt32("opcode", -1);
122			int64 directory = message->GetInt64("directory", -1);
123			int32 device = message->GetInt32("device", -1);
124			int64 node = message->GetInt64("node", -1);
125
126			if (opcode == B_ENTRY_CREATED) {
127				const char* name = message->GetString("name");
128				if (name != NULL) {
129					entry_ref ref(device, directory, name);
130					_AddEntry(ref, node);
131				}
132			} else if (opcode == B_ENTRY_REMOVED) {
133				node_ref nodeRef(device, node);
134				_RemoveEntry(nodeRef);
135			}
136			break;
137		}
138
139		default:
140			BHandler::MessageReceived(message);
141			break;
142	}
143}
144
145
146void
147QueryList::_AddEntry(const entry_ref& ref, ino_t node)
148{
149	BAutolock locker(this);
150
151	// TODO: catch bad_alloc
152	fRefs.insert(std::make_pair(node_ref(ref.device, node), ref));
153
154	_NotifyEntryCreated(ref, node);
155}
156
157
158void
159QueryList::_RemoveEntry(const node_ref& nodeRef)
160{
161	BAutolock locker(this);
162	RefMap::iterator found = fRefs.find(nodeRef);
163	if (found != fRefs.end())
164		_NotifyEntryRemoved(nodeRef);
165}
166
167
168void
169QueryList::_NotifyEntryCreated(const entry_ref& ref, ino_t node)
170{
171	ASSERT(IsLocked());
172
173	int32 count = fListeners.CountItems();
174	for (int32 index = 0; index < count; index++) {
175		fListeners.ItemAt(index)->EntryCreated(*this, ref, node);
176	}
177}
178
179
180void
181QueryList::_NotifyEntryRemoved(const node_ref& nodeRef)
182{
183	ASSERT(IsLocked());
184
185	int32 count = fListeners.CountItems();
186	for (int32 index = 0; index < count; index++) {
187		fListeners.ItemAt(index)->EntryRemoved(*this, nodeRef);
188	}
189}
190
191
192void
193QueryList::_AddVolume(BVolume& volume, const char* predicate)
194{
195	BQuery* query = new BQuery();
196	if (query->SetVolume(&volume) != B_OK
197		|| query->SetPredicate(predicate) != B_OK
198		|| query->SetTarget(this) != B_OK) {
199		delete query;
200	}
201
202	// TODO: catch bad_alloc
203	fQueries.push_back(query);
204	Lock();
205	fQueryQueue.push_back(query);
206	Unlock();
207
208	thread_id thread = spawn_thread(_FetchQuery, "query fetcher",
209		B_NORMAL_PRIORITY, this);
210	if (thread >= B_OK) {
211		resume_thread(thread);
212
213		fFetchThreads.push_back(thread);
214	}
215}
216
217
218/*static*/ status_t
219QueryList::_FetchQuery(void* self)
220{
221	return static_cast<QueryList*>(self)->_FetchQuery();
222}
223
224
225status_t
226QueryList::_FetchQuery()
227{
228	RefMap map;
229
230	BAutolock locker(this);
231	BQuery* query = fQueryQueue.back();
232	fQueryQueue.pop_back();
233	locker.Unlock();
234
235	query->Fetch();
236
237	entry_ref ref;
238	while (!fQuit && query->GetNextRef(&ref) == B_OK) {
239		BEntry entry(&ref);
240		node_ref nodeRef;
241		if (entry.GetNodeRef(&nodeRef) == B_OK)
242			map.insert(std::make_pair(nodeRef, ref));
243	}
244	if (fQuit)
245		return B_INTERRUPTED;
246
247	locker.Lock();
248
249	RefMap::const_iterator iterator = map.begin();
250	for (; iterator != map.end(); iterator++) {
251		_AddEntry(iterator->second, iterator->first.node);
252	}
253
254	return B_OK;
255}
256