zipping 0.2.3 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|