express: Add ZipArchive class, support mounting zips to VFS

This commit is contained in:
rdb 2020-10-01 23:58:27 +02:00
parent 54b93116e8
commit 6d228dfd2e
17 changed files with 3064 additions and 32 deletions

View File

@ -21,6 +21,42 @@ using std::ostream;
using std::string;
using std::wstring;
// Maps cp437 characters to Unicode codepoints.
static char16_t cp437_table[256] = {
0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
0x25ba, 0x25c4, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0,
};
TextEncoder::Encoding TextEncoder::_default_encoding = TextEncoder::E_utf8;
/**
@ -177,6 +213,20 @@ encode_wchar(char32_t ch, TextEncoder::Encoding encoding) {
};
return string(encoded, 4);
}
case E_cp437:
if ((ch & ~0x7f) == 0) {
return string(1, (char)ch);
}
else if (ch >= 0 && ch < 0x266b) {
// This case is not optimized, because we don't really need it right now.
for (int i = 0; i < 256; ++i) {
if (cp437_table[i] == ch) {
return std::string(1, (char)i);
}
}
}
return ".";
}
return "";
@ -233,6 +283,15 @@ decode_text(const string &text, TextEncoder::Encoding encoding) {
return decode_text_impl(decoder);
}
case E_cp437:
{
std::wstring result(text.size(), 0);
for (size_t i = 0; i < result.size(); ++i) {
result[i] = cp437_table[(uint8_t)text[i]];
}
return result;
}
case E_iso8859:
default:
{
@ -382,6 +441,9 @@ operator << (ostream &out, TextEncoder::Encoding encoding) {
case TextEncoder::E_utf16be:
return out << "utf16be";
case TextEncoder::E_cp437:
return out << "cp437";
};
return out << "**invalid TextEncoder::Encoding(" << (int)encoding << ")**";
@ -402,6 +464,8 @@ operator >> (istream &in, TextEncoder::Encoding &encoding) {
} else if (word == "unicode" || word == "utf16be" || word == "utf-16be" ||
word == "utf16-be" || word == "utf-16-be") {
encoding = TextEncoder::E_utf16be;
} else if (word == "cp437") {
encoding = TextEncoder::E_cp437;
} else {
ostream *notify_ptr = StringDecoder::get_notify_ptr();
if (notify_ptr != nullptr) {

View File

@ -36,6 +36,7 @@ PUBLISHED:
E_iso8859,
E_utf8,
E_utf16be,
E_cp437,
// Deprecated alias for E_utf16be
E_unicode = E_utf16be,

View File

@ -56,6 +56,7 @@ set(P3EXPRESS_HEADERS
virtualFileMountMultifile.h virtualFileMountMultifile.I
virtualFileMountRamdisk.h virtualFileMountRamdisk.I
virtualFileMountSystem.h virtualFileMountSystem.I
virtualFileMountZip.h virtualFileMountZip.I
virtualFileSimple.h virtualFileSimple.I
virtualFileSystem.h virtualFileSystem.I
weakPointerCallback.I weakPointerCallback.h
@ -64,6 +65,7 @@ set(P3EXPRESS_HEADERS
weakPointerToVoid.I weakPointerToVoid.h
weakReferenceList.I weakReferenceList.h
windowsRegistry.h
zipArchive.I zipArchive.h
zStream.I zStream.h zStreamBuf.h
)
@ -110,6 +112,7 @@ set(P3EXPRESS_SOURCES
virtualFileMountMultifile.cxx
virtualFileMountRamdisk.cxx
virtualFileMountSystem.cxx
virtualFileMountZip.cxx
virtualFileSimple.cxx virtualFileSystem.cxx
weakPointerCallback.cxx
weakPointerTo.cxx
@ -117,6 +120,7 @@ set(P3EXPRESS_SOURCES
weakPointerToVoid.cxx
weakReferenceList.cxx
windowsRegistry.cxx
zipArchive.cxx
zStream.cxx zStreamBuf.cxx
)

View File

@ -20,6 +20,7 @@
#include "virtualFileMountMultifile.cxx"
#include "virtualFileMountRamdisk.cxx"
#include "virtualFileMountSystem.cxx"
#include "virtualFileMountZip.cxx"
#include "virtualFileSimple.cxx"
#include "virtualFileSystem.cxx"
#include "weakPointerCallback.cxx"
@ -30,3 +31,4 @@
#include "windowsRegistry.cxx"
#include "zStream.cxx"
#include "zStreamBuf.cxx"
#include "zipArchive.cxx"

View File

@ -0,0 +1,30 @@
/**
* 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."
*
* @file virtualFileMountZip.I
* @author rdb
* @date 2019-11-07
*/
/**
*
*/
INLINE VirtualFileMountZip::
VirtualFileMountZip(ZipArchive *archive, const Filename &directory) :
_archive(archive),
_directory(directory)
{
}
/**
* Returns the ZipArchive pointer that this mount object is based on.
*/
INLINE ZipArchive *VirtualFileMountZip::
get_archive() const {
return _archive;
}

View File

