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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/zipping/version.rb +1 -1
  4. data/lib/zipping.rb +341 -570
  5. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 70577713282f8648bbde02ed2cd685e278d7266c
4
- data.tar.gz: 344ca0c1c1b95e31a5d8e7e0291762809b1fe05a
3
+ metadata.gz: 243995bc8d06cc56eb535cbca1b3ad5de5b61dbc
4
+ data.tar.gz: 5f563d079b0c7cbfbd0674f05c150e7ad330c7b1
5
5
  SHA512:
6
- metadata.gz: dea1a7d86f095c275af8aa06a01ab7c586f3bd8db8df4a54d7fc64d485b88ce61b9a2de74ca557e5159e5b34bbc8d06a01ac42bc13b420a46fe3814509f1bd09
7
- data.tar.gz: 72ffa3ecd714fbb0600dcf09c30bdfb81067271ac9e937c2ad0f4527c4d8c6c8688bd76a4b355ea8e8b6de5fc82ac6da0ed9aaf703a83b3543ffa8a9d202b91d
6
+ metadata.gz: 201505429f0c6572fe1b0dea7ee4e440598696bd73a537ba663fc73a28f5901597dfe38286697d452139822ebbde5571877707e73b1a474bebb29b08f2804907
7
+ data.tar.gz: 42df47b06e8e292857f030d58f397f8b623f0df7805d64d392f1020706761a422c8b7afd416b1fb678b6cdbfcd1011d72d610c5bce4eb70c0a10af68eb2ed8c0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  zipping
2
2
  =======
