libgig  4.5.0
RIFF.cpp
1 /***************************************************************************
2  * *
3  * libgig - C++ cross-platform Gigasampler format file access library *
4  * *
5  * Copyright (C) 2003-2025 by Christian Schoenebeck *
6  * <cuse@users.sourceforge.net> *
7  * *
8  * This library is free software; you can redistribute it and/or modify *
9  * it under the terms of the GNU General Public License as published by *
10  * the Free Software Foundation; either version 2 of the License, or *
11  * (at your option) any later version. *
12  * *
13  * This library is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License *
19  * along with this library; if not, write to the Free Software *
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
21  * MA 02111-1307 USA *
22  ***************************************************************************/
23 
24 #include <algorithm>
25 #include <set>
26 #include <string.h>
27 
28 #include "RIFF.h"
29 
30 #include "helper.h"
31 
32 #if POSIX
33 # include <errno.h>
34 #endif
35 
36 #if POSIX
37 # define RIFF_INVALID_FILE_HANDLE -1 /* not 0 as 0 refers to stdin on POSIX */
38 #elif defined(WIN32)
39 # define RIFF_INVALID_FILE_HANDLE INVALID_HANDLE_VALUE
40 #else
41 # define RIFF_INVALID_FILE_HANDLE NULL
42 #endif
43 
44 namespace RIFF {
45 
46 // *************** Internal functions **************
47 // *
48 
50  static String __resolveChunkPath(Chunk* pCk) {
51  String sPath;
52  for (Chunk* pChunk = pCk; pChunk; pChunk = pChunk->GetParent()) {
53  if (pChunk->GetChunkID() == CHUNK_ID_LIST) {
54  List* pList = (List*) pChunk;
55  sPath = "->'" + pList->GetListTypeString() + "'" + sPath;
56  } else {
57  sPath = "->'" + pChunk->GetChunkIDString() + "'" + sPath;
58  }
59  }
60  return sPath;
61  }
62 
63  inline static bool _isValidHandle(File::Handle handle) {
64  return handle != RIFF_INVALID_FILE_HANDLE;
65  }
66 
67  inline static void _close(File::Handle handle) {
68  if (!_isValidHandle(handle)) return;
69  #if POSIX
70  close(handle);
71  #elif defined(WIN32)
72  CloseHandle(handle);
73  #else
74  fclose(handle);
75  #endif
76  }
77 
78 
79 
80 // *************** progress_t ***************
81 // *
82 
83  progress_t::progress_t() {
84  callback = NULL;
85  custom = NULL;
86  activity = "";
87  __range_min = 0.0f;
88  __range_max = 1.0f;
89  __private = NULL;
90  }
91 
92  progress_t::~progress_t() {
93  }
94 
102  std::vector<progress_t> progress_t::subdivide(int iSubtasks) const {
103  std::vector<progress_t> v;
104  for (int i = 0; i < iSubtasks; ++i) {
105  progress_t p;
106  __divide_progress(this, &p, iSubtasks, i);
107  v.push_back(p);
108  }
109  return v;
110  }
111 
134  std::vector<progress_t> progress_t::subdivide(std::vector<float> vSubTaskPortions) const {
135  float fTotal = 0.f; // usually 1.0, but we sum the portions up below to be sure
136  for (int i = 0; i < vSubTaskPortions.size(); ++i)
137  fTotal += vSubTaskPortions[i];
138 
139  float fLow = 0.f, fHigh = 0.f;
140  std::vector<progress_t> v;
141  for (int i = 0; i < vSubTaskPortions.size(); ++i) {
142  fLow = fHigh;
143  fHigh = vSubTaskPortions[i];
144  progress_t p;
145  __divide_progress(this, &p, fTotal, fLow, fHigh);
146  v.push_back(p);
147  }
148  return v;
149  }
150 
151 
152 
153 // *************** _FileProgress **************
154 // *
155 
156 struct _FileProgress {
157  file_offset_t current;
158  file_offset_t total;
159 
160  void advance(progress_t* progress, file_offset_t bytes) {
161  current += bytes;
162  __notify_progress(progress, float(current) / float(total));
163  }
164 };
165 
166 
167 
168 // *************** Chunk **************
169 // *
170 
171  Chunk::Chunk(File* pFile) {
172  #if DEBUG_RIFF
173  std::cout << "Chunk::Chunk(File* pFile)" << std::endl;
174  #endif // DEBUG_RIFF
175  chunkPos.ullPos = 0;
176  pParent = NULL;
177  pChunkData = NULL;
178  ullCurrentChunkSize = 0;
179  ullNewChunkSize = 0;
180  ullChunkDataSize = 0;
181  ChunkID = CHUNK_ID_RIFF;
182  this->pFile = pFile;
183  }
184 
185  Chunk::Chunk(File* pFile, file_offset_t StartPos, List* Parent) {
186  #if DEBUG_RIFF
187  std::cout << "Chunk::Chunk(File*,file_offset_t,List*),StartPos=" << StartPos << std::endl;
188  #endif // DEBUG_RIFF
189  this->pFile = pFile;
190  ullStartPos = StartPos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
191  pParent = Parent;
192  chunkPos.ullPos = 0;
193  pChunkData = NULL;
194  ullCurrentChunkSize = 0;
195  ullNewChunkSize = 0;
196  ullChunkDataSize = 0;
197  ReadHeader(StartPos);
198  }
199 
200  Chunk::Chunk(File* pFile, List* pParent, uint32_t uiChunkID, file_offset_t ullBodySize) {
201  this->pFile = pFile;
202  ullStartPos = 0; // arbitrary usually, since it will be updated when we write the chunk
203  this->pParent = pParent;
204  chunkPos.ullPos = 0;
205  pChunkData = NULL;
206  ChunkID = uiChunkID;
207  ullChunkDataSize = 0;
208  ullCurrentChunkSize = 0;
209  ullNewChunkSize = ullBodySize;
210  }
211 
212  Chunk::~Chunk() {
213  if (pChunkData) delete[] pChunkData;
214  }
215 
216  void Chunk::ReadHeader(file_offset_t filePos) {
217  #if DEBUG_RIFF
218  std::cout << "Chunk::Readheader(" << filePos << ") ";
219  #endif // DEBUG_RIFF
220  ChunkID = 0;
221  ullNewChunkSize = ullCurrentChunkSize = 0;
222 
223  const File::Handle hRead = pFile->FileHandle();
224 
225  #if POSIX
226  if (lseek(hRead, filePos, SEEK_SET) != -1) {
227  read(hRead, &ChunkID, 4);
228  read(hRead, &ullCurrentChunkSize, pFile->FileOffsetSize);
229  #elif defined(WIN32)
230  LARGE_INTEGER liFilePos;
231  liFilePos.QuadPart = filePos;
232  if (SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) {
233  DWORD dwBytesRead;
234  ReadFile(hRead, &ChunkID, 4, &dwBytesRead, NULL);
235  ReadFile(hRead, &ullCurrentChunkSize, pFile->FileOffsetSize, &dwBytesRead, NULL);
236  #else
237  if (!fseeko(hRead, filePos, SEEK_SET)) {
238  fread(&ChunkID, 4, 1, hRead);
239  fread(&ullCurrentChunkSize, pFile->FileOffsetSize, 1, hRead);
240  #endif // POSIX
241  #if WORDS_BIGENDIAN
242  if (ChunkID == CHUNK_ID_RIFF) {
243  pFile->bEndianNative = false;
244  }
245  #else // little endian
246  if (ChunkID == CHUNK_ID_RIFX) {
247  pFile->bEndianNative = false;
248  ChunkID = CHUNK_ID_RIFF;
249  }
250  #endif // WORDS_BIGENDIAN
251  if (!pFile->bEndianNative) {
252  //swapBytes_32(&ChunkID);
253  if (pFile->FileOffsetSize == 4)
254  swapBytes_32(&ullCurrentChunkSize);
255  else
256  swapBytes_64(&ullCurrentChunkSize);
257  }
258  #if DEBUG_RIFF
259  std::cout << "ckID=" << convertToString(ChunkID) << " ";
260  std::cout << "ckSize=" << ullCurrentChunkSize << " ";
261  std::cout << "bEndianNative=" << pFile->bEndianNative << std::endl;
262  #endif // DEBUG_RIFF
263  ullNewChunkSize = ullCurrentChunkSize;
264  }
265  }
266 
267  void Chunk::WriteHeader(file_offset_t filePos) {
268  uint32_t uiNewChunkID = ChunkID;
269  if (ChunkID == CHUNK_ID_RIFF) {
270  #if WORDS_BIGENDIAN
271  if (pFile->bEndianNative) uiNewChunkID = CHUNK_ID_RIFX;
272  #else // little endian
273  if (!pFile->bEndianNative) uiNewChunkID = CHUNK_ID_RIFX;
274  #endif // WORDS_BIGENDIAN
275  }
276 
277  uint64_t ullNewChunkSize = this->ullNewChunkSize;
278  if (!pFile->bEndianNative) {
279  if (pFile->FileOffsetSize == 4)
280  swapBytes_32(&ullNewChunkSize);
281  else
282  swapBytes_64(&ullNewChunkSize);
283  }
284 
285  const File::Handle hWrite = pFile->FileWriteHandle();
286 
287  #if POSIX
288  if (lseek(hWrite, filePos, SEEK_SET) != -1) {
289  write(hWrite, &uiNewChunkID, 4);
290  write(hWrite, &ullNewChunkSize, pFile->FileOffsetSize);
291  }
292  #elif defined(WIN32)
293  LARGE_INTEGER liFilePos;
294  liFilePos.QuadPart = filePos;
295  if (SetFilePointerEx(hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) {
296  DWORD dwBytesWritten;
297  WriteFile(hWrite, &uiNewChunkID, 4, &dwBytesWritten, NULL);
298  WriteFile(hWrite, &ullNewChunkSize, pFile->FileOffsetSize, &dwBytesWritten, NULL);
299  }
300  #else
301  if (!fseeko(hWrite, filePos, SEEK_SET)) {
302  fwrite(&uiNewChunkID, 4, 1, hWrite);
303  fwrite(&ullNewChunkSize, pFile->FileOffsetSize, 1, hWrite);
304  }
305  #endif // POSIX
306  }
307 
312  String Chunk::GetChunkIDString() const {
313  return convertToString(ChunkID);
314  }
315 
324  file_offset_t& Chunk::GetPosUnsafeRef() {
325  if (!pFile->IsIOPerThread()) return chunkPos.ullPos;
326  const std::thread::id tid = std::this_thread::get_id();
327  return chunkPos.byThread[tid];
328  }
329 
336  if (!pFile->IsIOPerThread()) return chunkPos.ullPos;
337  const std::thread::id tid = std::this_thread::get_id();
338  std::lock_guard<std::mutex> lock(chunkPos.mutex);
339  return chunkPos.byThread[tid];
340  }
341 
349  return ullStartPos + GetPos();
350  }
351 
366  #if DEBUG_RIFF
367  std::cout << "Chunk::SetPos(file_offset_t,stream_whence_t)" << std::endl;
368  #endif // DEBUG_RIFF
369  std::lock_guard<std::mutex> lock(chunkPos.mutex);
370  file_offset_t& pos = GetPosUnsafeRef();
371  switch (Whence) {
372  case stream_curpos:
373  pos += Where;
374  break;
375  case stream_end:
376  pos = ullCurrentChunkSize - 1 - Where;
377  break;
378  case stream_backward:
379  pos -= Where;
380  break;
381  case stream_start: default:
382  pos = Where;
383  break;
384  }
385  if (pos > ullCurrentChunkSize) pos = ullCurrentChunkSize;
386  return pos;
387  }
388 
401  #if DEBUG_RIFF
402  std::cout << "Chunk::Remainingbytes()=" << ullCurrentChunkSize - ullPos << std::endl;
403  #endif // DEBUG_RIFF
404  const file_offset_t pos = GetPos();
405  return (ullCurrentChunkSize > pos) ? ullCurrentChunkSize - pos : 0;
406  }
407 
416  return CHUNK_HEADER_SIZE(fileOffsetSize) + // RIFF chunk header
417  ullNewChunkSize + // chunks's actual data body
418  ullNewChunkSize % 2; // optional pad byte
419  }
420 
435  #if DEBUG_RIFF
436  std::cout << "Chunk::GetState()" << std::endl;
437  #endif // DEBUG_RIFF
438 
439  const File::Handle hRead = pFile->FileHandle();
440 
441  if (!_isValidHandle(hRead))
442  return stream_closed;
443 
444  const file_offset_t pos = GetPos();
445  if (pos < ullCurrentChunkSize) return stream_ready;
446  else return stream_end_reached;
447  }
448 
465  file_offset_t Chunk::Read(void* pData, file_offset_t WordCount, file_offset_t WordSize) {
466  #if DEBUG_RIFF
467  std::cout << "Chunk::Read(void*,file_offset_t,file_offset_t)" << std::endl;
468  #endif // DEBUG_RIFF
469  //if (ulStartPos == 0) return 0; // is only 0 if this is a new chunk, so nothing to read (yet)
470  const file_offset_t pos = GetPos();
471  if (pos >= ullCurrentChunkSize) return 0;
472  if (pos + WordCount * WordSize >= ullCurrentChunkSize)
473  WordCount = (ullCurrentChunkSize - pos) / WordSize;
474 
475  const File::Handle hRead = pFile->FileHandle();
476 
477  #if POSIX
478  if (lseek(hRead, ullStartPos + pos, SEEK_SET) < 0) return 0;
479  ssize_t readWords = read(hRead, pData, WordCount * WordSize);
480  if (readWords < 1) {
481  #if DEBUG_RIFF
482  std::cerr << "POSIX read() failed: " << strerror(errno) << std::endl << std::flush;
483  #endif // DEBUG_RIFF
484  return 0;
485  }
486  readWords /= WordSize;
487  #elif defined(WIN32)
488  LARGE_INTEGER liFilePos;
489  liFilePos.QuadPart = ullStartPos + pos;
490  if (!SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN))
491  return 0;
492  DWORD readWords;
493  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)
494  if (readWords < 1) return 0;
495  readWords /= WordSize;
496  #else // standard C functions
497  if (fseeko(hRead, ullStartPos + pos, SEEK_SET)) return 0;
498  file_offset_t readWords = fread(pData, WordSize, WordCount, hRead);
499  #endif // POSIX
500  if (!pFile->bEndianNative && WordSize != 1) {
501  switch (WordSize) {
502  case 2:
503  for (file_offset_t iWord = 0; iWord < readWords; iWord++)
504  swapBytes_16((uint16_t*) pData + iWord);
505  break;
506  case 4:
507  for (file_offset_t iWord = 0; iWord < readWords; iWord++)
508  swapBytes_32((uint32_t*) pData + iWord);
509  break;
510  case 8:
511  for (file_offset_t iWord = 0; iWord < readWords; iWord++)
512  swapBytes_64((uint64_t*) pData + iWord);
513  break;
514  default:
515  for (file_offset_t iWord = 0; iWord < readWords; iWord++)
516  swapBytes((uint8_t*) pData + iWord * WordSize, WordSize);
517  break;
518  }
519  }
520  SetPos(readWords * WordSize, stream_curpos);
521  return readWords;
522  }
523 
541  file_offset_t Chunk::Write(void* pData, file_offset_t WordCount, file_offset_t WordSize) {
542  const File::HandlePair io = pFile->FileHandlePair();
543  if (io.Mode != stream_mode_read_write)
544  throw Exception("Cannot write data to chunk, file has to be opened in read+write mode first");
545  const file_offset_t pos = GetPos();
546  if (pos >= ullCurrentChunkSize || pos + WordCount * WordSize > ullCurrentChunkSize)
547  throw Exception("End of chunk reached while trying to write data");
548  if (!pFile->bEndianNative && WordSize != 1) {
549  switch (WordSize) {
550  case 2:
551  for (file_offset_t iWord = 0; iWord < WordCount; iWord++)
552  swapBytes_16((uint16_t*) pData + iWord);
553  break;
554  case 4:
555  for (file_offset_t iWord = 0; iWord < WordCount; iWord++)
556  swapBytes_32((uint32_t*) pData + iWord);
557  break;
558  case 8:
559  for (file_offset_t iWord = 0; iWord < WordCount; iWord++)
560  swapBytes_64((uint64_t*) pData + iWord);
561  break;
562  default:
563  for (file_offset_t iWord = 0; iWord < WordCount; iWord++)
564  swapBytes((uint8_t*) pData + iWord * WordSize, WordSize);
565  break;
566  }
567  }
568  #if POSIX
569  if (lseek(io.hWrite, ullStartPos + pos, SEEK_SET) < 0) {
570  throw Exception("Could not seek to position " + ToString(pos) +
571  " in chunk (" + ToString(ullStartPos + pos) + " in file)");
572  }
573  ssize_t writtenWords = write(io.hWrite, pData, WordCount * WordSize);
574  if (writtenWords < 1) throw Exception("POSIX IO Error while trying to write chunk data");
575  writtenWords /= WordSize;
576  #elif defined(WIN32)
577  LARGE_INTEGER liFilePos;
578  liFilePos.QuadPart = ullStartPos + pos;
579  if (!SetFilePointerEx(io.hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) {
580  throw Exception("Could not seek to position " + ToString(pos) +
581  " in chunk (" + ToString(ullStartPos + pos) + " in file)");
582  }
583  DWORD writtenWords;
584  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)
585  if (writtenWords < 1) throw Exception("Windows IO Error while trying to write chunk data");
586  writtenWords /= WordSize;
587  #else // standard C functions
588  if (fseeko(io.hWrite, ullStartPos + pos, SEEK_SET)) {
589  throw Exception("Could not seek to position " + ToString(pos) +
590  " in chunk (" + ToString(ullStartPos + pos) + " in file)");
591  }
592  file_offset_t writtenWords = fwrite(pData, WordSize, WordCount, io.hWrite);
593  #endif // POSIX
594  SetPos(writtenWords * WordSize, stream_curpos);
595  return writtenWords;
596  }
597 
599  file_offset_t Chunk::ReadSceptical(void* pData, file_offset_t WordCount, file_offset_t WordSize) {
600  file_offset_t readWords = Read(pData, WordCount, WordSize);
601  if (readWords != WordCount) throw RIFF::Exception("End of chunk data reached.");
602  return readWords;
603  }
604 
617  file_offset_t Chunk::ReadInt8(int8_t* pData, file_offset_t WordCount) {
618  #if DEBUG_RIFF
619  std::cout << "Chunk::ReadInt8(int8_t*,file_offset_t)" << std::endl;
620  #endif // DEBUG_RIFF
621  return ReadSceptical(pData, WordCount, 1);
622  }
623 
639  file_offset_t Chunk::WriteInt8(int8_t* pData, file_offset_t WordCount) {
640  return Write(pData, WordCount, 1);
641  }
642 
656  file_offset_t Chunk::ReadUint8(uint8_t* pData, file_offset_t WordCount) {
657  #if DEBUG_RIFF
658  std::cout << "Chunk::ReadUint8(uint8_t*,file_offset_t)" << std::endl;
659  #endif // DEBUG_RIFF
660  return ReadSceptical(pData, WordCount, 1);
661  }
662 
678  file_offset_t Chunk::WriteUint8(uint8_t* pData, file_offset_t WordCount) {
679  return Write(pData, WordCount, 1);
680  }
681 
695  file_offset_t Chunk::ReadInt16(int16_t* pData, file_offset_t WordCount) {
696  #if DEBUG_RIFF
697  std::cout << "Chunk::ReadInt16(int16_t*,file_offset_t)" << std::endl;
698  #endif // DEBUG_RIFF
699  return ReadSceptical(pData, WordCount, 2);
700  }
701 
717  file_offset_t Chunk::WriteInt16(int16_t* pData, file_offset_t WordCount) {
718  return Write(pData, WordCount, 2);
719  }
720 
734  file_offset_t Chunk::ReadUint16(uint16_t* pData, file_offset_t WordCount) {
735  #if DEBUG_RIFF
736  std::cout << "Chunk::ReadUint16(uint16_t*,file_offset_t)" << std::endl;
737  #endif // DEBUG_RIFF
738  return ReadSceptical(pData, WordCount, 2);
739  }
740 
756  file_offset_t Chunk::WriteUint16(uint16_t* pData, file_offset_t WordCount) {
757  return Write(pData, WordCount, 2);
758  }
759 
773  file_offset_t Chunk::ReadInt32(int32_t* pData, file_offset_t WordCount) {
774  #if DEBUG_RIFF
775  std::cout << "Chunk::ReadInt32(int32_t*,file_offset_t)" << std::endl;
776  #endif // DEBUG_RIFF
777  return ReadSceptical(pData, WordCount, 4);
778  }
779 
795  file_offset_t Chunk::WriteInt32(int32_t* pData, file_offset_t WordCount) {
796  return Write(pData, WordCount, 4);
797  }
798 
812  file_offset_t Chunk::ReadUint32(uint32_t* pData, file_offset_t WordCount) {
813  #if DEBUG_RIFF
814  std::cout << "Chunk::ReadUint32(uint32_t*,file_offset_t)" << std::endl;
815  #endif // DEBUG_RIFF
816  return ReadSceptical(pData, WordCount, 4);
817  }
818 
830  void Chunk::ReadString(String& s, int size) {
831  char* buf = new char[size];
832  ReadSceptical(buf, 1, size);
833  s.assign(buf, std::find(buf, buf + size, '\0'));
834  delete[] buf;
835  }
836 
852  file_offset_t Chunk::WriteUint32(uint32_t* pData, file_offset_t WordCount) {
853  return Write(pData, WordCount, 4);
854  }
855 
864  int8_t Chunk::ReadInt8() {
865  #if DEBUG_RIFF
866  std::cout << "Chunk::ReadInt8()" << std::endl;
867  #endif // DEBUG_RIFF
868  int8_t word;
869  ReadSceptical(&word,1,1);
870  return word;
871  }
872 
881  uint8_t Chunk::ReadUint8() {
882  #if DEBUG_RIFF
883  std::cout << "Chunk::ReadUint8()" << std::endl;
884  #endif // DEBUG_RIFF
885  uint8_t word;
886  ReadSceptical(&word,1,1);
887  return word;
888  }
889 
899  int16_t Chunk::ReadInt16() {
900  #if DEBUG_RIFF
901  std::cout << "Chunk::ReadInt16()" << std::endl;
902  #endif // DEBUG_RIFF
903  int16_t word;
904  ReadSceptical(&word,1,2);
905  return word;
906  }
907 
917  uint16_t Chunk::ReadUint16() {
918  #if DEBUG_RIFF
919  std::cout << "Chunk::ReadUint16()" << std::endl;
920  #endif // DEBUG_RIFF
921  uint16_t word;
922  ReadSceptical(&word,1,2);
923  return word;
924  }
925 
935  int32_t Chunk::ReadInt32() {
936  #if DEBUG_RIFF
937  std::cout << "Chunk::ReadInt32()" << std::endl;
938  #endif // DEBUG_RIFF
939  int32_t word;
940  ReadSceptical(&word,1,4);
941  return word;
942  }
943 
953  uint32_t Chunk::ReadUint32() {
954  #if DEBUG_RIFF
955  std::cout << "Chunk::ReadUint32()" << std::endl;
956  #endif // DEBUG_RIFF
957  uint32_t word;
958  ReadSceptical(&word,1,4);
959  return word;
960  }
961 
985  if (!pChunkData && pFile->Filename != "" /*&& ulStartPos != 0*/) {
986  File::Handle hRead = pFile->FileHandle();
987  #if POSIX
988  if (lseek(hRead, ullStartPos, SEEK_SET) == -1) return NULL;
989  #elif defined(WIN32)
990  LARGE_INTEGER liFilePos;
991  liFilePos.QuadPart = ullStartPos;
992  if (!SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) return NULL;
993  #else
994  if (fseeko(hRead, ullStartPos, SEEK_SET)) return NULL;
995  #endif // POSIX
996  file_offset_t ullBufferSize = (ullCurrentChunkSize > ullNewChunkSize) ? ullCurrentChunkSize : ullNewChunkSize;
997  pChunkData = new uint8_t[ullBufferSize];
998  if (!pChunkData) return NULL;
999  memset(pChunkData, 0, ullBufferSize);
1000  #if POSIX
1001  file_offset_t readWords = read(hRead, pChunkData, GetSize());
1002  #elif defined(WIN32)
1003  DWORD readWords;
1004  ReadFile(hRead, pChunkData, GetSize(), &readWords, NULL); //FIXME: won't load chunks larger than 2GB !
1005  #else
1006  file_offset_t readWords = fread(pChunkData, 1, GetSize(), hRead);
1007  #endif // POSIX
1008  if (readWords != GetSize()) {
1009  delete[] pChunkData;
1010  return (pChunkData = NULL);
1011  }
1012  ullChunkDataSize = ullBufferSize;
1013  } else if (ullNewChunkSize > ullChunkDataSize) {
1014  uint8_t* pNewBuffer = new uint8_t[ullNewChunkSize];
1015  if (!pNewBuffer) throw Exception("Could not enlarge chunk data buffer to " + ToString(ullNewChunkSize) + " bytes");
1016  memset(pNewBuffer, 0 , ullNewChunkSize);
1017  if (pChunkData) {
1018  memcpy(pNewBuffer, pChunkData, ullChunkDataSize);
1019  delete[] pChunkData;
1020  }
1021  pChunkData = pNewBuffer;
1022  ullChunkDataSize = ullNewChunkSize;
1023  }
1024  return pChunkData;
1025  }
1026 
1034  if (pChunkData) {
1035  delete[] pChunkData;
1036  pChunkData = NULL;
1037  }
1038  }
1039 
1059  if (NewSize == 0)
1060  throw Exception("There is at least one empty chunk (zero size): " + __resolveChunkPath(this));
1061  if ((NewSize >> 48) != 0)
1062  throw Exception("Unrealistic high chunk size detected: " + __resolveChunkPath(this));
1063  if (ullNewChunkSize == NewSize) return;
1064  ullNewChunkSize = NewSize;
1065  }
1066 
1081  file_offset_t Chunk::WriteChunk(file_offset_t ullWritePos, file_offset_t ullCurrentDataOffset, progress_t* pProgress) {
1082  const file_offset_t ullOriginalPos = ullWritePos;
1083  ullWritePos += CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
1084 
1085  const File::HandlePair io = pFile->FileHandlePair();
1086 
1087  if (io.Mode != stream_mode_read_write)
1088  throw Exception("Cannot write list chunk, file has to be opened in read+write mode");
1089 
1090  // if the whole chunk body was loaded into RAM
1091  if (pChunkData) {
1092  // make sure chunk data buffer in RAM is at least as large as the new chunk size
1093  LoadChunkData();
1094  // write chunk data from RAM persistently to the file
1095  #if POSIX
1096  lseek(io.hWrite, ullWritePos, SEEK_SET);
1097  if (write(io.hWrite, pChunkData, ullNewChunkSize) != ullNewChunkSize) {
1098  throw Exception("Writing Chunk data (from RAM) failed");
1099  }
1100  #elif defined(WIN32)
1101  LARGE_INTEGER liFilePos;
1102  liFilePos.QuadPart = ullWritePos;
1103  SetFilePointerEx(io.hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1104  DWORD dwBytesWritten;
1105  WriteFile(io.hWrite, pChunkData, ullNewChunkSize, &dwBytesWritten, NULL); //FIXME: won't save chunks larger than 2GB !
1106  if (dwBytesWritten != ullNewChunkSize) {
1107  throw Exception("Writing Chunk data (from RAM) failed");
1108  }
1109  #else
1110  fseeko(io.hWrite, ullWritePos, SEEK_SET);
1111  if (fwrite(pChunkData, 1, ullNewChunkSize, io.hWrite) != ullNewChunkSize) {
1112  throw Exception("Writing Chunk data (from RAM) failed");
1113  }
1114  #endif // POSIX
1115  } else {
1116  // move chunk data from the end of the file to the appropriate position
1117  int8_t* pCopyBuffer = new int8_t[4096];
1118  file_offset_t ullToMove = (ullNewChunkSize < ullCurrentChunkSize) ? ullNewChunkSize : ullCurrentChunkSize;
1119  #if defined(WIN32)
1120  DWORD iBytesMoved = 1; // we have to pass it via pointer to the Windows API, thus the correct size must be ensured
1121  #else
1122  int iBytesMoved = 1;
1123  #endif
1124  for (file_offset_t ullOffset = 0; ullToMove > 0 && iBytesMoved > 0; ullOffset += iBytesMoved, ullToMove -= iBytesMoved) {
1125  iBytesMoved = (ullToMove < 4096) ? int(ullToMove) : 4096;
1126  #if POSIX
1127  lseek(io.hRead, ullStartPos + ullCurrentDataOffset + ullOffset, SEEK_SET);
1128  iBytesMoved = (int) read(io.hRead, pCopyBuffer, (size_t) iBytesMoved);
1129  lseek(io.hWrite, ullWritePos + ullOffset, SEEK_SET);
1130  iBytesMoved = (int) write(io.hWrite, pCopyBuffer, (size_t) iBytesMoved);
1131  #elif defined(WIN32)
1132  LARGE_INTEGER liFilePos;
1133  liFilePos.QuadPart = ullStartPos + ullCurrentDataOffset + ullOffset;
1134  SetFilePointerEx(io.hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1135  ReadFile(io.hRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL);
1136  liFilePos.QuadPart = ullWritePos + ullOffset;
1137  SetFilePointerEx(io.hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1138  WriteFile(io.hWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL);
1139  #else
1140  fseeko(io.hRead, ullStartPos + ullCurrentDataOffset + ullOffset, SEEK_SET);
1141  iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, io.hRead);
1142  fseeko(io.hWrite, ullWritePos + ullOffset, SEEK_SET);
1143  iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, io.hWrite);
1144  #endif
1145  }
1146  delete[] pCopyBuffer;
1147  if (iBytesMoved < 0) throw Exception("Writing Chunk data (from file) failed");
1148  }
1149 
1150  // update this chunk's header
1151  ullCurrentChunkSize = ullNewChunkSize;
1152  WriteHeader(ullOriginalPos);
1153 
1154  // update chunk's position pointers
1155  ullStartPos = ullOriginalPos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
1157 
1158  if (pProgress && pProgress->__private) {
1159  _FileProgress* fileProgress = (_FileProgress*) pProgress->__private;
1160  fileProgress->advance(pProgress, ullNewChunkSize +
1161  CHUNK_HEADER_SIZE(pFile->FileOffsetSize));
1162  }
1163 
1164  // add pad byte if needed
1165  if ((ullStartPos + ullNewChunkSize) % 2 != 0) {
1166  const char cPadByte = 0;
1167  #if POSIX
1168  lseek(io.hWrite, ullStartPos + ullNewChunkSize, SEEK_SET);
1169  write(io.hWrite, &cPadByte, 1);
1170  #elif defined(WIN32)
1171  LARGE_INTEGER liFilePos;
1172  liFilePos.QuadPart = ullStartPos + ullNewChunkSize;
1173  SetFilePointerEx(io.hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1174  DWORD dwBytesWritten;
1175  WriteFile(io.hWrite, &cPadByte, 1, &dwBytesWritten, NULL);
1176  #else
1177  fseeko(io.hWrite, ullStartPos + ullNewChunkSize, SEEK_SET);
1178  fwrite(&cPadByte, 1, 1, io.hWrite);
1179  #endif
1180  return ullStartPos + ullNewChunkSize + 1;
1181  }
1182 
1183  return ullStartPos + ullNewChunkSize;
1184  }
1185 
1187  std::lock_guard<std::mutex> lock(chunkPos.mutex);
1188  chunkPos.ullPos = 0;
1189  chunkPos.byThread.clear();
1190  }
1191 
1192 
1193 
1194 // *************** List ***************
1195 // *
1196 
1197  List::List(File* pFile) : Chunk(pFile) {
1198  #if DEBUG_RIFF
1199  std::cout << "List::List(File* pFile)" << std::endl;
1200  #endif // DEBUG_RIFF
1201  pSubChunks = NULL;
1202  pSubChunksMap = NULL;
1203  }
1204 
1205  List::List(File* pFile, file_offset_t StartPos, List* Parent)
1206  : Chunk(pFile, StartPos, Parent) {
1207  #if DEBUG_RIFF
1208  std::cout << "List::List(File*,file_offset_t,List*)" << std::endl;
1209  #endif // DEBUG_RIFF
1210  pSubChunks = NULL;
1211  pSubChunksMap = NULL;
1212  ReadHeader(StartPos);
1213  ullStartPos = StartPos + LIST_HEADER_SIZE(pFile->FileOffsetSize);
1214  }
1215 
1216  List::List(File* pFile, List* pParent, uint32_t uiListID)
1217  : Chunk(pFile, pParent, CHUNK_ID_LIST, 0) {
1218  pSubChunks = NULL;
1219  pSubChunksMap = NULL;
1220  ListType = uiListID;
1221  }
1222 
1223  List::~List() {
1224  #if DEBUG_RIFF
1225  std::cout << "List::~List()" << std::endl;
1226  #endif // DEBUG_RIFF
1227  DeleteChunkList();
1228  }
1229 
1230  void List::DeleteChunkList() {
1231  if (pSubChunks) {
1232  ChunkList::iterator iter = pSubChunks->begin();
1233  ChunkList::iterator end = pSubChunks->end();
1234  while (iter != end) {
1235  delete *iter;
1236  iter++;
1237  }
1238  delete pSubChunks;
1239  pSubChunks = NULL;
1240  }
1241  if (pSubChunksMap) {
1242  delete pSubChunksMap;
1243  pSubChunksMap = NULL;
1244  }
1245  }
1246 
1257  if (!pSubChunks) LoadSubChunks();
1258  if (pos >= pSubChunks->size()) return NULL;
1259  return (*pSubChunks)[pos];
1260  }
1261 
1273  Chunk* List::GetSubChunk(uint32_t ChunkID) {
1274  #if DEBUG_RIFF
1275  std::cout << "List::GetSubChunk(uint32_t)" << std::endl;
1276  #endif // DEBUG_RIFF
1277  if (!pSubChunksMap) LoadSubChunks();
1278  return (*pSubChunksMap)[ChunkID];
1279  }
1280 
1291  List* List::GetSubListAt(size_t pos) {
1292  if (!pSubChunks) LoadSubChunks();
1293  if (pos >= pSubChunks->size()) return NULL;
1294  for (size_t iCk = 0, iLst = 0; iCk < pSubChunks->size(); ++iCk) {
1295  Chunk* pChunk = (*pSubChunks)[iCk];
1296  if (pChunk->GetChunkID() != CHUNK_ID_LIST) continue;
1297  if (iLst == pos) return (List*) pChunk;
1298  ++iLst;
1299  }
1300  return NULL;
1301  }
1302 
1314  List* List::GetSubList(uint32_t ListType) {
1315  #if DEBUG_RIFF
1316  std::cout << "List::GetSubList(uint32_t)" << std::endl;
1317  #endif // DEBUG_RIFF
1318  if (!pSubChunks) LoadSubChunks();
1319  ChunkList::iterator iter = pSubChunks->begin();
1320  ChunkList::iterator end = pSubChunks->end();
1321  while (iter != end) {
1322  if ((*iter)->GetChunkID() == CHUNK_ID_LIST) {
1323  List* l = (List*) *iter;
1324  if (l->GetListType() == ListType) return l;
1325  }
1326  iter++;
1327  }
1328  return NULL;
1329  }
1330 
1343  #if DEBUG_RIFF
1344  std::cout << "List::GetFirstSubChunk()" << std::endl;
1345  #endif // DEBUG_RIFF
1346  if (!pSubChunks) LoadSubChunks();
1347  ChunksIterator = pSubChunks->begin();
1348  return (ChunksIterator != pSubChunks->end()) ? *ChunksIterator : NULL;
1349  }
1350 
1362  #if DEBUG_RIFF
1363  std::cout << "List::GetNextSubChunk()" << std::endl;
1364  #endif // DEBUG_RIFF
1365  if (!pSubChunks) return NULL;
1366  ChunksIterator++;
1367  return (ChunksIterator != pSubChunks->end()) ? *ChunksIterator : NULL;
1368  }
1369 
1382  #if DEBUG_RIFF
1383  std::cout << "List::GetFirstSubList()" << std::endl;
1384  #endif // DEBUG_RIFF
1385  if (!pSubChunks) LoadSubChunks();
1386  ListIterator = pSubChunks->begin();
1387  ChunkList::iterator end = pSubChunks->end();
1388  while (ListIterator != end) {
1389  if ((*ListIterator)->GetChunkID() == CHUNK_ID_LIST) return (List*) *ListIterator;
1390  ListIterator++;
1391  }
1392  return NULL;
1393  }
1394 
1406  #if DEBUG_RIFF
1407  std::cout << "List::GetNextSubList()" << std::endl;
1408  #endif // DEBUG_RIFF
1409  if (!pSubChunks) return NULL;
1410  if (ListIterator == pSubChunks->end()) return NULL;
1411  ListIterator++;
1412  ChunkList::iterator end = pSubChunks->end();
1413  while (ListIterator != end) {
1414  if ((*ListIterator)->GetChunkID() == CHUNK_ID_LIST) return (List*) *ListIterator;
1415  ListIterator++;
1416  }
1417  return NULL;
1418  }
1419 
1424  if (!pSubChunks) LoadSubChunks();
1425  return pSubChunks->size();
1426  }
1427 
1432  size_t List::CountSubChunks(uint32_t ChunkID) {
1433  size_t result = 0;
1434  if (!pSubChunks) LoadSubChunks();
1435  ChunkList::iterator iter = pSubChunks->begin();
1436  ChunkList::iterator end = pSubChunks->end();
1437  while (iter != end) {
1438  if ((*iter)->GetChunkID() == ChunkID) {
1439  result++;
1440  }
1441  iter++;
1442  }
1443  return result;
1444  }
1445 
1450  return CountSubChunks(CHUNK_ID_LIST);
1451  }
1452 
1457  size_t List::CountSubLists(uint32_t ListType) {
1458  size_t result = 0;
1459  if (!pSubChunks) LoadSubChunks();
1460  ChunkList::iterator iter = pSubChunks->begin();
1461  ChunkList::iterator end = pSubChunks->end();
1462  while (iter != end) {
1463  if ((*iter)->GetChunkID() == CHUNK_ID_LIST) {
1464  List* l = (List*) *iter;
1465  if (l->GetListType() == ListType) result++;
1466  }
1467  iter++;
1468  }
1469  return result;
1470  }
1471 
1485  Chunk* List::AddSubChunk(uint32_t uiChunkID, file_offset_t ullBodySize) {
1486  if (ullBodySize == 0) throw Exception("Chunk body size must be at least 1 byte");
1487  if (!pSubChunks) LoadSubChunks();
1488 
1489  Chunk* pNewChunk = new Chunk(pFile, this, uiChunkID, 0);
1490  pSubChunks->push_back(pNewChunk);
1491  (*pSubChunksMap)[uiChunkID] = pNewChunk;
1492  pNewChunk->Resize(ullBodySize);
1493  ullNewChunkSize += CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
1494 
1495  pFile->nDataChunkCount++;
1496 
1497  return pNewChunk;
1498  }
1499 
1511  void List::MoveSubChunk(Chunk* pSrc, Chunk* pDst) {
1512  if (!pSubChunks) LoadSubChunks();
1513  for (size_t i = 0; i < pSubChunks->size(); ++i) {
1514  if ((*pSubChunks)[i] == pSrc) {
1515  pSubChunks->erase(pSubChunks->begin() + i);
1516  ChunkList::iterator iter =
1517  find(pSubChunks->begin(), pSubChunks->end(), pDst);
1518  pSubChunks->insert(iter, pSrc);
1519  return;
1520  }
1521  }
1522  }
1523 
1532  void List::MoveSubChunk(Chunk* pSrc, List* pNewParent) {
1533  if (pNewParent == this || !pNewParent) return;
1534  if (!pSubChunks) LoadSubChunks();
1535  if (!pNewParent->pSubChunks) pNewParent->LoadSubChunks();
1536  ChunkList::iterator iter =
1537  find(pSubChunks->begin(), pSubChunks->end(), pSrc);
1538  if (iter == pSubChunks->end()) return;
1539  pSubChunks->erase(iter);
1540  pNewParent->pSubChunks->push_back(pSrc);
1541  // update chunk id map of this List
1542  if ((*pSubChunksMap)[pSrc->GetChunkID()] == pSrc) {
1543  pSubChunksMap->erase(pSrc->GetChunkID());
1544  // try to find another chunk of the same chunk ID
1545  ChunkList::iterator iter = pSubChunks->begin();
1546  ChunkList::iterator end = pSubChunks->end();
1547  for (; iter != end; ++iter) {
1548  if ((*iter)->GetChunkID() == pSrc->GetChunkID()) {
1549  (*pSubChunksMap)[pSrc->GetChunkID()] = *iter;
1550  break; // we're done, stop search
1551  }
1552  }
1553  }
1554  // update chunk id map of other list
1555  if (!(*pNewParent->pSubChunksMap)[pSrc->GetChunkID()])
1556  (*pNewParent->pSubChunksMap)[pSrc->GetChunkID()] = pSrc;
1557  }
1558 
1568  List* List::AddSubList(uint32_t uiListType) {
1569  if (!pSubChunks) LoadSubChunks();
1570 
1571  List* pNewListChunk = new List(pFile, this, uiListType);
1572  pSubChunks->push_back(pNewListChunk);
1573  (*pSubChunksMap)[CHUNK_ID_LIST] = pNewListChunk;
1574  ullNewChunkSize += LIST_HEADER_SIZE(pFile->FileOffsetSize);
1575 
1576  pFile->nListChunkCount++;
1577 
1578  return pNewListChunk;
1579  }
1580 
1591  void List::DeleteSubChunk(Chunk* pSubChunk) {
1592  if (!pSubChunks) LoadSubChunks();
1593  ChunkList::iterator iter =
1594  find(pSubChunks->begin(), pSubChunks->end(), pSubChunk);
1595  if (iter == pSubChunks->end()) return;
1596  pSubChunks->erase(iter);
1597 
1598  if (dynamic_cast<List*>(pSubChunk))
1599  pFile->nListChunkCount--;
1600  else
1601  pFile->nDataChunkCount--;
1602 
1603  if ((*pSubChunksMap)[pSubChunk->GetChunkID()] == pSubChunk) {
1604  pSubChunksMap->erase(pSubChunk->GetChunkID());
1605  // try to find another chunk of the same chunk ID
1606  ChunkList::iterator iter = pSubChunks->begin();
1607  ChunkList::iterator end = pSubChunks->end();
1608  for (; iter != end; ++iter) {
1609  if ((*iter)->GetChunkID() == pSubChunk->GetChunkID()) {
1610  (*pSubChunksMap)[pSubChunk->GetChunkID()] = *iter;
1611  break; // we're done, stop search
1612  }
1613  }
1614  }
1615  delete pSubChunk;
1616  }
1617 
1626  if (!pSubChunks) LoadSubChunks();
1627  file_offset_t size = LIST_HEADER_SIZE(fileOffsetSize);
1628  ChunkList::iterator iter = pSubChunks->begin();
1629  ChunkList::iterator end = pSubChunks->end();
1630  for (; iter != end; ++iter)
1631  size += (*iter)->RequiredPhysicalSize(fileOffsetSize);
1632  return size;
1633  }
1634 
1635  void List::ReadHeader(file_offset_t filePos) {
1636  #if DEBUG_RIFF
1637  std::cout << "List::Readheader(file_offset_t) ";
1638  #endif // DEBUG_RIFF
1639  Chunk::ReadHeader(filePos);
1640  if (ullCurrentChunkSize < 4) return;
1641  ullNewChunkSize = ullCurrentChunkSize -= 4;
1642 
1643  const File::Handle hRead = pFile->FileHandle();
1644 
1645  #if POSIX
1646  lseek(hRead, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET);
1647  read(hRead, &ListType, 4);
1648  #elif defined(WIN32)
1649  LARGE_INTEGER liFilePos;
1650  liFilePos.QuadPart = filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
1651  SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1652  DWORD dwBytesRead;
1653  ReadFile(hRead, &ListType, 4, &dwBytesRead, NULL);
1654  #else
1655  fseeko(hRead, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET);
1656  fread(&ListType, 4, 1, hRead);
1657  #endif // POSIX
1658  #if DEBUG_RIFF
1659  std::cout << "listType=" << convertToString(ListType) << std::endl;
1660  #endif // DEBUG_RIFF
1661  if (!pFile->bEndianNative) {
1662  //swapBytes_32(&ListType);
1663  }
1664  }
1665 
1666  void List::WriteHeader(file_offset_t filePos) {
1667  // the four list type bytes officially belong the chunk's body in the RIFF format
1668  ullNewChunkSize += 4;
1669  Chunk::WriteHeader(filePos);
1670  ullNewChunkSize -= 4; // just revert the +4 incrementation
1671 
1672  const File::Handle hWrite = pFile->FileWriteHandle();
1673 
1674  #if POSIX
1675  lseek(hWrite, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET);
1676  write(hWrite, &ListType, 4);
1677  #elif defined(WIN32)
1678  LARGE_INTEGER liFilePos;
1679  liFilePos.QuadPart = filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
1680  SetFilePointerEx(hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1681  DWORD dwBytesWritten;
1682  WriteFile(hWrite, &ListType, 4, &dwBytesWritten, NULL);
1683  #else
1684  fseeko(hWrite, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET);
1685  fwrite(&ListType, 4, 1, hWrite);
1686  #endif // POSIX
1687  }
1688 
1689  void List::LoadSubChunks(progress_t* pProgress) {
1690  #if DEBUG_RIFF
1691  std::cout << "List::LoadSubChunks()";
1692  #endif // DEBUG_RIFF
1693  if (!pSubChunks) {
1694  pSubChunks = new ChunkList();
1695  pSubChunksMap = new ChunkMap();
1696 
1697  const File::Handle hRead = pFile->FileHandle();
1698  if (!_isValidHandle(hRead)) return;
1699 
1700  const file_offset_t ullOriginalPos = GetPos();
1701  SetPos(0); // jump to beginning of list chunk body
1702  while (RemainingBytes() >= CHUNK_HEADER_SIZE(pFile->FileOffsetSize)) {
1703  Chunk* ck;
1704  uint32_t ckid;
1705  // return value check is required here to prevent a potential
1706  // garbage data use of 'ckid' below in case Read() failed
1707  if (Read(&ckid, 4, 1) != 4)
1708  throw Exception("LoadSubChunks(): Failed reading RIFF chunk ID");
1709  #if DEBUG_RIFF
1710  std::cout << " ckid=" << convertToString(ckid) << std::endl;
1711  #endif // DEBUG_RIFF
1712  const file_offset_t pos = GetPos();
1713  if (ckid == CHUNK_ID_LIST) {
1714  ck = new RIFF::List(pFile, ullStartPos + pos - 4, this);
1715  SetPos(ck->GetSize() + LIST_HEADER_SIZE(pFile->FileOffsetSize) - 4, RIFF::stream_curpos);
1716 
1717  pFile->nListChunkCount++;
1718  }
1719  else { // simple chunk
1720  ck = new RIFF::Chunk(pFile, ullStartPos + pos - 4, this);
1721  SetPos(ck->GetSize() + CHUNK_HEADER_SIZE(pFile->FileOffsetSize) - 4, RIFF::stream_curpos);
1722 
1723  pFile->nDataChunkCount++;
1724  }
1725  pSubChunks->push_back(ck);
1726  (*pSubChunksMap)[ckid] = ck;
1727  if (GetPos() % 2 != 0) SetPos(1, RIFF::stream_curpos); // jump over pad byte
1728  }
1729  SetPos(ullOriginalPos); // restore position before this call
1730  }
1731  if (pProgress)
1732  __notify_progress(pProgress, 1.0); // notify done
1733  }
1734 
1735  void List::LoadSubChunksRecursively(progress_t* pProgress) {
1736  const bool alreadyLoaded = pSubChunks;
1737  const int n = (int) CountSubLists();
1738  int i = 0;
1739  for (List* pList = GetSubListAt(i); pList; pList = GetSubListAt(++i)) {
1740  if (pProgress) {
1741  // divide local progress into subprogress
1742  progress_t subprogress;
1743  __divide_progress(pProgress, &subprogress, n, i);
1744  // do the actual work
1745  pList->LoadSubChunksRecursively(&subprogress);
1746  } else
1747  pList->LoadSubChunksRecursively(NULL);
1748  }
1749  if (pProgress && !alreadyLoaded)
1750  __notify_progress(pProgress, 1.0); // notify done
1751  }
1752 
1768  file_offset_t List::WriteChunk(file_offset_t ullWritePos, file_offset_t ullCurrentDataOffset, progress_t* pProgress) {
1769  const file_offset_t ullOriginalPos = ullWritePos;
1770  ullWritePos += LIST_HEADER_SIZE(pFile->FileOffsetSize);
1771 
1772  if (pFile->GetMode() != stream_mode_read_write)
1773  throw Exception("Cannot write list chunk, file has to be opened in read+write mode");
1774 
1775  // write all subchunks (including sub list chunks) recursively
1776  if (pSubChunks) {
1777  for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter) {
1778  ullWritePos = (*iter)->WriteChunk(ullWritePos, ullCurrentDataOffset, pProgress);
1779  }
1780  }
1781 
1782  // update this list chunk's header
1783  ullCurrentChunkSize = ullNewChunkSize = ullWritePos - ullOriginalPos - LIST_HEADER_SIZE(pFile->FileOffsetSize);
1784  WriteHeader(ullOriginalPos);
1785 
1786  // offset of this list chunk in new written file may have changed
1787  ullStartPos = ullOriginalPos + LIST_HEADER_SIZE(pFile->FileOffsetSize);
1788 
1789  if (pProgress && pProgress->__private) {
1790  _FileProgress* fileProgress = (_FileProgress*) pProgress->__private;
1791  fileProgress->advance(pProgress,
1792  LIST_HEADER_SIZE(pFile->FileOffsetSize));
1793  }
1794 
1795  return ullWritePos;
1796  }
1797 
1800  if (pSubChunks) {
1801  for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter) {
1802  (*iter)->__resetPos();
1803  }
1804  }
1805  }
1806 
1810  String List::GetListTypeString() const {
1811  return convertToString(ListType);
1812  }
1813 
1814 
1815 
1816 // *************** File ***************
1817 // *
1818 
1833  File::File(uint32_t FileType)
1834  : List(this), bIsNewFile(true), Layout(layout_standard),
1835  FileOffsetPreference(offset_size_auto)
1836  {
1837  io.isPerThread = false;
1838  io.hRead = io.hWrite = RIFF_INVALID_FILE_HANDLE;
1839  io.Mode = stream_mode_closed;
1840  bEndianNative = true;
1841  ListType = FileType;
1842  FileOffsetSize = 4;
1843  nDataChunkCount = 0;
1844  nListChunkCount = 0;
1845  ullStartPos = RIFF_HEADER_SIZE(FileOffsetSize);
1846  }
1847 
1856  File::File(const String& path)
1857  : List(this), Filename(path), bIsNewFile(false), Layout(layout_standard),
1858  FileOffsetPreference(offset_size_auto)
1859  {
1860  #if DEBUG_RIFF
1861  std::cout << "File::File("<<path<<")" << std::endl;
1862  #endif // DEBUG_RIFF
1863  bEndianNative = true;
1864  FileOffsetSize = 4;
1865  nDataChunkCount = 0;
1866  nListChunkCount = 0;
1867  try {
1868  __openExistingFile(path);
1869  if (ChunkID != CHUNK_ID_RIFF && ChunkID != CHUNK_ID_RIFX) {
1870  throw RIFF::Exception("Not a RIFF file");
1871  }
1872  }
1873  catch (...) {
1874  Cleanup();
1875  throw;
1876  }
1877  }
1878 
1905  File::File(const String& path, uint32_t FileType, endian_t Endian, layout_t layout, offset_size_t fileOffsetSize)
1906  : List(this), Filename(path), bIsNewFile(false), Layout(layout),
1907  FileOffsetPreference(fileOffsetSize)
1908  {
1909  SetByteOrder(Endian);
1910  if (fileOffsetSize < offset_size_auto || fileOffsetSize > offset_size_64bit)
1911  throw Exception("Invalid RIFF::offset_size_t");
1912  FileOffsetSize = 4;
1913  nDataChunkCount = 0;
1914  nListChunkCount = 0;
1915  try {
1916  __openExistingFile(path, &FileType);
1917  }
1918  catch (...) {
1919  Cleanup();
1920  throw;
1921  }
1922  }
1923 
1934  void File::__openExistingFile(const String& path, uint32_t* FileType) {
1935  io.isPerThread = false;
1936  #if POSIX
1937  io.hRead = io.hWrite = open(path.c_str(), O_RDONLY | O_NONBLOCK);
1938  if (!_isValidHandle(io.hRead)) {
1939  String sError = strerror(errno);
1940  throw RIFF::Exception("Can't open \"" + path + "\": " + sError);
1941  }
1942  #elif defined(WIN32)
1943  io.hRead = io.hWrite = CreateFile(
1944  path.c_str(), GENERIC_READ,
1945  FILE_SHARE_READ | FILE_SHARE_WRITE,
1946  NULL, OPEN_EXISTING,
1947  FILE_ATTRIBUTE_NORMAL |
1948  FILE_FLAG_RANDOM_ACCESS, NULL
1949  );
1950  if (!_isValidHandle(io.hRead)) {
1951  throw RIFF::Exception("Can't open \"" + path + "\"");
1952  }
1953  #else
1954  io.hRead = io.hWrite = fopen(path.c_str(), "rb");
1955  if (!_isValidHandle(io.hRead))
1956  throw RIFF::Exception("Can't open \"" + path + "\"");
1957  #endif // POSIX
1958  io.Mode = stream_mode_read;
1959 
1960  // determine RIFF file offset size to be used (in RIFF chunk headers)
1961  // according to the current file offset preference
1962  FileOffsetSize = FileOffsetSizeFor(GetCurrentFileSize());
1963 
1964  switch (Layout) {
1965  case layout_standard: // this is a normal RIFF file
1966  ullStartPos = RIFF_HEADER_SIZE(FileOffsetSize);
1967  ReadHeader(0);
1968  if (FileType && ChunkID != *FileType)
1969  throw RIFF::Exception("Invalid file container ID");
1970  break;
1971  case layout_flat: // non-standard RIFF-alike file
1972  ullStartPos = 0;
1973  ullNewChunkSize = ullCurrentChunkSize = GetCurrentFileSize();
1974  if (FileType) {
1975  uint32_t ckid;
1976  if (Read(&ckid, 4, 1) != 4) {
1977  throw RIFF::Exception("Invalid file header ID (premature end of header)");
1978  } else if (ckid != *FileType) {
1979  String s = " (expected '" + convertToString(*FileType) + "' but got '" + convertToString(ckid) + "')";
1980  throw RIFF::Exception("Invalid file header ID" + s);
1981  }
1982  SetPos(0); // reset to first byte of file
1983  }
1984  LoadSubChunks();
1985  break;
1986  }
1987  }
1988 
1989  String File::GetFileName() const {
1990  return Filename;
1991  }
1992 
1993  void File::SetFileName(const String& path) {
1994  Filename = path;
1995  }
1996 
2005  File::HandlePair& File::FileHandlePairUnsafeRef() {
2006  if (io.byThread.empty()) return io;
2007  const std::thread::id tid = std::this_thread::get_id();
2008  const auto it = io.byThread.find(tid);
2009  return (it != io.byThread.end()) ?
2010  it->second :
2011  io.byThread[tid] = {
2012  // designated initializers require C++20, so commented for now
2013  /* .hRead = */ RIFF_INVALID_FILE_HANDLE,
2014  /* .hWrite = */ RIFF_INVALID_FILE_HANDLE,
2015  /* .Mode = */ stream_mode_closed
2016  };
2017  }
2018 
2025  File::HandlePair File::FileHandlePair() const {
2026  std::lock_guard<std::mutex> lock(io.mutex);
2027  if (io.byThread.empty()) return io;
2028  const std::thread::id tid = std::this_thread::get_id();
2029  const auto it = io.byThread.find(tid);
2030  return (it != io.byThread.end()) ?
2031  it->second :
2032  io.byThread[tid] = {
2033  // designated initializers require C++20, so commented for now
2034  /* .hRead = */ RIFF_INVALID_FILE_HANDLE,
2035  /* .hWrite = */ RIFF_INVALID_FILE_HANDLE,
2036  /* .Mode = */ stream_mode_closed
2037  };
2038  }
2039 
2047  return FileHandlePair().hRead;
2048  }
2049 
2057  return FileHandlePair().hWrite;
2058  }
2059 
2067  return FileHandlePair().Mode;
2068  }
2069 
2070  layout_t File::GetLayout() const {
2071  return Layout;
2072  }
2073 
2087  bool bResetPos = false;
2088  bool res = SetModeInternal(NewMode, &bResetPos);
2089  // resetting position must be handled outside above's call to avoid any
2090  // potential dead lock, as SetModeInternal() acquires a lock and
2091  // __resetPos() acquires a lock by itself (not a theoretical issue!)
2092  if (bResetPos)
2093  __resetPos(); // reset read/write position of ALL 'Chunk' objects
2094  return res;
2095  }
2096 
2097  bool File::SetModeInternal(stream_mode_t NewMode, bool* pResetPos) {
2098  std::lock_guard<std::mutex> lock(io.mutex);
2099  HandlePair& io = FileHandlePairUnsafeRef();
2100  if (NewMode != io.Mode) {
2101  switch (NewMode) {
2102  case stream_mode_read:
2103  if (_isValidHandle(io.hRead)) _close(io.hRead);
2104  #if POSIX
2105  io.hRead = io.hWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK);
2106  if (!_isValidHandle(io.hRead)) {
2107  String sError = strerror(errno);
2108  throw Exception("Could not (re)open file \"" + Filename + "\" in read mode: " + sError);
2109  }
2110  #elif defined(WIN32)
2111  io.hRead = io.hWrite = CreateFile(
2112  Filename.c_str(), GENERIC_READ,
2113  FILE_SHARE_READ | FILE_SHARE_WRITE,
2114  NULL, OPEN_EXISTING,
2115  FILE_ATTRIBUTE_NORMAL |
2116  FILE_FLAG_RANDOM_ACCESS,
2117  NULL
2118  );
2119  if (!_isValidHandle(io.hRead)) {
2120  throw Exception("Could not (re)open file \"" + Filename + "\" in read mode");
2121  }
2122  #else
2123  io.hRead = io.hWrite = fopen(Filename.c_str(), "rb");
2124  if (!_isValidHandle(io.hRead))
2125  throw Exception("Could not (re)open file \"" + Filename + "\" in read mode");
2126  #endif
2127  *pResetPos = true;
2128  break;
2129  case stream_mode_read_write:
2130  if (_isValidHandle(io.hRead)) _close(io.hRead);
2131  #if POSIX
2132  io.hRead = io.hWrite = open(Filename.c_str(), O_RDWR | O_NONBLOCK);
2133  if (!_isValidHandle(io.hRead)) {
2134  io.hRead = io.hWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK);
2135  String sError = strerror(errno);
2136  throw Exception("Could not open file \"" + Filename + "\" in read+write mode: " + sError);
2137  }
2138  #elif defined(WIN32)
2139  io.hRead = io.hWrite = CreateFile(
2140  Filename.c_str(),
2141  GENERIC_READ | GENERIC_WRITE,
2142  FILE_SHARE_READ,
2143  NULL, OPEN_ALWAYS,
2144  FILE_ATTRIBUTE_NORMAL |
2145  FILE_FLAG_RANDOM_ACCESS,
2146  NULL
2147  );
2148  if (!_isValidHandle(io.hRead)) {
2149  io.hRead = io.hWrite = CreateFile(
2150  Filename.c_str(), GENERIC_READ,
2151  FILE_SHARE_READ | FILE_SHARE_WRITE,
2152  NULL, OPEN_EXISTING,
2153  FILE_ATTRIBUTE_NORMAL |
2154  FILE_FLAG_RANDOM_ACCESS,
2155  NULL
2156  );
2157  throw Exception("Could not (re)open file \"" + Filename + "\" in read+write mode");
2158  }
2159  #else
2160  io.hRead = io.hWrite = fopen(Filename.c_str(), "r+b");
2161  if (!_isValidHandle(io.hRead)) {
2162  io.hRead = io.hWrite = fopen(Filename.c_str(), "rb");
2163  throw Exception("Could not open file \"" + Filename + "\" in read+write mode");
2164  }
2165  #endif
2166  *pResetPos = true;
2167  break;
2168  case stream_mode_closed:
2169  if (_isValidHandle(io.hRead)) _close(io.hRead);
2170  if (_isValidHandle(io.hWrite)) _close(io.hWrite);
2171  io.hRead = io.hWrite = RIFF_INVALID_FILE_HANDLE;
2172  break;
2173  default:
2174  throw Exception("Unknown file access mode");
2175  }
2176  io.Mode = NewMode;
2177  return true;
2178  }
2179  return false;
2180  }
2181 
2192  #if WORDS_BIGENDIAN
2193  bEndianNative = Endian != endian_little;
2194  #else
2195  bEndianNative = Endian != endian_big;
2196  #endif
2197  }
2198 
2209  void File::Save(progress_t* pProgress) {
2210  //TODO: implementation for the case where first chunk is not a global container (List chunk) is not implemented yet (i.e. Korg files)
2211  if (Layout == layout_flat)
2212  throw Exception("Saving a RIFF file with layout_flat is not implemented yet");
2213 
2214  const bool bNeedLoadSubChunks = !pSubChunks;
2215 
2216  // make sure the RIFF tree is built (from the original file)
2217  if (pProgress) {
2218  // divide progress into subprogress
2219  progress_t subprogress;
2220  __divide_progress(pProgress, &subprogress, 1.f, 0.f, 0.2f); // assume 20% of time
2221  // do the actual work
2222  LoadSubChunksRecursively(&subprogress);
2223  // notify subprogress done
2224  __notify_progress(&subprogress, 1.f);
2225  } else
2226  LoadSubChunksRecursively(NULL);
2227 
2228  // reopen file in write mode
2229  SetMode(stream_mode_read_write);
2230 
2231  // get the current file size as it is now still physically stored on disk
2232  const file_offset_t workingFileSize = GetCurrentFileSize();
2233 
2234  // get the overall file size required to save this file
2235  const file_offset_t newFileSize = GetRequiredFileSize(FileOffsetPreference);
2236 
2237  // determine whether this file will yield in a large file (>=4GB) and
2238  // the RIFF file offset size to be used accordingly for all chunks
2239  FileOffsetSize = FileOffsetSizeFor(newFileSize);
2240 
2241  const HandlePair io = FileHandlePair();
2242  const Handle hRead = io.hRead;
2243  const Handle hWrite = io.hWrite;
2244 
2245  // to be able to save the whole file without loading everything into
2246  // RAM and without having to store the data in a temporary file, we
2247  // enlarge the file with the overall positive file size change,
2248  // then move current data towards the end of the file by the calculated
2249  // positive file size difference and finally update / rewrite the file
2250  // by copying the old data back to the right position at the beginning
2251  // of the file
2252 
2253  // if there are positive size changes...
2254  file_offset_t positiveSizeDiff = 0;
2255  if (newFileSize > workingFileSize) {
2256  positiveSizeDiff = newFileSize - workingFileSize;
2257 
2258  // divide progress into subprogress
2259  progress_t subprogress;
2260  if (pProgress)
2261  __divide_progress(pProgress, &subprogress, 1.f,
2262  bNeedLoadSubChunks ? 0.2f : 0.f,
2263  bNeedLoadSubChunks ? 0.7f : 0.6f);
2264 
2265  // ... we enlarge this file first ...
2266  ResizeFile(newFileSize);
2267 
2268  // ... and move current data by the same amount towards end of file.
2269  int8_t* pCopyBuffer = new int8_t[4096];
2270  #if defined(WIN32)
2271  DWORD iBytesMoved = 1; // we have to pass it via pointer to the Windows API, thus the correct size must be ensured
2272  #else
2273  ssize_t iBytesMoved = 1;
2274  #endif
2275  for (file_offset_t ullPos = workingFileSize, iNotif = 0; iBytesMoved > 0; ++iNotif) {
2276  iBytesMoved = (ullPos < 4096) ? ullPos : 4096;
2277  ullPos -= iBytesMoved;
2278  #if POSIX
2279  lseek(hRead, ullPos, SEEK_SET);
2280  iBytesMoved = read(hRead, pCopyBuffer, iBytesMoved);
2281  lseek(hWrite, ullPos + positiveSizeDiff, SEEK_SET);
2282  iBytesMoved = write(hWrite, pCopyBuffer, iBytesMoved);
2283  #elif defined(WIN32)
2284  LARGE_INTEGER liFilePos;
2285  liFilePos.QuadPart = ullPos;
2286  SetFilePointerEx(hRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
2287  ReadFile(hRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL);
2288  liFilePos.QuadPart = ullPos + positiveSizeDiff;
2289  SetFilePointerEx(hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
2290  WriteFile(hWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL);
2291  #else
2292  fseeko(hRead, ullPos, SEEK_SET);
2293  iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, hRead);
2294  fseeko(hWrite, ullPos + positiveSizeDiff, SEEK_SET);
2295  iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, hWrite);
2296  #endif
2297  if (pProgress && !(iNotif % 8) && iBytesMoved > 0)
2298  __notify_progress(&subprogress, float(workingFileSize - ullPos) / float(workingFileSize));
2299  }
2300  delete[] pCopyBuffer;
2301  if (iBytesMoved < 0) throw Exception("Could not modify file while trying to enlarge it");
2302 
2303  if (pProgress)
2304  __notify_progress(&subprogress, 1.f); // notify subprogress done
2305  }
2306 
2307  // rebuild / rewrite complete RIFF tree ...
2308 
2309  // divide progress into subprogress
2310  progress_t subprogress;
2311  _FileProgress fileProgress = { 0, newFileSize };
2312  if (pProgress) {
2313  __divide_progress(pProgress, &subprogress, 1.f,
2314  bNeedLoadSubChunks ? 0.7f : 0.6f, 1.f);
2315  subprogress.__private = &fileProgress;
2316  }
2317  // do the actual work
2318  const file_offset_t finalSize = WriteChunk(0, positiveSizeDiff, pProgress ? &subprogress : NULL);
2319  const file_offset_t finalActualSize = __GetFileSize(hWrite);
2320  // notify subprogress done
2321  if (pProgress)
2322  __notify_progress(&subprogress, 1.f);
2323 
2324  // resize file to the final size
2325  if (finalSize < finalActualSize) ResizeFile(finalSize);
2326 
2327  if (pProgress)
2328  __notify_progress(pProgress, 1.0); // notify done
2329  }
2330 
2346  void File::Save(const String& path, progress_t* pProgress) {
2347  //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
2348 
2349  //TODO: implementation for the case where first chunk is not a global container (List chunk) is not implemented yet (i.e. Korg files)
2350  if (Layout == layout_flat)
2351  throw Exception("Saving a RIFF file with layout_flat is not implemented yet");
2352 
2353  const bool bNeedLoadSubChunks = !pSubChunks;
2354 
2355  // make sure the RIFF tree is built (from the original file)
2356  if (pProgress) {
2357  // divide progress into subprogress
2358  progress_t subprogress;
2359  __divide_progress(pProgress, &subprogress, 1.f, 0.f, 0.3f); // assume 30% of time
2360  // do the actual work
2361  LoadSubChunksRecursively(&subprogress);
2362  // notify subprogress done
2363  if (bNeedLoadSubChunks)
2364  __notify_progress(&subprogress, 1.f);
2365  } else
2366  LoadSubChunksRecursively(NULL);
2367 
2368  if (!bIsNewFile) SetMode(stream_mode_read);
2369 
2370  {
2371  std::lock_guard<std::mutex> lock(io.mutex);
2372  HandlePair& io = FileHandlePairUnsafeRef();
2373 
2374  // open the other (new) file for writing and truncate it to zero size
2375  #if POSIX
2376  io.hWrite = open(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP);
2377  if (!_isValidHandle(io.hWrite)) {
2378  io.hWrite = io.hRead;
2379  String sError = strerror(errno);
2380  throw Exception("Could not open file \"" + path + "\" for writing: " + sError);
2381  }
2382  #elif defined(WIN32)
2383  io.hWrite = CreateFile(
2384  path.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
2385  NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |
2386  FILE_FLAG_RANDOM_ACCESS, NULL
2387  );
2388  if (!_isValidHandle(io.hWrite)) {
2389  io.hWrite = io.hRead;
2390  throw Exception("Could not open file \"" + path + "\" for writing");
2391  }
2392  #else
2393  io.hWrite = fopen(path.c_str(), "w+b");
2394  if (!_isValidHandle(io.hWrite)) {
2395  io.hWrite = io.hRead;
2396  throw Exception("Could not open file \"" + path + "\" for writing");
2397  }
2398  #endif // POSIX
2399  io.Mode = stream_mode_read_write;
2400  }
2401 
2402  // get the overall file size required to save this file
2403  const file_offset_t newFileSize = GetRequiredFileSize(FileOffsetPreference);
2404 
2405  // determine whether this file will yield in a large file (>=4GB) and
2406  // the RIFF file offset size to be used accordingly for all chunks
2407  FileOffsetSize = FileOffsetSizeFor(newFileSize);
2408 
2409  // write complete RIFF tree to the other (new) file
2410  file_offset_t ullTotalSize;
2411  if (pProgress) {
2412  // divide progress into subprogress
2413  progress_t subprogress;
2414  __divide_progress(pProgress, &subprogress, 1.f,
2415  bNeedLoadSubChunks ? 0.3f : 0.f, 1.f);
2416  _FileProgress fileProgress = { 0, newFileSize };
2417  subprogress.__private = &fileProgress;
2418  // do the actual work
2419  ullTotalSize = WriteChunk(0, 0, &subprogress);
2420  // notify subprogress done
2421  __notify_progress(&subprogress, 1.f);
2422  } else
2423  ullTotalSize = WriteChunk(0, 0, NULL);
2424 
2425  const file_offset_t ullActualSize = __GetFileSize(FileWriteHandle());
2426 
2427  // resize file to the final size (if the file was originally larger)
2428  if (ullActualSize > ullTotalSize) ResizeFile(ullTotalSize);
2429 
2430  {
2431  std::lock_guard<std::mutex> lock(io.mutex);
2432  HandlePair& io = FileHandlePairUnsafeRef();
2433 
2434  if (_isValidHandle(io.hWrite)) _close(io.hWrite);
2435  io.hWrite = io.hRead;
2436 
2437  // associate new file with this File object from now on
2438  Filename = path;
2439  bIsNewFile = false;
2440  io.Mode = (stream_mode_t) -1; // Just set it to an undefined mode ...
2441  }
2442  SetMode(stream_mode_read_write); // ... so SetMode() has to reopen the file handles.
2443 
2444  if (pProgress)
2445  __notify_progress(pProgress, 1.0); // notify done
2446  }
2447 
2448  void File::ResizeFile(file_offset_t ullNewSize) {
2449  const Handle hWrite = FileWriteHandle();
2450  #if POSIX
2451  if (ftruncate(hWrite, ullNewSize) < 0)
2452  throw Exception("Could not resize file \"" + Filename + "\"");
2453  #elif defined(WIN32)
2454  LARGE_INTEGER liFilePos;
2455  liFilePos.QuadPart = ullNewSize;
2456  if (
2457  !SetFilePointerEx(hWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN) ||
2458  !SetEndOfFile(hWrite)
2459  ) throw Exception("Could not resize file \"" + Filename + "\"");
2460  #else
2461  # error Sorry, this version of libgig only supports POSIX and Windows systems yet.
2462  # error Reason: portable implementation of RIFF::File::ResizeFile() is missing (yet)!
2463  #endif
2464  }
2465 
2466  File::~File() {
2467  #if DEBUG_RIFF
2468  std::cout << "File::~File()" << std::endl;
2469  #endif // DEBUG_RIFF
2470  Cleanup();
2471  }
2472 
2477  bool File::IsNew() const {
2478  return bIsNewFile;
2479  }
2480 
2481  void File::Cleanup() {
2482  if (IsIOPerThread()) {
2483  for (auto it = io.byThread.begin(); it != io.byThread.end(); ++it) {
2484  _close(it->second.hRead);
2485  }
2486  } else {
2487  _close(io.hRead);
2488  }
2489  DeleteChunkList();
2490  pFile = NULL;
2491  }
2492 
2500  file_offset_t size = 0;
2501  const Handle hRead = FileHandle();
2502  try {
2503  size = __GetFileSize(hRead);
2504  } catch (...) {
2505  size = 0;
2506  }
2507  return size;
2508  }
2509 
2530  return GetRequiredFileSize(FileOffsetPreference);
2531  }
2532 
2545  switch (fileOffsetSize) {
2546  case offset_size_auto: {
2548  if (fileSize >> 32)
2550  else
2551  return fileSize;
2552  }
2553  case offset_size_32bit: break;
2554  case offset_size_64bit: break;
2555  default: throw Exception("Internal error: Invalid RIFF::offset_size_t");
2556  }
2558  }
2559 
2560  int File::FileOffsetSizeFor(file_offset_t fileSize) const {
2561  switch (FileOffsetPreference) {
2562  case offset_size_auto:
2563  return (fileSize >> 32) ? 8 : 4;
2564  case offset_size_32bit:
2565  return 4;
2566  case offset_size_64bit:
2567  return 8;
2568  default:
2569  throw Exception("Internal error: Invalid RIFF::offset_size_t");
2570  }
2571  }
2572 
2594  return FileOffsetSize;
2595  }
2596 
2608  return FileOffsetSizeFor(GetCurrentFileSize());
2609  }
2610 
2637  bool File::IsIOPerThread() const {
2638  //NOTE: Not caring about atomicity here at all, for three reasons:
2639  // 1. SetIOPerThread() is assumed to be called only once for the entire
2640  // life time of a RIFF::File, usually very early at its lifetime, and
2641  // hence a change to isPerThread should already safely be propagated
2642  // before any other thread would actually read this boolean flag.
2643  // 2. This method is called very frequently, and therefore any
2644  // synchronization techique would hurt runtime efficiency.
2645  // 3. Using even a mutex lock here might easily cause a deadlock due to
2646  // other locks been taken in this .cpp file, i.e. at a higher call
2647  // stack level (and this is the main reason why I removed it here).
2648  return io.isPerThread;
2649  }
2650 
2666  void File::SetIOPerThread(bool enable) {
2667  std::lock_guard<std::mutex> lock(io.mutex);
2668  if (!io.byThread.empty() == enable) return;
2669  io.isPerThread = enable;
2670  if (enable) {
2671  const std::thread::id tid = std::this_thread::get_id();
2672  io.byThread[tid] = io;
2673  } else {
2674  // retain an arbitrary handle pair, close all other handle pairs
2675  for (auto it = io.byThread.begin(); it != io.byThread.end(); ++it) {
2676  if (it == io.byThread.begin()) {
2677  io.hRead = it->second.hRead;
2678  io.hWrite = it->second.hWrite;
2679  } else {
2680  _close(it->second.hRead);
2681  _close(it->second.hWrite);
2682  }
2683  }
2684  io.byThread.clear();
2685  }
2686  }
2687 
2696  size_t File::totalDataChunkCount() const {
2697  return nDataChunkCount;
2698  }
2699 
2708  size_t File::totalListChunkCount() const {
2709  return nListChunkCount;
2710  }
2711 
2724  size_t File::totalChunkCount() const {
2725  return nDataChunkCount + nListChunkCount;
2726  }
2727 
2728  #if POSIX
2729  file_offset_t File::__GetFileSize(int hFile) const {
2730  struct stat filestat;
2731  if (fstat(hFile, &filestat) == -1)
2732  throw Exception("POSIX FS error: could not determine file size");
2733  return filestat.st_size;
2734  }
2735  #elif defined(WIN32)
2736  file_offset_t File::__GetFileSize(HANDLE hFile) const {
2737  LARGE_INTEGER size;
2738  if (!GetFileSizeEx(hFile, &size))
2739  throw Exception("Windows FS error: could not determine file size");
2740  return size.QuadPart;
2741  }
2742  #else // standard C functions
2743  file_offset_t File::__GetFileSize(FILE* hFile) const {
2744  off_t curpos = ftello(hFile);
2745  if (fseeko(hFile, 0, SEEK_END) == -1)
2746  throw Exception("FS error: could not determine file size");
2747  off_t size = ftello(hFile);
2748  fseeko(hFile, curpos, SEEK_SET);
2749  return size;
2750  }
2751  #endif
2752 
2753 
2754 // *************** Exception ***************
2755 // *
2756 
2757  Exception::Exception() {
2758  }
2759 
2760  Exception::Exception(String format, ...) {
2761  va_list arg;
2762  va_start(arg, format);
2763  Message = assemble(format, arg);
2764  va_end(arg);
2765  }
2766 
2767  Exception::Exception(String format, va_list arg) {
2768  Message = assemble(format, arg);
2769  }
2770 
2771  Exception::~Exception() {
2772  }
2773 
2774  void Exception::PrintMessage() const {
2775  std::cout << "RIFF::Exception: " << Message << std::endl;
2776  }
2777 
2778  String Exception::assemble(String format, va_list arg) {
2779  char* buf = NULL;
2780  vasprintf(&buf, format.c_str(), arg);
2781  String s = buf;
2782  free(buf);
2783  return s;
2784  }
2785 
2786 
2787 // *************** functions ***************
2788 // *
2789 
2795  String libraryName() {
2796  return PACKAGE;
2797  }
2798 
2803  String libraryVersion() {
2804  return VERSION;
2805  }
2806 
2807 } // namespace RIFF
Ordinary RIFF Chunk.
Definition: RIFF.h:186
file_offset_t WriteInt16(int16_t *pData, file_offset_t WordCount=1)
Writes WordCount number of 16 Bit signed integer words from the buffer pointed by pData to the chunk'...
Definition: RIFF.cpp:717
virtual void __resetPos()
Sets Chunk's read/write position to zero.
Definition: RIFF.cpp:1186
void Resize(file_offset_t NewSize)
Resize chunk.
Definition: RIFF.cpp:1058
int8_t ReadInt8()
Reads one 8 Bit signed integer word and increments the position within the chunk.
Definition: RIFF.cpp:864
uint32_t ReadUint32()
Reads one 32 Bit unsigned integer word and increments the position within the chunk.
Definition: RIFF.cpp:953
stream_state_t GetState() const
Returns the current state of the chunk object.
Definition: RIFF.cpp:434
file_offset_t SetPos(file_offset_t Where, stream_whence_t Whence=stream_start)
Sets the position within the chunk body, thus within the data portion of the chunk (in bytes).
Definition: RIFF.cpp:365
file_offset_t GetFilePos() const
Current, actual offset in file of current chunk data body read/write position.
Definition: RIFF.cpp:348
void ReleaseChunkData()
Free loaded chunk body from RAM.
Definition: RIFF.cpp:1033
file_offset_t Write(void *pData, file_offset_t WordCount, file_offset_t WordSize)
Writes WordCount number of data words with given WordSize from the buffer pointed by pData.
Definition: RIFF.cpp:541
file_offset_t RemainingBytes() const
Returns the number of bytes left to read in the chunk body.
Definition: RIFF.cpp:400
int16_t ReadInt16()
Reads one 16 Bit signed integer word and increments the position within the chunk.
Definition: RIFF.cpp:899
virtual file_offset_t WriteChunk(file_offset_t ullWritePos, file_offset_t ullCurrentDataOffset, progress_t *pProgress=NULL)
Write chunk persistently e.g.
Definition: RIFF.cpp:1081
file_offset_t GetPos() const
Current read/write position within the chunk data body (starting with 0).
Definition: RIFF.cpp:335
file_offset_t WriteInt32(int32_t *pData, file_offset_t WordCount=1)
Writes WordCount number of 32 Bit signed integer words from the buffer pointed by pData to the chunk'...
Definition: RIFF.cpp:795
file_offset_t ReadSceptical(void *pData, file_offset_t WordCount, file_offset_t WordSize)
Just an internal wrapper for the main Read() method with additional Exception throwing on errors.
Definition: RIFF.cpp:599
virtual file_offset_t RequiredPhysicalSize(int fileOffsetSize)
Returns the actual total size in bytes (including header) of this Chunk if being stored to a file.
Definition: RIFF.cpp:415
file_offset_t Read(void *pData, file_offset_t WordCount, file_offset_t WordSize)
Reads WordCount number of data words with given WordSize and copies it into a buffer pointed by pData...
Definition: RIFF.cpp:465
uint32_t GetChunkID() const
Chunk ID in unsigned integer representation.
Definition: RIFF.h:190
void * LoadChunkData()
Load chunk body into RAM.
Definition: RIFF.cpp:984
String GetChunkIDString() const
Returns the String representation of the chunk's ID (e.g.
Definition: RIFF.cpp:312
uint8_t ReadUint8()
Reads one 8 Bit unsigned integer word and increments the position within the chunk.
Definition: RIFF.cpp:881
file_offset_t WriteInt8(int8_t *pData, file_offset_t WordCount=1)
Writes WordCount number of 8 Bit signed integer words from the buffer pointed by pData to the chunk's...
Definition: RIFF.cpp:639
file_offset_t WriteUint16(uint16_t *pData, file_offset_t WordCount=1)
Writes WordCount number of 16 Bit unsigned integer words from the buffer pointed by pData to the chun...
Definition: RIFF.cpp:756
file_offset_t WriteUint32(uint32_t *pData, file_offset_t WordCount=1)
Writes WordCount number of 32 Bit unsigned integer words from the buffer pointed by pData to the chun...
Definition: RIFF.cpp:852
void ReadString(String &s, int size)
Reads a null-padded string of size characters and copies it into the string s.
Definition: RIFF.cpp:830
uint16_t ReadUint16()
Reads one 16 Bit unsigned integer word and increments the position within the chunk.
Definition: RIFF.cpp:917
file_offset_t GetSize() const
Chunk size in bytes (without header, thus the chunk data body)
Definition: RIFF.h:193
int32_t ReadInt32()
Reads one 32 Bit signed integer word and increments the position within the chunk.
Definition: RIFF.cpp:935
file_offset_t WriteUint8(uint8_t *pData, file_offset_t WordCount=1)
Writes WordCount number of 8 Bit unsigned integer words from the buffer pointed by pData to the chunk...
Definition: RIFF.cpp:678
Will be thrown whenever an error occurs while handling a RIFF file.
Definition: RIFF.h:397
RIFF File.
Definition: RIFF.h:314
Handle FileWriteHandle() const
Returns the OS dependent file I/O write handle intended to be used by the calling thread.
Definition: RIFF.cpp:2056
HandlePair FileHandlePair() const
Returns the OS dependent file I/O read and write handles intended to be used by the calling thread.
Definition: RIFF.cpp:2025
bool SetMode(stream_mode_t NewMode)
Change file access mode.
Definition: RIFF.cpp:2086
int GetRequiredFileOffsetSize()
Returns the required size (in bytes) of file offsets stored in the headers of all chunks of this file...
Definition: RIFF.cpp:2607
int GetFileOffsetSize() const
Returns the current size (in bytes) of file offsets stored in the headers of all chunks of this file.
Definition: RIFF.cpp:2593
File(uint32_t FileType)
Create new RIFF file.
Definition: RIFF.cpp:1833
file_offset_t GetCurrentFileSize() const
Returns the current size of this file (in bytes) as it is currently yet stored on disk.
Definition: RIFF.cpp:2499
void SetByteOrder(endian_t Endian)
Set the byte order to be used when saving.
Definition: RIFF.cpp:2191
bool IsNew() const
Returns true if this file has been created new from scratch and has not been stored to disk yet.
Definition: RIFF.cpp:2477
file_offset_t GetRequiredFileSize()
Returns the required size (in bytes) for this RIFF File to be saved to disk.
Definition: RIFF.cpp:2529
size_t totalDataChunkCount() const
File's total amount of ordinary (data) chunks.
Definition: RIFF.cpp:2696
Handle FileHandle() const
Returns the OS dependent file I/O read handle intended to be used by the calling thread.
Definition: RIFF.cpp:2046
bool IsIOPerThread() const
Whether file streams are independent for each thread.
Definition: RIFF.cpp:2637
size_t totalChunkCount() const
File's total amount of data chunks and list chunks.
Definition: RIFF.cpp:2724
stream_mode_t GetMode() const
Returns the file I/O mode currently being available for the calling thread for this RIFF file (either...
Definition: RIFF.cpp:2066
size_t totalListChunkCount() const
File's total amount of list chunks.
Definition: RIFF.cpp:2708
virtual void Save(progress_t *pProgress=NULL)
Save changes to same file.
Definition: RIFF.cpp:2209
layout_t Layout
An ordinary RIFF file is always set to layout_standard.
Definition: RIFF.h:367
int Handle
OS dependent type serving as file handle / descriptor for OS dependent file I/O operations.
Definition: RIFF.h:321
int FileOffsetSize
Size of file offsets (in bytes) when this file was opened (or saved the last time).
Definition: RIFF.h:369
void SetIOPerThread(bool enable)
Enable/disable file streams being independent for each thread.
Definition: RIFF.cpp:2666
RIFF List Chunk.
Definition: RIFF.h:262
size_t CountSubLists()
Returns number of sublists within the list.
Definition: RIFF.cpp:1449
Chunk * GetSubChunk(uint32_t ChunkID)
Returns subchunk with chunk ID ChunkID within this chunk list.
Definition: RIFF.cpp:1273
virtual file_offset_t RequiredPhysicalSize(int fileOffsetSize)
Returns the actual total size in bytes (including List chunk header and all subchunks) of this List C...
Definition: RIFF.cpp:1625
void MoveSubChunk(Chunk *pSrc, Chunk *pDst)
Moves a sub chunk witin this list.
Definition: RIFF.cpp:1511
List * GetFirstSubList()
Returns the first sublist within the list (that is a subchunk with chunk ID "LIST").
Definition: RIFF.cpp:1381
List * AddSubList(uint32_t uiListType)
Creates a new list sub chunk.
Definition: RIFF.cpp:1568
Chunk * GetFirstSubChunk()
Returns the first subchunk within the list (which may be an ordinary chunk as well as a list chunk).
Definition: RIFF.cpp:1342
virtual file_offset_t WriteChunk(file_offset_t ullWritePos, file_offset_t ullCurrentDataOffset, progress_t *pProgress=NULL)
Write list chunk persistently e.g.
Definition: RIFF.cpp:1768
void DeleteSubChunk(Chunk *pSubChunk)
Removes a sub chunk.
Definition: RIFF.cpp:1591
List * GetSubListAt(size_t pos)
Returns sublist chunk with list type ListType at supplied pos position among all subchunks of type Li...
Definition: RIFF.cpp:1291
size_t CountSubChunks()
Returns number of subchunks within the list (including list chunks).
Definition: RIFF.cpp:1423
List * GetSubList(uint32_t ListType)
Returns sublist chunk with list type ListType within this chunk list.
Definition: RIFF.cpp:1314
Chunk * GetNextSubChunk()
Returns the next subchunk within the list (which may be an ordinary chunk as well as a list chunk).
Definition: RIFF.cpp:1361
uint32_t GetListType() const
Returns unsigned integer representation of the list's ID.
Definition: RIFF.h:266
String GetListTypeString() const
Returns string representation of the lists's id.
Definition: RIFF.cpp:1810
List * GetNextSubList()
Returns the next sublist (that is a subchunk with chunk ID "LIST") within the list.
Definition: RIFF.cpp:1405
Chunk * AddSubChunk(uint32_t uiChunkID, file_offset_t ullBodySize)
Creates a new sub chunk.
Definition: RIFF.cpp:1485
virtual void __resetPos()
Sets List Chunk's read/write position to zero and causes all sub chunks to do the same.
Definition: RIFF.cpp:1798
Chunk * GetSubChunkAt(size_t pos)
Returns subchunk at supplied pos position within this chunk list.
Definition: RIFF.cpp:1256
RIFF specific classes and definitions.
Definition: RIFF.h:101
String libraryVersion()
Returns version of this C++ library.
Definition: RIFF.cpp:2803
stream_whence_t
File stream position dependent to these relations.
Definition: RIFF.h:128
stream_state_t
Current state of the file stream.
Definition: RIFF.h:121
String libraryName()
Returns the name of this C++ library.
Definition: RIFF.cpp:2795
offset_size_t
Size of RIFF file offsets used in all RIFF chunks' headers.
Definition: RIFF.h:149
@ offset_size_64bit
Always use 64 bit offsets (even for files smaller than 4 GB).
Definition: RIFF.h:152
@ offset_size_auto
Use 32 bit offsets for files smaller than 4 GB, use 64 bit offsets for files equal or larger than 4 G...
Definition: RIFF.h:150
@ offset_size_32bit
Always use 32 bit offsets (even for files larger than 4 GB).
Definition: RIFF.h:151
stream_mode_t
Whether file stream is open in read or in read/write mode.
Definition: RIFF.h:114
layout_t
General RIFF chunk structure of a RIFF file.
Definition: RIFF.h:143
@ layout_standard
Standard RIFF file layout: First chunk in file is a List chunk which contains all other chunks and th...
Definition: RIFF.h:144
@ layout_flat
Not a "real" RIFF file: First chunk in file is an ordinary data chunk, not a List chunk,...
Definition: RIFF.h:145
endian_t
Alignment of data bytes in memory (system dependant).
Definition: RIFF.h:136
uint64_t file_offset_t
Type used by libgig for handling file positioning during file I/O tasks.
Definition: RIFF.h:111
Used for indicating the progress of a certain task.
Definition: RIFF.h:167
float __range_min
Only for internal usage, do not modify!
Definition: RIFF.h:172
void * __private
Only for internal usage, do not modify!
Definition: RIFF.h:174
void(* callback)(progress_t *)
Callback function pointer which has to be assigned to a function for progress notification.
Definition: RIFF.h:168
void * custom
This pointer can be used for arbitrary data.
Definition: RIFF.h:170
const char * activity
Text which describes current ongoing action (e.g. to be displayed along a progress bar).
Definition: RIFF.h:171
std::vector< progress_t > subdivide(int iSubtasks) const
Divides this progress task into the requested amount of equal weighted sub-progress tasks and returns...
Definition: RIFF.cpp:102
float __range_max
Only for internal usage, do not modify!
Definition: RIFF.h:173