zip_kit 6.3.1 → 6.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +31 -8
- data/CHANGELOG.md +5 -0
- data/README.md +5 -0
- data/lib/zip_kit/output_enumerator.rb +11 -0
- data/lib/zip_kit/remote_io.rb +2 -2
- data/lib/zip_kit/size_estimator.rb +10 -4
- data/lib/zip_kit/streamer/heuristic.rb +8 -0
- data/lib/zip_kit/streamer.rb +23 -5
- data/lib/zip_kit/version.rb +1 -1
- data/rbi/zip_kit.rbi +13 -4
- data/zip_kit.gemspec +2 -0
- metadata +31 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ff5f4284066004d435d36f28020e6ef2279fe0abe36c520436db3bd39d7608a
|
4
|
+
data.tar.gz: ac9f9c4312c632410cee6ffb98c2ec471f9de16e5260793a30df8ac28639218d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34389f0a2d38a532af341c7694fcd10c2bbfb2f819ce9d1168afd51fbfc815b8c4ef0b36929f286f3ad31ead98479b07b871ec35d1d8f5adefbf2bb2717f3f58
|
7
|
+
data.tar.gz: 821151643cc5adafd9fe3446412e9d9de60ff049f6bab3ca1e9a7c0dc24dbcef0688b8bc0ab1fa51d88feb85892b3707ccbf8483e41c532680c2e9ddb073e823
|
data/.github/workflows/ci.yml
CHANGED
@@ -7,23 +7,46 @@ env:
|
|
7
7
|
BUNDLE_PATH: vendor/bundle
|
8
8
|
|
9
9
|
jobs:
|
10
|
-
|
11
|
-
name: Tests
|
10
|
+
test_baseline_ruby:
|
11
|
+
name: "Tests (Ruby 2.6 baseline)"
|
12
12
|
runs-on: ubuntu-22.04
|
13
|
-
strategy:
|
14
|
-
matrix:
|
15
|
-
ruby:
|
16
|
-
- '2.6'
|
17
|
-
- '3.3'
|
18
13
|
steps:
|
19
14
|
- name: Checkout
|
20
15
|
uses: actions/checkout@v4
|
21
16
|
- name: Setup Ruby
|
22
17
|
uses: ruby/setup-ruby@v1
|
23
18
|
with:
|
24
|
-
ruby-version:
|
19
|
+
ruby-version: '2.6'
|
25
20
|
bundler-cache: true
|
26
21
|
- name: "Tests"
|
27
22
|
run: bundle exec rspec --backtrace --fail-fast
|
23
|
+
|
24
|
+
test_newest_ruby:
|
25
|
+
name: "Tests (Ruby 3.4 with frozen string literals)"
|
26
|
+
runs-on: ubuntu-22.04
|
27
|
+
steps:
|
28
|
+
- name: Checkout
|
29
|
+
uses: actions/checkout@v4
|
30
|
+
- name: Setup Ruby
|
31
|
+
uses: ruby/setup-ruby@v1
|
32
|
+
with:
|
33
|
+
ruby-version: '3.4.1'
|
34
|
+
bundler-cache: true
|
35
|
+
- name: "Tests" # Make the test suite hard-crash on frozen string literal violations
|
36
|
+
env:
|
37
|
+
RUBYOPT: "--enable=frozen-string-literal --debug=frozen-string-literal"
|
38
|
+
run: "bundle exec rspec --backtrace --fail-fast"
|
39
|
+
|
40
|
+
lint_baseline_ruby: # We need to use syntax appropriate for the minimum supported Ruby version
|
41
|
+
name: Lint (Ruby 2.6 syntax)
|
42
|
+
runs-on: ubuntu-22.04
|
43
|
+
steps:
|
44
|
+
- name: Checkout
|
45
|
+
uses: actions/checkout@v4
|
46
|
+
- name: Setup Ruby
|
47
|
+
uses: ruby/setup-ruby@v1
|
48
|
+
with:
|
49
|
+
ruby-version: '2.6'
|
50
|
+
bundler-cache: true
|
28
51
|
- name: "Lint"
|
29
52
|
run: bundle exec rake standard
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 6.3.2
|
2
|
+
|
3
|
+
* Make sure `rollback!` correctly works with `write_file` and the original exception gets re-raised from `write_file` if
|
4
|
+
closing the current entry happens in `Writable#close`
|
5
|
+
|
1
6
|
## 6.3.1
|
2
7
|
|
3
8
|
* Include `RailsStreaming` in a Rails loader callback, so that ActionController does not need to be in the namespace.
|
data/README.md
CHANGED
@@ -21,6 +21,11 @@ the library can be used to generate JAR files, EPUBs, OpenOffice/Office document
|
|
21
21
|
|
22
22
|
## How does it work? How is it different from Rubyzip?
|
23
23
|
|
24
|
+
zip_kit outputs the metadata of the ZIP file as it becomes available. Same for the content of the ZIP
|
25
|
+
entries. This allows nearly-unbuffered, streaming output. When reading ZIP files, zip_kit only reads
|
26
|
+
the metadata and does so in an accelerated, efficient way - permitting ZIP unarchiving directly from
|
27
|
+
a resource on HTTP (provided that the server supports HTTP ranges).
|
28
|
+
|
24
29
|
Check out [the implementation details](IMPLEMENTATION_DETAILS.md) on the design of the library, and
|
25
30
|
we have a separate [reference](RUBYZIP_DIFFERENCES.md) on why you might want to use ZipKit over
|
26
31
|
Rubyzip and vice versa.
|
@@ -112,6 +112,17 @@ class ZipKit::OutputEnumerator
|
|
112
112
|
# but are of a file format built "on top" of ZIPs - such as ODTs, [pkpass files](https://developer.apple.com/documentation/walletpasses/building_a_pass)
|
113
113
|
# and ePubs.
|
114
114
|
#
|
115
|
+
# More value, however, is in the "technical" headers this method will provide. It will take the following steps to make sure streaming works correctly.
|
116
|
+
#
|
117
|
+
# * `Last-Modified` will be set to "now" so that the response is considered "fresh" by `Rack::ETag`. This is done so that `Rack::ETag` won't try to
|
118
|
+
# calculate a lax ETag value and thus won't start buffering your response out of nowhere
|
119
|
+
# * `Content-Encoding` will be set to `identity`. This is so that proxies or the Rack middleware that applies compression to the response (like gzip)
|
120
|
+
# is not going to try to compress your response. It also tells the receiving browsers (or downstream proxies) that they should not attempt to
|
121
|
+
# open or uncompress the response before saving it or passing it onwards.
|
122
|
+
# * `X-Accel-Buffering` will be set to 'no` - this tells both nginx and the Google Cloud load balancer that the response should not be buffered
|
123
|
+
#
|
124
|
+
# These header values are known to get as close as possible to guaranteeing streaming on most environments where Ruby web applications may be hosted.
|
125
|
+
#
|
115
126
|
# @return [Hash]
|
116
127
|
def self.streaming_http_headers
|
117
128
|
_headers = {
|
data/lib/zip_kit/remote_io.rb
CHANGED
@@ -74,7 +74,7 @@ class ZipKit::RemoteIO
|
|
74
74
|
# @param range[Range] the HTTP range of data to fetch from remote
|
75
75
|
# @return [String] the response body of the ranged request
|
76
76
|
def request_range(range)
|
77
|
-
http = Net::HTTP.start(@uri.hostname, @uri.port)
|
77
|
+
http = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https")
|
78
78
|
request = Net::HTTP::Get.new(@uri)
|
79
79
|
request.range = range
|
80
80
|
response = http.request(request)
|
@@ -91,7 +91,7 @@ class ZipKit::RemoteIO
|
|
91
91
|
#
|
92
92
|
# @return [Integer] the size of the remote resource, parsed either from Content-Length or Content-Range header
|
93
93
|
def request_object_size
|
94
|
-
http = Net::HTTP.start(@uri.hostname, @uri.port)
|
94
|
+
http = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https")
|
95
95
|
request = Net::HTTP::Get.new(@uri)
|
96
96
|
request.range = 0..0
|
97
97
|
response = http.request(request)
|
@@ -36,8 +36,11 @@ class ZipKit::SizeEstimator
|
|
36
36
|
#
|
37
37
|
# @param filename [String] the name of the file (filenames are variable-width in the ZIP)
|
38
38
|
# @param size [Integer] size of the uncompressed entry
|
39
|
-
# @param use_data_descriptor[Boolean] whether
|
40
|
-
#
|
39
|
+
# @param use_data_descriptor[Boolean] whether there is going to be a data descriptor written
|
40
|
+
# after the entry body, to specify size.
|
41
|
+
# You must enable this if you are going to be
|
42
|
+
# using {Streamer#write_stored_file} as otherwise your
|
43
|
+
# estimated size is not going to be accurate
|
41
44
|
# @return self
|
42
45
|
def add_stored_entry(filename:, size:, use_data_descriptor: false)
|
43
46
|
@streamer.add_stored_entry(filename: filename,
|
@@ -56,8 +59,11 @@ class ZipKit::SizeEstimator
|
|
56
59
|
# @param filename [String] the name of the file (filenames are variable-width in the ZIP)
|
57
60
|
# @param uncompressed_size [Integer] size of the uncompressed entry
|
58
61
|
# @param compressed_size [Integer] size of the compressed entry
|
59
|
-
# @param use_data_descriptor[Boolean] whether
|
60
|
-
#
|
62
|
+
# @param use_data_descriptor[Boolean] whether there is going to be a data descriptor written
|
63
|
+
# after the entry body, to specify size.
|
64
|
+
# You must enable this if you are going to be
|
65
|
+
# using {Streamer#write_deflated_file} as otherwise your
|
66
|
+
# estimated size is not going to be accurate
|
61
67
|
# @return self
|
62
68
|
def add_deflated_entry(filename:, uncompressed_size:, compressed_size:, use_data_descriptor: false)
|
63
69
|
@streamer.add_deflated_entry(filename: filename,
|
@@ -26,6 +26,7 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
|
|
26
26
|
@bytes_deflated = 0
|
27
27
|
|
28
28
|
@winner = nil
|
29
|
+
@started_closing = false
|
29
30
|
end
|
30
31
|
|
31
32
|
def <<(bytes)
|
@@ -40,6 +41,9 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
|
|
40
41
|
end
|
41
42
|
|
42
43
|
def close
|
44
|
+
return if @started_closing
|
45
|
+
@started_closing = true # started_closing because an exception may get raised inside close(), as we add an entry there
|
46
|
+
|
43
47
|
decide unless @winner
|
44
48
|
@winner.close
|
45
49
|
end
|
@@ -47,6 +51,7 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
|
|
47
51
|
private def decide
|
48
52
|
# Finish and then close the deflater - it has likely buffered some data
|
49
53
|
@bytes_deflated += @deflater.finish.bytesize until @deflater.finished?
|
54
|
+
|
50
55
|
# If the deflated version is smaller than the stored one
|
51
56
|
# - use deflate, otherwise stored
|
52
57
|
ratio = @bytes_deflated / @buf.size.to_f
|
@@ -55,9 +60,12 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
|
|
55
60
|
else
|
56
61
|
@streamer.write_stored_file(@filename, **@write_file_options)
|
57
62
|
end
|
63
|
+
|
58
64
|
# Copy the buffered uncompressed data into the newly initialized writable
|
59
65
|
@buf.rewind
|
60
66
|
IO.copy_stream(@buf, @winner)
|
61
67
|
@buf.truncate(0)
|
68
|
+
ensure
|
69
|
+
@deflater.close
|
62
70
|
end
|
63
71
|
end
|
data/lib/zip_kit/streamer.rb
CHANGED
@@ -5,8 +5,12 @@ require "set"
|
|
5
5
|
# Is used to write ZIP archives without having to read them back or to overwrite
|
6
6
|
# data. It outputs into any object that supports `<<` or `write`, namely:
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
8
|
+
# * `Array` - will contain binary strings
|
9
|
+
# * `File` - data will be written to it as it gets generated
|
10
|
+
# * `IO` (`Socket`, `StringIO`) - data gets written into it
|
11
|
+
# * `String` - in binary encoding and unfrozen - also makes a decent output target
|
12
|
+
#
|
13
|
+
# or anything else that responds to `#<<` or `#write`.
|
10
14
|
#
|
11
15
|
# You can also combine output through the `Streamer` with direct output to the destination,
|
12
16
|
# all while preserving the correct offsets in the ZIP file structures. This allows usage
|
@@ -482,6 +486,10 @@ class ZipKit::Streamer
|
|
482
486
|
# is likely already on the wire. However, excluding the entry from the central directory of the ZIP
|
483
487
|
# file will allow better-behaved ZIP unarchivers to extract the entries which did store correctly,
|
484
488
|
# provided they read the ZIP from the central directory and not straight-ahead.
|
489
|
+
# Rolling back does not perform any writes.
|
490
|
+
#
|
491
|
+
# `rollback!` gets called for you if an exception is raised inside the block of `write_file`,
|
492
|
+
# `write_deflated_file` and `write_stored_file`.
|
485
493
|
#
|
486
494
|
# @example
|
487
495
|
# zip.add_stored_entry(filename: "data.bin", size: 4.megabytes, crc32: the_crc)
|
@@ -493,14 +501,17 @@ class ZipKit::Streamer
|
|
493
501
|
# end
|
494
502
|
# @return [Integer] position in the output stream / ZIP archive
|
495
503
|
def rollback!
|
496
|
-
|
497
|
-
return @out.tell unless removed_entry
|
504
|
+
@files.pop if @remove_last_file_at_rollback
|
498
505
|
|
506
|
+
# Recreate the path set from remaining entries (PathSet does not support cheap deletes yet)
|
499
507
|
@path_set.clear
|
500
508
|
@files.each do |e|
|
501
509
|
@path_set.add_directory_or_file_path(e.filename) unless e.filler?
|
502
510
|
end
|
503
|
-
|
511
|
+
|
512
|
+
# Create filler for the truncated or unusable local file entry that did get written into the output
|
513
|
+
filler_size_bytes = @out.tell - @offset_before_last_local_file_header
|
514
|
+
@files << Filler.new(filler_size_bytes)
|
504
515
|
|
505
516
|
@out.tell
|
506
517
|
end
|
@@ -554,6 +565,11 @@ class ZipKit::Streamer
|
|
554
565
|
use_data_descriptor:,
|
555
566
|
unix_permissions:
|
556
567
|
)
|
568
|
+
# Set state needed for proper rollback later. If write_local_file_header
|
569
|
+
# does manage to write _some_ bytes, but fails later (we write in tiny bits sometimes)
|
570
|
+
# we should be able to create a filler from this offset on when we
|
571
|
+
@offset_before_last_local_file_header = @out.tell
|
572
|
+
@remove_last_file_at_rollback = false
|
557
573
|
|
558
574
|
# Clean backslashes
|
559
575
|
filename = remove_backslash(filename)
|
@@ -600,9 +616,11 @@ class ZipKit::Streamer
|
|
600
616
|
mtime: e.mtime,
|
601
617
|
filename: e.filename,
|
602
618
|
storage_mode: e.storage_mode)
|
619
|
+
|
603
620
|
e.bytes_used_for_local_header = @out.tell - e.local_header_offset
|
604
621
|
|
605
622
|
@files << e
|
623
|
+
@remove_last_file_at_rollback = true
|
606
624
|
end
|
607
625
|
|
608
626
|
def remove_backslash(filename)
|
data/lib/zip_kit/version.rb
CHANGED
data/rbi/zip_kit.rbi
CHANGED
@@ -1897,13 +1897,11 @@ end, T.untyped)
|
|
1897
1897
|
|
1898
1898
|
# Add a fake entry to the archive, to see how big it is going to be in the end.
|
1899
1899
|
#
|
1900
|
-
# data descriptor to specify size
|
1901
|
-
#
|
1902
1900
|
# _@param_ `filename` — the name of the file (filenames are variable-width in the ZIP)
|
1903
1901
|
#
|
1904
1902
|
# _@param_ `size` — size of the uncompressed entry
|
1905
1903
|
#
|
1906
|
-
# _@param_ `use_data_descriptor` — whether the entry
|
1904
|
+
# _@param_ `use_data_descriptor` — whether there is going to be a data descriptor written after the entry body, to specify size. You must enable this if you are going to be using {Streamer#write_stored_file} as otherwise your estimated size is not going to be accurate
|
1907
1905
|
#
|
1908
1906
|
# _@return_ — self
|
1909
1907
|
sig { params(filename: String, size: Integer, use_data_descriptor: T::Boolean).returns(T.untyped) }
|
@@ -1917,7 +1915,7 @@ end, T.untyped)
|
|
1917
1915
|
#
|
1918
1916
|
# _@param_ `compressed_size` — size of the compressed entry
|
1919
1917
|
#
|
1920
|
-
# _@param_ `use_data_descriptor` — whether
|
1918
|
+
# _@param_ `use_data_descriptor` — whether there is going to be a data descriptor written after the entry body, to specify size. You must enable this if you are going to be using {Streamer#write_deflated_file} as otherwise your estimated size is not going to be accurate
|
1921
1919
|
#
|
1922
1920
|
# _@return_ — self
|
1923
1921
|
sig do
|
@@ -2102,6 +2100,17 @@ end, T.untyped)
|
|
2102
2100
|
# ones - for example, specific content types are needed for files which are, technically, ZIP files
|
2103
2101
|
# but are of a file format built "on top" of ZIPs - such as ODTs, [pkpass files](https://developer.apple.com/documentation/walletpasses/building_a_pass)
|
2104
2102
|
# and ePubs.
|
2103
|
+
#
|
2104
|
+
# More value, however, is in the "technical" headers this method will provide. It will take the following steps to make sure streaming works correctly.
|
2105
|
+
#
|
2106
|
+
# * `Last-Modified` will be set to "now" so that the response is considered "fresh" by `Rack::ETag`. This is done so that `Rack::ETag` won't try to
|
2107
|
+
# calculate a lax ETag value and thus won't start buffering your response out of nowhere
|
2108
|
+
# * `Content-Encoding` will be set to `identity`. This is so that proxies or the Rack middleware that applies compression to the response (like gzip)
|
2109
|
+
# is not going to try to compress your response. It also tells the receiving browsers (or downstream proxies) that they should not attempt to
|
2110
|
+
# open or uncompress the response before saving it or passing it onwards.
|
2111
|
+
# * `X-Accel-Buffering` will be set to 'no` - this tells both nginx and the Google Cloud load balancer that the response should not be buffered
|
2112
|
+
#
|
2113
|
+
# These header values are known to get as close as possible to guaranteeing streaming on most environments where Ruby web applications may be hosted.
|
2105
2114
|
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
2106
2115
|
def self.streaming_http_headers; end
|
2107
2116
|
|
data/zip_kit.gemspec
CHANGED
@@ -40,6 +40,8 @@ Gem::Specification.new do |spec|
|
|
40
40
|
spec.add_development_dependency "standard", "1.28.5" # Very specific version of standard for 2.6 with _known_ settings
|
41
41
|
spec.add_development_dependency "magic_frozen_string_literal"
|
42
42
|
spec.add_development_dependency "puma"
|
43
|
+
spec.add_development_dependency "mutex_m" # Some deps use it but it is no longer in stdlib since 3.4
|
44
|
+
spec.add_development_dependency "bigdecimal" # Some deps use it but it is no longer in stdlib since 3.4
|
43
45
|
spec.add_development_dependency "rails", "~> 5" # For testing RailsStreaming against an actual Rails controller
|
44
46
|
spec.add_development_dependency "actionpack", "~> 5" # For testing RailsStreaming against an actual Rails controller
|
45
47
|
spec.add_development_dependency "nokogiri", "~> 1", ">= 1.13" # Rails 5 does by mistake use an older Nokogiri otherwise
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zip_kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.3.
|
4
|
+
version: 6.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
@@ -9,10 +9,9 @@ authors:
|
|
9
9
|
- Dmitry Tymchuk
|
10
10
|
- David Bosveld
|
11
11
|
- Felix Bünemann
|
12
|
-
autorequire:
|
13
12
|
bindir: exe
|
14
13
|
cert_chain: []
|
15
|
-
date:
|
14
|
+
date: 2025-02-25 00:00:00.000000000 Z
|
16
15
|
dependencies:
|
17
16
|
- !ruby/object:Gem::Dependency
|
18
17
|
name: bundler
|
@@ -216,6 +215,34 @@ dependencies:
|
|
216
215
|
- - ">="
|
217
216
|
- !ruby/object:Gem::Version
|
218
217
|
version: '0'
|
218
|
+
- !ruby/object:Gem::Dependency
|
219
|
+
name: mutex_m
|
220
|
+
requirement: !ruby/object:Gem::Requirement
|
221
|
+
requirements:
|
222
|
+
- - ">="
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: '0'
|
225
|
+
type: :development
|
226
|
+
prerelease: false
|
227
|
+
version_requirements: !ruby/object:Gem::Requirement
|
228
|
+
requirements:
|
229
|
+
- - ">="
|
230
|
+
- !ruby/object:Gem::Version
|
231
|
+
version: '0'
|
232
|
+
- !ruby/object:Gem::Dependency
|
233
|
+
name: bigdecimal
|
234
|
+
requirement: !ruby/object:Gem::Requirement
|
235
|
+
requirements:
|
236
|
+
- - ">="
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: '0'
|
239
|
+
type: :development
|
240
|
+
prerelease: false
|
241
|
+
version_requirements: !ruby/object:Gem::Requirement
|
242
|
+
requirements:
|
243
|
+
- - ">="
|
244
|
+
- !ruby/object:Gem::Version
|
245
|
+
version: '0'
|
219
246
|
- !ruby/object:Gem::Dependency
|
220
247
|
name: rails
|
221
248
|
requirement: !ruby/object:Gem::Requirement
|
@@ -356,7 +383,6 @@ licenses:
|
|
356
383
|
- MIT
|
357
384
|
metadata:
|
358
385
|
allowed_push_host: https://rubygems.org
|
359
|
-
post_install_message:
|
360
386
|
rdoc_options: []
|
361
387
|
require_paths:
|
362
388
|
- lib
|
@@ -371,8 +397,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
371
397
|
- !ruby/object:Gem::Version
|
372
398
|
version: '0'
|
373
399
|
requirements: []
|
374
|
-
rubygems_version: 3.
|
375
|
-
signing_key:
|
400
|
+
rubygems_version: 3.6.2
|
376
401
|
specification_version: 4
|
377
402
|
summary: Stream out ZIP files from Ruby. Successor to zip_tricks.
|
378
403
|
test_files: []
|