Skip to content

Commit 2fd48c0

Browse files
committed
Merge pull request #103283 from mashumafi/zip-compression-level
Add compression level support to Zip Module
2 parents abc5f77 + 0ed8bf8 commit 2fd48c0

File tree

11 files changed

+229
-2
lines changed

11 files changed

+229
-2
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
/modules/regex/tests/ @godotengine/core @godotengine/tests
180180
/modules/zip/ @godotengine/core
181181
/modules/zip/doc_classes/ @godotengine/core @godotengine/documentation
182+
/modules/zip/tests @godotengine/core @godotengine/tests
182183

183184
# Platform
184185

modules/zip/SCsub

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ env_zip = env_modules.Clone()
88

99
# Module files
1010
env_zip.add_source_files(env.modules_sources, "*.cpp")
11+
12+
if env["tests"]:
13+
env_zip.Append(CPPDEFINES=["TESTS_ENABLED"])
14+
env_zip.add_source_files(env.modules_sources, "./tests/*.cpp")

modules/zip/doc_classes/ZIPPacker.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@
6262
</description>
6363
</method>
6464
</methods>
65+
<members>
66+
<member name="compression_level" type="int" setter="set_compression_level" getter="get_compression_level" default="-1">
67+
The compression level used when [method start_file] is called. Use [enum ZIPPacker.CompressionLevel] as a reference.
68+
</member>
69+
</members>
6570
<constants>
6671
<constant name="APPEND_CREATE" value="0" enum="ZipAppend">
6772
Create a new zip archive at the given path.
@@ -72,5 +77,17 @@
7277
<constant name="APPEND_ADDINZIP" value="2" enum="ZipAppend">
7378
Add new files to the existing zip archive at the given path.
7479
</constant>
80+
<constant name="COMPRESSION_DEFAULT" value="-1" enum="CompressionLevel">
81+
Start a file with the default Deflate compression level ([code]6[/code]). This is a good compromise between speed and file size.
82+
</constant>
83+
<constant name="COMPRESSION_NONE" value="0" enum="CompressionLevel">
84+
Start a file with no compression. This is also known as the "Store" compression mode and is the fastest method of packing files inside a ZIP archive. Consider using this mode for files that are already compressed (such as JPEG, PNG, MP3, or Ogg Vorbis files).
85+
</constant>
86+
<constant name="COMPRESSION_FAST" value="1" enum="CompressionLevel">
87+
Start a file with the fastest Deflate compression level ([code]1[/code]). This is fast to compress, but results in larger file sizes than [constant COMPRESSION_DEFAULT]. Decompression speed is generally unaffected by the chosen compression level.
88+
</constant>
89+
<constant name="COMPRESSION_BEST" value="9" enum="CompressionLevel">
90+
Start a file with the the best Deflate compression level ([code]9[/code]). This is slow to compress, but results in smaller file sizes than [constant COMPRESSION_DEFAULT]. Decompression speed is generally unaffected by the chosen compression level.
91+
</constant>
7592
</constants>
7693
</class>

modules/zip/doc_classes/ZIPReader.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@
6161
Must be called after [method open].
6262
</description>
6363
</method>
64+
<method name="get_compression_level">
65+
<return type="int" />
66+
<param index="0" name="path" type="String" />
67+
<param index="1" name="case_sensitive" type="bool" default="true" />
68+
<description>
69+
Returns the compression level of the file in the loaded zip archive. Returns [code]-1[/code] if the file doesn't exist or any other error occurs. Must be called after [method open].
70+
</description>
71+
</method>
6472
<method name="get_files">
6573
<return type="PackedStringArray" />
6674
<description>

modules/zip/tests/data/test.zip

232 Bytes
Binary file not shown.

