--- libgig/trunk/src/RIFF.cpp 2016/05/17 19:22:17 2915 +++ libgig/trunk/src/RIFF.cpp 2021/06/07 18:57:17 3915 @@ -2,7 +2,7 @@ * * * libgig - C++ cross-platform Gigasampler format file access library * * * - * Copyright (C) 2003-2016 by Christian Schoenebeck * + * Copyright (C) 2003-2021 by Christian Schoenebeck * * * * * * This library is free software; you can redistribute it and/or modify * @@ -52,6 +52,25 @@ return sPath; } + inline static bool _isValidHandle(File::Handle handle) { + #if defined(WIN32) + return handle != INVALID_HANDLE_VALUE; + #else + return handle; + #endif + } + + inline static void _close(File::Handle handle) { + if (!_isValidHandle(handle)) return; + #if POSIX + close(handle); + #elif defined(WIN32) + CloseHandle(handle); + #else + fclose(handle); + #endif + } + // *************** progress_t *************** @@ -64,16 +83,72 @@ __range_max = 1.0f; } + /** + * Divides this progress task into the requested amount of equal weighted + * sub-progress tasks and returns a vector with those subprogress tasks. + * + * @param iSubtasks - total amount sub tasks this task should be subdivided + * @returns subtasks + */ + std::vector progress_t::subdivide(int iSubtasks) { + std::vector v; + for (int i = 0; i < iSubtasks; ++i) { + progress_t p; + __divide_progress(this, &p, iSubtasks, i); + v.push_back(p); + } + return v; + } + + /** + * Divides this progress task into the requested amount of sub-progress + * tasks, where each one of those new sub-progress tasks is created with its + * requested individual weight / portion, and finally returns a vector + * with those new subprogress tasks. + * + * The amount of subprogresses to be created is determined by this method + * by calling @c vSubTaskPortions.size() . + * + * Example: consider you wanted to create 3 subprogresses where the 1st + * subtask should be assigned 10% of the new 3 subprogresses' overall + * progress, the 2nd subtask should be assigned 50% of the new 3 + * subprogresses' overall progress, and the 3rd subtask should be assigned + * 40%, then you might call this method like this: + * @code + * std::vector subprogresses = progress.subdivide({0.1, 0.5, 0.4}); + * @endcode + * + * @param vSubTaskPortions - amount and individual weight of subtasks to be + * created + * @returns subtasks + */ + std::vector progress_t::subdivide(std::vector vSubTaskPortions) { + float fTotal = 0.f; // usually 1.0, but we sum the portions up below to be sure + for (int i = 0; i < vSubTaskPortions.size(); ++i) + fTotal += vSubTaskPortions[i]; + + float fLow = 0.f, fHigh = 0.f; + std::vector v; + for (int i = 0; i < vSubTaskPortions.size(); ++i) { + fLow = fHigh; + fHigh = vSubTaskPortions[i]; + progress_t p; + __divide_progress(this, &p, fTotal, fLow, fHigh); + v.push_back(p); + } + return v; + } + // *************** Chunk ************** // * Chunk::Chunk(File* pFile) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::Chunk(File* pFile)" << std::endl; - #endif // DEBUG - ullPos = 0; + #endif // DEBUG_RIFF + chunkPos.ullPos = 0; pParent = NULL; pChunkData = NULL; ullCurrentChunkSize = 0; @@ -84,13 +159,13 @@ } Chunk::Chunk(File* pFile, file_offset_t StartPos, List* Parent) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::Chunk(File*,file_offset_t,List*),StartPos=" << StartPos << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF this->pFile = pFile; ullStartPos = StartPos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize); pParent = Parent; - ullPos = 0; + chunkPos.ullPos = 0; pChunkData = NULL; ullCurrentChunkSize = 0; ullNewChunkSize = 0; @@ -102,7 +177,7 @@ this->pFile = pFile; ullStartPos = 0; // arbitrary usually, since it will be updated when we write the chunk this->pParent = pParent; - ullPos = 0; + chunkPos.ullPos = 0; pChunkData = NULL; ChunkID = uiChunkID; ullChunkDataSize = 0; @@ -115,26 +190,29 @@ } void Chunk::ReadHeader(file_offset_t filePos) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::Readheader(" << filePos << ") "; - #endif // DEBUG + #endif // DEBUG_RIFF ChunkID = 0; ullNewChunkSize = ullCurrentChunkSize = 0; + + const File::Handle hRead = pFile->FileHandle(); + #if POSIX - if (lseek(pFile->hFileRead, filePos, SEEK_SET) != -1) { - read(pFile->hFileRead, &ChunkID, 4); - read(pFile->hFileRead, &ullCurrentChunkSize, pFile->FileOffsetSize); + if (lseek(hRead, filePos, SEEK_SET) != -1) { + read(hRead, &ChunkID, 4); + read(hRead, &ullCurrentChunkSize, pFile->FileOffsetSize); #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = filePos; - if (SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) { + if (SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) { DWORD dwBytesRead; - ReadFile(pFile->hFileRead, &ChunkID, 4, &dwBytesRead, NULL); - ReadFile(pFile->hFileRead, &ullCurrentChunkSize, pFile->FileOffsetSize, &dwBytesRead, NULL); + ReadFile(hRead, &ChunkID, 4, &dwBytesRead, NULL); + ReadFile(hRead, &ullCurrentChunkSize, pFile->FileOffsetSize, &dwBytesRead, NULL); #else - if (!fseeko(pFile->hFileRead, filePos, SEEK_SET)) { - fread(&ChunkID, 4, 1, pFile->hFileRead); - fread(&ullCurrentChunkSize, pFile->FileOffsetSize, 1, pFile->hFileRead); + if (!fseeko(hRead, filePos, SEEK_SET)) { + fread(&ChunkID, 4, 1, hRead); + fread(&ullCurrentChunkSize, pFile->FileOffsetSize, 1, hRead); #endif // POSIX #if WORDS_BIGENDIAN if (ChunkID == CHUNK_ID_RIFF) { @@ -153,11 +231,11 @@ else swapBytes_64(&ullCurrentChunkSize); } - #if DEBUG + #if DEBUG_RIFF std::cout << "ckID=" << convertToString(ChunkID) << " "; std::cout << "ckSize=" << ullCurrentChunkSize << " "; std::cout << "bEndianNative=" << pFile->bEndianNative << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF ullNewChunkSize = ullCurrentChunkSize; } } @@ -180,23 +258,25 @@ swapBytes_64(&ullNewChunkSize); } + const File::Handle hWrite = pFile->FileWriteHandle(); + #if POSIX - if (lseek(pFile->hFileWrite, filePos, SEEK_SET) != -1) { - write(pFile->hFileWrite, &uiNewChunkID, 4); - write(pFile->hFileWrite, &ullNewChunkSize, pFile->FileOffsetSize); + if (lseek(hWrite, filePos, SEEK_SET) != -1) { + write(hWrite, &uiNewChunkID, 4); + write(hWrite, &ullNewChunkSize, pFile->FileOffsetSize); } #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = filePos; - if (SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) { + if (SetFilePointerEx(hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) { DWORD dwBytesWritten; - WriteFile(pFile->hFileWrite, &uiNewChunkID, 4, &dwBytesWritten, NULL); - WriteFile(pFile->hFileWrite, &ullNewChunkSize, pFile->FileOffsetSize, &dwBytesWritten, NULL); + WriteFile(hWrite, &uiNewChunkID, 4, &dwBytesWritten, NULL); + WriteFile(hWrite, &ullNewChunkSize, pFile->FileOffsetSize, &dwBytesWritten, NULL); } #else - if (!fseeko(pFile->hFileWrite, filePos, SEEK_SET)) { - fwrite(&uiNewChunkID, 4, 1, pFile->hFileWrite); - fwrite(&ullNewChunkSize, pFile->FileOffsetSize, 1, pFile->hFileWrite); + if (!fseeko(hWrite, filePos, SEEK_SET)) { + fwrite(&uiNewChunkID, 4, 1, hWrite); + fwrite(&ullNewChunkSize, pFile->FileOffsetSize, 1, hWrite); } #endif // POSIX } @@ -210,6 +290,42 @@ } /** + * This is an internal-only method which must not be used by any application + * and might change at any time. + * + * Returns a reference (memory location) of the chunk's current file + * (read/write) position variable which depends on the current value of + * File::IsIOPerThread(). + */ + file_offset_t& Chunk::GetPosUnsafeRef() { + if (!pFile->IsIOPerThread()) return chunkPos.ullPos; + const std::thread::id tid = std::this_thread::get_id(); + return chunkPos.byThread[tid]; + } + + /** + * Current read/write position within the chunk data body (starting with 0). + * + * @see File::IsIOPerThread() for multi-threaded streaming + */ + file_offset_t Chunk::GetPos() const { + if (!pFile->IsIOPerThread()) return chunkPos.ullPos; + const std::thread::id tid = std::this_thread::get_id(); + std::lock_guard lock(chunkPos.mutex); + return chunkPos.byThread[tid]; + } + + /** + * Current, actual offset in file of current chunk data body read/write + * position. + * + * @see File::IsIOPerThread() for multi-threaded streaming + */ + file_offset_t Chunk::GetFilePos() const { + return ullStartPos + GetPos(); + } + + /** * Sets the position within the chunk body, thus within the data portion * of the chunk (in bytes). * @@ -220,27 +336,30 @@ * @param Whence - optional: defines to what \a Where relates to, * if omitted \a Where relates to beginning of the chunk * data + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::SetPos(file_offset_t Where, stream_whence_t Whence) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::SetPos(file_offset_t,stream_whence_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF + std::lock_guard lock(chunkPos.mutex); + file_offset_t& pos = GetPosUnsafeRef(); switch (Whence) { case stream_curpos: - ullPos += Where; + pos += Where; break; case stream_end: - ullPos = ullCurrentChunkSize - 1 - Where; + pos = ullCurrentChunkSize - 1 - Where; break; case stream_backward: - ullPos -= Where; + pos -= Where; break; case stream_start: default: - ullPos = Where; + pos = Where; break; } - if (ullPos > ullCurrentChunkSize) ullPos = ullCurrentChunkSize; - return ullPos; + if (pos > ullCurrentChunkSize) pos = ullCurrentChunkSize; + return pos; } /** @@ -252,12 +371,14 @@ * of the chunk data. * * @returns number of bytes left to read + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::RemainingBytes() const { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::Remainingbytes()=" << ullCurrentChunkSize - ullPos << std::endl; - #endif // DEBUG - return (ullCurrentChunkSize > ullPos) ? ullCurrentChunkSize - ullPos : 0; + #endif // DEBUG_RIFF + const file_offset_t pos = GetPos(); + return (ullCurrentChunkSize > pos) ? ullCurrentChunkSize - pos : 0; } /** @@ -283,20 +404,21 @@ * - RIFF::stream_end_reached : * already reached the end of the chunk data, no more reading * possible without SetPos() + * + * @see File::IsIOPerThread() for multi-threaded streaming */ stream_state_t Chunk::GetState() const { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::GetState()" << std::endl; - #endif // DEBUG - #if POSIX - if (pFile->hFileRead == 0) return stream_closed; - #elif defined (WIN32) - if (pFile->hFileRead == INVALID_HANDLE_VALUE) + #endif // DEBUG_RIFF + + const File::Handle hRead = pFile->FileHandle(); + + if (!_isValidHandle(hRead)) return stream_closed; - #else - if (pFile->hFileRead == NULL) return stream_closed; - #endif // POSIX - if (ullPos < ullCurrentChunkSize) return stream_ready; + + const file_offset_t pos = GetPos(); + if (pos < ullCurrentChunkSize) return stream_ready; else return stream_end_reached; } @@ -313,37 +435,43 @@ * @param WordCount number of data words to read * @param WordSize size of each data word to read * @returns number of successfully read data words or 0 if end - * of file reached or error occured + * of file reached or error occurred + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::Read(void* pData, file_offset_t WordCount, file_offset_t WordSize) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::Read(void*,file_offset_t,file_offset_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF //if (ulStartPos == 0) return 0; // is only 0 if this is a new chunk, so nothing to read (yet) - if (ullPos >= ullCurrentChunkSize) return 0; - if (ullPos + WordCount * WordSize >= ullCurrentChunkSize) WordCount = (ullCurrentChunkSize - ullPos) / WordSize; + const file_offset_t pos = GetPos(); + if (pos >= ullCurrentChunkSize) return 0; + if (pos + WordCount * WordSize >= ullCurrentChunkSize) + WordCount = (ullCurrentChunkSize - pos) / WordSize; + + const File::Handle hRead = pFile->FileHandle(); + #if POSIX - if (lseek(pFile->hFileRead, ullStartPos + ullPos, SEEK_SET) < 0) return 0; - ssize_t readWords = read(pFile->hFileRead, pData, WordCount * WordSize); + if (lseek(hRead, ullStartPos + pos, SEEK_SET) < 0) return 0; + ssize_t readWords = read(hRead, pData, WordCount * WordSize); if (readWords < 1) { - #if DEBUG + #if DEBUG_RIFF std::cerr << "POSIX read() failed: " << strerror(errno) << std::endl << std::flush; - #endif // DEBUG + #endif // DEBUG_RIFF return 0; } readWords /= WordSize; #elif defined(WIN32) LARGE_INTEGER liFilePos; - liFilePos.QuadPart = ullStartPos + ullPos; - if (!SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) + liFilePos.QuadPart = ullStartPos + pos; + if (!SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) return 0; DWORD readWords; - ReadFile(pFile->hFileRead, pData, WordCount * WordSize, &readWords, NULL); //FIXME: does not work for reading buffers larger than 2GB (even though this should rarely be the case in practice) + ReadFile(hRead, pData, WordCount * WordSize, &readWords, NULL); //FIXME: does not work for reading buffers larger than 2GB (even though this should rarely be the case in practice) if (readWords < 1) return 0; readWords /= WordSize; #else // standard C functions - if (fseeko(pFile->hFileRead, ullStartPos + ullPos, SEEK_SET)) return 0; - file_offset_t readWords = fread(pData, WordSize, WordCount, pFile->hFileRead); + if (fseeko(hRead, ullStartPos + pos, SEEK_SET)) return 0; + file_offset_t readWords = fread(pData, WordSize, WordCount, hRead); #endif // POSIX if (!pFile->bEndianNative && WordSize != 1) { switch (WordSize) { @@ -382,13 +510,16 @@ * @param WordSize size of each data word to write * @returns number of successfully written data words * @throws RIFF::Exception if write operation would exceed current - * chunk size or any IO error occured + * chunk size or any IO error occurred * @see Resize() + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::Write(void* pData, file_offset_t WordCount, file_offset_t WordSize) { - if (pFile->Mode != stream_mode_read_write) + const File::HandlePair io = pFile->FileHandlePair(); + if (io.Mode != stream_mode_read_write) throw Exception("Cannot write data to chunk, file has to be opened in read+write mode first"); - if (ullPos >= ullCurrentChunkSize || ullPos + WordCount * WordSize > ullCurrentChunkSize) + const file_offset_t pos = GetPos(); + if (pos >= ullCurrentChunkSize || pos + WordCount * WordSize > ullCurrentChunkSize) throw Exception("End of chunk reached while trying to write data"); if (!pFile->bEndianNative && WordSize != 1) { switch (WordSize) { @@ -411,30 +542,30 @@ } } #if POSIX - if (lseek(pFile->hFileWrite, ullStartPos + ullPos, SEEK_SET) < 0) { - throw Exception("Could not seek to position " + ToString(ullPos) + - " in chunk (" + ToString(ullStartPos + ullPos) + " in file)"); + if (lseek(io.hWrite, ullStartPos + pos, SEEK_SET) < 0) { + throw Exception("Could not seek to position " + ToString(pos) + + " in chunk (" + ToString(ullStartPos + pos) + " in file)"); } - ssize_t writtenWords = write(pFile->hFileWrite, pData, WordCount * WordSize); + ssize_t writtenWords = write(io.hWrite, pData, WordCount * WordSize); if (writtenWords < 1) throw Exception("POSIX IO Error while trying to write chunk data"); writtenWords /= WordSize; #elif defined(WIN32) LARGE_INTEGER liFilePos; - liFilePos.QuadPart = ullStartPos + ullPos; - if (!SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) { - throw Exception("Could not seek to position " + ToString(ullPos) + - " in chunk (" + ToString(ullStartPos + ullPos) + " in file)"); + liFilePos.QuadPart = ullStartPos + pos; + if (!SetFilePointerEx(io.hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) { + throw Exception("Could not seek to position " + ToString(pos) + + " in chunk (" + ToString(ullStartPos + pos) + " in file)"); } DWORD writtenWords; - WriteFile(pFile->hFileWrite, pData, WordCount * WordSize, &writtenWords, NULL); //FIXME: does not work for writing buffers larger than 2GB (even though this should rarely be the case in practice) + WriteFile(io.hWrite, pData, WordCount * WordSize, &writtenWords, NULL); //FIXME: does not work for writing buffers larger than 2GB (even though this should rarely be the case in practice) if (writtenWords < 1) throw Exception("Windows IO Error while trying to write chunk data"); writtenWords /= WordSize; #else // standard C functions - if (fseeko(pFile->hFileWrite, ullStartPos + ullPos, SEEK_SET)) { - throw Exception("Could not seek to position " + ToString(ullPos) + - " in chunk (" + ToString(ullStartPos + ullPos) + " in file)"); + if (fseeko(io.hWrite, ullStartPos + pos, SEEK_SET)) { + throw Exception("Could not seek to position " + ToString(pos) + + " in chunk (" + ToString(ullStartPos + pos) + " in file)"); } - file_offset_t writtenWords = fwrite(pData, WordSize, WordCount, pFile->hFileWrite); + file_offset_t writtenWords = fwrite(pData, WordSize, WordCount, io.hWrite); #endif // POSIX SetPos(writtenWords * WordSize, stream_curpos); return writtenWords; @@ -455,13 +586,14 @@ * @param pData destination buffer * @param WordCount number of 8 Bit signed integers to read * @returns number of read integers - * @throws RIFF::Exception if an error occured or less than + * @throws RIFF::Exception if an error occurred or less than * \a WordCount integers could be read! + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::ReadInt8(int8_t* pData, file_offset_t WordCount) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadInt8(int8_t*,file_offset_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF return ReadSceptical(pData, WordCount, 1); } @@ -476,8 +608,9 @@ * @param pData source buffer (containing the data) * @param WordCount number of 8 Bit signed integers to write * @returns number of written integers - * @throws RIFF::Exception if an IO error occured + * @throws RIFF::Exception if an IO error occurred * @see Resize() + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::WriteInt8(int8_t* pData, file_offset_t WordCount) { return Write(pData, WordCount, 1); @@ -492,13 +625,14 @@ * @param pData destination buffer * @param WordCount number of 8 Bit unsigned integers to read * @returns number of read integers - * @throws RIFF::Exception if an error occured or less than + * @throws RIFF::Exception if an error occurred or less than * \a WordCount integers could be read! + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::ReadUint8(uint8_t* pData, file_offset_t WordCount) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadUint8(uint8_t*,file_offset_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF return ReadSceptical(pData, WordCount, 1); } @@ -513,8 +647,9 @@ * @param pData source buffer (containing the data) * @param WordCount number of 8 Bit unsigned integers to write * @returns number of written integers - * @throws RIFF::Exception if an IO error occured + * @throws RIFF::Exception if an IO error occurred * @see Resize() + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::WriteUint8(uint8_t* pData, file_offset_t WordCount) { return Write(pData, WordCount, 1); @@ -529,13 +664,14 @@ * @param pData destination buffer * @param WordCount number of 16 Bit signed integers to read * @returns number of read integers - * @throws RIFF::Exception if an error occured or less than + * @throws RIFF::Exception if an error occurred or less than * \a WordCount integers could be read! + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::ReadInt16(int16_t* pData, file_offset_t WordCount) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadInt16(int16_t*,file_offset_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF return ReadSceptical(pData, WordCount, 2); } @@ -550,8 +686,9 @@ * @param pData source buffer (containing the data) * @param WordCount number of 16 Bit signed integers to write * @returns number of written integers - * @throws RIFF::Exception if an IO error occured + * @throws RIFF::Exception if an IO error occurred * @see Resize() + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::WriteInt16(int16_t* pData, file_offset_t WordCount) { return Write(pData, WordCount, 2); @@ -566,13 +703,14 @@ * @param pData destination buffer * @param WordCount number of 8 Bit unsigned integers to read * @returns number of read integers - * @throws RIFF::Exception if an error occured or less than + * @throws RIFF::Exception if an error occurred or less than * \a WordCount integers could be read! + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::ReadUint16(uint16_t* pData, file_offset_t WordCount) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadUint16(uint16_t*,file_offset_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF return ReadSceptical(pData, WordCount, 2); } @@ -587,8 +725,9 @@ * @param pData source buffer (containing the data) * @param WordCount number of 16 Bit unsigned integers to write * @returns number of written integers - * @throws RIFF::Exception if an IO error occured + * @throws RIFF::Exception if an IO error occurred * @see Resize() + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::WriteUint16(uint16_t* pData, file_offset_t WordCount) { return Write(pData, WordCount, 2); @@ -603,13 +742,14 @@ * @param pData destination buffer * @param WordCount number of 32 Bit signed integers to read * @returns number of read integers - * @throws RIFF::Exception if an error occured or less than + * @throws RIFF::Exception if an error occurred or less than * \a WordCount integers could be read! + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::ReadInt32(int32_t* pData, file_offset_t WordCount) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadInt32(int32_t*,file_offset_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF return ReadSceptical(pData, WordCount, 4); } @@ -624,8 +764,9 @@ * @param pData source buffer (containing the data) * @param WordCount number of 32 Bit signed integers to write * @returns number of written integers - * @throws RIFF::Exception if an IO error occured + * @throws RIFF::Exception if an IO error occurred * @see Resize() + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::WriteInt32(int32_t* pData, file_offset_t WordCount) { return Write(pData, WordCount, 4); @@ -640,13 +781,14 @@ * @param pData destination buffer * @param WordCount number of 32 Bit unsigned integers to read * @returns number of read integers - * @throws RIFF::Exception if an error occured or less than + * @throws RIFF::Exception if an error occurred or less than * \a WordCount integers could be read! + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::ReadUint32(uint32_t* pData, file_offset_t WordCount) { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadUint32(uint32_t*,file_offset_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF return ReadSceptical(pData, WordCount, 4); } @@ -657,8 +799,9 @@ * * @param s destination string * @param size number of characters to read - * @throws RIFF::Exception if an error occured or less than + * @throws RIFF::Exception if an error occurred or less than * \a size characters could be read! + * @see File::IsIOPerThread() for multi-threaded streaming */ void Chunk::ReadString(String& s, int size) { char* buf = new char[size]; @@ -678,8 +821,9 @@ * @param pData source buffer (containing the data) * @param WordCount number of 32 Bit unsigned integers to write * @returns number of written integers - * @throws RIFF::Exception if an IO error occured + * @throws RIFF::Exception if an IO error occurred * @see Resize() + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::WriteUint32(uint32_t* pData, file_offset_t WordCount) { return Write(pData, WordCount, 4); @@ -690,12 +834,13 @@ * the chunk. * * @returns read integer word - * @throws RIFF::Exception if an error occured + * @throws RIFF::Exception if an error occurred + * @see File::IsIOPerThread() for multi-threaded streaming */ int8_t Chunk::ReadInt8() { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadInt8()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF int8_t word; ReadSceptical(&word,1,1); return word; @@ -706,12 +851,13 @@ * within the chunk. * * @returns read integer word - * @throws RIFF::Exception if an error occured + * @throws RIFF::Exception if an error occurred + * @see File::IsIOPerThread() for multi-threaded streaming */ uint8_t Chunk::ReadUint8() { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadUint8()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF uint8_t word; ReadSceptical(&word,1,1); return word; @@ -723,12 +869,13 @@ * needed. * * @returns read integer word - * @throws RIFF::Exception if an error occured + * @throws RIFF::Exception if an error occurred + * @see File::IsIOPerThread() for multi-threaded streaming */ int16_t Chunk::ReadInt16() { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadInt16()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF int16_t word; ReadSceptical(&word,1,2); return word; @@ -740,12 +887,13 @@ * needed. * * @returns read integer word - * @throws RIFF::Exception if an error occured + * @throws RIFF::Exception if an error occurred + * @see File::IsIOPerThread() for multi-threaded streaming */ uint16_t Chunk::ReadUint16() { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadUint16()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF uint16_t word; ReadSceptical(&word,1,2); return word; @@ -757,12 +905,13 @@ * needed. * * @returns read integer word - * @throws RIFF::Exception if an error occured + * @throws RIFF::Exception if an error occurred + * @see File::IsIOPerThread() for multi-threaded streaming */ int32_t Chunk::ReadInt32() { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadInt32()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF int32_t word; ReadSceptical(&word,1,4); return word; @@ -774,12 +923,13 @@ * needed. * * @returns read integer word - * @throws RIFF::Exception if an error occured + * @throws RIFF::Exception if an error occurred + * @see File::IsIOPerThread() for multi-threaded streamings */ uint32_t Chunk::ReadUint32() { - #if DEBUG + #if DEBUG_RIFF std::cout << "Chunk::ReadUint32()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF uint32_t word; ReadSceptical(&word,1,4); return word; @@ -805,29 +955,31 @@ * @returns a pointer to the data in RAM on success, NULL otherwise * @throws Exception if data buffer could not be enlarged * @see ReleaseChunkData() + * @see File::IsIOPerThread() for multi-threaded streaming */ void* Chunk::LoadChunkData() { if (!pChunkData && pFile->Filename != "" /*&& ulStartPos != 0*/) { + File::Handle hRead = pFile->FileHandle(); #if POSIX - if (lseek(pFile->hFileRead, ullStartPos, SEEK_SET) == -1) return NULL; + if (lseek(hRead, ullStartPos, SEEK_SET) == -1) return NULL; #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = ullStartPos; - if (!SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) return NULL; + if (!SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) return NULL; #else - if (fseeko(pFile->hFileRead, ullStartPos, SEEK_SET)) return NULL; + if (fseeko(hRead, ullStartPos, SEEK_SET)) return NULL; #endif // POSIX file_offset_t ullBufferSize = (ullCurrentChunkSize > ullNewChunkSize) ? ullCurrentChunkSize : ullNewChunkSize; pChunkData = new uint8_t[ullBufferSize]; if (!pChunkData) return NULL; memset(pChunkData, 0, ullBufferSize); #if POSIX - file_offset_t readWords = read(pFile->hFileRead, pChunkData, GetSize()); + file_offset_t readWords = read(hRead, pChunkData, GetSize()); #elif defined(WIN32) DWORD readWords; - ReadFile(pFile->hFileRead, pChunkData, GetSize(), &readWords, NULL); //FIXME: won't load chunks larger than 2GB ! + ReadFile(hRead, pChunkData, GetSize(), &readWords, NULL); //FIXME: won't load chunks larger than 2GB ! #else - file_offset_t readWords = fread(pChunkData, 1, GetSize(), pFile->hFileRead); + file_offset_t readWords = fread(pChunkData, 1, GetSize(), hRead); #endif // POSIX if (readWords != GetSize()) { delete[] pChunkData; @@ -838,8 +990,10 @@ uint8_t* pNewBuffer = new uint8_t[ullNewChunkSize]; if (!pNewBuffer) throw Exception("Could not enlarge chunk data buffer to " + ToString(ullNewChunkSize) + " bytes"); memset(pNewBuffer, 0 , ullNewChunkSize); - memcpy(pNewBuffer, pChunkData, ullChunkDataSize); - delete[] pChunkData; + if (pChunkData) { + memcpy(pNewBuffer, pChunkData, ullChunkDataSize); + delete[] pChunkData; + } pChunkData = pNewBuffer; ullChunkDataSize = ullNewChunkSize; } @@ -874,7 +1028,7 @@ * boundary! * * @param NewSize - new chunk body size in bytes (must be greater than zero) - * @throws RIFF::Exception if \a NewSize is less than 1 or Unrealistic large + * @throws RIFF::Exception if \a NewSize is less than 1 or unrealistic large * @see File::Save() */ void Chunk::Resize(file_offset_t NewSize) { @@ -898,12 +1052,15 @@ * @returns new write position in the "physical" file, that is * \a ullWritePos incremented by this chunk's new size * (including its header size of course) + * @see File::IsIOPerThread() for multi-threaded streaming */ file_offset_t Chunk::WriteChunk(file_offset_t ullWritePos, file_offset_t ullCurrentDataOffset, progress_t* pProgress) { const file_offset_t ullOriginalPos = ullWritePos; ullWritePos += CHUNK_HEADER_SIZE(pFile->FileOffsetSize); - if (pFile->Mode != stream_mode_read_write) + const File::HandlePair io = pFile->FileHandlePair(); + + if (io.Mode != stream_mode_read_write) throw Exception("Cannot write list chunk, file has to be opened in read+write mode"); // if the whole chunk body was loaded into RAM @@ -912,22 +1069,22 @@ LoadChunkData(); // write chunk data from RAM persistently to the file #if POSIX - lseek(pFile->hFileWrite, ullWritePos, SEEK_SET); - if (write(pFile->hFileWrite, pChunkData, ullNewChunkSize) != ullNewChunkSize) { + lseek(io.hWrite, ullWritePos, SEEK_SET); + if (write(io.hWrite, pChunkData, ullNewChunkSize) != ullNewChunkSize) { throw Exception("Writing Chunk data (from RAM) failed"); } #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = ullWritePos; - SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); + SetFilePointerEx(io.hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); DWORD dwBytesWritten; - WriteFile(pFile->hFileWrite, pChunkData, ullNewChunkSize, &dwBytesWritten, NULL); //FIXME: won't save chunks larger than 2GB ! + WriteFile(io.hWrite, pChunkData, ullNewChunkSize, &dwBytesWritten, NULL); //FIXME: won't save chunks larger than 2GB ! if (dwBytesWritten != ullNewChunkSize) { throw Exception("Writing Chunk data (from RAM) failed"); } #else - fseeko(pFile->hFileWrite, ullWritePos, SEEK_SET); - if (fwrite(pChunkData, 1, ullNewChunkSize, pFile->hFileWrite) != ullNewChunkSize) { + fseeko(io.hWrite, ullWritePos, SEEK_SET); + if (fwrite(pChunkData, 1, ullNewChunkSize, io.hWrite) != ullNewChunkSize) { throw Exception("Writing Chunk data (from RAM) failed"); } #endif // POSIX @@ -941,25 +1098,25 @@ int iBytesMoved = 1; #endif for (file_offset_t ullOffset = 0; ullToMove > 0 && iBytesMoved > 0; ullOffset += iBytesMoved, ullToMove -= iBytesMoved) { - iBytesMoved = (ullToMove < 4096) ? ullToMove : 4096; + iBytesMoved = (ullToMove < 4096) ? int(ullToMove) : 4096; #if POSIX - lseek(pFile->hFileRead, ullStartPos + ullCurrentDataOffset + ullOffset, SEEK_SET); - iBytesMoved = read(pFile->hFileRead, pCopyBuffer, iBytesMoved); - lseek(pFile->hFileWrite, ullWritePos + ullOffset, SEEK_SET); - iBytesMoved = write(pFile->hFileWrite, pCopyBuffer, iBytesMoved); + lseek(io.hRead, ullStartPos + ullCurrentDataOffset + ullOffset, SEEK_SET); + iBytesMoved = (int) read(io.hRead, pCopyBuffer, (size_t) iBytesMoved); + lseek(io.hWrite, ullWritePos + ullOffset, SEEK_SET); + iBytesMoved = (int) write(io.hWrite, pCopyBuffer, (size_t) iBytesMoved); #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = ullStartPos + ullCurrentDataOffset + ullOffset; - SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); - ReadFile(pFile->hFileRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); + SetFilePointerEx(io.hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); + ReadFile(io.hRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); liFilePos.QuadPart = ullWritePos + ullOffset; - SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); - WriteFile(pFile->hFileWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); + SetFilePointerEx(io.hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); + WriteFile(io.hWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); #else - fseeko(pFile->hFileRead, ullStartPos + ullCurrentDataOffset + ullOffset, SEEK_SET); - iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, pFile->hFileRead); - fseeko(pFile->hFileWrite, ullWritePos + ullOffset, SEEK_SET); - iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, pFile->hFileWrite); + fseeko(io.hRead, ullStartPos + ullCurrentDataOffset + ullOffset, SEEK_SET); + iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, io.hRead); + fseeko(io.hWrite, ullWritePos + ullOffset, SEEK_SET); + iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, io.hWrite); #endif } delete[] pCopyBuffer; @@ -970,27 +1127,28 @@ ullCurrentChunkSize = ullNewChunkSize; WriteHeader(ullOriginalPos); - __notify_progress(pProgress, 1.0); // notify done + if (pProgress) + __notify_progress(pProgress, 1.0); // notify done // update chunk's position pointers ullStartPos = ullOriginalPos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize); - ullPos = 0; + Chunk::__resetPos(); // add pad byte if needed if ((ullStartPos + ullNewChunkSize) % 2 != 0) { const char cPadByte = 0; #if POSIX - lseek(pFile->hFileWrite, ullStartPos + ullNewChunkSize, SEEK_SET); - write(pFile->hFileWrite, &cPadByte, 1); + lseek(io.hWrite, ullStartPos + ullNewChunkSize, SEEK_SET); + write(io.hWrite, &cPadByte, 1); #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = ullStartPos + ullNewChunkSize; - SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); + SetFilePointerEx(io.hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); DWORD dwBytesWritten; - WriteFile(pFile->hFileWrite, &cPadByte, 1, &dwBytesWritten, NULL); + WriteFile(io.hWrite, &cPadByte, 1, &dwBytesWritten, NULL); #else - fseeko(pFile->hFileWrite, ullStartPos + ullNewChunkSize, SEEK_SET); - fwrite(&cPadByte, 1, 1, pFile->hFileWrite); + fseeko(io.hWrite, ullStartPos + ullNewChunkSize, SEEK_SET); + fwrite(&cPadByte, 1, 1, io.hWrite); #endif return ullStartPos + ullNewChunkSize + 1; } @@ -999,7 +1157,9 @@ } void Chunk::__resetPos() { - ullPos = 0; + std::lock_guard lock(chunkPos.mutex); + chunkPos.ullPos = 0; + chunkPos.byThread.clear(); } @@ -1008,18 +1168,18 @@ // * List::List(File* pFile) : Chunk(pFile) { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::List(File* pFile)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF pSubChunks = NULL; pSubChunksMap = NULL; } List::List(File* pFile, file_offset_t StartPos, List* Parent) : Chunk(pFile, StartPos, Parent) { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::List(File*,file_offset_t,List*)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF pSubChunks = NULL; pSubChunksMap = NULL; ReadHeader(StartPos); @@ -1034,9 +1194,9 @@ } List::~List() { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::~List()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF DeleteChunkList(); } @@ -1069,9 +1229,9 @@ * that ID */ Chunk* List::GetSubChunk(uint32_t ChunkID) { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::GetSubChunk(uint32_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF if (!pSubChunksMap) LoadSubChunks(); return (*pSubChunksMap)[ChunkID]; } @@ -1079,7 +1239,7 @@ /** * Returns sublist chunk with list type \a ListType within this * chunk list. Use this method if you expect only one sublist chunk of - * that type in the list. It there are more than one, it's undetermined + * that type in the list. If there are more than one, it's undetermined * which one of them will be returned! If there are no sublists with * that desired list type, NULL will be returned. * @@ -1088,9 +1248,9 @@ * that type */ List* List::GetSubList(uint32_t ListType) { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::GetSubList(uint32_t)" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF if (!pSubChunks) LoadSubChunks(); ChunkList::iterator iter = pSubChunks->begin(); ChunkList::iterator end = pSubChunks->end(); @@ -1105,7 +1265,8 @@ } /** - * Returns the first subchunk within the list. You have to call this + * Returns the first subchunk within the list (which may be an ordinary + * chunk as well as a list chunk). You have to call this * method before you can call GetNextSubChunk(). Recall it when you want * to start from the beginning of the list again. * @@ -1113,25 +1274,26 @@ * otherwise */ Chunk* List::GetFirstSubChunk() { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::GetFirstSubChunk()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF if (!pSubChunks) LoadSubChunks(); ChunksIterator = pSubChunks->begin(); return (ChunksIterator != pSubChunks->end()) ? *ChunksIterator : NULL; } /** - * Returns the next subchunk within the list. You have to call + * Returns the next subchunk within the list (which may be an ordinary + * chunk as well as a list chunk). You have to call * GetFirstSubChunk() before you can use this method! * * @returns pointer to the next subchunk within the list or NULL if * end of list is reached */ Chunk* List::GetNextSubChunk() { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::GetNextSubChunk()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF if (!pSubChunks) return NULL; ChunksIterator++; return (ChunksIterator != pSubChunks->end()) ? *ChunksIterator : NULL; @@ -1147,9 +1309,9 @@ * otherwise */ List* List::GetFirstSubList() { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::GetFirstSubList()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF if (!pSubChunks) LoadSubChunks(); ListIterator = pSubChunks->begin(); ChunkList::iterator end = pSubChunks->end(); @@ -1169,9 +1331,9 @@ * end of list is reached */ List* List::GetNextSubList() { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::GetNextSubList()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF if (!pSubChunks) return NULL; if (ListIterator == pSubChunks->end()) return NULL; ListIterator++; @@ -1184,9 +1346,9 @@ } /** - * Returns number of subchunks within the list. + * Returns number of subchunks within the list (including list chunks). */ - unsigned int List::CountSubChunks() { + size_t List::CountSubChunks() { if (!pSubChunks) LoadSubChunks(); return pSubChunks->size(); } @@ -1195,8 +1357,8 @@ * Returns number of subchunks within the list with chunk ID * \a ChunkId. */ - unsigned int List::CountSubChunks(uint32_t ChunkID) { - unsigned int result = 0; + size_t List::CountSubChunks(uint32_t ChunkID) { + size_t result = 0; if (!pSubChunks) LoadSubChunks(); ChunkList::iterator iter = pSubChunks->begin(); ChunkList::iterator end = pSubChunks->end(); @@ -1212,7 +1374,7 @@ /** * Returns number of sublists within the list. */ - unsigned int List::CountSubLists() { + size_t List::CountSubLists() { return CountSubChunks(CHUNK_ID_LIST); } @@ -1220,8 +1382,8 @@ * Returns number of sublists within the list with list type * \a ListType */ - unsigned int List::CountSubLists(uint32_t ListType) { - unsigned int result = 0; + size_t List::CountSubLists(uint32_t ListType) { + size_t result = 0; if (!pSubChunks) LoadSubChunks(); ChunkList::iterator iter = pSubChunks->begin(); ChunkList::iterator end = pSubChunks->end(); @@ -1373,28 +1535,31 @@ } void List::ReadHeader(file_offset_t filePos) { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::Readheader(file_offset_t) "; - #endif // DEBUG + #endif // DEBUG_RIFF Chunk::ReadHeader(filePos); if (ullCurrentChunkSize < 4) return; ullNewChunkSize = ullCurrentChunkSize -= 4; + + const File::Handle hRead = pFile->FileHandle(); + #if POSIX - lseek(pFile->hFileRead, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET); - read(pFile->hFileRead, &ListType, 4); + lseek(hRead, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET); + read(hRead, &ListType, 4); #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize); - SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); + SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); DWORD dwBytesRead; - ReadFile(pFile->hFileRead, &ListType, 4, &dwBytesRead, NULL); + ReadFile(hRead, &ListType, 4, &dwBytesRead, NULL); #else - fseeko(pFile->hFileRead, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET); - fread(&ListType, 4, 1, pFile->hFileRead); + fseeko(hRead, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET); + fread(&ListType, 4, 1, hRead); #endif // POSIX - #if DEBUG + #if DEBUG_RIFF std::cout << "listType=" << convertToString(ListType) << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF if (!pFile->bEndianNative) { //swapBytes_32(&ListType); } @@ -1405,48 +1570,54 @@ ullNewChunkSize += 4; Chunk::WriteHeader(filePos); ullNewChunkSize -= 4; // just revert the +4 incrementation + + const File::Handle hWrite = pFile->FileWriteHandle(); + #if POSIX - lseek(pFile->hFileWrite, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET); - write(pFile->hFileWrite, &ListType, 4); + lseek(hWrite, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET); + write(hWrite, &ListType, 4); #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize); - SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); + SetFilePointerEx(hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); DWORD dwBytesWritten; - WriteFile(pFile->hFileWrite, &ListType, 4, &dwBytesWritten, NULL); + WriteFile(hWrite, &ListType, 4, &dwBytesWritten, NULL); #else - fseeko(pFile->hFileWrite, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET); - fwrite(&ListType, 4, 1, pFile->hFileWrite); + fseeko(hWrite, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET); + fwrite(&ListType, 4, 1, hWrite); #endif // POSIX } void List::LoadSubChunks(progress_t* pProgress) { - #if DEBUG + #if DEBUG_RIFF std::cout << "List::LoadSubChunks()"; - #endif // DEBUG + #endif // DEBUG_RIFF if (!pSubChunks) { pSubChunks = new ChunkList(); pSubChunksMap = new ChunkMap(); - #if defined(WIN32) - if (pFile->hFileRead == INVALID_HANDLE_VALUE) return; - #else - if (!pFile->hFileRead) return; - #endif - file_offset_t ullOriginalPos = GetPos(); + + const File::Handle hRead = pFile->FileHandle(); + if (!_isValidHandle(hRead)) return; + + const file_offset_t ullOriginalPos = GetPos(); SetPos(0); // jump to beginning of list chunk body while (RemainingBytes() >= CHUNK_HEADER_SIZE(pFile->FileOffsetSize)) { Chunk* ck; uint32_t ckid; - Read(&ckid, 4, 1); - #if DEBUG + // return value check is required here to prevent a potential + // garbage data use of 'ckid' below in case Read() failed + if (Read(&ckid, 4, 1) != 4) + throw Exception("LoadSubChunks(): Failed reading RIFF chunk ID"); + #if DEBUG_RIFF std::cout << " ckid=" << convertToString(ckid) << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF + const file_offset_t pos = GetPos(); if (ckid == CHUNK_ID_LIST) { - ck = new RIFF::List(pFile, ullStartPos + ullPos - 4, this); + ck = new RIFF::List(pFile, ullStartPos + pos - 4, this); SetPos(ck->GetSize() + LIST_HEADER_SIZE(pFile->FileOffsetSize) - 4, RIFF::stream_curpos); } else { // simple chunk - ck = new RIFF::Chunk(pFile, ullStartPos + ullPos - 4, this); + ck = new RIFF::Chunk(pFile, ullStartPos + pos - 4, this); SetPos(ck->GetSize() + CHUNK_HEADER_SIZE(pFile->FileOffsetSize) - 4, RIFF::stream_curpos); } pSubChunks->push_back(ck); @@ -1455,20 +1626,25 @@ } SetPos(ullOriginalPos); // restore position before this call } - __notify_progress(pProgress, 1.0); // notify done + if (pProgress) + __notify_progress(pProgress, 1.0); // notify done } void List::LoadSubChunksRecursively(progress_t* pProgress) { - const int n = CountSubLists(); + const int n = (int) 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); + if (pProgress) { + // divide local progress into subprogress + progress_t subprogress; + __divide_progress(pProgress, &subprogress, n, i); + // do the actual work + pList->LoadSubChunksRecursively(&subprogress); + } else + pList->LoadSubChunksRecursively(NULL); } - __notify_progress(pProgress, 1.0); // notify done + if (pProgress) + __notify_progress(pProgress, 1.0); // notify done } /** @brief Write list chunk persistently e.g. to disk. @@ -1490,19 +1666,22 @@ const file_offset_t ullOriginalPos = ullWritePos; ullWritePos += LIST_HEADER_SIZE(pFile->FileOffsetSize); - if (pFile->Mode != stream_mode_read_write) + if (pFile->GetMode() != stream_mode_read_write) throw Exception("Cannot write list chunk, file has to be opened in read+write mode"); // write all subchunks (including sub list chunks) recursively if (pSubChunks) { - int i = 0; - const int n = pSubChunks->size(); + size_t i = 0; + const size_t 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 - ullWritePos = (*iter)->WriteChunk(ullWritePos, ullCurrentDataOffset, &subprogress); + if (pProgress) { + // divide local progress into subprogress for loading current Instrument + progress_t subprogress; + __divide_progress(pProgress, &subprogress, n, i); + // do the actual work + ullWritePos = (*iter)->WriteChunk(ullWritePos, ullCurrentDataOffset, &subprogress); + } else + ullWritePos = (*iter)->WriteChunk(ullWritePos, ullCurrentDataOffset, NULL); } } @@ -1513,7 +1692,8 @@ // offset of this list chunk in new written file may have changed ullStartPos = ullOriginalPos + LIST_HEADER_SIZE(pFile->FileOffsetSize); - __notify_progress(pProgress, 1.0); // notify done + if (pProgress) + __notify_progress(pProgress, 1.0); // notify done return ullWritePos; } @@ -1558,11 +1738,11 @@ FileOffsetPreference(offset_size_auto) { #if defined(WIN32) - hFileRead = hFileWrite = INVALID_HANDLE_VALUE; + io.hRead = io.hWrite = INVALID_HANDLE_VALUE; #else - hFileRead = hFileWrite = 0; + io.hRead = io.hWrite = 0; #endif - Mode = stream_mode_closed; + io.Mode = stream_mode_closed; bEndianNative = true; ListType = FileType; FileOffsetSize = 4; @@ -1574,16 +1754,16 @@ * Loads an existing RIFF file with all its chunks. * * @param path - path and file name of the RIFF file to open - * @throws RIFF::Exception if error occured while trying to load the + * @throws RIFF::Exception if error occurred while trying to load the * given RIFF file */ File::File(const String& path) : List(this), Filename(path), bIsNewFile(false), Layout(layout_standard), FileOffsetPreference(offset_size_auto) { - #if DEBUG + #if DEBUG_RIFF std::cout << "File::File("<second : + io.byThread[tid] = { + #if defined(WIN32) + .hRead = INVALID_HANDLE_VALUE, + .hWrite = INVALID_HANDLE_VALUE, + #else + .hRead = 0, + .hWrite = 0, + #endif + .Mode = stream_mode_closed + }; + } + + /** + * Returns the OS dependent file I/O read and write handles intended to be + * used by the calling thread. + * + * @see File::IsIOPerThread() for multi-threaded streaming + */ + File::HandlePair File::FileHandlePair() const { + std::lock_guard lock(io.mutex); + if (io.byThread.empty()) return io; + const std::thread::id tid = std::this_thread::get_id(); + const auto it = io.byThread.find(tid); + return (it != io.byThread.end()) ? + it->second : + io.byThread[tid] = { + #if defined(WIN32) + .hRead = INVALID_HANDLE_VALUE, + .hWrite = INVALID_HANDLE_VALUE, + #else + .hRead = 0, + .hWrite = 0, + #endif + .Mode = stream_mode_closed + }; + } + + /** + * Returns the OS dependent file I/O read handle intended to be used by the + * calling thread. + * + * @see File::IsIOPerThread() for multi-threaded streaming + */ + File::Handle File::FileHandle() const { + return FileHandlePair().hRead; + } + + /** + * Returns the OS dependent file I/O write handle intended to be used by the + * calling thread. + * + * @see File::IsIOPerThread() for multi-threaded streaming + */ + File::Handle File::FileWriteHandle() const { + return FileHandlePair().hWrite; + } + + /** + * Returns the file I/O mode currently being available for the calling + * thread for this RIFF file (either ro, rw or closed). + * + * @see File::IsIOPerThread() for multi-threaded streaming + */ stream_mode_t File::GetMode() const { - return Mode; + return FileHandlePair().Mode; } layout_t File::GetLayout() const { @@ -1730,23 +1987,26 @@ * @param NewMode - new file access mode * @returns true if mode was changed, false if current mode already * equals new mode - * @throws RIFF::Exception if new file access mode is unknown + * @throws RIFF::Exception if file could not be opened in requested file + * access mode or if passed access mode is unknown + * @see File::IsIOPerThread() for multi-threaded streaming */ bool File::SetMode(stream_mode_t NewMode) { - if (NewMode != Mode) { + std::lock_guard lock(io.mutex); + HandlePair& io = FileHandlePairUnsafeRef(); + if (NewMode != io.Mode) { switch (NewMode) { case stream_mode_read: + if (_isValidHandle(io.hRead)) _close(io.hRead); #if POSIX - if (hFileRead) close(hFileRead); - hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK); - if (hFileRead == -1) { - hFileRead = hFileWrite = 0; + io.hRead = io.hWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK); + if (io.hRead == -1) { + io.hRead = io.hWrite = 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( + io.hRead = io.hWrite = CreateFile( Filename.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, @@ -1754,29 +2014,27 @@ FILE_FLAG_RANDOM_ACCESS, NULL ); - if (hFileRead == INVALID_HANDLE_VALUE) { - hFileRead = hFileWrite = INVALID_HANDLE_VALUE; + if (io.hRead == INVALID_HANDLE_VALUE) { + io.hRead = io.hWrite = INVALID_HANDLE_VALUE; throw Exception("Could not (re)open file \"" + Filename + "\" in read mode"); } #else - if (hFileRead) fclose(hFileRead); - hFileRead = hFileWrite = fopen(Filename.c_str(), "rb"); - if (!hFileRead) throw Exception("Could not (re)open file \"" + Filename + "\" in read mode"); + io.hRead = io.hWrite = fopen(Filename.c_str(), "rb"); + if (!io.hRead) throw Exception("Could not (re)open file \"" + Filename + "\" in read mode"); #endif __resetPos(); // reset read/write position of ALL 'Chunk' objects break; case stream_mode_read_write: + if (_isValidHandle(io.hRead)) _close(io.hRead); #if POSIX - if (hFileRead) close(hFileRead); - hFileRead = hFileWrite = open(Filename.c_str(), O_RDWR | O_NONBLOCK); - if (hFileRead == -1) { - hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK); + io.hRead = io.hWrite = open(Filename.c_str(), O_RDWR | O_NONBLOCK); + if (io.hRead == -1) { + io.hRead = io.hWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK); 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( + io.hRead = io.hWrite = CreateFile( Filename.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, @@ -1785,8 +2043,8 @@ FILE_FLAG_RANDOM_ACCESS, NULL ); - if (hFileRead == INVALID_HANDLE_VALUE) { - hFileRead = hFileWrite = CreateFile( + if (io.hRead == INVALID_HANDLE_VALUE) { + io.hRead = io.hWrite = CreateFile( Filename.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, @@ -1797,32 +2055,29 @@ throw Exception("Could not (re)open file \"" + Filename + "\" in read+write mode"); } #else - if (hFileRead) fclose(hFileRead); - hFileRead = hFileWrite = fopen(Filename.c_str(), "r+b"); - if (!hFileRead) { - hFileRead = hFileWrite = fopen(Filename.c_str(), "rb"); + io.hRead = io.hWrite = fopen(Filename.c_str(), "r+b"); + if (!io.hRead) { + io.hRead = io.hWrite = fopen(Filename.c_str(), "rb"); throw Exception("Could not open file \"" + Filename + "\" in read+write mode"); } #endif __resetPos(); // reset read/write position of ALL 'Chunk' objects break; case stream_mode_closed: + if (_isValidHandle(io.hRead)) _close(io.hRead); + if (_isValidHandle(io.hWrite)) _close(io.hWrite); #if POSIX - if (hFileRead) close(hFileRead); - if (hFileWrite) close(hFileWrite); + io.hRead = io.hWrite = 0; #elif defined(WIN32) - if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead); - if (hFileWrite != INVALID_HANDLE_VALUE) CloseHandle(hFileWrite); + io.hRead = io.hWrite = INVALID_HANDLE_VALUE; #else - if (hFileRead) fclose(hFileRead); - if (hFileWrite) fclose(hFileWrite); + io.hRead = io.hWrite = NULL; #endif - hFileRead = hFileWrite = 0; break; default: throw Exception("Unknown file access mode"); } - Mode = NewMode; + io.Mode = NewMode; return true; } return false; @@ -1852,7 +2107,8 @@ * * @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 + * chunk or any kind of IO error occurred + * @see File::IsIOPerThread() for multi-threaded streaming */ 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) @@ -1860,7 +2116,7 @@ throw Exception("Saving a RIFF file with layout_flat is not implemented yet"); // make sure the RIFF tree is built (from the original file) - { + if (pProgress) { // divide progress into subprogress progress_t subprogress; __divide_progress(pProgress, &subprogress, 3.f, 0.f); // arbitrarily subdivided into 1/3 of total progress @@ -1868,7 +2124,8 @@ LoadSubChunksRecursively(&subprogress); // notify subprogress done __notify_progress(&subprogress, 1.f); - } + } else + LoadSubChunksRecursively(NULL); // reopen file in write mode SetMode(stream_mode_read_write); @@ -1883,6 +2140,10 @@ // the RIFF file offset size to be used accordingly for all chunks FileOffsetSize = FileOffsetSizeFor(newFileSize); + const HandlePair io = FileHandlePair(); + const Handle hRead = io.hRead; + const Handle hWrite = io.hWrite; + // to be able to save the whole file without loading everything into // RAM and without having to store the data in a temporary file, we // enlarge the file with the overall positive file size change, @@ -1898,7 +2159,8 @@ // divide progress into subprogress progress_t subprogress; - __divide_progress(pProgress, &subprogress, 3.f, 1.f); // arbitrarily subdivided into 1/3 of total progress + if (pProgress) + __divide_progress(pProgress, &subprogress, 3.f, 1.f); // arbitrarily subdivided into 1/3 of total progress // ... we enlarge this file first ... ResizeFile(newFileSize); @@ -1908,54 +2170,58 @@ #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; + ssize_t iBytesMoved = 1; #endif for (file_offset_t ullPos = workingFileSize, iNotif = 0; iBytesMoved > 0; ++iNotif) { iBytesMoved = (ullPos < 4096) ? ullPos : 4096; ullPos -= iBytesMoved; #if POSIX - lseek(hFileRead, ullPos, SEEK_SET); - iBytesMoved = read(hFileRead, pCopyBuffer, iBytesMoved); - lseek(hFileWrite, ullPos + positiveSizeDiff, SEEK_SET); - iBytesMoved = write(hFileWrite, pCopyBuffer, iBytesMoved); + lseek(hRead, ullPos, SEEK_SET); + iBytesMoved = read(hRead, pCopyBuffer, iBytesMoved); + lseek(hWrite, ullPos + positiveSizeDiff, SEEK_SET); + iBytesMoved = write(hWrite, pCopyBuffer, iBytesMoved); #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = ullPos; - SetFilePointerEx(hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); - ReadFile(hFileRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); + SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); + ReadFile(hRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); liFilePos.QuadPart = ullPos + positiveSizeDiff; - SetFilePointerEx(hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); - WriteFile(hFileWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); + SetFilePointerEx(hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN); + WriteFile(hWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL); #else - fseeko(hFileRead, ullPos, SEEK_SET); - iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, hFileRead); - fseeko(hFileWrite, ullPos + positiveSizeDiff, SEEK_SET); - iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, hFileWrite); + fseeko(hRead, ullPos, SEEK_SET); + iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, hRead); + fseeko(hWrite, ullPos + positiveSizeDiff, SEEK_SET); + iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, hWrite); #endif - if (!(iNotif % 8) && iBytesMoved > 0) + if (pProgress && !(iNotif % 8) && iBytesMoved > 0) __notify_progress(&subprogress, float(workingFileSize - ullPos) / float(workingFileSize)); } delete[] pCopyBuffer; if (iBytesMoved < 0) throw Exception("Could not modify file while trying to enlarge it"); - __notify_progress(&subprogress, 1.f); // notify subprogress done + if (pProgress) + __notify_progress(&subprogress, 1.f); // notify subprogress done } // 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 + if (pProgress) + __divide_progress(pProgress, &subprogress, 3.f, 2.f); // arbitrarily subdivided into 1/3 of total progress // do the actual work - const file_offset_t finalSize = WriteChunk(0, positiveSizeDiff, &subprogress); - const file_offset_t finalActualSize = __GetFileSize(hFileWrite); + const file_offset_t finalSize = WriteChunk(0, positiveSizeDiff, pProgress ? &subprogress : NULL); + const file_offset_t finalActualSize = __GetFileSize(hWrite); // notify subprogress done - __notify_progress(&subprogress, 1.f); + if (pProgress) + __notify_progress(&subprogress, 1.f); // resize file to the final size if (finalSize < finalActualSize) ResizeFile(finalSize); - __notify_progress(pProgress, 1.0); // notify done + if (pProgress) + __notify_progress(pProgress, 1.0); // notify done } /** @brief Save changes to another file. @@ -1971,6 +2237,7 @@ * * @param path - path and file name where everything should be written to * @param pProgress - optional: callback function for progress notification + * @see File::IsIOPerThread() for multi-threaded streaming */ 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 @@ -1980,7 +2247,7 @@ throw Exception("Saving a RIFF file with layout_flat is not implemented yet"); // make sure the RIFF tree is built (from the original file) - { + if (pProgress) { // divide progress into subprogress progress_t subprogress; __divide_progress(pProgress, &subprogress, 2.f, 0.f); // arbitrarily subdivided into 1/2 of total progress @@ -1988,35 +2255,42 @@ LoadSubChunksRecursively(&subprogress); // notify subprogress done __notify_progress(&subprogress, 1.f); - } + } else + LoadSubChunksRecursively(NULL); 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 == -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"); - } - #else - hFileWrite = fopen(path.c_str(), "w+b"); - if (!hFileWrite) { - hFileWrite = hFileRead; - throw Exception("Could not open file \"" + path + "\" for writing"); + + { + std::lock_guard lock(io.mutex); + HandlePair& io = FileHandlePairUnsafeRef(); + + // open the other (new) file for writing and truncate it to zero size + #if POSIX + io.hWrite = open(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP); + if (io.hWrite == -1) { + io.hWrite = io.hRead; + String sError = strerror(errno); + throw Exception("Could not open file \"" + path + "\" for writing: " + sError); + } + #elif defined(WIN32) + io.hWrite = CreateFile( + path.c_str(), GENERIC_WRITE, FILE_SHARE_READ, + NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_RANDOM_ACCESS, NULL + ); + if (io.hWrite == INVALID_HANDLE_VALUE) { + io.hWrite = io.hRead; + throw Exception("Could not open file \"" + path + "\" for writing"); + } + #else + io.hWrite = fopen(path.c_str(), "w+b"); + if (!io.hWrite) { + io.hWrite = io.hRead; + throw Exception("Could not open file \"" + path + "\" for writing"); + } + #endif // POSIX + io.Mode = stream_mode_read_write; } - #endif // POSIX - Mode = stream_mode_read_write; // get the overall file size required to save this file const file_offset_t newFileSize = GetRequiredFileSize(FileOffsetPreference); @@ -2027,7 +2301,7 @@ // write complete RIFF tree to the other (new) file file_offset_t ullTotalSize; - { + if (pProgress) { // divide progress into subprogress progress_t subprogress; __divide_progress(pProgress, &subprogress, 2.f, 1.f); // arbitrarily subdivided into 1/2 of total progress @@ -2035,40 +2309,43 @@ ullTotalSize = WriteChunk(0, 0, &subprogress); // notify subprogress done __notify_progress(&subprogress, 1.f); - } - file_offset_t ullActualSize = __GetFileSize(hFileWrite); + } else + ullTotalSize = WriteChunk(0, 0, NULL); + + const file_offset_t ullActualSize = __GetFileSize(FileWriteHandle()); // resize file to the final size (if the file was originally larger) if (ullActualSize > ullTotalSize) ResizeFile(ullTotalSize); - #if POSIX - if (hFileWrite) close(hFileWrite); - #elif defined(WIN32) - if (hFileWrite != INVALID_HANDLE_VALUE) CloseHandle(hFileWrite); - #else - if (hFileWrite) fclose(hFileWrite); - #endif - hFileWrite = hFileRead; + { + std::lock_guard lock(io.mutex); + HandlePair& io = FileHandlePairUnsafeRef(); - // 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 ... + if (_isValidHandle(io.hWrite)) _close(io.hWrite); + io.hWrite = io.hRead; + + // associate new file with this File object from now on + Filename = path; + bIsNewFile = false; + io.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 + if (pProgress) + __notify_progress(pProgress, 1.0); // notify done } void File::ResizeFile(file_offset_t ullNewSize) { + const Handle hWrite = FileWriteHandle(); #if POSIX - if (ftruncate(hFileWrite, ullNewSize) < 0) + if (ftruncate(hWrite, ullNewSize) < 0) throw Exception("Could not resize file \"" + Filename + "\""); #elif defined(WIN32) LARGE_INTEGER liFilePos; liFilePos.QuadPart = ullNewSize; if ( - !SetFilePointerEx(hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN) || - !SetEndOfFile(hFileWrite) + !SetFilePointerEx(hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN) || + !SetEndOfFile(hWrite) ) throw Exception("Could not resize file \"" + Filename + "\""); #else # error Sorry, this version of libgig only supports POSIX and Windows systems yet. @@ -2077,9 +2354,9 @@ } File::~File() { - #if DEBUG + #if DEBUG_RIFF std::cout << "File::~File()" << std::endl; - #endif // DEBUG + #endif // DEBUG_RIFF Cleanup(); } @@ -2092,13 +2369,8 @@ } 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 + const Handle hRead = FileHandle(); + if (_isValidHandle(hRead)) _close(hRead); DeleteChunkList(); pFile = NULL; } @@ -2111,8 +2383,9 @@ */ file_offset_t File::GetCurrentFileSize() const { file_offset_t size = 0; + const Handle hRead = FileHandle(); try { - size = __GetFileSize(hFileRead); + size = __GetFileSize(hRead); } catch (...) { size = 0; } @@ -2220,6 +2493,73 @@ return FileOffsetSizeFor(GetCurrentFileSize()); } + /** @brief Whether file streams are independent for each thread. + * + * All file I/O operations like reading from a RIFF chunk body (e.g. by + * calling Chunk::Read(), Chunk::ReadInt8()), writing to a RIFF chunk body + * (e.g. by calling Chunk::Write(), Chunk::WriteInt8()) or saving the + * current RIFF tree structure to some file (e.g. by calling Save()) + * operate on a file I/O stream state, i.e. there is a "current" file + * read/write position and reading/writing by a certain amount of bytes + * automatically advances that "current" file position. + * + * By default there is only one stream state for a RIFF::File object, which + * is not an issue as long as only one thread is using the RIFF::File + * object at a time (which might also be the case in a collaborative / + * coroutine multi-threaded scenario). + * + * If however a RIFF::File object is read/written @b simultaniously by + * multiple threads this can lead to undefined behaviour as the individual + * threads would concurrently alter the file stream position. For such a + * concurrent multithreaded file I/O scenario @c SetIOPerThread(true) might + * be enabled which causes each thread to automatically use its own file + * stream state. + * + * @returns true if each thread has its own file stream state + * (default: false) + * @see SetIOPerThread() + */ + bool File::IsIOPerThread() const { + std::lock_guard lock(io.mutex); + return !io.byThread.empty(); + } + + /** @brief Enable/disable file streams being independent for each thread. + * + * By enabling this feature (default off) each thread will automatically use + * its own file I/O stream state for allowing simultanious multi-threaded + * file read/write operations. + * + * @b NOTE: After having enabled this feature, the individual threads must + * at least once check GetState() and if their file I/O stream is yet closed + * they must call SetMode() (i.e. once) respectively to open their own file + * handles before being able to use any of the Read() or Write() methods. + * + * @param enable - @c true: one independent stream state per thread, + * @c false: only one stream in total shared by @b all threads + * @see IsIOPerThread() for more details about this feature + */ + void File::SetIOPerThread(bool enable) { + std::lock_guard lock(io.mutex); + if (!io.byThread.empty() == enable) return; + if (enable) { + const std::thread::id tid = std::this_thread::get_id(); + io.byThread[tid] = io; + } else { + // retain an arbitrary handle pair, close all other handle pairs + for (auto it = io.byThread.begin(); it != io.byThread.end(); ++it) { + if (it == io.byThread.begin()) { + io.hRead = it->second.hRead; + io.hWrite = it->second.hWrite; + } else { + _close(it->second.hRead); + _close(it->second.hWrite); + } + } + io.byThread.clear(); + } + } + #if POSIX file_offset_t File::__GetFileSize(int hFile) const { struct stat filestat; @@ -2249,10 +2589,32 @@ // *************** Exception *************** // * + Exception::Exception() { + } + + Exception::Exception(String format, ...) { + va_list arg; + va_start(arg, format); + Message = assemble(format, arg); + va_end(arg); + } + + Exception::Exception(String format, va_list arg) { + Message = assemble(format, arg); + } + void Exception::PrintMessage() { std::cout << "RIFF::Exception: " << Message << std::endl; } + String Exception::assemble(String format, va_list arg) { + char* buf = NULL; + vasprintf(&buf, format.c_str(), arg); + String s = buf; + free(buf); + return s; + } + // *************** functions *************** // *