--- libgig/trunk/src/RIFF.cpp 2009/07/30 08:16:02 1953 +++ libgig/trunk/src/RIFF.cpp 2015/01/03 21:44:42 2685 @@ -2,7 +2,7 @@ * * * libgig - C++ cross-platform Gigasampler format file access library * * * - * Copyright (C) 2003-2009 by Christian Schoenebeck * + * Copyright (C) 2003-2014 by Christian Schoenebeck * * * * * * This library is free software; you can redistribute it and/or modify * @@ -29,6 +29,10 @@ #include "helper.h" +#if POSIX +# include +#endif + namespace RIFF { // *************** Internal functions ************** @@ -50,6 +54,18 @@ +// *************** progress_t *************** +// * + + progress_t::progress_t() { + callback = NULL; + custom = NULL; + __range_min = 0.0f; + __range_max = 1.0f; + } + + + // *************** Chunk ************** // * @@ -281,13 +297,18 @@ #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 (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 if (lseek(pFile->hFileRead, ulStartPos + ulPos, SEEK_SET) < 0) return 0; - unsigned long readWords = read(pFile->hFileRead, pData, WordCount * WordSize); - if (readWords < 1) return 0; + ssize_t readWords = read(pFile->hFileRead, pData, WordCount * WordSize); + if (readWords < 1) { + #if DEBUG + std::cerr << "POSIX read() failed: " << strerror(errno) << std::endl << std::flush; + #endif // DEBUG + return 0; + } readWords /= WordSize; #elif defined(WIN32) if (SetFilePointer(pFile->hFileRead, ulStartPos + ulPos, NULL/*32 bit*/, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return 0; @@ -297,7 +318,7 @@ 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); + size_t readWords = fread(pData, WordSize, WordCount, pFile->hFileRead); #endif // POSIX if (!pFile->bEndianNative && WordSize != 1) { switch (WordSize) { @@ -595,6 +616,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 @@ -734,7 +772,7 @@ * @see ReleaseChunkData() */ void* Chunk::LoadChunkData() { - if (!pChunkData && pFile->Filename != "" && ulStartPos != 0) { + if (!pChunkData && pFile->Filename != "" /*&& ulStartPos != 0*/) { #if POSIX if (lseek(pFile->hFileRead, ulStartPos, SEEK_SET) == -1) return NULL; #elif defined(WIN32) @@ -818,11 +856,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; @@ -861,7 +900,7 @@ #else int iBytesMoved = 1; #endif - for (unsigned long ulOffset = 0; iBytesMoved > 0; ulOffset += iBytesMoved, ulToMove -= iBytesMoved) { + 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); @@ -888,6 +927,8 @@ CurrentChunkSize = NewChunkSize; WriteHeader(ulOriginalPos); + __notify_progress(pProgress, 1.0); // notify done + // update chunk's position pointers ulStartPos = ulOriginalPos + CHUNK_HEADER_SIZE; ulPos = 0; @@ -1174,9 +1215,9 @@ return pNewChunk; } - /** @brief Moves a sub chunk. + /** @brief Moves a sub chunk witin this list. * - * Moves a sub chunk from one position in a list to another + * 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. * @@ -1192,6 +1233,38 @@ 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 @@ -1283,7 +1356,7 @@ #endif // POSIX } - void List::LoadSubChunks() { + void List::LoadSubChunks(progress_t* pProgress) { #if DEBUG std::cout << "List::LoadSubChunks()"; #endif // DEBUG @@ -1318,11 +1391,20 @@ } SetPos(uiOriginalPos); // restore position before this call } + __notify_progress(pProgress, 1.0); // notify done } - void List::LoadSubChunksRecursively() { - for (List* pList = GetFirstSubList(); pList; pList = GetNextSubList()) - pList->LoadSubChunksRecursively(); + 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. @@ -1335,11 +1417,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; @@ -1348,8 +1431,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); } } @@ -1360,6 +1449,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; } @@ -1402,7 +1493,9 @@ * @param FileType - four-byte identifier of the RIFF file type * @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) @@ -1424,18 +1517,82 @@ * @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; - throw RIFF::Exception("Can't open \"" + path + "\""); + String sError = strerror(errno); + throw RIFF::Exception("Can't open \"" + path + "\": " + sError); } #elif defined(WIN32) hFileRead = hFileWrite = CreateFile( @@ -1454,21 +1611,47 @@ 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 && ChunkID != CHUNK_ID_RIFX) { - 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 @@ -1486,9 +1669,10 @@ #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; - throw Exception("Could not (re)open file \"" + Filename + "\" in read mode"); + 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); @@ -1515,9 +1699,10 @@ #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); @@ -1597,12 +1782,25 @@ * 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) - LoadSubChunksRecursively(); + { + // 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); @@ -1630,6 +1828,10 @@ // 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); @@ -1641,7 +1843,7 @@ #else int iBytesMoved = 1; #endif - for (unsigned long ulPos = ulFileSize; iBytesMoved > 0; ) { + for (unsigned long ulPos = ulFileSize, iNotif = 0; iBytesMoved > 0; ++iNotif) { iBytesMoved = (ulPos < 4096) ? ulPos : 4096; ulPos -= iBytesMoved; #if POSIX @@ -1660,20 +1862,33 @@ 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(); + + __notify_progress(pProgress, 1.0); // notify done } /** @brief Save changes to another file. @@ -1688,20 +1903,34 @@ * 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 + //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) - LoadSubChunksRecursively(); + { + // 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 (Filename.length() > 0) SetMode(stream_mode_read); + 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; - throw Exception("Could not open file \"" + path + "\" for writing"); + String sError = strerror(errno); + throw Exception("Could not open file \"" + path + "\" for writing: " + sError); } #elif defined(WIN32) hFileWrite = CreateFile( @@ -1723,7 +1952,16 @@ 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) @@ -1743,8 +1981,11 @@ // 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) { @@ -1766,6 +2007,18 @@ #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)