OpenShot Audio Library | OpenShotAudio  0.3.0
juce_ZipFile.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2017 - ROLI Ltd.
6 
7  JUCE is an open source library subject to commercial or open-source
8  licensing.
9 
10  The code included in this file is provided under the terms of the ISC license
11  http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12  To use, copy, modify, and/or distribute this software for any purpose with or
13  without fee is hereby granted provided that the above copyright notice and
14  this permission notice appear in all copies.
15 
16  JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17  EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18  DISCLAIMED.
19 
20  ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 inline uint16 readUnalignedLittleEndianShort (const void* buffer)
27 {
28  auto data = readUnaligned<uint16> (buffer);
29  return ByteOrder::littleEndianShort (&data);
30 }
31 
32 inline uint32 readUnalignedLittleEndianInt (const void* buffer)
33 {
34  auto data = readUnaligned<uint32> (buffer);
35  return ByteOrder::littleEndianInt (&data);
36 }
37 
38 struct ZipFile::ZipEntryHolder
39 {
40  ZipEntryHolder (const char* buffer, int fileNameLen)
41  {
42  isCompressed = readUnalignedLittleEndianShort (buffer + 10) != 0;
43  entry.fileTime = parseFileTime (readUnalignedLittleEndianShort (buffer + 12),
44  readUnalignedLittleEndianShort (buffer + 14));
45  compressedSize = (int64) readUnalignedLittleEndianInt (buffer + 20);
46  entry.uncompressedSize = (int64) readUnalignedLittleEndianInt (buffer + 24);
47  streamOffset = (int64) readUnalignedLittleEndianInt (buffer + 42);
48 
49  entry.externalFileAttributes = readUnalignedLittleEndianInt (buffer + 38);
50  auto fileType = (entry.externalFileAttributes >> 28) & 0xf;
51  entry.isSymbolicLink = (fileType == 0xA);
52 
53  entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
54  }
55 
56  static Time parseFileTime (uint32 time, uint32 date) noexcept
57  {
58  auto year = (int) (1980 + (date >> 9));
59  auto month = (int) (((date >> 5) & 15) - 1);
60  auto day = (int) (date & 31);
61  auto hours = (int) time >> 11;
62  auto minutes = (int) ((time >> 5) & 63);
63  auto seconds = (int) ((time & 31) << 1);
64 
65  return { year, month, day, hours, minutes, seconds };
66  }
67 
68  ZipEntry entry;
69  int64 streamOffset, compressedSize;
70  bool isCompressed;
71 };
72 
73 //==============================================================================
74 static int64 findCentralDirectoryFileHeader (InputStream& input, int& numEntries)
75 {
76  BufferedInputStream in (input, 8192);
77 
78  in.setPosition (in.getTotalLength());
79  auto pos = in.getPosition();
80  auto lowestPos = jmax ((int64) 0, pos - 1048576);
81  char buffer[32] = {};
82 
83  while (pos > lowestPos)
84  {
85  in.setPosition (pos - 22);
86  pos = in.getPosition();
87  memcpy (buffer + 22, buffer, 4);
88 
89  if (in.read (buffer, 22) != 22)
90  return 0;
91 
92  for (int i = 0; i < 22; ++i)
93  {
94  if (readUnalignedLittleEndianInt (buffer + i) == 0x06054b50)
95  {
96  in.setPosition (pos + i);
97  in.read (buffer, 22);
98  numEntries = readUnalignedLittleEndianShort (buffer + 10);
99  auto offset = (int64) readUnalignedLittleEndianInt (buffer + 16);
100 
101  if (offset >= 4)
102  {
103  in.setPosition (offset);
104 
105  // This is a workaround for some zip files which seem to contain the
106  // wrong offset for the central directory - instead of including the
107  // header, they point to the byte immediately after it.
108  if (in.readInt() != 0x02014b50)
109  {
110  in.setPosition (offset - 4);
111 
112  if (in.readInt() == 0x02014b50)
113  offset -= 4;
114  }
115  }
116 
117  return offset;
118  }
119  }
120  }
121 
122  return 0;
123 }
124 
125 //==============================================================================
126 struct ZipFile::ZipInputStream : public InputStream
127 {
128  ZipInputStream (ZipFile& zf, const ZipFile::ZipEntryHolder& zei)
129  : file (zf),
130  zipEntryHolder (zei),
131  inputStream (zf.inputStream)
132  {
133  if (zf.inputSource != nullptr)
134  {
135  streamToDelete.reset (file.inputSource->createInputStream());
136  inputStream = streamToDelete.get();
137  }
138  else
139  {
140  #if JUCE_DEBUG
141  zf.streamCounter.numOpenStreams++;
142  #endif
143  }
144 
145  char buffer[30];
146 
147  if (inputStream != nullptr
148  && inputStream->setPosition (zei.streamOffset)
149  && inputStream->read (buffer, 30) == 30
150  && ByteOrder::littleEndianInt (buffer) == 0x04034b50)
151  {
152  headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26)
153  + ByteOrder::littleEndianShort (buffer + 28);
154  }
155  }
156 
157  ~ZipInputStream() override
158  {
159  #if JUCE_DEBUG
160  if (inputStream != nullptr && inputStream == file.inputStream)
161  file.streamCounter.numOpenStreams--;
162  #endif
163  }
164 
165  int64 getTotalLength() override
166  {
167  return zipEntryHolder.compressedSize;
168  }
169 
170  int read (void* buffer, int howMany) override
171  {
172  if (headerSize <= 0)
173  return 0;
174 
175  howMany = (int) jmin ((int64) howMany, zipEntryHolder.compressedSize - pos);
176 
177  if (inputStream == nullptr)
178  return 0;
179 
180  int num;
181 
182  if (inputStream == file.inputStream)
183  {
184  const ScopedLock sl (file.lock);
185  inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize);
186  num = inputStream->read (buffer, howMany);
187  }
188  else
189  {
190  inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize);
191  num = inputStream->read (buffer, howMany);
192  }
193 
194  pos += num;
195  return num;
196  }
197 
198  bool isExhausted() override
199  {
200  return headerSize <= 0 || pos >= zipEntryHolder.compressedSize;
201  }
202 
203  int64 getPosition() override
204  {
205  return pos;
206  }
207 
208  bool setPosition (int64 newPos) override
209  {
210  pos = jlimit ((int64) 0, zipEntryHolder.compressedSize, newPos);
211  return true;
212  }
213 
214 private:
215  ZipFile& file;
216  ZipEntryHolder zipEntryHolder;
217  int64 pos = 0;
218  int headerSize = 0;
219  InputStream* inputStream;
220  std::unique_ptr<InputStream> streamToDelete;
221 
222  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream)
223 };
224 
225 
226 //==============================================================================
227 ZipFile::ZipFile (InputStream* stream, bool deleteStreamWhenDestroyed)
228  : inputStream (stream)
229 {
230  if (deleteStreamWhenDestroyed)
231  streamToDelete.reset (inputStream);
232 
233  init();
234 }
235 
236 ZipFile::ZipFile (InputStream& stream) : inputStream (&stream)
237 {
238  init();
239 }
240 
241 ZipFile::ZipFile (const File& file) : inputSource (new FileInputSource (file))
242 {
243  init();
244 }
245 
246 ZipFile::ZipFile (InputSource* source) : inputSource (source)
247 {
248  init();
249 }
250 
252 {
253  entries.clear();
254 }
255 
256 #if JUCE_DEBUG
257 ZipFile::OpenStreamCounter::~OpenStreamCounter()
258 {
259  /* If you hit this assertion, it means you've created a stream to read one of the items in the
260  zipfile, but you've forgotten to delete that stream object before deleting the file..
261  Streams can't be kept open after the file is deleted because they need to share the input
262  stream that is managed by the ZipFile object.
263  */
264  jassert (numOpenStreams == 0);
265 }
266 #endif
267 
268 //==============================================================================
269 int ZipFile::getNumEntries() const noexcept
270 {
271  return entries.size();
272 }
273 
274 const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept
275 {
276  if (auto* zei = entries[index])
277  return &(zei->entry);
278 
279  return nullptr;
280 }
281 
282 int ZipFile::getIndexOfFileName (const String& fileName, bool ignoreCase) const noexcept
283 {
284  for (int i = 0; i < entries.size(); ++i)
285  {
286  auto& entryFilename = entries.getUnchecked (i)->entry.filename;
287 
288  if (ignoreCase ? entryFilename.equalsIgnoreCase (fileName)
289  : entryFilename == fileName)
290  return i;
291  }
292 
293  return -1;
294 }
295 
296 const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName, bool ignoreCase) const noexcept
297 {
298  return getEntry (getIndexOfFileName (fileName, ignoreCase));
299 }
300 
302 {
303  InputStream* stream = nullptr;
304 
305  if (auto* zei = entries[index])
306  {
307  stream = new ZipInputStream (*this, *zei);
308 
309  if (zei->isCompressed)
310  {
311  stream = new GZIPDecompressorInputStream (stream, true,
312  GZIPDecompressorInputStream::deflateFormat,
313  zei->entry.uncompressedSize);
314 
315  // (much faster to unzip in big blocks using a buffer..)
316  stream = new BufferedInputStream (stream, 32768, true);
317  }
318  }
319 
320  return stream;
321 }
322 
324 {
325  for (int i = 0; i < entries.size(); ++i)
326  if (&entries.getUnchecked (i)->entry == &entry)
327  return createStreamForEntry (i);
328 
329  return nullptr;
330 }
331 
333 {
334  std::sort (entries.begin(), entries.end(),
335  [] (const ZipEntryHolder* e1, const ZipEntryHolder* e2) { return e1->entry.filename < e2->entry.filename; });
336 }
337 
338 //==============================================================================
339 void ZipFile::init()
340 {
341  std::unique_ptr<InputStream> toDelete;
342  InputStream* in = inputStream;
343 
344  if (inputSource != nullptr)
345  {
346  in = inputSource->createInputStream();
347  toDelete.reset (in);
348  }
349 
350  if (in != nullptr)
351  {
352  int numEntries = 0;
353  auto centralDirectoryPos = findCentralDirectoryFileHeader (*in, numEntries);
354 
355  if (centralDirectoryPos >= 0 && centralDirectoryPos < in->getTotalLength())
356  {
357  auto size = (size_t) (in->getTotalLength() - centralDirectoryPos);
358 
359  in->setPosition (centralDirectoryPos);
360  MemoryBlock headerData;
361 
362  if (in->readIntoMemoryBlock (headerData, (ssize_t) size) == size)
363  {
364  size_t pos = 0;
365 
366  for (int i = 0; i < numEntries; ++i)
367  {
368  if (pos + 46 > size)
369  break;
370 
371  auto* buffer = static_cast<const char*> (headerData.getData()) + pos;
372  auto fileNameLen = readUnalignedLittleEndianShort (buffer + 28u);
373 
374  if (pos + 46 + fileNameLen > size)
375  break;
376 
377  entries.add (new ZipEntryHolder (buffer, fileNameLen));
378 
379  pos += 46u + fileNameLen
380  + readUnalignedLittleEndianShort (buffer + 30u)
381  + readUnalignedLittleEndianShort (buffer + 32u);
382  }
383  }
384  }
385  }
386 }
387 
388 Result ZipFile::uncompressTo (const File& targetDirectory,
389  const bool shouldOverwriteFiles)
390 {
391  for (int i = 0; i < entries.size(); ++i)
392  {
393  auto result = uncompressEntry (i, targetDirectory, shouldOverwriteFiles);
394 
395  if (result.failed())
396  return result;
397  }
398 
399  return Result::ok();
400 }
401 
402 Result ZipFile::uncompressEntry (int index, const File& targetDirectory, bool shouldOverwriteFiles)
403 {
404  auto* zei = entries.getUnchecked (index);
405 
406  #if JUCE_WINDOWS
407  auto entryPath = zei->entry.filename;
408  #else
409  auto entryPath = zei->entry.filename.replaceCharacter ('\\', '/');
410  #endif
411 
412  if (entryPath.isEmpty())
413  return Result::ok();
414 
415  auto targetFile = targetDirectory.getChildFile (entryPath);
416 
417  if (entryPath.endsWithChar ('/') || entryPath.endsWithChar ('\\'))
418  return targetFile.createDirectory(); // (entry is a directory, not a file)
419 
420  std::unique_ptr<InputStream> in (createStreamForEntry (index));
421 
422  if (in == nullptr)
423  return Result::fail ("Failed to open the zip file for reading");
424 
425  if (targetFile.exists())
426  {
427  if (! shouldOverwriteFiles)
428  return Result::ok();
429 
430  if (! targetFile.deleteFile())
431  return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
432  }
433 
434  if (! targetFile.getParentDirectory().createDirectory())
435  return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName());
436 
437  if (zei->entry.isSymbolicLink)
438  {
439  String originalFilePath (in->readEntireStreamAsString()
440  .replaceCharacter (L'/', File::getSeparatorChar()));
441 
442  if (! File::createSymbolicLink (targetFile, originalFilePath, true))
443  return Result::fail ("Failed to create symbolic link: " + originalFilePath);
444  }
445  else
446  {
447  FileOutputStream out (targetFile);
448 
449  if (out.failedToOpen())
450  return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
451 
452  out << *in;
453  }
454 
455  targetFile.setCreationTime (zei->entry.fileTime);
456  targetFile.setLastModificationTime (zei->entry.fileTime);
457  targetFile.setLastAccessTime (zei->entry.fileTime);
458 
459  return Result::ok();
460 }
461 
462 
463 //==============================================================================
464 struct ZipFile::Builder::Item
465 {
466  Item (const File& f, InputStream* s, int compression, const String& storedPath, Time time)
467  : file (f), stream (s), storedPathname (storedPath), fileTime (time), compressionLevel (compression)
468  {
469  symbolicLink = (file.exists() && file.isSymbolicLink());
470  }
471 
472  bool writeData (OutputStream& target, const int64 overallStartPosition)
473  {
474  MemoryOutputStream compressedData ((size_t) file.getSize());
475 
476  if (symbolicLink)
477  {
478  auto relativePath = file.getNativeLinkedTarget().replaceCharacter (File::getSeparatorChar(), L'/');
479 
480  uncompressedSize = relativePath.length();
481 
482  checksum = zlibNamespace::crc32 (0, (uint8_t*) relativePath.toRawUTF8(), (unsigned int) uncompressedSize);
483  compressedData << relativePath;
484  }
485  else if (compressionLevel > 0)
486  {
487  GZIPCompressorOutputStream compressor (compressedData, compressionLevel,
488  GZIPCompressorOutputStream::windowBitsRaw);
489  if (! writeSource (compressor))
490  return false;
491  }
492  else
493  {
494  if (! writeSource (compressedData))
495  return false;
496  }
497 
498  compressedSize = (int64) compressedData.getDataSize();
499  headerStart = target.getPosition() - overallStartPosition;
500 
501  target.writeInt (0x04034b50);
502  writeFlagsAndSizes (target);
503  target << storedPathname
504  << compressedData;
505 
506  return true;
507  }
508 
509  bool writeDirectoryEntry (OutputStream& target)
510  {
511  target.writeInt (0x02014b50);
512  target.writeShort (symbolicLink ? 0x0314 : 0x0014);
513  writeFlagsAndSizes (target);
514  target.writeShort (0); // comment length
515  target.writeShort (0); // start disk num
516  target.writeShort (0); // internal attributes
517  target.writeInt ((int) (symbolicLink ? 0xA1ED0000 : 0)); // external attributes
518  target.writeInt ((int) (uint32) headerStart);
519  target << storedPathname;
520 
521  return true;
522  }
523 
524 private:
525  const File file;
526  std::unique_ptr<InputStream> stream;
527  String storedPathname;
528  Time fileTime;
529  int64 compressedSize = 0, uncompressedSize = 0, headerStart = 0;
530  int compressionLevel = 0;
531  unsigned long checksum = 0;
532  bool symbolicLink = false;
533 
534  static void writeTimeAndDate (OutputStream& target, Time t)
535  {
536  target.writeShort ((short) (t.getSeconds() + (t.getMinutes() << 5) + (t.getHours() << 11)));
537  target.writeShort ((short) (t.getDayOfMonth() + ((t.getMonth() + 1) << 5) + ((t.getYear() - 1980) << 9)));
538  }
539 
540  bool writeSource (OutputStream& target)
541  {
542  if (stream == nullptr)
543  {
544  stream.reset (file.createInputStream());
545 
546  if (stream == nullptr)
547  return false;
548  }
549 
550  checksum = 0;
551  uncompressedSize = 0;
552  const int bufferSize = 4096;
553  HeapBlock<unsigned char> buffer (bufferSize);
554 
555  while (! stream->isExhausted())
556  {
557  auto bytesRead = stream->read (buffer, bufferSize);
558 
559  if (bytesRead < 0)
560  return false;
561 
562  checksum = zlibNamespace::crc32 (checksum, buffer, (unsigned int) bytesRead);
563  target.write (buffer, (size_t) bytesRead);
564  uncompressedSize += bytesRead;
565  }
566 
567  stream.reset();
568  return true;
569  }
570 
571  void writeFlagsAndSizes (OutputStream& target) const
572  {
573  target.writeShort (10); // version needed
574  target.writeShort ((short) (1 << 11)); // this flag indicates UTF-8 filename encoding
575  target.writeShort ((! symbolicLink && compressionLevel > 0) ? (short) 8 : (short) 0); //symlink target path is not compressed
576  writeTimeAndDate (target, fileTime);
577  target.writeInt ((int) checksum);
578  target.writeInt ((int) (uint32) compressedSize);
579  target.writeInt ((int) (uint32) uncompressedSize);
580  target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1);
581  target.writeShort (0); // extra field length
582  }
583 
584  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item)
585 };
586 
587 //==============================================================================
590 
591 void ZipFile::Builder::addFile (const File& file, int compression, const String& path)
592 {
593  items.add (new Item (file, nullptr, compression,
594  path.isEmpty() ? file.getFileName() : path,
595  file.getLastModificationTime()));
596 }
597 
598 void ZipFile::Builder::addEntry (InputStream* stream, int compression, const String& path, Time time)
599 {
600  jassert (stream != nullptr); // must not be null!
601  jassert (path.isNotEmpty());
602  items.add (new Item ({}, stream, compression, path, time));
603 }
604 
605 bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progress) const
606 {
607  auto fileStart = target.getPosition();
608 
609  for (int i = 0; i < items.size(); ++i)
610  {
611  if (progress != nullptr)
612  *progress = (i + 0.5) / items.size();
613 
614  if (! items.getUnchecked (i)->writeData (target, fileStart))
615  return false;
616  }
617 
618  auto directoryStart = target.getPosition();
619 
620  for (auto* item : items)
621  if (! item->writeDirectoryEntry (target))
622  return false;
623 
624  auto directoryEnd = target.getPosition();
625 
626  target.writeInt (0x06054b50);
627  target.writeShort (0);
628  target.writeShort (0);
629  target.writeShort ((short) items.size());
630  target.writeShort ((short) items.size());
631  target.writeInt ((int) (directoryEnd - directoryStart));
632  target.writeInt ((int) (directoryStart - fileStart));
633  target.writeShort (0);
634 
635  if (progress != nullptr)
636  *progress = 1.0;
637 
638  return true;
639 }
640 
641 
642 //==============================================================================
643 //==============================================================================
644 #if JUCE_UNIT_TESTS
645 
646 struct ZIPTests : public UnitTest
647 {
648  ZIPTests()
649  : UnitTest ("ZIP", UnitTestCategories::compression)
650  {}
651 
652  void runTest() override
653  {
654  beginTest ("ZIP");
655 
656  ZipFile::Builder builder;
657  StringArray entryNames { "first", "second", "third" };
659 
660  for (auto& entryName : entryNames)
661  {
662  auto& block = blocks.getReference (entryName);
663  MemoryOutputStream mo (block, false);
664  mo << entryName;
665  mo.flush();
666  builder.addEntry (new MemoryInputStream (block, false), 9, entryName, Time::getCurrentTime());
667  }
668 
669  MemoryBlock data;
670  MemoryOutputStream mo (data, false);
671  builder.writeToStream (mo, nullptr);
672  MemoryInputStream mi (data, false);
673 
674  ZipFile zip (mi);
675 
676  expectEquals (zip.getNumEntries(), entryNames.size());
677 
678  for (auto& entryName : entryNames)
679  {
680  auto* entry = zip.getEntry (entryName);
681  std::unique_ptr<InputStream> input (zip.createStreamForEntry (*entry));
682  expectEquals (input->readEntireStreamAsString(), entryName);
683  }
684  }
685 };
686 
687 static ZIPTests zipTests;
688 
689 #endif
690 
691 } // namespace juce
String getFileName() const
Definition: juce_File.cpp:366
virtual int64 getTotalLength()=0
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
void clear(bool deleteObjects=true)
static Result ok() noexcept
Definition: juce_Result.h:61
InputStream * createStreamForEntry(int index)
bool createSymbolicLink(const File &linkFileToCreate, bool overwriteExisting) const
Definition: juce_File.cpp:998
bool isNotEmpty() const noexcept
Definition: juce_String.h:302
Time getLastModificationTime() const
Definition: juce_File.cpp:532
Result createDirectory() const
Definition: juce_File.cpp:513
Result uncompressEntry(int index, const File &targetDirectory, bool shouldOverwriteFiles=true)
void addFile(const File &fileToAdd, int compressionLevel, const String &storedPathName=String())
int getMonth() const noexcept
Definition: juce_Time.cpp:334
Result uncompressTo(const File &targetDirectory, bool shouldOverwriteFiles=true)
File getChildFile(StringRef relativeOrAbsolutePath) const
Definition: juce_File.cpp:414
FileInputStream * createInputStream() const
Definition: juce_File.cpp:729
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Definition: juce_Time.cpp:218
ObjectClass ** end() noexcept
int getNumEntries() const noexcept
static Result fail(const String &errorMessage) noexcept
Definition: juce_Result.cpp:65
void * getData() noexcept
void sortEntriesByFilename()
virtual bool writeShort(short value)
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
static JUCE_CONSTEXPR uint16 littleEndianShort(const void *bytes) noexcept
int size() const noexcept
void addEntry(InputStream *streamToRead, int compressionLevel, const String &storedPathName, Time fileModificationTime)
int getMinutes() const noexcept
Definition: juce_Time.cpp:339
ObjectClass * getUnchecked(int index) const noexcept
bool isEmpty() const noexcept
Definition: juce_String.h:296
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
ZipFile(const File &file)
int getDayOfMonth() const noexcept
Definition: juce_Time.cpp:336
int getSeconds() const noexcept
Definition: juce_Time.cpp:340
ObjectClass * add(ObjectClass *newObject)
virtual bool writeInt(int value)
CharPointer_UTF8 toUTF8() const
int getIndexOfFileName(const String &fileName, bool ignoreCase=false) const noexcept
int getHours() const noexcept
Definition: juce_Time.cpp:338
bool writeToStream(OutputStream &target, double *progress) const
ObjectClass ** begin() noexcept
bool failedToOpen() const noexcept
virtual int64 getPosition()=0
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
const ZipEntry * getEntry(int index) const noexcept
ValueType & getReference(KeyTypeParameter keyToLookFor)
Definition: juce_HashMap.h:185
virtual bool setPosition(int64 newPosition)=0
static juce_wchar getSeparatorChar()
int getYear() const noexcept
Definition: juce_Time.cpp:333
size_t sizeInBytes() const noexcept