use multifiles instead of tarfiles

This commit is contained in:
David Rose 2009-06-16 05:30:00 +00:00
parent 9e7337557a
commit 7d2a154bf5
10 changed files with 406 additions and 92 deletions

View File

@ -18,6 +18,7 @@
p3dFileDownload.h p3dFileDownload.I \ p3dFileDownload.h p3dFileDownload.I \
p3dInstance.h p3dInstance.I \ p3dInstance.h p3dInstance.I \
p3dInstanceManager.h p3dInstanceManager.I \ p3dInstanceManager.h p3dInstanceManager.I \
p3dMultifileReader.h p3dMultifileReader.I \
p3dPackage.h p3dPackage.I \ p3dPackage.h p3dPackage.I \
p3dSession.h p3dSession.I p3dSession.h p3dSession.I
@ -27,6 +28,7 @@
p3dFileDownload.cxx \ p3dFileDownload.cxx \
p3dInstance.cxx \ p3dInstance.cxx \
p3dInstanceManager.cxx \ p3dInstanceManager.cxx \
p3dMultifileReader.cxx \
p3dPackage.cxx \ p3dPackage.cxx \
p3dSession.cxx p3dSession.cxx

View File

@ -27,10 +27,7 @@ Options:
import sys import sys
import getopt import getopt
import os import os
import tarfile import zlib
import gzip
import stat
import md5
import direct import direct
from pandac.PandaModules import * from pandac.PandaModules import *
@ -42,25 +39,15 @@ class FileSpec:
def __init__(self, filename, pathname): def __init__(self, filename, pathname):
self.filename = filename self.filename = filename
self.pathname = pathname self.pathname = pathname
self.size = 0
self.timestamp = 0
self.hash = None
s = os.stat(self.pathname) self.size = pathname.getFileSize()
self.size = s[stat.ST_SIZE] self.timestamp = pathname.getTimestamp()
self.timestamp = s[stat.ST_MTIME]
m = md5.new() hv = HashVal()
f = open(self.pathname, 'rb') hv.hashFile(pathname)
data = f.read(4096) self.hash = hv.asHex()
while data:
m.update(data)
data = f.read(4096)
f.close()
self.hash = m.hexdigest() def getParams(self):
def get_params(self):
return 'filename="%s" size=%s timestamp=%s hash="%s"' % ( return 'filename="%s" size=%s timestamp=%s hash="%s"' % (
self.filename, self.size, self.timestamp, self.hash) self.filename, self.size, self.timestamp, self.hash)
@ -85,9 +72,11 @@ class PackageMaker:
self.cleanDir(self.stageDir) self.cleanDir(self.stageDir)
uncompressedArchiveBasename = '%s.tar' % (self.packageFullname) uncompressedArchiveBasename = '%s.mf' % (self.packageFullname)
uncompressedArchivePathname = os.path.join(self.stageDir, uncompressedArchiveBasename) uncompressedArchivePathname = Filename(self.stageDir, uncompressedArchiveBasename)
self.archive = tarfile.open(uncompressedArchivePathname, 'w') self.archive = Multifile()
if not self.archive.openWrite(uncompressedArchivePathname):
raise IOError, "Couldn't open %s for writing" % (uncompressedArchivePathname)
self.components = [] self.components = []
@ -96,34 +85,37 @@ class PackageMaker:
uncompressedArchive = FileSpec(uncompressedArchiveBasename, uncompressedArchivePathname) uncompressedArchive = FileSpec(uncompressedArchiveBasename, uncompressedArchivePathname)
compressedArchiveBasename = '%s.tgz' % (self.packageFullname) compressedArchiveBasename = '%s.mf.pz' % (self.packageFullname)
compressedArchivePathname = os.path.join(self.stageDir, compressedArchiveBasename) compressedArchivePathname = Filename(self.stageDir, compressedArchiveBasename)
print "\ncompressing" print "\ncompressing"
f = open(uncompressedArchivePathname, 'rb')
gz = gzip.open(compressedArchivePathname, 'w', 9) source = open(uncompressedArchivePathname.toOsSpecific(), 'rb')
data = f.read(4096) target = open(compressedArchivePathname.toOsSpecific(), 'w')
z = zlib.compressobj(9)
data = source.read(4096)
while data: while data:
gz.write(data) target.write(z.compress(data))
data = f.read(4096) data = source.read(4096)
gz.close() target.write(z.flush())
f.close() target.close()
source.close()
compressedArchive = FileSpec(compressedArchiveBasename, compressedArchivePathname) compressedArchive = FileSpec(compressedArchiveBasename, compressedArchivePathname)
os.unlink(uncompressedArchivePathname) uncompressedArchivePathname.unlink()
descFileBasename = '%s.xml' % (self.packageFullname) 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, '<?xml version="1.0" ?>' print >> f, '<?xml version="1.0" ?>'
print >> f, '' print >> f, ''
print >> f, '<package name="%s" version="%s">' % (self.packageName, self.packageVersion) print >> f, '<package name="%s" version="%s">' % (self.packageName, self.packageVersion)
print >> f, ' <uncompressed_archive %s />' % (uncompressedArchive.get_params()) print >> f, ' <uncompressed_archive %s />' % (uncompressedArchive.getParams())
print >> f, ' <compressed_archive %s />' % (compressedArchive.get_params()) print >> f, ' <compressed_archive %s />' % (compressedArchive.getParams())
for file in self.components: for file in self.components:
print >> f, ' <component %s />' % (file.get_params()) print >> f, ' <component %s />' % (file.getParams())
print >> f, '</package>' print >> f, '</package>'
f.close() f.close()
@ -132,46 +124,41 @@ class PackageMaker:
""" Remove all the files in the named directory. Does not """ Remove all the files in the named directory. Does not
operate recursively. """ operate recursively. """
for filename in os.listdir(dirname): for filename in os.listdir(dirname.toOsSpecific()):
pathname = os.path.join(dirname, filename) pathname = Filename(dirname, filename)
try: pathname.unlink()
os.unlink(pathname)
except OSError:
pass
def addComponents(self): def addComponents(self):
""" Walks through all the files in the start directory and """ Walks through all the files in the start directory and
adds them to the archive. Recursively visits adds them to the archive. Recursively visits
sub-directories. """ sub-directories. """
startDir = self.startDir startDir = self.startDir.toOsSpecific()
if startDir.endswith(os.sep): if startDir.endswith(os.sep):
startDir = startDir[:-1] startDir = startDir[:-1]
elif os.altsep and startDir.endswith(os.altsep):
startDir = startDir[:-1]
prefix = startDir + os.sep prefix = startDir + os.sep
for dirpath, dirnames, filenames in os.walk(startDir): for dirpath, dirnames, filenames in os.walk(startDir):
if dirpath == startDir: if dirpath == startDir:
localpath = '' localpath = ''
else: else:
assert dirpath.startswith(prefix) assert dirpath.startswith(prefix)
localpath = dirpath[len(prefix):] localpath = dirpath[len(prefix):] + '/'
for basename in filenames: for basename in filenames:
file = FileSpec(os.path.join(localpath, basename), file = FileSpec(localpath + basename,
os.path.join(startDir, basename)) Filename(self.startDir, basename))
print file.filename print file.filename
self.components.append(file) self.components.append(file)
self.archive.add(file.pathname, file.filename, recursive = False) self.archive.addSubfile(file.filename, file.pathname, 0)
def makePackage(args): def makePackage(args):
opts, args = getopt.getopt(args, 'd:p:v:h') opts, args = getopt.getopt(args, 'd:p:v:h')
pm = PackageMaker() pm = PackageMaker()
pm.startDir = '.' pm.startDir = Filename('.')
for option, value in opts: for option, value in opts:
if option == '-d': if option == '-d':
pm.stageDir = Filename.fromOsSpecific(value).toOsSpecific() pm.stageDir = Filename.fromOsSpecific(value)
elif option == '-p': elif option == '-p':
pm.packageName = value pm.packageName = value
elif option == '-v': elif option == '-v':

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<Subfile> 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