3
- [![Gem Version](https://badge.fury.io/rb/zipping.png)](http://badge.fury.io/rb/zipping)
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 (c) 2013 [Nekojarashi Inc.](http://www.nekojarashi.com)
68
+ Copyright [Nekojarashi Inc.](http://www.nekojarashi.com)
@@ -1,3 +1,3 @@
1
1
  module Zipping
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.5"
3
3
  end
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
- ZS_c_ver_dir = 0x000a
17
- ZS_c_ver_file = 0x0014
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
- ZS_c_opt_nosize = 0x0008
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, files, file_division_size, encoding
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, files, file_division_size = 1048576, encoding = :utf8)
62
- @o = output_stream
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
- @s = file_division_size
97
+ @l = []
66
98
  end
67
99
 
68
- # Create an Array of entity Hashes.
69
- def self.to_entities(files)
70
- if files.is_a? Hash
71
- ret = [files]
72
- elsif files.is_a? String
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
- # Create an entity Hash with a path String
88
- def self.to_entity(path)
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
- # Create ASCII-8bits string. Also convert encoding if needed.
99
- def self.to_bytes(str, encoding = :utf8)
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
- # Pack file and directory entities and output to stream.
113
- def pack
114
- return if @f.empty?
115
+ def postpone_dir(dir)
116
+ queue_entity dir, @pending_dirs
117
+ end
115
118
 
116
- # directory entities found but not packed
117
- @pending_dirs = []
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
- # current directory
120
- @current_dir = ''
121
- @current_dir_created_at = Time.now
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
- # data of positions necessary to create headers
124
- @dp = []
140
+ def current_dir_entity
141
+ @current_dir
142
+ end
125
143
 
126
- o = StreamMeter.new @o
144
+ def abs_path_for(name)
145
+ root_dir? ? name : (@current_dir[:abs_path] + '/' + name)
146
+ end
127
147
 
128
- # pack file entities, store directory entities into @pending_dirs
129
- pack_file_entities o
148
+ def abs_path_for_entity(entity)
149
+ abs_path_for entity[:name] || File.basename(entity[:path])
150
+ end
130
151
 
131
- # retrieve and pack stored directories
132
- until @pending_dirs.empty? do
152
+ ### Conversions
133
153
 
134
- current_dir = @pending_dirs.shift
135
- @current_dir = current_dir[:name] << '/'
136
- @current_dir_created_at = current_dir[:time]
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
- # write directory entry
139
- pack_directory o, current_dir[:time]
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
- begin
142
- # get files in the directory
143
- files = Dir.glob(current_dir[:path] + '/*')
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
- # pack files, store directories as entities into @pending_dirs
146
- pack_files o, files, current_dir
147
- rescue => e
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
- # prepare to create headers
152
- @header_offset = o.size
153
- o = StreamMeter.new(@o)
191
+ ### Compression operations
154
192
 
155
- # write headers
156
- write_central_dir_headers o
157
- @header_size = o.size
158
- write_end_central_dir_header
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 file entities and output to stream. Directory entities are not packed but stored.
162
- def pack_file_entities(o = @o, files = @f, dir = @current_dir, dirs = @pending_dirs, data_positions = @dp, encoding = @e, file_division_size = @s)
163
- files.each do |file|
164
- next unless file.is_a?(Hash) && file[:path]
206
+ # Pack a directory
207
+ def pack_current_dir
208
+ pack_current_directory_entity
209
+ pack_entities subdir_entities
210
+ end
165
211
 
166
- f = file[:path]
167
- if File.directory? f
168
- dirs << file
169
- next
170
- end
171
- next unless File.file? f
172
- data_offset = o.size
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
- # Pack directory and output to stream.
235
- def pack_directory(o = @o, time_created_at = @current_dir_created_at, dir = @current_dir, data_positions = @dp, encoding = @e)
236
- bdir = ZipBuilder.to_bytes(dir, encoding)
237
- data_offset = o.size
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 files and output to stream. Directories are not packed but stored.
271
- def pack_files(o, files, dir, dirs = @pending_dirs, data_positions = @dp, encoding = @e, file_division_size = @s)
272
- files.each do |f|
273
- if File.directory? f
274
- dirs << {
275
- :path => f,
276
- :name => dir[:name] + File.basename(f),
277
- :time => dir[:time]
278
- }
279
- next
280
- end
281
- next unless File.file? f
282
- data_offset = o.size
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
- # Write central directories.
345
- def write_central_dir_headers(o = @o, data_positions = @dp)
346
- data_positions.each do |dp|
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
- # Write end of central directory.
374
- def write_end_central_dir_header(o = @o, entry_count = @dp.length, dirsize = @header_size, offset = @header_offset)
375
- pk = [
376
- ZS_c_pk0506,
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
- # Internal method
392
- #
263
+ def pack_entity(entity)
264
+ @w.load_entity fix_entity entity
265
+ yield
266
+ end
267
+ end
393
268
 
394
- # Pack files into zip.
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
- # prepare to measure stream size
428
- o = StreamMeter.new(outputStream)
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
- # container to collect header info
431
- data_positions = []
281
+ ### Data interface
432
282
 
433
- # compress entries
434
- self.compress_entries(o, entries, data_positions, usesjis, file_division_size)
283
+ def load_entity(entity)
284
+ @fixed_entity = entity
285
+ @entries << entity[:zip_path]
286
+ end
435
287
 
436
- # measure stream size
437
- pk0102_offset = o.size
288
+ def path_exists?(abs_path)
289
+ @entries.include? abs_path
290
+ end
438
291
 
439
- # prepare to measure header size
440
- m = StreamMeter.new(o)
292
+ ### Write operations
441
293
 
442
- # write headers
443
- self.create_central_dir_headers(m, data_positions)
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
- # measure header size
446
- pk0102_size = m.size
303
+ def write_directory_entry
304
+ write_entry_without_compress '', :dir
305
+ end
447
306
 
448
- # write tail header
449
- self.create_end_cent_dir_header(o, data_positions.length, pk0102_size, pk0102_offset)
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
- # Pack files and folders into zip.
455
- # All files in folders are also packed into zip, and the structure inside folders are conserved.
456
- def self.create_zip_file_with_files_and_directories(outputStream, target_files, usesjis = true, file_division_size = 1048576)
457
- begin
311
+ def close
312
+ # start of central directories
313
+ @header_offset = current_position
314
+ write_central_dir_headers
458
315
 
459
- target_files = [target_files] unless target_files.instance_of? Array
460
- return if target_files.empty?
316
+ # total size of central directories
317
+ @header_size = current_position - @header_offset
318
+ write_end_central_dir_header
319
+ end
461
320
 
462
- dirs = []
463
- o = StreamMeter.new(outputStream)
464
- data_positions = []
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
- self.compress_file_list(o, target_files, "", dirs, data_positions, usesjis, file_division_size)
330
+ def current_position
331
+ @o.size
332
+ end
467
333
 
468
- while dirs.length > 0 do
469
- current_directory = dirs.shift
470
- dir_path = current_directory[0]
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
- files = Dir.glob(dir_path << '/*')
476
- self.compress_file_list(o, files, dir, dirs, data_positions, usesjis, file_division_size)
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
- pk0102_offset = o.size
481
- m = StreamMeter.new(o)
482
- self.create_central_dir_headers(m, data_positions)
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
- self.create_end_cent_dir_header(o, data_positions.length, m.size, pk0102_offset)
485
- rescue => e
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
- # Get file name as ASCII-8bits.
490
- # If needed, also convert encoding.
491
- def self.get_binary_fname(str, usesjis)
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
- # Write directory entry.
502
- #
503
- def self.create_directory_entry(o, dir_path, dir, data_positions, usesjis = false, file_division_size = 1048576)
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
- def self.compress_entries(o, entries, data_positions, usesjis = false, file_division_size = 1048576)
539
- # `o' must be an output stream which has `size' method
540
-
541
- entries.each do |entry|
542
- path = entry[:path]
543
- name = entry[:name]
544
- mtime = entry[:time]
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
- next if File.directory? path
547
- next unless File.file? path
548
- data_offset = o.size
549
- b_name = self.get_binary_fname(name, usesjis)
550
- filesize = File.size(path)
551
- pk = [
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
- d.finish
399
+ end
584
400
 
585
- pk = [
586
- ZS_c_pk0708,
587
- d.crc,
588
- m.size,
589
- d.size
590
- ]
591
- bin = pk.pack('VVVV')
592
- o << bin
593
- data_positions << {
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
- def self.compress_file_list(o, files, dir, dirs, data_positions, usesjis = false, file_division_size = 1048576)
609
- files.each do |f|
610
- if File.directory? f
611
- dirs << [f, dir.clone << File.basename(f)]
612
- next
613
- end
614
- next unless File.file? f
615
- data_offset = o.size
616
- file_path = f
617
- file = dir.clone << File.basename(file_path)
618
- bfile = self.get_binary_fname(file, usesjis)
619
- filetime = Zip::DOSTime.at(File.mtime(file_path))
620
- filesize = File.size(file_path)
621
- pk = [
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
- ZS_c_ver_file,
624
- ZS_c_opt_nosize,
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
- bfile.length,
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
- end
677
-
678
- def self.create_central_dir_headers(o, data_positions)
679
- data_positions.each do |dp|
680
- pk = [
681
- ZS_c_pk0102,
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
- o << pk.pack('VvvvvVVv')
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.3
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-01-14 00:00:00.000000000 Z
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: