zipping 0.2.3 → 0.2.5
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/README.md +2 -2
- data/lib/zipping/version.rb +1 -1
- data/lib/zipping.rb +341 -570
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 243995bc8d06cc56eb535cbca1b3ad5de5b61dbc
|
4
|
+
data.tar.gz: 5f563d079b0c7cbfbd0674f05c150e7ad330c7b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 201505429f0c6572fe1b0dea7ee4e440598696bd73a537ba663fc73a28f5901597dfe38286697d452139822ebbde5571877707e73b1a474bebb29b08f2804907
|
7
|
+
data.tar.gz: 42df47b06e8e292857f030d58f397f8b623f0df7805d64d392f1020706761a422c8b7afd416b1fb678b6cdbfcd1011d72d610c5bce4eb70c0a10af68eb2ed8c0
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
zipping
|
2
2
|
=======
|
3
|
-
|
3
|
+
<a href="http://badge.fury.io/rb/zipping"><img src="https://badge.fury.io/rb/zipping@2x.png" alt="Gem Version" height="18"></a>
|
4
4
|
|
5
5
|
This gem is for compressing files as a zip and outputting to a stream (or a stream-like interface object). The output to a stream proceeds little by little, as files are compressed.
|
6
6
|
|
@@ -65,4 +65,4 @@ Then, you get zip binary data in `zip_data`.
|
|
65
65
|
|
66
66
|
---
|
67
67
|
|
68
|
-
Copyright
|
68
|
+
Copyright [Nekojarashi Inc.](http://www.nekojarashi.com)
|
data/lib/zipping/version.rb
CHANGED
data/lib/zipping.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require "zipping/version"
|
3
3
|
require 'zip'
|
4
|
+
require 'pathname'
|
4
5
|
|
5
6
|
module Zipping
|
6
7
|
|
@@ -9,21 +10,33 @@ module Zipping
|
|
9
10
|
# Constants
|
10
11
|
#
|
11
12
|
|
13
|
+
# header signatures
|
12
14
|
ZS_c_pk0102 = 0x02014b50
|
13
15
|
ZS_c_pk0304 = 0x04034b50
|
14
16
|
ZS_c_pk0506 = 0x06054b50
|
15
17
|
ZS_c_pk0708 = 0x08074b50
|
16
|
-
|
17
|
-
|
18
|
+
|
19
|
+
# values for zip version
|
20
|
+
ZS_c_ver_nocomp = 0x000a
|
21
|
+
ZS_c_ver_deflate = 0x0014
|
18
22
|
ZS_c_ver_made = 0x0315
|
23
|
+
|
24
|
+
# values for option
|
19
25
|
ZS_c_opt_none = 0x0000
|
20
|
-
|
26
|
+
ZS_c_opt_0708 = 0x0008
|
27
|
+
|
28
|
+
# compression types
|
21
29
|
ZS_c_comp_deflate = 0x0008
|
30
|
+
ZS_c_comp_none = 0x0000
|
31
|
+
|
32
|
+
# empty values
|
22
33
|
ZS_c_int2_zero = 0x0000
|
23
34
|
ZS_c_int4_zero = 0x00000000
|
24
35
|
|
36
|
+
# OS-specific attributes (OS X)
|
25
37
|
ZS_c_oattr_dir = 0x41ed4000
|
26
38
|
ZS_c_oattr_file = 0x81a44000
|
39
|
+
ZS_c_oattr_symlink = 0xa1ed4000
|
27
40
|
|
28
41
|
|
29
42
|
|
@@ -32,679 +45,424 @@ module Zipping
|
|
32
45
|
#
|
33
46
|
|
34
47
|
def self.files_to_zip(output_stream, files, file_division_size = 1048576, encoding = :utf8)
|
35
|
-
builder = ZipBuilder.new output_stream,
|
36
|
-
builder.pack
|
48
|
+
builder = ZipBuilder.new output_stream, file_division_size, encoding
|
49
|
+
builder.pack files
|
50
|
+
builder.close
|
51
|
+
output_stream
|
52
|
+
end
|
53
|
+
|
54
|
+
# use if you need to make File directly.
|
55
|
+
def self.files_to_zipfile(path, files, encoding = :utf8)
|
56
|
+
File.open(path, 'wb'){|f| files_to_zip f, files, 1048576, encoding}
|
57
|
+
end
|
58
|
+
|
59
|
+
# use if you need to make binary String directly.
|
60
|
+
def self.files_to_zipdata(files, encoding = :utf8)
|
61
|
+
files_to_zip ''.force_encoding('ASCII-8bit'), files, 1048576, encoding
|
37
62
|
end
|
38
63
|
|
39
64
|
|
65
|
+
|
40
66
|
#
|
41
67
|
# Internal Classes
|
42
68
|
#
|
43
69
|
|
70
|
+
# Errors
|
71
|
+
class Error < StandardError
|
72
|
+
end
|
73
|
+
|
44
74
|
# Output stream wrapper that measures size of stream passing through.
|
45
75
|
class StreamMeter
|
46
76
|
def initialize(output_stream)
|
47
77
|
@output_stream = output_stream
|
48
78
|
@size = 0
|
79
|
+
@crc = Zlib.crc32
|
49
80
|
end
|
50
81
|
def << (data)
|
51
82
|
@size += data.length
|
83
|
+
@crc = Zlib.crc32 data, @crc
|
52
84
|
@output_stream << data
|
53
85
|
end
|
54
86
|
attr_reader :size
|
87
|
+
attr_reader :crc
|
55
88
|
end
|
56
89
|
|
57
90
|
class ZipBuilder
|
58
91
|
|
59
92
|
# Initialize ZipBuilder.
|
60
93
|
# 'files' must be a String(file or directory path), a Hash(entity), or an Array of Strings and/or Hashes.
|
61
|
-
def initialize(output_stream,
|
62
|
-
@
|
63
|
-
@f = ZipBuilder.to_entities files
|
94
|
+
def initialize(output_stream, file_division_size = 1048576, encoding = :utf8)
|
95
|
+
@w = Writer.new output_stream, file_division_size
|
64
96
|
@e = encoding
|
65
|
-
@
|
97
|
+
@l = []
|
66
98
|
end
|
67
99
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
entity = ZipBuilder.to_entity(files)
|
74
|
-
ret = entity.nil? ? [] : [entity]
|
75
|
-
elsif files.is_a? Array
|
76
|
-
ret = []
|
77
|
-
files.each do |f|
|
78
|
-
entity = ZipBuilder.to_entity(f)
|
79
|
-
ret << entity unless entity.nil?
|
80
|
-
end
|
81
|
-
else
|
82
|
-
ret = []
|
83
|
-
end
|
84
|
-
ret
|
100
|
+
### Attr controls
|
101
|
+
|
102
|
+
def reset_state
|
103
|
+
@pending_dirs = []
|
104
|
+
@current_dir = {name: '', time: Time.now}
|
85
105
|
end
|
86
106
|
|
87
|
-
|
88
|
-
|
89
|
-
return path if path.is_a?(Hash)
|
90
|
-
return nil unless path.is_a?(String) && File.exists?(path)
|
91
|
-
ret = {
|
92
|
-
:path => path,
|
93
|
-
:name => File.basename(path),
|
94
|
-
:time => Time.now
|
95
|
-
}
|
107
|
+
def has_dir?
|
108
|
+
! @pending_dirs.empty?
|
96
109
|
end
|
97
110
|
|
98
|
-
|
99
|
-
|
100
|
-
unless encoding.nil? || encoding == :utf8
|
101
|
-
case encoding
|
102
|
-
when :shift_jis
|
103
|
-
begin
|
104
|
-
str = str.encode 'Shift_JIS', :invalid => :replace, :undef => :replace, :replace => '??'
|
105
|
-
rescue => e
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
[str].pack('a*')
|
111
|
+
def next_dir
|
112
|
+
@pending_dirs.shift
|
110
113
|
end
|
111
114
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
+
def postpone_dir(dir)
|
116
|
+
queue_entity dir, @pending_dirs
|
117
|
+
end
|
115
118
|
|
116
|
-
|
117
|
-
|
119
|
+
def postpone_symlink(link)
|
120
|
+
queue_entity link, @l
|
121
|
+
end
|
122
|
+
|
123
|
+
def queue_entity(entity, queue)
|
124
|
+
entity[:abs_path] = abs_path_for entity[:name] || File.basename(entity[:path])
|
125
|
+
queue << entity
|
126
|
+
end
|
118
127
|
|
119
|
-
|
120
|
-
@current_dir =
|
121
|
-
|
128
|
+
def cd(dir)
|
129
|
+
@current_dir = dir
|
130
|
+
end
|
131
|
+
|
132
|
+
def root_dir?
|
133
|
+
@current_dir.nil? || @current_dir[:abs_path].nil?
|
134
|
+
end
|
135
|
+
|
136
|
+
def current_dir
|
137
|
+
root_dir? ? '' : @current_dir[:abs_path]
|
138
|
+
end
|
122
139
|
|
123
|
-
|
124
|
-
@
|
140
|
+
def current_dir_entity
|
141
|
+
@current_dir
|
142
|
+
end
|
125
143
|
|
126
|
-
|
144
|
+
def abs_path_for(name)
|
145
|
+
root_dir? ? name : (@current_dir[:abs_path] + '/' + name)
|
146
|
+
end
|
127
147
|
|
128
|
-
|
129
|
-
|
148
|
+
def abs_path_for_entity(entity)
|
149
|
+
abs_path_for entity[:name] || File.basename(entity[:path])
|
150
|
+
end
|
130
151
|
|
131
|
-
|
132
|
-
until @pending_dirs.empty? do
|
152
|
+
### Conversions
|
133
153
|
|
134
|
-
|
135
|
-
|
136
|
-
|
154
|
+
# Get entities of files in dir
|
155
|
+
def subdir_entities(dir = @current_dir)
|
156
|
+
Dir.glob(dir[:path] + '/*').map!{|path| {path: path, time: File.mtime(path), name: File.basename(path)}}
|
157
|
+
end
|
137
158
|
|
138
|
-
|
139
|
-
|
159
|
+
# Fix an entity: time -> DOSTime object, name -> abs path in zip & encoded
|
160
|
+
def fix_entity(entity)
|
161
|
+
{
|
162
|
+
path: entity[:path],
|
163
|
+
filetime: Zip::DOSTime.at(entity[:time] || File.mtime(entity[:path])),
|
164
|
+
binary_name: string_to_bytes(abs_path_for_entity(entity)),
|
165
|
+
zip_path: abs_path_for_entity(entity)
|
166
|
+
}
|
167
|
+
end
|
140
168
|
|
141
|
-
|
142
|
-
|
143
|
-
|
169
|
+
def fix_current_dir_entity
|
170
|
+
fix_entity(@current_dir).merge!(
|
171
|
+
{
|
172
|
+
binary_name: string_to_bytes(@current_dir[:abs_path] + '/'),
|
173
|
+
zip_path: @current_dir[:abs_path]
|
174
|
+
}
|
175
|
+
)
|
176
|
+
end
|
144
177
|
|
145
|
-
|
146
|
-
|
147
|
-
|
178
|
+
# Create ASCII-8bits string. Also convert encoding if needed.
|
179
|
+
def string_to_bytes(str)
|
180
|
+
unless @e.nil? || @e == :utf8
|
181
|
+
if @e == :shift_jis
|
182
|
+
begin
|
183
|
+
str = str.encode 'Shift_JIS', :invalid => :replace, :undef => :replace, :replace => '??'
|
184
|
+
rescue => e
|
185
|
+
end
|
148
186
|
end
|
149
187
|
end
|
188
|
+
[str].pack('a*')
|
189
|
+
end
|
150
190
|
|
151
|
-
|
152
|
-
@header_offset = o.size
|
153
|
-
o = StreamMeter.new(@o)
|
191
|
+
### Compression operations
|
154
192
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
193
|
+
# Pack file and directory entities and output to stream.
|
194
|
+
def pack(files)
|
195
|
+
entities = Entity.entities_from files
|
196
|
+
return if entities.empty?
|
197
|
+
|
198
|
+
reset_state
|
199
|
+
pack_entities entities
|
200
|
+
while has_dir?
|
201
|
+
cd next_dir
|
202
|
+
pack_current_dir
|
203
|
+
end
|
159
204
|
end
|
160
205
|
|
161
|
-
# Pack
|
162
|
-
def
|
163
|
-
|
164
|
-
|
206
|
+
# Pack a directory
|
207
|
+
def pack_current_dir
|
208
|
+
pack_current_directory_entity
|
209
|
+
pack_entities subdir_entities
|
210
|
+
end
|
165
211
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
file_path = f
|
174
|
-
fname = dir.clone << (file[:name] || File.basename(file_path))
|
175
|
-
bfname = ZipBuilder.to_bytes(fname, encoding)
|
176
|
-
filetime = Zip::DOSTime.at(file[:time] || File.mtime(file_path))
|
177
|
-
filesize = File.size(file_path)
|
178
|
-
pk = [
|
179
|
-
ZS_c_pk0304,
|
180
|
-
ZS_c_ver_file,
|
181
|
-
ZS_c_opt_nosize,
|
182
|
-
ZS_c_comp_deflate,
|
183
|
-
filetime.to_binary_dos_time,
|
184
|
-
filetime.to_binary_dos_date,
|
185
|
-
ZS_c_int4_zero,
|
186
|
-
ZS_c_int4_zero,
|
187
|
-
ZS_c_int4_zero,
|
188
|
-
bfname.length,
|
189
|
-
ZS_c_int2_zero
|
190
|
-
]
|
191
|
-
bin = pk.pack('VvvvvvVVVvv')
|
192
|
-
o << bin
|
193
|
-
bin = bfname
|
194
|
-
o << bin
|
195
|
-
|
196
|
-
m = StreamMeter.new(o)
|
197
|
-
d = Zip::Deflater.new(m)
|
198
|
-
File.open(file_path) do |f|
|
199
|
-
cur_filesize = filesize
|
200
|
-
while cur_filesize > 0
|
201
|
-
if cur_filesize >= file_division_size
|
202
|
-
d << f.read(file_division_size)
|
203
|
-
cur_filesize -= file_division_size
|
204
|
-
else
|
205
|
-
d << f.read(cur_filesize)
|
206
|
-
cur_filesize = 0
|
207
|
-
end
|
208
|
-
end
|
212
|
+
# Pack symlinks if its link path exists in zip
|
213
|
+
def pack_symlinks
|
214
|
+
reset_state
|
215
|
+
@l.each do |link|
|
216
|
+
if @w.path_exists? Entity.linked_path(link[:abs_path], File.readlink(link[:path]))
|
217
|
+
link[:name] = link[:abs_path]
|
218
|
+
pack_symbolic_link_entity link
|
209
219
|
end
|
210
|
-
d.finish
|
211
|
-
|
212
|
-
pk = [
|
213
|
-
ZS_c_pk0708,
|
214
|
-
d.crc,
|
215
|
-
m.size,
|
216
|
-
d.size
|
217
|
-
]
|
218
|
-
bin = pk.pack('VVVV')
|
219
|
-
o << bin
|
220
|
-
data_positions << {
|
221
|
-
:folder => false,
|
222
|
-
:file => fname,
|
223
|
-
:file_dos_time => filetime.to_binary_dos_time,
|
224
|
-
:file_dos_date => filetime.to_binary_dos_date,
|
225
|
-
:binary_fname => bfname,
|
226
|
-
:offset => data_offset,
|
227
|
-
:crc => d.crc,
|
228
|
-
:complen => m.size,
|
229
|
-
:uncomplen => d.size
|
230
|
-
}
|
231
220
|
end
|
232
221
|
end
|
233
222
|
|
234
|
-
#
|
235
|
-
def
|
236
|
-
|
237
|
-
|
238
|
-
filetime = Zip::DOSTime.at(time_created_at);
|
239
|
-
filesize = 0
|
240
|
-
pk = [
|
241
|
-
ZS_c_pk0304,
|
242
|
-
ZS_c_ver_dir,
|
243
|
-
ZS_c_opt_none,
|
244
|
-
ZS_c_comp_deflate,
|
245
|
-
filetime.to_binary_dos_time,
|
246
|
-
filetime.to_binary_dos_date,
|
247
|
-
ZS_c_int4_zero,
|
248
|
-
ZS_c_int4_zero,
|
249
|
-
ZS_c_int4_zero,
|
250
|
-
bdir.length,
|
251
|
-
ZS_c_int2_zero
|
252
|
-
]
|
253
|
-
bin = pk.pack('VvvvvvVVVvv')
|
254
|
-
o << bin
|
255
|
-
bin = bdir
|
256
|
-
o << bin
|
257
|
-
data_positions << {
|
258
|
-
:folder => true,
|
259
|
-
:file => dir,
|
260
|
-
:file_dos_time => filetime.to_binary_dos_time,
|
261
|
-
:file_dos_date => filetime.to_binary_dos_date,
|
262
|
-
:binary_fname => bdir,
|
263
|
-
:offset => data_offset,
|
264
|
-
:crc => ZS_c_int4_zero,
|
265
|
-
:complen => ZS_c_int4_zero,
|
266
|
-
:uncomplen => ZS_c_int4_zero
|
267
|
-
}
|
223
|
+
# Create central directories
|
224
|
+
def close
|
225
|
+
pack_symlinks
|
226
|
+
@w.close
|
268
227
|
end
|
269
228
|
|
270
|
-
# Pack
|
271
|
-
def
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
file_path = f
|
284
|
-
file = dir[:name] + File.basename(file_path)
|
285
|
-
bfile = ZipBuilder.to_bytes(file, encoding)
|
286
|
-
filetime = Zip::DOSTime.at(dir[:time] || File.mtime(file_path))
|
287
|
-
filesize = File.size(file_path)
|
288
|
-
pk = [
|
289
|
-
ZS_c_pk0304,
|
290
|
-
ZS_c_ver_file,
|
291
|
-
ZS_c_opt_nosize,
|
292
|
-
ZS_c_comp_deflate,
|
293
|
-
filetime.to_binary_dos_time,
|
294
|
-
filetime.to_binary_dos_date,
|
295
|
-
ZS_c_int4_zero,
|
296
|
-
ZS_c_int4_zero,
|
297
|
-
ZS_c_int4_zero,
|
298
|
-
bfile.length,
|
299
|
-
ZS_c_int2_zero
|
300
|
-
]
|
301
|
-
bin = pk.pack('VvvvvvVVVvv')
|
302
|
-
o << bin
|
303
|
-
bin = bfile
|
304
|
-
o << bin
|
305
|
-
|
306
|
-
m = StreamMeter.new(o)
|
307
|
-
d = Zip::Deflater.new(m)
|
308
|
-
File.open(file_path) do |f|
|
309
|
-
cur_filesize = filesize
|
310
|
-
while cur_filesize > 0
|
311
|
-
if cur_filesize >= file_division_size
|
312
|
-
d << f.read(file_division_size)
|
313
|
-
cur_filesize -= file_division_size
|
314
|
-
else
|
315
|
-
d << f.read(cur_filesize)
|
316
|
-
cur_filesize = 0
|
317
|
-
end
|
318
|
-
end
|
229
|
+
# Pack file entities. Directory entities are queued, not packed in this method.
|
230
|
+
def pack_entities(entities)
|
231
|
+
entities.each do |entity|
|
232
|
+
# ignore bad entities
|
233
|
+
next unless entity.is_a?(Hash) && entity[:path]
|
234
|
+
|
235
|
+
path = entity[:path]
|
236
|
+
if File.symlink? path
|
237
|
+
postpone_symlink entity
|
238
|
+
elsif File.directory? path
|
239
|
+
postpone_dir entity
|
240
|
+
elsif File.file? path
|
241
|
+
pack_file_entity entity
|
319
242
|
end
|
320
|
-
d.finish
|
321
|
-
|
322
|
-
pk = [
|
323
|
-
ZS_c_pk0708,
|
324
|
-
d.crc,
|
325
|
-
m.size,
|
326
|
-
d.size
|
327
|
-
]
|
328
|
-
bin = pk.pack('VVVV')
|
329
|
-
o << bin
|
330
|
-
data_positions << {
|
331
|
-
:folder => false,
|
332
|
-
:file => file,
|
333
|
-
:file_dos_time => filetime.to_binary_dos_time,
|
334
|
-
:file_dos_date => filetime.to_binary_dos_date,
|
335
|
-
:binary_fname => bfile,
|
336
|
-
:offset => data_offset,
|
337
|
-
:crc => d.crc,
|
338
|
-
:complen => m.size,
|
339
|
-
:uncomplen => d.size
|
340
|
-
}
|
341
243
|
end
|
342
244
|
end
|
343
245
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
pk = [
|
348
|
-
ZS_c_pk0102,
|
349
|
-
ZS_c_ver_made,
|
350
|
-
(dp[:folder] ? ZS_c_ver_dir : ZS_c_ver_file),
|
351
|
-
ZS_c_opt_nosize,
|
352
|
-
ZS_c_comp_deflate,
|
353
|
-
dp[:file_dos_time],
|
354
|
-
dp[:file_dos_date],
|
355
|
-
dp[:crc],
|
356
|
-
dp[:complen],
|
357
|
-
dp[:uncomplen],
|
358
|
-
dp[:binary_fname].length,
|
359
|
-
ZS_c_int2_zero,
|
360
|
-
ZS_c_int2_zero,
|
361
|
-
ZS_c_int2_zero,
|
362
|
-
ZS_c_int2_zero,
|
363
|
-
(dp[:folder] ? ZS_c_oattr_dir : ZS_c_oattr_file),
|
364
|
-
dp[:offset]
|
365
|
-
]
|
366
|
-
bin = pk.pack('VvvvvvvVVVvvvvvVV')
|
367
|
-
o << bin
|
368
|
-
bin = dp[:binary_fname]
|
369
|
-
o << bin
|
246
|
+
def pack_file_entity(entity)
|
247
|
+
pack_entity entity do
|
248
|
+
@w.write_file_entry
|
370
249
|
end
|
371
250
|
end
|
372
251
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
ZS_c_int2_zero,
|
378
|
-
ZS_c_int2_zero,
|
379
|
-
entry_count,
|
380
|
-
entry_count,
|
381
|
-
dirsize,
|
382
|
-
offset,
|
383
|
-
ZS_c_int2_zero
|
384
|
-
]
|
385
|
-
o << pk.pack('VvvvvVVv')
|
252
|
+
def pack_symbolic_link_entity(entity)
|
253
|
+
pack_entity entity do
|
254
|
+
@w.write_symbolic_link_entry
|
255
|
+
end
|
386
256
|
end
|
387
|
-
end
|
388
257
|
|
258
|
+
def pack_current_directory_entity
|
259
|
+
@w.load_entity fix_current_dir_entity
|
260
|
+
@w.write_directory_entry
|
261
|
+
end
|
389
262
|
|
390
|
-
|
391
|
-
|
392
|
-
|
263
|
+
def pack_entity(entity)
|
264
|
+
@w.load_entity fix_entity entity
|
265
|
+
yield
|
266
|
+
end
|
267
|
+
end
|
393
268
|
|
394
|
-
|
395
|
-
# You must pass an entity Hash (or Array of them) consists of :path(path of file to pack into zip) and :name(file path inside zip) to this method as 'target_files'.
|
396
|
-
# Optionally, you may add :time(time created at) to the entity Hashes.
|
397
|
-
def self.create_zip_file_with_file_entities(outputStream, target_files, usesjis = true, file_division_size = 1048576)
|
398
|
-
begin
|
399
|
-
|
400
|
-
# prepare entry list for zip
|
401
|
-
target_files = [target_files] unless target_files.instance_of? Array
|
402
|
-
entries = []
|
403
|
-
target_files.each do |file|
|
404
|
-
if file.is_a? String
|
405
|
-
begin
|
406
|
-
entry = {
|
407
|
-
:path => file,
|
408
|
-
:name => File.basename(file),
|
409
|
-
:time => File.mtime(file)
|
410
|
-
}
|
411
|
-
rescue => e
|
412
|
-
next
|
413
|
-
end
|
414
|
-
elsif file.is_a? Hash
|
415
|
-
next unless file[:path] && file[:path].is_a?(String)
|
416
|
-
entry = file
|
417
|
-
path = entry[:path]
|
418
|
-
entry[:name] = File.basename(path) unless entry[:name] && entry[:name].is_a?(String)
|
419
|
-
entry[:time] = File.mtime(path) unless entry[:time] && entry[:time].is_a?(Time)
|
420
|
-
else
|
421
|
-
next
|
422
|
-
end
|
423
|
-
entries << entry
|
424
|
-
end
|
425
|
-
return if entries.empty?
|
269
|
+
class Writer
|
426
270
|
|
427
|
-
|
428
|
-
|
271
|
+
def initialize(output_stream, file_division_size)
|
272
|
+
@output_stream = output_stream
|
273
|
+
raise Error, 'Specified output stream does not support `<<\' method.' unless @output_stream.respond_to? :<<
|
274
|
+
@o = StreamMeter.new output_stream
|
275
|
+
@s = file_division_size
|
276
|
+
raise Error, 'Bad file_division_size' unless @s.is_a?(Integer) && @s > 0
|
277
|
+
@dps = []
|
278
|
+
@entries = []
|
279
|
+
end
|
429
280
|
|
430
|
-
|
431
|
-
data_positions = []
|
281
|
+
### Data interface
|
432
282
|
|
433
|
-
|
434
|
-
|
283
|
+
def load_entity(entity)
|
284
|
+
@fixed_entity = entity
|
285
|
+
@entries << entity[:zip_path]
|
286
|
+
end
|
435
287
|
|
436
|
-
|
437
|
-
|
288
|
+
def path_exists?(abs_path)
|
289
|
+
@entries.include? abs_path
|
290
|
+
end
|
438
291
|
|
439
|
-
|
440
|
-
m = StreamMeter.new(o)
|
292
|
+
### Write operations
|
441
293
|
|
442
|
-
|
443
|
-
|
294
|
+
def write_file_entry
|
295
|
+
write_entry do |path, filetime, name|
|
296
|
+
write PKHeader.pk0304(filetime, name.length, true).pack('VvvvvvVVVvv'), name
|
297
|
+
ret = deflate_file path
|
298
|
+
write PKHeader.pk0708(ret[:crc], ret[:complen], ret[:uncomplen]).pack('VVVV')
|
299
|
+
ret
|
300
|
+
end
|
301
|
+
end
|
444
302
|
|
445
|
-
|
446
|
-
|
303
|
+
def write_directory_entry
|
304
|
+
write_entry_without_compress '', :dir
|
305
|
+
end
|
447
306
|
|
448
|
-
|
449
|
-
|
450
|
-
rescue => e
|
307
|
+
def write_symbolic_link_entry
|
308
|
+
write_entry_without_compress File.readlink(@fixed_entity[:path]), :symlink
|
451
309
|
end
|
452
|
-
end
|
453
310
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
311
|
+
def close
|
312
|
+
# start of central directories
|
313
|
+
@header_offset = current_position
|
314
|
+
write_central_dir_headers
|
458
315
|
|
459
|
-
|
460
|
-
|
316
|
+
# total size of central directories
|
317
|
+
@header_size = current_position - @header_offset
|
318
|
+
write_end_central_dir_header
|
319
|
+
end
|
461
320
|
|
462
|
-
|
463
|
-
|
464
|
-
|
321
|
+
### Internal methods
|
322
|
+
private
|
323
|
+
|
324
|
+
def write(*args)
|
325
|
+
args.each do |content|
|
326
|
+
@o << content
|
327
|
+
end
|
328
|
+
end
|
465
329
|
|
466
|
-
|
330
|
+
def current_position
|
331
|
+
@o.size
|
332
|
+
end
|
467
333
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
dir = current_directory[1]
|
472
|
-
dir << '/'
|
473
|
-
self.create_directory_entry(o, dir_path, dir, data_positions, usesjis, file_division_size)
|
334
|
+
def pipe_little_by_little(o, i)
|
335
|
+
o << i.read(@s) until i.eof?
|
336
|
+
end
|
474
337
|
|
475
|
-
|
476
|
-
|
338
|
+
def deflate_file(path)
|
339
|
+
meter = StreamMeter.new @o
|
340
|
+
deflater = Zip::Deflater.new meter
|
341
|
+
File.open(path, 'rb'){|f| pipe_little_by_little deflater, f}
|
342
|
+
deflater.finish
|
343
|
+
{
|
344
|
+
crc: deflater.crc,
|
345
|
+
complen: meter.size,
|
346
|
+
uncomplen: deflater.size,
|
347
|
+
deflated: true
|
348
|
+
}
|
349
|
+
end
|
477
350
|
|
351
|
+
def write_entry_without_compress(data, type)
|
352
|
+
write_entry do |path, filetime, name|
|
353
|
+
crc = Zlib.crc32 data
|
354
|
+
write PKHeader.pk0304(filetime, name.length, false, crc, data.size, data.size).pack('VvvvvvVVVvv'), name, data
|
355
|
+
{
|
356
|
+
type: type,
|
357
|
+
crc: crc,
|
358
|
+
complen: data.size,
|
359
|
+
uncomplen: data.size
|
360
|
+
}
|
478
361
|
end
|
362
|
+
end
|
479
363
|
|
480
|
-
|
481
|
-
|
482
|
-
|
364
|
+
def write_entry
|
365
|
+
remember_entry_info current_position, yield(@fixed_entity[:path], @fixed_entity[:filetime], @fixed_entity[:binary_name])
|
366
|
+
end
|
483
367
|
|
484
|
-
|
485
|
-
|
368
|
+
def remember_entry_info(offset, write_result)
|
369
|
+
@dps << @fixed_entity.merge!(write_result).merge!({offset: offset})
|
486
370
|
end
|
487
|
-
end
|
488
371
|
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
if usesjis
|
493
|
-
begin
|
494
|
-
str = str.encode 'Shift_JIS', :invalid => :replace, :replace => '??'
|
495
|
-
rescue => e
|
372
|
+
def write_central_dir_headers
|
373
|
+
@dps.each do |dp|
|
374
|
+
write PKHeader.pk0102(dp).pack('VvvvvvvVVVvvvvvVV'), dp[:binary_name]
|
496
375
|
end
|
497
376
|
end
|
498
|
-
return [str].pack('a*')
|
499
|
-
end
|
500
377
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
bdir = self.get_binary_fname(dir, usesjis)
|
505
|
-
data_offset = o.size
|
506
|
-
filetime = Zip::DOSTime.at(File.mtime(dir_path))
|
507
|
-
filesize = 0
|
508
|
-
pk = [
|
509
|
-
ZS_c_pk0304,
|
510
|
-
ZS_c_ver_dir,
|
511
|
-
ZS_c_opt_none,
|
512
|
-
ZS_c_comp_deflate,
|
513
|
-
filetime.to_binary_dos_time,
|
514
|
-
filetime.to_binary_dos_date,
|
515
|
-
ZS_c_int4_zero,
|
516
|
-
ZS_c_int4_zero,
|
517
|
-
ZS_c_int4_zero,
|
518
|
-
bdir.length,
|
519
|
-
ZS_c_int2_zero
|
520
|
-
]
|
521
|
-
bin = pk.pack('VvvvvvVVVvv')
|
522
|
-
o << bin
|
523
|
-
bin = bdir
|
524
|
-
o << bin
|
525
|
-
data_positions << {
|
526
|
-
:folder => true,
|
527
|
-
:file => dir,
|
528
|
-
:file_dos_time => filetime.to_binary_dos_time,
|
529
|
-
:file_dos_date => filetime.to_binary_dos_date,
|
530
|
-
:binary_fname => bdir,
|
531
|
-
:offset => data_offset,
|
532
|
-
:crc => ZS_c_int4_zero,
|
533
|
-
:complen => ZS_c_int4_zero,
|
534
|
-
:uncomplen => ZS_c_int4_zero
|
535
|
-
}
|
378
|
+
def write_end_central_dir_header
|
379
|
+
write PKHeader.pk0506(@dps.length, @header_size, @header_offset).pack('VvvvvVVv')
|
380
|
+
end
|
536
381
|
end
|
537
382
|
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
383
|
+
class Entity
|
384
|
+
def self.entities_from(files)
|
385
|
+
if files.is_a? Array
|
386
|
+
entities_from_array files
|
387
|
+
else
|
388
|
+
[entity_from(files)]
|
389
|
+
end
|
390
|
+
end
|
545
391
|
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
ZS_c_pk0304,
|
553
|
-
ZS_c_ver_file,
|
554
|
-
ZS_c_opt_nosize,
|
555
|
-
ZS_c_comp_deflate,
|
556
|
-
mtime.to_binary_dos_time,
|
557
|
-
mtime.to_binary_dos_date,
|
558
|
-
ZS_c_int4_zero,
|
559
|
-
ZS_c_int4_zero,
|
560
|
-
ZS_c_int4_zero,
|
561
|
-
b_name.length,
|
562
|
-
ZS_c_int2_zero
|
563
|
-
]
|
564
|
-
bin = pk.pack('VvvvvvVVVvv')
|
565
|
-
o << bin
|
566
|
-
bin = b_name
|
567
|
-
o << bin
|
568
|
-
|
569
|
-
m = StreamMeter.new(o)
|
570
|
-
d = Zip::Deflater.new(m)
|
571
|
-
File.open(path) do |f|
|
572
|
-
cur_filesize = filesize
|
573
|
-
while cur_filesize > 0
|
574
|
-
if cur_filesize >= file_division_size
|
575
|
-
d << f.read(file_division_size)
|
576
|
-
cur_filesize -= file_division_size
|
577
|
-
else
|
578
|
-
d << f.read(cur_filesize)
|
579
|
-
cur_filesize = 0
|
580
|
-
end
|
581
|
-
end
|
392
|
+
# Create an entity Hash with a path String
|
393
|
+
def self.entity_from(ent)
|
394
|
+
if ent.is_a?(Hash) && File.exists?(ent[:path])
|
395
|
+
ent
|
396
|
+
elsif ent.is_a?(String) && File.exists?(ent)
|
397
|
+
entity_from_path ent
|
582
398
|
end
|
583
|
-
|
399
|
+
end
|
584
400
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
:folder => false,
|
595
|
-
:file => name,
|
596
|
-
:file_dos_time => filetime.to_binary_dos_time,
|
597
|
-
:file_dos_date => filetime.to_binary_dos_date,
|
598
|
-
:binary_fname => b_name,
|
599
|
-
:offset => data_offset,
|
600
|
-
:crc => d.crc,
|
601
|
-
:complen => m.size,
|
602
|
-
:uncomplen => d.size
|
401
|
+
def self.entities_from_array(arr)
|
402
|
+
arr.map{|ent| entity_from ent}.delete_if(&:nil?)
|
403
|
+
end
|
404
|
+
|
405
|
+
def self.entity_from_path(path)
|
406
|
+
{
|
407
|
+
:path => path,
|
408
|
+
:name => File.basename(path),
|
409
|
+
:time => File.mtime(path)
|
603
410
|
}
|
604
411
|
end
|
605
412
|
|
413
|
+
def self.linked_path(abs_path, link)
|
414
|
+
(Pathname.new(abs_path).parent + link).expand_path('/').to_s[1..-1]
|
415
|
+
end
|
606
416
|
end
|
607
417
|
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
418
|
+
module PKHeader
|
419
|
+
# 0102: central directory
|
420
|
+
def self.pk0102(dp)
|
421
|
+
# dp: info of an entry
|
422
|
+
[
|
423
|
+
ZS_c_pk0102,
|
424
|
+
ZS_c_ver_made,
|
425
|
+
(dp[:deflated] ? ZS_c_ver_nocomp : ZS_c_ver_deflate),
|
426
|
+
(dp[:deflated] ? ZS_c_opt_0708 : ZS_c_opt_none),
|
427
|
+
(dp[:deflated] ? ZS_c_comp_deflate : ZS_c_comp_none),
|
428
|
+
dp[:filetime].to_binary_dos_time,
|
429
|
+
dp[:filetime].to_binary_dos_date,
|
430
|
+
dp[:crc],
|
431
|
+
dp[:complen],
|
432
|
+
dp[:uncomplen],
|
433
|
+
dp[:binary_name].length,
|
434
|
+
ZS_c_int2_zero,
|
435
|
+
ZS_c_int2_zero,
|
436
|
+
ZS_c_int2_zero,
|
437
|
+
ZS_c_int2_zero,
|
438
|
+
(dp[:type] == :symlink ? ZS_c_oattr_symlink : dp[:type] == :dir ? ZS_c_oattr_dir : ZS_c_oattr_file),
|
439
|
+
dp[:offset]
|
440
|
+
]
|
441
|
+
end
|
442
|
+
|
443
|
+
# 0304: header of entries
|
444
|
+
def self.pk0304(filetime, namelen, deflated, crc = nil, compsize = nil, uncompsize = nil)
|
445
|
+
[
|
622
446
|
ZS_c_pk0304,
|
623
|
-
|
624
|
-
|
625
|
-
ZS_c_comp_deflate,
|
447
|
+
(deflated ? ZS_c_ver_deflate : ZS_c_ver_nocomp),
|
448
|
+
(deflated ? ZS_c_opt_0708 : ZS_c_opt_none),
|
449
|
+
(deflated ? ZS_c_comp_deflate : ZS_c_comp_none),
|
626
450
|
filetime.to_binary_dos_time,
|
627
451
|
filetime.to_binary_dos_date,
|
628
|
-
ZS_c_int4_zero,
|
629
|
-
ZS_c_int4_zero,
|
630
|
-
ZS_c_int4_zero,
|
631
|
-
|
452
|
+
(crc || ZS_c_int4_zero),
|
453
|
+
(compsize || ZS_c_int4_zero),
|
454
|
+
(uncompsize || ZS_c_int4_zero),
|
455
|
+
namelen,
|
632
456
|
ZS_c_int2_zero
|
633
457
|
]
|
634
|
-
bin = pk.pack('VvvvvvVVVvv')
|
635
|
-
o << bin
|
636
|
-
bin = bfile
|
637
|
-
o << bin
|
638
|
-
|
639
|
-
m = StreamMeter.new(o)
|
640
|
-
d = Zip::Deflater.new(m)
|
641
|
-
File.open(file_path) do |f|
|
642
|
-
cur_filesize = filesize
|
643
|
-
while cur_filesize > 0
|
644
|
-
if cur_filesize >= file_division_size
|
645
|
-
d << f.read(file_division_size)
|
646
|
-
cur_filesize -= file_division_size
|
647
|
-
else
|
648
|
-
d << f.read(cur_filesize)
|
649
|
-
cur_filesize = 0
|
650
|
-
end
|
651
|
-
end
|
652
|
-
end
|
653
|
-
d.finish
|
654
|
-
|
655
|
-
pk = [
|
656
|
-
ZS_c_pk0708,
|
657
|
-
d.crc,
|
658
|
-
m.size,
|
659
|
-
d.size
|
660
|
-
]
|
661
|
-
bin = pk.pack('VVVV')
|
662
|
-
o << bin
|
663
|
-
data_positions << {
|
664
|
-
:folder => false,
|
665
|
-
:file => file,
|
666
|
-
:file_dos_time => filetime.to_binary_dos_time,
|
667
|
-
:file_dos_date => filetime.to_binary_dos_date,
|
668
|
-
:binary_fname => bfile,
|
669
|
-
:offset => data_offset,
|
670
|
-
:crc => d.crc,
|
671
|
-
:complen => m.size,
|
672
|
-
:uncomplen => d.size
|
673
|
-
}
|
674
458
|
end
|
675
459
|
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
ZS_c_ver_made,
|
683
|
-
(dp[:folder] ? ZS_c_ver_dir : ZS_c_ver_file),
|
684
|
-
ZS_c_opt_nosize,
|
685
|
-
ZS_c_comp_deflate,
|
686
|
-
dp[:file_dos_time],
|
687
|
-
dp[:file_dos_date],
|
688
|
-
dp[:crc],
|
689
|
-
dp[:complen],
|
690
|
-
dp[:uncomplen],
|
691
|
-
dp[:binary_fname].length,
|
692
|
-
ZS_c_int2_zero,
|
693
|
-
ZS_c_int2_zero,
|
694
|
-
ZS_c_int2_zero,
|
695
|
-
ZS_c_int2_zero,
|
696
|
-
(dp[:folder] ? ZS_c_oattr_dir : ZS_c_oattr_file),
|
697
|
-
dp[:offset]
|
698
|
-
]
|
699
|
-
bin = pk.pack('VvvvvvvVVVvvvvvVV')
|
700
|
-
o << bin
|
701
|
-
bin = dp[:binary_fname]
|
702
|
-
o << bin
|
703
|
-
end
|
704
|
-
end
|
705
|
-
|
706
|
-
def self.create_end_cent_dir_header(o, entry_count, dirsize, offset)
|
707
|
-
pk = [
|
460
|
+
# 0506: end of central directory
|
461
|
+
def self.pk0506(entry_count, dirsize, offset)
|
462
|
+
# entry_count: count of entries in zip
|
463
|
+
# dirsize: sum of central directory sizes
|
464
|
+
# offset: byte offset of the first central directory
|
465
|
+
[
|
708
466
|
ZS_c_pk0506,
|
709
467
|
ZS_c_int2_zero,
|
710
468
|
ZS_c_int2_zero,
|
@@ -714,6 +472,19 @@ module Zipping
|
|
714
472
|
offset,
|
715
473
|
ZS_c_int2_zero
|
716
474
|
]
|
717
|
-
|
475
|
+
end
|
476
|
+
|
477
|
+
# 0708: optional trailer of entries
|
478
|
+
def self.pk0708(crc, compsize, uncompsize)
|
479
|
+
# crc: CRC32 for uncompressed data
|
480
|
+
# compsize: size of compressed data
|
481
|
+
# uncompsize: size of uncompressed data
|
482
|
+
[
|
483
|
+
ZS_c_pk0708,
|
484
|
+
crc,
|
485
|
+
compsize,
|
486
|
+
uncompsize
|
487
|
+
]
|
488
|
+
end
|
718
489
|
end
|
719
|
-
end
|
490
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zipping
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shuntaro Shitasako
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -114,3 +114,4 @@ summary: Compress files as a zip and output it to a stream.
|
|
114
114
|
test_files:
|
115
115
|
- spec/spec_helper.rb
|
116
116
|
- spec/zipping_spec.rb
|
117
|
+
has_rdoc:
|