⛏️ index : haiku.git

/*
 * Copyright 2010, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Clemens Zeidler <haiku@clemens-zeidler.de>
 */

#include "VolumeWatcher.h"

#include <sys/stat.h>

#include <Autolock.h>
#include <Directory.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <VolumeRoster.h>
#include <Query.h>


#include "IndexServerPrivate.h"


const bigtime_t kSecond = 1000000;


WatchNameHandler::WatchNameHandler(VolumeWatcher* volumeWatcher)
	:
	fVolumeWatcher(volumeWatcher)
{

}


void
WatchNameHandler::EntryCreated(const char *name, ino_t directory, dev_t device,
	ino_t node)
{
	entry_ref ref(device, directory, name);
	fVolumeWatcher->fCreatedList.CurrentList()->push_back(ref);
	fVolumeWatcher->_NewEntriesArrived();
}


void
WatchNameHandler::EntryRemoved(const char *name, ino_t directory, dev_t device,
	ino_t node)
{
	entry_ref ref(device, directory, name);
	fVolumeWatcher->fDeleteList.CurrentList()->push_back(ref);
	fVolumeWatcher->_NewEntriesArrived();
}


void
WatchNameHandler::EntryMoved(const char *name, const char *fromName,
	ino_t from_directory, ino_t to_directory, dev_t device, ino_t node,
	dev_t nodeDevice)
{
	entry_ref ref(device, to_directory, name);
	entry_ref refFrom(device, from_directory, fromName);

	fVolumeWatcher->fMovedList.CurrentList()->push_back(ref);
	fVolumeWatcher->fMovedFromList.CurrentList()->push_back(refFrom);
	fVolumeWatcher->_NewEntriesArrived();
}


void
WatchNameHandler::StatChanged(ino_t node, dev_t device, int32 statFields)
{
	if ((statFields & B_STAT_MODIFICATION_TIME) == 0)
		return;
}


void
WatchNameHandler::MessageReceived(BMessage* msg)
{
	if (msg->what == B_NODE_MONITOR) {
		int32 opcode;
		if (msg->FindInt32("opcode", &opcode) == B_OK) {
			switch (opcode) {
			case B_STAT_CHANGED: {
				BString name;
				entry_ref ref;
				ino_t node;
				int32 statFields;
				msg->FindInt32("fields", &statFields);
				if ((statFields & B_STAT_MODIFICATION_TIME) == 0)
					break;
				msg->FindInt32("device", &ref.device);
				msg->FindInt64("node", &node);
				msg->FindInt64("directory", &ref.directory);
				msg->FindString("name", &name);

				ref.set_name(name);

				BPath path(&ref);
				printf("stat changed node %i name %s %s\n", (int)node,
					name.String(), path.Path());

				fVolumeWatcher->fModifiedList.CurrentList()->push_back(ref);
				fVolumeWatcher->_NewEntriesArrived();

				break;
			}
			}
		}
	}
	NodeMonitorHandler::MessageReceived(msg);
}


AnalyserDispatcher::AnalyserDispatcher(const char* name)
	:
	BLooper(name, B_LOW_PRIORITY),

	fStopped(0)
{

}


AnalyserDispatcher::~AnalyserDispatcher()
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		delete fFileAnalyserList.ItemAt(i);
}


void
AnalyserDispatcher::Stop()
{
	atomic_set(&fStopped, 1);
}


bool
AnalyserDispatcher::Stopped()
{
	return (atomic_get(&fStopped) != 0);
}


void
AnalyserDispatcher::AnalyseEntry(const entry_ref& ref)
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		fFileAnalyserList.ItemAt(i)->AnalyseEntry(ref);
}


void
AnalyserDispatcher::DeleteEntry(const entry_ref& ref)
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		fFileAnalyserList.ItemAt(i)->DeleteEntry(ref);
}


void
AnalyserDispatcher::MoveEntry(const entry_ref& oldRef, const entry_ref& newRef)
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		fFileAnalyserList.ItemAt(i)->MoveEntry(oldRef, newRef);
}


void
AnalyserDispatcher::LastEntry()
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		fFileAnalyserList.ItemAt(i)->LastEntry();
}


