root/src/kits/interface/Picture.cpp
/*
 * Copyright 2001-2014 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Marc Flerackers, mflerackers@androme.be
 */


// Records a series of drawing instructions that can be "replayed" later.


#include <Picture.h>

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

//#define DEBUG 1
#include <ByteOrder.h>
#include <Debug.h>
#include <List.h>
#include <Message.h>

#include <AppServerLink.h>
#include <ObjectList.h>
#include <PicturePlayer.h>
#include <ServerProtocol.h>
#include <locks.h>

#include "PicturePrivate.h"


static BObjectList<BPicture> sPictureList;
static recursive_lock sPictureListLock = RECURSIVE_LOCK_INITIALIZER("BPicture list");


void
reconnect_pictures_to_app_server()
{
        RecursiveLocker _(sPictureListLock);
        for (int32 i = 0; i < sPictureList.CountItems(); i++) {
                BPicture::Private picture(sPictureList.ItemAt(i));
                picture.ReconnectToAppServer();
        }
}


BPicture::Private::Private(BPicture* picture)
        :
        fPicture(picture)
{
}


void
BPicture::Private::ReconnectToAppServer()
{
        fPicture->_Upload();
}


struct _BPictureExtent_ {
                                                        _BPictureExtent_(int32 size = 0);
                                                        ~_BPictureExtent_();

                        const void*             Data() const { return fNewData; }
                        status_t                ImportData(const void* data, int32 size);

                        status_t                Flatten(BDataIO* stream);
                        status_t                Unflatten(BDataIO* stream);

                        int32                   Size() const { return fNewSize; }
                        status_t                SetSize(int32 size);

                        bool                    AddPicture(BPicture* picture)
                                                                { return fPictures.AddItem(picture); }
                        void                    DeletePicture(int32 index)
                                                                { delete static_cast<BPicture*>
                                                                        (fPictures.RemoveItem(index)); }

                        BList*                  Pictures() { return &fPictures; }
                        BPicture*               PictureAt(int32 index)
                                                                { return static_cast<BPicture*>
                                                                        (fPictures.ItemAt(index)); }

                        int32                   CountPictures() const
                                                                { return fPictures.CountItems(); }

private:
                        void*   fNewData;
                        int32   fNewSize;

                        BList   fPictures;
                                // In R5 this is a BArray<BPicture*>
                                // which is completely inline.
};


struct picture_header {
        int32 magic1; // version ?
        int32 magic2; // endianess ?
};


BPicture::BPicture()
        :
        fToken(-1),
        fExtent(NULL),
        fUsurped(NULL)
{
        _InitData();
}


BPicture::BPicture(const BPicture& otherPicture)
        :
        fToken(-1),
        fExtent(NULL),
        fUsurped(NULL)
{
        _InitData();

        if (otherPicture.fToken != -1) {
                BPrivate::AppServerLink link;
                link.StartMessage(AS_CLONE_PICTURE);
                link.Attach<int32>(otherPicture.fToken);

                status_t status = B_ERROR;
                if (link.FlushWithReply(status) == B_OK && status == B_OK)
                        link.Read<int32>(&fToken);

                if (status < B_OK)
                        return;
        }

        if (otherPicture.fExtent->Size() > 0) {
                fExtent->ImportData(otherPicture.fExtent->Data(),
                        otherPicture.fExtent->Size());

                for (int32 i = 0; i < otherPicture.fExtent->CountPictures(); i++) {
                        BPicture* picture
                                = new BPicture(*otherPicture.fExtent->PictureAt(i));
                        fExtent->AddPicture(picture);
                }
        }
}


