mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-27 15:25:54 -04:00
express: Add ZipArchive class, support mounting zips to VFS
This commit is contained in:
parent
54b93116e8
commit
6d228dfd2e
@ -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) {
|
||||
|
@ -36,6 +36,7 @@ PUBLISHED:
|
||||
E_iso8859,
|
||||
E_utf8,
|
||||
E_utf16be,
|
||||
E_cp437,
|
||||
|
||||
// Deprecated alias for E_utf16be
|
||||
E_unicode = E_utf16be,
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
|
30
panda/src/express/virtualFileMountZip.I
Normal file
30
panda/src/express/virtualFileMountZip.I
Normal 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;
|
||||
}
|
204
panda/src/express/virtualFileMountZip.cxx
Normal file
204
panda/src/express/virtualFileMountZip.cxx
Normal 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();
|
||||
}
|
77
panda/src/express/virtualFileMountZip.h
Normal file
77
panda/src/express/virtualFileMountZip.h
Normal 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
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 ©) = 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 ©) = 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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
147
panda/src/express/zipArchive.I
Normal file
147
panda/src/express/zipArchive.I
Normal 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;
|
||||
}
|
1989
panda/src/express/zipArchive.cxx
Normal file
1989
panda/src/express/zipArchive.cxx
Normal file
File diff suppressed because it is too large
Load Diff
205
panda/src/express/zipArchive.h
Normal file
205
panda/src/express/zipArchive.h
Normal 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 ©) = delete;
|
||||
~ZipArchive();
|
||||
|
||||
ZipArchive &operator = (const ZipArchive ©) = 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
183
tests/express/test_zip.py
Normal 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"
|
Loading…
x
Reference in New Issue
Block a user