2016-06-14 00:50:05 +12:00
|
|
|
|
#include <sstream>
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
#include "tsystem.h"
|
|
|
|
|
#include "tcachedlevel.h"
|
|
|
|
|
|
|
|
|
|
#include "tcodec.h"
|
|
|
|
|
|
|
|
|
|
#include "texception.h"
|
|
|
|
|
//#include "tcachedlevel.h"
|
|
|
|
|
//#include "tcodec.h"
|
|
|
|
|
|
|
|
|
|
#include "tconvert.h"
|
|
|
|
|
|
|
|
|
|
#ifdef LINUX
|
|
|
|
|
#include "texception.h"
|
|
|
|
|
//#include "tsystem.h"
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef MACOSX
|
|
|
|
|
#include "sys/mman.h"
|
|
|
|
|
#include "sys/errno.h"
|
|
|
|
|
#endif
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
#define DWORDLONG_LO_DWORD(dwl64) ((DWORD)(dwl64))
|
|
|
|
|
#define DWORDLONG_HI_DWORD(dwl64) ((DWORD)(dwl64 >> 32))
|
|
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
//==============================================================================
|
|
|
|
|
//==============================================================================
|
|
|
|
|
// TDiskCachePersist
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class ImpPD {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ImpPD(const TFilePath &fn)
|
|
|
|
|
: m_fname(fn)
|
|
|
|
|
, m_chunkSize(0)
|
|
|
|
|
, m_currentFileSize(0)
|
|
|
|
|
, m_fileMapAddress(0)
|
|
|
|
|
, m_mapOffset(0) {
|
|
|
|
|
TFileStatus fileStatus(fn);
|
|
|
|
|
if (fileStatus.doesExist())
|
|
|
|
|
m_currentFileSize = fileStatus.getSize();
|
|
|
|
|
else
|
|
|
|
|
m_currentFileSize = ImpPD::m_defaultFileSize;
|
|
|
|
|
};
|
|
|
|
|
virtual ~ImpPD() {}
|
|
|
|
|
virtual void openFile(const TFilePath &, TINT64 fileSize) = 0;
|
|
|
|
|
virtual void setCurrentView(int pos, int &newLowPos, int &newHiPos) = 0;
|
|
|
|
|
TFilePath m_fname;
|
|
|
|
|
|
|
|
|
|
TINT64 m_chunkSize;
|
|
|
|
|
TINT64 m_currentFileSize;
|
|
|
|
|
void *m_fileMapAddress;
|
|
|
|
|
TINT64 m_mapOffset;
|
|
|
|
|
|
|
|
|
|
// quantita' espresse in byte
|
|
|
|
|
static TINT64 m_defaultFileSize;
|
|
|
|
|
TUINT32 m_viewSize;
|
|
|
|
|
TINT64 m_reallocSize;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TINT64 ImpPD::m_defaultFileSize(100 * 1024 * 1024);
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class TDiskCachePersist::Imp {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
Imp(const TFilePath &fp);
|
|
|
|
|
~Imp();
|
|
|
|
|
|
|
|
|
|
bool put(int frame, UCHAR *data, TUINT32 dataSize);
|
|
|
|
|
UCHAR *get(int pos, TUINT32 *size);
|
|
|
|
|
void openFile(const TFilePath &fp, TINT64 fileSize);
|
|
|
|
|
void setCurrentView(int frame) {
|
|
|
|
|
if (!m_force && ((m_lowFrame <= frame) && (frame < m_hiFrame)))
|
|
|
|
|
return; // la vista corrente gia' copre il frame
|
|
|
|
|
m_force = false;
|
|
|
|
|
m_impPD->setCurrentView(frame, m_lowFrame, m_hiFrame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImpPD *m_impPD;
|
|
|
|
|
|
|
|
|
|
int m_lowFrame, m_hiFrame;
|
|
|
|
|
TThread::Mutex m_mutex;
|
|
|
|
|
|
|
|
|
|
bool m_force;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#ifdef WIN32
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class ImpPDW : public ImpPD {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
private:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
string getLastErrorMessage() {
|
|
|
|
|
LPVOID lpMsgBuf;
|
|
|
|
|
|
|
|
|
|
DWORD err = GetLastError();
|
|
|
|
|
FormatMessage(
|
|
|
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
|
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
|
|
|
NULL, err,
|
|
|
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
|
|
|
(LPTSTR)&lpMsgBuf, 0, NULL);
|
|
|
|
|
|
|
|
|
|
string msg((LPCTSTR)lpMsgBuf);
|
|
|
|
|
|
|
|
|
|
// Free the buffer.
|
|
|
|
|
LocalFree(lpMsgBuf);
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ImpPDW(const TFilePath &fp);
|
|
|
|
|
~ImpPDW();
|
|
|
|
|
void openFile(const TFilePath &fname, TINT64 fileSize);
|
|
|
|
|
void setCurrentView(int pos, int &newLowPos, int &newHiPos);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
private:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
HANDLE m_hFile;
|
|
|
|
|
HANDLE m_hMap;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
SYSTEM_INFO m_systemInfo;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class ImpPDX : public ImpPD {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
private:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class TCachedLevelException : public TException {
|
|
|
|
|
static string msgFromErrorCode(int errorCode) {
|
|
|
|
|
switch (errorCode) {
|
|
|
|
|
case EBADF:
|
|
|
|
|
return " fd is not a valid file descriptor (and MAP_ANONYMOUS was "
|
|
|
|
|
"not set).";
|
|
|
|
|
break;
|
|
|
|
|
/*
|
|
|
|
|
case EACCES_MAP_PRIVATE:
|
|
|
|
|
return "Map private was requested, but fd is not open for reading. Or MAP_SHARED
|
|
|
|
|
was requested and PROT_WRITE is set, but fd is not open in read/write O_RDWR)
|
|
|
|
|
mode.";
|
|
|
|
|
break;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
*/
|
2016-06-15 18:43:10 +12:00
|
|
|
|
case EINVAL:
|
|
|
|
|
return "We don't like start or length or offset. (E.g., they are "
|
|
|
|
|
"too large, or not aligned on a PAGESIZE boundary.)";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ETXTBSY:
|
|
|
|
|
return "MAP_DENYWRITE was set but the object specified by fd is open "
|
|
|
|
|
"for writing.";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case EAGAIN:
|
|
|
|
|
return "The file has been locked, or too much memory has been "
|
|
|
|
|
"locked.";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ENOMEM:
|
|
|
|
|
return "No memory is available.";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
char *sysErr = strerror(errorCode);
|
|
|
|
|
ostringstream os;
|
|
|
|
|
os << errorCode << '\0';
|
|
|
|
|
return string(sysErr) + "(" + os.str() + ")";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
TCachedLevelException(int errorCode)
|
|
|
|
|
: TException(msgFromErrorCode(errorCode)) {}
|
|
|
|
|
~TCachedLevelException() {}
|
|
|
|
|
};
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ImpPDX(const TFilePath &fp);
|
|
|
|
|
~ImpPDX();
|
|
|
|
|
void openFile(const TFilePath &fname, TINT64 fileSize);
|
|
|
|
|
void setCurrentView(int pos, int &newLowPos, int &newHiPos);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
private:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int m_fd;
|
|
|
|
|
size_t m_pageSize;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// HIGH
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
TDiskCachePersist::TDiskCachePersist(TRasterCodec *codec, const TFilePath &fp)
|
2016-06-15 18:43:10 +12:00
|
|
|
|
: TCachePersist(codec), m_imp(new Imp(fp)) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TDiskCachePersist::~TDiskCachePersist() { delete m_imp; }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TDiskCachePersist::setFrameSize(int lx, int ly, int bpp) {
|
|
|
|
|
m_imp->m_impPD->m_chunkSize = lx * ly * (bpp >> 3) + m_codec->getHeaderSize();
|
|
|
|
|
m_imp->m_force = true;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TRasterP TDiskCachePersist::doGetRaster(int frame) {
|
|
|
|
|
TRasterP rasP;
|
|
|
|
|
TUINT32 size;
|
|
|
|
|
UCHAR *src = m_imp->get(frame, &size);
|
|
|
|
|
m_codec->decompress(src, size, rasP);
|
|
|
|
|
delete[] src;
|
|
|
|
|
return rasP;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TDiskCachePersist::doGetRaster(int frame, TRaster32P &ras) const {
|
|
|
|
|
assert(false);
|
|
|
|
|
return false;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TDiskCachePersist::doPutRaster(int frame, const TRasterP &ras) {
|
|
|
|
|
UCHAR *outData = 0;
|
|
|
|
|
TINT32 outDataSize = 0;
|
|
|
|
|
m_codec->compress(ras, 1, &outData, outDataSize);
|
|
|
|
|
bool cached = m_imp->put(frame, outData, outDataSize);
|
|
|
|
|
delete[] outData;
|
|
|
|
|
return cached;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
UCHAR *TDiskCachePersist::getRawData(int frame, TINT32 &size, int &lx,
|
|
|
|
|
int &ly) {
|
|
|
|
|
TUINT32 inDataSize;
|
|
|
|
|
UCHAR *src = m_imp->get(frame, &inDataSize);
|
|
|
|
|
return m_codec->removeHeader(src, inDataSize, size, lx, ly);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// MEDIUM
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TDiskCachePersist::Imp::Imp(const TFilePath &fp) : m_impPD(0) {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
#ifdef WIN32
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_impPD = new ImpPDW(fp);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
#else
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_impPD = new ImpPDX(fp);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
#endif
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_impPD->m_currentFileSize = TFileStatus(fp).doesExist()
|
|
|
|
|
? TFileStatus(fp).getSize()
|
|
|
|
|
: 0; // per gli arrotondamenti...
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TDiskCachePersist::Imp::~Imp() { delete m_impPD; }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// LOW
|
|
|
|
|
|
|
|
|
|
#ifdef WIN32
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ImpPDW::ImpPDW(const TFilePath &fp) : ImpPD(fp), m_hFile(0), m_hMap(0) {
|
|
|
|
|
GetSystemInfo(&m_systemInfo);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_viewSize = 100 * 1024 * 1024;
|
|
|
|
|
m_reallocSize = 250 * 1024 * 1024;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TINT64 allocUnitCount = m_reallocSize / m_systemInfo.dwAllocationGranularity;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
// rendo m_reallocSize multiplo di m_systemInfo.dwAllocationGranularity
|
|
|
|
|
if ((m_reallocSize % m_systemInfo.dwAllocationGranularity) != 0)
|
|
|
|
|
++allocUnitCount;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_reallocSize = allocUnitCount * m_systemInfo.dwAllocationGranularity;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TINT64 fileSize = m_defaultFileSize;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TFileStatus fileStatus(fp);
|
|
|
|
|
if (fileStatus.doesExist()) fileSize = fileStatus.getSize();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
try {
|
|
|
|
|
openFile(fp, fileSize);
|
|
|
|
|
} catch (TException &e) {
|
|
|
|
|
m_currentFileSize = 0;
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_currentFileSize = fileSize;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ImpPDW::~ImpPDW() {
|
|
|
|
|
if (m_fileMapAddress) UnmapViewOfFile(m_fileMapAddress);
|
|
|
|
|
CloseHandle(m_hMap);
|
|
|
|
|
CloseHandle(m_hFile);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void ImpPDW::openFile(const TFilePath &fname, TINT64 fileSize) {
|
|
|
|
|
DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
|
|
|
|
|
DWORD dwShareMode = 0; // dwShareMode == 0 --> accesso esclusivo
|
|
|
|
|
|
|
|
|
|
// lpSecurityAttributes == NULL --> l'handle non puo' essere
|
|
|
|
|
// ereditato da processi figli
|
|
|
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL;
|
|
|
|
|
|
|
|
|
|
DWORD dwCreationDisposition = OPEN_ALWAYS; // CREATE_ALWAYS;
|
|
|
|
|
DWORD dwFlagsAndAttributes =
|
|
|
|
|
FILE_FLAG_SEQUENTIAL_SCAN; // FILE_ATTRIBUTE_NORMAL;//
|
|
|
|
|
|
|
|
|
|
HANDLE hTemplateFile = NULL;
|
|
|
|
|
|
|
|
|
|
m_hFile = CreateFileW(fname.getWideString().c_str(), // file name
|
|
|
|
|
dwDesiredAccess, // access mode
|
|
|
|
|
dwShareMode, // share mode
|
|
|
|
|
NULL, // SD
|
|
|
|
|
dwCreationDisposition, // how to create
|
|
|
|
|
dwFlagsAndAttributes, // file attributes
|
|
|
|
|
hTemplateFile // handle to template file
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (m_hFile == INVALID_HANDLE_VALUE) {
|
|
|
|
|
string errMsg = getLastErrorMessage();
|
|
|
|
|
throw TException(wstring(L"Unable to open cache file: ") +
|
|
|
|
|
fname.getWideString() + L"\n" + toWideString(errMsg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD flProtect = PAGE_READWRITE;
|
|
|
|
|
DWORD dwMaximumSizeHigh = DWORDLONG_HI_DWORD(fileSize);
|
|
|
|
|
DWORD dwMaximumSizeLow = DWORDLONG_LO_DWORD(fileSize);
|
|
|
|
|
LPCTSTR lpName = NULL; // l'oggetto non ha nome
|
|
|
|
|
|
|
|
|
|
m_hMap = CreateFileMapping(m_hFile, // handle to file
|
|
|
|
|
NULL, // security
|
|
|
|
|
flProtect, // protection
|
|
|
|
|
dwMaximumSizeHigh, // high-order DWORD of size
|
|
|
|
|
dwMaximumSizeLow, // low-order DWORD of size
|
|
|
|
|
lpName // object name
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (m_hMap == NULL) {
|
|
|
|
|
string errMsg = getLastErrorMessage();
|
|
|
|
|
CloseHandle(m_hFile);
|
|
|
|
|
m_hFile = 0;
|
|
|
|
|
throw TException("Unable to create file mapping. " + errMsg);
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void ImpPDW::setCurrentView(int frame, int &newLowFrame, int &newHiFrame) {
|
|
|
|
|
if (m_fileMapAddress) UnmapViewOfFile(m_fileMapAddress);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
newLowFrame = frame;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
newHiFrame = newLowFrame + TINT32(m_viewSize / m_chunkSize);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
DWORD allocGranularity = m_systemInfo.dwAllocationGranularity;
|
|
|
|
|
TINT64 viewOffset =
|
|
|
|
|
(TINT64(newLowFrame * m_chunkSize) / allocGranularity) * allocGranularity;
|
|
|
|
|
m_mapOffset = newLowFrame * m_chunkSize - viewOffset;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TINT64 fileSize = newHiFrame * m_chunkSize;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
if ((fileSize > m_currentFileSize) || !m_hMap) // devo riallocare!
|
|
|
|
|
{
|
|
|
|
|
CloseHandle(m_hMap);
|
|
|
|
|
m_hMap = 0;
|
|
|
|
|
CloseHandle(m_hFile);
|
|
|
|
|
m_hFile = 0;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TINT64 allocUnitCount = fileSize / m_reallocSize;
|
|
|
|
|
// rendo fileSize multiplo di m_reallocSize
|
|
|
|
|
if ((fileSize % m_reallocSize) != 0) ++allocUnitCount;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
fileSize = allocUnitCount * m_reallocSize;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
openFile(m_fname, fileSize);
|
|
|
|
|
m_currentFileSize = fileSize;
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
DWORD dwDesiredAccess = FILE_MAP_WRITE;
|
|
|
|
|
m_fileMapAddress = MapViewOfFile(
|
|
|
|
|
m_hMap, // handle to file-mapping object
|
|
|
|
|
dwDesiredAccess, // access mode: Write permission
|
|
|
|
|
DWORDLONG_HI_DWORD(
|
|
|
|
|
viewOffset), // high-order DWORD of offset: Max. object size.
|
|
|
|
|
DWORDLONG_LO_DWORD(
|
|
|
|
|
viewOffset), // low-order DWORD of offset: Size of hFile.
|
|
|
|
|
m_viewSize); // number of bytes to map
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
if (m_fileMapAddress == NULL) {
|
|
|
|
|
string errMsg = getLastErrorMessage();
|
|
|
|
|
CloseHandle(m_hMap);
|
|
|
|
|
m_hMap = 0;
|
|
|
|
|
CloseHandle(m_hFile);
|
|
|
|
|
m_hFile = 0;
|
|
|
|
|
|
|
|
|
|
throw TException("Unable to memory map cache file. " + errMsg);
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ImpPDX::ImpPDX(const TFilePath &fp) : ImpPD(fp), m_fd(-1) {
|
|
|
|
|
// std::cout << "cache file " << toString(m_fname.getFullPath()) << std::endl;
|
|
|
|
|
m_pageSize = getpagesize();
|
|
|
|
|
openFile(m_fname, 0);
|
|
|
|
|
assert(m_fd >= 0);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ImpPDX::~ImpPDX() {
|
|
|
|
|
if (m_fileMapAddress) munmap(m_fileMapAddress, m_viewSize);
|
|
|
|
|
close(m_fd);
|
|
|
|
|
m_fd = 0;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void ImpPDX::openFile(const TFilePath &fname, TINT64 fileSize) {
|
|
|
|
|
assert(0);
|
|
|
|
|
/*
|
2016-03-19 06:57:51 +13:00
|
|
|
|
string fn(toString(fname.getWideString()));
|
|
|
|
|
std::cout << "open " << fn << std::endl;
|
|
|
|
|
m_fd = open(fn.c_str(), O_RDWR|O_CREAT, 00666);
|
|
|
|
|
assert(m_fd >=0);
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void ImpPDX::setCurrentView(int pos, int &newLowPos, int &newHiPos) {
|
|
|
|
|
newLowPos = pos;
|
|
|
|
|
newHiPos = newLowPos + (m_viewSize / m_chunkSize);
|
|
|
|
|
|
|
|
|
|
assert(m_fd >= 0);
|
|
|
|
|
if (m_fileMapAddress) // previous view...
|
|
|
|
|
if (munmap(m_fileMapAddress, m_viewSize) != 0)
|
|
|
|
|
throw TCachedLevelException(errno);
|
|
|
|
|
void *start = 0;
|
|
|
|
|
int flags = MAP_SHARED;
|
|
|
|
|
size_t viewOffset = ((newLowPos * m_chunkSize) / m_pageSize) * m_pageSize;
|
|
|
|
|
m_mapOffset = newLowPos * m_chunkSize - viewOffset;
|
|
|
|
|
|
|
|
|
|
assert(!"controllare le dimensioni");
|
|
|
|
|
unsigned long lastByte =
|
|
|
|
|
(unsigned long)(((newHiPos * m_chunkSize) / (double)m_pageSize + 0.5) *
|
|
|
|
|
m_pageSize);
|
|
|
|
|
|
|
|
|
|
if (lastByte > m_currentFileSize) // devo riallocare!
|
|
|
|
|
{
|
|
|
|
|
unsigned long bu =
|
|
|
|
|
(unsigned long)((lastByte / (double)m_reallocSize + 0.5) *
|
|
|
|
|
m_reallocSize);
|
|
|
|
|
bu = (unsigned long)((bu / (double)m_pageSize + 0.5) * m_pageSize);
|
|
|
|
|
// m_maxFileSize = tmax(m_maxFileSize + m_reallocFileSize, lastByte);
|
|
|
|
|
m_currentFileSize += bu;
|
|
|
|
|
|
|
|
|
|
std::cout << "new cache size " << m_currentFileSize << std::endl;
|
|
|
|
|
if (lseek(m_fd, m_currentFileSize, SEEK_SET) == -1)
|
|
|
|
|
throw TCachedLevelException(errno);
|
|
|
|
|
if (write(m_fd, "", 1) == -1) throw TCachedLevelException(errno);
|
|
|
|
|
if (ftruncate(m_fd, m_currentFileSize) == -1)
|
|
|
|
|
throw TCachedLevelException(errno);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_fileMapAddress =
|
|
|
|
|
mmap(start, m_viewSize, PROT_READ | PROT_WRITE, flags, m_fd, viewOffset);
|
|
|
|
|
if (m_fileMapAddress == (void *)-1) throw TCachedLevelException(errno);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
#ifndef WIN32
|
|
|
|
|
#define ULONGLONG unsigned long long
|
|
|
|
|
#endif
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TDiskCachePersist::Imp::put(int frame, UCHAR *data, TUINT32 dataSize) {
|
|
|
|
|
if (dataSize != m_impPD->m_chunkSize) return false;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TThread::ScopedLock sl(m_mutex);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
setCurrentView(frame);
|
|
|
|
|
ULONGLONG offset = (frame - m_lowFrame) * m_impPD->m_chunkSize;
|
|
|
|
|
UCHAR *dst = (UCHAR *)m_impPD->m_fileMapAddress + offset;
|
|
|
|
|
memcpy(dst + m_impPD->m_mapOffset, data, dataSize);
|
|
|
|
|
return true;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
UCHAR *TDiskCachePersist::Imp::get(int pos, TUINT32 *size) {
|
|
|
|
|
UCHAR *ret = new UCHAR[TINT32(m_impPD->m_chunkSize)];
|
|
|
|
|
|
|
|
|
|
TThread::ScopedLock sl(m_mutex);
|
|
|
|
|
setCurrentView(pos);
|
|
|
|
|
ULONGLONG offset = (pos - m_lowFrame) * m_impPD->m_chunkSize;
|
|
|
|
|
UCHAR *src =
|
|
|
|
|
(UCHAR *)m_impPD->m_fileMapAddress + offset + m_impPD->m_mapOffset;
|
|
|
|
|
memcpy(ret, src, TINT32(m_impPD->m_chunkSize));
|
|
|
|
|
*size = TUINT32(m_impPD->m_chunkSize);
|
|
|
|
|
return ret;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// TRasterCache
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class TRasterCache::Data {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
Data(TCachePersist *cp)
|
|
|
|
|
: m_cp(cp)
|
|
|
|
|
, m_size(0, 0)
|
|
|
|
|
, m_prefetchEnabled(false)
|
|
|
|
|
, m_prefetchedFrame(-1)
|
|
|
|
|
, m_frameToPrefetch(-1)
|
|
|
|
|
, m_preLoader(1, true) {}
|
|
|
|
|
~Data() {}
|
|
|
|
|
|
|
|
|
|
class FrameData {
|
|
|
|
|
public:
|
|
|
|
|
bool m_valid;
|
|
|
|
|
// int m_size; // dimensione in byte del raster codificato
|
|
|
|
|
~FrameData() {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool isFrameCached(int frame) const;
|
|
|
|
|
|
|
|
|
|
TDimension m_size; // dimensioni dei raster in cache
|
|
|
|
|
int m_bpp;
|
|
|
|
|
|
|
|
|
|
typedef map<int, FrameData> Status;
|
|
|
|
|
Status m_status;
|
|
|
|
|
TCachePersist *m_cp;
|
|
|
|
|
TThread::Mutex m_accessMutex;
|
|
|
|
|
|
|
|
|
|
TThread::Executor m_preLoader;
|
|
|
|
|
bool m_prefetchEnabled;
|
|
|
|
|
int m_prefetchedFrame;
|
|
|
|
|
int m_frameToPrefetch;
|
|
|
|
|
TRasterP m_prefetchedRas;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TRasterCache::Data::isFrameCached(int frame) const {
|
|
|
|
|
// volutamente senza ScopedLock
|
|
|
|
|
Data::Status::const_iterator it = m_status.find(frame);
|
|
|
|
|
if (it == m_status.end()) return false;
|
|
|
|
|
Data::FrameData fd = it->second;
|
|
|
|
|
return fd.m_valid;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
namespace {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class Load : public TThread::Runnable {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
Load(int frameToPrefetch, TCachePersist *cp, int &prefetchedFrame,
|
|
|
|
|
TRasterP &prefetchedRas)
|
|
|
|
|
: m_frame(frameToPrefetch)
|
|
|
|
|
, m_cp(cp)
|
|
|
|
|
, m_prefetchedFrame(prefetchedFrame)
|
|
|
|
|
, m_prefetchedRas(prefetchedRas) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void run();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
private:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int m_frame;
|
|
|
|
|
TCachePersist *m_cp;
|
|
|
|
|
int &m_prefetchedFrame;
|
|
|
|
|
TRasterP &m_prefetchedRas;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void Load::run() {
|
|
|
|
|
m_prefetchedRas = m_cp->doGetRaster(m_frame);
|
|
|
|
|
m_prefetchedFrame = m_frame;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TRasterCache::TRasterCache(TCachePersist *cp) : m_data(new Data(cp)) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TRasterCache::~TRasterCache() { delete m_data; }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TRasterCache::setMode(const TDimension &size, int bpp) {
|
|
|
|
|
TThread::ScopedLock sl(m_data->m_accessMutex);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_data->m_size = size; // dimensioni dei raster in cache
|
|
|
|
|
m_data->m_bpp = bpp;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_data->m_cp->setFrameSize(size.lx, size.ly, bpp);
|
|
|
|
|
invalidate();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TRasterCache::getMode(TDimension &size, int &bpp) const {
|
|
|
|
|
size = m_data->m_size; // dimensioni dei raster in cache
|
|
|
|
|
bpp = m_data->m_bpp;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TRasterP TRasterCache::getRaster(int frame) const {
|
|
|
|
|
TThread::ScopedLock sl(m_data->m_accessMutex);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
if (m_data->m_prefetchEnabled) {
|
|
|
|
|
/*
|
|
|
|
|
if (frame == m_data->m_frameToPrefetch)
|
|
|
|
|
m_data->m_preLoader.wait();
|
|
|
|
|
else
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
m_data->m_preLoader.clear();
|
|
|
|
|
// m_data->m_preLoader.cancel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRasterP ras;
|
|
|
|
|
if (frame == m_data->m_prefetchedFrame)
|
|
|
|
|
ras = m_data->m_prefetchedRas;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
else
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ras = m_data->m_cp->doGetRaster(frame);
|
|
|
|
|
|
|
|
|
|
if (isFrameCached(frame + 1)) {
|
|
|
|
|
// il frame successivo a quello richiesto e' nella cache
|
|
|
|
|
// -> avvia il prefetch di tale raster
|
|
|
|
|
m_data->m_frameToPrefetch = frame + 1;
|
|
|
|
|
m_data->m_preLoader.addTask(
|
|
|
|
|
new Load(m_data->m_frameToPrefetch, m_data->m_cp,
|
|
|
|
|
m_data->m_prefetchedFrame, m_data->m_prefetchedRas));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ras;
|
|
|
|
|
} else {
|
|
|
|
|
return m_data->m_cp->doGetRaster(frame);
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TRasterCache::getRaster(int frame, TRaster32P &ras) const {
|
|
|
|
|
TThread::ScopedLock sl(m_data->m_accessMutex);
|
|
|
|
|
if (m_data->isFrameCached(frame)) {
|
|
|
|
|
bool rc = m_data->m_cp->doGetRaster(frame, ras);
|
|
|
|
|
assert(rc);
|
|
|
|
|
return true;
|
|
|
|
|
} else
|
|
|
|
|
return false;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TRasterCache::putRaster(int frame, const TRasterP &ras) {
|
|
|
|
|
TThread::ScopedLock sl(m_data->m_accessMutex);
|
|
|
|
|
Data::Status::iterator it = m_data->m_status.find(frame);
|
|
|
|
|
bool cached = false;
|
|
|
|
|
try {
|
|
|
|
|
cached = m_data->m_cp->doPutRaster(frame, ras);
|
|
|
|
|
} catch (TException &e) {
|
|
|
|
|
if (it != m_data->m_status.end()) {
|
|
|
|
|
Data::FrameData fd;
|
|
|
|
|
fd.m_valid = false;
|
|
|
|
|
m_data->m_status[frame] = fd;
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
if (cached) {
|
|
|
|
|
Data::FrameData fd;
|
|
|
|
|
fd.m_valid = true;
|
|
|
|
|
m_data->m_status[frame] = fd;
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
UCHAR *TRasterCache::getRawData(int frame, TINT32 &size, int &lx,
|
|
|
|
|
int &ly) const {
|
|
|
|
|
return m_data->m_cp->getRawData(frame, size, lx, ly);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TRasterCache::isFrameCached(int frame) const {
|
|
|
|
|
TThread::ScopedLock sl(m_data->m_accessMutex);
|
|
|
|
|
return m_data->isFrameCached(frame);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TRasterCache::invalidate() {
|
|
|
|
|
TThread::ScopedLock sl(m_data->m_accessMutex);
|
|
|
|
|
m_data->m_status.clear();
|
|
|
|
|
m_data->m_cp->onInvalidate();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TRasterCache::invalidate(int startFrame, int endFrame) {
|
|
|
|
|
assert(startFrame <= endFrame);
|
|
|
|
|
TThread::ScopedLock sl(m_data->m_accessMutex);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
Data::Status::iterator low = m_data->m_status.lower_bound(startFrame);
|
|
|
|
|
Data::Status::iterator hi = m_data->m_status.upper_bound(endFrame);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int count = m_data->m_status.size();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
if (low != m_data->m_status.end() && hi != m_data->m_status.end()) {
|
|
|
|
|
int ll = low->first;
|
|
|
|
|
int hh = hi->first;
|
|
|
|
|
assert(ll <= hh);
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
#endif
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
if (low != m_data->m_status.end()) {
|
|
|
|
|
m_data->m_status.erase(low, hi);
|
|
|
|
|
m_data->m_cp->onInvalidate(startFrame, endFrame);
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TRasterCache::enablePrefetch(bool newState) {
|
|
|
|
|
m_data->m_prefetchEnabled = newState;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TRasterCache::isPrefetchEnabled() const {
|
|
|
|
|
return m_data->m_prefetchEnabled;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TUINT64 TRasterCache::getUsedSpace() { return m_data->m_cp->getUsedSpace(); }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
//==============================================================================
|
|
|
|
|
//==============================================================================
|
|
|
|
|
// TRamCachePersist
|
|
|
|
|
//------------------------------------------------------------------------------
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class TRamCachePersist::Imp {
|
|
|
|
|
friend class TRamCachePersist;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
Imp() : m_cacheSize(0), m_chunks() {}
|
|
|
|
|
~Imp() {
|
|
|
|
|
for (CompressedChunks::iterator it = m_chunks.begin(); it != m_chunks.end();
|
|
|
|
|
++it) {
|
|
|
|
|
CompressedChunk *cc = it->second;
|
|
|
|
|
m_cacheSize -= cc->m_size;
|
|
|
|
|
delete cc;
|
|
|
|
|
}
|
|
|
|
|
assert(m_cacheSize == 0); // se m_cacheSize > 0 mi sono perso qualche chunk
|
|
|
|
|
// se m_cacheSize < 0 ho liberato 2 volte qualche chunk
|
|
|
|
|
m_chunks.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class CompressedChunk {
|
|
|
|
|
public:
|
|
|
|
|
CompressedChunk(UCHAR *buffer, int size) : m_buffer(buffer), m_size(size) {}
|
|
|
|
|
~CompressedChunk() { delete[] m_buffer; }
|
|
|
|
|
UCHAR *m_buffer;
|
|
|
|
|
int m_size;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
typedef map<int, CompressedChunk *> CompressedChunks;
|
|
|
|
|
CompressedChunks m_chunks;
|
|
|
|
|
TUINT64 m_cacheSize;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TRamCachePersist::TRamCachePersist(TRasterCodec *codec)
|
2016-06-15 18:43:10 +12:00
|
|
|
|
: TCachePersist(codec), m_imp(new Imp) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TRamCachePersist::~TRamCachePersist() { delete m_imp; }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
TRasterP TRamCachePersist::doGetRaster(int frame)
|
2016-06-15 18:43:10 +12:00
|
|
|
|
// void TRamCachePersist::doGetRaster(int frame, const TRasterP &ras)
|
2016-03-19 06:57:51 +13:00
|
|
|
|
{
|
2016-06-15 18:43:10 +12:00
|
|
|
|
Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
|
|
|
|
|
if (it == m_imp->m_chunks.end()) return TRasterP();
|
|
|
|
|
Imp::CompressedChunk *cc = it->second;
|
|
|
|
|
assert(cc);
|
|
|
|
|
TRasterP rasP;
|
|
|
|
|
m_codec->decompress(cc->m_buffer, cc->m_size, rasP);
|
|
|
|
|
return rasP;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TRamCachePersist::doGetRaster(int frame, TRaster32P &ras) const {
|
|
|
|
|
Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
|
|
|
|
|
if (it == m_imp->m_chunks.end()) return false;
|
|
|
|
|
Imp::CompressedChunk *cc = it->second;
|
|
|
|
|
assert(cc);
|
|
|
|
|
TRasterP rasP(ras);
|
|
|
|
|
m_codec->decompress(cc->m_buffer, cc->m_size, rasP);
|
|
|
|
|
return true;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TRamCachePersist::doPutRaster(int frame, const TRasterP &ras) {
|
|
|
|
|
Imp::CompressedChunks::iterator it = m_imp->m_chunks.find(frame);
|
|
|
|
|
if (it != m_imp->m_chunks.end()) {
|
|
|
|
|
m_imp->m_cacheSize -= it->second->m_size;
|
|
|
|
|
delete it->second;
|
|
|
|
|
m_imp->m_chunks.erase(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UCHAR *outData = 0;
|
|
|
|
|
TINT32 outDataSize = 0;
|
|
|
|
|
m_codec->compress(ras, 1, &outData, outDataSize);
|
|
|
|
|
m_imp->m_cacheSize += outDataSize;
|
|
|
|
|
Imp::CompressedChunk *cc = new Imp::CompressedChunk(outData, outDataSize);
|
|
|
|
|
m_imp->m_chunks.insert(Imp::CompressedChunks::value_type(frame, cc));
|
|
|
|
|
return true;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TRamCachePersist::onInvalidate() {
|
|
|
|
|
for (Imp::CompressedChunks::iterator it = m_imp->m_chunks.begin();
|
|
|
|
|
it != m_imp->m_chunks.end(); ++it) {
|
|
|
|
|
Imp::CompressedChunk *cc = it->second;
|
|
|
|
|
m_imp->m_cacheSize -= cc->m_size;
|
|
|
|
|
delete cc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_imp->m_chunks.clear();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TRamCachePersist::onInvalidate(int startFrame,
|
|
|
|
|
int endFrame) { // ottimizzabile
|
|
|
|
|
assert(startFrame <= endFrame);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
for (int frame = startFrame; frame <= endFrame; ++frame) {
|
|
|
|
|
Imp::CompressedChunks::iterator it = m_imp->m_chunks.find(frame);
|
|
|
|
|
if (it != m_imp->m_chunks.end()) {
|
|
|
|
|
m_imp->m_cacheSize -= it->second->m_size;
|
|
|
|
|
delete it->second;
|
|
|
|
|
m_imp->m_chunks.erase(it);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
UCHAR *TRamCachePersist::getRawData(int frame, TINT32 &size, int &lx, int &ly) {
|
|
|
|
|
Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
|
|
|
|
|
if (it == m_imp->m_chunks.end()) return 0;
|
|
|
|
|
Imp::CompressedChunk *cc = it->second;
|
|
|
|
|
assert(cc);
|
|
|
|
|
return m_codec->removeHeader(cc->m_buffer, cc->m_size, size, lx, ly);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TUINT64 TRamCachePersist::getUsedSpace() { return m_imp->m_cacheSize; }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TDiskCachePersist::onInvalidate() {
|
|
|
|
|
// m_imp->m_chunkSize = 0;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TDiskCachePersist::onInvalidate(int startFrame, int endFrame) {
|
|
|
|
|
// m_imp->m_chunkSize = 0;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TUINT64 TDiskCachePersist::getUsedSpace() {
|
|
|
|
|
assert(0);
|
|
|
|
|
return 0;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
//==============================================================================
|
|
|
|
|
//==============================================================================
|
|
|
|
|
//
|
|
|
|
|
// TDiskCachePersist2
|
|
|
|
|
//
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
#ifdef WIN32
|
2016-06-15 18:43:10 +12:00
|
|
|
|
namespace {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class ZFile {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ZFile(const TFilePath &fp, bool directIO, bool asyncIO);
|
|
|
|
|
~ZFile();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void open();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int read(BYTE buf[], int size, TINT64 qwOffset) const;
|
|
|
|
|
int write(BYTE buf[], int size, TINT64 qwOffset) const;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void waitForAsyncIOCompletion() const;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TFilePath getFilePath() const { return m_filepath; }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int getBytesPerSector() const { return m_bytesPerSector; }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
static void CALLBACK FileIOCompletionRoutine(DWORD errCode,
|
|
|
|
|
DWORD byteTransferred,
|
|
|
|
|
LPOVERLAPPED overlapped);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
private:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TFilePath m_filepath;
|
|
|
|
|
bool m_directIO;
|
|
|
|
|
bool m_asyncIO;
|
|
|
|
|
DWORD m_bytesPerSector;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
HANDLE m_fileHandle;
|
|
|
|
|
HANDLE m_writeNotPending;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
ZFile::ZFile(const TFilePath &fp, bool directIO, bool asyncIO)
|
2016-06-15 18:43:10 +12:00
|
|
|
|
: m_filepath(fp)
|
|
|
|
|
, m_directIO(directIO)
|
|
|
|
|
, m_asyncIO(asyncIO)
|
|
|
|
|
, m_fileHandle(0)
|
|
|
|
|
, m_writeNotPending(0) {
|
|
|
|
|
DWORD sectorsPerCluster;
|
|
|
|
|
DWORD numberOfFreeClusters;
|
|
|
|
|
DWORD totalNumberOfClusters;
|
|
|
|
|
|
|
|
|
|
TFilePathSet disks = TSystem::getDisks();
|
|
|
|
|
|
|
|
|
|
TFilePath disk = fp;
|
|
|
|
|
while (std::find(disks.begin(), disks.end(), disk) == disks.end())
|
|
|
|
|
disk = disk.getParentDir();
|
|
|
|
|
|
|
|
|
|
BOOL ret = GetDiskFreeSpaceW(disk.getWideString().c_str(), // root path
|
|
|
|
|
§orsPerCluster, // sectors per cluster
|
|
|
|
|
&m_bytesPerSector, // bytes per sector
|
|
|
|
|
&numberOfFreeClusters, // free clusters
|
|
|
|
|
&totalNumberOfClusters // total clusters
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (m_asyncIO) m_writeNotPending = CreateEvent(NULL, TRUE, TRUE, NULL);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ZFile::~ZFile() {
|
|
|
|
|
if (m_fileHandle) CloseHandle(m_fileHandle);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
if (m_writeNotPending) CloseHandle(m_writeNotPending);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void ZFile::open() {
|
|
|
|
|
DWORD flagsAndAttributes = 0;
|
|
|
|
|
flagsAndAttributes = m_directIO ? FILE_FLAG_NO_BUFFERING : 0UL;
|
|
|
|
|
flagsAndAttributes |= m_asyncIO ? FILE_FLAG_OVERLAPPED : 0UL;
|
|
|
|
|
|
|
|
|
|
// Open the file for write access.
|
|
|
|
|
m_fileHandle =
|
|
|
|
|
CreateFileW(m_filepath.getWideString().c_str(),
|
|
|
|
|
GENERIC_READ | GENERIC_WRITE, // Read/Write access
|
|
|
|
|
0, // no sharing allowed
|
|
|
|
|
NULL, // no security
|
|
|
|
|
OPEN_ALWAYS, // open it or create new if it doesn't exist
|
|
|
|
|
flagsAndAttributes,
|
|
|
|
|
NULL); // ignored
|
|
|
|
|
|
|
|
|
|
if (m_fileHandle == INVALID_HANDLE_VALUE) {
|
|
|
|
|
m_fileHandle = 0;
|
|
|
|
|
|
|
|
|
|
char errorMessage[2048];
|
|
|
|
|
|
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
|
|
|
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage, 2048,
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
throw TException(errorMessage);
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int ZFile::read(BYTE buf[], int size, TINT64 qwOffset) const {
|
|
|
|
|
assert(size % m_bytesPerSector == 0);
|
|
|
|
|
assert(qwOffset % m_bytesPerSector == 0);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
char msg[2048] = "";
|
|
|
|
|
unsigned long bytesToRead; // Padded number of bytes to read.
|
|
|
|
|
unsigned long bytesRead; // count of bytes actually read
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
OVERLAPPED overLapped;
|
|
|
|
|
memset(&overLapped, 0, sizeof(overLapped));
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
#define DWORDLONG_LO_DWORD(dwl64) ((DWORD)(dwl64))
|
|
|
|
|
#define DWORDLONG_HI_DWORD(dwl64) ((DWORD)(dwl64 >> 32))
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
// set the overlapped stucture with the offsets
|
|
|
|
|
overLapped.Offset = DWORDLONG_LO_DWORD(qwOffset);
|
|
|
|
|
overLapped.OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);
|
|
|
|
|
|
|
|
|
|
if (m_asyncIO) {
|
|
|
|
|
overLapped.hEvent = CreateEvent(NULL, // SD
|
|
|
|
|
TRUE, // manual reset
|
|
|
|
|
FALSE, // initial state is not signaled
|
|
|
|
|
NULL); // object name
|
|
|
|
|
} else
|
|
|
|
|
overLapped.hEvent = NULL;
|
|
|
|
|
|
|
|
|
|
bytesToRead = size;
|
|
|
|
|
|
|
|
|
|
// Read a bunch of bytes and store in buf
|
|
|
|
|
int result = ReadFile(m_fileHandle, // file handle
|
|
|
|
|
(void *)buf, // buffer to store data
|
|
|
|
|
bytesToRead, // num bytes to read
|
|
|
|
|
&bytesRead, // bytes read
|
|
|
|
|
&overLapped); // stucture for file offsets
|
|
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
|
if (m_asyncIO && ERROR_IO_PENDING == error) {
|
|
|
|
|
if (!GetOverlappedResult(m_fileHandle, &overLapped, &bytesRead, TRUE)) {
|
|
|
|
|
char errorMessage[2048];
|
|
|
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
|
|
|
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage,
|
|
|
|
|
2048, NULL);
|
|
|
|
|
|
|
|
|
|
throw TException(errorMessage);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
char errorMessage[2048];
|
|
|
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
|
|
|
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage,
|
|
|
|
|
2048, NULL);
|
|
|
|
|
|
|
|
|
|
throw TException(errorMessage);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bytesRead;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int ZFile::write(BYTE buf[], int size, TINT64 qwOffset) const {
|
|
|
|
|
assert(size % m_bytesPerSector == 0);
|
|
|
|
|
assert(qwOffset % m_bytesPerSector == 0);
|
|
|
|
|
|
|
|
|
|
char msg[2048] = "";
|
|
|
|
|
unsigned long bytesToWrite; // Padded number of bytes to write.
|
|
|
|
|
unsigned long bytesWritten = 0; // count of bytes actually writtten
|
|
|
|
|
|
|
|
|
|
int result;
|
|
|
|
|
|
|
|
|
|
if (m_asyncIO) {
|
|
|
|
|
OVERLAPPED *overLapped = new OVERLAPPED;
|
|
|
|
|
memset(overLapped, 0, sizeof(OVERLAPPED));
|
|
|
|
|
|
|
|
|
|
// set the overlapped stucture with the offsets
|
|
|
|
|
overLapped->Offset = DWORDLONG_LO_DWORD(qwOffset);
|
|
|
|
|
overLapped->OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);
|
|
|
|
|
overLapped->hEvent = NULL;
|
|
|
|
|
|
|
|
|
|
bytesToWrite = size;
|
|
|
|
|
|
|
|
|
|
result = WriteFileEx(m_fileHandle, // file handle
|
|
|
|
|
(void *)buf, // data buffer
|
|
|
|
|
bytesToWrite, // num bytes to write
|
|
|
|
|
overLapped, // stucture for file offsets
|
|
|
|
|
&ZFile::FileIOCompletionRoutine);
|
|
|
|
|
|
|
|
|
|
ResetEvent(m_writeNotPending);
|
|
|
|
|
} else {
|
|
|
|
|
OVERLAPPED overLapped;
|
|
|
|
|
memset(&overLapped, 0, sizeof(overLapped));
|
|
|
|
|
|
|
|
|
|
// set the overlapped stucture with the offsets
|
|
|
|
|
overLapped.Offset = DWORDLONG_LO_DWORD(qwOffset);
|
|
|
|
|
overLapped.OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);
|
|
|
|
|
overLapped.hEvent = NULL;
|
|
|
|
|
|
|
|
|
|
bytesToWrite = size;
|
|
|
|
|
|
|
|
|
|
result = WriteFile(m_fileHandle, // file handle
|
|
|
|
|
(void *)buf, // data buffer
|
|
|
|
|
bytesToWrite, // num bytes to read
|
|
|
|
|
&bytesWritten, // bytes read
|
|
|
|
|
&overLapped); // stucture for file offsets
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
|
char errorMessage[2048];
|
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
|
|
|
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage, 2048,
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
throw TException(errorMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bytesWritten;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void ZFile::waitForAsyncIOCompletion() const {
|
|
|
|
|
if (m_asyncIO) {
|
|
|
|
|
WaitForSingleObjectEx(m_writeNotPending, INFINITE, TRUE);
|
|
|
|
|
SetEvent(m_writeNotPending);
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void CALLBACK ZFile::FileIOCompletionRoutine(DWORD errCode,
|
|
|
|
|
DWORD byteTransferred,
|
|
|
|
|
LPOVERLAPPED overlapped) {
|
|
|
|
|
delete overlapped;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class BufferQueue {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class Item {
|
|
|
|
|
public:
|
|
|
|
|
Item(int frame, UCHAR *buffer, int bufferSize, int chunkSize)
|
|
|
|
|
: m_frame(frame)
|
|
|
|
|
, m_buffer(buffer)
|
|
|
|
|
, m_bufferSize(bufferSize)
|
|
|
|
|
, m_chunkSize(chunkSize) {}
|
|
|
|
|
|
|
|
|
|
int m_frame;
|
|
|
|
|
UCHAR *m_buffer;
|
|
|
|
|
int m_bufferSize;
|
|
|
|
|
TINT64 m_chunkSize;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BufferQueue(int capacity, int allocUnit)
|
|
|
|
|
: m_capacity(capacity)
|
|
|
|
|
, m_allocUnit(allocUnit)
|
|
|
|
|
, m_notEmpty()
|
|
|
|
|
, m_notFull()
|
|
|
|
|
, m_mutex()
|
|
|
|
|
, m_bufferCount(0)
|
|
|
|
|
, m_nextPutItem(0)
|
|
|
|
|
, m_nextGetItem(0) {
|
|
|
|
|
for (int i = 0; i < m_capacity; ++i)
|
|
|
|
|
m_items.push_back(Item(-1, (UCHAR *)0, 0, 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~BufferQueue() {
|
|
|
|
|
for (int i = 0; i < m_capacity; ++i) delete[] m_items[i].m_buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void put(int frame, UCHAR *buffer, int bufferSize, int chunkSize) {
|
|
|
|
|
TThread::ScopedLock sl(m_mutex);
|
|
|
|
|
while (m_bufferCount == m_capacity) m_notFull.wait(sl);
|
|
|
|
|
|
|
|
|
|
if (m_items[m_nextPutItem].m_chunkSize != chunkSize) {
|
|
|
|
|
delete[] m_items[m_nextPutItem].m_buffer;
|
|
|
|
|
m_items[m_nextPutItem].m_buffer = new UCHAR[chunkSize];
|
|
|
|
|
m_items[m_nextPutItem].m_chunkSize = chunkSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memcpy(m_items[m_nextPutItem].m_buffer, buffer, bufferSize);
|
|
|
|
|
m_items[m_nextPutItem].m_frame = frame;
|
|
|
|
|
|
|
|
|
|
m_nextPutItem = (m_nextPutItem + 1) % m_capacity;
|
|
|
|
|
++m_bufferCount;
|
|
|
|
|
m_notEmpty.notifyOne();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BufferQueue::Item get() {
|
|
|
|
|
TThread::ScopedLock sl(m_mutex);
|
|
|
|
|
|
|
|
|
|
while (m_bufferCount == 0) m_notEmpty.wait(sl);
|
|
|
|
|
|
|
|
|
|
m_notFull.notifyOne();
|
|
|
|
|
|
|
|
|
|
BufferQueue::Item item = m_items[m_nextGetItem];
|
|
|
|
|
|
|
|
|
|
m_nextGetItem = (m_nextGetItem + 1) % m_capacity;
|
|
|
|
|
--m_bufferCount;
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void saveOne(ZFile *file) {
|
|
|
|
|
TThread::ScopedLock sl(m_mutex);
|
|
|
|
|
|
|
|
|
|
while (m_bufferCount == 0) m_notEmpty.wait(sl);
|
|
|
|
|
|
|
|
|
|
m_notFull.notifyOne();
|
|
|
|
|
|
|
|
|
|
BufferQueue::Item item = m_items[m_nextGetItem];
|
|
|
|
|
|
|
|
|
|
m_nextGetItem = (m_nextGetItem + 1) % m_capacity;
|
|
|
|
|
--m_bufferCount;
|
|
|
|
|
|
|
|
|
|
TINT64 pos = item.m_frame * item.m_chunkSize;
|
|
|
|
|
TINT64 sectorCount = pos / m_allocUnit;
|
|
|
|
|
|
|
|
|
|
if ((pos % m_allocUnit) != 0) ++sectorCount;
|
|
|
|
|
|
|
|
|
|
pos = sectorCount * m_allocUnit;
|
|
|
|
|
file->write(item.m_buffer, (TINT32)item.m_chunkSize, pos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int size() {
|
|
|
|
|
TThread::ScopedLock sl(m_mutex);
|
|
|
|
|
return m_bufferCount;
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
private:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int m_capacity;
|
|
|
|
|
int m_allocUnit;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TThread::Condition m_notEmpty;
|
|
|
|
|
TThread::Condition m_notFull;
|
|
|
|
|
TThread::Mutex m_mutex;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
vector<Item> m_items;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
int m_bufferCount;
|
|
|
|
|
int m_nextPutItem;
|
|
|
|
|
int m_nextGetItem;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class WriteBufferTask : public TThread::Runnable {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
WriteBufferTask(ZFile *file, BufferQueue *bufferQueue)
|
|
|
|
|
: m_file(file), m_bufferQueue(bufferQueue) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void run();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
ZFile *m_file;
|
|
|
|
|
BufferQueue *m_bufferQueue;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void WriteBufferTask::run() {
|
|
|
|
|
while (true) {
|
|
|
|
|
TThread::milestone();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
m_bufferQueue->saveOne(m_file);
|
|
|
|
|
m_file->waitForAsyncIOCompletion();
|
|
|
|
|
} catch (TException & /*e*/) {
|
|
|
|
|
} catch (...) {
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
} // anonymous namespace
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
class TDiskCachePersist2::Imp {
|
2016-03-19 06:57:51 +13:00
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
|
Imp(const TFilePath &fp, bool asyncWrite)
|
|
|
|
|
: m_chunkSize(0)
|
|
|
|
|
, m_readBuffer(0)
|
|
|
|
|
, m_file(new ZFile(fp, true, asyncWrite))
|
|
|
|
|
, m_asyncWrite(asyncWrite)
|
|
|
|
|
, m_executor(0)
|
|
|
|
|
, m_bufferQueue(0) {
|
|
|
|
|
m_file->open();
|
|
|
|
|
m_allocUnit = m_file->getBytesPerSector();
|
|
|
|
|
|
|
|
|
|
if (m_asyncWrite) {
|
|
|
|
|
m_executor = new TThread::Executor();
|
|
|
|
|
m_bufferQueue = new BufferQueue(4, m_allocUnit);
|
|
|
|
|
m_executor->addTask(new WriteBufferTask(m_file, m_bufferQueue));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~Imp() {
|
|
|
|
|
delete m_file;
|
|
|
|
|
delete[] m_readBuffer;
|
|
|
|
|
|
|
|
|
|
if (m_executor) {
|
|
|
|
|
m_executor->cancel();
|
|
|
|
|
delete m_executor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete m_bufferQueue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool put(int frame, UCHAR *data, TUINT32 dataSize);
|
|
|
|
|
UCHAR *get(int pos, TUINT32 *size);
|
|
|
|
|
|
|
|
|
|
TThread::Mutex m_mutex;
|
|
|
|
|
TINT64 m_chunkSize;
|
|
|
|
|
|
|
|
|
|
ZFile *m_file;
|
|
|
|
|
int m_allocUnit;
|
|
|
|
|
UCHAR *m_readBuffer;
|
|
|
|
|
int m_lx;
|
|
|
|
|
int m_ly;
|
|
|
|
|
int m_bpp;
|
|
|
|
|
|
|
|
|
|
bool m_asyncWrite;
|
|
|
|
|
TThread::Executor *m_executor;
|
|
|
|
|
BufferQueue *m_bufferQueue;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TDiskCachePersist2::Imp::put(int frame, UCHAR *data, TUINT32 dataSize) {
|
|
|
|
|
if (dataSize != m_chunkSize) return false;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TINT64 pos = frame * m_chunkSize;
|
|
|
|
|
TINT64 sectorCount = pos / m_allocUnit;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
if ((pos % m_allocUnit) != 0) ++sectorCount;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
pos = sectorCount * m_allocUnit;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_file->write(data, dataSize, pos);
|
|
|
|
|
return true;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
UCHAR *TDiskCachePersist2::Imp::get(int frame, TUINT32 *size) {
|
|
|
|
|
UCHAR *ret = new UCHAR[TINT32(m_chunkSize)];
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TThread::ScopedLock sl(m_mutex);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TINT64 pos = frame * m_chunkSize;
|
|
|
|
|
TINT64 sectorCount = pos / m_allocUnit;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
if ((pos % m_allocUnit) != 0) ++sectorCount;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
pos = sectorCount * m_allocUnit;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
m_file->read(ret, TINT32(m_chunkSize), pos);
|
|
|
|
|
*size = TUINT32(m_chunkSize);
|
|
|
|
|
return ret;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TDiskCachePersist2::TDiskCachePersist2(TRasterCodec *codec,
|
|
|
|
|
const TFilePath &fullpath)
|
|
|
|
|
: TCachePersist(codec), m_imp(new Imp(fullpath, false /*true*/)) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TDiskCachePersist2::~TDiskCachePersist2() { delete m_imp; }
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TDiskCachePersist2::setFrameSize(int lx, int ly, int bpp) {
|
|
|
|
|
m_imp->m_lx = lx;
|
|
|
|
|
m_imp->m_ly = ly;
|
|
|
|
|
m_imp->m_bpp = bpp;
|
|
|
|
|
|
|
|
|
|
// inizializza m_imp->m_chunkSize in modo che sia un multiplo di
|
|
|
|
|
// m_imp->m_allocUnit
|
|
|
|
|
if (m_codec)
|
|
|
|
|
m_imp->m_chunkSize = m_codec->getMaxCompressionSize(lx * ly * (bpp >> 3));
|
|
|
|
|
else
|
|
|
|
|
m_imp->m_chunkSize = lx * ly * (bpp >> 3);
|
|
|
|
|
|
|
|
|
|
TINT64 allocUnitCount = m_imp->m_chunkSize / m_imp->m_allocUnit;
|
|
|
|
|
if ((m_imp->m_chunkSize % m_imp->m_allocUnit) != 0) ++allocUnitCount;
|
|
|
|
|
|
|
|
|
|
m_imp->m_chunkSize = allocUnitCount * m_imp->m_allocUnit;
|
|
|
|
|
delete[] m_imp->m_readBuffer;
|
|
|
|
|
m_imp->m_readBuffer = 0;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TRasterP TDiskCachePersist2::doGetRaster(int frame) {
|
|
|
|
|
TRasterP outRas;
|
|
|
|
|
TUINT32 size;
|
|
|
|
|
|
|
|
|
|
if (!m_imp->m_readBuffer)
|
|
|
|
|
m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];
|
|
|
|
|
|
|
|
|
|
TINT64 pos = frame * m_imp->m_chunkSize;
|
|
|
|
|
TINT64 sectorCount = pos / m_imp->m_allocUnit;
|
|
|
|
|
|
|
|
|
|
if ((pos % m_imp->m_allocUnit) != 0) ++sectorCount;
|
|
|
|
|
|
|
|
|
|
pos = sectorCount * m_imp->m_allocUnit;
|
|
|
|
|
|
|
|
|
|
m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
|
|
|
|
|
size = TUINT32(m_imp->m_chunkSize);
|
|
|
|
|
|
|
|
|
|
if (m_codec)
|
|
|
|
|
m_codec->decompress(m_imp->m_readBuffer, size, outRas);
|
|
|
|
|
else {
|
|
|
|
|
switch (m_imp->m_bpp) {
|
|
|
|
|
case 32: {
|
|
|
|
|
TRaster32P ras(m_imp->m_lx, m_imp->m_ly);
|
|
|
|
|
outRas = ras;
|
|
|
|
|
} break;
|
|
|
|
|
case 64: {
|
|
|
|
|
TRaster64P ras(m_imp->m_lx, m_imp->m_ly);
|
|
|
|
|
outRas = ras;
|
|
|
|
|
} break;
|
|
|
|
|
default:
|
|
|
|
|
throw TException("unsupported pixel format");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned int rasSize = outRas->getRowSize() * outRas->getLy();
|
|
|
|
|
assert(size >= rasSize);
|
|
|
|
|
outRas->lock();
|
|
|
|
|
memcpy(outRas->getRawData(), m_imp->m_readBuffer, rasSize);
|
|
|
|
|
outRas->unlock();
|
|
|
|
|
}
|
|
|
|
|
return outRas;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TDiskCachePersist2::doGetRaster(int frame, TRaster32P &ras) const {
|
|
|
|
|
if (!m_imp->m_readBuffer)
|
|
|
|
|
m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];
|
|
|
|
|
|
|
|
|
|
TINT64 pos = frame * m_imp->m_chunkSize;
|
|
|
|
|
TINT64 sectorCount = pos / m_imp->m_allocUnit;
|
|
|
|
|
|
|
|
|
|
if ((pos % m_imp->m_allocUnit) != 0) ++sectorCount;
|
|
|
|
|
|
|
|
|
|
pos = sectorCount * m_imp->m_allocUnit;
|
|
|
|
|
|
|
|
|
|
TRasterP rasP = ras;
|
|
|
|
|
if (m_codec) {
|
|
|
|
|
m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
|
|
|
|
|
m_codec->decompress(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), rasP);
|
|
|
|
|
assert(rasP->getSize() == ras->getSize());
|
|
|
|
|
ras->copy(rasP);
|
|
|
|
|
} else {
|
|
|
|
|
assert(ras->getLx() == ras->getWrap());
|
|
|
|
|
int rasSize = ras->getRowSize() * ras->getLy();
|
|
|
|
|
ras->lock();
|
|
|
|
|
if (rasSize == m_imp->m_chunkSize)
|
|
|
|
|
m_imp->m_file->read(ras->getRawData(), TINT32(m_imp->m_chunkSize), pos);
|
|
|
|
|
else {
|
|
|
|
|
assert(rasSize < m_imp->m_chunkSize);
|
|
|
|
|
m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
|
|
|
|
|
memcpy(ras->getRawData(), m_imp->m_readBuffer, rasSize);
|
|
|
|
|
}
|
|
|
|
|
ras->unlock();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
bool TDiskCachePersist2::doPutRaster(int frame, const TRasterP &inRas) {
|
|
|
|
|
UCHAR *outData = 0;
|
|
|
|
|
TINT32 outDataSize = 0;
|
|
|
|
|
int actualDataSize = 0;
|
|
|
|
|
|
|
|
|
|
bool deleteDataBuffer = false;
|
|
|
|
|
if (m_codec) {
|
|
|
|
|
m_codec->compress(inRas, m_imp->m_allocUnit, &outData, outDataSize);
|
|
|
|
|
deleteDataBuffer = true;
|
|
|
|
|
;
|
|
|
|
|
actualDataSize = outDataSize;
|
|
|
|
|
} else {
|
|
|
|
|
assert(inRas->getLx() == inRas->getWrap());
|
|
|
|
|
int rasSize = inRas->getLx() * inRas->getLy() * inRas->getPixelSize();
|
|
|
|
|
|
|
|
|
|
outDataSize = TINT32(m_imp->m_chunkSize);
|
|
|
|
|
inRas->lock();
|
|
|
|
|
if (rasSize == m_imp->m_chunkSize)
|
|
|
|
|
outData = inRas->getRawData();
|
|
|
|
|
else {
|
|
|
|
|
if (!m_imp->m_readBuffer)
|
|
|
|
|
m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];
|
|
|
|
|
|
|
|
|
|
memcpy(m_imp->m_readBuffer, inRas->getRawData(), rasSize);
|
|
|
|
|
outData = m_imp->m_readBuffer;
|
|
|
|
|
}
|
|
|
|
|
inRas->unlock();
|
|
|
|
|
actualDataSize = rasSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert((outDataSize % m_imp->m_allocUnit) == 0);
|
|
|
|
|
|
|
|
|
|
bool cached = true;
|
|
|
|
|
if (m_imp->m_asyncWrite)
|
|
|
|
|
m_imp->m_bufferQueue->put(frame, outData, actualDataSize, outDataSize);
|
|
|
|
|
else
|
|
|
|
|
cached = m_imp->put(frame, outData, outDataSize);
|
|
|
|
|
|
|
|
|
|
if (deleteDataBuffer) delete[] outData;
|
|
|
|
|
return cached;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
UCHAR *TDiskCachePersist2::getRawData(int frame, TINT32 &size, int &lx,
|
|
|
|
|
int &ly) {
|
|
|
|
|
TUINT32 inDataSize;
|
|
|
|
|
UCHAR *src = m_imp->get(frame, &inDataSize);
|
|
|
|
|
return m_codec->removeHeader(src, inDataSize, size, lx, ly);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TDiskCachePersist2::onInvalidate() {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
void TDiskCachePersist2::onInvalidate(int startFrame, int endFrame) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
|
TUINT64 TDiskCachePersist2::getUsedSpace() {
|
|
|
|
|
TFileStatus fs(m_imp->m_file->getFilePath());
|
|
|
|
|
return fs.getSize();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
2016-06-15 18:43:10 +12:00
|
|
|
|
#endif // WIN32
|