BPicture::BPicture(BMessage* data)
        :
        fToken(-1),
        fExtent(NULL),
        fUsurped(NULL)
{
        _InitData();

        int32 version;
        if (data->FindInt32("_ver", &version) != B_OK)
                version = 0;

        int8 endian;
        if (data->FindInt8("_endian", &endian) != B_OK)
                endian = 0;

        const void* pictureData;
        ssize_t size;
        if (data->FindData("_data", B_RAW_TYPE, &pictureData, &size) != B_OK)
                return;

        // Load sub pictures
        BMessage pictureMessage;
        int32 i = 0;
        while (data->FindMessage("piclib", i++, &pictureMessage) == B_OK) {
                BPicture* picture = new BPicture(&pictureMessage);
                fExtent->AddPicture(picture);
        }

        if (version == 0) {
                // TODO: For now. We'll see if it's worth to support old style data
                debugger("old style BPicture data is not supported");
        } else if (version == 1) {
                fExtent->ImportData(pictureData, size);

//              swap_data(fExtent->fNewData, fExtent->fNewSize);

                if (fExtent->Size() > 0)
                        _AssertServerCopy();
        }

        // Do we just free the data now?
        if (fExtent->Size() > 0)
                fExtent->SetSize(0);

        // What with the sub pictures?
        for (i = fExtent->CountPictures() - 1; i >= 0; i--)
                fExtent->DeletePicture(i);
}


BPicture::BPicture(const void* data, int32 size)
{
        _InitData();
        // TODO: For now. We'll see if it's worth to support old style data
        debugger("old style BPicture data is not supported");
}


void
BPicture::_InitData()
{
        fToken = -1;
        fUsurped = NULL;

        fExtent = new (std::nothrow) _BPictureExtent_;

        RecursiveLocker _(sPictureListLock);
        sPictureList.AddItem(this);
}


BPicture::~BPicture()
{
        RecursiveLocker _(sPictureListLock);
        sPictureList.RemoveItem(this, false);
        _DisposeData();
}


void
BPicture::_DisposeData()
{
        if (fToken != -1) {
                BPrivate::AppServerLink link;

                link.StartMessage(AS_DELETE_PICTURE);
                link.Attach<int32>(fToken);
                link.Flush();
                SetToken(-1);
        }

        delete fExtent;
        fExtent = NULL;
}


BArchivable*
BPicture::Instantiate(BMessage* data)
{
        if (validate_instantiation(data, "BPicture"))
                return new BPicture(data);

        return NULL;
}


status_t
BPicture::Archive(BMessage* data, bool deep) const
{
        if (!const_cast<BPicture*>(this)->_AssertLocalCopy())
                return B_ERROR;

        status_t err = BArchivable::Archive(data, deep);
        if (err != B_OK)
                return err;

        err = data->AddInt32("_ver", 1);
        if (err != B_OK)
                return err;

        err = data->AddInt8("_endian", B_HOST_IS_BENDIAN);
        if (err != B_OK)
                return err;

        err = data->AddData("_data", B_RAW_TYPE, fExtent->Data(), fExtent->Size());
        if (err != B_OK)
                return err;

        for (int32 i = 0; i < fExtent->CountPictures(); i++) {
                BMessage pictureMessage;

                err = fExtent->PictureAt(i)->Archive(&pictureMessage, deep);
                if (err != B_OK)
                        break;

                err = data->AddMessage("piclib", &pictureMessage);
                if (err != B_OK)
                        break;
        }

        return err;
}


status_t
BPicture::Perform(perform_code code, void* arg)
{
        return BArchivable::Perform(code, arg);
}


status_t
BPicture::Play(void** callBackTable, int32 tableEntries, void* user)
{
        if (!_AssertLocalCopy())
                return B_ERROR;

        BPrivate::PicturePlayer player(fExtent->Data(), fExtent->Size(),
                fExtent->Pictures());

        return player.Play(callBackTable, tableEntries, user);
}


status_t
BPicture::Flatten(BDataIO* stream)
{
        // TODO: what about endianess?

        if (!_AssertLocalCopy())
                return B_ERROR;

        const picture_header header = { 2, 0 };
        ssize_t bytesWritten = stream->Write(&header, sizeof(header));
        if (bytesWritten < B_OK)
                return bytesWritten;

        if (bytesWritten != (ssize_t)sizeof(header))
                return B_IO_ERROR;

        return fExtent->Flatten(stream);
}


