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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 808 - (show annotations) (download)
Tue Nov 22 09:11:17 2005 UTC (13 years, 9 months ago) by schoenebeck
File size: 62959 byte(s)
* src/RIFF.cpp: compilation error fix for big endian systems
* src/gigdump.cpp: show correct amount of dimension regions instead of 32
(patch by James Wylder)

1 /***************************************************************************
2 * *
3 * libgig - C++ cross-platform Gigasampler format file loader library *
4 * *
5 * Copyright (C) 2003-2005 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 <string.h>
25
26 #include "RIFF.h"
27
28 #include "helper.h"
29
30 namespace RIFF {
31
32 // *************** Chunk **************
33 // *
34
35 Chunk::Chunk(File* pFile) {
36 #if DEBUG
37 std::cout << "Chunk::Chunk(File* pFile)" << std::endl;
38 #endif // DEBUG
39 ulPos = 0;
40 pParent = NULL;
41 pChunkData = NULL;
42 ulChunkDataSize = 0;
43 ChunkID = CHUNK_ID_RIFF;
44 this->pFile = pFile;
45 }
46
47 Chunk::Chunk(File* pFile, unsigned long StartPos, List* Parent) {
48 #if DEBUG
49 std::cout << "Chunk::Chunk(File*,ulong,bool,List*),StartPos=" << StartPos << std::endl;
50 #endif // DEBUG
51 this->pFile = pFile;
52 ulStartPos = StartPos + CHUNK_HEADER_SIZE;
53 pParent = Parent;
54 ulPos = 0;
55 pChunkData = NULL;
56 ulChunkDataSize = 0;
57 ReadHeader(StartPos);
58 }
59
60 Chunk::Chunk(File* pFile, List* pParent, uint32_t uiChunkID, uint uiBodySize) {
61 this->pFile = pFile;
62 ulStartPos = 0; // arbitrary usually, since it will be updated when we write the chunk
63 this->pParent = pParent;
64 ulPos = 0;
65 pChunkData = NULL;
66 ulChunkDataSize = 0;
67 ChunkID = uiChunkID;
68 CurrentChunkSize = 0;
69 NewChunkSize = uiBodySize;
70 }
71
72 Chunk::~Chunk() {
73 if (pChunkData) delete[] pChunkData;
74 }
75
76 void Chunk::ReadHeader(unsigned long fPos) {
77 #if DEBUG
78 std::cout << "Chunk::Readheader(" << fPos << ") ";
79 #endif // DEBUG
80 #if POSIX
81 if (lseek(pFile->hFileRead, fPos, SEEK_SET) != -1) {
82 read(pFile->hFileRead, &ChunkID, 4);
83 read(pFile->hFileRead, &CurrentChunkSize, 4);
84 #else
85 if (!fseek(pFile->hFileRead, fPos, SEEK_SET)) {
86 fread(&ChunkID, 4, 1, pFile->hFileRead);
87 fread(&CurrentChunkSize, 4, 1, pFile->hFileRead);
88 #endif // POSIX
89 #if WORDS_BIGENDIAN
90 if (ChunkID == CHUNK_ID_RIFF) {
91 pFile->bEndianNative = false;
92 }
93 #else // little endian
94 if (ChunkID == CHUNK_ID_RIFX) {
95 pFile->bEndianNative = false;
96 ChunkID = CHUNK_ID_RIFF;
97 }
98 #endif // WORDS_BIGENDIAN
99 if (!pFile->bEndianNative) {
100 //swapBytes_32(&ChunkID);
101 swapBytes_32(&CurrentChunkSize);
102 }
103 #if DEBUG
104 std::cout << "ckID=" << convertToString(ChunkID) << " ";
105 std::cout << "ckSize=" << ChunkSize << " ";
106 std::cout << "bEndianNative=" << bEndianNative << std::endl;
107 #endif // DEBUG
108 NewChunkSize = CurrentChunkSize;
109 }
110 }
111
112 void Chunk::WriteHeader(unsigned long fPos) {
113 uint32_t uiNewChunkID = ChunkID;
114 if (ChunkID == CHUNK_ID_RIFF) {
115 #if WORDS_BIGENDIAN
116 if (pFile->bEndianNative) uiNewChunkID = CHUNK_ID_RIFX;
117 #else // little endian
118 if (!pFile->bEndianNative) uiNewChunkID = CHUNK_ID_RIFX;
119 #endif // WORDS_BIGENDIAN
120 }
121
122 uint32_t uiNewChunkSize = NewChunkSize;
123 if (!pFile->bEndianNative) {
124 swapBytes_32(&uiNewChunkSize);
125 }
126
127 #if POSIX
128 if (lseek(pFile->hFileWrite, fPos, SEEK_SET) != -1) {
129 write(pFile->hFileWrite, &uiNewChunkID, 4);
130 write(pFile->hFileWrite, &uiNewChunkSize, 4);
131 }
132 #else
133 if (!fseek(pFile->hFileWrite, fPos, SEEK_SET)) {
134 fwrite(&uiNewChunkID, 4, 1, pFile->hFileWrite);
135 fwrite(&uiNewChunkSize, 4, 1, pFile->hFileWrite);
136 }
137 #endif // POSIX
138 }
139
140 /**
141 * Returns the String representation of the chunk's ID (e.g. "RIFF",
142 * "LIST").
143 */
144 String Chunk::GetChunkIDString() {
145 return convertToString(ChunkID);
146 }
147
148 /**
149 * Sets the position within the chunk body, thus within the data portion
150 * of the chunk (in bytes).
151 *
152 * <b>Caution:</b> the position will be reset to zero whenever
153 * File::Save() was called.
154 *
155 * @param Where - position offset (in bytes)
156 * @param Whence - optional: defines to what <i>\a Where</i> relates to,
157 * if omitted \a Where relates to beginning of the chunk
158 * data
159 */
160 unsigned long Chunk::SetPos(unsigned long Where, stream_whence_t Whence) {
161 #if DEBUG
162 std::cout << "Chunk::SetPos(ulong)" << std::endl;
163 #endif // DEBUG
164 switch (Whence) {
165 case stream_curpos:
166 ulPos += Where;
167 break;
168 case stream_end:
169 ulPos = CurrentChunkSize - 1 - Where;
170 break;
171 case stream_backward:
172 ulPos -= Where;
173 break;
174 case stream_start: default:
175 ulPos = Where;
176 break;
177 }
178 if (ulPos > CurrentChunkSize) ulPos = CurrentChunkSize;
179 return ulPos;
180 }
181
182 /**
183 * Returns the number of bytes left to read in the chunk body.
184 * When reading data from the chunk using the Read*() Methods, the
185 * position within the chunk data (that is the chunk body) will be
186 * incremented by the number of read bytes and RemainingBytes() returns
187 * how much data is left to read from the current position to the end
188 * of the chunk data.
189 *
190 * @returns number of bytes left to read
191 */
192 unsigned long Chunk::RemainingBytes() {
193 #if DEBUG
194 std::cout << "Chunk::Remainingbytes()=" << CurrentChunkSize - ulPos << std::endl;
195 #endif // DEBUG
196 return CurrentChunkSize - ulPos;
197 }
198
199 /**
200 * Returns the current state of the chunk object.
201 * Following values are possible:
202 * - RIFF::stream_ready :
203 * chunk data can be read (this is the usual case)
204 * - RIFF::stream_closed :
205 * the data stream was closed somehow, no more reading possible
206 * - RIFF::stream_end_reached :
207 * alreaady reached the end of the chunk data, no more reading
208 * possible without SetPos()
209 */
210 stream_state_t Chunk::GetState() {
211 #if DEBUG
212 std::cout << "Chunk::GetState()" << std::endl;
213 #endif // DEBUG
214 #if POSIX
215 if (pFile->hFileRead == 0) return stream_closed;
216 #else
217 if (pFile->hFileRead == NULL) return stream_closed;
218 #endif // POSIX
219 if (ulPos < CurrentChunkSize) return stream_ready;
220 else return stream_end_reached;
221 }
222
223 /**
224 * Reads \a WordCount number of data words with given \a WordSize and
225 * copies it into a buffer pointed by \a pData. The buffer has to be
226 * allocated and be sure to provide the correct \a WordSize, as this
227 * will be important and taken into account for eventual endian
228 * correction (swapping of bytes due to different native byte order of
229 * a system). The position within the chunk will automatically be
230 * incremented.
231 *
232 * @param pData destination buffer
233 * @param WordCount number of data words to read
234 * @param WordSize size of each data word to read
235 * @returns number of successfully read data words or 0 if end
236 * of file reached or error occured
237 */
238 unsigned long Chunk::Read(void* pData, unsigned long WordCount, unsigned long WordSize) {
239 #if DEBUG
240 std::cout << "Chunk::Read(void*,ulong,ulong)" << std::endl;
241 #endif // DEBUG
242 if (ulPos >= CurrentChunkSize) return 0;
243 if (ulPos + WordCount * WordSize >= CurrentChunkSize) WordCount = (CurrentChunkSize - ulPos) / WordSize;
244 #if POSIX
245 if (lseek(pFile->hFileRead, ulStartPos + ulPos, SEEK_SET) < 0) return 0;
246 unsigned long readWords = read(pFile->hFileRead, pData, WordCount * WordSize);
247 if (readWords < 1) return 0;
248 readWords /= WordSize;
249 #else // standard C functions
250 if (fseek(pFile->hFileRead, ulStartPos + ulPos, SEEK_SET)) return 0;
251 unsigned long readWords = fread(pData, WordSize, WordCount, pFile->hFileRead);
252 #endif // POSIX
253 if (!pFile->bEndianNative && WordSize != 1) {
254 switch (WordSize) {
255 case 2:
256 for (unsigned long iWord = 0; iWord < readWords; iWord++)
257 swapBytes_16((uint16_t*) pData + iWord);
258 break;
259 case 4:
260 for (unsigned long iWord = 0; iWord < readWords; iWord++)
261 swapBytes_32((uint32_t*) pData + iWord);
262 break;
263 default:
264 for (unsigned long iWord = 0; iWord < readWords; iWord++)
265 swapBytes((uint8_t*) pData + iWord * WordSize, WordSize);
266 break;
267 }
268 }
269 SetPos(readWords * WordSize, stream_curpos);
270 return readWords;
271 }
272
273 /**
274 * Writes \a WordCount number of data words with given \a WordSize from
275 * the buffer pointed by \a pData. Be sure to provide the correct
276 * \a WordSize, as this will be important and taken into account for
277 * eventual endian correction (swapping of bytes due to different
278 * native byte order of a system). The position within the chunk will
279 * automatically be incremented.
280 *
281 * @param pData source buffer (containing the data)
282 * @param WordCount number of data words to write
283 * @param WordSize size of each data word to write
284 * @returns number of successfully written data words
285 * @throws RIFF::Exception if write operation would exceed current
286 * chunk size or any IO error occured
287 * @see Resize()
288 */
289 unsigned long Chunk::Write(void* pData, unsigned long WordCount, unsigned long WordSize) {
290 if (pFile->Mode != stream_mode_read_write)
291 throw Exception("Cannot write data to chunk, file has to be opened in read+write mode first");
292 if (ulPos >= CurrentChunkSize || ulPos + WordCount * WordSize > CurrentChunkSize)
293 throw Exception("End of chunk reached while trying to write data");
294 if (!pFile->bEndianNative && WordSize != 1) {
295 switch (WordSize) {
296 case 2:
297 for (unsigned long iWord = 0; iWord < WordCount; iWord++)
298 swapBytes_16((uint16_t*) pData + iWord);
299 break;
300 case 4:
301 for (unsigned long iWord = 0; iWord < WordCount; iWord++)
302 swapBytes_32((uint32_t*) pData + iWord);
303 break;
304 default:
305 for (unsigned long iWord = 0; iWord < WordCount; iWord++)
306 swapBytes((uint8_t*) pData + iWord * WordSize, WordSize);
307 break;
308 }
309 }
310 #if POSIX
311 if (lseek(pFile->hFileWrite, ulStartPos + ulPos, SEEK_SET) < 0) {
312 throw Exception("Could not seek to position " + ToString(ulPos) +
313 " in chunk (" + ToString(ulStartPos + ulPos) + " in file)");
314 }
315 unsigned long writtenWords = write(pFile->hFileWrite, pData, WordCount * WordSize);
316 if (writtenWords < 1) throw Exception("POSIX IO Error while trying to write chunk data");
317 writtenWords /= WordSize;
318 #else // standard C functions
319 if (fseek(pFile->hFileWrite, ulStartPos + ulPos, SEEK_SET)) {
320 throw Exception("Could not seek to position " + ToString(ulPos) +
321 " in chunk (" + ToString(ulStartPos + ulPos) + " in file)");
322 }
323 unsigned long writtenWords = fwrite(pData, WordSize, WordCount, pFile->hFileWrite);
324 #endif // POSIX
325 SetPos(writtenWords * WordSize, stream_curpos);
326 return writtenWords;
327 }
328
329 /** Just an internal wrapper for the main <i>Read()</i> method with additional Exception throwing on errors. */
330 unsigned long Chunk::ReadSceptical(void* pData, unsigned long WordCount, unsigned long WordSize) {
331 unsigned long readWords = Read(pData, WordCount, WordSize);
332 if (readWords != WordCount) throw RIFF::Exception("End of chunk data reached.");
333 return readWords;
334 }
335
336 /**
337 * Reads \a WordCount number of 8 Bit signed integer words and copies it
338 * into the buffer pointed by \a pData. The buffer has to be allocated.
339 * The position within the chunk will automatically be incremented.
340 *
341 * @param pData destination buffer
342 * @param WordCount number of 8 Bit signed integers to read
343 * @returns number of read integers
344 * @throws RIFF::Exception if an error occured or less than
345 * \a WordCount integers could be read!
346 */
347 unsigned long Chunk::ReadInt8(int8_t* pData, unsigned long WordCount) {
348 #if DEBUG
349 std::cout << "Chunk::ReadInt8(int8_t*,ulong)" << std::endl;
350 #endif // DEBUG
351 return ReadSceptical(pData, WordCount, 1);
352 }
353
354 /**
355 * Writes \a WordCount number of 8 Bit signed integer words from the
356 * buffer pointed by \a pData to the chunk's body, directly to the
357 * actual "physical" file. The position within the chunk will
358 * automatically be incremented. Note: you cannot write beyond the
359 * boundaries of the chunk, to append data to the chunk call Resize()
360 * before.
361 *
362 * @param pData source buffer (containing the data)
363 * @param WordCount number of 8 Bit signed integers to write
364 * @returns number of written integers
365 * @throws RIFF::Exception if an IO error occured
366 * @see Resize()
367 */
368 unsigned long Chunk::WriteInt8(int8_t* pData, unsigned long WordCount) {
369 return Write(pData, WordCount, 1);
370 }
371
372 /**
373 * Reads \a WordCount number of 8 Bit unsigned integer words and copies
374 * it into the buffer pointed by \a pData. The buffer has to be
375 * allocated. The position within the chunk will automatically be
376 * incremented.
377 *
378 * @param pData destination buffer
379 * @param WordCount number of 8 Bit unsigned integers to read
380 * @returns number of read integers
381 * @throws RIFF::Exception if an error occured or less than
382 * \a WordCount integers could be read!
383 */
384 unsigned long Chunk::ReadUint8(uint8_t* pData, unsigned long WordCount) {
385 #if DEBUG
386 std::cout << "Chunk::ReadUint8(uint8_t*,ulong)" << std::endl;
387 #endif // DEBUG
388 return ReadSceptical(pData, WordCount, 1);
389 }
390
391 /**
392 * Writes \a WordCount number of 8 Bit unsigned integer words from the
393 * buffer pointed by \a pData to the chunk's body, directly to the
394 * actual "physical" file. The position within the chunk will
395 * automatically be incremented. Note: you cannot write beyond the
396 * boundaries of the chunk, to append data to the chunk call Resize()
397 * before.
398 *
399 * @param pData source buffer (containing the data)
400 * @param WordCount number of 8 Bit unsigned integers to write
401 * @returns number of written integers
402 * @throws RIFF::Exception if an IO error occured
403 * @see Resize()
404 */
405 unsigned long Chunk::WriteUint8(uint8_t* pData, unsigned long WordCount) {
406 return Write(pData, WordCount, 1);
407 }
408
409 /**
410 * Reads \a WordCount number of 16 Bit signed integer words and copies
411 * it into the buffer pointed by \a pData. The buffer has to be
412 * allocated. Endian correction will automatically be done if needed.
413 * The position within the chunk will automatically be incremented.
414 *
415 * @param pData destination buffer
416 * @param WordCount number of 16 Bit signed integers to read
417 * @returns number of read integers
418 * @throws RIFF::Exception if an error occured or less than
419 * \a WordCount integers could be read!
420 */
421 unsigned long Chunk::ReadInt16(int16_t* pData, unsigned long WordCount) {
422 #if DEBUG
423 std::cout << "Chunk::ReadInt16(int16_t*,ulong)" << std::endl;
424 #endif // DEBUG
425 return ReadSceptical(pData, WordCount, 2);
426 }
427
428 /**
429 * Writes \a WordCount number of 16 Bit signed integer words from the
430 * buffer pointed by \a pData to the chunk's body, directly to the
431 * actual "physical" file. The position within the chunk will
432 * automatically be incremented. Note: you cannot write beyond the
433 * boundaries of the chunk, to append data to the chunk call Resize()
434 * before.
435 *
436 * @param pData source buffer (containing the data)
437 * @param WordCount number of 16 Bit signed integers to write
438 * @returns number of written integers
439 * @throws RIFF::Exception if an IO error occured
440 * @see Resize()
441 */
442 unsigned long Chunk::WriteInt16(int16_t* pData, unsigned long WordCount) {
443 return Write(pData, WordCount, 2);
444 }
445
446 /**
447 * Reads \a WordCount number of 16 Bit unsigned integer words and copies
448 * it into the buffer pointed by \a pData. The buffer has to be
449 * allocated. Endian correction will automatically be done if needed.
450 * The position within the chunk will automatically be incremented.
451 *
452 * @param pData destination buffer
453 * @param WordCount number of 8 Bit unsigned integers to read
454 * @returns number of read integers
455 * @throws RIFF::Exception if an error occured or less than
456 * \a WordCount integers could be read!
457 */
458 unsigned long Chunk::ReadUint16(uint16_t* pData, unsigned long WordCount) {
459 #if DEBUG
460 std::cout << "Chunk::ReadUint16(uint16_t*,ulong)" << std::endl;
461 #endif // DEBUG
462 return ReadSceptical(pData, WordCount, 2);
463 }
464
465 /**
466 * Writes \a WordCount number of 16 Bit unsigned integer words from the
467 * buffer pointed by \a pData to the chunk's body, directly to the
468 * actual "physical" file. The position within the chunk will
469 * automatically be incremented. Note: you cannot write beyond the
470 * boundaries of the chunk, to append data to the chunk call Resize()
471 * before.
472 *
473 * @param pData source buffer (containing the data)
474 * @param WordCount number of 16 Bit unsigned integers to write
475 * @returns number of written integers
476 * @throws RIFF::Exception if an IO error occured
477 * @see Resize()
478 */
479 unsigned long Chunk::WriteUint16(uint16_t* pData, unsigned long WordCount) {
480 return Write(pData, WordCount, 2);
481 }
482
483 /**
484 * Reads \a WordCount number of 32 Bit signed integer words and copies
485 * it into the buffer pointed by \a pData. The buffer has to be
486 * allocated. Endian correction will automatically be done if needed.
487 * The position within the chunk will automatically be incremented.
488 *
489 * @param pData destination buffer
490 * @param WordCount number of 32 Bit signed integers to read
491 * @returns number of read integers
492 * @throws RIFF::Exception if an error occured or less than
493 * \a WordCount integers could be read!
494 */
495 unsigned long Chunk::ReadInt32(int32_t* pData, unsigned long WordCount) {
496 #if DEBUG
497 std::cout << "Chunk::ReadInt32(int32_t*,ulong)" << std::endl;
498 #endif // DEBUG
499 return ReadSceptical(pData, WordCount, 4);
500 }
501
502 /**
503 * Writes \a WordCount number of 32 Bit signed integer words from the
504 * buffer pointed by \a pData to the chunk's body, directly to the
505 * actual "physical" file. The position within the chunk will
506 * automatically be incremented. Note: you cannot write beyond the
507 * boundaries of the chunk, to append data to the chunk call Resize()
508 * before.
509 *
510 * @param pData source buffer (containing the data)
511 * @param WordCount number of 32 Bit signed integers to write
512 * @returns number of written integers
513 * @throws RIFF::Exception if an IO error occured
514 * @see Resize()
515 */
516 unsigned long Chunk::WriteInt32(int32_t* pData, unsigned long WordCount) {
517 return Write(pData, WordCount, 4);
518 }
519
520 /**
521 * Reads \a WordCount number of 32 Bit unsigned integer words and copies
522 * it into the buffer pointed by \a pData. The buffer has to be
523 * allocated. Endian correction will automatically be done if needed.
524 * The position within the chunk will automatically be incremented.
525 *
526 * @param pData destination buffer
527 * @param WordCount number of 32 Bit unsigned integers to read
528 * @returns number of read integers
529 * @throws RIFF::Exception if an error occured or less than
530 * \a WordCount integers could be read!
531 */
532 unsigned long Chunk::ReadUint32(uint32_t* pData, unsigned long WordCount) {
533 #if DEBUG
534 std::cout << "Chunk::ReadUint32(uint32_t*,ulong)" << std::endl;
535 #endif // DEBUG
536 return ReadSceptical(pData, WordCount, 4);
537 }
538
539 /**
540 * Writes \a WordCount number of 32 Bit unsigned integer words from the
541 * buffer pointed by \a pData to the chunk's body, directly to the
542 * actual "physical" file. The position within the chunk will
543 * automatically be incremented. Note: you cannot write beyond the
544 * boundaries of the chunk, to append data to the chunk call Resize()
545 * before.
546 *
547 * @param pData source buffer (containing the data)
548 * @param WordCount number of 32 Bit unsigned integers to write
549 * @returns number of written integers
550 * @throws RIFF::Exception if an IO error occured
551 * @see Resize()
552 */
553 unsigned long Chunk::WriteUint32(uint32_t* pData, unsigned long WordCount) {
554 return Write(pData, WordCount, 4);
555 }
556
557 /**
558 * Reads one 8 Bit signed integer word and increments the position within
559 * the chunk.
560 *
561 * @returns read integer word
562 * @throws RIFF::Exception if an error occured
563 */
564 int8_t Chunk::ReadInt8() {
565 #if DEBUG
566 std::cout << "Chunk::ReadInt8()" << std::endl;
567 #endif // DEBUG
568 int8_t word;
569 ReadSceptical(&word,1,1);
570 return word;
571 }
572
573 /**
574 * Reads one 8 Bit unsigned integer word and increments the position
575 * within the chunk.
576 *
577 * @returns read integer word
578 * @throws RIFF::Exception if an error occured
579 */
580 uint8_t Chunk::ReadUint8() {
581 #if DEBUG
582 std::cout << "Chunk::ReadUint8()" << std::endl;
583 #endif // DEBUG
584 uint8_t word;
585 ReadSceptical(&word,1,1);
586 return word;
587 }
588
589 /**
590 * Reads one 16 Bit signed integer word and increments the position
591 * within the chunk. Endian correction will automatically be done if
592 * needed.
593 *
594 * @returns read integer word
595 * @throws RIFF::Exception if an error occured
596 */
597 int16_t Chunk::ReadInt16() {
598 #if DEBUG
599 std::cout << "Chunk::ReadInt16()" << std::endl;
600 #endif // DEBUG
601 int16_t word;
602 ReadSceptical(&word,1,2);
603 return word;
604 }
605
606 /**
607 * Reads one 16 Bit unsigned integer word and increments the position
608 * within the chunk. Endian correction will automatically be done if
609 * needed.
610 *
611 * @returns read integer word
612 * @throws RIFF::Exception if an error occured
613 */
614 uint16_t Chunk::ReadUint16() {
615 #if DEBUG
616 std::cout << "Chunk::ReadUint16()" << std::endl;
617 #endif // DEBUG
618 uint16_t word;
619 ReadSceptical(&word,1,2);
620 return word;
621 }
622
623 /**
624 * Reads one 32 Bit signed integer word and increments the position
625 * within the chunk. Endian correction will automatically be done if
626 * needed.
627 *
628 * @returns read integer word
629 * @throws RIFF::Exception if an error occured
630 */
631 int32_t Chunk::ReadInt32() {
632 #if DEBUG
633 std::cout << "Chunk::ReadInt32()" << std::endl;
634 #endif // DEBUG
635 int32_t word;
636 ReadSceptical(&word,1,4);
637 return word;
638 }
639
640 /**
641 * Reads one 32 Bit unsigned integer word and increments the position
642 * within the chunk. Endian correction will automatically be done if
643 * needed.
644 *
645 * @returns read integer word
646 * @throws RIFF::Exception if an error occured
647 */
648 uint32_t Chunk::ReadUint32() {
649 #if DEBUG
650 std::cout << "Chunk::ReadUint32()" << std::endl;
651 #endif // DEBUG
652 uint32_t word;
653 ReadSceptical(&word,1,4);
654 return word;
655 }
656
657 /** @brief Load chunk body into RAM.
658 *
659 * Loads the whole chunk body into memory. You can modify the data in
660 * RAM and save the data by calling File::Save() afterwards.
661 *
662 * <b>Caution:</b> the buffer pointer will be invalidated once
663 * File::Save() was called. You have to call LoadChunkData() again to
664 * get a new, valid pointer whenever File::Save() was called.
665 *
666 * You can call LoadChunkData() again if you previously scheduled to
667 * enlarge this chunk with a Resize() call. In that case the buffer will
668 * be enlarged to the new, scheduled chunk size and you can already
669 * place the new chunk data to the buffer and finally call File::Save()
670 * to enlarge the chunk physically and write the new data in one rush.
671 * This approach is definitely recommended if you have to enlarge and
672 * write new data to a lot of chunks.
673 *
674 * @returns a pointer to the data in RAM on success, NULL otherwise
675 * @throws Exception if data buffer could not be enlarged
676 * @see ReleaseChunkData()
677 */
678 void* Chunk::LoadChunkData() {
679 if (!pChunkData && pFile->Filename != "") {
680 #if POSIX
681 if (lseek(pFile->hFileRead, ulStartPos, SEEK_SET) == -1) return NULL;
682 #else
683 if (fseek(pFile->hFileRead, ulStartPos, SEEK_SET)) return NULL;
684 #endif // POSIX
685 unsigned long ulBufferSize = (CurrentChunkSize > NewChunkSize) ? CurrentChunkSize : NewChunkSize;
686 pChunkData = new uint8_t[ulBufferSize];
687 if (!pChunkData) return NULL;
688 memset(pChunkData, 0, ulBufferSize);
689 #if POSIX
690 unsigned long readWords = read(pFile->hFileRead, pChunkData, GetSize());
691 #else
692 unsigned long readWords = fread(pChunkData, 1, GetSize(), pFile->hFileRead);
693 #endif // POSIX
694 if (readWords != GetSize()) {
695 delete[] pChunkData;
696 return (pChunkData = NULL);
697 }
698 ulChunkDataSize = ulBufferSize;
699 } else if (NewChunkSize > ulChunkDataSize) {
700 uint8_t* pNewBuffer = new uint8_t[NewChunkSize];
701 if (!pNewBuffer) throw Exception("Could not enlarge chunk data buffer to " + ToString(NewChunkSize) + " bytes");
702 memset(pNewBuffer, 0 , NewChunkSize);
703 memcpy(pNewBuffer, pChunkData, ulChunkDataSize);
704 delete[] pChunkData;
705 pChunkData = pNewBuffer;
706 ulChunkDataSize = NewChunkSize;
707 }
708 return pChunkData;
709 }
710
711 /** @brief Free loaded chunk body from RAM.
712 *
713 * Frees loaded chunk body data from memory (RAM). You should call
714 * File::Save() before calling this method if you modified the data to
715 * make the changes persistent.
716 */
717 void Chunk::ReleaseChunkData() {
718 if (pChunkData) {
719 delete[] pChunkData;
720 pChunkData = NULL;
721 }
722 }
723
724 /** @brief Resize chunk.
725 *
726 * Resizes this chunk's body, that is the actual size of data possible
727 * to be written to this chunk. This call will return immediately and
728 * just schedule the resize operation. You should call File::Save() to
729 * actually perform the resize operation(s) "physically" to the file.
730 * As this can take a while on large files, it is recommended to call
731 * Resize() first on all chunks which have to be resized and finally to
732 * call File::Save() to perform all those resize operations in one rush.
733 *
734 * <b>Caution:</b> You cannot directly write to enlarged chunks before
735 * calling File::Save() as this might exceed the current chunk's body
736 * boundary!
737 *
738 * @param iNewSize - new chunk body size in bytes (must be greater than zero)
739 * @throws RIFF::Exception if \a iNewSize is less than 1
740 * @see File::Save()
741 */
742 void Chunk::Resize(int iNewSize) {
743 if (iNewSize <= 0) throw Exception("Chunk size must be at least one byte");
744 if (NewChunkSize == iNewSize) return;
745 NewChunkSize = iNewSize;
746 pFile->LogAsResized(this);
747 }
748
749 /** @brief Write chunk persistently e.g. to disk.
750 *
751 * Stores the chunk persistently to its actual "physical" file.
752 *
753 * @param ulWritePos - position within the "physical" file where this
754 * chunk should be written to
755 * @param ulCurrentDataOffset - offset of current (old) data within
756 * the file
757 * @returns new write position in the "physical" file, that is
758 * \a ulWritePos incremented by this chunk's new size
759 * (including its header size of course)
760 */
761 unsigned long Chunk::WriteChunk(unsigned long ulWritePos, unsigned long ulCurrentDataOffset) {
762 const unsigned long ulOriginalPos = ulWritePos;
763 ulWritePos += CHUNK_HEADER_SIZE;
764
765 if (pFile->Mode != stream_mode_read_write)
766 throw Exception("Cannot write list chunk, file has to be opened in read+write mode");
767
768 // if the whole chunk body was loaded into RAM
769 if (pChunkData) {
770 // make sure chunk data buffer in RAM is at least as large as the new chunk size
771 LoadChunkData();
772 // write chunk data from RAM persistently to the file
773 #if POSIX
774 lseek(pFile->hFileWrite, ulWritePos, SEEK_SET);
775 if (write(pFile->hFileWrite, pChunkData, NewChunkSize) != NewChunkSize) {
776 throw Exception("Writing Chunk data (from RAM) failed");
777 }
778 #else
779 fseek(pFile->hFileWrite, ulWritePos, SEEK_SET);
780 if (fwrite(pChunkData, 1, NewChunkSize, pFile->hFileWrite) != NewChunkSize) {
781 throw Exception("Writing Chunk data (from RAM) failed");
782 }
783 #endif // POSIX
784 } else {
785 // move chunk data from the end of the file to the appropriate position
786 int8_t* pCopyBuffer = new int8_t[4096];
787 unsigned long ulToMove = (NewChunkSize < CurrentChunkSize) ? NewChunkSize : CurrentChunkSize;
788 int iBytesMoved = 1;
789 for (unsigned long ulOffset = 0; iBytesMoved > 0; ulOffset += iBytesMoved, ulToMove -= iBytesMoved) {
790 iBytesMoved = (ulToMove < 4096) ? ulToMove : 4096;
791 #if POSIX
792 lseek(pFile->hFileRead, ulStartPos + ulCurrentDataOffset + ulOffset, SEEK_SET);
793 iBytesMoved = read(pFile->hFileRead, pCopyBuffer, iBytesMoved);
794 lseek(pFile->hFileWrite, ulWritePos + ulOffset, SEEK_SET);
795 iBytesMoved = write(pFile->hFileWrite, pCopyBuffer, iBytesMoved);
796 #else
797 fseek(pFile->hFileRead, ulStartPos + ulCurrentDataOffset + ulOffset, SEEK_SET);
798 iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, pFile->hFileRead);
799 fseek(pFile->hFileWrite, ulWritePos + ulOffset, SEEK_SET);
800 iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, pFile->hFileWrite);
801 #endif
802 }
803 delete[] pCopyBuffer;
804 if (iBytesMoved < 0) throw Exception("Writing Chunk data (from file) failed");
805 }
806
807 // update this chunk's header
808 CurrentChunkSize = NewChunkSize;
809 WriteHeader(ulOriginalPos);
810
811 // update chunk's position pointers
812 ulStartPos = ulOriginalPos + CHUNK_HEADER_SIZE;
813 ulPos = 0;
814
815 // add pad byte if needed
816 if ((ulStartPos + NewChunkSize) % 2 != 0) {
817 const char cPadByte = 0;
818 #if POSIX
819 lseek(pFile->hFileWrite, ulStartPos + NewChunkSize, SEEK_SET);
820 write(pFile->hFileWrite, &cPadByte, 1);
821 #else
822 fseek(pFile->hFileWrite, ulStartPos + NewChunkSize, SEEK_SET);
823 fwrite(&cPadByte, 1, 1, pFile->hFileWrite);
824 #endif
825 return ulStartPos + NewChunkSize + 1;
826 }
827
828 return ulStartPos + NewChunkSize;
829 }
830
831 void Chunk::__resetPos() {
832 ulPos = 0;
833 }
834
835
836
837 // *************** List ***************
838 // *
839
840 List::List(File* pFile) : Chunk(pFile) {
841 #if DEBUG
842 std::cout << "List::List(File* pFile)" << std::endl;
843 #endif // DEBUG
844 pSubChunks = NULL;
845 pSubChunksMap = NULL;
846 }
847
848 List::List(File* pFile, unsigned long StartPos, List* Parent)
849 : Chunk(pFile, StartPos, Parent) {
850 #if DEBUG
851 std::cout << "List::List(File*,ulong,bool,List*)" << std::endl;
852 #endif // DEBUG
853 pSubChunks = NULL;
854 pSubChunksMap = NULL;
855 ReadHeader(StartPos);
856 ulStartPos = StartPos + LIST_HEADER_SIZE;
857 }
858
859 List::List(File* pFile, List* pParent, uint32_t uiListID)
860 : Chunk(pFile, pParent, CHUNK_ID_LIST, 0) {
861 pSubChunks = NULL;
862 pSubChunksMap = NULL;
863 ListType = uiListID;
864 }
865
866 List::~List() {
867 #if DEBUG
868 std::cout << "List::~List()" << std::endl;
869 #endif // DEBUG
870 if (pSubChunks) {
871 ChunkList::iterator iter = pSubChunks->begin();
872 ChunkList::iterator end = pSubChunks->end();
873 while (iter != end) {
874 delete *iter;
875 iter++;
876 }
877 delete pSubChunks;
878 }
879 if (pSubChunksMap) delete pSubChunksMap;
880 }
881
882 /**
883 * Returns subchunk with chunk ID <i>\a ChunkID</i> within this chunk
884 * list. Use this method if you expect only one subchunk of that type in
885 * the list. It there are more than one, it's undetermined which one of
886 * them will be returned! If there are no subchunks with that desired
887 * chunk ID, NULL will be returned.
888 *
889 * @param ChunkID - chunk ID of the sought subchunk
890 * @returns pointer to the subchunk or NULL if there is none of
891 * that ID
892 */
893 Chunk* List::GetSubChunk(uint32_t ChunkID) {
894 #if DEBUG
895 std::cout << "List::GetSubChunk(uint32_t)" << std::endl;
896 #endif // DEBUG
897 if (!pSubChunksMap) LoadSubChunks();
898 return (*pSubChunksMap)[ChunkID];
899 }
900
901 /**
902 * Returns sublist chunk with list type <i>\a ListType</i> within this
903 * chunk list. Use this method if you expect only one sublist chunk of
904 * that type in the list. It there are more than one, it's undetermined
905 * which one of them will be returned! If there are no sublists with
906 * that desired list type, NULL will be returned.
907 *
908 * @param ListType - list type of the sought sublist
909 * @returns pointer to the sublist or NULL if there is none of
910 * that type
911 */
912 List* List::GetSubList(uint32_t ListType) {
913 #if DEBUG
914 std::cout << "List::GetSubList(uint32_t)" << std::endl;
915 #endif // DEBUG
916 if (!pSubChunks) LoadSubChunks();
917 ChunkList::iterator iter = pSubChunks->begin();
918 ChunkList::iterator end = pSubChunks->end();
919 while (iter != end) {
920 if ((*iter)->GetChunkID() == CHUNK_ID_LIST) {
921 List* l = (List*) *iter;
922 if (l->GetListType() == ListType) return l;
923 }
924 iter++;
925 }
926 return NULL;
927 }
928
929 /**
930 * Returns the first subchunk within the list. You have to call this
931 * method before you can call GetNextSubChunk(). Recall it when you want
932 * to start from the beginning of the list again.
933 *
934 * @returns pointer to the first subchunk within the list, NULL
935 * otherwise
936 */
937 Chunk* List::GetFirstSubChunk() {
938 #if DEBUG
939 std::cout << "List::GetFirstSubChunk()" << std::endl;
940 #endif // DEBUG
941 if (!pSubChunks) LoadSubChunks();
942 ChunksIterator = pSubChunks->begin();
943 return (ChunksIterator != pSubChunks->end()) ? *ChunksIterator : NULL;
944 }
945
946 /**
947 * Returns the next subchunk within the list. You have to call
948 * GetFirstSubChunk() before you can use this method!
949 *
950 * @returns pointer to the next subchunk within the list or NULL if
951 * end of list is reached
952 */
953 Chunk* List::GetNextSubChunk() {
954 #if DEBUG
955 std::cout << "List::GetNextSubChunk()" << std::endl;
956 #endif // DEBUG
957 if (!pSubChunks) return NULL;
958 ChunksIterator++;
959 return (ChunksIterator != pSubChunks->end()) ? *ChunksIterator : NULL;
960 }
961
962 /**
963 * Returns the first sublist within the list (that is a subchunk with
964 * chunk ID "LIST"). You have to call this method before you can call
965 * GetNextSubList(). Recall it when you want to start from the beginning
966 * of the list again.
967 *
968 * @returns pointer to the first sublist within the list, NULL
969 * otherwise
970 */
971 List* List::GetFirstSubList() {
972 #if DEBUG
973 std::cout << "List::GetFirstSubList()" << std::endl;
974 #endif // DEBUG
975 if (!pSubChunks) LoadSubChunks();
976 ListIterator = pSubChunks->begin();
977 ChunkList::iterator end = pSubChunks->end();
978 while (ListIterator != end) {
979 if ((*ListIterator)->GetChunkID() == CHUNK_ID_LIST) return (List*) *ListIterator;
980 ListIterator++;
981 }
982 return NULL;
983 }
984
985 /**
986 * Returns the next sublist (that is a subchunk with chunk ID "LIST")
987 * within the list. You have to call GetFirstSubList() before you can
988 * use this method!
989 *
990 * @returns pointer to the next sublist within the list, NULL if
991 * end of list is reached
992 */
993 List* List::GetNextSubList() {
994 #if DEBUG
995 std::cout << "List::GetNextSubList()" << std::endl;
996 #endif // DEBUG
997 if (!pSubChunks) return NULL;
998 if (ListIterator == pSubChunks->end()) return NULL;
999 ListIterator++;
1000 ChunkList::iterator end = pSubChunks->end();
1001 while (ListIterator != end) {
1002 if ((*ListIterator)->GetChunkID() == CHUNK_ID_LIST) return (List*) *ListIterator;
1003 ListIterator++;
1004 }
1005 return NULL;
1006 }
1007
1008 /**
1009 * Returns number of subchunks within the list.
1010 */
1011 unsigned int List::CountSubChunks() {
1012 if (!pSubChunks) LoadSubChunks();
1013 return pSubChunks->size();
1014 }
1015
1016 /**
1017 * Returns number of subchunks within the list with chunk ID
1018 * <i>\a ChunkId</i>.
1019 */
1020 unsigned int List::CountSubChunks(uint32_t ChunkID) {
1021 unsigned int result = 0;
1022 if (!pSubChunks) LoadSubChunks();
1023 ChunkList::iterator iter = pSubChunks->begin();
1024 ChunkList::iterator end = pSubChunks->end();
1025 while (iter != end) {
1026 if ((*iter)->GetChunkID() == ChunkID) {
1027 result++;
1028 }
1029 iter++;
1030 }
1031 return result;
1032 }
1033
1034 /**
1035 * Returns number of sublists within the list.
1036 */
1037 unsigned int List::CountSubLists() {
1038 return CountSubChunks(CHUNK_ID_LIST);
1039 }
1040
1041 /**
1042 * Returns number of sublists within the list with list type
1043 * <i>\a ListType</i>
1044 */
1045 unsigned int List::CountSubLists(uint32_t ListType) {
1046 unsigned int result = 0;
1047 if (!pSubChunks) LoadSubChunks();
1048 ChunkList::iterator iter = pSubChunks->begin();
1049 ChunkList::iterator end = pSubChunks->end();
1050 while (iter != end) {
1051 if ((*iter)->GetChunkID() == CHUNK_ID_LIST) {
1052 List* l = (List*) *iter;
1053 if (l->GetListType() == ListType) result++;
1054 }
1055 iter++;
1056 }
1057 return result;
1058 }
1059
1060 /** @brief Creates a new sub chunk.
1061 *
1062 * Creates and adds a new sub chunk to this list chunk. Note that the
1063 * chunk's body size given by \a uiBodySize must be greater than zero.
1064 * You have to call File::Save() to make this change persistent to the
1065 * actual file and <b>before</b> performing any data write operations
1066 * on the new chunk!
1067 *
1068 * @param uiChunkID - chunk ID of the new chunk
1069 * @param uiBodySize - size of the new chunk's body, that is its actual
1070 * data size (without header)
1071 * @throws RIFF::Exception if \a uiBodySize equals zero
1072 */
1073 Chunk* List::AddSubChunk(uint32_t uiChunkID, uint uiBodySize) {
1074 if (uiBodySize == 0) throw Exception("Chunk body size must be at least 1 byte");
1075 if (!pSubChunks) LoadSubChunks();
1076 Chunk* pNewChunk = new Chunk(pFile, this, uiChunkID, 0);
1077 pSubChunks->push_back(pNewChunk);
1078 (*pSubChunksMap)[uiChunkID] = pNewChunk;
1079 pNewChunk->Resize(uiBodySize);
1080 return pNewChunk;
1081 }
1082
1083 /** @brief Creates a new list sub chunk.
1084 *
1085 * Creates and adds a new list sub chunk to this list chunk. Note that
1086 * you have to add sub chunks / sub list chunks to the new created chunk
1087 * <b>before</b> trying to make this change persisten to the actual
1088 * file with File::Save()!
1089 *
1090 * @param uiListType - list ID of the new list chunk
1091 */
1092 List* List::AddSubList(uint32_t uiListType) {
1093 if (!pSubChunks) LoadSubChunks();
1094 List* pNewListChunk = new List(pFile, this, uiListType);
1095 pSubChunks->push_back(pNewListChunk);
1096 (*pSubChunksMap)[CHUNK_ID_LIST] = pNewListChunk;
1097 return pNewListChunk;
1098 }
1099
1100 /** @brief Removes a sub chunk.
1101 *
1102 * Removes the sub chunk given by \a pSubChunk from this list and frees
1103 * it completely from RAM. The given chunk can either be a normal sub
1104 * chunk or a list sub chunk. You should call File::Save() to make this
1105 * change persistent at any time.
1106 *
1107 * @param pSubChunk - sub chunk or sub list chunk to be removed
1108 */
1109 void List::DeleteSubChunk(Chunk* pSubChunk) {
1110 if (!pSubChunks) LoadSubChunks();
1111 pSubChunks->remove(pSubChunk);
1112 if ((*pSubChunksMap)[pSubChunk->GetChunkID()] == pSubChunk) {
1113 pSubChunksMap->erase(pSubChunk->GetChunkID());
1114 // try to find another chunk of the same chunk ID
1115 ChunkList::iterator iter = pSubChunks->begin();
1116 ChunkList::iterator end = pSubChunks->end();
1117 for (; iter != end; ++iter) {
1118 if ((*iter)->GetChunkID() == pSubChunk->GetChunkID()) {
1119 (*pSubChunksMap)[pSubChunk->GetChunkID()] = *iter;
1120 break; // we're done, stop search
1121 }
1122 }
1123 }
1124 delete pSubChunk;
1125 }
1126
1127 void List::ReadHeader(unsigned long fPos) {
1128 #if DEBUG
1129 std::cout << "List::Readheader(ulong) ";
1130 #endif // DEBUG
1131 Chunk::ReadHeader(fPos);
1132 NewChunkSize = CurrentChunkSize -= 4;
1133 #if POSIX
1134 lseek(pFile->hFileRead, fPos + CHUNK_HEADER_SIZE, SEEK_SET);
1135 read(pFile->hFileRead, &ListType, 4);
1136 #else
1137 fseek(pFile->hFileRead, fPos + CHUNK_HEADER_SIZE, SEEK_SET);
1138 fread(&ListType, 4, 1, pFile->hFileRead);
1139 #endif // POSIX
1140 #if DEBUG
1141 std::cout << "listType=" << convertToString(ListType) << std::endl;
1142 #endif // DEBUG
1143 if (!pFile->bEndianNative) {
1144 //swapBytes_32(&ListType);
1145 }
1146 }
1147
1148 void List::WriteHeader(unsigned long fPos) {
1149 // the four list type bytes officially belong the chunk's body in the RIFF format
1150 NewChunkSize += 4;
1151 Chunk::WriteHeader(fPos);
1152 NewChunkSize -= 4; // just revert the +4 incrementation
1153 #if POSIX
1154 lseek(pFile->hFileWrite, fPos + CHUNK_HEADER_SIZE, SEEK_SET);
1155 write(pFile->hFileWrite, &ListType, 4);
1156 #else
1157 fseek(pFile->hFileWrite, fPos + CHUNK_HEADER_SIZE, SEEK_SET);
1158 fwrite(&ListType, 4, 1, pFile->hFileWrite);
1159 #endif // POSIX
1160 }
1161
1162 void List::LoadSubChunks() {
1163 #if DEBUG
1164 std::cout << "List::LoadSubChunks()";
1165 #endif // DEBUG
1166 if (!pSubChunks) {
1167 pSubChunks = new ChunkList();
1168 pSubChunksMap = new ChunkMap();
1169 if (!pFile->hFileRead) return;
1170 unsigned long uiOriginalPos = GetPos();
1171 SetPos(0); // jump to beginning of list chunk body
1172 while (RemainingBytes() >= CHUNK_HEADER_SIZE) {
1173 Chunk* ck;
1174 uint32_t ckid;
1175 Read(&ckid, 4, 1);
1176 #if DEBUG
1177 std::cout << " ckid=" << convertToString(ckid) << std::endl;
1178 #endif // DEBUG
1179 if (ckid == CHUNK_ID_LIST) {
1180 ck = new RIFF::List(pFile, ulStartPos + ulPos - 4, this);
1181 SetPos(ck->GetSize() + LIST_HEADER_SIZE - 4, RIFF::stream_curpos);
1182 }
1183 else { // simple chunk
1184 ck = new RIFF::Chunk(pFile, ulStartPos + ulPos - 4, this);
1185 SetPos(ck->GetSize() + CHUNK_HEADER_SIZE - 4, RIFF::stream_curpos);
1186 }
1187 pSubChunks->push_back(ck);
1188 (*pSubChunksMap)[ckid] = ck;
1189 if (GetPos() % 2 != 0) SetPos(1, RIFF::stream_curpos); // jump over pad byte
1190 }
1191 SetPos(uiOriginalPos); // restore position before this call
1192 }
1193 }
1194
1195 /** @brief Write list chunk persistently e.g. to disk.
1196 *
1197 * Stores the list chunk persistently to its actual "physical" file. All
1198 * subchunks (including sub list chunks) will be stored recursively as
1199 * well.
1200 *
1201 * @param ulWritePos - position within the "physical" file where this
1202 * list chunk should be written to
1203 * @param ulCurrentDataOffset - offset of current (old) data within
1204 * the file
1205 * @returns new write position in the "physical" file, that is
1206 * \a ulWritePos incremented by this list chunk's new size
1207 * (including its header size of course)
1208 */
1209 unsigned long List::WriteChunk(unsigned long ulWritePos, unsigned long ulCurrentDataOffset) {
1210 const unsigned long ulOriginalPos = ulWritePos;
1211 ulWritePos += LIST_HEADER_SIZE;
1212
1213 if (pFile->Mode != stream_mode_read_write)
1214 throw Exception("Cannot write list chunk, file has to be opened in read+write mode");
1215
1216 // write all subchunks (including sub list chunks) recursively
1217 if (pSubChunks) {
1218 for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter) {
1219 ulWritePos = (*iter)->WriteChunk(ulWritePos, ulCurrentDataOffset);
1220 }
1221 }
1222
1223 // update this list chunk's header
1224 CurrentChunkSize = NewChunkSize = ulWritePos - ulOriginalPos - LIST_HEADER_SIZE;
1225 WriteHeader(ulOriginalPos);
1226
1227 // offset of this list chunk in new written file may have changed
1228 ulStartPos = ulOriginalPos + LIST_HEADER_SIZE;
1229
1230 return ulWritePos;
1231 }
1232
1233 void List::__resetPos() {
1234 Chunk::__resetPos();
1235 if (pSubChunks) {
1236 for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter) {
1237 (*iter)->__resetPos();
1238 }
1239 }
1240 }
1241
1242 /**
1243 * Returns string representation of the lists's id
1244 */
1245 String List::GetListTypeString() {
1246 return convertToString(ListType);
1247 }
1248
1249
1250
1251 // *************** File ***************
1252 // *
1253
1254 /** @brief Create new RIFF file.
1255 *
1256 * Use this constructor if you want to create a new RIFF file completely
1257 * "from scratch". Note: there must be no empty chunks or empty list
1258 * chunks when trying to make the new RIFF file persistent with Save()!
1259 *
1260 * @param FileType - four-byte identifier of the RIFF file type
1261 * @see AddSubChunk(), AddSubList()
1262 */
1263 File::File(uint32_t FileType) : List(this) {
1264 hFileRead = hFileWrite = 0;
1265 Mode = stream_mode_closed;
1266 bEndianNative = true;
1267 ulStartPos = RIFF_HEADER_SIZE;
1268 ListType = FileType;
1269 }
1270
1271 /** @brief Load existing RIFF file.
1272 *
1273 * Loads an existing RIFF file with all its chunks.
1274 *
1275 * @param path - path and file name of the RIFF file to open
1276 * @throws RIFF::Exception if error occured while trying to load the
1277 * given RIFF file
1278 */
1279 File::File(const String& path) : List(this), Filename(path) {
1280 #if DEBUG
1281 std::cout << "File::File("<<path<<")" << std::endl;
1282 #endif // DEBUG
1283 bEndianNative = true;
1284 #if POSIX
1285 hFileRead = hFileWrite = open(path.c_str(), O_RDONLY | O_NONBLOCK);
1286 if (hFileRead <= 0) {
1287 hFileRead = hFileWrite = 0;
1288 throw RIFF::Exception("Can't open \"" + path + "\"");
1289 }
1290 #else
1291 hFileRead = hFileWrite = fopen(path.c_str(), "rb");
1292 if (!hFile) throw RIFF::Exception("Can't open \"" + path + "\"");
1293 #endif // POSIX
1294 Mode = stream_mode_read;
1295 ulStartPos = RIFF_HEADER_SIZE;
1296 ReadHeader(0);
1297 if (ChunkID != CHUNK_ID_RIFF) {
1298 throw RIFF::Exception("Not a RIFF file");
1299 }
1300 }
1301
1302 String File::GetFileName() {
1303 return Filename;
1304 }
1305
1306 stream_mode_t File::GetMode() {
1307 return Mode;
1308 }
1309
1310 /** @brief Change file access mode.
1311 *
1312 * Changes files access mode either to read-only mode or to read/write
1313 * mode.
1314 *
1315 * @param NewMode - new file access mode
1316 * @returns true if mode was changed, false if current mode already
1317 * equals new mode
1318 * @throws RIFF::Exception if new file access mode is unknown
1319 */
1320 bool File::SetMode(stream_mode_t NewMode) {
1321 if (NewMode != Mode) {
1322 switch (NewMode) {
1323 case stream_mode_read:
1324 #if POSIX
1325 if (hFileRead) close(hFileRead);
1326 hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK);
1327 if (hFileRead < 0) {
1328 hFileRead = hFileWrite = 0;
1329 throw Exception("Could not (re)open file \"" + Filename + "\" in read mode");
1330 }
1331 #else
1332 if (hFileRead) fclose(hFileRead);
1333 hFileRead = hFileWrite = fopen(path.c_str(), "rb");
1334 if (!hFileRead) throw Exception("Could not (re)open file \"" + Filename + "\" in read mode");
1335 #endif
1336 __resetPos(); // reset read/write position of ALL 'Chunk' objects
1337 break;
1338 case stream_mode_read_write:
1339 #if POSIX
1340 if (hFileRead) close(hFileRead);
1341 hFileRead = hFileWrite = open(Filename.c_str(), O_RDWR | O_NONBLOCK);
1342 if (hFileRead < 0) {
1343 hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK);
1344 throw Exception("Could not open file \"" + Filename + "\" in read+write mode");
1345 }
1346 #else
1347 if (hFileRead) fclose(hFileRead);
1348 hFileRead = hFileWrite = fopen(path.c_str(), "r+b");
1349 if (!hFileRead) {
1350 hFileRead = hFileWrite = fopen(path.c_str(), "rb");
1351 throw Exception("Could not open file \"" + Filename + "\" in read+write mode");
1352 }
1353 #endif
1354 __resetPos(); // reset read/write position of ALL 'Chunk' objects
1355 break;
1356 case stream_mode_closed:
1357 #if POSIX
1358 if (hFileRead) close(hFileRead);
1359 if (hFileWrite) close(hFileWrite);
1360 #else
1361 if (hFileRead) fclose(hFileRead);
1362 if (hFileWrite) fclose(hFileWrite);
1363 #endif
1364 hFileRead = hFileWrite = 0;
1365 break;
1366 default:
1367 throw Exception("Unknown file access mode");
1368 }
1369 Mode = NewMode;
1370 return true;
1371 }
1372 return false;
1373 }
1374
1375 /** @brief Save changes to same file.
1376 *
1377 * Make all changes of all chunks persistent by writing them to the
1378 * actual (same) file. The file might temporarily grow to a higher size
1379 * than it will have at the end of the saving process, in case chunks
1380 * were grown.
1381 *
1382 * @throws RIFF::Exception if there is an empty chunk or empty list
1383 * chunk or any kind of IO error occured
1384 */
1385 void File::Save() {
1386 // reopen file in write mode
1387 SetMode(stream_mode_read_write);
1388
1389 // to be able to save the whole file without loading everything into
1390 // RAM and without having to store the data in a temporary file, we
1391 // enlarge the file with the sum of all _positive_ chunk size
1392 // changes, move current data towards the end of the file with the
1393 // calculated sum and finally update / rewrite the file by copying
1394 // the old data back to the right position at the beginning of the file
1395
1396 // first we sum up all positive chunk size changes (and skip all negative ones)
1397 unsigned long ulPositiveSizeDiff = 0;
1398 for (ChunkList::iterator iter = ResizedChunks.begin(), end = ResizedChunks.end(); iter != end; ++iter) {
1399 if ((*iter)->GetNewSize() == 0) throw Exception("There is at least one empty chunk (zero size)");
1400 if ((*iter)->GetNewSize() + 1L > (*iter)->GetSize()) {
1401 unsigned long ulDiff = (*iter)->GetNewSize() - (*iter)->GetSize() + 1L; // +1 in case we have to add a pad byte
1402 ulPositiveSizeDiff += ulDiff;
1403 }
1404 }
1405
1406 unsigned long ulWorkingFileSize = GetFileSize();
1407
1408 // if there are positive size changes...
1409 if (ulPositiveSizeDiff > 0) {
1410 // ... we enlarge this file first ...
1411 ulWorkingFileSize += ulPositiveSizeDiff;
1412 ResizeFile(ulWorkingFileSize);
1413 // ... and move current data by the same amount towards end of file.
1414 int8_t* pCopyBuffer = new int8_t[4096];
1415 const unsigned long ulFileSize = GetSize() + RIFF_HEADER_SIZE;
1416 int iBytesMoved = 1;
1417 for (unsigned long ulPos = 0; iBytesMoved > 0; ulPos += iBytesMoved) {
1418 const unsigned long ulToMove = ulFileSize - ulPos;
1419 iBytesMoved = (ulToMove < 4096) ? ulToMove : 4096;
1420 #if POSIX
1421 lseek(hFileRead, ulPos, SEEK_SET);
1422 iBytesMoved = read(hFileRead, pCopyBuffer, iBytesMoved);
1423 lseek(hFileWrite, ulPos + ulPositiveSizeDiff, SEEK_SET);
1424 iBytesMoved = write(hFileWrite, pCopyBuffer, iBytesMoved);
1425 #else
1426 fseek(hFileRead, ulPos, SEEK_SET);
1427 iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, hFileRead);
1428 fseek(hFileWrite, ulPos + ulPositiveSizeDiff, SEEK_SET);
1429 iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, hFileWrite);
1430 #endif
1431 }
1432 delete[] pCopyBuffer;
1433 if (iBytesMoved < 0) throw Exception("Could not modify file while trying to enlarge it");
1434 }
1435
1436 // rebuild / rewrite complete RIFF tree
1437 unsigned long ulTotalSize = WriteChunk(0, ulPositiveSizeDiff);
1438 unsigned long ulActualSize = __GetFileSize(hFileWrite);
1439
1440 // resize file to the final size
1441 if (ulTotalSize < ulActualSize) ResizeFile(ulTotalSize);
1442
1443 // forget all resized chunks
1444 ResizedChunks.clear();
1445 }
1446
1447 /** @brief Save changes to another file.
1448 *
1449 * Make all changes of all chunks persistent by writing them to another
1450 * file. <b>Caution:</b> this method is optimized for writing to
1451 * <b>another</b> file, do not use it to save the changes to the same
1452 * file! Use File::Save() in that case instead! Ignoring this might
1453 * result in a corrupted file, especially in case chunks were resized!
1454 *
1455 * After calling this method, this File object will be associated with
1456 * the new file (given by \a path) afterwards.
1457 *
1458 * @param path - path and file name where everything should be written to
1459 */
1460 void File::Save(const String& path) {
1461 //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
1462
1463 if (Filename.length() > 0) SetMode(stream_mode_read);
1464 // open the other (new) file for writing and truncate it to zero size
1465 #if POSIX
1466 hFileWrite = open(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP);
1467 if (hFileWrite < 0) {
1468 hFileWrite = hFileRead;
1469 throw Exception("Could not open file \"" + path + "\" for writing");
1470 }
1471 #else
1472 hFileWrite = fopen(path.c_str(), "w+b");
1473 if (!hFileWrite) {
1474 hFileWrite = hFileRead;
1475 throw Exception("Could not open file \"" + path + "\" for writing");
1476 }
1477 #endif // POSIX
1478 Mode = stream_mode_read_write;
1479
1480 // write complete RIFF tree to the other (new) file
1481 unsigned long ulTotalSize = WriteChunk(0, 0);
1482 unsigned long ulActualSize = __GetFileSize(hFileWrite);
1483
1484 // resize file to the final size (if the file was originally larger)
1485 if (ulTotalSize < ulActualSize) ResizeFile(ulTotalSize);
1486
1487 // forget all resized chunks
1488 ResizedChunks.clear();
1489
1490 if (Filename.length() > 0) {
1491 #if POSIX
1492 close(hFileWrite);
1493 #else
1494 fclose(hFileWrite);
1495 #endif
1496 hFileWrite = hFileRead;
1497 }
1498
1499 // associate new file with this File object from now on
1500 Filename = path;
1501 Mode = (stream_mode_t) -1; // Just set it to an undefined mode ...
1502 SetMode(stream_mode_read_write); // ... so SetMode() has to reopen the file handles.
1503 }
1504
1505 void File::ResizeFile(unsigned long ulNewSize) {
1506 #if POSIX
1507 if (ftruncate(hFileWrite, ulNewSize) < 0)
1508 throw Exception("Could not resize file \"" + Filename + "\"");
1509 #else
1510 # error Sorry, this version of libgig only supports POSIX systems yet.
1511 # error Reason: portable implementation of RIFF::File::ResizeFile() is missing (yet)!
1512 #endif
1513 }
1514
1515 File::~File() {
1516 #if DEBUG
1517 std::cout << "File::~File()" << std::endl;
1518 #endif // DEBUG
1519 #if POSIX
1520 if (hFileRead) close(hFileRead);
1521 #else
1522 if (hFileRead) fclose(hFileRead);
1523 #endif // POSIX
1524 }
1525
1526 void File::LogAsResized(Chunk* pResizedChunk) {
1527 ResizedChunks.push_back(pResizedChunk);
1528 }
1529
1530 unsigned long File::GetFileSize() {
1531 return __GetFileSize(hFileRead);
1532 }
1533
1534 #if POSIX
1535 unsigned long File::__GetFileSize(int hFile) {
1536 struct stat filestat;
1537 fstat(hFile, &filestat);
1538 long size = filestat.st_size;
1539 return size;
1540 }
1541 #else // standard C functions
1542 unsigned long File::__GetFileSize(FILE* hFile) {
1543 long curpos = ftell(hFile);
1544 fseek(hFile, 0, SEEK_END);
1545 long size = ftell(hFile);
1546 fseek(hFile, curpos, SEEK_SET);
1547 return size;
1548 }
1549 #endif
1550
1551
1552 // *************** Exception ***************
1553 // *
1554
1555 void Exception::PrintMessage() {
1556 std::cout << "RIFF::Exception: " << Message << std::endl;
1557 }
1558
1559
1560 // *************** functions ***************
1561 // *
1562
1563 /**
1564 * Returns the name of this C++ library. This is usually "libgig" of
1565 * course. This call is equivalent to DLS::libraryName() and
1566 * gig::libraryName().
1567 */
1568 String libraryName() {
1569 return PACKAGE;
1570 }
1571
1572 /**
1573 * Returns version of this C++ library. This call is equivalent to
1574 * DLS::libraryVersion() and gig::libraryVersion().
1575 */
1576 String libraryVersion() {
1577 return VERSION;
1578 }
1579
1580 } // namespace RIFF

  ViewVC Help
Powered by ViewVC