root/src/servers/launch/Events.cpp
/*
 * Copyright 2015-2018, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "Events.h"

#include <stdio.h>

#include <Entry.h>
#include <LaunchRoster.h>
#include <Message.h>
#include <ObjectList.h>
#include <Path.h>
#include <StringList.h>

#include "BaseJob.h"
#include "FileWatcher.h"
#include "LaunchDaemon.h"
#include "NetworkWatcher.h"
#include "Utility.h"
#include "VolumeWatcher.h"


class EventContainer : public Event {
protected:
                                                                EventContainer(Event* parent,
                                                                        const BMessenger* target,
                                                                        const BMessage& args);
                                                                EventContainer(BaseJob* owner,
                                                                        const BMessenger& target);

public:
                        void                            AddEvent(Event* event);
                        BObjectList<Event, true>& Events();

                        const BMessenger&       Target() const;

        virtual status_t                        Register(EventRegistrator& registrator);
        virtual void                            Unregister(EventRegistrator& registrator);

        virtual void                            Trigger(Event* origin);

        virtual BaseJob*                        Owner() const;
        virtual void                            SetOwner(BaseJob* owner);

protected:
                        void                            AddEventsToString(BString& string) const;

protected:
                        BaseJob*                        fOwner;
                        BMessenger                      fTarget;
                        BObjectList<Event, true> fEvents;
                        bool                            fRegistered;
};


class OrEvent : public EventContainer {
public:
                                                                OrEvent(Event* parent, const BMessenger* target,
                                                                        const BMessage& args);
                                                                OrEvent(BaseJob* owner,
                                                                        const BMessenger& target);

        virtual void                            ResetTrigger();

        virtual BString                         ToString() const;
};


class StickyEvent : public Event {
public:
                                                                StickyEvent(Event* parent);
        virtual                                         ~StickyEvent();

        virtual void                            ResetSticky();
        virtual void                            ResetTrigger();
};


class DemandEvent : public Event {
public:
                                                                DemandEvent(Event* parent);

        virtual status_t                        Register(EventRegistrator& registrator);
        virtual void                            Unregister(EventRegistrator& registrator);

        virtual BString                         ToString() const;
};


class ExternalEvent : public Event {
public:
                                                                ExternalEvent(Event* parent, const char* name,
                                                                        const BMessage& args);

                        const BString&          Name() const;
                        bool                            Resolve(uint32 flags);

                        void                            ResetSticky();
        virtual void                            ResetTrigger();

        virtual status_t                        Register(EventRegistrator& registrator);
        virtual void                            Unregister(EventRegistrator& registrator);

        virtual BString                         ToString() const;

private:
                        BString                         fName;
                        BStringList                     fArguments;
                        uint32                          fFlags;
                        bool                            fResolved;
};


class FileCreatedEvent : public Event, FileListener {
public:
                                                                FileCreatedEvent(Event* parent,
                                                                        const BMessage& args);

        virtual status_t                        Register(EventRegistrator& registrator);
        virtual void                            Unregister(EventRegistrator& registrator);

        virtual BString                         ToString() const;

        virtual void                            FileCreated(const char* path);

private:
                        BPath                           fPath;
};


class VolumeMountedEvent : public Event, public VolumeListener {
public:
                                                                VolumeMountedEvent(Event* parent,
                                                                        const BMessage& args);

        virtual status_t                        Register(EventRegistrator& registrator);
        virtual void                            Unregister(EventRegistrator& registrator);

        virtual BString                         ToString() const;

        virtual void                            VolumeMounted(dev_t device);
        virtual void                            VolumeUnmounted(dev_t device);
};


class NetworkAvailableEvent : public StickyEvent, public NetworkListener {
public:
                                                                NetworkAvailableEvent(Event* parent,
                                                                        const BMessage& args);

        virtual status_t                        Register(EventRegistrator& registrator);
        virtual void                            Unregister(EventRegistrator& registrator);

        virtual BString                         ToString() const;

        virtual void                            NetworkAvailabilityChanged(bool available);
};


static Event*
create_event(Event* parent, const char* name, const BMessenger* target,
        const BMessage& args)
{
        if (strcmp(name, "or") == 0) {
                if (args.IsEmpty())
                        return NULL;

                return new OrEvent(parent, target, args);
        }

        if (strcmp(name, "demand") == 0)
                return new DemandEvent(parent);
        if (strcmp(name, "file_created") == 0)
                return new FileCreatedEvent(parent, args);
        if (strcmp(name, "volume_mounted") == 0)
                return new VolumeMountedEvent(parent, args);
        if (strcmp(name, "network_available") == 0)
                return new NetworkAvailableEvent(parent, args);

        return new ExternalEvent(parent, name, args);
}


// #pragma mark -


Event::Event(Event* parent)
        :
        fParent(parent),
        fTriggered(false)
{
}


Event::~Event()
{
}


bool
Event::Triggered() const
{
        return fTriggered;
}


void
Event::Trigger(Event* origin)
{
        fTriggered = true;
        if (fParent != NULL)
                fParent->Trigger(origin);
}


void
Event::ResetTrigger()
{
        fTriggered = false;
}


BaseJob*
Event::Owner() const
{
        if (fParent != NULL)
                return fParent->Owner();

        return NULL;
}


void
Event::SetOwner(BaseJob* owner)
{
        if (fParent != NULL)
                fParent->SetOwner(owner);
}


Event*
Event::Parent() const
{
        return fParent;
}


// #pragma mark -


EventContainer::EventContainer(Event* parent, const BMessenger* target,
        const BMessage& args)
        :
        Event(parent),
        fEvents(5),
        fRegistered(false)
{
        if (target != NULL)
                fTarget = *target;

        char* name;
        type_code type;
        int32 count;
        for (int32 index = 0; args.GetInfo(B_MESSAGE_TYPE, index, &name, &type,
                        &count) == B_OK; index++) {
                BMessage message;
                for (int32 messageIndex = 0; args.FindMessage(name, messageIndex,
                                &message) == B_OK; messageIndex++) {
                        AddEvent(create_event(this, name, target, message));
                }
        }
}


EventContainer::EventContainer(BaseJob* owner, const BMessenger& target)
        :
        Event(NULL),
        fOwner(owner),
        fTarget(target),
        fEvents(5),
        fRegistered(false)
{
}


void
EventContainer::AddEvent(Event* event)
{
        if (event != NULL)
                fEvents.AddItem(event);
}


BObjectList<Event, true>&
EventContainer::Events()
{
        return fEvents;
}


const BMessenger&
EventContainer::Target() const
{
        return fTarget;
}


status_t
EventContainer::Register(EventRegistrator& registrator)
{
        if (fRegistered)
                return B_OK;

        int32 count = fEvents.CountItems();
        for (int32 index = 0; index < count; index++) {
                Event* event = fEvents.ItemAt(index);
                status_t status = event->Register(registrator);
                if (status != B_OK)
                        return status;
        }

        fRegistered = true;
        return B_OK;
}


void
EventContainer::Unregister(EventRegistrator& registrator)
{
        int32 count = fEvents.CountItems();
        for (int32 index = 0; index < count; index++) {
                Event* event = fEvents.ItemAt(index);
                event->Unregister(registrator);
        }
}


void
EventContainer::Trigger(Event* origin)
{
        Event::Trigger(origin);

        if (Parent() == NULL && Owner() != NULL) {
                BMessage message(kMsgEventTriggered);
                message.AddPointer("event", origin);
                message.AddString("owner", Owner()->Name());
                fTarget.SendMessage(&message);
        }
}


BaseJob*
EventContainer::Owner() const
{
        return fOwner;
}


void
EventContainer::SetOwner(BaseJob* owner)
{
        Event::SetOwner(owner);
        fOwner = owner;
}


void
EventContainer::AddEventsToString(BString& string) const
{
        string += "[";

        for (int32 index = 0; index < fEvents.CountItems(); index++) {
                if (index != 0)
                        string += ", ";
                string += fEvents.ItemAt(index)->ToString();
        }
        string += "]";
}


// #pragma mark - or


OrEvent::OrEvent(Event* parent, const BMessenger* target, const BMessage& args)
        :
        EventContainer(parent, target, args)
{
}


OrEvent::OrEvent(BaseJob* owner, const BMessenger& target)
        :
        EventContainer(owner, target)
{
}


void
OrEvent::ResetTrigger()
{
        fTriggered = false;

        int32 count = fEvents.CountItems();
        for (int32 index = 0; index < count; index++) {
                Event* event = fEvents.ItemAt(index);
                event->ResetTrigger();
                fTriggered |= event->Triggered();
        }
}


BString
OrEvent::ToString() const
{
        BString string = "or ";
        EventContainer::AddEventsToString(string);
        return string;
}


// #pragma mark - StickyEvent


StickyEvent::StickyEvent(Event* parent)
        :
        Event(parent)
{
}


StickyEvent::~StickyEvent()
{
}


void
StickyEvent::ResetSticky()
{
        Event::ResetTrigger();
}


void
StickyEvent::ResetTrigger()
{
        // This is a sticky event; we don't reset the trigger here
}


// #pragma mark - demand


DemandEvent::DemandEvent(Event* parent)
        :
        Event(parent)
{
}


status_t
DemandEvent::Register(EventRegistrator& registrator)
{
        return B_OK;
}


void
DemandEvent::Unregister(EventRegistrator& registrator)
{
}


BString
DemandEvent::ToString() const
{
        return "demand";
}


// #pragma mark - External event


ExternalEvent::ExternalEvent(Event* parent, const char* name,
        const BMessage& args)
        :
        Event(parent),
        fName(name),
        fFlags(0),
        fResolved(false)
{
        const char* argument;
        for (int32 index = 0; args.FindString("args", index, &argument) == B_OK;
                        index++) {
                fArguments.Add(argument);
        }
}


const BString&
ExternalEvent::Name() const
{
        return fName;
}


bool
ExternalEvent::Resolve(uint32 flags)
{
        if (fResolved)
                return false;

        fResolved = true;
        fFlags = flags;
        return true;
}


void
ExternalEvent::ResetSticky()
{
        if ((fFlags & B_STICKY_EVENT) != 0)
                Event::ResetTrigger();
}


void
ExternalEvent::ResetTrigger()
{
        if ((fFlags & B_STICKY_EVENT) == 0)
                Event::ResetTrigger();
}


status_t
ExternalEvent::Register(EventRegistrator& registrator)
{
        return registrator.RegisterExternalEvent(this, Name().String(), fArguments);
}


void
ExternalEvent::Unregister(EventRegistrator& registrator)
{
        registrator.UnregisterExternalEvent(this, Name().String());
}


BString
ExternalEvent::ToString() const
{
        return fName;
}


// #pragma mark - file_created


FileCreatedEvent::FileCreatedEvent(Event* parent, const BMessage& args)
        :
        Event(parent)
{
        fPath.SetTo(args.GetString("args", NULL));
}


status_t
FileCreatedEvent::Register(EventRegistrator& registrator)
{
        return FileWatcher::Register(this, fPath);
}


void
FileCreatedEvent::Unregister(EventRegistrator& registrator)
{
        FileWatcher::Unregister(this, fPath);
}


BString
FileCreatedEvent::ToString() const
{
        BString string = "file_created ";
        string << fPath.Path();
        return string;
}


void
FileCreatedEvent::FileCreated(const char* path)
{
        if (strcmp(fPath.Path(), path) == 0)
                Trigger(this);
}


// #pragma mark -


VolumeMountedEvent::VolumeMountedEvent(Event* parent, const BMessage& args)
        :
        Event(parent)
{
}


status_t
VolumeMountedEvent::Register(EventRegistrator& registrator)
{
        VolumeWatcher::Register(this);
        return B_OK;
}


void
VolumeMountedEvent::Unregister(EventRegistrator& registrator)
{
        VolumeWatcher::Unregister(this);
}


BString
VolumeMountedEvent::ToString() const
{
        return "volume_mounted";
}


void
VolumeMountedEvent::VolumeMounted(dev_t device)
{
        Trigger(this);
}


void
VolumeMountedEvent::VolumeUnmounted(dev_t device)
{
}


// #pragma mark -


NetworkAvailableEvent::NetworkAvailableEvent(Event* parent,
        const BMessage& args)
        :
        StickyEvent(parent)
{
}


status_t
NetworkAvailableEvent::Register(EventRegistrator& registrator)
{
        NetworkWatcher::Register(this);
        return B_OK;
}


void
NetworkAvailableEvent::Unregister(EventRegistrator& registrator)
{
        NetworkWatcher::Unregister(this);
}


BString
NetworkAvailableEvent::ToString() const
{
        return "network_available";
}


void
NetworkAvailableEvent::NetworkAvailabilityChanged(bool available)
{
        if (available)
                Trigger(this);
        else
                ResetSticky();
}


// #pragma mark -


/*static*/ Event*
Events::FromMessage(const BMessenger& target, const BMessage& message)
{
        return create_event(NULL, "or", &target, message);
}