View File

@ -47,6 +47,26 @@ get_package_dir() const {
return _package_dir; 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 // Function: P3DPackage::decode_hexdigit
// Access: Private // Access: Private

View File

@ -14,10 +14,10 @@
#include "p3dPackage.h" #include "p3dPackage.h"
#include "p3dInstanceManager.h" #include "p3dInstanceManager.h"
#include "p3dMultifileReader.h"
#include "openssl/md5.h" #include "openssl/md5.h"
#include "zlib.h" #include "zlib.h"
#include "libtar.h"
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
@ -301,8 +301,8 @@ uncompress_archive() {
string source_pathname = _package_dir + "/" + _compressed_archive._filename; string source_pathname = _package_dir + "/" + _compressed_archive._filename;
string target_pathname = _package_dir + "/" + _uncompressed_archive._filename; string target_pathname = _package_dir + "/" + _uncompressed_archive._filename;
gzFile source = gzopen(source_pathname.c_str(), "rb"); ifstream source(source_pathname.c_str(), ios::in | ios::binary);
if (source == NULL) { if (!source) {
cerr << "Couldn't open " << source_pathname << "\n"; cerr << "Couldn't open " << source_pathname << "\n";
report_done(false); report_done(false);
return; return;
@ -312,38 +312,85 @@ uncompress_archive() {
if (!target) { if (!target) {
cerr << "Couldn't write to " << target_pathname << "\n"; cerr << "Couldn't write to " << target_pathname << "\n";
report_done(false); report_done(false);
return;
} }
static const int buffer_size = 1024; z_stream z;
char buffer[buffer_size]; 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); int result = inflateInit(&z);
while (count > 0) { if (result < 0) {
target.write(buffer, count); cerr << z.msg << "\n";
count = gzread(source, buffer, buffer_size); 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) { result = inflateEnd(&z);
cerr << "gzip error decompressing " << source_pathname << "\n"; if (result < 0) {
int errnum; cerr << z.msg << "\n";
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";
report_done(false); report_done(false);
return; return;
} }
source.close();
target.close(); target.close();
if (!_uncompressed_archive.full_verify(_package_dir)) { 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); report_done(false);
return; return;
} }
@ -364,25 +411,14 @@ extract_archive() {
cerr << "extracting " << _uncompressed_archive._filename << "\n"; cerr << "extracting " << _uncompressed_archive._filename << "\n";
string source_pathname = _package_dir + "/" + _uncompressed_archive._filename; string source_pathname = _package_dir + "/" + _uncompressed_archive._filename;
P3DMultifileReader reader;
TAR *tar = NULL; if (!reader.extract(source_pathname, _package_dir)) {
int result = tar_open cerr << "Failure extracting " << _uncompressed_archive._filename
(&tar, (char *)source_pathname.c_str(), NULL, O_RDONLY, 0666, TAR_VERBOSE); << "\n";
if (result != 0) {
cerr << "Unable to open " << source_pathname << "\n";
report_done(false); report_done(false);
return; 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"; cerr << "done extracting\n";
report_done(true); report_done(true);
} }

View File

@ -43,6 +43,8 @@ public:
inline bool get_ready() const; inline bool get_ready() const;
inline bool get_failed() const; inline bool get_failed() const;
inline const string &get_package_dir() 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 set_callback(Callback *callback);
void cancel_callback(Callback *callback); void cancel_callback(Callback *callback);

View File

@ -649,7 +649,12 @@ package_ready(P3DPackage *package, bool success) {
if (this == _session->_panda3d_callback) { if (this == _session->_panda3d_callback) {
_session->_panda3d_callback = NULL; _session->_panda3d_callback = NULL;
if (package == _session->_panda3d) { 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 { } else {
cerr << "Unexpected panda3d package: " << package << "\n"; cerr << "Unexpected panda3d package: " << package << "\n";
} }

View File

@ -3,5 +3,6 @@
#include "p3dFileDownload.cxx" #include "p3dFileDownload.cxx"
#include "p3dInstance.cxx" #include "p3dInstance.cxx"
#include "p3dInstanceManager.cxx" #include "p3dInstanceManager.cxx"
#include "p3dMultifileReader.cxx"
#include "p3dPackage.cxx" #include "p3dPackage.cxx"
#include "p3dSession.cxx" #include "p3dSession.cxx"