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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2915 - (show annotations) (download)
Tue May 17 19:22:17 2016 UTC (6 years, 6 months ago) by schoenebeck
File size: 95885 byte(s)
- Fixed strict compile error on Windows.

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

  ViewVC Help
Powered by ViewVC