1/*
2 * Copyright 2004-2010, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Andrew Bachmann
7 *		Stephan A��mus <superstippi@gmx.de>
8 */
9
10
11#include "AddOnMonitorHandler.h"
12
13#include <string.h>
14
15#include <Autolock.h>
16#include <Directory.h>
17
18
19#ifndef ADD_ON_STABLE_SECONDS
20#	define ADD_ON_STABLE_SECONDS 1
21#endif
22
23
24AddOnMonitorHandler::AddOnMonitorHandler(const char* name)
25	:
26	NodeMonitorHandler(name != NULL ? name : "AddOnMonitorHandler")
27{
28}
29
30
31AddOnMonitorHandler::~AddOnMonitorHandler()
32{
33	// TODO: Actually calling watch_node() here should be too late, since we
34	// are likely not attached to a looper anymore, and thus consitute no valid
35	// BMessenger at this time.
36	DirectoryList::iterator it = fDirectories.begin();
37	for (; it != fDirectories.end(); it++) {
38		EntryList::iterator eiter = it->entries.begin();
39		for (; eiter != it->entries.end(); eiter++)
40			watch_node(&eiter->addon_nref, B_STOP_WATCHING, this);
41		watch_node(&it->nref, B_STOP_WATCHING, this);
42	}
43}
44
45
46void
47AddOnMonitorHandler::MessageReceived(BMessage* msg)
48{
49	if (msg->what == B_PULSE)
50		_HandlePendingEntries();
51
52	inherited::MessageReceived(msg);
53}
54
55
56status_t
57AddOnMonitorHandler::AddDirectory(const node_ref* nref, bool sync)
58{
59	// Keep the looper thread locked, since this method is likely to be called
60	// in a thread other than the looper thread. Otherwise we may access the
61	// lists concurrently with the looper thread, when node monitor
62	// notifications arrive while we are still adding initial entries from the
63	// directory, or (much more likely) if the looper thread handles a pulse
64	// message and wants to process pending entries while we are still adding
65	// them.
66	BAutolock _(Looper());
67
68	// ignore directories added twice
69	DirectoryList::iterator it = fDirectories.begin();
70	for (; it != fDirectories.end(); it++) {
71		if (it->nref == *nref)
72			return B_OK;
73	}
74
75	BDirectory directory(nref);
76	status_t status = directory.InitCheck();
77	if (status != B_OK)
78		return status;
79
80	status = watch_node(nref, B_WATCH_DIRECTORY, this);
81	if (status != B_OK)
82		return status;
83
84	add_on_directory_info dirInfo;
85	dirInfo.nref = *nref;
86	fDirectories.push_back(dirInfo);
87
88	add_on_entry_info entryInfo;
89	entryInfo.dir_nref = *nref;
90
91	BEntry entry;
92	while (directory.GetNextEntry(&entry) == B_OK) {
93		if (entry.GetName(entryInfo.name) != B_OK
94			|| entry.GetNodeRef(&entryInfo.nref) != B_OK) {
95			continue;
96		}
97
98		fPendingEntries.push_back(entryInfo);
99	}
100
101	if (sync)
102		_HandlePendingEntries();
103
104	return B_OK;
105}
106
107
108//	#pragma mark - AddOnMonitorHandler hooks
109
110
111void
112AddOnMonitorHandler::AddOnCreated(const add_on_entry_info* entryInfo)
113{
114
115}
116
117
118void
119AddOnMonitorHandler::AddOnEnabled(const add_on_entry_info* entryInfo)
120{
121}
122
123
124void
125AddOnMonitorHandler::AddOnDisabled(const add_on_entry_info* entryInfo)
126{
127}
128
129
130void
131AddOnMonitorHandler::AddOnRemoved(const add_on_entry_info* entryInfo)
132{
133}
134
135
136//	#pragma mark - NodeMonitorHandler hooks
137
138
139void
140AddOnMonitorHandler::EntryCreated(const char* name, ino_t directory,
141	dev_t device, ino_t node)
142{
143	add_on_entry_info entryInfo;
144	strlcpy(entryInfo.name, name, sizeof(entryInfo.name));
145	make_node_ref(device, node, &entryInfo.nref);
146	make_node_ref(device, directory, &entryInfo.dir_nref);
147	fPendingEntries.push_back(entryInfo);
148}
149
150
151void
152AddOnMonitorHandler::EntryRemoved(const char* name, ino_t directory,
153	dev_t device, ino_t node)
154{
155	node_ref entryNodeRef;
156	make_node_ref(device, node, &entryNodeRef);
157
158	// Search pending entries first, which can simply be discarded
159	// We might have this entry in the pending list multiple times,
160	// so we search entire list through, even after finding one.
161	EntryList::iterator eiter = fPendingEntries.begin();
162	while (eiter != fPendingEntries.end()) {
163		if (eiter->nref == entryNodeRef)
164			eiter = fPendingEntries.erase(eiter);
165		else
166			eiter++;
167	}
168
169	// Find the directory of the entry.
170	DirectoryList::iterator diter = fDirectories.begin();
171	if (!_FindDirectory(directory, device, diter)) {
172		// If it was not found, we're done
173		return;
174	}
175
176	eiter = diter->entries.begin();
177	if (!_FindEntry(entryNodeRef, diter->entries, eiter)) {
178		// This must be the directory, but we didn't find the entry.
179		return;
180	}
181
182	add_on_entry_info info = *eiter;
183	watch_node(&entryNodeRef, B_STOP_WATCHING, this);
184	diter->entries.erase(eiter);
185
186	// Start at the top again, and search until the directory we found
187	// the old add-on in. If we find an add-on with the same name then
188	// the old add-on was not enabled. So we deallocate the old add-on and
189	// return.
190	DirectoryList::iterator diter2 = fDirectories.begin();
191	for (; diter2 != diter; diter2++) {
192		if (_HasEntry(info.name, diter2->entries)) {
193			AddOnRemoved(&info);
194			return;
195		}
196	}
197
198	// An active add-on was removed. We need to disable and then subsequently
199	// deallocate it.
200	AddOnDisabled(&info);
201	AddOnRemoved(&info);
202
203	// Continue searching for an add-on below us. If we find an add-on
204	// with the same name, we must enable it.
205	for (diter++; diter != fDirectories.end(); diter++) {
206		eiter = diter->entries.begin();
207		if (_FindEntry(info.name, diter->entries, eiter)) {
208			AddOnEnabled(&*eiter);
209			break;
210		}
211	}
212}
213
214
215void
216AddOnMonitorHandler::EntryMoved(const char* name, const char* fromName,
217	ino_t fromDirectory, ino_t toDirectory, dev_t device, ino_t node,
218	dev_t nodeDevice)
219{
220	node_ref toNodeRef;
221	make_node_ref(device, toDirectory, &toNodeRef);
222
223	// Search the "from" and "to" directory in the known directories
224	DirectoryList::iterator fromIter = fDirectories.begin();
225	bool watchingFromDirectory = _FindDirectory(fromDirectory, device,
226		fromIter);
227
228	DirectoryList::iterator toIter = fDirectories.begin();
229	bool watchingToDirectory = _FindDirectory(toNodeRef, toIter);
230
231	if (!watchingFromDirectory && !watchingToDirectory) {
232		// It seems the notification was for a directory we are not
233		// actually watching (at least not by intention).
234		return;
235	}
236
237	add_on_entry_info info;
238
239	node_ref entryNodeRef;
240	make_node_ref(device, node, &entryNodeRef);
241
242	if (!watchingToDirectory) {
243		// moved out of our view
244		EntryList::iterator eiter = fromIter->entries.begin();
245		if (!_FindEntry(entryNodeRef, fromIter->entries, eiter)) {
246			// we don't know anything about this entry yet.. ignore it
247			return;
248		}
249
250		// save the info and remove the entry
251		info = *eiter;
252		watch_node(&entryNodeRef, B_STOP_WATCHING, this);
253		fromIter->entries.erase(eiter);
254
255		// Start at the top again, and search until the from directory.
256		// If we find a add-on with the same name then the moved add-on
257		// was not enabled.  So we are done.
258		DirectoryList::iterator diter = fDirectories.begin();
259		for (; diter != fromIter; diter++) {
260			eiter = diter->entries.begin();
261			if (_FindEntry(info.name, diter->entries, eiter))
262				return;
263		}
264
265		// finally disable the add-on
266		AddOnDisabled(&info);
267
268		// Continue searching for a add-on below us.  If we find a add-on
269		// with the same name, we must enable it.
270		for (fromIter++; fromIter != fDirectories.end(); fromIter++) {
271			eiter = fromIter->entries.begin();
272			if (_FindEntry(info.name, fromIter->entries, eiter)) {
273				AddOnEnabled(&*eiter);
274				return;
275			}
276		}
277
278		// finally destroy the addon
279		AddOnRemoved(&info);
280
281		// done
282		return;
283	}
284
285	if (!watchingFromDirectory) {
286		// moved into our view
287
288		// update the info
289		strlcpy(info.name, name, sizeof(info.name));
290		info.nref = entryNodeRef;
291		info.dir_nref = toNodeRef;
292
293		AddOnCreated(&info);
294
295		// Start at the top again, and search until the to directory.
296		// If we find an add-on with the same name then the moved add-on
297		// is not to be enabled. So we are done.
298		DirectoryList::iterator diter = fDirectories.begin();
299		for (; diter != toIter; diter++) {
300			if (_HasEntry(info.name, diter->entries)) {
301				// The new add-on is being shadowed.
302				return;
303			}
304		}
305
306		// The new add-on should be enabled, but first we check to see
307		// if there is an add-on below us. If we find one, we disable it.
308		for (diter++ ; diter != fDirectories.end(); diter++) {
309			EntryList::iterator eiter = diter->entries.begin();
310			if (_FindEntry(info.name, diter->entries, eiter)) {
311				AddOnDisabled(&*eiter);
312				break;
313			}
314		}
315
316		// enable the new add-on
317		AddOnEnabled(&info);
318
319		// put the new entry into the target directory
320		_AddNewEntry(toIter->entries, info);
321
322		// done
323		return;
324	}
325
326	// The add-on was renamed, or moved within our hierarchy.
327
328	EntryList::iterator eiter = fromIter->entries.begin();
329	if (_FindEntry(entryNodeRef, fromIter->entries, eiter)) {
330		// save the old info and remove the entry
331		info = *eiter;
332	} else {
333		// If an entry moved from one watched directory into another watched
334		// directory, there will be two notifications, and this may be the
335		// second. We have handled everything in the first. In that case the
336		// entry was already removed from the fromDirectory and added in the
337		// toDirectory list.
338		return;
339	}
340
341	if (strcmp(info.name, name) == 0) {
342		// It should be impossible for the name to stay the same, unless the
343		// node moved in the watched hierarchy. Handle this case by removing
344		// the entry and readding it. TODO: This can temporarily enable add-ons
345		// which should in fact stay hidden (moving add-on from home to common
346		// folder or vice versa, the system add-on should remain hidden).
347		EntryRemoved(name, fromDirectory, device, node);
348		info.dir_nref = toNodeRef;
349		_EntryCreated(info);
350	} else {
351		// Erase the entry
352		fromIter->entries.erase(eiter);
353
354		// check to see if it was formerly enabled
355		bool wasEnabled = true;
356		DirectoryList::iterator oldIter = fDirectories.begin();
357		for (; oldIter != fromIter; oldIter++) {
358			if (_HasEntry(info.name, oldIter->entries)) {
359				wasEnabled = false;
360				break;
361			}
362		}
363
364		// If it was enabled, disable it and enable the one under us, if it
365		// exists.
366		if (wasEnabled) {
367			AddOnDisabled(&info);
368			for (; oldIter != fDirectories.end(); oldIter++) {
369				eiter = oldIter->entries.begin();
370				if (_FindEntry(info.name, oldIter->entries, eiter)) {
371					AddOnEnabled(&*eiter);
372					break;
373				}
374			}
375		}
376
377		// kaboom!
378		AddOnRemoved(&info);
379
380		// set up new addon info
381		strlcpy(info.name, name, sizeof(info.name));
382		info.dir_nref = toNodeRef;
383
384		// presto!
385		AddOnCreated(&info);
386
387		// check to see if we are newly enabled
388		bool isEnabled = true;
389		DirectoryList::iterator newIter = fDirectories.begin();
390		for (; newIter != toIter; newIter++) {
391			if (_HasEntry(info.name, newIter->entries)) {
392				isEnabled = false;
393				break;
394			}
395		}
396
397		// if it is newly enabled, check under us for an enabled one, and
398		// disable that first
399		if (isEnabled) {
400			for (; newIter != fDirectories.end(); newIter++) {
401				eiter = newIter->entries.begin();
402				if (_FindEntry(info.name, newIter->entries, eiter)) {
403					AddOnDisabled(&*eiter);
404					break;
405				}
406			}
407			AddOnEnabled(&info);
408		}
409		// put the new entry into the target directory
410		toIter->entries.push_back(info);
411	}
412}
413
414
415void
416AddOnMonitorHandler::StatChanged(ino_t node, dev_t device, int32 statFields)
417{
418	// This notification is received for the add-ons themselves.
419
420	// TODO: Add the entry to the pending list, disable/enable it
421	// when the modification time remains stable.
422
423	node_ref entryNodeRef;
424	make_node_ref(device, node, &entryNodeRef);
425
426	DirectoryList::iterator diter = fDirectories.begin();
427	for (; diter != fDirectories.end(); diter++) {
428		EntryList::iterator eiter = diter->entries.begin();
429		for (; eiter != diter->entries.end(); eiter++) {
430			if (eiter->addon_nref == entryNodeRef) {
431				// Trigger reloading of the add-on
432				const add_on_entry_info* info = &*eiter;
433				AddOnDisabled(info);
434				AddOnRemoved(info);
435				AddOnCreated(info);
436				AddOnEnabled(info);
437				return;
438			}
439		}
440	}
441}
442
443
444// #pragma mark - private
445
446
447//!	Process pending entries.
448void
449AddOnMonitorHandler::_HandlePendingEntries()
450{
451	BDirectory directory;
452	EntryList::iterator iter = fPendingEntries.begin();
453	while (iter != fPendingEntries.end()) {
454		add_on_entry_info info = *iter;
455
456		// Initialize directory, or re-use from previous iteration, if
457		// directory node_ref remained the same from the last pending entry.
458		node_ref dirNodeRef;
459		if (directory.GetNodeRef(&dirNodeRef) != B_OK
460			|| dirNodeRef != info.dir_nref) {
461			if (directory.SetTo(&info.dir_nref) != B_OK) {
462				// invalid directory, discard this pending entry
463				iter = fPendingEntries.erase(iter);
464				continue;
465			}
466			dirNodeRef = info.dir_nref;
467		}
468
469		struct stat st;
470		if (directory.GetStatFor(info.name, &st) != B_OK) {
471			// invalid file name, discard this pending entry
472			iter = fPendingEntries.erase(iter);
473			continue;
474		}
475
476		// stat units are seconds, real_time_clock units are seconds
477		if (real_time_clock() - st.st_mtime < ADD_ON_STABLE_SECONDS) {
478			// entry not stable, skip the entry for this pulse
479			iter++;
480			continue;
481		}
482
483		// we are going to deal with the stable entry, so remove it
484		iter = fPendingEntries.erase(iter);
485
486		_EntryCreated(info);
487	}
488}
489
490
491void
492AddOnMonitorHandler::_EntryCreated(add_on_entry_info& info)
493{
494	// put the new entry into the directory info
495	DirectoryList::iterator diter = fDirectories.begin();
496	for (; diter != fDirectories.end(); diter++) {
497		if (diter->nref == info.dir_nref) {
498			_AddNewEntry(diter->entries, info);
499			break;
500		}
501	}
502
503	// report it
504	AddOnCreated(&info);
505
506	// Start at the top again, and search until the directory we put
507	// the new add-on in.  If we find an add-on with the same name then
508	// the new add-on should not be enabled.
509	bool enabled = true;
510	DirectoryList::iterator diter2 = fDirectories.begin();
511	for (; diter2 != diter; diter2++) {
512		if (_HasEntry(info.name, diter2->entries)) {
513			enabled = false;
514			break;
515		}
516	}
517	if (!enabled)
518		return;
519
520	// The new add-on should be enabled, but first we check to see
521	// if there is an add-on shadowed by the new one.  If we find one,
522	// we disable it.
523	for (diter++ ; diter != fDirectories.end(); diter++) {
524		EntryList::iterator eiter = diter->entries.begin();
525		if (_FindEntry(info.name, diter->entries, eiter)) {
526			AddOnDisabled(&*eiter);
527			break;
528		}
529	}
530
531	// enable the new entry
532	AddOnEnabled(&info);
533}
534
535
536bool
537AddOnMonitorHandler::_FindEntry(const node_ref& entry, const EntryList& list,
538	EntryList::iterator& it) const
539{
540	for (; EntryList::const_iterator(it) != list.end(); it++) {
541		if (it->nref == entry)
542			return true;
543	}
544	return false;
545}
546
547
548bool
549AddOnMonitorHandler::_FindEntry(const char* name, const EntryList& list,
550	EntryList::iterator& it) const
551{
552	for (; EntryList::const_iterator(it) != list.end(); it++) {
553		if (strcmp(it->name, name) == 0)
554			return true;
555	}
556	return false;
557}
558
559
560bool
561AddOnMonitorHandler::_HasEntry(const node_ref& entry, EntryList& list) const
562{
563	EntryList::iterator it = list.begin();
564	return _FindEntry(entry, list, it);
565}
566
567
568bool
569AddOnMonitorHandler::_HasEntry(const char* name, EntryList& list) const
570{
571	EntryList::iterator it = list.begin();
572	return _FindEntry(name, list, it);
573}
574
575
576bool
577AddOnMonitorHandler::_FindDirectory(ino_t directory, dev_t device,
578	DirectoryList::iterator& it) const
579{
580	node_ref nodeRef;
581	make_node_ref(device, directory, &nodeRef);
582	return _FindDirectory(nodeRef, it, fDirectories.end());
583}
584
585
586bool
587AddOnMonitorHandler::_FindDirectory(const node_ref& directoryNodeRef,
588	DirectoryList::iterator& it) const
589{
590	return _FindDirectory(directoryNodeRef, it, fDirectories.end());
591}
592
593
594bool
595AddOnMonitorHandler::_FindDirectory(ino_t directory, dev_t device,
596	DirectoryList::iterator& it,
597	const DirectoryList::const_iterator& end) const
598{
599	node_ref nodeRef;
600	make_node_ref(device, directory, &nodeRef);
601	return _FindDirectory(nodeRef, it, end);
602}
603
604
605bool
606AddOnMonitorHandler::_FindDirectory(const node_ref& directoryNodeRef,
607	DirectoryList::iterator& it,
608	const DirectoryList::const_iterator& end) const
609{
610	for (; DirectoryList::const_iterator(it) != end; it++) {
611		if (it->nref == directoryNodeRef)
612			return true;
613	}
614	return false;
615}
616
617
618void
619AddOnMonitorHandler::_AddNewEntry(EntryList& list, add_on_entry_info& info)
620{
621	BDirectory directory(&info.dir_nref);
622	BEntry entry(&directory, info.name, true);
623
624	node_ref addOnRef;
625	if (entry.GetNodeRef(&addOnRef) == B_OK) {
626		watch_node(&addOnRef, B_WATCH_STAT, this);
627		info.addon_nref = addOnRef;
628	} else
629		info.addon_nref = info.nref;
630
631	list.push_back(info);
632}
633