#define USE_RESOURCES 1
#include "ResourceSet.h"
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <Application.h>
#include <Autolock.h>
#include <Bitmap.h>
#include <DataIO.h>
#include <Debug.h>
#include <Entry.h>
#include <File.h>
#include <Path.h>
#include <String.h>
#if USE_RESOURCES
#include <Resources.h>
#endif
#if USE_RESOURCES
#define RESOURCES_ONLY(x) x
#else
#define RESOURCES_ONLY(x)
#endif
namespace TResourcePrivate {
class TypeObject {
public:
TypeObject()
: fDeleteOK(false)
{
}
virtual ~TypeObject()
{
if (!fDeleteOK)
debugger("deleting object owned by BResourceSet");
}
void Delete()
{
fDeleteOK = true;
}
private:
TypeObject(const TypeObject &);
TypeObject &operator=(const TypeObject &);
bool operator==(const TypeObject &);
bool operator!=(const TypeObject &);
bool fDeleteOK;
};
class BitmapTypeItem : public BBitmap, public TypeObject {
public:
BitmapTypeItem(BRect bounds, uint32 flags, color_space depth,
int32 bytesPerRow = B_ANY_BYTES_PER_ROW, screen_id screenID
= B_MAIN_SCREEN_ID)
: BBitmap(bounds, flags, depth, bytesPerRow, screenID)
{
}
BitmapTypeItem(const BBitmap* source, bool accepts_views = false,
bool need_contiguous = false)
: BBitmap(source, accepts_views, need_contiguous)
{
}
BitmapTypeItem(BMessage* data)
: BBitmap(data)
{
}
virtual ~BitmapTypeItem()
{
}
};
class StringBlockTypeItem : public TStringBlock, public TypeObject {
public:
StringBlockTypeItem(BDataIO* data)
: TStringBlock(data)
{
}
StringBlockTypeItem(const void* block, size_t size)
: TStringBlock(block, size)
{
}
virtual ~StringBlockTypeItem()
{
}
};
class TypeItem {
public:
TypeItem(int32 id, const char* name, const void* data, size_t size)
: fID(id), fName(name),
fData(const_cast<void*>(data)), fSize(size), fObject(0),
fOwnData(false), fSourceIsFile(false)
{
}
TypeItem(int32 id, const char* name, BFile* file)
: fID(id),
fName(name),
fData(NULL),
fSize(0),
fObject(NULL),
fOwnData(true),
fSourceIsFile(false)
{
off_t size;
if (file->GetSize(&size) == B_OK) {
fSize = (size_t)size;
fData = malloc(fSize);
if (file->ReadAt(0, fData, fSize) < B_OK ) {
free(fData);
fData = NULL;
fSize = 0;
}
}
}
virtual ~TypeItem()
{
if (fOwnData) {
free(fData);
fData = NULL;
fSize = 0;
}
SetObject(NULL);
}
int32 ID() const
{
return fID;
}
const char* Name() const
{
return fName.String();
}
const void* Data() const
{
return fData;
}
size_t Size() const
{
return fSize;
}
void SetObject(TypeObject* object)
{
if (object == fObject)
return;
if (fObject)
fObject->Delete();
fObject = object;
}
TypeObject* Object() const
{
return fObject;
}
void SetSourceIsFile(bool state)
{
fSourceIsFile = state;
}
bool SourceIsFile() const
{
return fSourceIsFile;
}
private:
int32 fID;
BString fName;
void* fData;
size_t fSize;
TypeObject* fObject;
bool fOwnData;
bool fSourceIsFile;
};
static bool FreeTypeItemFunc(void* item)
{
delete reinterpret_cast<TypeItem*>(item);
return false;
}
class TypeList {
public:
TypeList(type_code type)
: fType(type)
{
}
virtual ~TypeList()
{
fItems.DoForEach(FreeTypeItemFunc);
fItems.MakeEmpty();
}
type_code Type() const
{
return fType;
}
TypeItem* FindItemByID(int32 id)
{
for (int32 i = fItems.CountItems() - 1; i >= 0; i--) {
TypeItem* it = (TypeItem*)fItems.ItemAt(i);
if (it->ID() == id)
return it;
}
return NULL;
}
TypeItem* FindItemByName(const char* name)
{
for (int32 i = fItems.CountItems() - 1; i >= 0; i--) {
TypeItem* it = (TypeItem*)fItems.ItemAt(i);
if (strcmp(it->Name(), name) == 0)
return it;
}
return NULL;
}
void AddItem(TypeItem* item)
{
fItems.AddItem(item);
}
private:
type_code fType;
BList fItems;
};
}
using namespace TResourcePrivate;
TStringBlock::TStringBlock(BDataIO* data)
: fNumEntries(0),
fIndex(0),
fStrings(NULL),
fOwnData(true)
{
fStrings = (char*)malloc(1024);
size_t pos = 0;
ssize_t amount;
while ((amount = data->Read(fStrings + pos, 1024)) == 1024) {
pos += amount;
fStrings = (char*)realloc(fStrings, pos + 1024);
}
if (amount > 0)
pos += amount;
fNumEntries = PreIndex(fStrings, amount);
fIndex = (size_t*)malloc(sizeof(size_t) * fNumEntries);
MakeIndex(fStrings, amount, fNumEntries, fIndex);
}
TStringBlock::TStringBlock(const void* block, size_t size)
:
fNumEntries(0),
fIndex(0),
fStrings(NULL),
fOwnData(false)
{
fIndex = (size_t*)const_cast<void*>(block);
fStrings = (char*)const_cast<void*>(block);
size_t last_off = 0;
while (fIndex[fNumEntries] > last_off && fIndex[fNumEntries] < size ) {
last_off = fIndex[fNumEntries];
fNumEntries++;
}
}
TStringBlock::~TStringBlock()
{
if (fOwnData) {
free(fIndex);
free(fStrings);
}
fIndex = 0;
fStrings = NULL;
}
const char*
TStringBlock::String(size_t index) const
{
if (index >= fNumEntries)
return NULL;
return fStrings + fIndex[index];
}
size_t
TStringBlock::PreIndex(char* strings, ssize_t len)
{
size_t count = 0;
char* orig = strings;
char* end = strings + len;
bool in_cr = false;
bool first = true;
bool skipping = false;
while (orig < end) {
if (*orig == '\n' || *orig == '\r' || *orig == 0) {
if (!in_cr && *orig == '\r')
in_cr = true;
if (in_cr && *orig == '\n') {
orig++;
continue;
}
first = true;
skipping = false;
*strings = 0;
count++;
} else if (first && *orig == '#') {
skipping = true;
first = false;
orig++;
continue;
} else if (skipping) {
orig++;
continue;
} else if (*orig == '\\' && (orig + 1) < end) {
orig++;
switch (*orig) {
case '\\':
*strings = '\\';
break;
case '\n':
*strings = '\n';
break;
case '\r':
*strings = '\r';
break;
case '\t':
*strings = '\t';
break;
default:
*strings = *orig;
break;
}
} else
*strings = *orig;
orig++;
strings++;
}
return count;
}
void
TStringBlock::MakeIndex(const char* strings, ssize_t len,
size_t indexLength, size_t* resultingIndex)
{
*resultingIndex++ = 0;
indexLength--;
ssize_t pos = 0;
while (pos < len && indexLength > 0) {
if (strings[pos] == 0 ) {
*resultingIndex++ = (size_t)pos + 1;
indexLength--;
}
pos++;
}
}
#if USE_RESOURCES
static bool
FreeResourcesFunc(void* item)
{
delete reinterpret_cast<BResources*>(item);
return false;
}
#endif
static bool
FreePathFunc(void* item)
{
delete reinterpret_cast<BPath*>(item);
return false;
}
static bool
FreeTypeFunc(void* item)
{
delete reinterpret_cast<TypeList*>(item);
return false;
}
TResourceSet::TResourceSet()
{
}
TResourceSet::~TResourceSet()
{
BAutolock lock(&fLock);
#if USE_RESOURCES
fResources.DoForEach(FreeResourcesFunc);
fResources.MakeEmpty();
#endif
fDirectories.DoForEach(FreePathFunc);
fDirectories.MakeEmpty();
fTypes.DoForEach(FreeTypeFunc);
fTypes.MakeEmpty();
}
status_t
TResourceSet::AddResources(BResources* RESOURCES_ONLY(resources))
{
#if USE_RESOURCES
if (!resources)
return B_BAD_VALUE;
BAutolock lock(&fLock);
status_t err = fResources.AddItem(resources) ? B_OK : B_ERROR;
if (err != B_OK)
delete resources;
return err;
#else
return B_ERROR;
#endif
}
status_t
TResourceSet::AddDirectory(const char* fullPath)
{
if (!fullPath)
return B_BAD_VALUE;
BPath* path = new BPath(fullPath);
status_t err = path->InitCheck();
if (err != B_OK) {
delete path;
return err;
}
BAutolock lock(&fLock);
err = fDirectories.AddItem(path) ? B_OK : B_ERROR;
if (err != B_OK)
delete path;
return err;
}
status_t
TResourceSet::AddEnvDirectory(const char* in, const char* defaultValue)
{
BString buf;
status_t err = ExpandString(&buf, in);
if (err != B_OK) {
if (defaultValue)
return AddDirectory(defaultValue);
return err;
}
return AddDirectory(buf.String());
}
status_t
TResourceSet::ExpandString(BString* out, const char* in)
{
const char* start = in;
while (*in) {
if (*in == '$') {
if (start < in)
out->Append(start, (int32)(in - start));
in++;
char variableName[1024];
size_t i = 0;
if (*in == '{') {
in++;
while (*in && *in != '}' && i < sizeof(variableName) - 1)
variableName[i++] = *in++;
if (*in)
in++;
} else {
while ((isalnum(*in) || *in == '_')
&& i < sizeof(variableName) - 1)
variableName[i++] = *in++;
}
start = in;
variableName[i] = '\0';
const char* val = getenv(variableName);
if (!val) {
PRINT(("Error: env var %s not found.\n", &variableName[0]));
return B_NAME_NOT_FOUND;
}
status_t err = ExpandString(out, val);
if (err != B_OK)
return err;
} else if (*in == '\\') {
if (start < in)
out->Append(start, (int32)(in - start));
in++;
start = in;
in++;
} else
in++;
}
if (start < in)
out->Append(start, (int32)(in - start));
return B_OK;
}
const void*
TResourceSet::FindResource(type_code type, int32 id, size_t* outSize)
{
TypeItem* item = FindItemID(type, id);
if (outSize)
*outSize = item ? item->Size() : 0;
return item ? item->Data() : NULL;
}
const void*
TResourceSet::FindResource(type_code type, const char* name, size_t* outSize)
{
TypeItem* item = FindItemName(type, name);
if (outSize)
*outSize = item ? item->Size() : 0;
return item ? item->Data() : NULL;
}
const BBitmap*
TResourceSet::FindBitmap(type_code type, int32 id)
{
return ReturnBitmapItem(type, FindItemID(type, id));
}
const BBitmap*
TResourceSet::FindBitmap(type_code type, const char* name)
{
return ReturnBitmapItem(type, FindItemName(type, name));
}
const TStringBlock*
TResourceSet::FindStringBlock(type_code type, int32 id)
{
return ReturnStringBlockItem(FindItemID(type, id));
}
const TStringBlock*
TResourceSet::FindStringBlock(type_code type, const char* name)
{
return ReturnStringBlockItem(FindItemName(type, name));
}
const char*
TResourceSet::FindString(type_code type, int32 id, uint32 index)
{
const TStringBlock* stringBlock = FindStringBlock(type, id);
if (!stringBlock)
return NULL;
return stringBlock->String(index);
}
const char*
TResourceSet::FindString(type_code type, const char* name, uint32 index)
{
const TStringBlock* stringBlock = FindStringBlock(type, name);
if (!stringBlock)
return NULL;
return stringBlock->String(index);
}
TypeList*
TResourceSet::FindTypeList(type_code type)
{
BAutolock lock(&fLock);
for (int32 i = fTypes.CountItems() - 1; i >= 0; i--) {
TypeList* list = (TypeList*)fTypes.ItemAt(i);
if (list && list->Type() == type)
return list;
}
return NULL;
}
TypeItem*
TResourceSet::FindItemID(type_code type, int32 id)
{
TypeList* list = FindTypeList(type);
TypeItem* item = NULL;
if (list)
item = list->FindItemByID(id);
if (!item)
item = LoadResource(type, id, 0, &list);
return item;
}
TypeItem*
TResourceSet::FindItemName(type_code type, const char* name)
{
TypeList* list = FindTypeList(type);
TypeItem* item = NULL;
if (list)
item = list->FindItemByName(name);
if (!item)
item = LoadResource(type, -1, name, &list);
return item;
}
TypeItem*
TResourceSet::LoadResource(type_code type, int32 id, const char* name,
TypeList** inOutList)
{
TypeItem* item = NULL;
if (name) {
BEntry entry;
fLock.Lock();
for (int32 i = fDirectories.CountItems() - 1; i >= 0; i--) {
BPath* dir = (BPath*)fDirectories.ItemAt(i);
if (dir) {
fLock.Unlock();
BPath path(dir->Path(), name);
if (entry.SetTo(path.Path(), true) == B_OK ) {
BFile file(&entry, B_READ_ONLY);
if (file.InitCheck() == B_OK ) {
item = new TypeItem(id, name, &file);
item->SetSourceIsFile(true);
}
}
fLock.Lock();
}
}
fLock.Unlock();
}
#if USE_RESOURCES
if (!item) {
fLock.Lock();
for (int32 i = fResources.CountItems() - 1; i >= 0; i--) {
BResources* resource = (BResources*)fResources.ItemAt(i);
if (resource) {
const void* data = NULL;
size_t size = 0;
if (id >= 0)
data = resource->LoadResource(type, id, &size);
else if (name != NULL)
data = resource->LoadResource(type, name, &size);
if (data && size) {
item = new TypeItem(id, name, data, size);
item->SetSourceIsFile(false);
}
}
}
fLock.Unlock();
}
#endif
if (item) {
TypeList* list = inOutList ? *inOutList : NULL;
if (!list) {
list = FindTypeList(type);
}
BAutolock lock(&fLock);
if (!list) {
list = new TypeList(type);
fTypes.AddItem(list);
}
if (inOutList)
*inOutList = list;
list->AddItem(item);
}
return item;
}
BBitmap*
TResourceSet::ReturnBitmapItem(type_code, TypeItem* from)
{
if (!from)
return NULL;
TypeObject* obj = from->Object();
BitmapTypeItem* bitmap = dynamic_cast<BitmapTypeItem*>(obj);
if (bitmap)
return bitmap;
if (obj)
return NULL;
BMemoryIO stream(from->Data(), from->Size());
stream.Seek(0, SEEK_SET);
BMessage archive;
if (archive.Unflatten(&stream) == B_OK) {
bitmap = new BitmapTypeItem(&archive);
if (bitmap && bitmap->InitCheck() != B_OK) {
bitmap->Delete();
delete bitmap;
bitmap = NULL;
}
}
if (bitmap) {
BAutolock lock(&fLock);
if (from->Object() != NULL) {
bitmap->Delete();
delete bitmap;
bitmap = dynamic_cast<BitmapTypeItem*>(from->Object());
} else
from->SetObject(bitmap);
}
return bitmap;
}
TStringBlock*
TResourceSet::ReturnStringBlockItem(TypeItem* from)
{
if (!from)
return NULL;
TypeObject* obj = from->Object();
StringBlockTypeItem* stringBlock = dynamic_cast<StringBlockTypeItem*>(obj);
if (stringBlock)
return stringBlock;
if (obj)
return NULL;
if (from->SourceIsFile() ) {
BMemoryIO stream(from->Data(), from->Size());
stringBlock = new StringBlockTypeItem(&stream);
} else
stringBlock = new StringBlockTypeItem(from->Data(), from->Size());
if (stringBlock) {
BAutolock lock(&fLock);
if (from->Object() != NULL) {
delete stringBlock;
stringBlock = dynamic_cast<StringBlockTypeItem*>(from->Object());
} else
from->SetObject(stringBlock);
}
return stringBlock;
}
namespace TResourcePrivate {
TResourceSet* gResources = NULL;
BLocker gResourceLocker;
}
TResourceSet*
AppResSet()
{
if (gResources)
return gResources;
if (!gResourceLocker.Lock())
return NULL;
if (gResources) {
gResourceLocker.Unlock();
return gResources;
}
gResources = new TResourceSet;
gResources->AddResources(BApplication::AppResources());
gResourceLocker.Unlock();
return gResources;
}