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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2685 - (show annotations) (download)
Sat Jan 3 21:44:42 2015 UTC (9 years, 3 months ago) by schoenebeck
File size: 86938 byte(s)
* RIFF: Fixed embarrassing old bug: POSIX read() errors were never detected
  on Chunk::Read() calls due to signment incompatible variable.
* Added new command line tool "gig2stereo" (and a man page for it).
* Bumped version (v3.3.0.svn23).

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

  ViewVC Help
Powered by ViewVC