root/src/kits/storage/Resources.cpp
/*
 * Copyright 2001-2006, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
 * Copyright 2013 Haiku, Inc.
 * All Rights Reserved. Distributed under the terms of the MIT License.
 *
 * Authors:
 *              John Scipione, jscipione@gmail.com
 *              Ingo Weinhold, bonefish@cs.tu-berlin.de
 */


#include <Resources.h>

#include <new>
#include <stdio.h>
#include <stdlib.h>

#include "ResourceFile.h"
#include "ResourceItem.h"
#include "ResourcesContainer.h"


using namespace BPrivate::Storage;
using namespace std;


// debugging
//#define DBG(x) x
#define DBG(x)
#define OUT     printf


// Creates an unitialized BResources object.
BResources::BResources()
        :
        fFile(),
        fContainer(NULL),
        fResourceFile(NULL),
        fReadOnly(false)
{
        fContainer = new(nothrow) ResourcesContainer;
}


// Creates a BResources object that represents the resources of the
// supplied file.
BResources::BResources(const BFile* file, bool clobber)
        :
        fFile(),
        fContainer(NULL),
        fResourceFile(NULL),
        fReadOnly(false)
{
        fContainer = new(nothrow) ResourcesContainer;
        SetTo(file, clobber);
}


// Creates a BResources object that represents the resources of the
// file referenced by the supplied path.
BResources::BResources(const char* path, bool clobber)
        :
        fFile(),
        fContainer(NULL),
        fResourceFile(NULL),
        fReadOnly(false)
{
        fContainer = new(nothrow) ResourcesContainer;
        SetTo(path, clobber);
}


// Creates a BResources object that represents the resources of the
// file referenced by the supplied ref.
BResources::BResources(const entry_ref* ref, bool clobber)
        :
        fFile(),
        fContainer(NULL),
        fResourceFile(NULL),
        fReadOnly(false)
{
        fContainer = new(nothrow) ResourcesContainer;
        SetTo(ref, clobber);
}


// Frees all resources associated with this object
BResources::~BResources()
{
        Unset();
        delete fContainer;
}


// Initialized the BResources object to represent the resources of
// the supplied file.
status_t
BResources::SetTo(const BFile* file, bool clobber)
{
        Unset();
        status_t error = B_OK;
        if (file) {
                error = file->InitCheck();
                if (error == B_OK) {
                        fFile = *file;
                        error = fFile.InitCheck();
                }
                if (error == B_OK) {
                        fReadOnly = !fFile.IsWritable();
                        fResourceFile = new(nothrow) ResourceFile;
                        if (fResourceFile)
                                error = fResourceFile->SetTo(&fFile, clobber);
                        else
                                error = B_NO_MEMORY;
                }
                if (error == B_OK) {
                        if (fContainer)
                                error = fResourceFile->InitContainer(*fContainer);
                        else
                                error = B_NO_MEMORY;
                }
        }
        if (error != B_OK) {
                delete fResourceFile;
                fResourceFile = NULL;
                if (fContainer)
                        fContainer->MakeEmpty();
        }
        return error;
}


// Initialized the BResources object to represent the resources of
// the file referred to by the supplied path.
status_t
BResources::SetTo(const char* path, bool clobber)
{
        if (!path)
                return B_BAD_VALUE;

        // open file
        BFile file;
        status_t error = file.SetTo(path, B_READ_WRITE);
        if (error != B_OK && error != B_ENTRY_NOT_FOUND)
                error = file.SetTo(path, B_READ_ONLY);
        if (error != B_OK) {
                Unset();
                return error;
        }

        // delegate the actual work
        return SetTo(&file, clobber);
}


// Initialized the BResources object to represent the resources of the
// file referenced by the supplied ref.
status_t
BResources::SetTo(const entry_ref* ref, bool clobber)
{
        if (!ref)
                return B_BAD_VALUE;

        // open file
        BFile file;
        status_t error = file.SetTo(ref, B_READ_WRITE);
        if (error != B_OK && error != B_ENTRY_NOT_FOUND)
                error = file.SetTo(ref, B_READ_ONLY);
        if (error != B_OK) {
                Unset();
                return error;
        }

        // delegate the actual work
        return SetTo(&file, clobber);
}


// Initialized the BResources object to represent the resources of
// the file from which the specified image has been loaded.
status_t
BResources::SetToImage(image_id image, bool clobber)
{
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
        // get an image info
        image_info info;
        status_t error = get_image_info(image, &info);
        if (error != B_OK) {
                Unset();
                return error;
        }

        // delegate the actual work
        return SetTo(info.name, clobber);
#else   // HAIKU_TARGET_PLATFORM_HAIKU
        return B_NOT_SUPPORTED;
#endif
}