bool
AnalyserDispatcher::AddAnalyser(FileAnalyser* analyser)
{
	if (analyser == NULL)
		return false;

	bool result;
	BAutolock _(this);
	if (_FindAnalyser(analyser->Name()))
		return false;

	result = fFileAnalyserList.AddItem(analyser);
	return result;
}


bool
AnalyserDispatcher::RemoveAnalyser(const BString& name)
{
	BAutolock _(this);
	FileAnalyser* analyser = _FindAnalyser(name);
	if (analyser) {
		fFileAnalyserList.RemoveItem(analyser);
		delete analyser;
		return true;
	}
	return false;
}


FileAnalyser*
AnalyserDispatcher::_FindAnalyser(const BString& name)
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++) {
		FileAnalyser* analyser = fFileAnalyserList.ItemAt(i);
		if (analyser->Name() == name)
			return analyser;
	}
	return NULL;
}


void
AnalyserDispatcher::WriteAnalyserSettings()
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		fFileAnalyserList.ItemAt(i)->Settings()->WriteSettings();
}


void
AnalyserDispatcher::SetSyncPosition(bigtime_t time)
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		fFileAnalyserList.ItemAt(i)->Settings()->SetSyncPosition(time);
}


void
AnalyserDispatcher::SetWatchingStart(bigtime_t time)
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		fFileAnalyserList.ItemAt(i)->Settings()->SetWatchingStart(time);
}


void
AnalyserDispatcher::SetWatchingPosition(bigtime_t time)
{
	for (int i = 0; i < fFileAnalyserList.CountItems(); i++)
		fFileAnalyserList.ItemAt(i)->Settings()->SetWatchingPosition(time);
}


VolumeWorker::VolumeWorker(VolumeWatcher* watcher)
	:
	AnalyserDispatcher("VolumeWorker"),

	fVolumeWatcher(watcher),
	fBusy(0)
{

}


void
VolumeWorker::MessageReceived(BMessage *message)
{
	switch (message->what) {
		case kTriggerWork:
			_Work();
			break;

		default:
			BLooper::MessageReceived(message);
	}
}


bool
VolumeWorker::IsBusy()
{
	return (atomic_get(&fBusy) != 0);
}


void
VolumeWorker::_Work()
{
	list_collection collection;
	fVolumeWatcher->GetSecureEntries(collection);

	if (collection.createdList->size() == 0
		&& collection.deletedList->size() == 0
		&& collection.modifiedList->size() == 0
		&& collection.movedList->size() == 0)
		return;

	_SetBusy(true);
	for (unsigned int i = 0; i < collection.createdList->size() || Stopped();
		i++)
		AnalyseEntry((*collection.createdList)[i]);
	collection.createdList->clear();

	for (unsigned int i = 0; i < collection.deletedList->size() || Stopped();
		i++)
		DeleteEntry((*collection.deletedList)[i]);
	collection.deletedList->clear();

	for (unsigned int i = 0; i < collection.modifiedList->size() || Stopped();
		i++)
		AnalyseEntry((*collection.modifiedList)[i]);
	collection.modifiedList->clear();

	for (unsigned int i = 0; i < collection.movedList->size() || Stopped();
		i++)
		MoveEntry((*collection.movedFromList)[i], (*collection.movedList)[i]);
	collection.movedList->clear();
	collection.movedFromList->clear();

	LastEntry();
	PostMessage(kTriggerWork);

	_SetBusy(false);
}


void
VolumeWorker::_SetBusy(bool busy)
{
	if (busy)
		atomic_set(&fBusy, 1);
	else
		atomic_set(&fBusy, 0);
}


VolumeWatcherBase::VolumeWatcherBase(const BVolume& volume)
	:
	fVolume(volume),
	fEnabled(true),
	fLastUpdated(0)
{
	ReadSettings();
}


const char* kEnabledAttr = "Enabled";


bool
VolumeWatcherBase::ReadSettings()
{
	// TODO remove this
	BVolume bootVolume;
	BVolumeRoster roster;
	roster.GetBootVolume(&bootVolume);
	if (bootVolume == fVolume) {
		fEnabled = true;
		WriteSettings();
	}

	BDirectory rootDir;
	fVolume.GetRootDirectory(&rootDir);
	BPath path(&rootDir);
	path.Append(kIndexServerDirectory);
	path.Append(kVolumeStatusFileName);
	BFile file(path.Path(), B_READ_ONLY);
	if (file.InitCheck() != B_OK)
		return false;

	uint32 enabled;
	file.WriteAttr(kEnabledAttr, B_UINT32_TYPE, 0, &enabled, sizeof(uint32));
	fEnabled = enabled == 0 ? false : true;

	return true;
}


