root/src/kits/support/ArchivingManagers.cpp
/*
 * Copyright 2010 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Alex Wilson, yourpalal2@gmail.com
 */


#include "ArchivingManagers.h"

#include <syslog.h>
#include <typeinfo>

#include <StackOrHeapArray.h>


namespace BPrivate {
namespace Archiving {
        const char* kArchivableField = "_managed_archivable";
        const char* kManagedField = "_managed_archive";
}
}


using namespace BPrivate::Archiving;


BArchiveManager*
BManagerBase::ArchiveManager(const BMessage* archive)
{
        BManagerBase* manager = ManagerPointer(archive);
        if (!manager)
                return NULL;

        if (manager->fType == ARCHIVE_MANAGER)
                return static_cast<BArchiveManager*>(manager);

        debugger("Overlapping managed unarchive/archive sessions.");
        return NULL;
}


BUnarchiveManager*
BManagerBase::UnarchiveManager(const BMessage* archive)
{
        BManagerBase* manager = ManagerPointer(archive);
        if (!manager)
                return NULL;

        if (manager->fType == UNARCHIVE_MANAGER)
                return static_cast<BUnarchiveManager*>(manager);

        debugger("More calls to BUnarchiver::PrepareArchive()"
                " than BUnarchivers created.");

        return NULL;
}


// #pragma mark -


struct BArchiveManager::ArchiveInfo {
        ArchiveInfo()
                :
                token(-1),
                archive(NULL)
        {
        }


        ~ArchiveInfo()
        {
                delete archive;
        }


        int32           token;
        BMessage*       archive;
};


BArchiveManager::BArchiveManager(const BArchiver* creator)
        :
        BManagerBase(creator->ArchiveMessage(), BManagerBase::ARCHIVE_MANAGER),
        fTokenMap(),
        fCreator(creator),
        fError(B_OK)
{
}


BArchiveManager::~BArchiveManager()
{
        fTopLevelArchive->AddBool(kManagedField, true);
}


status_t
BArchiveManager::GetTokenForArchivable(BArchivable* archivable, int32& _token)
{
        if (!archivable) {
                _token = NULL_TOKEN;
                return B_OK;
        }

        TokenMap::iterator it = fTokenMap.find(archivable);

        if (it == fTokenMap.end())
                return B_ENTRY_NOT_FOUND;

        _token = it->second.token;
        return B_OK;
}


status_t
BArchiveManager::ArchiveObject(BArchivable* archivable,
        bool deep, int32& _token)
{
        if (!archivable) {
                _token = NULL_TOKEN;
                return B_OK;
        }

        ArchiveInfo& info = fTokenMap[archivable];

        status_t err = B_OK;

        if (!info.archive) {
                info.archive = new BMessage();
                info.token = fTokenMap.size() - 1;

                MarkArchive(info.archive);
                err = archivable->Archive(info.archive, deep);
        }

        if (err != B_OK) {
                fTokenMap.erase(archivable);
                        // info.archive gets deleted here
                _token = NULL_TOKEN;
        } else
                _token = info.token;

        return err;
}


bool
BArchiveManager::IsArchived(BArchivable* archivable)
{
        if (!archivable)
                return true;

        return fTokenMap.find(archivable) != fTokenMap.end();
}


status_t
BArchiveManager::ArchiverLeaving(const BArchiver* archiver, status_t err)
{
        if (fError == B_OK)
                fError = err;

        if (archiver == fCreator && fError == B_OK) {
                // first, we must sort the objects into the order they were archived in
                typedef std::pair<BMessage*, const BArchivable*> ArchivePair;
                BStackOrHeapArray<ArchivePair, 64> pairs(fTokenMap.size());

                for(TokenMap::iterator it = fTokenMap.begin(), end = fTokenMap.end();
                                it != end; it++) {
                        ArchiveInfo& info = it->second;
                        pairs[info.token].first = info.archive;
                        pairs[info.token].second = it->first;

                        // make sure fTopLevelArchive isn't deleted
                        if (info.archive == fTopLevelArchive)
                                info.archive = NULL;
                }

                int32 count = fTokenMap.size();
                for (int32 i = 0; i < count; i++) {
                        const ArchivePair& pair = pairs[i];
                        fError = pair.second->AllArchived(pair.first);

                        if (fError == B_OK && i > 0) {
                                fError = fTopLevelArchive->AddMessage(kArchivableField,
                                        pair.first);
                        }

                        if (fError != B_OK) {
                                syslog(LOG_ERR, "AllArchived failed for object of type %s.",
                                        typeid(*pairs[i].second).name());
                                break;
                        }
                }
        }

        status_t result = fError;
        if (archiver == fCreator) 
                delete this;

        return result;
}


void
BArchiveManager::RegisterArchivable(const BArchivable* archivable)
{
        if (fTokenMap.size() == 0) {
                ArchiveInfo& info = fTokenMap[archivable];
                info.archive = fTopLevelArchive;
                info.token = 0;
        }
}


