The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

435 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_ZipFile.h"
  21. #include "../streams/juce_GZIPDecompressorInputStream.h"
  22. #include "../streams/juce_BufferedInputStream.h"
  23. #include "../streams/juce_FileInputSource.h"
  24. #include "juce_FileInputStream.h"
  25. #include "juce_FileOutputStream.h"
  26. #include "../../threads/juce_ScopedLock.h"
  27. //==============================================================================
  28. class ZipFile::ZipEntryInfo
  29. {
  30. public:
  31. ZipFile::ZipEntry entry;
  32. int streamOffset;
  33. int compressedSize;
  34. bool compressed;
  35. };
  36. //==============================================================================
  37. class ZipFile::ZipInputStream : public InputStream
  38. {
  39. public:
  40. //==============================================================================
  41. ZipInputStream (ZipFile& file_, ZipFile::ZipEntryInfo& zei)
  42. : file (file_),
  43. zipEntryInfo (zei),
  44. pos (0),
  45. headerSize (0),
  46. inputStream (0)
  47. {
  48. inputStream = file_.inputStream;
  49. if (file_.inputSource != 0)
  50. {
  51. inputStream = file.inputSource->createInputStream();
  52. }
  53. else
  54. {
  55. #if JUCE_DEBUG
  56. file_.numOpenStreams++;
  57. #endif
  58. }
  59. char buffer [30];
  60. if (inputStream != 0
  61. && inputStream->setPosition (zei.streamOffset)
  62. && inputStream->read (buffer, 30) == 30
  63. && ByteOrder::littleEndianInt (buffer) == 0x04034b50)
  64. {
  65. headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26)
  66. + ByteOrder::littleEndianShort (buffer + 28);
  67. }
  68. }
  69. ~ZipInputStream()
  70. {
  71. #if JUCE_DEBUG
  72. if (inputStream != 0 && inputStream == file.inputStream)
  73. file.numOpenStreams--;
  74. #endif
  75. if (inputStream != file.inputStream)
  76. delete inputStream;
  77. }
  78. int64 getTotalLength()
  79. {
  80. return zipEntryInfo.compressedSize;
  81. }
  82. int read (void* buffer, int howMany)
  83. {
  84. if (headerSize <= 0)
  85. return 0;
  86. howMany = (int) jmin ((int64) howMany, zipEntryInfo.compressedSize - pos);
  87. if (inputStream == 0)
  88. return 0;
  89. int num;
  90. if (inputStream == file.inputStream)
  91. {
  92. const ScopedLock sl (file.lock);
  93. inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize);
  94. num = inputStream->read (buffer, howMany);
  95. }
  96. else
  97. {
  98. inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize);
  99. num = inputStream->read (buffer, howMany);
  100. }
  101. pos += num;
  102. return num;
  103. }
  104. bool isExhausted()
  105. {
  106. return headerSize <= 0 || pos >= zipEntryInfo.compressedSize;
  107. }
  108. int64 getPosition()
  109. {
  110. return pos;
  111. }
  112. bool setPosition (int64 newPos)
  113. {
  114. pos = jlimit ((int64) 0, (int64) zipEntryInfo.compressedSize, newPos);
  115. return true;
  116. }
  117. private:
  118. //==============================================================================
  119. ZipFile& file;
  120. ZipEntryInfo zipEntryInfo;
  121. int64 pos;
  122. int headerSize;
  123. InputStream* inputStream;
  124. ZipInputStream (const ZipInputStream&);
  125. ZipInputStream& operator= (const ZipInputStream&);
  126. };
  127. //==============================================================================
  128. ZipFile::ZipFile (InputStream* const source_, const bool deleteStreamWhenDestroyed)
  129. : inputStream (source_)
  130. #if JUCE_DEBUG
  131. , numOpenStreams (0)
  132. #endif
  133. {
  134. if (deleteStreamWhenDestroyed)
  135. streamToDelete = inputStream;
  136. init();
  137. }
  138. ZipFile::ZipFile (const File& file)
  139. : inputStream (0)
  140. #if JUCE_DEBUG
  141. , numOpenStreams (0)
  142. #endif
  143. {
  144. inputSource = new FileInputSource (file);
  145. init();
  146. }
  147. ZipFile::ZipFile (InputSource* const inputSource_)
  148. : inputStream (0),
  149. inputSource (inputSource_)
  150. #if JUCE_DEBUG
  151. , numOpenStreams (0)
  152. #endif
  153. {
  154. init();
  155. }
  156. ZipFile::~ZipFile()
  157. {
  158. #if JUCE_DEBUG
  159. entries.clear();
  160. // If you hit this assertion, it means you've created a stream to read
  161. // one of the items in the zipfile, but you've forgotten to delete that
  162. // stream object before deleting the file.. Streams can't be kept open
  163. // after the file is deleted because they need to share the input
  164. // stream that the file uses to read itself.
  165. jassert (numOpenStreams == 0);
  166. #endif
  167. }
  168. //==============================================================================
  169. int ZipFile::getNumEntries() const throw()
  170. {
  171. return entries.size();
  172. }
  173. const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const throw()
  174. {
  175. ZipEntryInfo* const zei = entries [index];
  176. return zei != 0 ? &(zei->entry) : 0;
  177. }
  178. int ZipFile::getIndexOfFileName (const String& fileName) const throw()
  179. {
  180. for (int i = 0; i < entries.size(); ++i)
  181. if (entries.getUnchecked (i)->entry.filename == fileName)
  182. return i;
  183. return -1;
  184. }
  185. const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const throw()
  186. {
  187. return getEntry (getIndexOfFileName (fileName));
  188. }
  189. InputStream* ZipFile::createStreamForEntry (const int index)
  190. {
  191. ZipEntryInfo* const zei = entries[index];
  192. InputStream* stream = 0;
  193. if (zei != 0)
  194. {
  195. stream = new ZipInputStream (*this, *zei);
  196. if (zei->compressed)
  197. {
  198. stream = new GZIPDecompressorInputStream (stream, true, true,
  199. zei->entry.uncompressedSize);
  200. // (much faster to unzip in big blocks using a buffer..)
  201. stream = new BufferedInputStream (stream, 32768, true);
  202. }
  203. }
  204. return stream;
  205. }
  206. class ZipFile::ZipFilenameComparator
  207. {
  208. public:
  209. int compareElements (const ZipFile::ZipEntryInfo* first, const ZipFile::ZipEntryInfo* second)
  210. {
  211. return first->entry.filename.compare (second->entry.filename);
  212. }
  213. };
  214. void ZipFile::sortEntriesByFilename()
  215. {
  216. ZipFilenameComparator sorter;
  217. entries.sort (sorter);
  218. }
  219. //==============================================================================
  220. void ZipFile::init()
  221. {
  222. ScopedPointer <InputStream> toDelete;
  223. InputStream* in = inputStream;
  224. if (inputSource != 0)
  225. {
  226. in = inputSource->createInputStream();
  227. toDelete = in;
  228. }
  229. if (in != 0)
  230. {
  231. int numEntries = 0;
  232. int pos = findEndOfZipEntryTable (in, numEntries);
  233. if (pos >= 0 && pos < in->getTotalLength())
  234. {
  235. const int size = (int) (in->getTotalLength() - pos);
  236. in->setPosition (pos);
  237. MemoryBlock headerData;
  238. if (in->readIntoMemoryBlock (headerData, size) == size)
  239. {
  240. pos = 0;
  241. for (int i = 0; i < numEntries; ++i)
  242. {
  243. if (pos + 46 > size)
  244. break;
  245. const char* const buffer = static_cast <const char*> (headerData.getData()) + pos;
  246. const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28);
  247. if (pos + 46 + fileNameLen > size)
  248. break;
  249. ZipEntryInfo* const zei = new ZipEntryInfo();
  250. zei->entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
  251. const int time = ByteOrder::littleEndianShort (buffer + 12);
  252. const int date = ByteOrder::littleEndianShort (buffer + 14);
  253. const int year = 1980 + (date >> 9);
  254. const int month = ((date >> 5) & 15) - 1;
  255. const int day = date & 31;
  256. const int hours = time >> 11;
  257. const int minutes = (time >> 5) & 63;
  258. const int seconds = (time & 31) << 1;
  259. zei->entry.fileTime = Time (year, month, day, hours, minutes, seconds);
  260. zei->compressed = ByteOrder::littleEndianShort (buffer + 10) != 0;
  261. zei->compressedSize = ByteOrder::littleEndianInt (buffer + 20);
  262. zei->entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24);
  263. zei->streamOffset = ByteOrder::littleEndianInt (buffer + 42);
  264. entries.add (zei);
  265. pos += 46 + fileNameLen
  266. + ByteOrder::littleEndianShort (buffer + 30)
  267. + ByteOrder::littleEndianShort (buffer + 32);
  268. }
  269. }
  270. }
  271. }
  272. }
  273. int ZipFile::findEndOfZipEntryTable (InputStream* input, int& numEntries)
  274. {
  275. BufferedInputStream in (input, 8192, false);
  276. in.setPosition (in.getTotalLength());
  277. int64 pos = in.getPosition();
  278. const int64 lowestPos = jmax ((int64) 0, pos - 1024);
  279. char buffer [32];
  280. zeromem (buffer, sizeof (buffer));
  281. while (pos > lowestPos)
  282. {
  283. in.setPosition (pos - 22);
  284. pos = in.getPosition();
  285. memcpy (buffer + 22, buffer, 4);
  286. if (in.read (buffer, 22) != 22)
  287. return 0;
  288. for (int i = 0; i < 22; ++i)
  289. {
  290. if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50)
  291. {
  292. in.setPosition (pos + i);
  293. in.read (buffer, 22);
  294. numEntries = ByteOrder::littleEndianShort (buffer + 10);
  295. return ByteOrder::littleEndianInt (buffer + 16);
  296. }
  297. }
  298. }
  299. return 0;
  300. }
  301. bool ZipFile::uncompressTo (const File& targetDirectory,
  302. const bool shouldOverwriteFiles)
  303. {
  304. for (int i = 0; i < entries.size(); ++i)
  305. if (! uncompressEntry (i, targetDirectory, shouldOverwriteFiles))
  306. return false;
  307. return true;
  308. }
  309. bool ZipFile::uncompressEntry (const int index,
  310. const File& targetDirectory,
  311. bool shouldOverwriteFiles)
  312. {
  313. const ZipEntryInfo* zei = entries [index];
  314. if (zei != 0)
  315. {
  316. const File targetFile (targetDirectory.getChildFile (zei->entry.filename));
  317. if (zei->entry.filename.endsWithChar ('/'))
  318. {
  319. targetFile.createDirectory(); // (entry is a directory, not a file)
  320. }
  321. else
  322. {
  323. ScopedPointer<InputStream> in (createStreamForEntry (index));
  324. if (in != 0)
  325. {
  326. if (shouldOverwriteFiles && ! targetFile.deleteFile())
  327. return false;
  328. if ((! targetFile.exists()) && targetFile.getParentDirectory().createDirectory())
  329. {
  330. ScopedPointer<FileOutputStream> out (targetFile.createOutputStream());
  331. if (out != 0)
  332. {
  333. out->writeFromInputStream (*in, -1);
  334. out = 0;
  335. targetFile.setCreationTime (zei->entry.fileTime);
  336. targetFile.setLastModificationTime (zei->entry.fileTime);
  337. targetFile.setLastAccessTime (zei->entry.fileTime);
  338. return true;
  339. }
  340. }
  341. }
  342. }
  343. }
  344. return false;
  345. }
  346. END_JUCE_NAMESPACE