563 lines
14 KiB
C++
563 lines
14 KiB
C++
#include "resourceManager.h"
|
|
#include "resourceDatabase.h"
|
|
#include "Connection.h"
|
|
#include "common.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
#include "minorGems/io/file/File.h"
|
|
#include "minorGems/util/stringUtils.h"
|
|
#include "minorGems/util/SimpleVector.h"
|
|
#include "minorGems/util/log/AppLog.h"
|
|
|
|
#include "minorGems/system/Thread.h"
|
|
#include "minorGems/system/Time.h"
|
|
|
|
|
|
|
|
|
|
extern Connection *connection;
|
|
|
|
|
|
char testResourceCacheWritePermissions() {
|
|
File cacheDir( NULL, "resourceCache" );
|
|
|
|
if( !cacheDir.exists() ) {
|
|
return true;
|
|
}
|
|
else {
|
|
File *writeFile = cacheDir.getChildFile( "testWrite.txt " );
|
|
|
|
char success = writeFile->writeToFile( "test" );
|
|
|
|
writeFile->remove();
|
|
|
|
delete writeFile;
|
|
|
|
return success;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void initResourceManager() {
|
|
|
|
File cacheDir( NULL, "resourceCache" );
|
|
|
|
if( !cacheDir.exists() ) {
|
|
AppLog::warning( "No resourceCache directory found, creating it" );
|
|
cacheDir.makeDirectory();
|
|
}
|
|
|
|
if( cacheDir.exists() && cacheDir.isDirectory() ) {
|
|
// good
|
|
}
|
|
else {
|
|
AppLog::criticalError(
|
|
"ERROR: resourceCache not found, and could not create it" );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void freeResourceManager() {
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char useNetwork = true;
|
|
|
|
|
|
void setUseNetwork( char inUseNetwork ) {
|
|
useNetwork = inUseNetwork;
|
|
}
|
|
|
|
|
|
static File *getResourceDir( const char *inResourceType ) {
|
|
char *pathSteps[1];
|
|
|
|
pathSteps[0] = (char *)"resourceCache";
|
|
|
|
|
|
File *resourceDir = new File( new Path( pathSteps, 1, false ),
|
|
(char*)inResourceType );
|
|
|
|
return resourceDir;
|
|
}
|
|
|
|
|
|
|
|
|
|
File *getResourceFile( const char *inResourceType, uniqueID inID ) {
|
|
|
|
char *idString = getHumanReadableString( inID );
|
|
|
|
char *pathSteps[2];
|
|
|
|
pathSteps[0] = (char *)"resourceCache";
|
|
pathSteps[1] = (char *)inResourceType;
|
|
|
|
|
|
File *resourceFile = new File( new Path( pathSteps, 2, false ),
|
|
idString );
|
|
|
|
delete [] idString;
|
|
|
|
return resourceFile;
|
|
}
|
|
|
|
|
|
|
|
static void addRequestMessage( SimpleVector<unsigned char> *inMessageAccum,
|
|
const char *inResourceType,
|
|
uniqueID inID ) {
|
|
|
|
int typeLength = strlen( inResourceType );
|
|
inMessageAccum->push_back( (unsigned char)typeLength );
|
|
inMessageAccum->push_back( (unsigned char *)inResourceType, typeLength );
|
|
|
|
inMessageAccum->push_back( inID.bytes, U );
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned char *loadResourceData( const char *inResourceType,
|
|
uniqueID inID,
|
|
int *outLength,
|
|
char *outCameFromNetwork ) {
|
|
|
|
File *resourceFile = getResourceFile( inResourceType, inID );
|
|
|
|
if( resourceFile->exists() ) {
|
|
|
|
*outLength = resourceFile->getLength();
|
|
|
|
char *fullFileName = resourceFile->getFullFileName();
|
|
|
|
delete resourceFile;
|
|
|
|
|
|
FILE *f = fopen( fullFileName, "rb" );
|
|
|
|
delete [] fullFileName;
|
|
|
|
unsigned char *data = new unsigned char[ *outLength ];
|
|
|
|
int numRead = fread( data, 1, *outLength, f );
|
|
|
|
fclose( f );
|
|
|
|
*outCameFromNetwork = false;
|
|
|
|
if( numRead != *outLength ) {
|
|
delete [] data;
|
|
AppLog::getLog()->logPrintf(
|
|
Log::ERROR_LEVEL,
|
|
"Failed to read from %s file\n", inResourceType );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
return data;
|
|
}
|
|
else if( useNetwork &&
|
|
connection != NULL && connection->isConnected() ) {
|
|
|
|
delete resourceFile;
|
|
|
|
// try to fetch from network
|
|
|
|
SimpleVector<unsigned char> messageAccum;
|
|
|
|
// single request in this bundle
|
|
messageAccum.push_back( 1 );
|
|
|
|
addRequestMessage( &messageAccum, inResourceType, inID );
|
|
|
|
unsigned char *message = messageAccum.getElementArray();
|
|
|
|
// resource requests on channel 1
|
|
connection->sendMessage( message, messageAccum.size(), 1 );
|
|
|
|
delete [] message;
|
|
|
|
|
|
// now wait for response
|
|
|
|
int length;
|
|
message = NULL;
|
|
|
|
unsigned long sec, ms;
|
|
|
|
Time::getCurrentTime( &sec, &ms );
|
|
|
|
char *idString = getHumanReadableString( inID );
|
|
AppLog::getLog()->logPrintf(
|
|
Log::DETAIL_LEVEL,
|
|
"Waiting for %s resource %s from network (%lu:%lu)...\n",
|
|
inResourceType, idString, sec, ms );
|
|
delete [] idString;
|
|
|
|
while( message == NULL ) {
|
|
// keep stepping until network operations are finished
|
|
char workLeft = true;
|
|
while( workLeft ) {
|
|
workLeft = connection->step();
|
|
}
|
|
|
|
message = connection->receiveMessage( &length, 1 );
|
|
|
|
if( message == NULL ) {
|
|
// wait
|
|
AppLog::trace( "sleeping\n" );
|
|
Thread::staticSleep( 50 );
|
|
}
|
|
}
|
|
|
|
Time::getCurrentTime( &sec, &ms );
|
|
AppLog::getLog()->logPrintf(
|
|
Log::DETAIL_LEVEL,
|
|
"...got response (%lu:%lu)\n", sec, ms );
|
|
|
|
// Got message
|
|
|
|
*outCameFromNetwork = true;
|
|
|
|
// first 4 bytes represent length of chunk, skip them (only one chunk)
|
|
*outLength = length - 4;
|
|
|
|
unsigned char *returnVal = new unsigned char[ length - 4 ];
|
|
memcpy( returnVal, &( message[4] ), length - 4 );
|
|
|
|
delete [] message;
|
|
|
|
return returnVal;
|
|
}
|
|
else {
|
|
delete resourceFile;
|
|
|
|
AppLog::getLog()->logPrintf(
|
|
Log::ERROR_LEVEL,
|
|
"Failed to open %s file (does not exist)\n", inResourceType );
|
|
|
|
// fix:
|
|
// why were we trying to open it anyway? Remove it from
|
|
// database for future
|
|
removeData( (char*)inResourceType, inID );
|
|
|
|
// failure
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char **loadResourceData( const char *inResourceType,
|
|
int inNumResources,
|
|
uniqueID *inIDs,
|
|
int *outLengths,
|
|
char *outCameFromNetwork ) {
|
|
|
|
unsigned char **results = new unsigned char *[ inNumResources ];
|
|
|
|
int numToFetch = 0;
|
|
|
|
// mark duplicates so that we can fill them in later w/out sending
|
|
// duplicate requests in our batch
|
|
int *duplicate = new int[ inNumResources ];
|
|
|
|
|
|
for( int i=0; i<inNumResources; i++ ) {
|
|
|
|
File *resourceFile = getResourceFile( inResourceType, inIDs[i] );
|
|
|
|
if( resourceFile->exists() ) {
|
|
|
|
// simply fetch from disk using single-function
|
|
results[i] = loadResourceData( inResourceType,
|
|
inIDs[i],
|
|
&( outLengths[i] ),
|
|
&( outCameFromNetwork[i] ) );
|
|
duplicate[i] = -1;
|
|
}
|
|
else {
|
|
// flag and deal with in batch below
|
|
|
|
// but only flag it if it is the *first* occurrence of this
|
|
// ID in the batch request (don't fetch same resource
|
|
// multiple times in same batch)
|
|
duplicate[i] = -1;
|
|
|
|
int alreadyListed = false;
|
|
for( int j=0; j<i && !alreadyListed; j++ ) {
|
|
if( equal( inIDs[j], inIDs[i] ) ) {
|
|
alreadyListed = true;
|
|
duplicate[i] = j;
|
|
}
|
|
}
|
|
results[i] = NULL;
|
|
outLengths[i] = -1;
|
|
|
|
if( !alreadyListed ) {
|
|
duplicate[i] = false;
|
|
outCameFromNetwork[i] = true;
|
|
numToFetch ++;
|
|
}
|
|
else {
|
|
// fill this in later
|
|
outCameFromNetwork[i] = false;
|
|
}
|
|
}
|
|
delete resourceFile;
|
|
}
|
|
|
|
if( numToFetch == 0 ) {
|
|
// all on disk
|
|
delete [] duplicate;
|
|
return results;
|
|
}
|
|
|
|
|
|
SimpleVector<unsigned char> messageAccum;
|
|
|
|
if( numToFetch > 255 ) {
|
|
AppLog::getLog()->logPrintf(
|
|
Log::ERROR_LEVEL,
|
|
"Error: too many resources fetched from network in one"
|
|
" batch: %d\n", numToFetch );
|
|
// return null results
|
|
delete [] duplicate;
|
|
return results;
|
|
}
|
|
|
|
AppLog::getLog()->logPrintf(
|
|
Log::INFO_LEVEL,
|
|
"Sending a batch request for %d [%s] resources\n", numToFetch,
|
|
inResourceType );
|
|
|
|
|
|
// first, send number in batch
|
|
messageAccum.push_back( (unsigned char)numToFetch );
|
|
|
|
for( int i=0; i<inNumResources; i++ ) {
|
|
if( outCameFromNetwork[i] ) {
|
|
addRequestMessage( &messageAccum, inResourceType, inIDs[i] );
|
|
}
|
|
}
|
|
|
|
unsigned char *message = messageAccum.getElementArray();
|
|
|
|
// resource requests on channel 1
|
|
connection->sendMessage( message, messageAccum.size(), 1 );
|
|
|
|
delete [] message;
|
|
|
|
|
|
// now wait for response
|
|
int length;
|
|
message = NULL;
|
|
|
|
unsigned long sec, ms;
|
|
|
|
Time::getCurrentTime( &sec, &ms );
|
|
|
|
AppLog::getLog()->logPrintf(
|
|
Log::INFO_LEVEL,
|
|
"Waiting for batch of %s resources from network (%lu:%lu)...\n",
|
|
inResourceType, sec, ms );
|
|
|
|
while( message == NULL ) {
|
|
// keep stepping until network operations are finished
|
|
char workLeft = true;
|
|
while( workLeft ) {
|
|
workLeft = connection->step();
|
|
}
|
|
|
|
message = connection->receiveMessage( &length, 1 );
|
|
|
|
if( message == NULL ) {
|
|
// wait
|
|
AppLog::trace( "sleeping\n" );
|
|
Thread::staticSleep( 50 );
|
|
}
|
|
}
|
|
|
|
Time::getCurrentTime( &sec, &ms );
|
|
AppLog::getLog()->logPrintf(
|
|
Log::INFO_LEVEL,
|
|
"...got response (%lu:%lu)\n", sec, ms );
|
|
|
|
// Got message
|
|
|
|
unsigned char *messageLeft = message;
|
|
int lengthLeft = length;
|
|
|
|
// split into parts
|
|
for( int i=0; i<inNumResources; i++ ) {
|
|
if( outCameFromNetwork[i] ) {
|
|
|
|
int numUsed;
|
|
|
|
int chunkLength = readInt( messageLeft, lengthLeft, &numUsed );
|
|
|
|
if( numUsed == -1 ) {
|
|
AppLog::getLog()->logPrintf(
|
|
Log::ERROR_LEVEL,
|
|
"Error:Failed to read resource chunk from message" );
|
|
delete [] message;
|
|
delete [] duplicate;
|
|
return results;
|
|
}
|
|
|
|
messageLeft = &( messageLeft[ numUsed ] );
|
|
lengthLeft -= numUsed;
|
|
|
|
|
|
outLengths[i] = chunkLength;
|
|
|
|
if( lengthLeft < chunkLength ) {
|
|
AppLog::getLog()->logPrintf(
|
|
Log::ERROR_LEVEL,
|
|
"Error:Failed to read resource chunk "
|
|
"from messag\n" );
|
|
delete [] message;
|
|
delete [] duplicate;
|
|
return results;
|
|
}
|
|
|
|
results[i] = new unsigned char[ chunkLength ];
|
|
memcpy( results[i], messageLeft, chunkLength );
|
|
|
|
messageLeft = &( messageLeft[ chunkLength ] );
|
|
lengthLeft -= chunkLength;
|
|
}
|
|
}
|
|
|
|
|
|
// fill in duplicates by simply copying fetched data
|
|
for( int i=0; i<inNumResources; i++ ) {
|
|
if( !outCameFromNetwork[i] && duplicate[i] != -1 ) {
|
|
|
|
int j = duplicate[i];
|
|
|
|
outLengths[i] = outLengths[j];
|
|
results[i] = new unsigned char[ outLengths[i] ];
|
|
memcpy( results[i], results[j], outLengths[i] );
|
|
}
|
|
}
|
|
|
|
|
|
delete [] message;
|
|
|
|
|
|
delete [] duplicate;
|
|
|
|
|
|
return results;
|
|
}
|
|
|
|
|
|
|
|
void saveResourceData( const char *inResourceType,
|
|
uniqueID inID,
|
|
char *inWordString,
|
|
unsigned char *inData, int inLength ) {
|
|
|
|
File *resourceDir = getResourceDir( inResourceType );
|
|
|
|
if( !resourceDir->exists() ) {
|
|
AppLog::warning( "No appropriate resourceCache subdirectory "
|
|
"directory found, creating it" );
|
|
resourceDir->makeDirectory();
|
|
}
|
|
|
|
if( resourceDir->exists() && resourceDir->isDirectory() ) {
|
|
// good
|
|
}
|
|
else {
|
|
AppLog::criticalError(
|
|
"ERROR: necessary resourceCache subdirectory not found, and "
|
|
"could not create it" );
|
|
delete resourceDir;
|
|
return;
|
|
}
|
|
delete resourceDir;
|
|
|
|
|
|
File *resourceFile = getResourceFile( inResourceType, inID );
|
|
|
|
|
|
if( resourceFile->exists() ) {
|
|
AppLog::info( "Resource collides with same ID already on disk\n" );
|
|
|
|
// but just because data file exists doesn't mean it has the same
|
|
// contents (rare chance of a hash collision... we want to overwrite
|
|
// the old data, not ignore the new data)
|
|
|
|
AppLog::detail( " Removing old search data from database\n" );
|
|
removeData( (char *)inResourceType, inID );
|
|
|
|
AppLog::detail(
|
|
" Adding replacement resource data into that ID slot\n" );
|
|
}
|
|
|
|
|
|
char *fullFileName = resourceFile->getFullFileName();
|
|
|
|
FILE *f = fopen( fullFileName, "wb" );
|
|
|
|
delete [] fullFileName;
|
|
|
|
|
|
int numWritten = fwrite( inData, 1, inLength, f );
|
|
|
|
fclose( f );
|
|
|
|
if( numWritten != inLength ) {
|
|
AppLog::getLog()->logPrintf(
|
|
Log::ERROR_LEVEL, "Failed to write to %s file\n", inResourceType );
|
|
}
|
|
|
|
delete resourceFile;
|
|
|
|
|
|
// add to the search database
|
|
addData( (char*)inResourceType, inID, inWordString );
|
|
}
|
|
|
|
|
|
|
|
void deleteResource( const char *inResourceType,
|
|
uniqueID inID ) {
|
|
File *resourceFile = getResourceFile( inResourceType, inID );
|
|
|
|
resourceFile->remove();
|
|
|
|
delete resourceFile;
|
|
|
|
removeData( (char*)inResourceType, inID );
|
|
}
|
|
|
|
|
|
|
|
|
|
char resourceExists( const char *inResourceType,
|
|
uniqueID inID ) {
|
|
|
|
File *resourceFile = getResourceFile( inResourceType, inID );
|
|
|
|
char exists = resourceFile->exists();
|
|
|
|
delete resourceFile;
|
|
|
|
return exists;
|
|
}
|