// #pragma mark -


struct BUnarchiveManager::ArchiveInfo {
        ArchiveInfo()
                :
                archivable(NULL),
                archive(),
                adopted(false)
        {
        }

        bool
        operator<(const ArchiveInfo& other)
        {
                return archivable < other.archivable;
        }

        BArchivable*    archivable;
        BMessage                archive;
        bool                    adopted;
};


// #pragma mark -


BUnarchiveManager::BUnarchiveManager(BMessage* archive)
        :
        BManagerBase(archive, BManagerBase::UNARCHIVE_MANAGER),
        fObjects(NULL),
        fObjectCount(0),
        fTokenInProgress(0),
        fRefCount(0),
        fError(B_OK)
{
        archive->GetInfo(kArchivableField, NULL, &fObjectCount);
        fObjectCount++;
                // root object needs a spot too
        fObjects = new ArchiveInfo[fObjectCount];

        // fObjects[0] is a placeholder for the object that started
        // this unarchiving session.
        for (int32 i = 0; i < fObjectCount - 1; i++) {
                BMessage* into = &fObjects[i + 1].archive;
                status_t err = archive->FindMessage(kArchivableField, i, into);
                MarkArchive(into);

                if (err != B_OK)
                        syslog(LOG_ERR, "Failed to find managed archivable");
        }
}


BUnarchiveManager::~BUnarchiveManager()
{
        delete[] fObjects;
}


status_t
BUnarchiveManager::GetArchivableForToken(int32 token,
        BUnarchiver::ownership_policy owning, BArchivable*& _archivable)
{
        if (token >= fObjectCount)
                return B_BAD_VALUE;

        if (token < 0) {
                _archivable = NULL;
                return B_OK;
        }

        status_t err = B_OK;
        ArchiveInfo& info = fObjects[token];
        if (!info.archivable) {
                if (fRefCount > 0) {
                        fTokenInProgress = token;
                        if(!instantiate_object(&info.archive))
                                err = B_ERROR;
                } else {
                        syslog(LOG_ERR, "Object requested from AllUnarchived()"
                                " was not previously instantiated");
                        err = B_ERROR;
                }
        }

        if (owning == BUnarchiver::B_ASSUME_OWNERSHIP)
                info.adopted = true;

        _archivable = info.archivable;
        return err;
}


bool
BUnarchiveManager::IsInstantiated(int32 token)
{
        if (token < 0 || token >= fObjectCount)
                return false;
        return fObjects[token].archivable;
}


void
BUnarchiveManager::RegisterArchivable(BArchivable* archivable)
{
        if (!archivable)
                debugger("Cannot register NULL pointer");

        fObjects[fTokenInProgress].archivable = archivable;
        archivable->fArchivingToken = fTokenInProgress;
}


status_t
BUnarchiveManager::UnarchiverLeaving(const BUnarchiver* unarchiver,
        status_t err)
{
        if (--fRefCount >= 0 && fError == B_OK)
                fError = err;

        if (fRefCount != 0)
                return fError;

        if (fError == B_OK) {
                BArchivable* archivable = fObjects[0].archivable;
                if (archivable) {
                        fError = archivable->AllUnarchived(fTopLevelArchive);
                        archivable->fArchivingToken = NULL_TOKEN;
                }

                for (int32 i = 1; i < fObjectCount && fError == B_OK; i++) {
                        archivable = fObjects[i].archivable;
                        if (archivable) {
                                fError = archivable->AllUnarchived(&fObjects[i].archive);
                                archivable->fArchivingToken = NULL_TOKEN;
                        }
                }
                if (fError != B_OK) {
                        syslog(LOG_ERR, "Error in AllUnarchived"
                                " method of object of type %s", typeid(*archivable).name());
                }
        }

        if (fError != B_OK) {
                syslog(LOG_ERR, "An error occured during unarchival, cleaning up.");
                for (int32 i = 1; i < fObjectCount; i++) {
                        if (!fObjects[i].adopted)
                                delete fObjects[i].archivable;
                }
        }

        status_t result = fError;
        delete this;
        return result;
}


void
BUnarchiveManager::RelinquishOwnership(BArchivable* archivable)
{
        int32 token = NULL_TOKEN;
        if (archivable)
                token = archivable->fArchivingToken;

        if (token < 0 || token >= fObjectCount
                || fObjects[token].archivable != archivable)
                return;

        fObjects[token].adopted = false;
}


void
BUnarchiveManager::AssumeOwnership(BArchivable* archivable)
{
        int32 token = NULL_TOKEN;
        if (archivable)
                token = archivable->fArchivingToken;

        if (token < 0 || token >= fObjectCount
                || fObjects[token].archivable != archivable)
                return;

        fObjects[token].adopted = true;
}


void
BUnarchiveManager::Acquire()
{
        if (fRefCount >= 0)
                fRefCount++;
}