modules/zip/tests/test_zip.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**************************************************************************/
2+
/* test_zip.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "test_zip.h"
32+
33+
namespace TestZip {
34+
35+
void check_file_size(const String &p_path, int p_expected_size) {
36+
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
37+
CHECK(f.is_valid());
38+
CHECK(f->get_length() == p_expected_size);
39+
}
40+
41+
} // namespace TestZip

modules/zip/tests/test_zip.h

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**************************************************************************/
2+
/* test_zip.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#pragma once
32+
33+
#include "tests/test_macros.h"
34+
#include "tests/test_utils.h"
35+
36+
#include "../zip_packer.h"
37+
#include "../zip_reader.h"
38+
39+
namespace TestZip {
40+
41+
void check_file_size(const String &p_path, int p_expected_size);
42+
43+
TEST_CASE("[ZIPPacker] default compression") {
44+
const String path = TestUtils::get_temp_path("compressed.zip");
45+
Ref<ZIPPacker> packer;
46+
packer.instantiate();
47+
Error open_result = packer->open(path, ZIPPacker::APPEND_CREATE);
48+
CHECK(open_result == OK);
49+
Error start_file_result = packer->start_file("demo.txt");
50+
CHECK(start_file_result == OK);
51+
String text = "hello world!";
52+
Error write_file_result = packer->write_file(text.to_utf8_buffer());
53+
CHECK(write_file_result == OK);
54+
Error close_file_result = packer->close_file();
55+
CHECK(close_file_result == OK);
56+
Error close_result = packer->close();
57+
CHECK(close_result == OK);
58+
check_file_size(path, 128);
59+
}
60+
61+
TEST_CASE("[ZIPPacker] no compression") {
62+
const String path = TestUtils::get_temp_path("uncompressed.zip");
63+
Ref<ZIPPacker> packer;
64+
packer.instantiate();
65+
Error open_result = packer->open(path, ZIPPacker::APPEND_CREATE);
66+
CHECK(open_result == OK);
67+
packer->set_compression_level(ZIPPacker::COMPRESSION_NONE);
68+
Error start_file_result = packer->start_file("demo.txt");
69+
CHECK(start_file_result == OK);
70+
String text = "hello world!";
71+
Error write_file_result = packer->write_file(text.to_utf8_buffer());
72+
CHECK(write_file_result == OK);
73+
Error close_file_result = packer->close_file();
74+
CHECK(close_file_result == OK);
75+
Error close_result = packer->close();
76+
CHECK(close_result == OK);
77+
check_file_size(path, 131);
78+
}
79+
80+
TEST_CASE("[ZIPReader] read files") {
81+
String test_data = String("modules/zip/tests/data/").path_join("test.zip");
82+
Ref<ZIPReader> reader;
83+
reader.instantiate();
84+
Error open_result = reader->open(test_data);
85+
CHECK(open_result == OK);
86+
87+
const String hello_path = "hello.txt";
88+
const String world_path = "world.txt";
89+
PackedStringArray expected_files;
90+
expected_files.push_back(hello_path);
91+
expected_files.push_back(world_path);
92+
CHECK(reader->get_files() == expected_files);
93+
94+
const String expected_hello_text = "hello world!";
95+
const String expected_world_text = "game over!";
96+
PackedByteArray hello_bytes = reader->read_file(hello_path, false);
97+
PackedByteArray world_bytes = reader->read_file(world_path, true);
98+
CHECK(hello_bytes == expected_hello_text.to_utf8_buffer());
99+
CHECK(world_bytes == expected_world_text.to_utf8_buffer());
100+
101+
CHECK(reader->get_compression_level(hello_path, true) == 6);
102+
CHECK(reader->get_compression_level(world_path, false) == 9);
103+
}
104+
105+
} // namespace TestZip

modules/zip/zip_packer.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ Error ZIPPacker::close() {
5555
return err;
5656
}
5757

58+
void ZIPPacker::set_compression_level(int p_compression_level) {
59+
ERR_FAIL_COND_MSG(p_compression_level < Z_DEFAULT_COMPRESSION || p_compression_level > Z_BEST_COMPRESSION, "Invalid compression level.");
60+
compression_level = p_compression_level;
61+
}
62+
63+
int ZIPPacker::get_compression_level() const {
64+
return compression_level;
65+
}
66+
5867
Error ZIPPacker::start_file(const String &p_path) {
5968
ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use.");
6069

@@ -81,7 +90,7 @@ Error ZIPPacker::start_file(const String &p_path) {
8190
0,
8291
nullptr,
8392
Z_DEFLATED,
84-
Z_DEFAULT_COMPRESSION,
93+
compression_level,
8594
0,
8695
-MAX_WBITS,
8796
DEF_MEM_LEVEL,
@@ -107,6 +116,9 @@ Error ZIPPacker::close_file() {
107116

108117
void ZIPPacker::_bind_methods() {
109118
ClassDB::bind_method(D_METHOD("open", "path", "append"), &ZIPPacker::open, DEFVAL(Variant(APPEND_CREATE)));
119+
ClassDB::bind_method(D_METHOD("set_compression_level", "compression_level"), &ZIPPacker::set_compression_level);
120+
ClassDB::bind_method(D_METHOD("get_compression_level"), &ZIPPacker::get_compression_level);
121+
ADD_PROPERTY(PropertyInfo(Variant::INT, "compression_level"), "set_compression_level", "get_compression_level");
110122
ClassDB::bind_method(D_METHOD("start_file", "path"), &ZIPPacker::start_file);
111123
ClassDB::bind_method(D_METHOD("write_file", "data"), &ZIPPacker::write_file);
112124
ClassDB::bind_method(D_METHOD("close_file"), &ZIPPacker::close_file);
@@ -115,9 +127,15 @@ void ZIPPacker::_bind_methods() {
115127
BIND_ENUM_CONSTANT(APPEND_CREATE);
116128
BIND_ENUM_CONSTANT(APPEND_CREATEAFTER);
117129
BIND_ENUM_CONSTANT(APPEND_ADDINZIP);
130+
131+
BIND_ENUM_CONSTANT(COMPRESSION_DEFAULT);
132+
BIND_ENUM_CONSTANT(COMPRESSION_NONE);
133+
BIND_ENUM_CONSTANT(COMPRESSION_FAST);
134+
BIND_ENUM_CONSTANT(COMPRESSION_BEST);
118135
}
119136

120-
ZIPPacker::ZIPPacker() {}
137+
ZIPPacker::ZIPPacker() {
138+
}
121139

122140
ZIPPacker::~ZIPPacker() {
123141
if (fa.is_valid()) {

modules/zip/zip_packer.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ZIPPacker : public RefCounted {
4040

4141
Ref<FileAccess> fa;
4242
zipFile zf = nullptr;
43+
int compression_level = Z_DEFAULT_COMPRESSION;
4344

4445
protected:
4546
static void _bind_methods();
@@ -51,9 +52,19 @@ class ZIPPacker : public RefCounted {
5152
APPEND_ADDINZIP = 2,
5253
};
5354

55+
enum CompressionLevel {
56+
COMPRESSION_DEFAULT = Z_DEFAULT_COMPRESSION,
57+
COMPRESSION_NONE = Z_NO_COMPRESSION,
58+
COMPRESSION_FAST = Z_BEST_SPEED,
59+
COMPRESSION_BEST = Z_BEST_COMPRESSION,
60+
};
61+
5462
Error open(const String &p_path, ZipAppend p_append);
5563
Error close();
5664

65+
void set_compression_level(int p_compression_level);
66+
int get_compression_level() const;
67+
5768
Error start_file(const String &p_path);
5869
Error write_file(const Vector<uint8_t> &p_data);
5970
Error close_file();
@@ -63,3 +74,4 @@ class ZIPPacker : public RefCounted {
6374
};
6475

6576
VARIANT_ENUM_CAST(ZIPPacker::ZipAppend)
77+
VARIANT_ENUM_CAST(ZIPPacker::CompressionLevel)

modules/zip/zip_reader.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,25 @@ bool ZIPReader::file_exists(const String &p_path, bool p_case_sensitive) {
140140
return true;
141141
}
142142

143+
int ZIPReader::get_compression_level(const String &p_path, bool p_case_sensitive) {
144+
ERR_FAIL_COND_V_MSG(fa.is_null(), -1, "ZIPReader must be opened before use.");
145+
146+
int cs = p_case_sensitive ? 1 : 2;
147+
if (unzLocateFile(uzf, p_path.utf8().get_data(), cs) != UNZ_OK) {
148+
return -1;
149+
}
150+
151+
int method;
152+
int level;
153+
if (unzOpenCurrentFile2(uzf, &method, &level, 1) != UNZ_OK) {
154+
return -1;
155+
}
156+
157+
unzCloseCurrentFile(uzf);
158+
159+
return level;
160+
}
161+
143162
ZIPReader::ZIPReader() {}
144163

145164
ZIPReader::~ZIPReader() {
@@ -154,4 +173,5 @@ void ZIPReader::_bind_methods() {
154173
ClassDB::bind_method(D_METHOD("get_files"), &ZIPReader::get_files);
155174
ClassDB::bind_method(D_METHOD("read_file", "path", "case_sensitive"), &ZIPReader::read_file, DEFVAL(Variant(true)));
156175
ClassDB::bind_method(D_METHOD("file_exists", "path", "case_sensitive"), &ZIPReader::file_exists, DEFVAL(Variant(true)));
176+
ClassDB::bind_method(D_METHOD("get_compression_level", "path", "case_sensitive"), &ZIPReader::get_compression_level, DEFVAL(Variant(true)));
157177
}

0 commit comments

Comments
 (0)