root/src/add-ons/kernel/file_systems/packagefs/package_links/PackageLinkDirectory.cpp
/*
 * Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "PackageLinkDirectory.h"

#include <new>

#include <NodeMonitor.h>

#include <AutoDeleter.h>

#include "AutoPackageAttributeDirectoryCookie.h"
#include "DebugSupport.h"
#include "PackageLinksListener.h"
#include "StringConstants.h"
#include "Utils.h"
#include "Version.h"
#include "Volume.h"


PackageLinkDirectory::PackageLinkDirectory()
        :
        Directory(0),
                // the ID needs to be assigned later, when added to a volume
        fSelfLink(NULL),
        fSettingsLink(NULL)
{
        get_real_time(fModifiedTime);
}


PackageLinkDirectory::~PackageLinkDirectory()
{
        if (fSelfLink != NULL)
                fSelfLink->ReleaseReference();
        if (fSettingsLink != NULL)
                fSettingsLink->ReleaseReference();

        while (DependencyLink* link = fDependencyLinks.RemoveHead())
                link->ReleaseReference();
}


status_t
PackageLinkDirectory::Init(Package* package)
{
        // init the directory/node
        status_t error = Init(package->VersionedName());
        if (error != B_OK)
                RETURN_ERROR(error);

        // add the package
        AddPackage(package, NULL);

        return B_OK;
}


status_t
PackageLinkDirectory::Init(const String& name)
{
        return Directory::Init(name);
}


timespec
PackageLinkDirectory::ModifiedTime() const
{
        return fModifiedTime;
}


status_t
PackageLinkDirectory::OpenAttributeDirectory(AttributeDirectoryCookie*& _cookie)
{
        Package* package = fPackages.Head();
        if (package == NULL)
                return B_ENTRY_NOT_FOUND;

        AutoPackageAttributeDirectoryCookie* cookie = new(std::nothrow)
                AutoPackageAttributeDirectoryCookie();
        if (cookie == NULL)
                return B_NO_MEMORY;

        _cookie = cookie;
        return B_OK;
}


status_t
PackageLinkDirectory::OpenAttribute(const StringKey& name, int openMode,
        AttributeCookie*& _cookie)
{
        Package* package = fPackages.Head();
        if (package == NULL)
                return B_ENTRY_NOT_FOUND;

        return AutoPackageAttributes::OpenCookie(package, name, openMode, _cookie);
}


void
PackageLinkDirectory::AddPackage(Package* package,
        PackageLinksListener* listener)
{
        DirectoryWriteLocker writeLocker(this);

        // Find the insertion point in the list. We sort by mount type -- the more
        // specific the higher the priority.
        MountType mountType = package->Volume()->MountType();
        Package* otherPackage = NULL;
        for (PackageList::Iterator it = fPackages.GetIterator();
                        (otherPackage = it.Next()) != NULL;) {
                if (otherPackage->Volume()->MountType() <= mountType)
                        break;
        }

        fPackages.InsertBefore(otherPackage, package);
        package->SetLinkDirectory(this);

        if (package == fPackages.Head())
                _Update(listener);
}


void
PackageLinkDirectory::RemovePackage(Package* package,
        PackageLinksListener* listener)
{
        ASSERT(package->LinkDirectory() == this);

        DirectoryWriteLocker writeLocker(this);

        bool firstPackage = package == fPackages.Head();

        package->SetLinkDirectory(NULL);
        fPackages.Remove(package);

        if (firstPackage)
                _Update(listener);
}


void
PackageLinkDirectory::UpdatePackageDependencies(Package* package,
        PackageLinksListener* listener)
{
        ASSERT(package->LinkDirectory() == this);

        DirectoryWriteLocker writeLocker(this);

        // We only need to update, if that head package is affected.
        if (package != fPackages.Head())
                return;

        _UpdateDependencies(listener);
}


status_t
PackageLinkDirectory::_Update(PackageLinksListener* listener)
{
        // Always remove all dependency links -- if there's still a package, they
        // will be re-created below.
        while (DependencyLink* link = fDependencyLinks.RemoveHead())
                _RemoveLink(link, listener);

        // check, if empty
        Package* package = fPackages.Head();
        if (package == NULL) {
                // remove self and settings link
                _RemoveLink(fSelfLink, listener);
                fSelfLink = NULL;

                _RemoveLink(fSettingsLink, listener);
                fSettingsLink = NULL;

                return B_OK;
        }

        // create/update self and settings link
        status_t error = _CreateOrUpdateLink(fSelfLink, package,
                Link::TYPE_INSTALLATION_LOCATION, StringConstants::Get().kSelfLinkName,
                listener);
        if (error != B_OK)
                RETURN_ERROR(error);

        error = _CreateOrUpdateLink(fSettingsLink, package, Link::TYPE_SETTINGS,
                StringConstants::Get().kSettingsLinkName, listener);
        if (error != B_OK)
                RETURN_ERROR(error);

        // update the dependency links
        return _UpdateDependencies(listener);
}


status_t
PackageLinkDirectory::_UpdateDependencies(PackageLinksListener* listener)
{
        ASSERT_WRITE_LOCKED_RW_LOCK(&fLock);

        Package* package = fPackages.Head();
        if (package == NULL)
                return B_OK;

        // Iterate through the package's dependencies
        for (DependencyList::ConstIterator it
                                = package->Dependencies().GetIterator();
                        Dependency* dependency = it.Next();) {
                Resolvable* resolvable = dependency->Resolvable();
                Package* resolvablePackage = resolvable != NULL
                        ? resolvable->Package() : NULL;

                Node* node = FindChild(dependency->FileName());
                if (node != NULL) {
                        // link already exists -- update
                        DependencyLink* link = static_cast<DependencyLink*>(node);
                        link->Update(resolvablePackage, listener);
                } else {
                        // no link for the dependency yet -- create one
                        DependencyLink* link = new(std::nothrow) DependencyLink(
                                resolvablePackage);
                        if (link == NULL)
                                return B_NO_MEMORY;

                        status_t error = link->Init(dependency->FileName());
                        if (error != B_OK) {
                                delete link;
                                RETURN_ERROR(error);
                        }

                        AddChild(link);
                        fDependencyLinks.Add(link);

                        if (listener != NULL)
                                listener->PackageLinkNodeAdded(link);
                }
        }

        return B_OK;
}


void
PackageLinkDirectory::_RemoveLink(Link* link, PackageLinksListener* listener)
{
        ASSERT_WRITE_LOCKED_RW_LOCK(&fLock);

        if (link != NULL) {
                if (listener != NULL)
                        listener->PackageLinkNodeRemoved(link);

                RemoveChild(link);
                link->ReleaseReference();
        }
}


status_t
PackageLinkDirectory::_CreateOrUpdateLink(Link*& link, Package* package,
        Link::Type type, const String& name, PackageLinksListener* listener)
{
        ASSERT_WRITE_LOCKED_RW_LOCK(&fLock);

        if (link == NULL) {
                link = new(std::nothrow) Link(package, type);
                if (link == NULL)
                        return B_NO_MEMORY;

                status_t error = link->Init(name);
                if (error != B_OK)
                        RETURN_ERROR(error);

                AddChild(link);

                if (listener != NULL)
                        listener->PackageLinkNodeAdded(link);
        } else {
                link->Update(package, listener);
        }

        return B_OK;
}