zipVolume.cpp
Engine/source/core/util/zip/zipVolume.cpp
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