bool
VolumeWatcherBase::WriteSettings()
{
	BDirectory rootDir;
	fVolume.GetRootDirectory(&rootDir);
	BPath path(&rootDir);
	path.Append(kIndexServerDirectory);
	if (create_directory(path.Path(), 777) != B_OK)
		return false;

	path.Append(kVolumeStatusFileName);
	BFile file(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
	if (file.InitCheck() != B_OK)
		return false;

	uint32 enabled = fEnabled ? 1 : 0;
	file.WriteAttr(kEnabledAttr, B_UINT32_TYPE, 0, &enabled, sizeof(uint32));

	return true;
}


SwapEntryRefVector::SwapEntryRefVector()
{
	fCurrentList = &fFirstList;
	fNextList = &fSecondList;
}


EntryRefVector*
SwapEntryRefVector::SwapList()
{
	EntryRefVector* temp = fCurrentList;
	fCurrentList = fNextList;
	fNextList = temp;
	return temp;
}


EntryRefVector*
SwapEntryRefVector::CurrentList()
{
	return fCurrentList;
}


VolumeWatcher::VolumeWatcher(const BVolume& volume)
	:
	VolumeWatcherBase(volume),
	BLooper("VolumeWatcher"),

	fWatching(false),
	fWatchNameHandler(this),
	fCatchUpManager(volume)
{
	AddHandler(&fWatchNameHandler);

	fVolumeWorker = new VolumeWorker(this);
	fVolumeWorker->Run();
}


VolumeWatcher::~VolumeWatcher()
{
	Stop();
	thread_id threadId = fVolumeWorker->Thread();
	fVolumeWorker->PostMessage(B_QUIT_REQUESTED);
	status_t error;
	wait_for_thread(threadId, &error);
}


bool
VolumeWatcher::StartWatching()
{
	Run();

	watch_volume(fVolume.Device(), B_WATCH_NAME | B_WATCH_STAT,
		&fWatchNameHandler);

	// set the time after start watching to not miss anything
	fVolumeWorker->SetWatchingStart(real_time_clock_usecs());

	char name[255];
	fVolume.GetName(name);

	fCatchUpManager.CatchUp();

	fWatching = true;
	return true;
}


void
VolumeWatcher::Stop()
{

	char name[255];
	fVolume.GetName(name);

	// set the time before stop watching to not miss anything
	fVolumeWorker->SetWatchingPosition(real_time_clock_usecs());

	stop_watching(&fWatchNameHandler);

	fVolumeWorker->WriteAnalyserSettings();

	// don't stop the work because we have to handle all entries after writing
	// the watching position
	//fVolumeWorker->Stop();
	fCatchUpManager.Stop();
}


bool
VolumeWatcher::AddAnalyser(FileAnalyser* analyser)
{
	if (!fVolumeWorker->AddAnalyser(analyser))
		return false;

	BAutolock _(this);
	if (!fCatchUpManager.AddAnalyser(analyser))
		return false;

	if (fWatching)
		fCatchUpManager.CatchUp();

	return true;
}


bool
VolumeWatcher::RemoveAnalyser(const BString& name)
{
	if (!fVolumeWorker->RemoveAnalyser(name))
		return false;

	BAutolock _(this);
	fCatchUpManager.RemoveAnalyser(name);
	return true;
}


void
VolumeWatcher::GetSecureEntries(list_collection& collection)
{
	BAutolock _(this);
	collection.createdList = fCreatedList.SwapList();
	collection.deletedList = fDeleteList.SwapList();
	collection.modifiedList = fModifiedList.SwapList();
	collection.movedList = fMovedList.SwapList();
	collection.movedFromList = fMovedFromList.SwapList();
}


bool
VolumeWatcher::FindEntryRef(ino_t node, dev_t device, entry_ref& entry)
{
	return false;
}


void
VolumeWatcher::_NewEntriesArrived()
{
	// The fVolumeWorker has to exist as long as we live so directly post to
	// the queue.
	if (fVolumeWorker->IsBusy())
		return;
	fVolumeWorker->PostMessage(kTriggerWork);
}