@ -0,0 +1,204 @@
/**
* 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."
*
* @file virtualFileMountZip.cxx
* @author drose
* @date 2002-08-03
*/
#include "virtualFileMountZip.h"
#include "virtualFileSystem.h"
TypeHandle VirtualFileMountZip::_type_handle;
/**
*
*/
VirtualFileMountZip::
~VirtualFileMountZip() {
}
/**
* Returns true if the indicated file exists within the mount system.
*/
bool VirtualFileMountZip::
has_file(const Filename &file) const {
Filename path(_directory, file);
return (path.empty() ||
_archive->find_subfile(path) >= 0 ||
_archive->has_directory(path));
}
/**
* Returns true if the indicated file exists within the mount system and is a
* directory.
*/
bool VirtualFileMountZip::
is_directory(const Filename &file) const {
Filename path(_directory, file);
return (path.empty() || _archive->has_directory(file));
}
/**
* Returns true if the indicated file exists within the mount system and is a
* regular file.
*/
bool VirtualFileMountZip::
is_regular_file(const Filename &file) const {
Filename path(_directory, file);
return (_archive->find_subfile(path) >= 0);
}
/**
* Fills up the indicated pvector with the contents of the file, if it is a
* regular file. Returns true on success, false otherwise.
*/
bool VirtualFileMountZip::
read_file(const Filename &file, bool do_uncompress,
vector_uchar &result) const {
Filename path(_directory, file);
if (do_uncompress) {
// If the file is to be decompressed, we'd better just use the higher-
// level implementation, which includes support for on-the-fly
// decompression.
return VirtualFileMount::read_file(path, do_uncompress, result);
}
// But if we're just reading a straight file, let the Multifile do the
// reading, which avoids a few levels of buffer copies.
int subfile_index = _archive->find_subfile(path);
if (subfile_index < 0) {
express_cat.info()
<< "Unable to read " << path << "\n";
return false;
}
return _archive->read_subfile(subfile_index, result);
}
/**
* Opens the file for reading, if it exists. Returns a newly allocated
* istream on success (which you should eventually delete when you are done
* reading). Returns NULL on failure.
*/
std::istream *VirtualFileMountZip::
open_read_file(const Filename &file) const {
Filename path(_directory, file);
int subfile_index = _archive->find_subfile(path);
if (subfile_index < 0) {
return nullptr;
}
// The caller will eventually pass this pointer to
// VirtualFileSystem::close_read_file(), not to
// Multifile::close_read_subfile(). Fortunately, these two methods do the
// same thing, so that doesn't matter.
return _archive->open_read_subfile(subfile_index);
}
/**
* Returns the current size on disk (or wherever it is) of the already-open
* file. Pass in the stream that was returned by open_read_file(); some
* implementations may require this stream to determine the size.
*/
std::streamsize VirtualFileMountZip::
get_file_size(const Filename &file, std::istream *) const {
Filename path(_directory, file);
int subfile_index = _archive->find_subfile(path);
if (subfile_index < 0) {
return 0;
}
return _archive->get_subfile_length(subfile_index);
}
/**
* Returns the current size on disk (or wherever it is) of the file before it
* has been opened.
*/
std::streamsize VirtualFileMountZip::
get_file_size(const Filename &file) const {
Filename path(_directory, file);
int subfile_index = _archive->find_subfile(path);
if (subfile_index < 0) {
return 0;
}
return _archive->get_subfile_length(subfile_index);
}
/**
* Returns a time_t value that represents the time the file was last modified,
* to within whatever precision the operating system records this information
* (on a Windows95 system, for instance, this may only be accurate to within 2
* seconds).
*
* If the timestamp cannot be determined, either because it is not supported
* by the operating system or because there is some error (such as file not
* found), returns 0.
*/
time_t VirtualFileMountZip::
get_timestamp(const Filename &file) const {
Filename path(_directory, file);
int subfile_index = _archive->find_subfile(path);
if (subfile_index < 0) {
return 0;
}
return _archive->get_subfile_timestamp(subfile_index);
}
/**
* Populates the SubfileInfo structure with the data representing where the
* file actually resides on disk, if this is knowable. Returns true if the
* file might reside on disk, and the info is populated, or false if it might
* not (or it is not known where the file resides), in which case the info is
* meaningless.
*/
bool VirtualFileMountZip::
get_system_info(const Filename &file, SubfileInfo &info) {
Filename path(_directory, file);
Filename filename = _archive->get_filename();
if (filename.empty()) {
return false;
}
int subfile_index = _archive->find_subfile(path);
if (subfile_index < 0) {
return false;
}
if (_archive->is_subfile_compressed(subfile_index) ||
_archive->is_subfile_encrypted(subfile_index)) {
return false;
}
std::streampos start = _archive->get_subfile_internal_start(subfile_index);
size_t length = _archive->get_subfile_internal_length(subfile_index);
info = SubfileInfo(filename, start, length);
return true;
}
/**
* Fills the given vector up with the list of filenames that are local to this
* directory, if the filename is a directory. Returns true if successful, or
* false if the file is not a directory or cannot be read.
*/
bool VirtualFileMountZip::
scan_directory(vector_string &contents, const Filename &dir) const {
Filename path(_directory, dir);
return _archive->scan_directory(contents, path);
}
/**
*
*/
void VirtualFileMountZip::
output(std::ostream &out) const {
out << _archive->get_filename();
}