// Initialized the BResources object to represent the resources of
// the file from which the specified pointer has been loaded.
status_t
BResources::SetToImage(const void* codeOrDataPointer, bool clobber)
{
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
        // iterate through the images and find the one in question
        addr_t address = (addr_t)codeOrDataPointer;
        image_info info;
        int32 cookie = 0;

        while (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) {
                if (address == 0
                        ? info.type == B_APP_IMAGE
                        : (((addr_t)info.text <= address
                                        && address - (addr_t)info.text < (addr_t)info.text_size)
                                || ((addr_t)info.data <= address
                                        && address - (addr_t)info.data < (addr_t)info.data_size))) {
                        return SetTo(info.name, clobber);
                }
        }

        return B_ENTRY_NOT_FOUND;
#else   // HAIKU_TARGET_PLATFORM_HAIKU
        return B_NOT_SUPPORTED;
#endif
}


// Returns the BResources object to an uninitialized state.
void
BResources::Unset()
{
        if (fContainer && fContainer->IsModified())
                Sync();
        delete fResourceFile;
        fResourceFile = NULL;
        fFile.Unset();
        if (fContainer)
                fContainer->MakeEmpty();
        else
                fContainer = new(nothrow) ResourcesContainer;
        fReadOnly = false;
}


// Gets the initialization status of the object.
status_t
BResources::InitCheck() const
{
        return (fContainer ? B_OK : B_NO_MEMORY);
}


// Gets a reference to the internal BFile object.
const BFile&
BResources::File() const
{
        return fFile;
}


// Loads a resource identified by type and id into memory.
const void*
BResources::LoadResource(type_code type, int32 id, size_t* _size)
{
        // find the resource
        status_t error = InitCheck();
        ResourceItem* resource = NULL;
        if (error == B_OK) {
                resource = fContainer->ResourceAt(fContainer->IndexOf(type, id));
                if (!resource)
                        error = B_ENTRY_NOT_FOUND;
        }
        // load it, if necessary
        if (error == B_OK && !resource->IsLoaded() && fResourceFile)
                error = fResourceFile->ReadResource(*resource);
        // return the result
        const void *result = NULL;
        if (error == B_OK) {
                result = resource->Data();
                if (_size)
                        *_size = resource->DataSize();
        }
        return result;
}


// Loads a resource identified by type and name into memory.
const void*
BResources::LoadResource(type_code type, const char* name, size_t* _size)
{
        // find the resource
        status_t error = InitCheck();
        ResourceItem* resource = NULL;
        if (error == B_OK) {
                resource = fContainer->ResourceAt(fContainer->IndexOf(type, name));
                if (!resource)
                        error = B_ENTRY_NOT_FOUND;
        }
        // load it, if necessary
        if (error == B_OK && !resource->IsLoaded() && fResourceFile)
                error = fResourceFile->ReadResource(*resource);
        // return the result
        const void* result = NULL;
        if (error == B_OK) {
                result = resource->Data();
                if (_size)
                        *_size = resource->DataSize();
        }
        return result;
}


// Loads all resources of the specified type into memory.
status_t
BResources::PreloadResourceType(type_code type)
{
        status_t error = InitCheck();
        if (error == B_OK && fResourceFile) {
                if (type == 0)
                        error = fResourceFile->ReadResources(*fContainer);
                else {
                        int32 count = fContainer->CountResources();
                        int32 errorCount = 0;
                        for (int32 i = 0; i < count; i++) {
                                ResourceItem *resource = fContainer->ResourceAt(i);
                                if (resource->Type() == type) {
                                        if (fResourceFile->ReadResource(*resource) != B_OK)
                                                errorCount++;
                                }
                        }
                        error = -errorCount;
                }
        }
        return error;
}


// Writes all changes to the resources to the file.
status_t
BResources::Sync()
{
        status_t error = InitCheck();
        if (error == B_OK)
                error = fFile.InitCheck();
        if (error == B_OK) {
                if (fReadOnly)
                        error = B_NOT_ALLOWED;
                else if (!fResourceFile)
                        error = B_FILE_ERROR;
        }
        if (error == B_OK)
                error = fResourceFile->ReadResources(*fContainer);
        if (error == B_OK)
                error = fResourceFile->WriteResources(*fContainer);
        return error;
}


// Adds the resources of fromFile to the internal file of the
// BResources object.
status_t
BResources::MergeFrom(BFile* fromFile)
{
        status_t error = (fromFile ? B_OK : B_BAD_VALUE);
        if (error == B_OK)
                error = InitCheck();
        if (error == B_OK) {
                ResourceFile resourceFile;
                error = resourceFile.SetTo(fromFile);
                ResourcesContainer container;
                if (error == B_OK)
                        error = resourceFile.InitContainer(container);
                if (error == B_OK)
                        error = resourceFile.ReadResources(container);
                if (error == B_OK)
                        fContainer->AssimilateResources(container);
        }
        return error;
}