/*static*/ Event*
Events::AddOnDemand(const BMessenger& target, Event* event)
{
        OrEvent* orEvent = dynamic_cast<OrEvent*>(event);
        if (orEvent == NULL) {
                EventContainer* container = dynamic_cast<EventContainer*>(event);
                if (container != NULL)
                        orEvent = new OrEvent(container->Owner(), container->Target());
                else
                        orEvent = new OrEvent(NULL, target);
        }
        if (orEvent != event && event != NULL)
                orEvent->AddEvent(event);

        orEvent->AddEvent(new DemandEvent(orEvent));
        return orEvent;
}


/*static*/ Event*
Events::ResolveExternalEvent(Event* event, const char* name, uint32 flags)
{
        if (event == NULL)
                return NULL;

        if (EventContainer* container = dynamic_cast<EventContainer*>(event)) {
                for (int32 index = 0; index < container->Events().CountItems();
                                index++) {
                        Event* event = ResolveExternalEvent(container->Events().ItemAt(index), name, flags);
                        if (event != NULL)
                                return event;
                }
        } else if (ExternalEvent* external = dynamic_cast<ExternalEvent*>(event)) {
                if (external->Name() == name && external->Resolve(flags))
                        return external;
        }

        return NULL;
}


/*static*/ void
Events::TriggerExternalEvent(Event* event)
{
        if (event == NULL)
                return;

        ExternalEvent* external = dynamic_cast<ExternalEvent*>(event);
        if (external == NULL)
                return;

        external->Trigger(external);
}


/*static*/ void
Events::ResetStickyExternalEvent(Event* event)
{
        if (event == NULL)
                return;

        ExternalEvent* external = dynamic_cast<ExternalEvent*>(event);
        if (external == NULL)
                return;

        external->ResetSticky();
}


/*!     This will trigger a demand event, if it exists.

        \param testOnly If \c true, the deman will not actually be triggered,
                        it will only be checked if it could.
        \return \c true, if there is a demand event, and it has been
                        triggered by this call. \c false if not.
*/
/*static*/ bool
Events::TriggerDemand(Event* event, bool testOnly)
{
        if (event == NULL || event->Triggered())
                return false;

        if (EventContainer* container = dynamic_cast<EventContainer*>(event)) {
                for (int32 index = 0; index < container->Events().CountItems();
                                index++) {
                        Event* childEvent = container->Events().ItemAt(index);
                        if (dynamic_cast<DemandEvent*>(childEvent) != NULL) {
                                if (testOnly)
                                        return true;

                                childEvent->Trigger(childEvent);
                                break;
                        }
                        if (dynamic_cast<EventContainer*>(childEvent) != NULL) {
                                if (TriggerDemand(childEvent, testOnly))
                                        break;
                        }
                }
        }

        return event->Triggered();
}