status_t
BPicture::Unflatten(BDataIO* stream)
{
        // TODO: clear current picture data?

        picture_header header;
        ssize_t bytesRead = stream->Read(&header, sizeof(header));
        if (bytesRead < B_OK)
                return bytesRead;

        if (bytesRead != (ssize_t)sizeof(header)
                || header.magic1 != 2 || header.magic2 != 0)
                return B_BAD_TYPE;

        status_t status = fExtent->Unflatten(stream);
        if (status < B_OK)
                return status;

//      swap_data(fExtent->fNewData, fExtent->fNewSize);

        if (!_AssertServerCopy())
                return B_ERROR;

        // Data is now kept server side, remove the local copy
        if (fExtent->Data() != NULL)
                fExtent->SetSize(0);

        return status;
}


void
BPicture::_ImportOldData(const void* data, int32 size)
{
        // TODO: We don't support old data for now
}


void
BPicture::SetToken(int32 token)
{
        fToken = token;
}


int32
BPicture::Token() const
{
        return fToken;
}


bool
BPicture::_AssertLocalCopy()
{
        if (fExtent->Data() != NULL)
                return true;

        if (fToken == -1)
                return false;

        return _Download() == B_OK;
}


bool
BPicture::_AssertOldLocalCopy()
{
        // TODO: We don't support old data for now

        return false;
}


bool
BPicture::_AssertServerCopy()
{
        if (fToken != -1)
                return true;

        if (fExtent->Data() == NULL)
                return false;

        for (int32 i = 0; i < fExtent->CountPictures(); i++) {
                if (!fExtent->PictureAt(i)->_AssertServerCopy())
                        return false;
        }

        return _Upload() == B_OK;
}


status_t
BPicture::_Upload()
{
        if (fExtent == NULL || fExtent->Data() == NULL)
                return B_BAD_VALUE;

        BPrivate::AppServerLink link;

        link.StartMessage(AS_CREATE_PICTURE);
        link.Attach<int32>(fExtent->CountPictures());

        for (int32 i = 0; i < fExtent->CountPictures(); i++) {
                BPicture* picture = fExtent->PictureAt(i);
                if (picture != NULL)
                        link.Attach<int32>(picture->fToken);
                else
                        link.Attach<int32>(-1);
        }
        link.Attach<int32>(fExtent->Size());
        link.Attach(fExtent->Data(), fExtent->Size());

        status_t status = B_ERROR;
        if (link.FlushWithReply(status) == B_OK
                && status == B_OK) {
                link.Read<int32>(&fToken);
        }

        return status;
}


status_t
BPicture::_Download()
{
        ASSERT(fExtent->Data() == NULL);
        ASSERT(fToken != -1);

        BPrivate::AppServerLink link;

        link.StartMessage(AS_DOWNLOAD_PICTURE);
        link.Attach<int32>(fToken);

        status_t status = B_ERROR;
        if (link.FlushWithReply(status) == B_OK && status == B_OK) {
                int32 count = 0;
                link.Read<int32>(&count);

                // Read sub picture tokens
                for (int32 i = 0; i < count; i++) {
                        BPicture* picture = new BPicture;
                        link.Read<int32>(&picture->fToken);
                        fExtent->AddPicture(picture);
                }

                int32 size;
                link.Read<int32>(&size);
                status = fExtent->SetSize(size);
                if (status == B_OK)
                        link.Read(const_cast<void*>(fExtent->Data()), size);
        }

        return status;
}


const void*
BPicture::Data() const
{
        if (fExtent->Data() == NULL)
                const_cast<BPicture*>(this)->_AssertLocalCopy();

        return fExtent->Data();
}


int32
BPicture::DataSize() const
{
        if (fExtent->Data() == NULL)
                const_cast<BPicture*>(this)->_AssertLocalCopy();

        return fExtent->Size();
}


