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.

411 lines
12KB

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