root/src/servers/index/VolumeWatcher.cpp
/*
 * 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);
}