/[svn]/libgig/trunk/src/RIFF.cpp
ViewVC logotype

Contents of /libgig/trunk/src/RIFF.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2922 - (show annotations) (download)
Wed May 18 18:04:49 2016 UTC (4 years, 5 months ago) by schoenebeck
File size: 96013 byte(s)
* Using now native integer size where appropriate.
* Bumped version (4.0.0.svn5).

1 /***************************************************************************
2 * *
3 * libgig - C++ cross-platform Gigasampler format file access library *
4 * *
5 * Copyright (C) 2003-2016 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 namespace RIFF {
37
38 // *************** Internal functions **************
39 // *
40
41 /// Returns a human readable path of the given chunk.
42 static String __resolveChunkPath(Chunk* pCk) {
43 String sPath;
44 for (Chunk* pChunk = pCk; pChunk; pChunk = pChunk->GetParent()) {
45 if (pChunk->GetChunkID() == CHUNK_ID_LIST) {
46 List* pList = (List*) pChunk;
47 sPath = "->'" + pList->GetListTypeString() + "'" + sPath;
48 } else {
49 sPath = "->'" + pChunk->GetChunkIDString() + "'" + sPath;
50 }
51 }
52 return sPath;
53 }
54
55
56
57 // *************** progress_t ***************
58 // *
59
60 progress_t::progress_t() {
61 callback = NULL;
62 custom = NULL;
63 __range_min = 0.0f;
64 __range_max = 1.0f;
65 }
66
67
68
69 // *************** Chunk **************
70 // *
71
72 Chunk::Chunk(File* pFile) {
73 #if DEBUG
74 std::cout << "Chunk::Chunk(File* pFile)" << std::endl;
75 #endif // DEBUG
76 ullPos = 0;
77 pParent = NULL;
78 pChunkData = NULL;
79 ullCurrentChunkSize = 0;
80 ullNewChunkSize = 0;
81 ullChunkDataSize = 0;
82 ChunkID = CHUNK_ID_RIFF;
83 this->pFile = pFile;
84 }
85
86 Chunk::Chunk(File* pFile, file_offset_t StartPos, List* Parent) {
87 #if DEBUG
88 std::cout << "Chunk::Chunk(File*,file_offset_t,List*),StartPos=" << StartPos << std::endl;
89 #endif // DEBUG
90 this->pFile = pFile;
91 ullStartPos = StartPos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
92 pParent = Parent;
93 ullPos = 0;
94 pChunkData = NULL;
95 ullCurrentChunkSize = 0;
96 ullNewChunkSize = 0;
97 ullChunkDataSize = 0;
98 ReadHeader(StartPos);
99 }
100
101 Chunk::Chunk(File* pFile, List* pParent, uint32_t uiChunkID, file_offset_t ullBodySize) {
102 this->pFile = pFile;
103 ullStartPos = 0; // arbitrary usually, since it will be updated when we write the chunk
104 this->pParent = pParent;
105 ullPos = 0;
106 pChunkData = NULL;
107 ChunkID = uiChunkID;
108 ullChunkDataSize = 0;
109 ullCurrentChunkSize = 0;
110 ullNewChunkSize = ullBodySize;
111 }
112
113 Chunk::~Chunk() {
114 if (pChunkData) delete[] pChunkData;
115 }
116
117 void Chunk::ReadHeader(file_offset_t filePos) {
118 #if DEBUG
119 std::cout << "Chunk::Readheader(" << filePos << ") ";
120 #endif // DEBUG
121 ChunkID = 0;
122 ullNewChunkSize = ullCurrentChunkSize = 0;
123 #if POSIX
124 if (lseek(pFile->hFileRead, filePos, SEEK_SET) != -1) {
125 read(pFile->hFileRead, &ChunkID, 4);
126 read(pFile->hFileRead, &ullCurrentChunkSize, pFile->FileOffsetSize);
127 #elif defined(WIN32)
128 LARGE_INTEGER liFilePos;
129 liFilePos.QuadPart = filePos;
130 if (SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) {
131 DWORD dwBytesRead;
132 ReadFile(pFile->hFileRead, &ChunkID, 4, &dwBytesRead, NULL);
133 ReadFile(pFile->hFileRead, &ullCurrentChunkSize, pFile->FileOffsetSize, &dwBytesRead, NULL);
134 #else
135 if (!fseeko(pFile->hFileRead, filePos, SEEK_SET)) {
136 fread(&ChunkID, 4, 1, pFile->hFileRead);
137 fread(&ullCurrentChunkSize, pFile->FileOffsetSize, 1, pFile->hFileRead);
138 #endif // POSIX
139 #if WORDS_BIGENDIAN
140 if (ChunkID == CHUNK_ID_RIFF) {
141 pFile->bEndianNative = false;
142 }
143 #else // little endian
144 if (ChunkID == CHUNK_ID_RIFX) {
145 pFile->bEndianNative = false;
146 ChunkID = CHUNK_ID_RIFF;
147 }
148 #endif // WORDS_BIGENDIAN
149 if (!pFile->bEndianNative) {
150 //swapBytes_32(&ChunkID);
151 if (pFile->FileOffsetSize == 4)
152 swapBytes_32(&ullCurrentChunkSize);
153 else
154 swapBytes_64(&ullCurrentChunkSize);
155 }
156 #if DEBUG
157 std::cout << "ckID=" << convertToString(ChunkID) << " ";
158 std::cout << "ckSize=" << ullCurrentChunkSize << " ";
159 std::cout << "bEndianNative=" << pFile->bEndianNative << std::endl;
160 #endif // DEBUG
161 ullNewChunkSize = ullCurrentChunkSize;
162 }
163 }
164
165 void Chunk::WriteHeader(file_offset_t filePos) {
166 uint32_t uiNewChunkID = ChunkID;
167 if (ChunkID == CHUNK_ID_RIFF) {
168 #if WORDS_BIGENDIAN
169 if (pFile->bEndianNative) uiNewChunkID = CHUNK_ID_RIFX;
170 #else // little endian
171 if (!pFile->bEndianNative) uiNewChunkID = CHUNK_ID_RIFX;
172 #endif // WORDS_BIGENDIAN
173 }
174
175 uint64_t ullNewChunkSize = this->ullNewChunkSize;
176 if (!pFile->bEndianNative) {
177 if (pFile->FileOffsetSize == 4)
178 swapBytes_32(&ullNewChunkSize);
179 else
180 swapBytes_64(&ullNewChunkSize);
181 }
182
183 #if POSIX
184 if (lseek(pFile->hFileWrite, filePos, SEEK_SET) != -1) {
185 write(pFile->hFileWrite, &uiNewChunkID, 4);
186 write(pFile->hFileWrite, &ullNewChunkSize, pFile->FileOffsetSize);
187 }
188 #elif defined(WIN32)
189 LARGE_INTEGER liFilePos;
190 liFilePos.QuadPart = filePos;
191 if (SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) {
192 DWORD dwBytesWritten;
193 WriteFile(pFile->hFileWrite, &uiNewChunkID, 4, &dwBytesWritten, NULL);
194 WriteFile(pFile->hFileWrite, &ullNewChunkSize, pFile->FileOffsetSize, &dwBytesWritten, NULL);
195 }
196 #else
197 if (!fseeko(pFile->hFileWrite, filePos, SEEK_SET)) {
198 fwrite(&uiNewChunkID, 4, 1, pFile->hFileWrite);
199 fwrite(&ullNewChunkSize, pFile->FileOffsetSize, 1, pFile->hFileWrite);
200 }
201 #endif // POSIX
202 }
203
204 /**
205 * Returns the String representation of the chunk's ID (e.g. "RIFF",
206 * "LIST").
207 */
208 String Chunk::GetChunkIDString() const {
209 return convertToString(ChunkID);
210 }
211
212 /**
213 * Sets the position within the chunk body, thus within the data portion
214 * of the chunk (in bytes).
215 *
216 * <b>Caution:</b> the position will be reset to zero whenever
217 * File::Save() was called.
218 *
219 * @param Where - position offset (in bytes)
220 * @param Whence - optional: defines to what <i>\a Where</i> relates to,
221 * if omitted \a Where relates to beginning of the chunk
222 * data
223 */
224 file_offset_t Chunk::SetPos(file_offset_t Where, stream_whence_t Whence) {
225 #if DEBUG
226 std::cout << "Chunk::SetPos(file_offset_t,stream_whence_t)" << std::endl;
227 #endif // DEBUG
228 switch (Whence) {
229 case stream_curpos:
230 ullPos += Where;
231 break;
232 case stream_end:
233 ullPos = ullCurrentChunkSize - 1 - Where;
234 break;
235 case stream_backward:
236 ullPos -= Where;
237 break;
238 case stream_start: default:
239 ullPos = Where;
240 break;
241 }
242 if (ullPos > ullCurrentChunkSize) ullPos = ullCurrentChunkSize;
243 return ullPos;
244 }
245
246 /**
247 * Returns the number of bytes left to read in the chunk body.
248 * When reading data from the chunk using the Read*() Methods, the
249 * position within the chunk data (that is the chunk body) will be
250 * incremented by the number of read bytes and RemainingBytes() returns
251 * how much data is left to read from the current position to the end
252 * of the chunk data.
253 *
254 * @returns number of bytes left to read
255 */
256 file_offset_t Chunk::RemainingBytes() const {
257 #if DEBUG
258 std::cout << "Chunk::Remainingbytes()=" << ullCurrentChunkSize - ullPos << std::endl;
259 #endif // DEBUG
260 return (ullCurrentChunkSize > ullPos) ? ullCurrentChunkSize - ullPos : 0;
261 }
262
263 /**
264 * Returns the actual total size in bytes (including header) of this Chunk
265 * if being stored to a file.
266 *
267 * @param fileOffsetSize - RIFF file offset size (in bytes) assumed when
268 * being saved to a file
269 */
270 file_offset_t Chunk::RequiredPhysicalSize(int fileOffsetSize) {
271 return CHUNK_HEADER_SIZE(fileOffsetSize) + // RIFF chunk header
272 ullNewChunkSize + // chunks's actual data body
273 ullNewChunkSize % 2; // optional pad byte
274 }
275
276 /**
277 * Returns the current state of the chunk object.
278 * Following values are possible:
279 * - RIFF::stream_ready :
280 * chunk data can be read (this is the usual case)
281 * - RIFF::stream_closed :
282 * the data stream was closed somehow, no more reading possible
283 * - RIFF::stream_end_reached :
284 * already reached the end of the chunk data, no more reading
285 * possible without SetPos()
286 */
287 stream_state_t Chunk::GetState() const {
288 #if DEBUG
289 std::cout << "Chunk::GetState()" << std::endl;
290 #endif // DEBUG
291 #if POSIX
292 if (pFile->hFileRead == 0) return stream_closed;
293 #elif defined (WIN32)
294 if (pFile->hFileRead == INVALID_HANDLE_VALUE)
295 return stream_closed;
296 #else
297 if (pFile->hFileRead == NULL) return stream_closed;
298 #endif // POSIX
299 if (ullPos < ullCurrentChunkSize) return stream_ready;
300 else return stream_end_reached;
301 }
302
303 /**
304 * Reads \a WordCount number of data words with given \a WordSize and
305 * copies it into a buffer pointed by \a pData. The buffer has to be
306 * allocated and be sure to provide the correct \a WordSize, as this
307 * will be important and taken into account for eventual endian
308 * correction (swapping of bytes due to different native byte order of
309 * a system). The position within the chunk will automatically be
310 * incremented.
311 *
312 * @param pData destination buffer
313 * @param WordCount number of data words to read
314 * @param WordSize size of each data word to read
315 * @returns number of successfully read data words or 0 if end
316 * of file reached or error occured
317 */
318 file_offset_t Chunk::Read(void* pData, file_offset_t WordCount, file_offset_t WordSize) {
319 #if DEBUG
320 std::cout << "Chunk::Read(void*,file_offset_t,file_offset_t)" << std::endl;
321 #endif // DEBUG
322 //if (ulStartPos == 0) return 0; // is only 0 if this is a new chunk, so nothing to read (yet)
323 if (ullPos >= ullCurrentChunkSize) return 0;
324 if (ullPos + WordCount * WordSize >= ullCurrentChunkSize) WordCount = (ullCurrentChunkSize - ullPos) / WordSize;
325 #if POSIX
326 if (lseek(pFile->hFileRead, ullStartPos + ullPos, SEEK_SET) < 0) return 0;
327 ssize_t readWords = read(pFile->hFileRead, pData, WordCount * WordSize);
328 if (readWords < 1) {
329 #if DEBUG
330 std::cerr << "POSIX read() failed: " << strerror(errno) << std::endl << std::flush;
331 #endif // DEBUG
332 return 0;
333 }
334 readWords /= WordSize;
335 #elif defined(WIN32)
336 LARGE_INTEGER liFilePos;
337 liFilePos.QuadPart = ullStartPos + ullPos;
338 if (!SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN))
339 return 0;
340 DWORD readWords;
341 ReadFile(pFile->hFileRead, pData, WordCount * WordSize, &readWords, NULL); //FIXME: does not work for reading buffers larger than 2GB (even though this should rarely be the case in practice)
342 if (readWords < 1) return 0;
343 readWords /= WordSize;
344 #else // standard C functions
345 if (fseeko(pFile->hFileRead, ullStartPos + ullPos, SEEK_SET)) return 0;
346 file_offset_t readWords = fread(pData, WordSize, WordCount, pFile->hFileRead);
347 #endif // POSIX
348 if (!pFile->bEndianNative && WordSize != 1) {
349 switch (WordSize) {
350 case 2:
351 for (file_offset_t iWord = 0; iWord < readWords; iWord++)
352 swapBytes_16((uint16_t*) pData + iWord);
353 break;
354 case 4:
355 for (file_offset_t iWord = 0; iWord < readWords; iWord++)
356 swapBytes_32((uint32_t*) pData + iWord);
357 break;
358 case 8:
359 for (file_offset_t iWord = 0; iWord < readWords; iWord++)
360 swapBytes_64((uint64_t*) pData + iWord);
361 break;
362 default:
363 for (file_offset_t iWord = 0; iWord < readWords; iWord++)
364 swapBytes((uint8_t*) pData + iWord * WordSize, WordSize);
365 break;
366 }
367 }
368 SetPos(readWords * WordSize, stream_curpos);
369 return readWords;
370 }
371
372 /**
373 * Writes \a WordCount number of data words with given \a WordSize from
374 * the buffer pointed by \a pData. Be sure to provide the correct
375 * \a WordSize, as this will be important and taken into account for
376 * eventual endian correction (swapping of bytes due to different
377 * native byte order of a system). The position within the chunk will
378 * automatically be incremented.
379 *
380 * @param pData source buffer (containing the data)
381 * @param WordCount number of data words to write
382 * @param WordSize size of each data word to write
383 * @returns number of successfully written data words
384 * @throws RIFF::Exception if write operation would exceed current
385 * chunk size or any IO error occured
386 * @see Resize()
387 */
388 file_offset_t Chunk::Write(void* pData, file_offset_t WordCount, file_offset_t WordSize) {
389 if (pFile->Mode != stream_mode_read_write)
390 throw Exception("Cannot write data to chunk, file has to be opened in read+write mode first");
391 if (ullPos >= ullCurrentChunkSize || ullPos + WordCount * WordSize > ullCurrentChunkSize)
392 throw Exception("End of chunk reached while trying to write data");
393 if (!pFile->bEndianNative && WordSize != 1) {
394 switch (WordSize) {
395 case 2:
396 for (file_offset_t iWord = 0; iWord < WordCount; iWord++)
397 swapBytes_16((uint16_t*) pData + iWord);
398 break;
399 case 4:
400 for (file_offset_t iWord = 0; iWord < WordCount; iWord++)
401 swapBytes_32((uint32_t*) pData + iWord);
402 break;
403 case 8:
404 for (file_offset_t iWord = 0; iWord < WordCount; iWord++)
405 swapBytes_64((uint64_t*) pData + iWord);
406 break;
407 default:
408 for (file_offset_t iWord = 0; iWord < WordCount; iWord++)
409 swapBytes((uint8_t*) pData + iWord * WordSize, WordSize);
410 break;
411 }
412 }
413 #if POSIX
414 if (lseek(pFile->hFileWrite, ullStartPos + ullPos, SEEK_SET) < 0) {
415 throw Exception("Could not seek to position " + ToString(ullPos) +
416 " in chunk (" + ToString(ullStartPos + ullPos) + " in file)");
417 }
418 ssize_t writtenWords = write(pFile->hFileWrite, pData, WordCount * WordSize);
419 if (writtenWords < 1) throw Exception("POSIX IO Error while trying to write chunk data");
420 writtenWords /= WordSize;
421 #elif defined(WIN32)
422 LARGE_INTEGER liFilePos;
423 liFilePos.QuadPart = ullStartPos + ullPos;
424 if (!SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) {
425 throw Exception("Could not seek to position " + ToString(ullPos) +
426 " in chunk (" + ToString(ullStartPos + ullPos) + " in file)");
427 }
428 DWORD writtenWords;
429 WriteFile(pFile->hFileWrite, pData, WordCount * WordSize, &writtenWords, NULL); //FIXME: does not work for writing buffers larger than 2GB (even though this should rarely be the case in practice)
430 if (writtenWords < 1) throw Exception("Windows IO Error while trying to write chunk data");
431 writtenWords /= WordSize;
432 #else // standard C functions
433 if (fseeko(pFile->hFileWrite, ullStartPos + ullPos, SEEK_SET)) {
434 throw Exception("Could not seek to position " + ToString(ullPos) +
435 " in chunk (" + ToString(ullStartPos + ullPos) + " in file)");
436 }
437 file_offset_t writtenWords = fwrite(pData, WordSize, WordCount, pFile->hFileWrite);
438 #endif // POSIX
439 SetPos(writtenWords * WordSize, stream_curpos);
440 return writtenWords;
441 }
442
443 /** Just an internal wrapper for the main <i>Read()</i> method with additional Exception throwing on errors. */
444 file_offset_t Chunk::ReadSceptical(void* pData, file_offset_t WordCount, file_offset_t WordSize) {
445 file_offset_t readWords = Read(pData, WordCount, WordSize);
446 if (readWords != WordCount) throw RIFF::Exception("End of chunk data reached.");
447 return readWords;
448 }
449
450 /**
451 * Reads \a WordCount number of 8 Bit signed integer words and copies it
452 * into the buffer pointed by \a pData. The buffer has to be allocated.
453 * The position within the chunk will automatically be incremented.
454 *
455 * @param pData destination buffer
456 * @param WordCount number of 8 Bit signed integers to read
457 * @returns number of read integers
458 * @throws RIFF::Exception if an error occured or less than
459 * \a WordCount integers could be read!
460 */
461 file_offset_t Chunk::ReadInt8(int8_t* pData, file_offset_t WordCount) {
462 #if DEBUG
463 std::cout << "Chunk::ReadInt8(int8_t*,file_offset_t)" << std::endl;
464 #endif // DEBUG
465 return ReadSceptical(pData, WordCount, 1);
466 }
467
468 /**
469 * Writes \a WordCount number of 8 Bit signed integer words from the
470 * buffer pointed by \a pData to the chunk's body, directly to the
471 * actual "physical" file. The position within the chunk will
472 * automatically be incremented. Note: you cannot write beyond the
473 * boundaries of the chunk, to append data to the chunk call Resize()
474 * before.
475 *
476 * @param pData source buffer (containing the data)
477 * @param WordCount number of 8 Bit signed integers to write
478 * @returns number of written integers
479 * @throws RIFF::Exception if an IO error occured
480 * @see Resize()
481 */
482 file_offset_t Chunk::WriteInt8(int8_t* pData, file_offset_t WordCount) {
483 return Write(pData, WordCount, 1);
484 }
485
486 /**
487 * Reads \a WordCount number of 8 Bit unsigned integer words and copies
488 * it into the buffer pointed by \a pData. The buffer has to be
489 * allocated. The position within the chunk will automatically be
490 * incremented.
491 *
492 * @param pData destination buffer
493 * @param WordCount number of 8 Bit unsigned integers to read
494 * @returns number of read integers
495 * @throws RIFF::Exception if an error occured or less than
496 * \a WordCount integers could be read!
497 */
498 file_offset_t Chunk::ReadUint8(uint8_t* pData, file_offset_t WordCount) {
499 #if DEBUG
500 std::cout << "Chunk::ReadUint8(uint8_t*,file_offset_t)" << std::endl;
501 #endif // DEBUG
502 return ReadSceptical(pData, WordCount, 1);
503 }
504
505 /**
506 * Writes \a WordCount number of 8 Bit unsigned integer words from the
507 * buffer pointed by \a pData to the chunk's body, directly to the
508 * actual "physical" file. The position within the chunk will
509 * automatically be incremented. Note: you cannot write beyond the
510 * boundaries of the chunk, to append data to the chunk call Resize()
511 * before.
512 *
513 * @param pData source buffer (containing the data)
514 * @param WordCount number of 8 Bit unsigned integers to write
515 * @returns number of written integers
516 * @throws RIFF::Exception if an IO error occured
517 * @see Resize()
518 */
519 file_offset_t Chunk::WriteUint8(uint8_t* pData, file_offset_t WordCount) {
520 return Write(pData, WordCount, 1);
521 }
522
523 /**
524 * Reads \a WordCount number of 16 Bit signed integer words and copies
525 * it into the buffer pointed by \a pData. The buffer has to be
526 * allocated. Endian correction will automatically be done if needed.
527 * The position within the chunk will automatically be incremented.
528 *
529 * @param pData destination buffer
530 * @param WordCount number of 16 Bit signed integers to read
531 * @returns number of read integers
532 * @throws RIFF::Exception if an error occured or less than
533 * \a WordCount integers could be read!
534 */
535 file_offset_t Chunk::ReadInt16(int16_t* pData, file_offset_t WordCount) {
536 #if DEBUG
537 std::cout << "Chunk::ReadInt16(int16_t*,file_offset_t)" << std::endl;
538 #endif // DEBUG
539 return ReadSceptical(pData, WordCount, 2);
540 }
541
542 /**
543 * Writes \a WordCount number of 16 Bit signed integer words from the
544 * buffer pointed by \a pData to the chunk's body, directly to the
545 * actual "physical" file. The position within the chunk will
546 * automatically be incremented. Note: you cannot write beyond the
547 * boundaries of the chunk, to append data to the chunk call Resize()
548 * before.
549 *
550 * @param pData source buffer (containing the data)
551 * @param WordCount number of 16 Bit signed integers to write
552 * @returns number of written integers
553 * @throws RIFF::Exception if an IO error occured
554 * @see Resize()
555 */
556 file_offset_t Chunk::WriteInt16(int16_t* pData, file_offset_t WordCount) {
557 return Write(pData, WordCount, 2);
558 }
559
560 /**
561 * Reads \a WordCount number of 16 Bit unsigned integer words and copies
562 * it into the buffer pointed by \a pData. The buffer has to be
563 * allocated. Endian correction will automatically be done if needed.
564 * The position within the chunk will automatically be incremented.
565 *
566 * @param pData destination buffer
567 * @param WordCount number of 8 Bit unsigned integers to read
568 * @returns number of read integers
569 * @throws RIFF::Exception if an error occured or less than
570 * \a WordCount integers could be read!
571 */
572 file_offset_t Chunk::ReadUint16(uint16_t* pData, file_offset_t WordCount) {
573 #if DEBUG
574 std::cout << "Chunk::ReadUint16(uint16_t*,file_offset_t)" << std::endl;
575 #endif // DEBUG
576 return ReadSceptical(pData, WordCount, 2);
577 }
578
579 /**
580 * Writes \a WordCount number of 16 Bit unsigned integer words from the
581 * buffer pointed by \a pData to the chunk's body, directly to the
582 * actual "physical" file. The position within the chunk will
583 * automatically be incremented. Note: you cannot write beyond the
584 * boundaries of the chunk, to append data to the chunk call Resize()
585 * before.
586 *
587 * @param pData source buffer (containing the data)
588 * @param WordCount number of 16 Bit unsigned integers to write
589 * @returns number of written integers
590 * @throws RIFF::Exception if an IO error occured
591 * @see Resize()
592 */
593 file_offset_t Chunk::WriteUint16(uint16_t* pData, file_offset_t WordCount) {
594 return Write(pData, WordCount, 2);
595 }
596
597 /**
598 * Reads \a WordCount number of 32 Bit signed integer words and copies
599 * it into the buffer pointed by \a pData. The buffer has to be
600 * allocated. Endian correction will automatically be done if needed.
601 * The position within the chunk will automatically be incremented.
602 *
603 * @param pData destination buffer
604 * @param WordCount number of 32 Bit signed integers to read
605 * @returns number of read integers
606 * @throws RIFF::Exception if an error occured or less than
607 * \a WordCount integers could be read!
608 */
609 file_offset_t Chunk::ReadInt32(int32_t* pData, file_offset_t WordCount) {
610 #if DEBUG
611 std::cout << "Chunk::ReadInt32(int32_t*,file_offset_t)" << std::endl;
612 #endif // DEBUG
613 return ReadSceptical(pData, WordCount, 4);
614 }
615
616 /**
617 * Writes \a WordCount number of 32 Bit signed integer words from the
618 * buffer pointed by \a pData to the chunk's body, directly to the
619 * actual "physical" file. The position within the chunk will
620 * automatically be incremented. Note: you cannot write beyond the
621 * boundaries of the chunk, to append data to the chunk call Resize()
622 * before.
623 *
624 * @param pData source buffer (containing the data)
625 * @param WordCount number of 32 Bit signed integers to write
626 * @returns number of written integers
627 * @throws RIFF::Exception if an IO error occured
628 * @see Resize()
629 */
630 file_offset_t Chunk::WriteInt32(int32_t* pData, file_offset_t WordCount) {
631 return Write(pData, WordCount, 4);
632 }
633
634 /**
635 * Reads \a WordCount number of 32 Bit unsigned integer words and copies
636 * it into the buffer pointed by \a pData. The buffer has to be
637 * allocated. Endian correction will automatically be done if needed.
638 * The position within the chunk will automatically be incremented.
639 *
640 * @param pData destination buffer
641 * @param WordCount number of 32 Bit unsigned integers to read
642 * @returns number of read integers
643 * @throws RIFF::Exception if an error occured or less than
644 * \a WordCount integers could be read!
645 */
646 file_offset_t Chunk::ReadUint32(uint32_t* pData, file_offset_t WordCount) {
647 #if DEBUG
648 std::cout << "Chunk::ReadUint32(uint32_t*,file_offset_t)" << std::endl;
649 #endif // DEBUG
650 return ReadSceptical(pData, WordCount, 4);
651 }
652
653 /**
654 * Reads a null-padded string of size characters and copies it
655 * into the string \a s. The position within the chunk will
656 * automatically be incremented.
657 *
658 * @param s destination string
659 * @param size number of characters to read
660 * @throws RIFF::Exception if an error occured or less than
661 * \a size characters could be read!
662 */
663 void Chunk::ReadString(String& s, int size) {
664 char* buf = new char[size];
665 ReadSceptical(buf, 1, size);
666 s.assign(buf, std::find(buf, buf + size, '\0'));
667 delete[] buf;
668 }
669
670 /**
671 * Writes \a WordCount number of 32 Bit unsigned integer words from the
672 * buffer pointed by \a pData to the chunk's body, directly to the
673 * actual "physical" file. The position within the chunk will
674 * automatically be incremented. Note: you cannot write beyond the
675 * boundaries of the chunk, to append data to the chunk call Resize()
676 * before.
677 *
678 * @param pData source buffer (containing the data)
679 * @param WordCount number of 32 Bit unsigned integers to write
680 * @returns number of written integers
681 * @throws RIFF::Exception if an IO error occured
682 * @see Resize()
683 */
684 file_offset_t Chunk::WriteUint32(uint32_t* pData, file_offset_t WordCount) {
685 return Write(pData, WordCount, 4);
686 }
687
688 /**
689 * Reads one 8 Bit signed integer word and increments the position within
690 * the chunk.
691 *
692 * @returns read integer word
693 * @throws RIFF::Exception if an error occured
694 */
695 int8_t Chunk::ReadInt8() {
696 #if DEBUG
697 std::cout << "Chunk::ReadInt8()" << std::endl;
698 #endif // DEBUG
699 int8_t word;
700 ReadSceptical(&word,1,1);
701 return word;
702 }
703
704 /**
705 * Reads one 8 Bit unsigned integer word and increments the position
706 * within the chunk.
707 *
708 * @returns read integer word
709 * @throws RIFF::Exception if an error occured
710 */
711 uint8_t Chunk::ReadUint8() {
712 #if DEBUG
713 std::cout << "Chunk::ReadUint8()" << std::endl;
714 #endif // DEBUG
715 uint8_t word;
716 ReadSceptical(&word,1,1);
717 return word;
718 }
719
720 /**
721 * Reads one 16 Bit signed integer word and increments the position
722 * within the chunk. Endian correction will automatically be done if
723 * needed.
724 *
725 * @returns read integer word
726 * @throws RIFF::Exception if an error occured
727 */
728 int16_t Chunk::ReadInt16() {
729 #if DEBUG
730 std::cout << "Chunk::ReadInt16()" << std::endl;
731 #endif // DEBUG
732 int16_t word;
733 ReadSceptical(&word,1,2);
734 return word;
735 }
736
737 /**
738 * Reads one 16 Bit unsigned integer word and increments the position
739 * within the chunk. Endian correction will automatically be done if
740 * needed.
741 *
742 * @returns read integer word
743 * @throws RIFF::Exception if an error occured
744 */
745 uint16_t Chunk::ReadUint16() {
746 #if DEBUG
747 std::cout << "Chunk::ReadUint16()" << std::endl;
748 #endif // DEBUG
749 uint16_t word;
750 ReadSceptical(&word,1,2);
751 return word;
752 }
753
754 /**
755 * Reads one 32 Bit signed integer word and increments the position
756 * within the chunk. Endian correction will automatically be done if
757 * needed.
758 *
759 * @returns read integer word
760 * @throws RIFF::Exception if an error occured
761 */
762 int32_t Chunk::ReadInt32() {
763 #if DEBUG
764 std::cout << "Chunk::ReadInt32()" << std::endl;
765 #endif // DEBUG
766 int32_t word;
767 ReadSceptical(&word,1,4);
768 return word;
769 }
770
771 /**
772 * Reads one 32 Bit unsigned integer word and increments the position
773 * within the chunk. Endian correction will automatically be done if
774 * needed.
775 *
776 * @returns read integer word
777 * @throws RIFF::Exception if an error occured
778 */
779 uint32_t Chunk::ReadUint32() {
780 #if DEBUG
781 std::cout << "Chunk::ReadUint32()" << std::endl;
782 #endif // DEBUG
783 uint32_t word;
784 ReadSceptical(&word,1,4);
785 return word;
786 }
787
788 /** @brief Load chunk body into RAM.
789 *
790 * Loads the whole chunk body into memory. You can modify the data in
791 * RAM and save the data by calling File::Save() afterwards.
792 *
793 * <b>Caution:</b> the buffer pointer will be invalidated once
794 * File::Save() was called. You have to call LoadChunkData() again to
795 * get a new, valid pointer whenever File::Save() was called.
796 *
797 * You can call LoadChunkData() again if you previously scheduled to
798 * enlarge this chunk with a Resize() call. In that case the buffer will
799 * be enlarged to the new, scheduled chunk size and you can already
800 * place the new chunk data to the buffer and finally call File::Save()
801 * to enlarge the chunk physically and write the new data in one rush.
802 * This approach is definitely recommended if you have to enlarge and
803 * write new data to a lot of chunks.
804 *
805 * @returns a pointer to the data in RAM on success, NULL otherwise
806 * @throws Exception if data buffer could not be enlarged
807 * @see ReleaseChunkData()
808 */
809 void* Chunk::LoadChunkData() {
810 if (!pChunkData && pFile->Filename != "" /*&& ulStartPos != 0*/) {
811 #if POSIX
812 if (lseek(pFile->hFileRead, ullStartPos, SEEK_SET) == -1) return NULL;
813 #elif defined(WIN32)
814 LARGE_INTEGER liFilePos;
815 liFilePos.QuadPart = ullStartPos;
816 if (!SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN)) return NULL;
817 #else
818 if (fseeko(pFile->hFileRead, ullStartPos, SEEK_SET)) return NULL;
819 #endif // POSIX
820 file_offset_t ullBufferSize = (ullCurrentChunkSize > ullNewChunkSize) ? ullCurrentChunkSize : ullNewChunkSize;
821 pChunkData = new uint8_t[ullBufferSize];
822 if (!pChunkData) return NULL;
823 memset(pChunkData, 0, ullBufferSize);
824 #if POSIX
825 file_offset_t readWords = read(pFile->hFileRead, pChunkData, GetSize());
826 #elif defined(WIN32)
827 DWORD readWords;
828 ReadFile(pFile->hFileRead, pChunkData, GetSize(), &readWords, NULL); //FIXME: won't load chunks larger than 2GB !
829 #else
830 file_offset_t readWords = fread(pChunkData, 1, GetSize(), pFile->hFileRead);
831 #endif // POSIX
832 if (readWords != GetSize()) {
833 delete[] pChunkData;
834 return (pChunkData = NULL);
835 }
836 ullChunkDataSize = ullBufferSize;
837 } else if (ullNewChunkSize > ullChunkDataSize) {
838 uint8_t* pNewBuffer = new uint8_t[ullNewChunkSize];
839 if (!pNewBuffer) throw Exception("Could not enlarge chunk data buffer to " + ToString(ullNewChunkSize) + " bytes");
840 memset(pNewBuffer, 0 , ullNewChunkSize);
841 memcpy(pNewBuffer, pChunkData, ullChunkDataSize);
842 delete[] pChunkData;
843 pChunkData = pNewBuffer;
844 ullChunkDataSize = ullNewChunkSize;
845 }
846 return pChunkData;
847 }
848
849 /** @brief Free loaded chunk body from RAM.
850 *
851 * Frees loaded chunk body data from memory (RAM). You should call
852 * File::Save() before calling this method if you modified the data to
853 * make the changes persistent.
854 */
855 void Chunk::ReleaseChunkData() {
856 if (pChunkData) {
857 delete[] pChunkData;
858 pChunkData = NULL;
859 }
860 }
861
862 /** @brief Resize chunk.
863 *
864 * Resizes this chunk's body, that is the actual size of data possible
865 * to be written to this chunk. This call will return immediately and
866 * just schedule the resize operation. You should call File::Save() to
867 * actually perform the resize operation(s) "physically" to the file.
868 * As this can take a while on large files, it is recommended to call
869 * Resize() first on all chunks which have to be resized and finally to
870 * call File::Save() to perform all those resize operations in one rush.
871 *
872 * <b>Caution:</b> You cannot directly write to enlarged chunks before
873 * calling File::Save() as this might exceed the current chunk's body
874 * boundary!
875 *
876 * @param NewSize - new chunk body size in bytes (must be greater than zero)
877 * @throws RIFF::Exception if \a NewSize is less than 1 or unrealistic large
878 * @see File::Save()
879 */
880 void Chunk::Resize(file_offset_t NewSize) {
881 if (NewSize == 0)
882 throw Exception("There is at least one empty chunk (zero size): " + __resolveChunkPath(this));
883 if ((NewSize >> 48) != 0)
884 throw Exception("Unrealistic high chunk size detected: " + __resolveChunkPath(this));
885 if (ullNewChunkSize == NewSize) return;
886 ullNewChunkSize = NewSize;
887 }
888
889 /** @brief Write chunk persistently e.g. to disk.
890 *
891 * Stores the chunk persistently to its actual "physical" file.
892 *
893 * @param ullWritePos - position within the "physical" file where this
894 * chunk should be written to
895 * @param ullCurrentDataOffset - offset of current (old) data within
896 * the file
897 * @param pProgress - optional: callback function for progress notification
898 * @returns new write position in the "physical" file, that is
899 * \a ullWritePos incremented by this chunk's new size
900 * (including its header size of course)
901 */
902 file_offset_t Chunk::WriteChunk(file_offset_t ullWritePos, file_offset_t ullCurrentDataOffset, progress_t* pProgress) {
903 const file_offset_t ullOriginalPos = ullWritePos;
904 ullWritePos += CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
905
906 if (pFile->Mode != stream_mode_read_write)
907 throw Exception("Cannot write list chunk, file has to be opened in read+write mode");
908
909 // if the whole chunk body was loaded into RAM
910 if (pChunkData) {
911 // make sure chunk data buffer in RAM is at least as large as the new chunk size
912 LoadChunkData();
913 // write chunk data from RAM persistently to the file
914 #if POSIX
915 lseek(pFile->hFileWrite, ullWritePos, SEEK_SET);
916 if (write(pFile->hFileWrite, pChunkData, ullNewChunkSize) != ullNewChunkSize) {
917 throw Exception("Writing Chunk data (from RAM) failed");
918 }
919 #elif defined(WIN32)
920 LARGE_INTEGER liFilePos;
921 liFilePos.QuadPart = ullWritePos;
922 SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
923 DWORD dwBytesWritten;
924 WriteFile(pFile->hFileWrite, pChunkData, ullNewChunkSize, &dwBytesWritten, NULL); //FIXME: won't save chunks larger than 2GB !
925 if (dwBytesWritten != ullNewChunkSize) {
926 throw Exception("Writing Chunk data (from RAM) failed");
927 }
928 #else
929 fseeko(pFile->hFileWrite, ullWritePos, SEEK_SET);
930 if (fwrite(pChunkData, 1, ullNewChunkSize, pFile->hFileWrite) != ullNewChunkSize) {
931 throw Exception("Writing Chunk data (from RAM) failed");
932 }
933 #endif // POSIX
934 } else {
935 // move chunk data from the end of the file to the appropriate position
936 int8_t* pCopyBuffer = new int8_t[4096];
937 file_offset_t ullToMove = (ullNewChunkSize < ullCurrentChunkSize) ? ullNewChunkSize : ullCurrentChunkSize;
938 #if defined(WIN32)
939 DWORD iBytesMoved = 1; // we have to pass it via pointer to the Windows API, thus the correct size must be ensured
940 #else
941 int iBytesMoved = 1;
942 #endif
943 for (file_offset_t ullOffset = 0; ullToMove > 0 && iBytesMoved > 0; ullOffset += iBytesMoved, ullToMove -= iBytesMoved) {
944 iBytesMoved = (ullToMove < 4096) ? ullToMove : 4096;
945 #if POSIX
946 lseek(pFile->hFileRead, ullStartPos + ullCurrentDataOffset + ullOffset, SEEK_SET);
947 iBytesMoved = read(pFile->hFileRead, pCopyBuffer, iBytesMoved);
948 lseek(pFile->hFileWrite, ullWritePos + ullOffset, SEEK_SET);
949 iBytesMoved = write(pFile->hFileWrite, pCopyBuffer, iBytesMoved);
950 #elif defined(WIN32)
951 LARGE_INTEGER liFilePos;
952 liFilePos.QuadPart = ullStartPos + ullCurrentDataOffset + ullOffset;
953 SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
954 ReadFile(pFile->hFileRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL);
955 liFilePos.QuadPart = ullWritePos + ullOffset;
956 SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
957 WriteFile(pFile->hFileWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL);
958 #else
959 fseeko(pFile->hFileRead, ullStartPos + ullCurrentDataOffset + ullOffset, SEEK_SET);
960 iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, pFile->hFileRead);
961 fseeko(pFile->hFileWrite, ullWritePos + ullOffset, SEEK_SET);
962 iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, pFile->hFileWrite);
963 #endif
964 }
965 delete[] pCopyBuffer;
966 if (iBytesMoved < 0) throw Exception("Writing Chunk data (from file) failed");
967 }
968
969 // update this chunk's header
970 ullCurrentChunkSize = ullNewChunkSize;
971 WriteHeader(ullOriginalPos);
972
973 __notify_progress(pProgress, 1.0); // notify done
974
975 // update chunk's position pointers
976 ullStartPos = ullOriginalPos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
977 ullPos = 0;
978
979 // add pad byte if needed
980 if ((ullStartPos + ullNewChunkSize) % 2 != 0) {
981 const char cPadByte = 0;
982 #if POSIX
983 lseek(pFile->hFileWrite, ullStartPos + ullNewChunkSize, SEEK_SET);
984 write(pFile->hFileWrite, &cPadByte, 1);
985 #elif defined(WIN32)
986 LARGE_INTEGER liFilePos;
987 liFilePos.QuadPart = ullStartPos + ullNewChunkSize;
988 SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
989 DWORD dwBytesWritten;
990 WriteFile(pFile->hFileWrite, &cPadByte, 1, &dwBytesWritten, NULL);
991 #else
992 fseeko(pFile->hFileWrite, ullStartPos + ullNewChunkSize, SEEK_SET);
993 fwrite(&cPadByte, 1, 1, pFile->hFileWrite);
994 #endif
995 return ullStartPos + ullNewChunkSize + 1;
996 }
997
998 return ullStartPos + ullNewChunkSize;
999 }
1000
1001 void Chunk::__resetPos() {
1002 ullPos = 0;
1003 }
1004
1005
1006
1007 // *************** List ***************
1008 // *
1009
1010 List::List(File* pFile) : Chunk(pFile) {
1011 #if DEBUG
1012 std::cout << "List::List(File* pFile)" << std::endl;
1013 #endif // DEBUG
1014 pSubChunks = NULL;
1015 pSubChunksMap = NULL;
1016 }
1017
1018 List::List(File* pFile, file_offset_t StartPos, List* Parent)
1019 : Chunk(pFile, StartPos, Parent) {
1020 #if DEBUG
1021 std::cout << "List::List(File*,file_offset_t,List*)" << std::endl;
1022 #endif // DEBUG
1023 pSubChunks = NULL;
1024 pSubChunksMap = NULL;
1025 ReadHeader(StartPos);
1026 ullStartPos = StartPos + LIST_HEADER_SIZE(pFile->FileOffsetSize);
1027 }
1028
1029 List::List(File* pFile, List* pParent, uint32_t uiListID)
1030 : Chunk(pFile, pParent, CHUNK_ID_LIST, 0) {
1031 pSubChunks = NULL;
1032 pSubChunksMap = NULL;
1033 ListType = uiListID;
1034 }
1035
1036 List::~List() {
1037 #if DEBUG
1038 std::cout << "List::~List()" << std::endl;
1039 #endif // DEBUG
1040 DeleteChunkList();
1041 }
1042
1043 void List::DeleteChunkList() {
1044 if (pSubChunks) {
1045 ChunkList::iterator iter = pSubChunks->begin();
1046 ChunkList::iterator end = pSubChunks->end();
1047 while (iter != end) {
1048 delete *iter;
1049 iter++;
1050 }
1051 delete pSubChunks;
1052 pSubChunks = NULL;
1053 }
1054 if (pSubChunksMap) {
1055 delete pSubChunksMap;
1056 pSubChunksMap = NULL;
1057 }
1058 }
1059
1060 /**
1061 * Returns subchunk with chunk ID <i>\a ChunkID</i> within this chunk
1062 * list. Use this method if you expect only one subchunk of that type in
1063 * the list. It there are more than one, it's undetermined which one of
1064 * them will be returned! If there are no subchunks with that desired
1065 * chunk ID, NULL will be returned.
1066 *
1067 * @param ChunkID - chunk ID of the sought subchunk
1068 * @returns pointer to the subchunk or NULL if there is none of
1069 * that ID
1070 */
1071 Chunk* List::GetSubChunk(uint32_t ChunkID) {
1072 #if DEBUG
1073 std::cout << "List::GetSubChunk(uint32_t)" << std::endl;
1074 #endif // DEBUG
1075 if (!pSubChunksMap) LoadSubChunks();
1076 return (*pSubChunksMap)[ChunkID];
1077 }
1078
1079 /**
1080 * Returns sublist chunk with list type <i>\a ListType</i> within this
1081 * chunk list. Use this method if you expect only one sublist chunk of
1082 * that type in the list. If there are more than one, it's undetermined
1083 * which one of them will be returned! If there are no sublists with
1084 * that desired list type, NULL will be returned.
1085 *
1086 * @param ListType - list type of the sought sublist
1087 * @returns pointer to the sublist or NULL if there is none of
1088 * that type
1089 */
1090 List* List::GetSubList(uint32_t ListType) {
1091 #if DEBUG
1092 std::cout << "List::GetSubList(uint32_t)" << std::endl;
1093 #endif // DEBUG
1094 if (!pSubChunks) LoadSubChunks();
1095 ChunkList::iterator iter = pSubChunks->begin();
1096 ChunkList::iterator end = pSubChunks->end();
1097 while (iter != end) {
1098 if ((*iter)->GetChunkID() == CHUNK_ID_LIST) {
1099 List* l = (List*) *iter;
1100 if (l->GetListType() == ListType) return l;
1101 }
1102 iter++;
1103 }
1104 return NULL;
1105 }
1106
1107 /**
1108 * Returns the first subchunk within the list (which may be an ordinary
1109 * chunk as well as a list chunk). You have to call this
1110 * method before you can call GetNextSubChunk(). Recall it when you want
1111 * to start from the beginning of the list again.
1112 *
1113 * @returns pointer to the first subchunk within the list, NULL
1114 * otherwise
1115 */
1116 Chunk* List::GetFirstSubChunk() {
1117 #if DEBUG
1118 std::cout << "List::GetFirstSubChunk()" << std::endl;
1119 #endif // DEBUG
1120 if (!pSubChunks) LoadSubChunks();
1121 ChunksIterator = pSubChunks->begin();
1122 return (ChunksIterator != pSubChunks->end()) ? *ChunksIterator : NULL;
1123 }
1124
1125 /**
1126 * Returns the next subchunk within the list (which may be an ordinary
1127 * chunk as well as a list chunk). You have to call
1128 * GetFirstSubChunk() before you can use this method!
1129 *
1130 * @returns pointer to the next subchunk within the list or NULL if
1131 * end of list is reached
1132 */
1133 Chunk* List::GetNextSubChunk() {
1134 #if DEBUG
1135 std::cout << "List::GetNextSubChunk()" << std::endl;
1136 #endif // DEBUG
1137 if (!pSubChunks) return NULL;
1138 ChunksIterator++;
1139 return (ChunksIterator != pSubChunks->end()) ? *ChunksIterator : NULL;
1140 }
1141
1142 /**
1143 * Returns the first sublist within the list (that is a subchunk with
1144 * chunk ID "LIST"). You have to call this method before you can call
1145 * GetNextSubList(). Recall it when you want to start from the beginning
1146 * of the list again.
1147 *
1148 * @returns pointer to the first sublist within the list, NULL
1149 * otherwise
1150 */
1151 List* List::GetFirstSubList() {
1152 #if DEBUG
1153 std::cout << "List::GetFirstSubList()" << std::endl;
1154 #endif // DEBUG
1155 if (!pSubChunks) LoadSubChunks();
1156 ListIterator = pSubChunks->begin();
1157 ChunkList::iterator end = pSubChunks->end();
1158 while (ListIterator != end) {
1159 if ((*ListIterator)->GetChunkID() == CHUNK_ID_LIST) return (List*) *ListIterator;
1160 ListIterator++;
1161 }
1162 return NULL;
1163 }
1164
1165 /**
1166 * Returns the next sublist (that is a subchunk with chunk ID "LIST")
1167 * within the list. You have to call GetFirstSubList() before you can
1168 * use this method!
1169 *
1170 * @returns pointer to the next sublist within the list, NULL if
1171 * end of list is reached
1172 */
1173 List* List::GetNextSubList() {
1174 #if DEBUG
1175 std::cout << "List::GetNextSubList()" << std::endl;
1176 #endif // DEBUG
1177 if (!pSubChunks) return NULL;
1178 if (ListIterator == pSubChunks->end()) return NULL;
1179 ListIterator++;
1180 ChunkList::iterator end = pSubChunks->end();
1181 while (ListIterator != end) {
1182 if ((*ListIterator)->GetChunkID() == CHUNK_ID_LIST) return (List*) *ListIterator;
1183 ListIterator++;
1184 }
1185 return NULL;
1186 }
1187
1188 /**
1189 * Returns number of subchunks within the list (including list chunks).
1190 */
1191 size_t List::CountSubChunks() {
1192 if (!pSubChunks) LoadSubChunks();
1193 return pSubChunks->size();
1194 }
1195
1196 /**
1197 * Returns number of subchunks within the list with chunk ID
1198 * <i>\a ChunkId</i>.
1199 */
1200 size_t List::CountSubChunks(uint32_t ChunkID) {
1201 size_t result = 0;
1202 if (!pSubChunks) LoadSubChunks();
1203 ChunkList::iterator iter = pSubChunks->begin();
1204 ChunkList::iterator end = pSubChunks->end();
1205 while (iter != end) {
1206 if ((*iter)->GetChunkID() == ChunkID) {
1207 result++;
1208 }
1209 iter++;
1210 }
1211 return result;
1212 }
1213
1214 /**
1215 * Returns number of sublists within the list.
1216 */
1217 size_t List::CountSubLists() {
1218 return CountSubChunks(CHUNK_ID_LIST);
1219 }
1220
1221 /**
1222 * Returns number of sublists within the list with list type
1223 * <i>\a ListType</i>
1224 */
1225 size_t List::CountSubLists(uint32_t ListType) {
1226 size_t result = 0;
1227 if (!pSubChunks) LoadSubChunks();
1228 ChunkList::iterator iter = pSubChunks->begin();
1229 ChunkList::iterator end = pSubChunks->end();
1230 while (iter != end) {
1231 if ((*iter)->GetChunkID() == CHUNK_ID_LIST) {
1232 List* l = (List*) *iter;
1233 if (l->GetListType() == ListType) result++;
1234 }
1235 iter++;
1236 }
1237 return result;
1238 }
1239
1240 /** @brief Creates a new sub chunk.
1241 *
1242 * Creates and adds a new sub chunk to this list chunk. Note that the
1243 * chunk's body size given by \a ullBodySize must be greater than zero.
1244 * You have to call File::Save() to make this change persistent to the
1245 * actual file and <b>before</b> performing any data write operations
1246 * on the new chunk!
1247 *
1248 * @param uiChunkID - chunk ID of the new chunk
1249 * @param ullBodySize - size of the new chunk's body, that is its actual
1250 * data size (without header)
1251 * @throws RIFF::Exception if \a ullBodySize equals zero
1252 */
1253 Chunk* List::AddSubChunk(uint32_t uiChunkID, file_offset_t ullBodySize) {
1254 if (ullBodySize == 0) throw Exception("Chunk body size must be at least 1 byte");
1255 if (!pSubChunks) LoadSubChunks();
1256 Chunk* pNewChunk = new Chunk(pFile, this, uiChunkID, 0);
1257 pSubChunks->push_back(pNewChunk);
1258 (*pSubChunksMap)[uiChunkID] = pNewChunk;
1259 pNewChunk->Resize(ullBodySize);
1260 ullNewChunkSize += CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
1261 return pNewChunk;
1262 }
1263
1264 /** @brief Moves a sub chunk witin this list.
1265 *
1266 * Moves a sub chunk from one position in this list to another
1267 * position in the same list. The pSrc chunk is placed before the
1268 * pDst chunk.
1269 *
1270 * @param pSrc - sub chunk to be moved
1271 * @param pDst - the position to move to. pSrc will be placed
1272 * before pDst. If pDst is 0, pSrc will be placed
1273 * last in list.
1274 */
1275 void List::MoveSubChunk(Chunk* pSrc, Chunk* pDst) {
1276 if (!pSubChunks) LoadSubChunks();
1277 pSubChunks->remove(pSrc);
1278 ChunkList::iterator iter = find(pSubChunks->begin(), pSubChunks->end(), pDst);
1279 pSubChunks->insert(iter, pSrc);
1280 }
1281
1282 /** @brief Moves a sub chunk from this list to another list.
1283 *
1284 * Moves a sub chunk from this list list to the end of another
1285 * list.
1286 *
1287 * @param pSrc - sub chunk to be moved
1288 * @param pDst - destination list where the chunk shall be moved to
1289 */
1290 void List::MoveSubChunk(Chunk* pSrc, List* pNewParent) {
1291 if (pNewParent == this || !pNewParent) return;
1292 if (!pSubChunks) LoadSubChunks();
1293 if (!pNewParent->pSubChunks) pNewParent->LoadSubChunks();
1294 pSubChunks->remove(pSrc);
1295 pNewParent->pSubChunks->push_back(pSrc);
1296 // update chunk id map of this List
1297 if ((*pSubChunksMap)[pSrc->GetChunkID()] == pSrc) {
1298 pSubChunksMap->erase(pSrc->GetChunkID());
1299 // try to find another chunk of the same chunk ID
1300 ChunkList::iterator iter = pSubChunks->begin();
1301 ChunkList::iterator end = pSubChunks->end();
1302 for (; iter != end; ++iter) {
1303 if ((*iter)->GetChunkID() == pSrc->GetChunkID()) {
1304 (*pSubChunksMap)[pSrc->GetChunkID()] = *iter;
1305 break; // we're done, stop search
1306 }
1307 }
1308 }
1309 // update chunk id map of other list
1310 if (!(*pNewParent->pSubChunksMap)[pSrc->GetChunkID()])
1311 (*pNewParent->pSubChunksMap)[pSrc->GetChunkID()] = pSrc;
1312 }
1313
1314 /** @brief Creates a new list sub chunk.
1315 *
1316 * Creates and adds a new list sub chunk to this list chunk. Note that
1317 * you have to add sub chunks / sub list chunks to the new created chunk
1318 * <b>before</b> trying to make this change persisten to the actual
1319 * file with File::Save()!
1320 *
1321 * @param uiListType - list ID of the new list chunk
1322 */
1323 List* List::AddSubList(uint32_t uiListType) {
1324 if (!pSubChunks) LoadSubChunks();
1325 List* pNewListChunk = new List(pFile, this, uiListType);
1326 pSubChunks->push_back(pNewListChunk);
1327 (*pSubChunksMap)[CHUNK_ID_LIST] = pNewListChunk;
1328 ullNewChunkSize += LIST_HEADER_SIZE(pFile->FileOffsetSize);
1329 return pNewListChunk;
1330 }
1331
1332 /** @brief Removes a sub chunk.
1333 *
1334 * Removes the sub chunk given by \a pSubChunk from this list and frees
1335 * it completely from RAM. The given chunk can either be a normal sub
1336 * chunk or a list sub chunk. In case the given chunk is a list chunk,
1337 * all its subchunks (if any) will be removed recursively as well. You
1338 * should call File::Save() to make this change persistent at any time.
1339 *
1340 * @param pSubChunk - sub chunk or sub list chunk to be removed
1341 */
1342 void List::DeleteSubChunk(Chunk* pSubChunk) {
1343 if (!pSubChunks) LoadSubChunks();
1344 pSubChunks->remove(pSubChunk);
1345 if ((*pSubChunksMap)[pSubChunk->GetChunkID()] == pSubChunk) {
1346 pSubChunksMap->erase(pSubChunk->GetChunkID());
1347 // try to find another chunk of the same chunk ID
1348 ChunkList::iterator iter = pSubChunks->begin();
1349 ChunkList::iterator end = pSubChunks->end();
1350 for (; iter != end; ++iter) {
1351 if ((*iter)->GetChunkID() == pSubChunk->GetChunkID()) {
1352 (*pSubChunksMap)[pSubChunk->GetChunkID()] = *iter;
1353 break; // we're done, stop search
1354 }
1355 }
1356 }
1357 delete pSubChunk;
1358 }
1359
1360 /**
1361 * Returns the actual total size in bytes (including List chunk header and
1362 * all subchunks) of this List Chunk if being stored to a file.
1363 *
1364 * @param fileOffsetSize - RIFF file offset size (in bytes) assumed when
1365 * being saved to a file
1366 */
1367 file_offset_t List::RequiredPhysicalSize(int fileOffsetSize) {
1368 if (!pSubChunks) LoadSubChunks();
1369 file_offset_t size = LIST_HEADER_SIZE(fileOffsetSize);
1370 ChunkList::iterator iter = pSubChunks->begin();
1371 ChunkList::iterator end = pSubChunks->end();
1372 for (; iter != end; ++iter)
1373 size += (*iter)->RequiredPhysicalSize(fileOffsetSize);
1374 return size;
1375 }
1376
1377 void List::ReadHeader(file_offset_t filePos) {
1378 #if DEBUG
1379 std::cout << "List::Readheader(file_offset_t) ";
1380 #endif // DEBUG
1381 Chunk::ReadHeader(filePos);
1382 if (ullCurrentChunkSize < 4) return;
1383 ullNewChunkSize = ullCurrentChunkSize -= 4;
1384 #if POSIX
1385 lseek(pFile->hFileRead, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET);
1386 read(pFile->hFileRead, &ListType, 4);
1387 #elif defined(WIN32)
1388 LARGE_INTEGER liFilePos;
1389 liFilePos.QuadPart = filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
1390 SetFilePointerEx(pFile->hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1391 DWORD dwBytesRead;
1392 ReadFile(pFile->hFileRead, &ListType, 4, &dwBytesRead, NULL);
1393 #else
1394 fseeko(pFile->hFileRead, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET);
1395 fread(&ListType, 4, 1, pFile->hFileRead);
1396 #endif // POSIX
1397 #if DEBUG
1398 std::cout << "listType=" << convertToString(ListType) << std::endl;
1399 #endif // DEBUG
1400 if (!pFile->bEndianNative) {
1401 //swapBytes_32(&ListType);
1402 }
1403 }
1404
1405 void List::WriteHeader(file_offset_t filePos) {
1406 // the four list type bytes officially belong the chunk's body in the RIFF format
1407 ullNewChunkSize += 4;
1408 Chunk::WriteHeader(filePos);
1409 ullNewChunkSize -= 4; // just revert the +4 incrementation
1410 #if POSIX
1411 lseek(pFile->hFileWrite, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET);
1412 write(pFile->hFileWrite, &ListType, 4);
1413 #elif defined(WIN32)
1414 LARGE_INTEGER liFilePos;
1415 liFilePos.QuadPart = filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize);
1416 SetFilePointerEx(pFile->hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1417 DWORD dwBytesWritten;
1418 WriteFile(pFile->hFileWrite, &ListType, 4, &dwBytesWritten, NULL);
1419 #else
1420 fseeko(pFile->hFileWrite, filePos + CHUNK_HEADER_SIZE(pFile->FileOffsetSize), SEEK_SET);
1421 fwrite(&ListType, 4, 1, pFile->hFileWrite);
1422 #endif // POSIX
1423 }
1424
1425 void List::LoadSubChunks(progress_t* pProgress) {
1426 #if DEBUG
1427 std::cout << "List::LoadSubChunks()";
1428 #endif // DEBUG
1429 if (!pSubChunks) {
1430 pSubChunks = new ChunkList();
1431 pSubChunksMap = new ChunkMap();
1432 #if defined(WIN32)
1433 if (pFile->hFileRead == INVALID_HANDLE_VALUE) return;
1434 #else
1435 if (!pFile->hFileRead) return;
1436 #endif
1437 file_offset_t ullOriginalPos = GetPos();
1438 SetPos(0); // jump to beginning of list chunk body
1439 while (RemainingBytes() >= CHUNK_HEADER_SIZE(pFile->FileOffsetSize)) {
1440 Chunk* ck;
1441 uint32_t ckid;
1442 Read(&ckid, 4, 1);
1443 #if DEBUG
1444 std::cout << " ckid=" << convertToString(ckid) << std::endl;
1445 #endif // DEBUG
1446 if (ckid == CHUNK_ID_LIST) {
1447 ck = new RIFF::List(pFile, ullStartPos + ullPos - 4, this);
1448 SetPos(ck->GetSize() + LIST_HEADER_SIZE(pFile->FileOffsetSize) - 4, RIFF::stream_curpos);
1449 }
1450 else { // simple chunk
1451 ck = new RIFF::Chunk(pFile, ullStartPos + ullPos - 4, this);
1452 SetPos(ck->GetSize() + CHUNK_HEADER_SIZE(pFile->FileOffsetSize) - 4, RIFF::stream_curpos);
1453 }
1454 pSubChunks->push_back(ck);
1455 (*pSubChunksMap)[ckid] = ck;
1456 if (GetPos() % 2 != 0) SetPos(1, RIFF::stream_curpos); // jump over pad byte
1457 }
1458 SetPos(ullOriginalPos); // restore position before this call
1459 }
1460 __notify_progress(pProgress, 1.0); // notify done
1461 }
1462
1463 void List::LoadSubChunksRecursively(progress_t* pProgress) {
1464 const int n = CountSubLists();
1465 int i = 0;
1466 for (List* pList = GetFirstSubList(); pList; pList = GetNextSubList(), ++i) {
1467 // divide local progress into subprogress
1468 progress_t subprogress;
1469 __divide_progress(pProgress, &subprogress, n, i);
1470 // do the actual work
1471 pList->LoadSubChunksRecursively(&subprogress);
1472 }
1473 __notify_progress(pProgress, 1.0); // notify done
1474 }
1475
1476 /** @brief Write list chunk persistently e.g. to disk.
1477 *
1478 * Stores the list chunk persistently to its actual "physical" file. All
1479 * subchunks (including sub list chunks) will be stored recursively as
1480 * well.
1481 *
1482 * @param ullWritePos - position within the "physical" file where this
1483 * list chunk should be written to
1484 * @param ullCurrentDataOffset - offset of current (old) data within
1485 * the file
1486 * @param pProgress - optional: callback function for progress notification
1487 * @returns new write position in the "physical" file, that is
1488 * \a ullWritePos incremented by this list chunk's new size
1489 * (including its header size of course)
1490 */
1491 file_offset_t List::WriteChunk(file_offset_t ullWritePos, file_offset_t ullCurrentDataOffset, progress_t* pProgress) {
1492 const file_offset_t ullOriginalPos = ullWritePos;
1493 ullWritePos += LIST_HEADER_SIZE(pFile->FileOffsetSize);
1494
1495 if (pFile->Mode != stream_mode_read_write)
1496 throw Exception("Cannot write list chunk, file has to be opened in read+write mode");
1497
1498 // write all subchunks (including sub list chunks) recursively
1499 if (pSubChunks) {
1500 size_t i = 0;
1501 const size_t n = pSubChunks->size();
1502 for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter, ++i) {
1503 // divide local progress into subprogress for loading current Instrument
1504 progress_t subprogress;
1505 __divide_progress(pProgress, &subprogress, n, i);
1506 // do the actual work
1507 ullWritePos = (*iter)->WriteChunk(ullWritePos, ullCurrentDataOffset, &subprogress);
1508 }
1509 }
1510
1511 // update this list chunk's header
1512 ullCurrentChunkSize = ullNewChunkSize = ullWritePos - ullOriginalPos - LIST_HEADER_SIZE(pFile->FileOffsetSize);
1513 WriteHeader(ullOriginalPos);
1514
1515 // offset of this list chunk in new written file may have changed
1516 ullStartPos = ullOriginalPos + LIST_HEADER_SIZE(pFile->FileOffsetSize);
1517
1518 __notify_progress(pProgress, 1.0); // notify done
1519
1520 return ullWritePos;
1521 }
1522
1523 void List::__resetPos() {
1524 Chunk::__resetPos();
1525 if (pSubChunks) {
1526 for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter) {
1527 (*iter)->__resetPos();
1528 }
1529 }
1530 }
1531
1532 /**
1533 * Returns string representation of the lists's id
1534 */
1535 String List::GetListTypeString() const {
1536 return convertToString(ListType);
1537 }
1538
1539
1540
1541 // *************** File ***************
1542 // *
1543
1544 /** @brief Create new RIFF file.
1545 *
1546 * Use this constructor if you want to create a new RIFF file completely
1547 * "from scratch". Note: there must be no empty chunks or empty list
1548 * chunks when trying to make the new RIFF file persistent with Save()!
1549 *
1550 * Note: by default, the RIFF file will be saved in native endian
1551 * format; that is, as a RIFF file on little-endian machines and
1552 * as a RIFX file on big-endian. To change this behaviour, call
1553 * SetByteOrder() before calling Save().
1554 *
1555 * @param FileType - four-byte identifier of the RIFF file type
1556 * @see AddSubChunk(), AddSubList(), SetByteOrder()
1557 */
1558 File::File(uint32_t FileType)
1559 : List(this), bIsNewFile(true), Layout(layout_standard),
1560 FileOffsetPreference(offset_size_auto)
1561 {
1562 #if defined(WIN32)
1563 hFileRead = hFileWrite = INVALID_HANDLE_VALUE;
1564 #else
1565 hFileRead = hFileWrite = 0;
1566 #endif
1567 Mode = stream_mode_closed;
1568 bEndianNative = true;
1569 ListType = FileType;
1570 FileOffsetSize = 4;
1571 ullStartPos = RIFF_HEADER_SIZE(FileOffsetSize);
1572 }
1573
1574 /** @brief Load existing RIFF file.
1575 *
1576 * Loads an existing RIFF file with all its chunks.
1577 *
1578 * @param path - path and file name of the RIFF file to open
1579 * @throws RIFF::Exception if error occured while trying to load the
1580 * given RIFF file
1581 */
1582 File::File(const String& path)
1583 : List(this), Filename(path), bIsNewFile(false), Layout(layout_standard),
1584 FileOffsetPreference(offset_size_auto)
1585 {
1586 #if DEBUG
1587 std::cout << "File::File("<<path<<")" << std::endl;
1588 #endif // DEBUG
1589 bEndianNative = true;
1590 FileOffsetSize = 4;
1591 try {
1592 __openExistingFile(path);
1593 if (ChunkID != CHUNK_ID_RIFF && ChunkID != CHUNK_ID_RIFX) {
1594 throw RIFF::Exception("Not a RIFF file");
1595 }
1596 }
1597 catch (...) {
1598 Cleanup();
1599 throw;
1600 }
1601 }
1602
1603 /** @brief Load existing RIFF-like file.
1604 *
1605 * Loads an existing file, which is not a "real" RIFF file, but similar to
1606 * an ordinary RIFF file.
1607 *
1608 * A "real" RIFF file contains at top level a List chunk either with chunk
1609 * ID "RIFF" or "RIFX". The simple constructor above expects this to be
1610 * case, and if it finds the toplevel List chunk to have another chunk ID
1611 * than one of those two expected ones, it would throw an Exception and
1612 * would refuse to load the file accordingly.
1613 *
1614 * Since there are however a lot of file formats which use the same simple
1615 * principles of the RIFF format, with another toplevel List chunk ID
1616 * though, you can use this alternative constructor here to be able to load
1617 * and handle those files in the same way as you would do with "real" RIFF
1618 * files.
1619 *
1620 * @param path - path and file name of the RIFF-alike file to be opened
1621 * @param FileType - expected toplevel List chunk ID (this is the very
1622 * first chunk found in the file)
1623 * @param Endian - whether the file uses little endian or big endian layout
1624 * @param layout - general file structure type
1625 * @param fileOffsetSize - (optional) preference how to deal with large files
1626 * @throws RIFF::Exception if error occured while trying to load the
1627 * given RIFF-alike file
1628 */
1629 File::File(const String& path, uint32_t FileType, endian_t Endian, layout_t layout, offset_size_t fileOffsetSize)
1630 : List(this), Filename(path), bIsNewFile(false), Layout(layout),
1631 FileOffsetPreference(fileOffsetSize)
1632 {
1633 SetByteOrder(Endian);
1634 if (fileOffsetSize < offset_size_auto || fileOffsetSize > offset_size_64bit)
1635 throw Exception("Invalid RIFF::offset_size_t");
1636 FileOffsetSize = 4;
1637 try {
1638 __openExistingFile(path, &FileType);
1639 }
1640 catch (...) {
1641 Cleanup();
1642 throw;
1643 }
1644 }
1645
1646 /**
1647 * Opens an already existing RIFF file or RIFF-alike file. This method
1648 * shall only be called once (in a File class constructor).
1649 *
1650 * @param path - path and file name of the RIFF file or RIFF-alike file to
1651 * be opened
1652 * @param FileType - (optional) expected chunk ID of first chunk in file
1653 * @throws RIFF::Exception if error occured while trying to load the
1654 * given RIFF file or RIFF-alike file
1655 */
1656 void File::__openExistingFile(const String& path, uint32_t* FileType) {
1657 #if POSIX
1658 hFileRead = hFileWrite = open(path.c_str(), O_RDONLY | O_NONBLOCK);
1659 if (hFileRead == -1) {
1660 hFileRead = hFileWrite = 0;
1661 String sError = strerror(errno);
1662 throw RIFF::Exception("Can't open \"" + path + "\": " + sError);
1663 }
1664 #elif defined(WIN32)
1665 hFileRead = hFileWrite = CreateFile(
1666 path.c_str(), GENERIC_READ,
1667 FILE_SHARE_READ | FILE_SHARE_WRITE,
1668 NULL, OPEN_EXISTING,
1669 FILE_ATTRIBUTE_NORMAL |
1670 FILE_FLAG_RANDOM_ACCESS, NULL
1671 );
1672 if (hFileRead == INVALID_HANDLE_VALUE) {
1673 hFileRead = hFileWrite = INVALID_HANDLE_VALUE;
1674 throw RIFF::Exception("Can't open \"" + path + "\"");
1675 }
1676 #else
1677 hFileRead = hFileWrite = fopen(path.c_str(), "rb");
1678 if (!hFileRead) throw RIFF::Exception("Can't open \"" + path + "\"");
1679 #endif // POSIX
1680 Mode = stream_mode_read;
1681
1682 // determine RIFF file offset size to be used (in RIFF chunk headers)
1683 // according to the current file offset preference
1684 FileOffsetSize = FileOffsetSizeFor(GetCurrentFileSize());
1685
1686 switch (Layout) {
1687 case layout_standard: // this is a normal RIFF file
1688 ullStartPos = RIFF_HEADER_SIZE(FileOffsetSize);
1689 ReadHeader(0);
1690 if (FileType && ChunkID != *FileType)
1691 throw RIFF::Exception("Invalid file container ID");
1692 break;
1693 case layout_flat: // non-standard RIFF-alike file
1694 ullStartPos = 0;
1695 ullNewChunkSize = ullCurrentChunkSize = GetCurrentFileSize();
1696 if (FileType) {
1697 uint32_t ckid;
1698 if (Read(&ckid, 4, 1) != 4) {
1699 throw RIFF::Exception("Invalid file header ID (premature end of header)");
1700 } else if (ckid != *FileType) {
1701 String s = " (expected '" + convertToString(*FileType) + "' but got '" + convertToString(ckid) + "')";
1702 throw RIFF::Exception("Invalid file header ID" + s);
1703 }
1704 SetPos(0); // reset to first byte of file
1705 }
1706 LoadSubChunks();
1707 break;
1708 }
1709 }
1710
1711 String File::GetFileName() const {
1712 return Filename;
1713 }
1714
1715 void File::SetFileName(const String& path) {
1716 Filename = path;
1717 }
1718
1719 stream_mode_t File::GetMode() const {
1720 return Mode;
1721 }
1722
1723 layout_t File::GetLayout() const {
1724 return Layout;
1725 }
1726
1727 /** @brief Change file access mode.
1728 *
1729 * Changes files access mode either to read-only mode or to read/write
1730 * mode.
1731 *
1732 * @param NewMode - new file access mode
1733 * @returns true if mode was changed, false if current mode already
1734 * equals new mode
1735 * @throws RIFF::Exception if new file access mode is unknown
1736 */
1737 bool File::SetMode(stream_mode_t NewMode) {
1738 if (NewMode != Mode) {
1739 switch (NewMode) {
1740 case stream_mode_read:
1741 #if POSIX
1742 if (hFileRead) close(hFileRead);
1743 hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK);
1744 if (hFileRead == -1) {
1745 hFileRead = hFileWrite = 0;
1746 String sError = strerror(errno);
1747 throw Exception("Could not (re)open file \"" + Filename + "\" in read mode: " + sError);
1748 }
1749 #elif defined(WIN32)
1750 if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead);
1751 hFileRead = hFileWrite = CreateFile(
1752 Filename.c_str(), GENERIC_READ,
1753 FILE_SHARE_READ | FILE_SHARE_WRITE,
1754 NULL, OPEN_EXISTING,
1755 FILE_ATTRIBUTE_NORMAL |
1756 FILE_FLAG_RANDOM_ACCESS,
1757 NULL
1758 );
1759 if (hFileRead == INVALID_HANDLE_VALUE) {
1760 hFileRead = hFileWrite = INVALID_HANDLE_VALUE;
1761 throw Exception("Could not (re)open file \"" + Filename + "\" in read mode");
1762 }
1763 #else
1764 if (hFileRead) fclose(hFileRead);
1765 hFileRead = hFileWrite = fopen(Filename.c_str(), "rb");
1766 if (!hFileRead) throw Exception("Could not (re)open file \"" + Filename + "\" in read mode");
1767 #endif
1768 __resetPos(); // reset read/write position of ALL 'Chunk' objects
1769 break;
1770 case stream_mode_read_write:
1771 #if POSIX
1772 if (hFileRead) close(hFileRead);
1773 hFileRead = hFileWrite = open(Filename.c_str(), O_RDWR | O_NONBLOCK);
1774 if (hFileRead == -1) {
1775 hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK);
1776 String sError = strerror(errno);
1777 throw Exception("Could not open file \"" + Filename + "\" in read+write mode: " + sError);
1778 }
1779 #elif defined(WIN32)
1780 if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead);
1781 hFileRead = hFileWrite = CreateFile(
1782 Filename.c_str(),
1783 GENERIC_READ | GENERIC_WRITE,
1784 FILE_SHARE_READ,
1785 NULL, OPEN_ALWAYS,
1786 FILE_ATTRIBUTE_NORMAL |
1787 FILE_FLAG_RANDOM_ACCESS,
1788 NULL
1789 );
1790 if (hFileRead == INVALID_HANDLE_VALUE) {
1791 hFileRead = hFileWrite = CreateFile(
1792 Filename.c_str(), GENERIC_READ,
1793 FILE_SHARE_READ | FILE_SHARE_WRITE,
1794 NULL, OPEN_EXISTING,
1795 FILE_ATTRIBUTE_NORMAL |
1796 FILE_FLAG_RANDOM_ACCESS,
1797 NULL
1798 );
1799 throw Exception("Could not (re)open file \"" + Filename + "\" in read+write mode");
1800 }
1801 #else
1802 if (hFileRead) fclose(hFileRead);
1803 hFileRead = hFileWrite = fopen(Filename.c_str(), "r+b");
1804 if (!hFileRead) {
1805 hFileRead = hFileWrite = fopen(Filename.c_str(), "rb");
1806 throw Exception("Could not open file \"" + Filename + "\" in read+write mode");
1807 }
1808 #endif
1809 __resetPos(); // reset read/write position of ALL 'Chunk' objects
1810 break;
1811 case stream_mode_closed:
1812 #if POSIX
1813 if (hFileRead) close(hFileRead);
1814 if (hFileWrite) close(hFileWrite);
1815 #elif defined(WIN32)
1816 if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead);
1817 if (hFileWrite != INVALID_HANDLE_VALUE) CloseHandle(hFileWrite);
1818 #else
1819 if (hFileRead) fclose(hFileRead);
1820 if (hFileWrite) fclose(hFileWrite);
1821 #endif
1822 hFileRead = hFileWrite = 0;
1823 break;
1824 default:
1825 throw Exception("Unknown file access mode");
1826 }
1827 Mode = NewMode;
1828 return true;
1829 }
1830 return false;
1831 }
1832
1833 /** @brief Set the byte order to be used when saving.
1834 *
1835 * Set the byte order to be used in the file. A value of
1836 * endian_little will create a RIFF file, endian_big a RIFX file
1837 * and endian_native will create a RIFF file on little-endian
1838 * machines and RIFX on big-endian machines.
1839 *
1840 * @param Endian - endianess to use when file is saved.
1841 */
1842 void File::SetByteOrder(endian_t Endian) {
1843 #if WORDS_BIGENDIAN
1844 bEndianNative = Endian != endian_little;
1845 #else
1846 bEndianNative = Endian != endian_big;
1847 #endif
1848 }
1849
1850 /** @brief Save changes to same file.
1851 *
1852 * Make all changes of all chunks persistent by writing them to the
1853 * actual (same) file.
1854 *
1855 * @param pProgress - optional: callback function for progress notification
1856 * @throws RIFF::Exception if there is an empty chunk or empty list
1857 * chunk or any kind of IO error occured
1858 */
1859 void File::Save(progress_t* pProgress) {
1860 //TODO: implementation for the case where first chunk is not a global container (List chunk) is not implemented yet (i.e. Korg files)
1861 if (Layout == layout_flat)
1862 throw Exception("Saving a RIFF file with layout_flat is not implemented yet");
1863
1864 // make sure the RIFF tree is built (from the original file)
1865 {
1866 // divide progress into subprogress
1867 progress_t subprogress;
1868 __divide_progress(pProgress, &subprogress, 3.f, 0.f); // arbitrarily subdivided into 1/3 of total progress
1869 // do the actual work
1870 LoadSubChunksRecursively(&subprogress);
1871 // notify subprogress done
1872 __notify_progress(&subprogress, 1.f);
1873 }
1874
1875 // reopen file in write mode
1876 SetMode(stream_mode_read_write);
1877
1878 // get the current file size as it is now still physically stored on disk
1879 const file_offset_t workingFileSize = GetCurrentFileSize();
1880
1881 // get the overall file size required to save this file
1882 const file_offset_t newFileSize = GetRequiredFileSize(FileOffsetPreference);
1883
1884 // determine whether this file will yield in a large file (>=4GB) and
1885 // the RIFF file offset size to be used accordingly for all chunks
1886 FileOffsetSize = FileOffsetSizeFor(newFileSize);
1887
1888 // to be able to save the whole file without loading everything into
1889 // RAM and without having to store the data in a temporary file, we
1890 // enlarge the file with the overall positive file size change,
1891 // then move current data towards the end of the file by the calculated
1892 // positive file size difference and finally update / rewrite the file
1893 // by copying the old data back to the right position at the beginning
1894 // of the file
1895
1896 // if there are positive size changes...
1897 file_offset_t positiveSizeDiff = 0;
1898 if (newFileSize > workingFileSize) {
1899 positiveSizeDiff = newFileSize - workingFileSize;
1900
1901 // divide progress into subprogress
1902 progress_t subprogress;
1903 __divide_progress(pProgress, &subprogress, 3.f, 1.f); // arbitrarily subdivided into 1/3 of total progress
1904
1905 // ... we enlarge this file first ...
1906 ResizeFile(newFileSize);
1907
1908 // ... and move current data by the same amount towards end of file.
1909 int8_t* pCopyBuffer = new int8_t[4096];
1910 #if defined(WIN32)
1911 DWORD iBytesMoved = 1; // we have to pass it via pointer to the Windows API, thus the correct size must be ensured
1912 #else
1913 ssize_t iBytesMoved = 1;
1914 #endif
1915 for (file_offset_t ullPos = workingFileSize, iNotif = 0; iBytesMoved > 0; ++iNotif) {
1916 iBytesMoved = (ullPos < 4096) ? ullPos : 4096;
1917 ullPos -= iBytesMoved;
1918 #if POSIX
1919 lseek(hFileRead, ullPos, SEEK_SET);
1920 iBytesMoved = read(hFileRead, pCopyBuffer, iBytesMoved);
1921 lseek(hFileWrite, ullPos + positiveSizeDiff, SEEK_SET);
1922 iBytesMoved = write(hFileWrite, pCopyBuffer, iBytesMoved);
1923 #elif defined(WIN32)
1924 LARGE_INTEGER liFilePos;
1925 liFilePos.QuadPart = ullPos;
1926 SetFilePointerEx(hFileRead, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1927 ReadFile(hFileRead, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL);
1928 liFilePos.QuadPart = ullPos + positiveSizeDiff;
1929 SetFilePointerEx(hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN);
1930 WriteFile(hFileWrite, pCopyBuffer, iBytesMoved, &iBytesMoved, NULL);
1931 #else
1932 fseeko(hFileRead, ullPos, SEEK_SET);
1933 iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, hFileRead);
1934 fseeko(hFileWrite, ullPos + positiveSizeDiff, SEEK_SET);
1935 iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, hFileWrite);
1936 #endif
1937 if (!(iNotif % 8) && iBytesMoved > 0)
1938 __notify_progress(&subprogress, float(workingFileSize - ullPos) / float(workingFileSize));
1939 }
1940 delete[] pCopyBuffer;
1941 if (iBytesMoved < 0) throw Exception("Could not modify file while trying to enlarge it");
1942
1943 __notify_progress(&subprogress, 1.f); // notify subprogress done
1944 }
1945
1946 // rebuild / rewrite complete RIFF tree ...
1947
1948 // divide progress into subprogress
1949 progress_t subprogress;
1950 __divide_progress(pProgress, &subprogress, 3.f, 2.f); // arbitrarily subdivided into 1/3 of total progress
1951 // do the actual work
1952 const file_offset_t finalSize = WriteChunk(0, positiveSizeDiff, &subprogress);
1953 const file_offset_t finalActualSize = __GetFileSize(hFileWrite);
1954 // notify subprogress done
1955 __notify_progress(&subprogress, 1.f);
1956
1957 // resize file to the final size
1958 if (finalSize < finalActualSize) ResizeFile(finalSize);
1959
1960 __notify_progress(pProgress, 1.0); // notify done
1961 }
1962
1963 /** @brief Save changes to another file.
1964 *
1965 * Make all changes of all chunks persistent by writing them to another
1966 * file. <b>Caution:</b> this method is optimized for writing to
1967 * <b>another</b> file, do not use it to save the changes to the same
1968 * file! Use File::Save() in that case instead! Ignoring this might
1969 * result in a corrupted file, especially in case chunks were resized!
1970 *
1971 * After calling this method, this File object will be associated with
1972 * the new file (given by \a path) afterwards.
1973 *
1974 * @param path - path and file name where everything should be written to
1975 * @param pProgress - optional: callback function for progress notification
1976 */
1977 void File::Save(const String& path, progress_t* pProgress) {
1978 //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
1979
1980 //TODO: implementation for the case where first chunk is not a global container (List chunk) is not implemented yet (i.e. Korg files)
1981 if (Layout == layout_flat)
1982 throw Exception("Saving a RIFF file with layout_flat is not implemented yet");
1983
1984 // make sure the RIFF tree is built (from the original file)
1985 {
1986 // divide progress into subprogress
1987 progress_t subprogress;
1988 __divide_progress(pProgress, &subprogress, 2.f, 0.f); // arbitrarily subdivided into 1/2 of total progress
1989 // do the actual work
1990 LoadSubChunksRecursively(&subprogress);
1991 // notify subprogress done
1992 __notify_progress(&subprogress, 1.f);
1993 }
1994
1995 if (!bIsNewFile) SetMode(stream_mode_read);
1996 // open the other (new) file for writing and truncate it to zero size
1997 #if POSIX
1998 hFileWrite = open(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP);
1999 if (hFileWrite == -1) {
2000 hFileWrite = hFileRead;
2001 String sError = strerror(errno);
2002 throw Exception("Could not open file \"" + path + "\" for writing: " + sError);
2003 }
2004 #elif defined(WIN32)
2005 hFileWrite = CreateFile(
2006 path.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
2007 NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |
2008 FILE_FLAG_RANDOM_ACCESS, NULL
2009 );
2010 if (hFileWrite == INVALID_HANDLE_VALUE) {
2011 hFileWrite = hFileRead;
2012 throw Exception("Could not open file \"" + path + "\" for writing");
2013 }
2014 #else
2015 hFileWrite = fopen(path.c_str(), "w+b");
2016 if (!hFileWrite) {
2017 hFileWrite = hFileRead;
2018 throw Exception("Could not open file \"" + path + "\" for writing");
2019 }
2020 #endif // POSIX
2021 Mode = stream_mode_read_write;
2022
2023 // get the overall file size required to save this file
2024 const file_offset_t newFileSize = GetRequiredFileSize(FileOffsetPreference);
2025
2026 // determine whether this file will yield in a large file (>=4GB) and
2027 // the RIFF file offset size to be used accordingly for all chunks
2028 FileOffsetSize = FileOffsetSizeFor(newFileSize);
2029
2030 // write complete RIFF tree to the other (new) file
2031 file_offset_t ullTotalSize;
2032 {
2033 // divide progress into subprogress
2034 progress_t subprogress;
2035 __divide_progress(pProgress, &subprogress, 2.f, 1.f); // arbitrarily subdivided into 1/2 of total progress
2036 // do the actual work
2037 ullTotalSize = WriteChunk(0, 0, &subprogress);
2038 // notify subprogress done
2039 __notify_progress(&subprogress, 1.f);
2040 }
2041 file_offset_t ullActualSize = __GetFileSize(hFileWrite);
2042
2043 // resize file to the final size (if the file was originally larger)
2044 if (ullActualSize > ullTotalSize) ResizeFile(ullTotalSize);
2045
2046 #if POSIX
2047 if (hFileWrite) close(hFileWrite);
2048 #elif defined(WIN32)
2049 if (hFileWrite != INVALID_HANDLE_VALUE) CloseHandle(hFileWrite);
2050 #else
2051 if (hFileWrite) fclose(hFileWrite);
2052 #endif
2053 hFileWrite = hFileRead;
2054
2055 // associate new file with this File object from now on
2056 Filename = path;
2057 bIsNewFile = false;
2058 Mode = (stream_mode_t) -1; // Just set it to an undefined mode ...
2059 SetMode(stream_mode_read_write); // ... so SetMode() has to reopen the file handles.
2060
2061 __notify_progress(pProgress, 1.0); // notify done
2062 }
2063
2064 void File::ResizeFile(file_offset_t ullNewSize) {
2065 #if POSIX
2066 if (ftruncate(hFileWrite, ullNewSize) < 0)
2067 throw Exception("Could not resize file \"" + Filename + "\"");
2068 #elif defined(WIN32)
2069 LARGE_INTEGER liFilePos;
2070 liFilePos.QuadPart = ullNewSize;
2071 if (
2072 !SetFilePointerEx(hFileWrite, liFilePos, NULL/*new pos pointer*/, FILE_BEGIN) ||
2073 !SetEndOfFile(hFileWrite)
2074 ) throw Exception("Could not resize file \"" + Filename + "\"");
2075 #else
2076 # error Sorry, this version of libgig only supports POSIX and Windows systems yet.
2077 # error Reason: portable implementation of RIFF::File::ResizeFile() is missing (yet)!
2078 #endif
2079 }
2080
2081 File::~File() {
2082 #if DEBUG
2083 std::cout << "File::~File()" << std::endl;
2084 #endif // DEBUG
2085 Cleanup();
2086 }
2087
2088 /**
2089 * Returns @c true if this file has been created new from scratch and
2090 * has not been stored to disk yet.
2091 */
2092 bool File::IsNew() const {
2093 return bIsNewFile;
2094 }
2095
2096 void File::Cleanup() {
2097 #if POSIX
2098 if (hFileRead) close(hFileRead);
2099 #elif defined(WIN32)
2100 if (hFileRead != INVALID_HANDLE_VALUE) CloseHandle(hFileRead);
2101 #else
2102 if (hFileRead) fclose(hFileRead);
2103 #endif // POSIX
2104 DeleteChunkList();
2105 pFile = NULL;
2106 }
2107
2108 /**
2109 * Returns the current size of this file (in bytes) as it is currently
2110 * yet stored on disk. If this file does not yet exist on disk (i.e. when
2111 * this RIFF File has just been created from scratch and Save() has not
2112 * been called yet) then this method returns 0.
2113 */
2114 file_offset_t File::GetCurrentFileSize() const {
2115 file_offset_t size = 0;
2116 try {
2117 size = __GetFileSize(hFileRead);
2118 } catch (...) {
2119 size = 0;
2120 }
2121 return size;
2122 }
2123
2124 /**
2125 * Returns the required size (in bytes) for this RIFF File to be saved to
2126 * disk. The precise size of the final file on disk depends on the RIFF
2127 * file offset size actually used internally in all headers of the RIFF
2128 * chunks. By default libgig handles the required file offset size
2129 * automatically for you; that means it is using 32 bit offsets for files
2130 * smaller than 4 GB and 64 bit offsets for files equal or larger than
2131 * 4 GB. You may however also override this default behavior by passing the
2132 * respective option to the RIFF File constructor to force one particular
2133 * offset size. In the latter case this method will return the file size
2134 * for the requested forced file offset size that will be used when calling
2135 * Save() later on.
2136 *
2137 * You may also use the overridden method below to get the file size for
2138 * an arbitrary other file offset size instead.
2139 *
2140 * @see offset_size_t
2141 * @see GetFileOffsetSize()
2142 */
2143 file_offset_t File::GetRequiredFileSize() {
2144 return GetRequiredFileSize(FileOffsetPreference);
2145 }
2146
2147 /**
2148 * Returns the rquired size (in bytes) for this RIFF file to be saved to
2149 * disk, assuming the passed @a fileOffsestSize would be used for the
2150 * Save() operation.
2151 *
2152 * This overridden method essentialy behaves like the above method, with
2153 * the difference that you must provide a specific RIFF @a fileOffsetSize
2154 * for calculating the theoretical final file size.
2155 *
2156 * @see GetFileOffsetSize()
2157 */
2158 file_offset_t File::GetRequiredFileSize(offset_size_t fileOffsetSize) {
2159 switch (fileOffsetSize) {
2160 case offset_size_auto: {
2161 file_offset_t fileSize = GetRequiredFileSize(offset_size_32bit);
2162 if (fileSize >> 32)
2163 return GetRequiredFileSize(offset_size_64bit);
2164 else
2165 return fileSize;
2166 }
2167 case offset_size_32bit: break;
2168 case offset_size_64bit: break;
2169 default: throw Exception("Internal error: Invalid RIFF::offset_size_t");
2170 }
2171 return RequiredPhysicalSize(FileOffsetSize);
2172 }
2173
2174 int File::FileOffsetSizeFor(file_offset_t fileSize) const {
2175 switch (FileOffsetPreference) {
2176 case offset_size_auto:
2177 return (fileSize >> 32) ? 8 : 4;
2178 case offset_size_32bit:
2179 return 4;
2180 case offset_size_64bit:
2181 return 8;
2182 default:
2183 throw Exception("Internal error: Invalid RIFF::offset_size_t");
2184 }
2185 }
2186
2187 /**
2188 * Returns the current size (in bytes) of file offsets stored in the
2189 * headers of all chunks of this file.
2190 *
2191 * Most RIFF files are using 32 bit file offsets internally, which limits
2192 * them to a maximum file size of less than 4 GB though. In contrast to the
2193 * common standard, this RIFF File class implementation supports handling of
2194 * RIFF files equal or larger than 4 GB. In such cases 64 bit file offsets
2195 * have to be used in all headers of all RIFF Chunks when being stored to a
2196 * physical file. libgig by default automatically selects the correct file
2197 * offset size for you. You may however also force one particular file
2198 * offset size by supplying the respective option to the RIFF::File
2199 * constructor.
2200 *
2201 * This method can be used to check which RIFF file offset size is currently
2202 * being used for this RIFF File.
2203 *
2204 * @returns current RIFF file offset size used (in bytes)
2205 * @see offset_size_t
2206 */
2207 int File::GetFileOffsetSize() const {
2208 return FileOffsetSize;
2209 }
2210
2211 /**
2212 * Returns the required size (in bytes) of file offsets stored in the
2213 * headers of all chunks of this file if the current RIFF tree would be
2214 * saved to disk by calling Save().
2215 *
2216 * See GetFileOffsetSize() for mor details about RIFF file offsets.
2217 *
2218 * @returns RIFF file offset size required (in bytes) if being saved
2219 * @see offset_size_t
2220 */
2221 int File::GetRequiredFileOffsetSize() {
2222 return FileOffsetSizeFor(GetCurrentFileSize());
2223 }
2224
2225 #if POSIX
2226 file_offset_t File::__GetFileSize(int hFile) const {
2227 struct stat filestat;
2228 if (fstat(hFile, &filestat) == -1)
2229 throw Exception("POSIX FS error: could not determine file size");
2230 return filestat.st_size;
2231 }
2232 #elif defined(WIN32)
2233 file_offset_t File::__GetFileSize(HANDLE hFile) const {
2234 LARGE_INTEGER size;
2235 if (!GetFileSizeEx(hFile, &size))
2236 throw Exception("Windows FS error: could not determine file size");
2237 return size.QuadPart;
2238 }
2239 #else // standard C functions
2240 file_offset_t File::__GetFileSize(FILE* hFile) const {
2241 off_t curpos = ftello(hFile);
2242 if (fseeko(hFile, 0, SEEK_END) == -1)
2243 throw Exception("FS error: could not determine file size");
2244 off_t size = ftello(hFile);
2245 fseeko(hFile, curpos, SEEK_SET);
2246 return size;
2247 }
2248 #endif
2249
2250
2251 // *************** Exception ***************
2252 // *
2253
2254 void Exception::PrintMessage() {
2255 std::cout << "RIFF::Exception: " << Message << std::endl;
2256 }
2257
2258
2259 // *************** functions ***************
2260 // *
2261
2262 /**
2263 * Returns the name of this C++ library. This is usually "libgig" of
2264 * course. This call is equivalent to DLS::libraryName() and
2265 * gig::libraryName().
2266 */
2267 String libraryName() {
2268 return PACKAGE;
2269 }
2270
2271 /**
2272 * Returns version of this C++ library. This call is equivalent to
2273 * DLS::libraryVersion() and gig::libraryVersion().
2274 */
2275 String libraryVersion() {
2276 return VERSION;
2277 }
2278
2279 } // namespace RIFF

  ViewVC Help
Powered by ViewVC