diff --git a/direct/src/plugin/Sources.pp b/direct/src/plugin/Sources.pp index 92e66cd358..f9efe6ce34 100644 --- a/direct/src/plugin/Sources.pp +++ b/direct/src/plugin/Sources.pp @@ -18,6 +18,7 @@ p3dFileDownload.h p3dFileDownload.I \ p3dInstance.h p3dInstance.I \ p3dInstanceManager.h p3dInstanceManager.I \ + p3dMultifileReader.h p3dMultifileReader.I \ p3dPackage.h p3dPackage.I \ p3dSession.h p3dSession.I @@ -27,6 +28,7 @@ p3dFileDownload.cxx \ p3dInstance.cxx \ p3dInstanceManager.cxx \ + p3dMultifileReader.cxx \ p3dPackage.cxx \ p3dSession.cxx diff --git a/direct/src/plugin/make_package.py b/direct/src/plugin/make_package.py index 6bc1370aa8..5e35bcf1e0 100755 --- a/direct/src/plugin/make_package.py +++ b/direct/src/plugin/make_package.py @@ -27,10 +27,7 @@ Options: import sys import getopt import os -import tarfile -import gzip -import stat -import md5 +import zlib import direct from pandac.PandaModules import * @@ -42,25 +39,15 @@ class FileSpec: def __init__(self, filename, pathname): self.filename = filename self.pathname = pathname - self.size = 0 - self.timestamp = 0 - self.hash = None - s = os.stat(self.pathname) - self.size = s[stat.ST_SIZE] - self.timestamp = s[stat.ST_MTIME] + self.size = pathname.getFileSize() + self.timestamp = pathname.getTimestamp() - m = md5.new() - f = open(self.pathname, 'rb') - data = f.read(4096) - while data: - m.update(data) - data = f.read(4096) - f.close() + hv = HashVal() + hv.hashFile(pathname) + self.hash = hv.asHex() - self.hash = m.hexdigest() - - def get_params(self): + def getParams(self): return 'filename="%s" size=%s timestamp=%s hash="%s"' % ( self.filename, self.size, self.timestamp, self.hash) @@ -85,9 +72,11 @@ class PackageMaker: self.cleanDir(self.stageDir) - uncompressedArchiveBasename = '%s.tar' % (self.packageFullname) - uncompressedArchivePathname = os.path.join(self.stageDir, uncompressedArchiveBasename) - self.archive = tarfile.open(uncompressedArchivePathname, 'w') + uncompressedArchiveBasename = '%s.mf' % (self.packageFullname) + uncompressedArchivePathname = Filename(self.stageDir, uncompressedArchiveBasename) + self.archive = Multifile() + if not self.archive.openWrite(uncompressedArchivePathname): + raise IOError, "Couldn't open %s for writing" % (uncompressedArchivePathname) self.components = [] @@ -96,34 +85,37 @@ class PackageMaker: uncompressedArchive = FileSpec(uncompressedArchiveBasename, uncompressedArchivePathname) - compressedArchiveBasename = '%s.tgz' % (self.packageFullname) - compressedArchivePathname = os.path.join(self.stageDir, compressedArchiveBasename) + compressedArchiveBasename = '%s.mf.pz' % (self.packageFullname) + compressedArchivePathname = Filename(self.stageDir, compressedArchiveBasename) print "\ncompressing" - f = open(uncompressedArchivePathname, 'rb') - gz = gzip.open(compressedArchivePathname, 'w', 9) - data = f.read(4096) + + source = open(uncompressedArchivePathname.toOsSpecific(), 'rb') + target = open(compressedArchivePathname.toOsSpecific(), 'w') + z = zlib.compressobj(9) + data = source.read(4096) while data: - gz.write(data) - data = f.read(4096) - gz.close() - f.close() + target.write(z.compress(data)) + data = source.read(4096) + target.write(z.flush()) + target.close() + source.close() compressedArchive = FileSpec(compressedArchiveBasename, compressedArchivePathname) - os.unlink(uncompressedArchivePathname) + uncompressedArchivePathname.unlink() descFileBasename = '%s.xml' % (self.packageFullname) - descFilePathname = os.path.join(self.stageDir, descFileBasename) + descFilePathname = Filename(self.stageDir, descFileBasename) - f = open(descFilePathname, 'w') + f = open(descFilePathname.toOsSpecific(), 'w') print >> f, '' print >> f, '' print >> f, '' % (self.packageName, self.packageVersion) - print >> f, ' ' % (uncompressedArchive.get_params()) - print >> f, ' ' % (compressedArchive.get_params()) + print >> f, ' ' % (uncompressedArchive.getParams()) + print >> f, ' ' % (compressedArchive.getParams()) for file in self.components: - print >> f, ' ' % (file.get_params()) + print >> f, ' ' % (file.getParams()) print >> f, '' f.close() @@ -132,46 +124,41 @@ class PackageMaker: """ Remove all the files in the named directory. Does not operate recursively. """ - for filename in os.listdir(dirname): - pathname = os.path.join(dirname, filename) - try: - os.unlink(pathname) - except OSError: - pass + for filename in os.listdir(dirname.toOsSpecific()): + pathname = Filename(dirname, filename) + pathname.unlink() def addComponents(self): """ Walks through all the files in the start directory and adds them to the archive. Recursively visits sub-directories. """ - startDir = self.startDir + startDir = self.startDir.toOsSpecific() if startDir.endswith(os.sep): startDir = startDir[:-1] - elif os.altsep and startDir.endswith(os.altsep): - startDir = startDir[:-1] prefix = startDir + os.sep for dirpath, dirnames, filenames in os.walk(startDir): if dirpath == startDir: localpath = '' else: assert dirpath.startswith(prefix) - localpath = dirpath[len(prefix):] + localpath = dirpath[len(prefix):] + '/' for basename in filenames: - file = FileSpec(os.path.join(localpath, basename), - os.path.join(startDir, basename)) + file = FileSpec(localpath + basename, + Filename(self.startDir, basename)) print file.filename self.components.append(file) - self.archive.add(file.pathname, file.filename, recursive = False) + self.archive.addSubfile(file.filename, file.pathname, 0) def makePackage(args): opts, args = getopt.getopt(args, 'd:p:v:h') pm = PackageMaker() - pm.startDir = '.' + pm.startDir = Filename('.') for option, value in opts: if option == '-d': - pm.stageDir = Filename.fromOsSpecific(value).toOsSpecific() + pm.stageDir = Filename.fromOsSpecific(value) elif option == '-p': pm.packageName = value elif option == '-v': diff --git a/direct/src/plugin/p3dMultifileReader.I b/direct/src/plugin/p3dMultifileReader.I new file mode 100644 index 0000000000..9552da3063 --- /dev/null +++ b/direct/src/plugin/p3dMultifileReader.I @@ -0,0 +1,40 @@ +// Filename: p3dMultifileReader.I +// Created by: drose (15Jun09) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: P3DMultifileReader::read_uint16 +// Access: Private +// Description: Extracts an unsigned short from the file. +//////////////////////////////////////////////////////////////////// +inline unsigned int P3DMultifileReader:: +read_uint16() { + unsigned int a = _in.get(); + unsigned int b = _in.get(); + return (b << 8) | a; +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DMultifileReader::read_uint32 +// Access: Private +// Description: Extracts an unsigned long from the file. +//////////////////////////////////////////////////////////////////// +inline unsigned int P3DMultifileReader:: +read_uint32() { + unsigned int a = _in.get(); + unsigned int b = _in.get(); + unsigned int c = _in.get(); + unsigned int d = _in.get(); + return (d << 24) | (c << 16) | (b << 8) | a; +} diff --git a/direct/src/plugin/p3dMultifileReader.cxx b/direct/src/plugin/p3dMultifileReader.cxx new file mode 100644 index 0000000000..3b09f5bc6e --- /dev/null +++ b/direct/src/plugin/p3dMultifileReader.cxx @@ -0,0 +1,162 @@ +// Filename: p3dMultifileReader.cxx +// Created by: drose (15Jun09) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "p3dMultifileReader.h" + +// This sequence of bytes begins each Multifile to identify it as a +// Multifile. +const char P3DMultifileReader::_header[] = "pmf\0\n\r"; +const size_t P3DMultifileReader::_header_size = 6; + +const int P3DMultifileReader::_current_major_ver = 1; +const int P3DMultifileReader::_current_minor_ver = 1; + +//////////////////////////////////////////////////////////////////// +// Function: P3DMultifileReader::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +P3DMultifileReader:: +P3DMultifileReader() { +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DMultifileReader::extract +// Access: Public +// Description: Reads the named multifile, and extracts all files +// within it to the indicated directory. Returns true +// on success, false on failure. +//////////////////////////////////////////////////////////////////// +bool P3DMultifileReader:: +extract(const string &pathname, const string &to_dir) { + _subfiles.clear(); + + _in.open(pathname.c_str(), ios::in | ios::binary); + if (!_in) { + cerr << "Couldn't open " << pathname << "\n"; + return false; + } + + for (size_t i = 0; i < _header_size; ++i) { + int ch = _in.get(); + if (ch != _header[i]) { + cerr << "Failed header check: " << pathname << "\n"; + return false; + } + } + + unsigned int major = read_uint16(); + unsigned int minor = read_uint16(); + if (major != _current_major_ver || minor != _current_minor_ver) { + cerr << "Incompatible multifile version: " << pathname << "\n"; + return false; + } + + unsigned int scale = read_uint32(); + if (scale != 1) { + cerr << "Unsupported scale factor in " << pathname << "\n"; + return false; + } + + // We don't care about the timestamp. + read_uint32(); + + if (!read_index()) { + cerr << "Error reading multifile index\n"; + return false; + } + + // Now walk through all of the files. + Subfiles::iterator si; + for (si = _subfiles.begin(); si != _subfiles.end(); ++si) { + const Subfile &s = (*si); + cerr << s._filename << "\n"; + + string output_pathname = to_dir + "/" + s._filename; + ofstream out(output_pathname.c_str(), ios::out | ios::trunc | ios::binary); + if (!out) { + cerr << "Unable to create " << output_pathname << "\n"; + return false; + } + + _in.seekg(s._start); + + static const size_t buffer_size = 1024; + char buffer[buffer_size]; + + size_t remaining_data = s._length; + _in.read(buffer, min(buffer_size, remaining_data)); + size_t count = _in.gcount(); + while (count != 0) { + remaining_data -= count; + out.write(buffer, count); + _in.read(buffer, min(buffer_size, remaining_data)); + count = _in.gcount(); + } + + if (remaining_data != 0) { + cerr << "Unable to extract " << s._filename << "\n"; + return false; + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DMultifileReader::read_index +// Access: Public +// Description: Assuming the file stream is positioned at the first +// record, reads all of the records into the _subfiles +// list. Returns true on success, false on failure. +//////////////////////////////////////////////////////////////////// +bool P3DMultifileReader:: +read_index() { + unsigned int next_entry = read_uint32(); + if (!_in) { + return false; + } + while (next_entry != 0) { + Subfile s; + s._start = read_uint32(); + s._length = read_uint32(); + unsigned int flags = read_uint16(); + if (flags != 0) { + cerr << "Unsupported per-subfile options in multifile\n"; + return false; + } + read_uint32(); + size_t name_length = read_uint16(); + char *buffer = new char[name_length]; + _in.read(buffer, name_length); + + // The filenames are xored with 0xff just for fun. + for (size_t ni = 0; ni < name_length; ++ni) { + buffer[ni] ^= 0xff; + } + + s._filename = string(buffer, name_length); + delete[] buffer; + + _subfiles.push_back(s); + + _in.seekg(next_entry); + next_entry = read_uint32(); + if (!_in) { + return false; + } + } + + return true; +} diff --git a/direct/src/plugin/p3dMultifileReader.h b/direct/src/plugin/p3dMultifileReader.h new file mode 100644 index 0000000000..d8efd15ab5 --- /dev/null +++ b/direct/src/plugin/p3dMultifileReader.h @@ -0,0 +1,59 @@ +// Filename: p3dMultifileReader.h +// Created by: drose (15Jun09) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef P3DMULTIFILEREADER_H +#define P3DMULTIFILEREADER_H + +#include "p3d_plugin_common.h" + +//////////////////////////////////////////////////////////////////// +// Class : P3DMultifileReader +// Description : A way-simple implementation of Panda's multifile +// reader. See panda/src/express/multifile.cxx for a +// full description of the binary format. This +// implementation doesn't support per-subfile +// compression or encryption. +//////////////////////////////////////////////////////////////////// +class P3DMultifileReader { +public: + P3DMultifileReader(); + + bool extract(const string &pathname, const string &to_dir); + +private: + bool read_index(); + inline unsigned int read_uint16(); + inline unsigned int read_uint32(); + + ifstream _in; + + class Subfile { + public: + string _filename; + size_t _start; + size_t _length; + }; + + typedef vector Subfiles; + Subfiles _subfiles; + + static const char _header[]; + static const size_t _header_size; + static const int _current_major_ver; + static const int _current_minor_ver; +}; + +#include "p3dMultifileReader.I" + +#endif diff --git a/direct/src/plugin/p3dPackage.I b/direct/src/plugin/p3dPackage.I index 379b853251..96aec4d1d0 100755 --- a/direct/src/plugin/p3dPackage.I +++ b/direct/src/plugin/p3dPackage.I @@ -47,6 +47,26 @@ get_package_dir() const { return _package_dir; } +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::get_package_name +// Access: Public +// Description: Returns the name of this package. +//////////////////////////////////////////////////////////////////// +inline const string &P3DPackage:: +get_package_name() const { + return _package_name; +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::get_package_version +// Access: Public +// Description: Returns the version string of this package. +//////////////////////////////////////////////////////////////////// +inline const string &P3DPackage:: +get_package_version() const { + return _package_version; +} + //////////////////////////////////////////////////////////////////// // Function: P3DPackage::decode_hexdigit // Access: Private diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index 91d535c14b..2a4b5a516f 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/direct/src/plugin/p3dPackage.cxx @@ -14,10 +14,10 @@ #include "p3dPackage.h" #include "p3dInstanceManager.h" +#include "p3dMultifileReader.h" #include "openssl/md5.h" #include "zlib.h" -#include "libtar.h" #include #include @@ -301,8 +301,8 @@ uncompress_archive() { string source_pathname = _package_dir + "/" + _compressed_archive._filename; string target_pathname = _package_dir + "/" + _uncompressed_archive._filename; - gzFile source = gzopen(source_pathname.c_str(), "rb"); - if (source == NULL) { + ifstream source(source_pathname.c_str(), ios::in | ios::binary); + if (!source) { cerr << "Couldn't open " << source_pathname << "\n"; report_done(false); return; @@ -312,38 +312,85 @@ uncompress_archive() { if (!target) { cerr << "Couldn't write to " << target_pathname << "\n"; report_done(false); + return; } - static const int buffer_size = 1024; - char buffer[buffer_size]; + z_stream z; + z.next_in = Z_NULL; + z.avail_in = 0; + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + z.msg = (char *)"no error message"; - int count = gzread(source, buffer, buffer_size); - while (count > 0) { - target.write(buffer, count); - count = gzread(source, buffer, buffer_size); + int result = inflateInit(&z); + if (result < 0) { + cerr << z.msg << "\n"; + report_done(false); + return; + } + + static const int decompress_buffer_size = 1024; + char decompress_buffer[decompress_buffer_size]; + static const int write_buffer_size = 1024; + char write_buffer[write_buffer_size]; + + bool eof = false; + int flush = 0; + + while (true) { + if (z.avail_in == 0 && !eof) { + source.read(decompress_buffer, decompress_buffer_size); + size_t read_count = source.gcount(); + eof = (read_count == 0 || source.eof() || source.fail()); + + z.next_in = (Bytef *)decompress_buffer; + z.avail_in = read_count; + } + + z.next_out = (Bytef *)write_buffer; + z.avail_out = write_buffer_size; + int result = inflate(&z, flush); + if (z.avail_out < write_buffer_size) { + target.write(write_buffer, write_buffer_size - z.avail_out); + if (!target) { + cerr << "Couldn't write entire file to " << target_pathname << "\n"; + report_done(false); + return; + } + } + + if (result == Z_STREAM_END) { + // Here's the end of the file. + break; + + } else if (result == Z_BUF_ERROR && flush == 0) { + // We might get this if no progress is possible, for instance if + // the input stream is truncated. In this case, tell zlib to + // dump everything it's got. + flush = Z_FINISH; + + } else if (result < 0) { + cerr << z.msg << "\n"; + inflateEnd(&z); + report_done(false); + return; + } } - if (count < 0) { - cerr << "gzip error decompressing " << source_pathname << "\n"; - int errnum; - cerr << gzerror(source, &errnum) << "\n"; - gzclose(source); - report_done(false); - return; - } - - gzclose(source); - - if (!target) { - cerr << "Couldn't write entire file to " << target_pathname << "\n"; + result = inflateEnd(&z); + if (result < 0) { + cerr << z.msg << "\n"; report_done(false); return; } + source.close(); target.close(); if (!_uncompressed_archive.full_verify(_package_dir)) { - cerr << "after uncompressing " << target_pathname << ", failed hash check\n"; + cerr << "after uncompressing " << target_pathname + << ", failed hash check\n"; report_done(false); return; } @@ -364,25 +411,14 @@ extract_archive() { cerr << "extracting " << _uncompressed_archive._filename << "\n"; string source_pathname = _package_dir + "/" + _uncompressed_archive._filename; - - TAR *tar = NULL; - int result = tar_open - (&tar, (char *)source_pathname.c_str(), NULL, O_RDONLY, 0666, TAR_VERBOSE); - if (result != 0) { - cerr << "Unable to open " << source_pathname << "\n"; + P3DMultifileReader reader; + if (!reader.extract(source_pathname, _package_dir)) { + cerr << "Failure extracting " << _uncompressed_archive._filename + << "\n"; report_done(false); return; } - while (th_read(tar) == 0) { - string basename = th_get_pathname(tar); - cerr << basename << "\n"; - string pathname = _package_dir + "/" + basename; - tar_extract_file(tar, (char *)pathname.c_str()); - } - - tar_close(tar); - cerr << "done extracting\n"; report_done(true); } diff --git a/direct/src/plugin/p3dPackage.h b/direct/src/plugin/p3dPackage.h index 9832d869d6..93b14f373e 100755 --- a/direct/src/plugin/p3dPackage.h +++ b/direct/src/plugin/p3dPackage.h @@ -43,6 +43,8 @@ public: inline bool get_ready() const; inline bool get_failed() const; inline const string &get_package_dir() const; + inline const string &get_package_name() const; + inline const string &get_package_version() const; void set_callback(Callback *callback); void cancel_callback(Callback *callback); diff --git a/direct/src/plugin/p3dSession.cxx b/direct/src/plugin/p3dSession.cxx index a1e72af918..2e49a4d29c 100644 --- a/direct/src/plugin/p3dSession.cxx +++ b/direct/src/plugin/p3dSession.cxx @@ -649,7 +649,12 @@ package_ready(P3DPackage *package, bool success) { if (this == _session->_panda3d_callback) { _session->_panda3d_callback = NULL; if (package == _session->_panda3d) { - _session->start_p3dpython(); + if (success) { + _session->start_p3dpython(); + } else { + cerr << "Failed to install " << package->get_package_name() + << "_" << package->get_package_version() << "\n"; + } } else { cerr << "Unexpected panda3d package: " << package << "\n"; } diff --git a/direct/src/plugin/p3d_plugin_composite1.cxx b/direct/src/plugin/p3d_plugin_composite1.cxx index 3ff57ea1e3..950714b314 100644 --- a/direct/src/plugin/p3d_plugin_composite1.cxx +++ b/direct/src/plugin/p3d_plugin_composite1.cxx @@ -3,5 +3,6 @@ #include "p3dFileDownload.cxx" #include "p3dInstance.cxx" #include "p3dInstanceManager.cxx" +#include "p3dMultifileReader.cxx" #include "p3dPackage.cxx" #include "p3dSession.cxx"