11namespace tracktion {
inline namespace engine
14static bool isWorthConvertingToOgg (AudioFile& source,
int quality)
20 return estimatedQuality == 0
21 || quality < estimatedQuality;
43 DBG (originalName +
" / " + storedName +
" length: " << length);
46TracktionArchiveFile::IndexEntry::~IndexEntry() {}
57 for (
int i = 0; i < extraNames.size(); ++i)
67 : engine (e), file (f)
72TracktionArchiveFile::~TracktionArchiveFile()
77int TracktionArchiveFile::getMagicNumber()
82juce::File TracktionArchiveFile::getFile()
const
87void TracktionArchiveFile::readIndex()
90 needToWriteIndex =
false;
99 if (magic == getMagicNumber())
102 jassert (indexOffset >= 8 && indexOffset < file.getSize());
104 if (indexOffset >= 8 && indexOffset < file.getSize())
110 entries.add (
new IndexEntry (in));
118void TracktionArchiveFile::flush()
120 if (needToWriteIndex && valid)
129 for (
auto e : entries)
136 needToWriteIndex =
false;
140bool TracktionArchiveFile::isValidArchive()
const
145int TracktionArchiveFile::getNumFiles()
const
147 return entries.size();
150juce::String TracktionArchiveFile::getOriginalFileName (
int index)
const
152 if (
auto i = entries[index])
158int TracktionArchiveFile::indexOfFile (
const juce::String& name)
const
160 for (
int i = entries.size(); --i >= 0;)
161 if (getOriginalFileName(i).equalsIgnoreCase (name))
169 if (entries[index] !=
nullptr)
170 if (
auto f = file.createInputStream())
172 entries[index]->offset,
173 entries[index]->length,
179bool TracktionArchiveFile::extractFile (
int index,
const juce::File& destDirectory,
180 juce::File& fileCreated,
bool askBeforeOverwriting)
185 auto destFile = destDirectory.
getChildFile (getOriginalFileName (index));
186 fileCreated = destFile;
188 if (askBeforeOverwriting && destFile.existsAsFile())
190 auto r = engine.getUIBehaviour()
191 .showYesNoCancelAlertBox (
TRANS(
"Unpacking archive"),
192 TRANS(
"The file XZZX already exists - do you want to overwrite it?")
193 .replace (
"XZZX", destFile.getFullPathName()),
195 TRANS(
"Leave existing"));
197 if (r == 1)
return true;
198 if (r == 0)
return false;
201 if (destFile.isDirectory()
202 || ! destFile.hasWriteAccess()
203 || ! destFile.deleteFile()
204 || entries[index] ==
nullptr)
207 auto source = createStoredInputStream (index);
209 if (source ==
nullptr)
212 auto storedName = entries[index]->storedName;
214 if (storedName != entries[index]->originalName)
216 if (storedName.endsWithIgnoreCase (
".flac"))
217 return AudioFileUtils::readFromFormat<juce::FlacAudioFormat> (engine, *source, destFile);
219 if (storedName.endsWithIgnoreCase (
".ogg"))
220 return AudioFileUtils::readFromFormat<juce::OggVorbisAudioFormat> (engine, *source, destFile);
222 if (storedName.endsWithIgnoreCase (
".gz"))
231 if (! out.openedOk())
240bool TracktionArchiveFile::extractAll (
const juce::File& destDirectory,
246 for (
int i = 0; i < entries.size(); ++i)
250 if (! extractFile (i, destDirectory, fileCreated,
false))
253 filesCreated.
add (fileCreated);
264 bool warnAboutOverwrite_,
268 archive (archive_), destDir (destDir_),
269 wasAborted (wasAborted_),
270 warnAboutOverwrite (warnAboutOverwrite_),
271 filesCreated (filesCreated_)
281 if (! destDir.createDirectory())
282 return jobHasFinished;
284 for (
int i = 0; i < archive.getNumFiles(); ++i)
290 for (
auto& f : filesCreated)
296 progress = i / (
float) archive.getNumFiles();
300 if (! archive.extractFile (i, destDir, fileCreated, warnAboutOverwrite))
301 return jobHasFinished;
304 filesCreated.
add (fileCreated);
308 return jobHasFinished;
311 float getCurrentTaskProgress()
321 bool warnAboutOverwrite =
false;
326bool TracktionArchiveFile::extractAllAsTask (
const juce::File& destDirectory,
bool warnAboutOverwrite,
329 ExtractionTask task (*
this, destDirectory, warnAboutOverwrite, filesCreated, wasAborted);
331 engine.getUIBehaviour().runTaskWithProgressBar (task);
337 CompressionType compression)
347 return addFile (f, name, compression);
351 CompressionType compression)
354 if (compression != CompressionType::none && f.
getSize() <= 16 * 1024)
355 compression = CompressionType::zip;
377 jassert (indexOffset < 2147483648);
379 if (indexOffset >= 2147483648)
381 TRACKTION_LOG_ERROR (
"Archive too large when archiving file: " + f.
getFileName());
388 entry->offset = indexOffset;
390 entry->originalName = filenameToUse;
391 entry->storedName = filenameToUse;
395 case CompressionType::none:
401 case CompressionType::zip:
403 entry->storedName = filenameRoot +
".gz";
406 deflater.writeFromInputStream (in, -1);
410 case CompressionType::lossless:
412 AudioFile af (engine, f);
414 if (af.isOggFile() || af.isMp3File() || af.isFlacFile())
420 if (af.getBitsPerSample() > 24)
423 entry->storedName = filenameRoot +
".gz";
426 deflater.writeFromInputStream (in, -1);
430 entry->storedName = filenameRoot +
".flac";
432 if (! AudioFileUtils::convertToFormat<juce::FlacAudioFormat> (engine, f, out, 0,
435 needToWriteIndex =
true;
436 TRACKTION_LOG_ERROR (
"Failed to add file to archive flac: " + f.
getFileName());
445 case CompressionType::lossyGoodQuality:
446 case CompressionType::lossyMediumQuality:
447 case CompressionType::lossyLowQuality:
449 entry->storedName = filenameRoot +
".ogg";
450 entry->originalName = entry->storedName;
452 auto quality = getOggQuality (compression);
453 AudioFile af (engine, f);
455 if (! isWorthConvertingToOgg (af, quality))
459 if (! fin.openedOk())
461 needToWriteIndex =
true;
462 TRACKTION_LOG_ERROR (
"Failed to add file to archive: " + f.
getFileName());
468 else if (! AudioFileUtils::convertToFormat<juce::OggVorbisAudioFormat> (engine, f, out, quality,
471 needToWriteIndex =
true;
472 TRACKTION_LOG_ERROR (
"Failed to add file to archive ogg: " + f.
getFileName());
481 TRACKTION_LOG_ERROR (
"Unknown compression type when archiving file: " + f.
getFileName());
493 jassert (indexOffset + entry->length < 2147483648);
495 if (indexOffset + entry->length >= 2147483648)
499 TRACKTION_LOG_ERROR (
"Archive too large when archiving file: " + f.
getFileName());
503 indexOffset += entry->length;
504 needToWriteIndex =
true;
507 entries.add (entry.release());
514void TracktionArchiveFile::addFileInfo (
const juce::String& filename,
518 auto i = indexOfFile (filename);
520 if (
auto entry = entries[i])
522 entry->extraNames.add (itemName);
523 entry->extraValues.add (itemValue);
524 needToWriteIndex =
true;
528int TracktionArchiveFile::getOggQuality (CompressionType c)
532 if (c == CompressionType::lossyGoodQuality)
return numOptions - 1;
533 if (c == CompressionType::lossyMediumQuality)
return numOptions / 2;
535 return numOptions / 5;
void add(const ElementType &newElement)
static constexpr uint32 littleEndianInt(const void *bytes) noexcept
String getFileName() const
File getChildFile(StringRef relativeOrAbsolutePath) const
String getRelativePathFrom(const File &directoryToBeRelativeTo) const
bool isAChildOf(const File &potentialParentDirectory) const
Result createDirectory() const
static void JUCE_CALLTYPE disableDenormalisedNumberSupport(bool shouldDisable=true) noexcept
virtual int64 getPosition()=0
virtual int64 writeFromInputStream(InputStream &source, int64 maxNumBytesToWrite)
virtual bool writeShort(short value)
virtual bool setPosition(int64 newPosition)=0
virtual bool writeInt(int value)
virtual bool writeString(const String &text)
int size() const noexcept
void add(String stringToAdd)
int lastIndexOfChar(juce_wchar character) const noexcept
String replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const
String substring(int startIndex, int endIndex) const
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
#define TRANS(stringLiteral)
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.