1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-18 00:54:19 +00:00
JUCE/src/io/files/juce_ZipFile.cpp

434 lines
12 KiB
C++

/*
==============================================================================
This file is part of the JUCE library - "Jules' Utility Class Extensions"
Copyright 2004-10 by Raw Material Software Ltd.
------------------------------------------------------------------------------
JUCE can be redistributed and/or modified under the terms of the GNU General
Public License (Version 2), as published by the Free Software Foundation.
A copy of the license is included in the JUCE distribution, or can be found
online at www.gnu.org/licenses.
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.rawmaterialsoftware.com/juce for more information.
==============================================================================
*/
#include "../../core/juce_StandardHeader.h"
BEGIN_JUCE_NAMESPACE
#include "juce_ZipFile.h"
#include "../streams/juce_GZIPDecompressorInputStream.h"
#include "../streams/juce_BufferedInputStream.h"
#include "../streams/juce_FileInputSource.h"
#include "juce_FileInputStream.h"
#include "juce_FileOutputStream.h"
#include "../../threads/juce_ScopedLock.h"
//==============================================================================
class ZipFile::ZipEntryInfo
{
public:
ZipFile::ZipEntry entry;
int streamOffset;
int compressedSize;
bool compressed;
};
//==============================================================================
class ZipFile::ZipInputStream : public InputStream
{
public:
//==============================================================================
ZipInputStream (ZipFile& file_, ZipFile::ZipEntryInfo& zei)
: file (file_),
zipEntryInfo (zei),
pos (0),
headerSize (0),
inputStream (0)
{
inputStream = file_.inputStream;
if (file_.inputSource != 0)
{
inputStream = file.inputSource->createInputStream();
}
else
{
#if JUCE_DEBUG
file_.numOpenStreams++;
#endif
}
char buffer [30];
if (inputStream != 0
&& inputStream->setPosition (zei.streamOffset)
&& inputStream->read (buffer, 30) == 30
&& ByteOrder::littleEndianInt (buffer) == 0x04034b50)
{
headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26)
+ ByteOrder::littleEndianShort (buffer + 28);
}
}
~ZipInputStream()
{
#if JUCE_DEBUG
if (inputStream != 0 && inputStream == file.inputStream)
file.numOpenStreams--;
#endif
if (inputStream != file.inputStream)
delete inputStream;
}
int64 getTotalLength()
{
return zipEntryInfo.compressedSize;
}
int read (void* buffer, int howMany)
{
if (headerSize <= 0)
return 0;
howMany = (int) jmin ((int64) howMany, zipEntryInfo.compressedSize - pos);
if (inputStream == 0)
return 0;
int num;
if (inputStream == file.inputStream)
{
const ScopedLock sl (file.lock);
inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize);
num = inputStream->read (buffer, howMany);
}
else
{
inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize);
num = inputStream->read (buffer, howMany);
}
pos += num;
return num;
}
bool isExhausted()
{
return headerSize <= 0 || pos >= zipEntryInfo.compressedSize;
}
int64 getPosition()
{
return pos;
}
bool setPosition (int64 newPos)
{
pos = jlimit ((int64) 0, (int64) zipEntryInfo.compressedSize, newPos);
return true;
}
private:
//==============================================================================
ZipFile& file;
ZipEntryInfo zipEntryInfo;
int64 pos;
int headerSize;
InputStream* inputStream;
ZipInputStream (const ZipInputStream&);
ZipInputStream& operator= (const ZipInputStream&);
};
//==============================================================================
ZipFile::ZipFile (InputStream* const source_, const bool deleteStreamWhenDestroyed)
: inputStream (source_)
#if JUCE_DEBUG
, numOpenStreams (0)
#endif
{
if (deleteStreamWhenDestroyed)
streamToDelete = inputStream;
init();
}
ZipFile::ZipFile (const File& file)
: inputStream (0)
#if JUCE_DEBUG
, numOpenStreams (0)
#endif
{
inputSource = new FileInputSource (file);
init();
}
ZipFile::ZipFile (InputSource* const inputSource_)
: inputStream (0),
inputSource (inputSource_)
#if JUCE_DEBUG
, numOpenStreams (0)
#endif
{
init();
}
ZipFile::~ZipFile()
{
#if JUCE_DEBUG
entries.clear();
// If you hit this assertion, it means you've created a stream to read
// one of the items in the zipfile, but you've forgotten to delete that
// stream object before deleting the file.. Streams can't be kept open
// after the file is deleted because they need to share the input
// stream that the file uses to read itself.
jassert (numOpenStreams == 0);
#endif
}
//==============================================================================
int ZipFile::getNumEntries() const throw()
{
return entries.size();
}
const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const throw()
{
ZipEntryInfo* const zei = entries [index];
return zei != 0 ? &(zei->entry) : 0;
}
int ZipFile::getIndexOfFileName (const String& fileName) const throw()
{
for (int i = 0; i < entries.size(); ++i)
if (entries.getUnchecked (i)->entry.filename == fileName)
return i;
return -1;
}
const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const throw()
{
return getEntry (getIndexOfFileName (fileName));
}
InputStream* ZipFile::createStreamForEntry (const int index)
{
ZipEntryInfo* const zei = entries[index];
InputStream* stream = 0;
if (zei != 0)
{
stream = new ZipInputStream (*this, *zei);
if (zei->compressed)
{
stream = new GZIPDecompressorInputStream (stream, true, true,
zei->entry.uncompressedSize);
// (much faster to unzip in big blocks using a buffer..)
stream = new BufferedInputStream (stream, 32768, true);
}
}
return stream;
}
class ZipFile::ZipFilenameComparator
{
public:
int compareElements (const ZipFile::ZipEntryInfo* first, const ZipFile::ZipEntryInfo* second)
{
return first->entry.filename.compare (second->entry.filename);
}
};
void ZipFile::sortEntriesByFilename()
{
ZipFilenameComparator sorter;
entries.sort (sorter);
}
//==============================================================================
void ZipFile::init()
{
ScopedPointer <InputStream> toDelete;
InputStream* in = inputStream;
if (inputSource != 0)
{
in = inputSource->createInputStream();
toDelete = in;
}
if (in != 0)
{
int numEntries = 0;
int pos = findEndOfZipEntryTable (in, numEntries);
if (pos >= 0 && pos < in->getTotalLength())
{
const int size = (int) (in->getTotalLength() - pos);
in->setPosition (pos);
MemoryBlock headerData;
if (in->readIntoMemoryBlock (headerData, size) == size)
{
pos = 0;
for (int i = 0; i < numEntries; ++i)
{
if (pos + 46 > size)
break;
const char* const buffer = static_cast <const char*> (headerData.getData()) + pos;
const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28);
if (pos + 46 + fileNameLen > size)
break;
ZipEntryInfo* const zei = new ZipEntryInfo();
zei->entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
const int time = ByteOrder::littleEndianShort (buffer + 12);
const int date = ByteOrder::littleEndianShort (buffer + 14);
const int year = 1980 + (date >> 9);
const int month = ((date >> 5) & 15) - 1;
const int day = date & 31;
const int hours = time >> 11;
const int minutes = (time >> 5) & 63;
const int seconds = (time & 31) << 1;
zei->entry.fileTime = Time (year, month, day, hours, minutes, seconds);
zei->compressed = ByteOrder::littleEndianShort (buffer + 10) != 0;
zei->compressedSize = ByteOrder::littleEndianInt (buffer + 20);
zei->entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24);
zei->streamOffset = ByteOrder::littleEndianInt (buffer + 42);
entries.add (zei);
pos += 46 + fileNameLen
+ ByteOrder::littleEndianShort (buffer + 30)
+ ByteOrder::littleEndianShort (buffer + 32);
}
}
}
}
}
int ZipFile::findEndOfZipEntryTable (InputStream* input, int& numEntries)
{
BufferedInputStream in (input, 8192, false);
in.setPosition (in.getTotalLength());
int64 pos = in.getPosition();
const int64 lowestPos = jmax ((int64) 0, pos - 1024);
char buffer [32];
zeromem (buffer, sizeof (buffer));
while (pos > lowestPos)
{
in.setPosition (pos - 22);
pos = in.getPosition();
memcpy (buffer + 22, buffer, 4);
if (in.read (buffer, 22) != 22)
return 0;
for (int i = 0; i < 22; ++i)
{
if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50)
{
in.setPosition (pos + i);
in.read (buffer, 22);
numEntries = ByteOrder::littleEndianShort (buffer + 10);
return ByteOrder::littleEndianInt (buffer + 16);
}
}
}
return 0;
}
bool ZipFile::uncompressTo (const File& targetDirectory,
const bool shouldOverwriteFiles)
{
for (int i = 0; i < entries.size(); ++i)
if (! uncompressEntry (i, targetDirectory, shouldOverwriteFiles))
return false;
return true;
}
bool ZipFile::uncompressEntry (const int index,
const File& targetDirectory,
bool shouldOverwriteFiles)
{
const ZipEntryInfo* zei = entries [index];
if (zei != 0)
{
const File targetFile (targetDirectory.getChildFile (zei->entry.filename));
if (zei->entry.filename.endsWithChar ('/'))
{
targetFile.createDirectory(); // (entry is a directory, not a file)
}
else
{
ScopedPointer<InputStream> in (createStreamForEntry (index));
if (in != 0)
{
if (shouldOverwriteFiles && ! targetFile.deleteFile())
return false;
if ((! targetFile.exists()) && targetFile.getParentDirectory().createDirectory())
{
ScopedPointer<FileOutputStream> out (targetFile.createOutputStream());
if (out != 0)
{
out->writeFromInputStream (*in, -1);
out = 0;
targetFile.setCreationTime (zei->entry.fileTime);
targetFile.setLastModificationTime (zei->entry.fileTime);
targetFile.setLastAccessTime (zei->entry.fileTime);
return true;
}
}
}
}
}
return false;
}
END_JUCE_NAMESPACE