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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2675 - (show annotations) (download)
Sun Sep 14 16:07:34 2014 UTC (9 years, 7 months ago) by schoenebeck
File size: 83293 byte(s)
* RIFF.cpp, POSIX: only assume -1 result value as error on open() calls.
* RIFF.cpp, POSIX: show operating system's error reason if opening a file
  failed.
* Bumped version (3.3.0.svn21).

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

  ViewVC Help
Powered by ViewVC