zipVolume.cpp

Engine/source/core/util/zip/zipVolume.cpp

More...

Classes:

Namespaces:

namespace

Detailed Description

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25#include "core/util/zip/zipVolume.h"
 26
 27#include "core/util/zip/zipSubStream.h"
 28#include "core/util/noncopyable.h"
 29#include "console/console.h"
 30
 31namespace Torque
 32{
 33   using namespace FS;
 34   using namespace Zip;
 35
 36class ZipFileNode : public Torque::FS::File, public Noncopyable
 37{
 38   typedef FileNode Parent;
 39
 40   //--------------------------------------------------------------------------
 41   // ZipFileNode class (Internal)
 42   //--------------------------------------------------------------------------
 43public:
 44   ZipFileNode(StrongRefPtr<ZipArchive>& archive, String zipFilename, Stream* zipStream, ZipArchive::ZipEntry* ze) 
 45   {
 46      mZipStream = zipStream;
 47      mArchive = archive;
 48      mZipFilename = zipFilename;
 49      mByteCount = dynamic_cast<IStreamByteCount*>(mZipStream);
 50      AssertFatal(mByteCount, "error, zip stream interface does not implement IStreamByteCount");
 51      mZipEntry = ze;
 52   }
 53   virtual ~ZipFileNode()
 54   {
 55      close();
 56   }
 57
 58   virtual Path   getName() const { return mZipFilename; }
 59   virtual NodeStatus getStatus() const
 60   { 
 61      if (mZipStream) 
 62      {
 63         // great, Stream Status is different from FileNode Status...
 64         switch (mZipStream->getStatus())
 65         {
 66         case Stream::Ok:
 67            return FileNode::Open;
 68         case Stream::EOS:
 69            return FileNode::EndOfFile;
 70         case Stream::IOError:
 71            return FileNode::UnknownError;
 72         default:
 73            return FileNode::UnknownError;
 74         }
 75      }
 76      else
 77         return FileNode::Closed;
 78   }
 79
 80   virtual bool   getAttributes(Attributes* attr) 
 81   { 
 82      if (!attr)
 83         return false;
 84
 85      attr->flags = FileNode::File | FileNode::Compressed | FileNode::ReadOnly;
 86      attr->name = mZipFilename;
 87      // use the mod time for both mod and access time, since we only have mod time in the CD
 88      attr->mtime = ZipArchive::DOSTimeToTime(mZipEntry->mCD.mModTime, mZipEntry->mCD.mModDate);
 89      attr->atime = ZipArchive::DOSTimeToTime(mZipEntry->mCD.mModTime, mZipEntry->mCD.mModDate);
 90      attr->size = mZipEntry->mCD.mUncompressedSize;
 91
 92      return true; 
 93   }
 94
 95   virtual U32 getPosition()
 96   {
 97      if (mZipStream)
 98         return mZipStream->getPosition();
 99      else
100         return 0;
101   }
102
103   virtual U32 setPosition(U32 pos, SeekMode mode)
104   {
105      if (!mZipStream || mode != Begin)
106         return 0;
107      else 
108         return mZipStream->setPosition(pos);
109   }
110
111   virtual bool open(AccessMode mode)
112   {
113      // stream is already open so just check to make sure that they are using a valid mode
114      if (mode == Read)
115         return mZipStream != NULL;
116      else 
117      {
118         Con::errorf("ZipFileSystem: Write access denied for file %s", mZipFilename.c_str());
119         return false;
120      }
121   }
122
123   virtual bool close()
124   {
125      if (mZipStream != NULL && mArchive != NULL)
126      {
127         mArchive->closeFile(mZipStream);
128         mZipStream = NULL;
129         mByteCount = NULL;
130      }
131      return true;
132   }
133
134   virtual U32 read(void* dst, U32 size)
135   {
136      if (mZipStream && mZipStream->read(size, dst) && mByteCount)
137         return mByteCount->getLastBytesRead();
138      else
139         return 0;
140   }
141
142   virtual U32 write(const void* src, U32 size)
143   {
144      if (mZipStream && mZipStream->write(size, src) && mByteCount)
145         return mByteCount->getLastBytesWritten();
146      else
147         return 0;
148   }
149
150   protected:
151      virtual U32    calculateChecksum()
152      {
153         // JMQ: implement
154         return 0;
155      };
156
157      Stream* mZipStream;
158      StrongRefPtr<ZipArchive> mArchive;
159      ZipArchive::ZipEntry* mZipEntry;
160      String mZipFilename;
161      IStreamByteCount* mByteCount;
162};
163
164//--------------------------------------------------------------------------
165// ZipDirectoryNode class (Internal)
166//--------------------------------------------------------------------------
167
168class ZipDirectoryNode : public Torque::FS::Directory, public Noncopyable
169{
170public:
171   ZipDirectoryNode(StrongRefPtr<ZipArchive>& archive, const Torque::Path& path, ZipArchive::ZipEntry* ze)
172   {
173      mPath = path;
174      mArchive = archive;
175      mZipEntry = ze;
176      if (mZipEntry)
177         mChildIter = mZipEntry->mChildren.end();
178   }
179   ~ZipDirectoryNode()
180   {
181   }
182
183   Torque::Path getName() const { return mPath; }
184
185   // getStatus() doesn't appear to be used for directories
186   NodeStatus getStatus() const
187   {
188      return FileNode::Open;
189   }
190
191   bool getAttributes(Attributes* attr)
192   {
193      if (!attr)
194         return false;
195
196      attr->flags = FileNode::Directory | FileNode::Compressed | FileNode::ReadOnly;
197      attr->name = mPath.getFullPath();
198      // use the mod time for both mod and access time, since we only have mod time in the CD
199      attr->mtime = ZipArchive::DOSTimeToTime(mZipEntry->mCD.mModTime, mZipEntry->mCD.mModDate);
200      attr->atime = ZipArchive::DOSTimeToTime(mZipEntry->mCD.mModTime, mZipEntry->mCD.mModDate);
201      attr->size = mZipEntry->mCD.mUncompressedSize;
202
203      return true; 
204   }
205
206   bool open()
207   {
208      // reset iterator
209      if (mZipEntry)
210         mChildIter = mZipEntry->mChildren.begin();
211      return (mZipEntry != NULL && mArchive.getPointer() != NULL);
212   }
213   bool close()
214   {
215      if (mZipEntry)
216         mChildIter = mZipEntry->mChildren.end();
217      return true;
218   }
219   bool read(Attributes* attr)
220   {
221      if (!attr)
222         return false;
223
224      if (mChildIter == mZipEntry->mChildren.end())
225         return false;
226
227      ZipArchive::ZipEntry* ze = (*mChildIter).value;
228
229      attr->flags = FileNode::Compressed;
230      if (ze->mIsDirectory)
231         attr->flags |= FileNode::Directory;
232      else
233         attr->flags |= FileNode::File;
234
235      attr->name = ze->mName;
236      attr->mtime = ZipArchive::DOSTimeToTime(ze->mCD.mModTime, ze->mCD.mModDate);
237      attr->atime = ZipArchive::DOSTimeToTime(ze->mCD.mModTime, ze->mCD.mModDate);
238      attr->size = 0; // we don't know the size until we open a stream, so we'll have to use size 0
239
240      mChildIter++;
241
242      return true;
243   }
244
245private:
246   U32 calculateChecksum() 
247   {
248      return 0;
249   }
250
251   Torque::Path mPath;
252   Map<String,ZipArchive::ZipEntry*>::Iterator mChildIter;
253   StrongRefPtr<ZipArchive> mArchive;
254   ZipArchive::ZipEntry* mZipEntry;
255};
256
257//--------------------------------------------------------------------------
258// ZipFakeRootNode class (Internal)
259//--------------------------------------------------------------------------
260
261class ZipFakeRootNode : public Torque::FS::Directory, public Noncopyable
262{
263public:
264   ZipFakeRootNode(StrongRefPtr<ZipArchive>& archive, const Torque::Path& path, const String &fakeRoot)
265   {
266      mPath = path;
267      mArchive = archive;
268      mRead = false;
269      mFakeRoot = fakeRoot;
270   }
271   ~ZipFakeRootNode()
272   {
273   }
274
275   Torque::Path getName() const { return mPath; }
276
277   // getStatus() doesn't appear to be used for directories
278   NodeStatus getStatus() const
279   {
280      return FileNode::Open;
281   }
282
283   bool getAttributes(Attributes* attr)
284   {
285      if (!attr)
286         return false;
287
288      attr->flags = FileNode::Directory | FileNode::Compressed | FileNode::ReadOnly;
289      attr->name = mPath.getFullPath();
290      // use the mod time for both mod and access time, since we only have mod time in the CD
291      
292      ZipArchive::ZipEntry* zipEntry = mArchive->getRoot();
293      attr->mtime = ZipArchive::DOSTimeToTime(zipEntry->mCD.mModTime, zipEntry->mCD.mModDate);
294      attr->atime = ZipArchive::DOSTimeToTime(zipEntry->mCD.mModTime, zipEntry->mCD.mModDate);
295      attr->size = zipEntry->mCD.mUncompressedSize;
296
297      return true; 
298   }
299
300   bool open()
301   {
302      mRead = false;
303      return (mArchive.getPointer() != NULL);
304   }
305   bool close()
306   {
307      mRead = false;
308      return true;
309   }
310   bool read(Attributes* attr)
311   {
312      if (!attr)
313         return false;
314
315      if (mRead)
316         return false;
317
318      ZipArchive::ZipEntry* ze = mArchive->getRoot();
319
320      attr->flags = FileNode::Compressed;
321      if (ze->mIsDirectory)
322         attr->flags |= FileNode::Directory;
323      else
324         attr->flags |= FileNode::File;
325
326      attr->name = mFakeRoot;
327      attr->mtime = ZipArchive::DOSTimeToTime(ze->mCD.mModTime, ze->mCD.mModDate);
328      attr->atime = ZipArchive::DOSTimeToTime(ze->mCD.mModTime, ze->mCD.mModDate);
329      attr->size = 0; // we don't know the size until we open a stream, so we'll have to use size 0
330
331      mRead = true;
332
333      return true;
334   }
335
336private:
337   U32 calculateChecksum() 
338   {
339      return 0;
340   }
341
342   Torque::Path mPath;
343   Map<String,ZipArchive::ZipEntry*>::Iterator mChildIter;
344   StrongRefPtr<ZipArchive> mArchive;
345   bool mRead;
346   String mFakeRoot;
347};
348
349//--------------------------------------------------------------------------
350// ZipFileSystem
351//--------------------------------------------------------------------------
352
353ZipFileSystem::ZipFileSystem(String& zipFilename, bool zipNameIsDir /* = false */)
354{
355   mZipFilename = zipFilename;
356   mInitted = false;
357   mZipNameIsDir = zipNameIsDir;
358   if(mZipNameIsDir)
359   {
360      Path path(zipFilename);
361      mFakeRoot = Path::Join(path.getPath(), '/', path.getFileName());
362   }
363
364   // open the file now but don't read it yet, since we want construction to be lightweight
365   // we open the file now so that whatever filesystems are mounted right now (which may be temporary)
366   // can be umounted without affecting this file system.
367   mZipArchiveStream = new FileStream();
368   mZipArchiveStream->open(mZipFilename, Torque::FS::File::Read);
369   
370   // As far as the mount system is concerned, ZFSes are read only write now (even though 
371   // ZipArchive technically support read-write, we don't expose this to the mount system because we 
372   // don't want to support that as a standard run case right now.)
373   mReadOnly = true;
374}
375
376ZipFileSystem::~ZipFileSystem()
377{
378   if (mZipArchiveStream)
379   {
380      mZipArchiveStream->close();
381      delete mZipArchiveStream;
382   }
383   mZipArchive = NULL;
384}
385
386FileNodeRef ZipFileSystem::resolve(const Path& path)
387{
388   if (!mInitted)
389      _init();
390
391   if (mZipArchive.isNull())
392      return NULL;
393
394   // eat leading "/"
395   String name = path.getFullPathWithoutRoot();
396   if (name.find("/") == 0)
397      name = name.substr(1, name.length() - 1);
398
399   if(name.isEmpty() && mZipNameIsDir)
400      return new ZipFakeRootNode(mZipArchive, path, mFakeRoot);
401
402   if(mZipNameIsDir)
403   {
404      // Remove the fake root from the name so things can be found
405      if(name.find(mFakeRoot) == 0)
406         name = name.substr(mFakeRoot.length());
407
408#ifdef TORQUE_DISABLE_FIND_ROOT_WITHIN_ZIP
409      else
410         // If a zip file's name isn't the root of the path we're looking for
411         // then do not continue.  Otherwise, we'll continue to look for the
412         // path's root within the zip file itself.  i.e. we're looking for the
413         // path "scripts/test.tscript".  If the zip file itself isn't called scripts.zip
414         // then we won't look within the archive for a "scripts" directory.
415         return NULL;
416#endif
417
418      if (name.find("/") == 0)
419         name = name.substr(1, name.length() - 1);
420   }
421
422   // first check to see if input path is a directory
423   // check for request of root directory
424   if (name.isEmpty())
425   {
426      ZipDirectoryNode* zdn = new ZipDirectoryNode(mZipArchive, path, mZipArchive->getRoot());
427      return zdn;
428   }
429
430   ZipArchive::ZipEntry* ze = mZipArchive->findZipEntry(name);
431   if (ze == NULL)
432      return NULL;
433
434   if (ze->mIsDirectory)
435   {
436      ZipDirectoryNode* zdn = new ZipDirectoryNode(mZipArchive, path, ze);
437      return zdn;
438   }
439
440   // pass in the zip entry so that openFile() doesn't need to look it up again.
441   Stream* stream = mZipArchive->openFile(name, ze, ZipArchive::Read);
442   if (stream == NULL)
443      return NULL;
444
445   ZipFileNode* zfn = new ZipFileNode(mZipArchive, name, stream, ze);
446   return zfn;
447}
448
449void ZipFileSystem::_init()
450{
451   if (mInitted)
452      return;
453   mInitted = true;
454
455   if (!mZipArchive.isNull())
456      return;
457   if (mZipArchiveStream->getStatus() != Stream::Ok)
458      return;
459
460   mZipArchive = new ZipArchive();
461   if (!mZipArchive->openArchive(mZipArchiveStream, ZipArchive::Read))
462   {
463      Con::errorf("ZipFileSystem: failed to open zip archive %s", mZipFilename.c_str());
464      return;
465   }
466
467   // tell the archive that it owns the zipStream now
468   mZipArchive->setDiskStream(mZipArchiveStream);
469   // and null it out because we don't own it anymore
470   mZipArchiveStream = NULL;
471
472   // for debugging
473   //mZipArchive->dumpCentralDirectory();
474}
475
476};
477