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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 833 - (show annotations) (download)
Sun Feb 5 17:30:13 2006 UTC (18 years, 2 months ago) by schoenebeck
File size: 63348 byte(s)
* src/RIFF.cpp: bugfix in File::Save() methods which did not ensure that
  the whole RIFF tree was already built before writing which caused opening
  an existing file and saving it afterwards to write just an empty file

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 void List::LoadSubChunksRecursively() {
1196 for (List* pList = GetFirstSubList(); pList; pList = GetNextSubList())
1197 pList->LoadSubChunksRecursively();
1198 }
1199
1200 /** @brief Write list chunk persistently e.g. to disk.
1201 *
1202 * Stores the list chunk persistently to its actual "physical" file. All
1203 * subchunks (including sub list chunks) will be stored recursively as
1204 * well.
1205 *
1206 * @param ulWritePos - position within the "physical" file where this
1207 * list chunk should be written to
1208 * @param ulCurrentDataOffset - offset of current (old) data within
1209 * the file
1210 * @returns new write position in the "physical" file, that is
1211 * \a ulWritePos incremented by this list chunk's new size
1212 * (including its header size of course)
1213 */
1214 unsigned long List::WriteChunk(unsigned long ulWritePos, unsigned long ulCurrentDataOffset) {
1215 const unsigned long ulOriginalPos = ulWritePos;
1216 ulWritePos += LIST_HEADER_SIZE;
1217
1218 if (pFile->Mode != stream_mode_read_write)
1219 throw Exception("Cannot write list chunk, file has to be opened in read+write mode");
1220
1221 // write all subchunks (including sub list chunks) recursively
1222 if (pSubChunks) {
1223 for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter) {
1224 ulWritePos = (*iter)->WriteChunk(ulWritePos, ulCurrentDataOffset);
1225 }
1226 }
1227
1228 // update this list chunk's header
1229 CurrentChunkSize = NewChunkSize = ulWritePos - ulOriginalPos - LIST_HEADER_SIZE;
1230 WriteHeader(ulOriginalPos);
1231
1232 // offset of this list chunk in new written file may have changed
1233 ulStartPos = ulOriginalPos + LIST_HEADER_SIZE;
1234
1235 return ulWritePos;
1236 }
1237
1238 void List::__resetPos() {
1239 Chunk::__resetPos();
1240 if (pSubChunks) {
1241 for (ChunkList::iterator iter = pSubChunks->begin(), end = pSubChunks->end(); iter != end; ++iter) {
1242 (*iter)->__resetPos();
1243 }
1244 }
1245 }
1246
1247 /**
1248 * Returns string representation of the lists's id
1249 */
1250 String List::GetListTypeString() {
1251 return convertToString(ListType);
1252 }
1253
1254
1255
1256 // *************** File ***************
1257 // *
1258
1259 /** @brief Create new RIFF file.
1260 *
1261 * Use this constructor if you want to create a new RIFF file completely
1262 * "from scratch". Note: there must be no empty chunks or empty list
1263 * chunks when trying to make the new RIFF file persistent with Save()!
1264 *
1265 * @param FileType - four-byte identifier of the RIFF file type
1266 * @see AddSubChunk(), AddSubList()
1267 */
1268 File::File(uint32_t FileType) : List(this) {
1269 hFileRead = hFileWrite = 0;
1270 Mode = stream_mode_closed;
1271 bEndianNative = true;
1272 ulStartPos = RIFF_HEADER_SIZE;
1273 ListType = FileType;
1274 }
1275
1276 /** @brief Load existing RIFF file.
1277 *
1278 * Loads an existing RIFF file with all its chunks.
1279 *
1280 * @param path - path and file name of the RIFF file to open
1281 * @throws RIFF::Exception if error occured while trying to load the
1282 * given RIFF file
1283 */
1284 File::File(const String& path) : List(this), Filename(path) {
1285 #if DEBUG
1286 std::cout << "File::File("<<path<<")" << std::endl;
1287 #endif // DEBUG
1288 bEndianNative = true;
1289 #if POSIX
1290 hFileRead = hFileWrite = open(path.c_str(), O_RDONLY | O_NONBLOCK);
1291 if (hFileRead <= 0) {
1292 hFileRead = hFileWrite = 0;
1293 throw RIFF::Exception("Can't open \"" + path + "\"");
1294 }
1295 #else
1296 hFileRead = hFileWrite = fopen(path.c_str(), "rb");
1297 if (!hFile) throw RIFF::Exception("Can't open \"" + path + "\"");
1298 #endif // POSIX
1299 Mode = stream_mode_read;
1300 ulStartPos = RIFF_HEADER_SIZE;
1301 ReadHeader(0);
1302 if (ChunkID != CHUNK_ID_RIFF) {
1303 throw RIFF::Exception("Not a RIFF file");
1304 }
1305 }
1306
1307 String File::GetFileName() {
1308 return Filename;
1309 }
1310
1311 stream_mode_t File::GetMode() {
1312 return Mode;
1313 }
1314
1315 /** @brief Change file access mode.
1316 *
1317 * Changes files access mode either to read-only mode or to read/write
1318 * mode.
1319 *
1320 * @param NewMode - new file access mode
1321 * @returns true if mode was changed, false if current mode already
1322 * equals new mode
1323 * @throws RIFF::Exception if new file access mode is unknown
1324 */
1325 bool File::SetMode(stream_mode_t NewMode) {
1326 if (NewMode != Mode) {
1327 switch (NewMode) {
1328 case stream_mode_read:
1329 #if POSIX
1330 if (hFileRead) close(hFileRead);
1331 hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK);
1332 if (hFileRead < 0) {
1333 hFileRead = hFileWrite = 0;
1334 throw Exception("Could not (re)open file \"" + Filename + "\" in read mode");
1335 }
1336 #else
1337 if (hFileRead) fclose(hFileRead);
1338 hFileRead = hFileWrite = fopen(path.c_str(), "rb");
1339 if (!hFileRead) throw Exception("Could not (re)open file \"" + Filename + "\" in read mode");
1340 #endif
1341 __resetPos(); // reset read/write position of ALL 'Chunk' objects
1342 break;
1343 case stream_mode_read_write:
1344 #if POSIX
1345 if (hFileRead) close(hFileRead);
1346 hFileRead = hFileWrite = open(Filename.c_str(), O_RDWR | O_NONBLOCK);
1347 if (hFileRead < 0) {
1348 hFileRead = hFileWrite = open(Filename.c_str(), O_RDONLY | O_NONBLOCK);
1349 throw Exception("Could not open file \"" + Filename + "\" in read+write mode");
1350 }
1351 #else
1352 if (hFileRead) fclose(hFileRead);
1353 hFileRead = hFileWrite = fopen(path.c_str(), "r+b");
1354 if (!hFileRead) {
1355 hFileRead = hFileWrite = fopen(path.c_str(), "rb");
1356 throw Exception("Could not open file \"" + Filename + "\" in read+write mode");
1357 }
1358 #endif
1359 __resetPos(); // reset read/write position of ALL 'Chunk' objects
1360 break;
1361 case stream_mode_closed:
1362 #if POSIX
1363 if (hFileRead) close(hFileRead);
1364 if (hFileWrite) close(hFileWrite);
1365 #else
1366 if (hFileRead) fclose(hFileRead);
1367 if (hFileWrite) fclose(hFileWrite);
1368 #endif
1369 hFileRead = hFileWrite = 0;
1370 break;
1371 default:
1372 throw Exception("Unknown file access mode");
1373 }
1374 Mode = NewMode;
1375 return true;
1376 }
1377 return false;
1378 }
1379
1380 /** @brief Save changes to same file.
1381 *
1382 * Make all changes of all chunks persistent by writing them to the
1383 * actual (same) file. The file might temporarily grow to a higher size
1384 * than it will have at the end of the saving process, in case chunks
1385 * were grown.
1386 *
1387 * @throws RIFF::Exception if there is an empty chunk or empty list
1388 * chunk or any kind of IO error occured
1389 */
1390 void File::Save() {
1391 // make sure the RIFF tree is built (from the original file)
1392 LoadSubChunksRecursively();
1393
1394 // reopen file in write mode
1395 SetMode(stream_mode_read_write);
1396
1397 // to be able to save the whole file without loading everything into
1398 // RAM and without having to store the data in a temporary file, we
1399 // enlarge the file with the sum of all _positive_ chunk size
1400 // changes, move current data towards the end of the file with the
1401 // calculated sum and finally update / rewrite the file by copying
1402 // the old data back to the right position at the beginning of the file
1403
1404 // first we sum up all positive chunk size changes (and skip all negative ones)
1405 unsigned long ulPositiveSizeDiff = 0;
1406 for (ChunkList::iterator iter = ResizedChunks.begin(), end = ResizedChunks.end(); iter != end; ++iter) {
1407 if ((*iter)->GetNewSize() == 0) throw Exception("There is at least one empty chunk (zero size)");
1408 if ((*iter)->GetNewSize() + 1L > (*iter)->GetSize()) {
1409 unsigned long ulDiff = (*iter)->GetNewSize() - (*iter)->GetSize() + 1L; // +1 in case we have to add a pad byte
1410 ulPositiveSizeDiff += ulDiff;
1411 }
1412 }
1413
1414 unsigned long ulWorkingFileSize = GetFileSize();
1415
1416 // if there are positive size changes...
1417 if (ulPositiveSizeDiff > 0) {
1418 // ... we enlarge this file first ...
1419 ulWorkingFileSize += ulPositiveSizeDiff;
1420 ResizeFile(ulWorkingFileSize);
1421 // ... and move current data by the same amount towards end of file.
1422 int8_t* pCopyBuffer = new int8_t[4096];
1423 const unsigned long ulFileSize = GetSize() + RIFF_HEADER_SIZE;
1424 int iBytesMoved = 1;
1425 for (unsigned long ulPos = 0; iBytesMoved > 0; ulPos += iBytesMoved) {
1426 const unsigned long ulToMove = ulFileSize - ulPos;
1427 iBytesMoved = (ulToMove < 4096) ? ulToMove : 4096;
1428 #if POSIX
1429 lseek(hFileRead, ulPos, SEEK_SET);
1430 iBytesMoved = read(hFileRead, pCopyBuffer, iBytesMoved);
1431 lseek(hFileWrite, ulPos + ulPositiveSizeDiff, SEEK_SET);
1432 iBytesMoved = write(hFileWrite, pCopyBuffer, iBytesMoved);
1433 #else
1434 fseek(hFileRead, ulPos, SEEK_SET);
1435 iBytesMoved = fread(pCopyBuffer, 1, iBytesMoved, hFileRead);
1436 fseek(hFileWrite, ulPos + ulPositiveSizeDiff, SEEK_SET);
1437 iBytesMoved = fwrite(pCopyBuffer, 1, iBytesMoved, hFileWrite);
1438 #endif
1439 }
1440 delete[] pCopyBuffer;
1441 if (iBytesMoved < 0) throw Exception("Could not modify file while trying to enlarge it");
1442 }
1443
1444 // rebuild / rewrite complete RIFF tree
1445 unsigned long ulTotalSize = WriteChunk(0, ulPositiveSizeDiff);
1446 unsigned long ulActualSize = __GetFileSize(hFileWrite);
1447
1448 // resize file to the final size
1449 if (ulTotalSize < ulActualSize) ResizeFile(ulTotalSize);
1450
1451 // forget all resized chunks
1452 ResizedChunks.clear();
1453 }
1454
1455 /** @brief Save changes to another file.
1456 *
1457 * Make all changes of all chunks persistent by writing them to another
1458 * file. <b>Caution:</b> this method is optimized for writing to
1459 * <b>another</b> file, do not use it to save the changes to the same
1460 * file! Use File::Save() in that case instead! Ignoring this might
1461 * result in a corrupted file, especially in case chunks were resized!
1462 *
1463 * After calling this method, this File object will be associated with
1464 * the new file (given by \a path) afterwards.
1465 *
1466 * @param path - path and file name where everything should be written to
1467 */
1468 void File::Save(const String& path) {
1469 //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
1470
1471 // make sure the RIFF tree is built (from the original file)
1472 LoadSubChunksRecursively();
1473
1474 if (Filename.length() > 0) SetMode(stream_mode_read);
1475 // open the other (new) file for writing and truncate it to zero size
1476 #if POSIX
1477 hFileWrite = open(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP);
1478 if (hFileWrite < 0) {
1479 hFileWrite = hFileRead;
1480 throw Exception("Could not open file \"" + path + "\" for writing");
1481 }
1482 #else
1483 hFileWrite = fopen(path.c_str(), "w+b");
1484 if (!hFileWrite) {
1485 hFileWrite = hFileRead;
1486 throw Exception("Could not open file \"" + path + "\" for writing");
1487 }
1488 #endif // POSIX
1489 Mode = stream_mode_read_write;
1490
1491 // write complete RIFF tree to the other (new) file
1492 unsigned long ulTotalSize = WriteChunk(0, 0);
1493 unsigned long ulActualSize = __GetFileSize(hFileWrite);
1494
1495 // resize file to the final size (if the file was originally larger)
1496 if (ulTotalSize < ulActualSize) ResizeFile(ulTotalSize);
1497
1498 // forget all resized chunks
1499 ResizedChunks.clear();
1500
1501 if (Filename.length() > 0) {
1502 #if POSIX
1503 close(hFileWrite);
1504 #else
1505 fclose(hFileWrite);
1506 #endif
1507 hFileWrite = hFileRead;
1508 }
1509
1510 // associate new file with this File object from now on
1511 Filename = path;
1512 Mode = (stream_mode_t) -1; // Just set it to an undefined mode ...
1513 SetMode(stream_mode_read_write); // ... so SetMode() has to reopen the file handles.
1514 }
1515
1516 void File::ResizeFile(unsigned long ulNewSize) {
1517 #if POSIX
1518 if (ftruncate(hFileWrite, ulNewSize) < 0)
1519 throw Exception("Could not resize file \"" + Filename + "\"");
1520 #else
1521 # error Sorry, this version of libgig only supports POSIX systems yet.
1522 # error Reason: portable implementation of RIFF::File::ResizeFile() is missing (yet)!
1523 #endif
1524 }
1525
1526 File::~File() {
1527 #if DEBUG
1528 std::cout << "File::~File()" << std::endl;
1529 #endif // DEBUG
1530 #if POSIX
1531 if (hFileRead) close(hFileRead);
1532 #else
1533 if (hFileRead) fclose(hFileRead);
1534 #endif // POSIX
1535 }
1536
1537 void File::LogAsResized(Chunk* pResizedChunk) {
1538 ResizedChunks.push_back(pResizedChunk);
1539 }
1540
1541 unsigned long File::GetFileSize() {
1542 return __GetFileSize(hFileRead);
1543 }
1544
1545 #if POSIX
1546 unsigned long File::__GetFileSize(int hFile) {
1547 struct stat filestat;
1548 fstat(hFile, &filestat);
1549 long size = filestat.st_size;
1550 return size;
1551 }
1552 #else // standard C functions
1553 unsigned long File::__GetFileSize(FILE* hFile) {
1554 long curpos = ftell(hFile);
1555 fseek(hFile, 0, SEEK_END);
1556 long size = ftell(hFile);
1557 fseek(hFile, curpos, SEEK_SET);
1558 return size;
1559 }
1560 #endif
1561
1562
1563 // *************** Exception ***************
1564 // *
1565
1566 void Exception::PrintMessage() {
1567 std::cout << "RIFF::Exception: " << Message << std::endl;
1568 }
1569
1570
1571 // *************** functions ***************
1572 // *
1573
1574 /**
1575 * Returns the name of this C++ library. This is usually "libgig" of
1576 * course. This call is equivalent to DLS::libraryName() and
1577 * gig::libraryName().
1578 */
1579 String libraryName() {
1580 return PACKAGE;
1581 }
1582
1583 /**
1584 * Returns version of this C++ library. This call is equivalent to
1585 * DLS::libraryVersion() and gig::libraryVersion().
1586 */
1587 String libraryVersion() {
1588 return VERSION;
1589 }
1590
1591 } // namespace RIFF

  ViewVC Help
Powered by ViewVC