View File

@ -0,0 +1,77 @@
/**
* 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."
*
* @file virtualFileMountZip.h
* @author drose
* @date 2002-08-03
*/
#ifndef VIRTUALFILEMOUNTZIP_H
#define VIRTUALFILEMOUNTZIP_H
#include "pandabase.h"
#include "virtualFileMount.h"
#include "zipArchive.h"
#include "pointerTo.h"
/**
* Maps a .zip archive into the VirtualFileSystem.
*/
class EXPCL_PANDA_EXPRESS VirtualFileMountZip : public VirtualFileMount {
PUBLISHED:
INLINE VirtualFileMountZip(ZipArchive *archive,
const Filename &directory = Filename());
virtual ~VirtualFileMountZip();
INLINE ZipArchive *get_archive() const;
public:
virtual bool has_file(const Filename &file) const;
virtual bool is_directory(const Filename &file) const;
virtual bool is_regular_file(const Filename &file) const;
virtual bool read_file(const Filename &file, bool do_uncompress,
vector_uchar &result) const;
virtual std::istream *open_read_file(const Filename &file) const;
virtual std::streamsize get_file_size(const Filename &file, std::istream *stream) const;
virtual std::streamsize get_file_size(const Filename &file) const;
virtual time_t get_timestamp(const Filename &file) const;
virtual bool get_system_info(const Filename &file, SubfileInfo &info);
virtual bool scan_directory(vector_string &contents,
const Filename &dir) const;
virtual void output(std::ostream &out) const;
private:
PT(ZipArchive) _archive;
Filename _directory;
public:
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
static TypeHandle get_class_type() {
return _type_handle;
}
static void init_type() {
VirtualFileMount::init_type();
register_type(_type_handle, "VirtualFileMountZip",
VirtualFileMount::get_class_type());
}
private:
static TypeHandle _type_handle;
};
#include "virtualFileMountZip.I"
#endif

View File