// Writes the resources to a new file.
status_t
BResources::WriteTo(BFile* file)
{
        status_t error = (file ? B_OK : B_BAD_VALUE);
        if (error == B_OK)
                error = InitCheck();
        // make sure, that all resources are loaded
        if (error == B_OK && fResourceFile) {
                error = fResourceFile->ReadResources(*fContainer);
                fResourceFile->Unset();
        }
        // set the new file, but keep the old container
        if (error == B_OK) {
                ResourcesContainer *container = fContainer;
                fContainer = new(nothrow) ResourcesContainer;
                if (fContainer) {
                        error = SetTo(file, false);
                        delete fContainer;
                } else
                        error = B_NO_MEMORY;
                fContainer = container;
        }
        // write the resources
        if (error == B_OK && fResourceFile)
                error = fResourceFile->WriteResources(*fContainer);
        return error;
}


// Adds a new resource to the file.
status_t
BResources::AddResource(type_code type, int32 id, const void* data,
                                                size_t length, const char* name)
{
        status_t error = (data ? B_OK : B_BAD_VALUE);
        if (error == B_OK)
                error = InitCheck();
        if (error == B_OK)
                error = (fReadOnly ? B_NOT_ALLOWED : B_OK);
        if (error == B_OK) {
                ResourceItem* item = new(nothrow) ResourceItem;
                if (!item)
                        error = B_NO_MEMORY;
                if (error == B_OK) {
                        item->SetIdentity(type, id, name);
                        ssize_t written = item->WriteAt(0, data, length);
                        if (written < 0)
                                error = written;
                        else if (written != (ssize_t)length)
                                error = B_ERROR;
                }
                if (error == B_OK) {
                        if (!fContainer->AddResource(item))
                                error = B_NO_MEMORY;
                }
                if (error != B_OK)
                        delete item;
        }
        return error;
}


// Returns whether the file contains a resource with the specified
// type and id.
bool
BResources::HasResource(type_code type, int32 id)
{
        return (InitCheck() == B_OK && fContainer->IndexOf(type, id) >= 0);
}


// Returns whether the file contains a resource with the specified
// type and name.
bool
BResources::HasResource(type_code type, const char* name)
{
        return (InitCheck() == B_OK && fContainer->IndexOf(type, name) >= 0);
}


// Gets information about a resource identified by byindex.
bool
BResources::GetResourceInfo(int32 byIndex, type_code* typeFound,
        int32* idFound, const char** nameFound, size_t* lengthFound)
{
        ResourceItem* item = NULL;
        if (InitCheck() == B_OK)
                item = fContainer->ResourceAt(byIndex);
        if (item) {
                if (typeFound)
                        *typeFound = item->Type();
                if (idFound)
                        *idFound = item->ID();
                if (nameFound)
                        *nameFound = item->Name();
                if (lengthFound)
                        *lengthFound = item->DataSize();
        }
        return item;
}


// Gets information about a resource identified by byType and andIndex.
bool
BResources::GetResourceInfo(type_code byType, int32 andIndex, int32* idFound,
        const char** nameFound, size_t* lengthFound)
{
        ResourceItem* item = NULL;
        if (InitCheck() == B_OK) {
                item = fContainer->ResourceAt(fContainer->IndexOfType(byType,
                                                                                                                          andIndex));
        }
        if (item) {
                if (idFound)
                        *idFound = item->ID();
                if (nameFound)
                        *nameFound = item->Name();
                if (lengthFound)
                        *lengthFound = item->DataSize();
        }
        return item;
}


// Gets information about a resource identified by byType and andID.
bool
BResources::GetResourceInfo(type_code byType, int32 andID,
        const char** nameFound, size_t* lengthFound)
{
        ResourceItem* item = NULL;
        if (InitCheck() == B_OK)
                item = fContainer->ResourceAt(fContainer->IndexOf(byType, andID));
        if (item) {
                if (nameFound)
                        *nameFound = item->Name();
                if (lengthFound)
                        *lengthFound = item->DataSize();
        }
        return item;
}


// Gets information about a resource identified by byType and andName.
bool
BResources::GetResourceInfo(type_code byType, const char* andName,
        int32* idFound, size_t* lengthFound)
{
        ResourceItem* item = NULL;
        if (InitCheck() == B_OK)
                item = fContainer->ResourceAt(fContainer->IndexOf(byType, andName));
        if (item) {
                if (idFound)
                        *idFound = item->ID();
                if (lengthFound)
                        *lengthFound = item->DataSize();
        }
        return item;
}


