--- libgig/trunk/src/RIFF.cpp 2005/11/03 23:49:11 798 +++ libgig/trunk/src/RIFF.cpp 2014/12/29 16:25:51 2682 @@ -1,8 +1,8 @@ /*************************************************************************** * * - * libgig - C++ cross-platform Gigasampler format file loader library * + * libgig - C++ cross-platform Gigasampler format file access library * * * - * Copyright (C) 2003-2005 by Christian Schoenebeck * + * Copyright (C) 2003-2014 by Christian Schoenebeck * * * * * * This library is free software; you can redistribute it and/or modify * @@ -21,20 +21,47 @@ * MA 02111-1307 USA * ***************************************************************************/ +#include +#include #include -#include #include "RIFF.h" +#include "helper.h" + +#if POSIX +# include +#endif + namespace RIFF { -// *************** Helper Functions ************** +// *************** Internal functions ************** +// * + + /// Returns a human readable path of the given chunk. + static String __resolveChunkPath(Chunk* pCk) { + String sPath; + for (Chunk* pChunk = pCk; pChunk; pChunk = pChunk->GetParent()) { + if (pChunk->GetChunkID() == CHUNK_ID_LIST) { + List* pList = (List*) pChunk; + sPath = "->'" + pList->GetListTypeString() + "'" + sPath; + } else { + sPath = "->'" + pChunk->GetChunkIDString() + "'" + sPath; + } + } + return sPath; + } + + + +// *************** progress_t *************** // * - template inline String ToString(T o) { - std::stringstream ss; - ss << o; - return ss.str(); + progress_t::progress_t() { + callback = NULL; + custom = NULL; + __range_min = 0.0f; + __range_max = 1.0f; } @@ -49,6 +76,9 @@ ulPos = 0; pParent = NULL; pChunkData = NULL; + CurrentChunkSize = 0; + NewChunkSize = 0; + ulChunkDataSize = 0; ChunkID = CHUNK_ID_RIFF; this->pFile = pFile; } @@ -62,6 +92,9 @@ pParent = Parent; ulPos = 0; pChunkData = NULL; + CurrentChunkSize = 0; + NewChunkSize = 0; + ulChunkDataSize = 0; ReadHeader(StartPos); } @@ -72,11 +105,13 @@ ulPos = 0; pChunkData = NULL; ChunkID = uiChunkID; + ulChunkDataSize = 0; CurrentChunkSize = 0; NewChunkSize = uiBodySize; } Chunk::~Chunk() { + if (pFile) pFile->UnlogResized(this); if (pChunkData) delete[] pChunkData; } @@ -84,10 +119,17 @@ #if DEBUG std::cout << "Chunk::Readheader(" << fPos << ") "; #endif // DEBUG + ChunkID = 0; + NewChunkSize = CurrentChunkSize = 0; #if POSIX if (lseek(pFile->hFileRead, fPos, SEEK_SET) != -1) { read(pFile->hFileRead, &ChunkID, 4); read(pFile->hFileRead, &CurrentChunkSize, 4); + #elif defined(WIN32) + if (SetFilePointer(pFile->hFileRead, fPos, NULL/*32 bit*/, FILE_BEGIN) != INVALID_SET_FILE_POINTER) { + DWORD dwBytesRead; + ReadFile(pFile->hFileRead, &ChunkID, 4, &dwBytesRead, NULL); + ReadFile(pFile->hFileRead, &CurrentChunkSize, 4, &dwBytesRead, NULL); #else if (!fseek(pFile->hFileRead, fPos, SEEK_SET)) { fread(&ChunkID, 4, 1, pFile->hFileRead); @@ -95,7 +137,7 @@ #endif // POSIX #if WORDS_BIGENDIAN if (ChunkID == CHUNK_ID_RIFF) { - bEndianNative = false; + pFile->bEndianNative = false; } #else // little endian if (ChunkID == CHUNK_ID_RIFX) { @@ -109,8 +151,8 @@ } #if DEBUG std::cout << "ckID=" << convertToString(ChunkID) << " "; - std::cout << "ckSize=" << ChunkSize << " "; - std::cout << "bEndianNative=" << bEndianNative << std::endl; + std::cout << "ckSize=" << CurrentChunkSize << " "; + std::cout << "bEndianNative=" << pFile->bEndianNative << std::endl; #endif // DEBUG NewChunkSize = CurrentChunkSize; } @@ -136,6 +178,12 @@ write(pFile->hFileWrite, &uiNewChunkID, 4); write(pFile->hFileWrite, &uiNewChunkSize, 4); } + #elif defined(WIN32) + if (SetFilePointer(pFile->hFileWrite, fPos, NULL/*32 bit*/, FILE_BEGIN) != INVALID_SET_FILE_POINTER) { + DWORD dwBytesWritten; + WriteFile(pFile->hFileWrite, &uiNewChunkID, 4, &dwBytesWritten, NULL); + WriteFile(pFile->hFileWrite, &uiNewChunkSize, 4, &dwBytesWritten, NULL); + } #else if (!fseek(pFile->hFileWrite, fPos, SEEK_SET)) { fwrite(&uiNewChunkID, 4, 1, pFile->hFileWrite); @@ -200,7 +248,7 @@ #if DEBUG std::cout << "Chunk::Remainingbytes()=" << CurrentChunkSize - ulPos << std::endl; #endif // DEBUG - return CurrentChunkSize - ulPos; + return (CurrentChunkSize > ulPos) ? CurrentChunkSize - ulPos : 0; } /** @@ -211,7 +259,7 @@ * - RIFF::stream_closed : * the data stream was closed somehow, no more reading possible * - RIFF::stream_end_reached : - * alreaady reached the end of the chunk data, no more reading + * already reached the end of the chunk data, no more reading * possible without SetPos() */ stream_state_t Chunk::GetState() { @@ -219,7 +267,10 @@ std::cout << "Chunk::GetState()" << std::endl; #endif // DEBUG #if POSIX - if (pFile->hFileRead == 0) return stream_closed; + if (pFile->hFileRead == 0) return stream_closed; + #elif defined (WIN32) + if (pFile->hFileRead == INVALID_HANDLE_VALUE) + return stream_closed; #else if (pFile->hFileRead == NULL) return stream_closed; #endif // POSIX @@ -246,6 +297,7 @@ #if DEBUG std::cout << "Chunk::Read(void*,ulong,ulong)" << std::endl; #endif // DEBUG + //if (ulStartPos == 0) return 0; // is only 0 if this is a new chunk, so nothing to read (yet) if (ulPos >= CurrentChunkSize) return 0; if (ulPos + WordCount * WordSize >= CurrentChunkSize) WordCount = (CurrentChunkSize - ulPos) / WordSize; #if POSIX @@ -253,6 +305,12 @@ unsigned long readWords = read(pFile->hFileRead, pData, WordCount * WordSize); if (readWords < 1) return 0; readWords /= WordSize; + #elif defined(WIN32) + if (SetFilePointer(pFile->hFileRead, ulStartPos + ulPos, NULL/*32 bit*/, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return 0; + DWORD readWords; + ReadFile(pFile->hFileRead, pData, WordCount * WordSize, &readWords, NULL); + if (readWords < 1) return 0; + readWords /= WordSize; #else // standard C functions if (fseek(pFile->hFileRead, ulStartPos + ulPos, SEEK_SET)) return 0; unsigned long readWords = fread(pData, WordSize, WordCount, pFile->hFileRead); @@ -322,6 +380,15 @@ unsigned long writtenWords = write(pFile->hFileWrite, pData, WordCount * WordSize); if (writtenWords < 1) throw Exception("POSIX IO Error while trying to write chunk data"); writtenWords /= WordSize; + #elif defined(WIN32) + if (SetFilePointer(pFile->hFileWrite, ulStartPos + ulPos, NULL/*32 bit*/, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + throw Exception("Could not seek to position " + ToString(ulPos) + + " in chunk (" + ToString(ulStartPos + ulPos) + " in file)"); + } + DWORD writtenWords; + WriteFile(pFile->hFileWrite, pData, WordCount * WordSize, &writtenWords, NULL); + if (writtenWords < 1) throw Exception("Windows IO Error while trying to write chunk data"); + writtenWords /= WordSize; #else // standard C functions if (fseek(pFile->hFileWrite, ulStartPos + ulPos, SEEK_SET)) { throw Exception("Could not seek to position " + ToString(ulPos) + @@ -544,6 +611,23 @@ } /** + * Reads a null-padded string of size characters and copies it + * into the string \a s. The position within the chunk will + * automatically be incremented. + * + * @param s destination string + * @param size number of characters to read + * @throws RIFF::Exception if an error occured or less than + * \a size characters could be read! + */ + void Chunk::ReadString(String& s, int size) { + char* buf = new char[size]; + ReadSceptical(buf, 1, size); + s.assign(buf, std::find(buf, buf + size, '\0')); + delete[] buf; + } + + /** * Writes \a WordCount number of 32 Bit unsigned integer words from the * buffer pointed by \a pData to the chunk's body, directly to the * actual "physical" file. The position within the chunk will @@ -668,28 +752,54 @@ * * Caution: the buffer pointer will be invalidated once * File::Save() was called. You have to call LoadChunkData() again to - * get a new pointer whenever File::Save() was called. + * get a new, valid pointer whenever File::Save() was called. + * + * You can call LoadChunkData() again if you previously scheduled to + * enlarge this chunk with a Resize() call. In that case the buffer will + * be enlarged to the new, scheduled chunk size and you can already + * place the new chunk data to the buffer and finally call File::Save() + * to enlarge the chunk physically and write the new data in one rush. + * This approach is definitely recommended if you have to enlarge and + * write new data to a lot of chunks. * * @returns a pointer to the data in RAM on success, NULL otherwise + * @throws Exception if data buffer could not be enlarged * @see ReleaseChunkData() */ void* Chunk::LoadChunkData() { - if (!pChunkData) { + if (!pChunkData && pFile->Filename != "" /*&& ulStartPos != 0*/) { #if POSIX if (lseek(pFile->hFileRead, ulStartPos, SEEK_SET) == -1) return NULL; - pChunkData = new uint8_t[GetSize()]; - if (!pChunkData) return NULL; - unsigned long readWords = read(pFile->hFileRead, pChunkData, GetSize()); + #elif defined(WIN32) + if (SetFilePointer(pFile->hFileRead, ulStartPos, NULL/*32 bit*/, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return NULL; #else if (fseek(pFile->hFileRead, ulStartPos, SEEK_SET)) return NULL; - pChunkData = new uint8_t[GetSize()]; + #endif // POSIX + unsigned long ulBufferSize = (CurrentChunkSize > NewChunkSize) ? CurrentChunkSize : NewChunkSize; + pChunkData = new uint8_t[ulBufferSize]; if (!pChunkData) return NULL; + memset(pChunkData, 0, ulBufferSize); + #if POSIX + unsigned long readWords = read(pFile->hFileRead, pChunkData, GetSize()); + #elif defined(WIN32) + DWORD readWords; + ReadFile(pFile->hFileRead, pChunkData, GetSize(), &readWords, NULL); + #else unsigned long readWords = fread(pChunkData, 1, GetSize(), pFile->hFileRead); #endif // POSIX if (readWords != GetSize()) { delete[] pChunkData; return (pChunkData = NULL); } + ulChunkDataSize = ulBufferSize; + } else if (NewChunkSize > ulChunkDataSize) { + uint8_t* pNewBuffer = new uint8_t[NewChunkSize]; + if (!pNewBuffer) throw Exception("Could not enlarge chunk data buffer to " + ToString(NewChunkSize) + " bytes"); + memset(pNewBuffer, 0 , NewChunkSize); + memcpy(pNewBuffer, pChunkData, ulChunkDataSize); + delete[] pChunkData; + pChunkData = pNewBuffer; + ulChunkDataSize = NewChunkSize; } return pChunkData; } @@ -719,14 +829,16 @@ * * Caution: You cannot directly write to enlarged chunks before * calling File::Save() as this might exceed the current chunk's body - * boundary. + * boundary! * * @param iNewSize - new chunk body size in bytes (must be greater than zero) * @throws RIFF::Exception if \a iNewSize is less than 1 * @see File::Save() */ void Chunk::Resize(int iNewSize) { - if (iNewSize <= 0) throw Exception("Chunk size must be at least one byte"); + if (iNewSize <= 0) + throw Exception("There is at least one empty chunk (zero size): " + __resolveChunkPath(this)); + if (NewChunkSize == iNewSize) return; NewChunkSize = iNewSize; pFile->LogAsResized(this); } @@ -739,11 +851,12 @@ * chunk should be written to * @param ulCurrentDataOffset - offset of current (old) data within * the file + * @param pProgress - optional: callback function for progress notification * @returns new write position in the "physical" file, that is * \a ulWritePos incremented by this chunk's new size * (including its header size of course) */ - unsigned long Chunk::WriteChunk(unsigned long ulWritePos, unsigned long ulCurrentDataOffset) { + unsigned long Chunk::WriteChunk(unsigned long ulWritePos, unsigned long ulCurrentDataOffset, progress_t* pProgress) { const unsigned long ulOriginalPos = ulWritePos; ulWritePos += CHUNK_HEADER_SIZE; @@ -752,25 +865,21 @@ // if the whole chunk body was loaded into RAM if (pChunkData) { - // in case the chunk size was changed, reallocate the data in RAM with the chunk's new size - if (NewChunkSize != CurrentChunkSize) { - uint8_t* pNewBuffer = new uint8_t[NewChunkSize]; - if (NewChunkSize > CurrentChunkSize) { - memcpy(pNewBuffer, pChunkData, CurrentChunkSize); - memset(pNewBuffer + CurrentChunkSize, 0, NewChunkSize - CurrentChunkSize); - } else { - memcpy(pNewBuffer, pChunkData, NewChunkSize); - } - delete[] pChunkData; - pChunkData = pNewBuffer; - } - + // make sure chunk data buffer in RAM is at least as large as the new chunk size + LoadChunkData(); // write chunk data from RAM persistently to the file #if POSIX lseek(pFile->hFileWrite, ulWritePos, SEEK_SET); if (write(pFile->hFileWrite, pChunkData, NewChunkSize) != NewChunkSize) { throw Exception("Writing Chunk data (from RAM) failed"); } + #elif defined(WIN32) + SetFilePointer(pFile->hFileWrite, ulWritePos, NULL/*32 bit*/, FILE_BEGIN); + DWORD dwBytesWritten; + WriteFile(pFile->hFileWrite, pChunkData, NewChunkSize, &dwBytesWritten, NULL); + if (dwBytesWritten != NewChunkSize) { + throw Exception("Writing Chunk data (from RAM) failed"); + } #else fseek(pFile->hFileWrite, ulWritePos, SEEK_SET); if (fwrite(pChunkData, 1, NewChunkSize, pFile->hFileWrite) != NewChunkSize) { @@ -781,14 +890,23 @@ // move chunk data from the end of the file to the appropriate position int8_t* pCopyBuffer = new int8_t[4096]; unsigned long ulToMove = (NewChunkSize < CurrentChunkSize) ? NewChunkSize : CurrentChunkSize; + #if defined(WIN32) + DWORD iBytesMoved = 1; // we have to pass it via pointer to the Windows API, thus the correct size must be ensured + #else int iBytesMoved = 1; - for (unsigned long ulOffset = 0; iBytesMoved > 0; ulOffset += iBytesMoved, ulToMove -= iBytesMoved) { + #endif + for (unsigned long ulOffset = 0; ulToMove > 0 && iBytesMoved > 0; ulOffset += iBytesMoved, ulToMove -= iBytesMoved) { iBytesMoved = (ulToMove < 4096) ? ulToMove : 4096; #if POSIX lseek(pFile->hFileRead, ulStartPos + ulCurrentDataOffset + ulOffset, SEEK_SET); iBytesMoved = read(pFile->hFileRead, pCopyBuffer, iBytesMoved); lseek(pFile->hFileWrite, ulWritePos + ulOffset, SEEK_SET); iBytesMoved = write(pFile->hFileWrite, pCopyBuffer, iBytesMoved); + #elif defined(WIN32) + SetFilePointer(pFile->hFileRead, ulStartPos + ulCurrentDataOffset + ulOffset, NULL/*32 bit*/, FILE_BEGIN); + ReadFile(pFile->hFileRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); + SetFilePointer(pFile->hFileWrite, ulWritePos + ulOffset, NULL/*32 bit*/, FILE_BEGIN); + WriteFile(pFile->hFileWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); #else fseek(pFile->hFileRead, ulStartPos + ulCurrentDataOffset + ulOffset, SEEK_SET); iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, pFile->hFileRead); @@ -804,6 +922,8 @@ CurrentChunkSize = NewChunkSize; WriteHeader(ulOriginalPos); + __notify_progress(pProgress, 1.0); // notify done + // update chunk's position pointers ulStartPos = ulOriginalPos + CHUNK_HEADER_SIZE; ulPos = 0; @@ -814,6 +934,10 @@ #if POSIX lseek(pFile->hFileWrite, ulStartPos + NewChunkSize, SEEK_SET); write(pFile->hFileWrite, &cPadByte, 1); + #elif defined(WIN32) + SetFilePointer(pFile->hFileWrite, ulStartPos + NewChunkSize, NULL/*32 bit*/, FILE_BEGIN); + DWORD dwBytesWritten; + WriteFile(pFile->hFileWrite, &cPadByte, 1, &dwBytesWritten, NULL); #else fseek(pFile->hFileWrite, ulStartPos + NewChunkSize, SEEK_SET); fwrite(&cPadByte, 1, 1, pFile->hFileWrite); @@ -863,6 +987,10 @@ #if DEBUG std::cout << "List::~List()" << std::endl; #endif // DEBUG + DeleteChunkList(); + } + + void List::DeleteChunkList() { if (pSubChunks) { ChunkList::iterator iter = pSubChunks->begin(); ChunkList::iterator end = pSubChunks->end(); @@ -871,8 +999,12 @@ iter++; } delete pSubChunks; + pSubChunks = NULL; + } + if (pSubChunksMap) { + delete pSubChunksMap; + pSubChunksMap = NULL; } - if (pSubChunksMap) delete pSubChunksMap; } /** @@ -1073,9 +1205,61 @@ pSubChunks->push_back(pNewChunk); (*pSubChunksMap)[uiChunkID] = pNewChunk; pNewChunk->Resize(uiBodySize); + NewChunkSize += CHUNK_HEADER_SIZE; + pFile->LogAsResized(this); return pNewChunk; } + /** @brief Moves a sub chunk witin this list. + * + * Moves a sub chunk from one position in this list to another + * position in the same list. The pSrc chunk is placed before the + * pDst chunk. + * + * @param pSrc - sub chunk to be moved + * @param pDst - the position to move to. pSrc will be placed + * before pDst. If pDst is 0, pSrc will be placed + * last in list. + */ + void List::MoveSubChunk(Chunk* pSrc, Chunk* pDst) { + if (!pSubChunks) LoadSubChunks(); + pSubChunks->remove(pSrc); + ChunkList::iterator iter = find(pSubChunks->begin(), pSubChunks->end(), pDst); + pSubChunks->insert(iter, pSrc); + } + + /** @brief Moves a sub chunk from this list to another list. + * + * Moves a sub chunk from this list list to the end of another + * list. + * + * @param pSrc - sub chunk to be moved + * @param pDst - destination list where the chunk shall be moved to + */ + void List::MoveSubChunk(Chunk* pSrc, List* pNewParent) { + if (pNewParent == this || !pNewParent) return; + if (!pSubChunks) LoadSubChunks(); + if (!pNewParent->pSubChunks) pNewParent->LoadSubChunks(); + pSubChunks->remove(pSrc); + pNewParent->pSubChunks->push_back(pSrc); + // update chunk id map of this List + if ((*pSubChunksMap)[pSrc->GetChunkID()] == pSrc) { + pSubChunksMap->erase(pSrc->GetChunkID()); + // try to find another chunk of the same chunk ID + ChunkList::iterator iter = pSubChunks->begin(); + ChunkList::iterator end = pSubChunks->end(); + for (; iter != end; ++iter) { + if ((*iter)->GetChunkID() == pSrc->GetChunkID()) { + (*pSubChunksMap)[pSrc->GetChunkID()] = *iter; + break; // we're done, stop search + } + } + } + // update chunk id map of other list + if (!(*pNewParent->pSubChunksMap)[pSrc->GetChunkID()]) + (*pNewParent->pSubChunksMap)[pSrc->GetChunkID()] = pSrc; + } + /** @brief Creates a new list sub chunk. * * Creates and adds a new list sub chunk to this list chunk. Note that @@ -1090,6 +1274,8 @@ List* pNewListChunk = new List(pFile, this, uiListType); pSubChunks->push_back(pNewListChunk); (*pSubChunksMap)[CHUNK_ID_LIST] = pNewListChunk; + NewChunkSize += LIST_HEADER_SIZE; + pFile->LogAsResized(this); return pNewListChunk; } @@ -1097,8 +1283,9 @@ * * Removes the sub chunk given by \a pSubChunk from this list and frees * it completely from RAM. The given chunk can either be a normal sub - * chunk or a list sub chunk. You should call File::Save() to make this - * change persistent at any time. + * chunk or a list sub chunk. In case the given chunk is a list chunk, + * all its subchunks (if any) will be removed recursively as well. You + * should call File::Save() to make this change persistent at any time. * * @param pSubChunk - sub chunk or sub list chunk to be removed */ @@ -1125,10 +1312,15 @@ std::cout << "List::Readheader(ulong) "; #endif // DEBUG Chunk::ReadHeader(fPos); + if (CurrentChunkSize < 4) return; NewChunkSize = CurrentChunkSize -= 4; #if POSIX lseek(pFile->hFileRead, fPos + CHUNK_HEADER_SIZE, SEEK_SET); read(pFile->hFileRead, &ListType, 4); + #elif defined(WIN32) + SetFilePointer(pFile->hFileRead, fPos + CHUNK_HEADER_SIZE, NULL/*32 bit*/, FILE_BEGIN); + DWORD dwBytesRead; + ReadFile(pFile->hFileRead, &ListType, 4, &dwBytesRead, NULL); #else fseek(pFile->hFileRead, fPos + CHUNK_HEADER_SIZE, SEEK_SET); fread(&ListType, 4, 1, pFile->hFileRead); @@ -1149,20 +1341,28 @@ #if POSIX lseek(pFile->hFileWrite, fPos + CHUNK_HEADER_SIZE, SEEK_SET); write(pFile->hFileWrite, &ListType, 4); + #elif defined(WIN32) + SetFilePointer(pFile->hFileWrite, fPos + CHUNK_HEADER_SIZE, NULL/*32 bit*/, FILE_BEGIN); + DWORD dwBytesWritten; + WriteFile(pFile->hFileWrite, &ListType, 4, &dwBytesWritten, NULL); #else fseek(pFile->hFileWrite, fPos + CHUNK_HEADER_SIZE, SEEK_SET); fwrite(&ListType, 4, 1, pFile->hFileWrite); #endif // POSIX } - void List::LoadSubChunks() { + void List::LoadSubChunks(progress_t* pProgress) { #if DEBUG std::cout << "List::LoadSubChunks()"; #endif // DEBUG if (!pSubChunks) { pSubChunks = new ChunkList(); pSubChunksMap = new ChunkMap(); + #if defined(WIN32) + if (pFile->hFileRead == INVALID_HANDLE_VALUE) return; + #else if (!pFile->hFileRead) return; + #endif unsigned long uiOriginalPos = GetPos(); SetPos(0); // jump to beginning of list chunk body while (RemainingBytes() >= CHUNK_HEADER_SIZE) { @@ -1186,6 +1386,20 @@ } SetPos(uiOriginalPos); // restore position before this call } + __notify_progress(pProgress, 1.0); // notify done + } + + void List::LoadSubChunksRecursively(progress_t* pProgress) { + const int n = CountSubLists(); + int i = 0; + for (List* pList = GetFirstSubList(); pList; pList = GetNextSubList(), ++i) { + // divide local progress into subprogress + progress_t subprogress; + __divide_progress(pProgress, &subprogress, n, i); + // do the actual work + pList->LoadSubChunksRecursively(&subprogress); + } + __notify_progress(pProgress, 1.0); // notify done } /** @brief Write list chunk persistently e.g. to disk. @@ -1198,11 +1412,12 @@ * list chunk should be written to * @param ulCurrentDataOffset - offset of current (old) data within * the file + * @param pProgress - optional: callback function for progress notification * @returns new write position in the "physical" file, that is * \a ulWritePos incremented by this list chunk's new size * (including its header size of course) */ - unsigned long List::WriteChunk(unsigned long ulWritePos, unsigned long ulCurrentDataOffset) { + unsigned long List::WriteChunk(unsigned long ulWritePos, unsigned long ulCurrentDataOffset, progress_t* pProgress) { const unsigned long ulOriginalPos = ulWritePos; ulWritePos += LIST_HEADER_SIZE; @@ -1211,8 +1426,14 @@ // write all subchunks (including sub list chunks) recursively if (pSubChunks) { - for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter) { - ulWritePos = (*iter)->WriteChunk(ulWritePos, ulCurrentDataOffset); + int i = 0; + const int n = pSubChunks->size(); + for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter, ++i) { + // divide local progress into subprogress for loading current Instrument + progress_t subprogress; + __divide_progress(pProgress, &subprogress, n, i); + // do the actual work + ulWritePos = (*iter)->WriteChunk(ulWritePos, ulCurrentDataOffset, &subprogress); } } @@ -1223,6 +1444,8 @@ // offset of this list chunk in new written file may have changed ulStartPos = ulOriginalPos + LIST_HEADER_SIZE; + __notify_progress(pProgress, 1.0); // notify done + return ulWritePos; } @@ -1247,17 +1470,34 @@ // *************** File *************** // * +//HACK: to avoid breaking DLL compatibility to older versions of libgig we roll the new std::set into the old std::list container, should be replaced on member variable level soon though +#define _GET_RESIZED_CHUNKS() \ + (reinterpret_cast*>(ResizedChunks.front())) + /** @brief Create new RIFF file. * * Use this constructor if you want to create a new RIFF file completely * "from scratch". Note: there must be no empty chunks or empty list * chunks when trying to make the new RIFF file persistent with Save()! * + * Note: by default, the RIFF file will be saved in native endian + * format; that is, as a RIFF file on little-endian machines and + * as a RIFX file on big-endian. To change this behaviour, call + * SetByteOrder() before calling Save(). + * * @param FileType - four-byte identifier of the RIFF file type - * @see AddSubChunk(), AddSubList() + * @see AddSubChunk(), AddSubList(), SetByteOrder() */ - File::File(uint32_t FileType) : List(this) { + File::File(uint32_t FileType) + : List(this), bIsNewFile(true), Layout(layout_standard) + { + //HACK: see _GET_RESIZED_CHUNKS() comment + ResizedChunks.push_back(reinterpret_cast(new std::set)); + #if defined(WIN32) + hFileRead = hFileWrite = INVALID_HANDLE_VALUE; + #else hFileRead = hFileWrite = 0; + #endif Mode = stream_mode_closed; bEndianNative = true; ulStartPos = RIFF_HEADER_SIZE; @@ -1272,37 +1512,141 @@ * @throws RIFF::Exception if error occured while trying to load the * given RIFF file */ - File::File(const String& path) : List(this), Filename(path) { - #if DEBUG - std::cout << "File::File("<(new std::set)); #if POSIX hFileRead = hFileWrite = open(path.c_str(), O_RDONLY | O_NONBLOCK); - if (hFileRead <= 0) { + if (hFileRead == -1) { hFileRead = hFileWrite = 0; + String sError = strerror(errno); + throw RIFF::Exception("Can't open \"" + path + "\": " + sError); + } + #elif defined(WIN32) + hFileRead = hFileWrite = CreateFile( + path.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_RANDOM_ACCESS, NULL + ); + if (hFileRead == INVALID_HANDLE_VALUE) { + hFileRead = hFileWrite = INVALID_HANDLE_VALUE; throw RIFF::Exception("Can't open \"" + path + "\""); } #else hFileRead = hFileWrite = fopen(path.c_str(), "rb"); - if (!hFile) throw RIFF::Exception("Can't open \"" + path + "\""); + if (!hFileRead) throw RIFF::Exception("Can't open \"" + path + "\""); #endif // POSIX Mode = stream_mode_read; - ulStartPos = RIFF_HEADER_SIZE; - ReadHeader(0); - if (ChunkID != CHUNK_ID_RIFF) { - throw RIFF::Exception("Not a RIFF file"); + switch (Layout) { + case layout_standard: // this is a normal RIFF file + ulStartPos = RIFF_HEADER_SIZE; + ReadHeader(0); + if (FileType && ChunkID != *FileType) + throw RIFF::Exception("Invalid file container ID"); + break; + case layout_flat: // non-standard RIFF-alike file + ulStartPos = 0; + NewChunkSize = CurrentChunkSize = GetFileSize(); + if (FileType) { + uint32_t ckid; + if (Read(&ckid, 4, 1) != 4) { + throw RIFF::Exception("Invalid file header ID (premature end of header)"); + } else if (ckid != *FileType) { + String s = " (expected '" + convertToString(*FileType) + "' but got '" + convertToString(ckid) + "')"; + throw RIFF::Exception("Invalid file header ID" + s); + } + SetPos(0); // reset to first byte of file + } + LoadSubChunks(); + break; } } String File::GetFileName() { return Filename; } + + void File::SetFileName(const String& path) { + Filename = path; + } stream_mode_t File::GetMode() { return Mode; } + layout_t File::GetLayout() const { + return Layout; + } + /** @brief Change file access mode. * * Changes files access mode either to read-only mode or to read/write @@ -1320,13 +1664,28 @@ #if POSIX if (hFileRead) close(hFileRead); hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK); - if (hFileRead < 0) { + if (hFileRead == -1) { hFileRead = hFileWrite = 0; + String sError = strerror(errno); + throw Exception("Could not (re)open file \"" + Filename + "\" in read mode: " + sError); + } + #elif defined(WIN32) + if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead); + hFileRead = hFileWrite = CreateFile( + Filename.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_RANDOM_ACCESS, + NULL + ); + if (hFileRead == INVALID_HANDLE_VALUE) { + hFileRead = hFileWrite = INVALID_HANDLE_VALUE; throw Exception("Could not (re)open file \"" + Filename + "\" in read mode"); } #else if (hFileRead) fclose(hFileRead); - hFileRead = hFileWrite = fopen(path.c_str(), "rb"); + hFileRead = hFileWrite = fopen(Filename.c_str(), "rb"); if (!hFileRead) throw Exception("Could not (re)open file \"" + Filename + "\" in read mode"); #endif __resetPos(); // reset read/write position of ALL 'Chunk' objects @@ -1335,15 +1694,38 @@ #if POSIX if (hFileRead) close(hFileRead); hFileRead = hFileWrite = open(Filename.c_str(), O_RDWR | O_NONBLOCK); - if (hFileRead < 0) { + if (hFileRead == -1) { hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK); - throw Exception("Could not open file \"" + Filename + "\" in read+write mode"); + String sError = strerror(errno); + throw Exception("Could not open file \"" + Filename + "\" in read+write mode: " + sError); + } + #elif defined(WIN32) + if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead); + hFileRead = hFileWrite = CreateFile( + Filename.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_RANDOM_ACCESS, + NULL + ); + if (hFileRead == INVALID_HANDLE_VALUE) { + hFileRead = hFileWrite = CreateFile( + Filename.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_RANDOM_ACCESS, + NULL + ); + throw Exception("Could not (re)open file \"" + Filename + "\" in read+write mode"); } #else if (hFileRead) fclose(hFileRead); - hFileRead = hFileWrite = fopen(path.c_str(), "r+b"); + hFileRead = hFileWrite = fopen(Filename.c_str(), "r+b"); if (!hFileRead) { - hFileRead = hFileWrite = fopen(path.c_str(), "rb"); + hFileRead = hFileWrite = fopen(Filename.c_str(), "rb"); throw Exception("Could not open file \"" + Filename + "\" in read+write mode"); } #endif @@ -1353,6 +1735,9 @@ #if POSIX if (hFileRead) close(hFileRead); if (hFileWrite) close(hFileWrite); + #elif defined(WIN32) + if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead); + if (hFileWrite != INVALID_HANDLE_VALUE) CloseHandle(hFileWrite); #else if (hFileRead) fclose(hFileRead); if (hFileWrite) fclose(hFileWrite); @@ -1368,6 +1753,23 @@ return false; } + /** @brief Set the byte order to be used when saving. + * + * Set the byte order to be used in the file. A value of + * endian_little will create a RIFF file, endian_big a RIFX file + * and endian_native will create a RIFF file on little-endian + * machines and RIFX on big-endian machines. + * + * @param Endian - endianess to use when file is saved. + */ + void File::SetByteOrder(endian_t Endian) { + #if WORDS_BIGENDIAN + bEndianNative = Endian != endian_little; + #else + bEndianNative = Endian != endian_big; + #endif + } + /** @brief Save changes to same file. * * Make all changes of all chunks persistent by writing them to the @@ -1375,10 +1777,26 @@ * than it will have at the end of the saving process, in case chunks * were grown. * + * @param pProgress - optional: callback function for progress notification * @throws RIFF::Exception if there is an empty chunk or empty list * chunk or any kind of IO error occured */ - void File::Save() { + void File::Save(progress_t* pProgress) { + //TODO: implementation for the case where first chunk is not a global container (List chunk) is not implemented yet (i.e. Korg files) + if (Layout == layout_flat) + throw Exception("Saving a RIFF file with layout_flat is not implemented yet"); + + // make sure the RIFF tree is built (from the original file) + { + // divide progress into subprogress + progress_t subprogress; + __divide_progress(pProgress, &subprogress, 3.f, 0.f); // arbitrarily subdivided into 1/3 of total progress + // do the actual work + LoadSubChunksRecursively(&subprogress); + // notify subprogress done + __notify_progress(&subprogress, 1.f); + } + // reopen file in write mode SetMode(stream_mode_read_write); @@ -1391,53 +1809,81 @@ // first we sum up all positive chunk size changes (and skip all negative ones) unsigned long ulPositiveSizeDiff = 0; - for (ChunkList::iterator iter = ResizedChunks.begin(), end = ResizedChunks.end(); iter != end; ++iter) { - if ((*iter)->GetNewSize() == 0) throw Exception("There is at least one empty chunk (zero size)"); - if ((*iter)->GetNewSize() + 1L > (*iter)->GetSize()) { - unsigned long ulDiff = (*iter)->GetNewSize() - (*iter)->GetSize() + 1L; // +1 in case we have to add a pad byte - ulPositiveSizeDiff += ulDiff; - } + std::set* resizedChunks = _GET_RESIZED_CHUNKS(); + for (std::set::const_iterator iter = resizedChunks->begin(), end = resizedChunks->end(); iter != end; ++iter) { + if ((*iter)->GetNewSize() == 0) { + throw Exception("There is at least one empty chunk (zero size): " + __resolveChunkPath(*iter)); + } + unsigned long newSizePadded = (*iter)->GetNewSize() + (*iter)->GetNewSize() % 2; + unsigned long oldSizePadded = (*iter)->GetSize() + (*iter)->GetSize() % 2; + if (newSizePadded > oldSizePadded) ulPositiveSizeDiff += newSizePadded - oldSizePadded; } unsigned long ulWorkingFileSize = GetFileSize(); // if there are positive size changes... if (ulPositiveSizeDiff > 0) { + // divide progress into subprogress + progress_t subprogress; + __divide_progress(pProgress, &subprogress, 3.f, 1.f); // arbitrarily subdivided into 1/3 of total progress + // ... we enlarge this file first ... ulWorkingFileSize += ulPositiveSizeDiff; ResizeFile(ulWorkingFileSize); // ... and move current data by the same amount towards end of file. int8_t* pCopyBuffer = new int8_t[4096]; const unsigned long ulFileSize = GetSize() + RIFF_HEADER_SIZE; + #if defined(WIN32) + DWORD iBytesMoved = 1; // we have to pass it via pointer to the Windows API, thus the correct size must be ensured + #else int iBytesMoved = 1; - for (unsigned long ulPos = 0; iBytesMoved > 0; ulPos += iBytesMoved) { - const unsigned long ulToMove = ulFileSize - ulPos; - iBytesMoved = (ulToMove < 4096) ? ulToMove : 4096; + #endif + for (unsigned long ulPos = ulFileSize, iNotif = 0; iBytesMoved > 0; ++iNotif) { + iBytesMoved = (ulPos < 4096) ? ulPos : 4096; + ulPos -= iBytesMoved; #if POSIX lseek(hFileRead, ulPos, SEEK_SET); iBytesMoved = read(hFileRead, pCopyBuffer, iBytesMoved); lseek(hFileWrite, ulPos + ulPositiveSizeDiff, SEEK_SET); iBytesMoved = write(hFileWrite, pCopyBuffer, iBytesMoved); + #elif defined(WIN32) + SetFilePointer(hFileRead, ulPos, NULL/*32 bit*/, FILE_BEGIN); + ReadFile(hFileRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); + SetFilePointer(hFileWrite, ulPos + ulPositiveSizeDiff, NULL/*32 bit*/, FILE_BEGIN); + WriteFile(hFileWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); #else fseek(hFileRead, ulPos, SEEK_SET); iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, hFileRead); fseek(hFileWrite, ulPos + ulPositiveSizeDiff, SEEK_SET); iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, hFileWrite); #endif + if (!(iNotif % 8) && iBytesMoved > 0) + __notify_progress(&subprogress, float(ulFileSize - ulPos) / float(ulFileSize)); } delete[] pCopyBuffer; if (iBytesMoved < 0) throw Exception("Could not modify file while trying to enlarge it"); + + __notify_progress(&subprogress, 1.f); // notify subprogress done } - // rebuild / rewrite complete RIFF tree - unsigned long ulTotalSize = WriteChunk(0, ulPositiveSizeDiff); + // rebuild / rewrite complete RIFF tree ... + + // divide progress into subprogress + progress_t subprogress; + __divide_progress(pProgress, &subprogress, 3.f, 2.f); // arbitrarily subdivided into 1/3 of total progress + // do the actual work + unsigned long ulTotalSize = WriteChunk(0, ulPositiveSizeDiff, &subprogress); unsigned long ulActualSize = __GetFileSize(hFileWrite); + // notify subprogress done + __notify_progress(&subprogress, 1.f); // resize file to the final size if (ulTotalSize < ulActualSize) ResizeFile(ulTotalSize); // forget all resized chunks - ResizedChunks.clear(); + resizedChunks->clear(); + + __notify_progress(pProgress, 1.0); // notify done } /** @brief Save changes to another file. @@ -1452,15 +1898,42 @@ * the new file (given by \a path) afterwards. * * @param path - path and file name where everything should be written to + * @param pProgress - optional: callback function for progress notification */ - void File::Save(const String& path) { + void File::Save(const String& path, progress_t* pProgress) { //TODO: we should make a check here if somebody tries to write to the same file and automatically call the other Save() method in that case - if (Filename.length() > 0) SetMode(stream_mode_read); + //TODO: implementation for the case where first chunk is not a global container (List chunk) is not implemented yet (i.e. Korg files) + if (Layout == layout_flat) + throw Exception("Saving a RIFF file with layout_flat is not implemented yet"); + + // make sure the RIFF tree is built (from the original file) + { + // divide progress into subprogress + progress_t subprogress; + __divide_progress(pProgress, &subprogress, 2.f, 0.f); // arbitrarily subdivided into 1/2 of total progress + // do the actual work + LoadSubChunksRecursively(&subprogress); + // notify subprogress done + __notify_progress(&subprogress, 1.f); + } + + if (!bIsNewFile) SetMode(stream_mode_read); // open the other (new) file for writing and truncate it to zero size #if POSIX hFileWrite = open(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP); - if (hFileWrite < 0) { + if (hFileWrite == -1) { + hFileWrite = hFileRead; + String sError = strerror(errno); + throw Exception("Could not open file \"" + path + "\" for writing: " + sError); + } + #elif defined(WIN32) + hFileWrite = CreateFile( + path.c_str(), GENERIC_WRITE, FILE_SHARE_READ, + NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_RANDOM_ACCESS, NULL + ); + if (hFileWrite == INVALID_HANDLE_VALUE) { hFileWrite = hFileRead; throw Exception("Could not open file \"" + path + "\" for writing"); } @@ -1474,36 +1947,53 @@ Mode = stream_mode_read_write; // write complete RIFF tree to the other (new) file - unsigned long ulTotalSize = WriteChunk(0, 0); + unsigned long ulTotalSize; + { + // divide progress into subprogress + progress_t subprogress; + __divide_progress(pProgress, &subprogress, 2.f, 1.f); // arbitrarily subdivided into 1/2 of total progress + // do the actual work + ulTotalSize = WriteChunk(0, 0, &subprogress); + // notify subprogress done + __notify_progress(&subprogress, 1.f); + } unsigned long ulActualSize = __GetFileSize(hFileWrite); // resize file to the final size (if the file was originally larger) if (ulTotalSize < ulActualSize) ResizeFile(ulTotalSize); // forget all resized chunks - ResizedChunks.clear(); + _GET_RESIZED_CHUNKS()->clear(); - if (Filename.length() > 0) { - #if POSIX - close(hFileWrite); - #else - fclose(hFileWrite); - #endif - hFileWrite = hFileRead; - } + #if POSIX + if (hFileWrite) close(hFileWrite); + #elif defined(WIN32) + if (hFileWrite != INVALID_HANDLE_VALUE) CloseHandle(hFileWrite); + #else + if (hFileWrite) fclose(hFileWrite); + #endif + hFileWrite = hFileRead; // associate new file with this File object from now on Filename = path; + bIsNewFile = false; Mode = (stream_mode_t) -1; // Just set it to an undefined mode ... SetMode(stream_mode_read_write); // ... so SetMode() has to reopen the file handles. + + __notify_progress(pProgress, 1.0); // notify done } void File::ResizeFile(unsigned long ulNewSize) { #if POSIX if (ftruncate(hFileWrite, ulNewSize) < 0) throw Exception("Could not resize file \"" + Filename + "\""); + #elif defined(WIN32) + if ( + SetFilePointer(hFileWrite, ulNewSize, NULL/*32 bit*/, FILE_BEGIN) == INVALID_SET_FILE_POINTER || + !SetEndOfFile(hFileWrite) + ) throw Exception("Could not resize file \"" + Filename + "\""); #else - # error Sorry, this version of libgig only supports POSIX systems yet. + # error Sorry, this version of libgig only supports POSIX and Windows systems yet. # error Reason: portable implementation of RIFF::File::ResizeFile() is missing (yet)! #endif } @@ -1512,15 +2002,37 @@ #if DEBUG std::cout << "File::~File()" << std::endl; #endif // DEBUG + Cleanup(); + } + + /** + * Returns @c true if this file has been created new from scratch and + * has not been stored to disk yet. + */ + bool File::IsNew() const { + return bIsNewFile; + } + + void File::Cleanup() { #if POSIX if (hFileRead) close(hFileRead); + #elif defined(WIN32) + if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead); #else if (hFileRead) fclose(hFileRead); #endif // POSIX + DeleteChunkList(); + pFile = NULL; + //HACK: see _GET_RESIZED_CHUNKS() comment + delete _GET_RESIZED_CHUNKS(); } void File::LogAsResized(Chunk* pResizedChunk) { - ResizedChunks.push_back(pResizedChunk); + _GET_RESIZED_CHUNKS()->insert(pResizedChunk); + } + + void File::UnlogResized(Chunk* pResizedChunk) { + _GET_RESIZED_CHUNKS()->erase(pResizedChunk); } unsigned long File::GetFileSize() { @@ -1534,6 +2046,13 @@ long size = filestat.st_size; return size; } + #elif defined(WIN32) + unsigned long File::__GetFileSize(HANDLE hFile) { + DWORD dwSize = ::GetFileSize(hFile, NULL /*32bit*/); + if (dwSize == INVALID_FILE_SIZE) + throw Exception("Windows FS error: could not determine file size"); + return dwSize; + } #else // standard C functions unsigned long File::__GetFileSize(FILE* hFile) { long curpos = ftell(hFile);