@ -18,6 +18,7 @@
#include "virtualFileMountMultifile.h"
#include "virtualFileMountRamdisk.h"
#include "virtualFileMountSystem.h"
#include "virtualFileMountZip.h"
#include "streamWrapper.h"
#include "dSearchPath.h"
#include "dcast.h"
@ -93,6 +94,16 @@ mount(Multifile *multifile, const Filename &mount_point, int flags) {
return mount(new_mount, mount_point, flags);
}
/**
* Mounts the indicated ZipArchive at the given mount point.
*/
bool VirtualFileSystem::
mount(ZipArchive *archive, const Filename &mount_point, int flags) {
PT(VirtualFileMountZip) new_mount =
new VirtualFileMountZip(archive);
return mount(new_mount, mount_point, flags);
}
/**
* Mounts the indicated system file or directory at the given mount point. If
* the named file is a directory, mounts the directory. If the named file is
@ -126,20 +137,70 @@ mount(const Filename &physical_filename, const Filename &mount_point,
PT(VirtualFileMountSystem) new_mount =
new VirtualFileMountSystem(physical_filename);
return mount(new_mount, mount_point, flags);
} else {
// It's not a directory; it must be a Multifile.
PT(Multifile) multifile = new Multifile;
multifile->set_encryption_password(password);
}
// For now these are always opened read only. Maybe later we'll support
// read-write on Multifiles.
flags |= MF_read_only;
if (!multifile->open_read(physical_filename)) {
return false;
// It's not a directory; it must be a multifile or .zip file.
Filename fname = physical_filename;
fname.set_binary();
PT(VirtualFile) vfile = get_file(fname);
if (vfile == nullptr) {
return false;
}
istream *stream = vfile->open_read_file(false);
if (stream == nullptr) {
return false;
}
// For now these are always opened read only. Maybe later we'll support
// read-write on multifiles and .zip files.
flags |= MF_read_only;
char ch = stream->get();
if (ch == '#' || ch == 'p') {
// It *might* be a multifile.
while (ch == '#') {
// Skip to the end of the line.
while (ch != EOF && ch != '\n') {
ch = stream->get();
}
// Skip to the first non-whitespace character of the line.
while (ch != EOF && (isspace(ch) || ch == '\r')) {
ch = stream->get();
}
}
return mount(multifile, mount_point, flags);
// Now read the actual Multifile header.
char this_header[6];
this_header[0] = ch;
stream->read(this_header + 1, 6 - 1);
if (!stream->fail() && stream->gcount() == 6 - 1 &&
memcmp(this_header, "pmf\0\n\r", 6) == 0) {
// Looks like a multifile all right. Reopen it.
close_read_file(stream);
PT(Multifile) multifile = new Multifile;
multifile->set_encryption_password(password);
if (!multifile->open_read(physical_filename)) {
return false;
}
return mount(multifile, mount_point, flags);
}
}
// It must be a ZIP file. Note that ZipArchive does not require rewinding
// the stream back to 0.
IStreamWrapper *read = new IStreamWrapper(stream, true);
PT(ZipArchive) archive = new ZipArchive;
if (!archive->open_read(read, true)) {
return false;
}
archive->set_filename(physical_filename);
return mount(archive, mount_point, flags);
}
/**
@ -245,6 +306,48 @@ unmount(Multifile *multifile) {
return num_removed;
}
/**
* Unmounts all appearances of the indicated ZipArchive from the file system.
* Returns the number of appearances unmounted.
*/
int VirtualFileSystem::
unmount(ZipArchive *archive) {
_lock.lock();
Mounts::iterator ri, wi;
wi = ri = _mounts.begin();
while (ri != _mounts.end()) {
VirtualFileMount *mount = (*ri);
(*wi) = mount;
if (mount->is_exact_type(VirtualFileMountZip::get_class_type())) {
VirtualFileMountZip *zip_mount =
DCAST(VirtualFileMountZip, mount);
if (zip_mount->get_archive() == archive) {
// Remove this one. Don't increment wi.
if (express_cat->is_debug()) {
express_cat->debug()
<< "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
}
mount->_file_system = nullptr;
} else {
// Don't remove this one.
++wi;
}
} else {
// Don't remove this one.
++wi;
}
++ri;
}
int num_removed = _mounts.end() - wi;
_mounts.erase(wi, _mounts.end());
++_mount_seq;
_lock.unlock();
return num_removed;
}
/**
* Unmounts all appearances of the indicated directory name or multifile name
* from the file system. Returns the number of appearances unmounted.

View File

@ -25,6 +25,7 @@
#include "config_express.h"
#include "mutexImpl.h"
#include "pvector.h"
#include "zipArchive.h"
class Multifile;
class VirtualFileComposite;
@ -47,12 +48,14 @@ PUBLISHED:
};
BLOCKING bool mount(Multifile *multifile, const Filename &mount_point, int flags);
BLOCKING bool mount(ZipArchive *archive, const Filename &mount_point, int flags);
BLOCKING bool mount(const Filename &physical_filename, const Filename &mount_point,
int flags, const std::string &password = "");
BLOCKING bool mount_loop(const Filename &virtual_filename, const Filename &mount_point,
int flags, const std::string &password = "");
bool mount(VirtualFileMount *mount, const Filename &mount_point, int flags);
BLOCKING int unmount(Multifile *multifile);
BLOCKING int unmount(ZipArchive *archive);
BLOCKING int unmount(const Filename &physical_filename);
int unmount(VirtualFileMount *mount);
BLOCKING int unmount_point(const Filename &mount_point);

View File

@ -22,17 +22,17 @@ IDecompressStream() : std::istream(&_buf) {
*
*/
INLINE IDecompressStream::
IDecompressStream(std::istream *source, bool owns_source) : std::istream(&_buf) {
open(source, owns_source);
IDecompressStream(std::istream *source, bool owns_source, std::streamsize source_length, bool header) : std::istream(&_buf) {
open(source, owns_source, source_length, header);
}
/**
*
*/
INLINE IDecompressStream &IDecompressStream::
open(std::istream *source, bool owns_source) {
open(std::istream *source, bool owns_source, std::streamsize source_length, bool header) {
clear((ios_iostate)0);
_buf.open_read(source, owns_source);
_buf.open_read(source, owns_source, source_length, header);
return *this;
}
@ -58,19 +58,19 @@ OCompressStream() : std::ostream(&_buf) {
*
*/
INLINE OCompressStream::
OCompressStream(std::ostream *dest, bool owns_dest, int compression_level) :
OCompressStream(std::ostream *dest, bool owns_dest, int compression_level, bool header) :
std::ostream(&_buf)
{
open(dest, owns_dest, compression_level);
open(dest, owns_dest, compression_level, header);
}
/**
*
*/
INLINE OCompressStream &OCompressStream::
open(std::ostream *dest, bool owns_dest, int compression_level) {
open(std::ostream *dest, bool owns_dest, int compression_level, bool header) {
clear((ios_iostate)0);
_buf.open_write(dest, owns_dest, compression_level);
_buf.open_write(dest, owns_dest, compression_level, header);
return *this;
}

View File

@ -34,13 +34,17 @@
class EXPCL_PANDA_EXPRESS IDecompressStream : public std::istream {
PUBLISHED:
INLINE IDecompressStream();
INLINE explicit IDecompressStream(std::istream *source, bool owns_source);
INLINE explicit IDecompressStream(std::istream *source, bool owns_source,
std::streamsize source_length = -1,
bool header=true);
#if _MSC_VER >= 1800
INLINE IDecompressStream(const IDecompressStream &copy) = delete;
#endif
INLINE IDecompressStream &open(std::istream *source, bool owns_source);
INLINE IDecompressStream &open(std::istream *source, bool owns_source,
std::streamsize source_length = -1,
bool header=true);
INLINE IDecompressStream &close();
private:
@ -61,14 +65,16 @@ class EXPCL_PANDA_EXPRESS OCompressStream : public std::ostream {
PUBLISHED:
INLINE OCompressStream();
INLINE explicit OCompressStream(std::ostream *dest, bool owns_dest,
int compression_level = 6);
int compression_level = 6,
bool header=true);
#if _MSC_VER >= 1800
INLINE OCompressStream(const OCompressStream &copy) = delete;
#endif
INLINE OCompressStream &open(std::ostream *dest, bool owns_dest,
int compression_level = 6);
int compression_level = 6,
bool header=true);
INLINE OCompressStream &close();
private:

View File

@ -73,8 +73,9 @@ ZStreamBuf::
*
*/
void ZStreamBuf::
open_read(std::istream *source, bool owns_source) {
open_read(std::istream *source, bool owns_source, std::streamsize source_length, bool header) {
_source = source;
_source_bytes_left = source_length;
_owns_source = owns_source;
_z_source.next_in = Z_NULL;
@ -91,7 +92,7 @@ open_read(std::istream *source, bool owns_source) {
_z_source.opaque = Z_NULL;
_z_source.msg = (char *)"no error message";
int result = inflateInit2(&_z_source, 32 + 15);
int result = inflateInit2(&_z_source, header ? 32 + 15 : -15);
if (result < 0) {
show_zlib_error("inflateInit2", result, _z_source);
close_read();
@ -104,6 +105,8 @@ open_read(std::istream *source, bool owns_source) {
*/
void ZStreamBuf::
close_read() {
_source_bytes_left = 0;
if (_source != nullptr) {
int result = inflateEnd(&_z_source);
@ -124,7 +127,7 @@ close_read() {
*
*/
void ZStreamBuf::
open_write(std::ostream *dest, bool owns_dest, int compression_level) {
open_write(std::ostream *dest, bool owns_dest, int compression_level, bool header) {
_dest = dest;
_owns_dest = owns_dest;
@ -142,9 +145,10 @@ open_write(std::ostream *dest, bool owns_dest, int compression_level) {
_z_dest.opaque = Z_NULL;
_z_dest.msg = (char *)"no error message";
int result = deflateInit(&_z_dest, compression_level);
int result = deflateInit2(&_z_dest, compression_level, Z_DEFLATED,
header ? 15 : -15, 8, Z_DEFAULT_STRATEGY);
if (result < 0) {
show_zlib_error("deflateInit", result, _z_dest);
show_zlib_error("deflateInit2", result, _z_dest);
close_write();
}
thread_consider_yield();
@ -310,13 +314,22 @@ read_chars(char *start, size_t length) {
_z_source.next_out = (Bytef *)start;
_z_source.avail_out = length;
bool eof = (_source->eof() || _source->fail());
bool eof = (_source_bytes_left == 0 || _source->eof() || _source->fail());
int flush = 0;
while (_z_source.avail_out > 0) {
if (_z_source.avail_in == 0 && !eof) {
_source->read(decompress_buffer, decompress_buffer_size);
size_t read_count = _source->gcount();
size_t read_count = 0;
if (_source_bytes_left >= 0) {
// Don't read more than the specified limit.
_source->read(decompress_buffer,
std::min(_source_bytes_left, (std::streamsize)decompress_buffer_size));
read_count = _source->gcount();
_source_bytes_left -= read_count;
} else {
_source->read(decompress_buffer, decompress_buffer_size);
read_count = _source->gcount();
}
eof = (read_count == 0 || _source->eof() || _source->fail());
_z_source.next_in = (Bytef *)decompress_buffer;

View File

@ -29,10 +29,10 @@ public:
ZStreamBuf();
virtual ~ZStreamBuf();
void open_read(std::istream *source, bool owns_source);
void open_read(std::istream *source, bool owns_source, std::streamsize source_length=-1, bool header=true);
void close_read();
void open_write(std::ostream *dest, bool owns_dest, int compression_level);
void open_write(std::ostream *dest, bool owns_dest, int compression_level, bool header=true);
void close_write();
virtual std::streampos seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which);
@ -50,6 +50,7 @@ private:
private:
std::istream *_source;
std::streamsize _source_bytes_left = -1;
bool _owns_source;
std::ostream *_dest;

View File

@ -0,0 +1,147 @@
/**
* 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."
*
* @file zipArchive.I
* @author rdb
* @date 2019-10-23
*/
/**
* Returns the filename of the ZipArchive, if it is available.
*/
INLINE const Filename &ZipArchive::
get_filename() const {
return _filename;
}
/**
* Replaces the filename of the ZipArchive. This is primarily used for
* documentation purposes only; changing this name does not open the indicated
* file. See open_read() or open_write() for that.
*/
INLINE void ZipArchive::
set_filename(const Filename &filename) {
_filename = filename;
}
/**
* Returns true if the ZipArchive has been opened for read mode and there have
* been no errors, and individual Subfile contents may be extracted.
*/
INLINE bool ZipArchive::
is_read_valid() const {
return (_read != nullptr);
}
/**
* Returns true if the ZipArchive has been opened for write mode and there have
* been no errors, and Subfiles may be added or removed from the ZipArchive.
*/
INLINE bool ZipArchive::
is_write_valid() const {
return (_write != nullptr && !_write->fail());
}
/**
* Returns true if the ZipArchive index is suboptimal and should be repacked.
* Call repack() to achieve this. It is not done automatically.
*/
INLINE bool ZipArchive::
needs_repack() const {
return _needs_repack;
}
/**
* Sets the flag indicating whether timestamps should be recorded within the
* ZipArchive or not. The default is true, indicating the ZipArchive will
* record timestamps for each subfile that is added.
*
* If this is false, the ZipArchive will not record timestamps internally. In
* this case, the return value from get_subfile_timestamp() will be zero.
*
* You may want to set this false to minimize the bitwise difference between
* independently-generated ZipArchives.
*/
INLINE void ZipArchive::
set_record_timestamp(bool flag) {
_record_timestamp = flag;
}
/**
* Returns the flag indicating whether timestamps should be recorded within
* the ZipArchive or not. See set_record_timestamp().
*/
INLINE bool ZipArchive::
get_record_timestamp() const {
return _record_timestamp;
}
/**
* Removes the named subfile from the ZipArchive, if it exists; returns true if
* successfully removed, or false if it did not exist in the first place. The
* file will not actually be removed from the disk until the next call to
* flush().
*
* Note that this does not actually remove the data from the indicated
* subfile; it simply removes it from the index. The ZipArchive will not be
* reduced in size after this operation, until the next call to repack().
*/
INLINE bool ZipArchive::
remove_subfile(const std::string &subfile_name) {
int index = find_subfile(subfile_name);
if (index >= 0) {
remove_subfile(index);
return true;
}
return false;
}
/**
* Returns a vector_uchar that contains the entire contents of the indicated
* subfile.
*/
INLINE vector_uchar ZipArchive::
read_subfile(int index) {
vector_uchar result;
read_subfile(index, result);
return result;
}
/**
* Returns the comment string that was at the end of the ZIP end-of-directory
* record, if any.
* See set_comment().
*/
INLINE const std::string &ZipArchive::
get_comment() const {
return _comment;
}
/**
* Compares two Subfiles for proper sorting within the index.
*/
INLINE bool ZipArchive::Subfile::
operator < (const ZipArchive::Subfile &other) const {
return _name < other._name;
}
/**
* Returns true if this Subfile is compressed.
*/
INLINE bool ZipArchive::Subfile::
is_compressed() const {
return _compression_method != CM_store;
}
/**
* Returns true if this Subfile is encrypted.
*/
INLINE bool ZipArchive::Subfile::
is_encrypted() const {
return (_flags & SF_encrypted) != 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,205 @@
/**
* 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."
*
* @file zipArchive.h
* @author rdb
* @date 2019-01-20
*/
#ifndef ZIPARCHIVE_H
#define ZIPARCHIVE_H
#include "pandabase.h"
#include "config_express.h"
#include "streamWrapper.h"
#include "subStream.h"
#include "filename.h"
#include "ordered_vector.h"
#include "indirectLess.h"
#include "referenceCount.h"
#include "pvector.h"
#include "vector_uchar.h"
/**
* A file that contains a set of files.
*/
class EXPCL_PANDA_EXPRESS ZipArchive : public ReferenceCount {
PUBLISHED:
ZipArchive();
ZipArchive(const ZipArchive &copy) = delete;
~ZipArchive();
ZipArchive &operator = (const ZipArchive &copy) = delete;
PUBLISHED:
BLOCKING bool open_read(const Filename &filename);
BLOCKING bool open_read(IStreamWrapper *stream, bool owns_pointer = false);
BLOCKING bool open_write(const Filename &filename);
BLOCKING bool open_write(std::ostream *stream, bool owns_pointer = false);
BLOCKING bool open_read_write(const Filename &filename);
BLOCKING bool open_read_write(std::iostream *stream, bool owns_pointer = false);
BLOCKING bool verify();
BLOCKING void close();
INLINE const Filename &get_filename() const;
INLINE void set_filename(const Filename &filename);
INLINE bool is_read_valid() const;
INLINE bool is_write_valid() const;
INLINE bool needs_repack() const;
INLINE void set_record_timestamp(bool record_timestamp);
INLINE bool get_record_timestamp() const;
std::string add_subfile(const std::string &subfile_name, const Filename &filename,
int compression_level);
std::string add_subfile(const std::string &subfile_name, std::istream *subfile_data,
int compression_level);
std::string update_subfile(const std::string &subfile_name, const Filename &filename,
int compression_level);
BLOCKING bool flush();
BLOCKING bool repack();
int get_num_subfiles() const;
int find_subfile(const std::string &subfile_name) const;
bool has_directory(const std::string &subfile_name) const;
bool scan_directory(vector_string &contents,
const std::string &subfile_name) const;
void remove_subfile(int index);
INLINE bool remove_subfile(const std::string &subfile_name);
const std::string &get_subfile_name(int index) const;
MAKE_SEQ(get_subfile_names, get_num_subfiles, get_subfile_name);
size_t get_subfile_length(int index) const;
time_t get_subfile_timestamp(int index) const;
bool is_subfile_compressed(int index) const;
bool is_subfile_encrypted(int index) const;
std::streampos get_subfile_internal_start(int index) const;
size_t get_subfile_internal_length(int index) const;
BLOCKING INLINE vector_uchar read_subfile(int index);
BLOCKING std::istream *open_read_subfile(int index);
BLOCKING static void close_read_subfile(std::istream *stream);
BLOCKING bool extract_subfile(int index, const Filename &filename);
BLOCKING bool extract_subfile_to(int index, std::ostream &out);
BLOCKING bool compare_subfile(int index, const Filename &filename);
void output(std::ostream &out) const;
void ls(std::ostream &out = std::cout) const;
void set_comment(const std::string &comment);
INLINE const std::string &get_comment() const;
public:
bool read_subfile(int index, std::string &result);
bool read_subfile(int index, vector_uchar &result);
private:
enum SubfileFlags : uint16_t {
SF_encrypted = (1 << 0),
SF_deflate_best = (1 << 1),
SF_deflate_fast = (1 << 2),
SF_deflate_fastest = SF_deflate_best | SF_deflate_fast,
SF_data_descriptor = (1 << 3),
SF_strong_encryption = (1 << 6),
SF_utf8_encoding = (1 << 11),
};
enum CompressionMethod : uint16_t {
CM_store = 0,
CM_shrink = 1,
CM_reduce1 = 2,
CM_reduce2 = 3,
CM_reduce3 = 4,
CM_reduce4 = 5,
CM_implode = 6,
CM_tokenize = 7,
CM_deflate = 8,
CM_deflate64 = 9,
CM_bzip2 = 12,
CM_lzma = 14,
CM_terse = 18,
CM_lz77 = 19,
CM_wavpack = 97,
CM_ppmd = 98,
};
class Subfile {
public:
Subfile() = default;
Subfile(const std::string &name, int compression_level);
INLINE bool operator < (const Subfile &other) const;
bool read_index(std::istream &read);
bool read_header(std::istream &read);
bool verify_data(std::istream &read);
bool write_index(std::ostream &write, std::streampos &fpos);
bool write_header(std::ostream &write, std::streampos &fpos);
bool write_data(std::ostream &write, std::istream *read,
std::streampos &fpos, int compression_level);
INLINE bool is_compressed() const;
INLINE bool is_encrypted() const;
INLINE std::streampos get_last_byte_pos() const;
std::string _name;
uint8_t _system = 0;
size_t _index_length = 0;
uint32_t _checksum = 0;
uint64_t _data_length = 0;
uint64_t _uncompressed_length = 0;
time_t _timestamp = 0;
std::streampos _header_start = 0;
uint16_t _internal_attribs = 0;
uint32_t _external_attribs = 0;
std::string _comment;
int _flags = SF_data_descriptor;
CompressionMethod _compression_method = CM_store;
};
void add_new_subfile(Subfile *subfile, int compression_level);
std::istream *open_read_subfile(Subfile *subfile);
std::string standardize_subfile_name(const std::string &subfile_name) const;
void clear_subfiles();
bool read_index();
bool write_index(std::ostream &write, std::streampos &fpos);
typedef ov_set<Subfile *, IndirectLess<Subfile> > Subfiles;
Subfiles _subfiles;
typedef pvector<Subfile *> PendingSubfiles;
PendingSubfiles _removed_subfiles;
std::streampos _offset;
IStreamWrapper *_read;
std::ostream *_write;
bool _owns_stream;
std::streampos _index_start = 0;
std::streampos _file_end = 0;
bool _index_changed;
bool _needs_repack;
bool _record_timestamp;
pifstream _read_file;
IStreamWrapper _read_filew;
pofstream _write_file;
pfstream _read_write_file;
StreamWrapper _read_write_filew;
Filename _filename;
std::string _header_prefix;
std::string _comment;
friend class Subfile;
};
#include "zipArchive.I"
#endif

183
tests/express/test_zip.py Normal file
View File

@ -0,0 +1,183 @@
from panda3d.core import ZipArchive, IStreamWrapper, StringStream, Filename
from direct.stdpy.file import StreamIOWrapper
import zipfile
from io import BytesIO
EMPTY_ZIP = b'PK\x05\x06' + b'\x00' * 18
def test_zip_read_empty():
stream = StringStream(EMPTY_ZIP)
wrapper = IStreamWrapper(stream)
zip = ZipArchive()
zip.open_read(wrapper)
assert zip.is_read_valid()
assert not zip.is_write_valid()
assert not zip.needs_repack()
assert zip.get_num_subfiles() == 0
zip.close()
def test_zip_write_empty():
stream = StringStream()
zip = ZipArchive()
zip.open_write(stream)
assert not zip.is_read_valid()
assert zip.is_write_valid()
assert not zip.needs_repack()
zip.close()
assert stream.data == EMPTY_ZIP
with zipfile.ZipFile(StreamIOWrapper(stream), 'r') as zf:
assert zf.testzip() is None
def test_zip_read_extract(tmp_path):
stream = StringStream()
zf = zipfile.ZipFile(StreamIOWrapper(stream), mode='w', allowZip64=True)
zf.writestr("test.txt", b"test stored", compress_type=zipfile.ZIP_STORED)
zf.writestr("test2.txt", b"test deflated", compress_type=zipfile.ZIP_DEFLATED)
zf.writestr("dir/dtest.txt", b"test in dir")
zf.writestr("dir1/dir2/test.txt", b"test nested dir")
zf.writestr("emptydir/", b"", compress_type=zipfile.ZIP_STORED)
zf.close()
wrapper = IStreamWrapper(stream)
zip = ZipArchive()
zip.open_read(wrapper)
assert zip.is_read_valid()
assert not zip.is_write_valid()
assert not zip.needs_repack()
assert zip.verify()
assert zip.find_subfile("nonexistent.txt") == -1
sf = zip.find_subfile("test.txt")
assert sf >= 0
assert zip.read_subfile(sf) == b"test stored"
assert zip.extract_subfile(sf, tmp_path / "test.txt")
assert open(tmp_path / "test.txt", 'rb').read() == b"test stored"
sf = zip.find_subfile("test2.txt")
assert sf >= 0
assert zip.read_subfile(sf) == b"test deflated"
assert zip.extract_subfile(sf, tmp_path / "test2.txt")
assert open(tmp_path / "test2.txt", 'rb').read() == b"test deflated"
def test_zip_write():
stream = StringStream()
zip = ZipArchive()
zip.open_write(stream)
zip.add_subfile("test.txt", StringStream(b"test deflated"), 6)
zip.add_subfile("test2.txt", StringStream(b"test stored"), 0)
zip.close()
with zipfile.ZipFile(StreamIOWrapper(stream), 'r') as zf:
assert zf.testzip() is None
assert tuple(sorted(zf.namelist())) == ("test.txt", "test2.txt")
def test_zip_replace_subfile(tmp_path):
stream = StringStream()
zf = zipfile.ZipFile(StreamIOWrapper(stream), mode='w', allowZip64=True)
zf.writestr("test1.txt", b"contents of first file")
zf.writestr("test2.txt", b"")
zf.writestr("test3.txt", b"contents of third file")
zf.close()
zip = ZipArchive()
zip.open_read_write(stream)
assert zip.is_read_valid()
assert zip.is_write_valid()
assert not zip.needs_repack()
assert zip.verify()
sf = zip.find_subfile("test2.txt")
assert sf >= 0
zip.add_subfile("test2.txt", StringStream(b"contents of second file"), 6)
zip.close()
with zipfile.ZipFile(StreamIOWrapper(stream), 'r') as zf:
assert zf.testzip() is None
assert zf.read("test1.txt") == b"contents of first file"
assert zf.read("test2.txt") == b"contents of second file"
assert zf.read("test3.txt") == b"contents of third file"
def test_zip_remove_subfile(tmp_path):
stream = StringStream()
zf = zipfile.ZipFile(StreamIOWrapper(stream), mode='w', allowZip64=True)
zf.writestr("test1.txt", b"contents of first file")
zf.writestr("test2.txt", b"contents of second file")
zf.writestr("test3.txt", b"contents of third file")
zf.close()
zip = ZipArchive()
zip.open_read_write(stream)
assert zip.is_read_valid()
assert zip.is_write_valid()
assert not zip.needs_repack()
assert zip.verify()
removed = zip.remove_subfile("test2.txt")
assert removed
zip.close()
with zipfile.ZipFile(StreamIOWrapper(stream), 'r') as zf:
assert zf.testzip() is None
names = zf.namelist()
assert "test1.txt" in names
assert "test2.txt" not in names
assert "test3.txt" in names
def test_zip_repack(tmp_path):
zip_path = tmp_path / "test_zip_repack.zip"
zf = zipfile.ZipFile(zip_path, mode='w', allowZip64=True)
zf.writestr("test1.txt", b"contents of first file")
zf.writestr("test2.txt", b"contents of second file")
zf.close()
zip = ZipArchive()
zip.open_read_write(zip_path)
assert zip.is_read_valid()
assert zip.is_write_valid()
assert not zip.needs_repack()
assert zip.verify()
removed = zip.remove_subfile("test2.txt")
assert removed
zip.add_subfile("test3.txt", StringStream(b"contents of third file"), 6)
assert zip.needs_repack()
result = zip.repack()
assert result
assert not zip.needs_repack()
assert zip.verify()
zip.close()
with zipfile.ZipFile(zip_path, 'r') as zf:
assert zf.testzip() is None
assert zf.read("test1.txt") == b"contents of first file"
assert "test2.txt" not in zf.namelist()
assert zf.read("test3.txt") == b"contents of third file"