// Gets information about a resource identified by byPointer.
bool
BResources::GetResourceInfo(const void* byPointer, type_code* typeFound,
        int32* idFound, size_t* lengthFound, const char** nameFound)
{
        ResourceItem* item = NULL;
        if (InitCheck() == B_OK)
                item = fContainer->ResourceAt(fContainer->IndexOf(byPointer));
        if (item) {
                if (typeFound)
                        *typeFound = item->Type();
                if (idFound)
                        *idFound = item->ID();
                if (nameFound)
                        *nameFound = item->Name();
                if (lengthFound)
                        *lengthFound = item->DataSize();
        }
        return item;
}


// Removes a resource identified by its data pointer.
status_t
BResources::RemoveResource(const void* resource)
{
        status_t error = (resource ? B_OK : B_BAD_VALUE);
        if (error == B_OK)
                error = InitCheck();
        if (error == B_OK)
                error = (fReadOnly ? B_NOT_ALLOWED : B_OK);
        if (error == B_OK) {
                ResourceItem* item
                        = fContainer->RemoveResource(fContainer->IndexOf(resource));
                if (item)
                        delete item;
                else
                        error = B_BAD_VALUE;
        }
        return error;
}


// Removes a resource identified by type and id.
status_t
BResources::RemoveResource(type_code type, int32 id)
{
        status_t error = InitCheck();
        if (error == B_OK)
                error = (fReadOnly ? B_NOT_ALLOWED : B_OK);
        if (error == B_OK) {
                ResourceItem* item
                        = fContainer->RemoveResource(fContainer->IndexOf(type, id));
                if (item)
                        delete item;
                else
                        error = B_BAD_VALUE;
        }
        return error;
}


// #pragma mark - deprecated methods


// Writes data into an existing resource
// (deprecated, use AddResource() instead).
status_t
BResources::WriteResource(type_code type, int32 id, const void* data,
        off_t offset, size_t length)
{
        status_t error = (data && offset >= 0 ? B_OK : B_BAD_VALUE);
        if (error == B_OK)
                error = InitCheck();
        if (error == B_OK)
                error = (fReadOnly ? B_NOT_ALLOWED : B_OK);

        if (error != B_OK)
                return error;

        ResourceItem *item = fContainer->ResourceAt(fContainer->IndexOf(type, id));
        if (!item)
                return B_BAD_VALUE;

        if (fResourceFile) {
                error = fResourceFile->ReadResource(*item);
                if (error != B_OK)
                        return error;
        }

        ssize_t written = item->WriteAt(offset, data, length);

        if (written < 0)
                error = written;
        else if (written != (ssize_t)length)
                error = B_ERROR;

        return error;
}


// Reads data from an existing resource
// (deprecated, use LoadResource() instead).
status_t
BResources::ReadResource(type_code type, int32 id, void* data, off_t offset,
        size_t length)
{
        status_t error = (data && offset >= 0 ? B_OK : B_BAD_VALUE);
        if (error == B_OK)
                error = InitCheck();
        ResourceItem* item = NULL;
        if (error == B_OK) {
                item = fContainer->ResourceAt(fContainer->IndexOf(type, id));
                if (!item)
                        error = B_BAD_VALUE;
        }
        if (error == B_OK && fResourceFile)
                error = fResourceFile->ReadResource(*item);
        if (error == B_OK) {
                if (item) {
                        ssize_t read = item->ReadAt(offset, data, length);
                        if (read < 0)
                                error = read;
                } else
                        error = B_BAD_VALUE;
        }
        return error;
}


// Finds a resource by type and id and returns a pointer to a copy of
// its data (deprecated, use LoadResource() instead).
void*
BResources::FindResource(type_code type, int32 id, size_t* lengthFound)
{
        void* result = NULL;
        size_t size = 0;
        const void* data = LoadResource(type, id, &size);
        if (data != NULL) {
                if ((result = malloc(size)))
                        memcpy(result, data, size);
        }
        if (lengthFound)
                *lengthFound = size;
        return result;
}


// Finds a resource by type and name and returns a pointer to a copy of
// its data (deprecated, use LoadResource() instead).
void*
BResources::FindResource(type_code type, const char* name, size_t* lengthFound)
{
        void* result = NULL;
        size_t size = 0;
        const void *data = LoadResource(type, name, &size);
        if (data != NULL) {
                if ((result = malloc(size)))
                        memcpy(result, data, size);
        }
        if (lengthFound)
                *lengthFound = size;
        return result;
}


// FBC
void BResources::_ReservedResources1() {}
void BResources::_ReservedResources2() {}
void BResources::_ReservedResources3() {}
void BResources::_ReservedResources4() {}
void BResources::_ReservedResources5() {}
void BResources::_ReservedResources6() {}
void BResources::_ReservedResources7() {}
void BResources::_ReservedResources8() {}