void
BPicture::Usurp(BPicture* lameDuck)
{
        _DisposeData();

        // Reinitializes the BPicture
        _InitData();

        // Do the Usurping
        fUsurped = lameDuck;
}


BPicture*
BPicture::StepDown()
{
        BPicture* lameDuck = fUsurped;
        fUsurped = NULL;

        return lameDuck;
}


void BPicture::_ReservedPicture1() {}
void BPicture::_ReservedPicture2() {}
void BPicture::_ReservedPicture3() {}


BPicture&
BPicture::operator=(const BPicture&)
{
        return* this;
}


// _BPictureExtent_
_BPictureExtent_::_BPictureExtent_(int32 size)
        :
        fNewData(NULL),
        fNewSize(0)
{
        SetSize(size);
}


_BPictureExtent_::~_BPictureExtent_()
{
        free(fNewData);
        for (int32 i = 0; i < fPictures.CountItems(); i++)
                delete static_cast<BPicture*>(fPictures.ItemAtFast(i));
}


status_t
_BPictureExtent_::ImportData(const void* data, int32 size)
{
        if (data == NULL)
                return B_BAD_VALUE;

        status_t status = B_OK;
        if (Size() != size)
                status = SetSize(size);

        if (status == B_OK)
                memcpy(fNewData, data, size);

        return status;
}


status_t
_BPictureExtent_::Unflatten(BDataIO* stream)
{
        if (stream == NULL)
                return B_BAD_VALUE;

        int32 count = 0;
        ssize_t bytesRead = stream->Read(&count, sizeof(count));
        if (bytesRead < B_OK)
                return bytesRead;
        if (bytesRead != (ssize_t)sizeof(count))
                return B_BAD_DATA;

        for (int32 i = 0; i < count; i++) {
                BPicture* picture = new BPicture;
                status_t status = picture->Unflatten(stream);
                if (status < B_OK) {
                        delete picture;
                        return status;
                }

                AddPicture(picture);
        }

        int32 size;
        bytesRead = stream->Read(&size, sizeof(size));
        if (bytesRead < B_OK)
                return bytesRead;

        if (bytesRead != (ssize_t)sizeof(size))
                return B_IO_ERROR;

        status_t status = B_OK;
        if (Size() != size)
                status = SetSize(size);

        if (status < B_OK)
                return status;

        bytesRead = stream->Read(fNewData, size);
        if (bytesRead < B_OK)
                return bytesRead;

        if (bytesRead != (ssize_t)size)
                return B_IO_ERROR;

        return B_OK;
}


status_t
_BPictureExtent_::Flatten(BDataIO* stream)
{
        int32 count = fPictures.CountItems();
        ssize_t bytesWritten = stream->Write(&count, sizeof(count));
        if (bytesWritten < B_OK)
                return bytesWritten;

        if (bytesWritten != (ssize_t)sizeof(count))
                return B_IO_ERROR;

        for (int32 i = 0; i < count; i++) {
                status_t status = PictureAt(i)->Flatten(stream);
                if (status < B_OK)
                        return status;
        }

        bytesWritten = stream->Write(&fNewSize, sizeof(fNewSize));
        if (bytesWritten < B_OK)
                return bytesWritten;

        if (bytesWritten != (ssize_t)sizeof(fNewSize))
                return B_IO_ERROR;

        bytesWritten = stream->Write(fNewData, fNewSize);
        if (bytesWritten < B_OK)
                return bytesWritten;

        if (bytesWritten != fNewSize)
                return B_IO_ERROR;

        return B_OK;
}


status_t
_BPictureExtent_::SetSize(int32 size)
{
        if (size < 0)
                return B_BAD_VALUE;

        if (size == fNewSize)
                return B_OK;

        if (size == 0) {
                free(fNewData);
                fNewData = NULL;
        } else {
                void* data = realloc(fNewData, size);
                if (data == NULL)
                        return B_NO_MEMORY;

                fNewData = data;
        }

        fNewSize = size;
        return B_OK;
}