zipping 0.2.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c6d26ef58bcb6f2cf5e72b4979157edcacfc2b15
4
+ data.tar.gz: 934f22728e3df8c6aebb2cb5233512a693267e84
5
+ SHA512:
6
+ metadata.gz: 91db69bf6bb30b3573d4ac116b17e3c53d3f1559c504aa67a96236b2687fdf860592d4b150c6aa5339fd28df2dbd463c1cacb0b229889ade0802b0403ad71408
7
+ data.tar.gz: 24796b591dc42fa83ab0cff4afe6646b1cc355ddaa78fbe0bc38c319a619c65a571256a8db3395ad17284a41a08f6682ea3306343a0eeb78ba09d9ff527ed0ae
data/.gitignore ADDED
@@ -0,0 +1,51 @@
1
+ # rcov generated
2
+ coverage
3
+ coverage.data
4
+
5
+ # rdoc generated
6
+ rdoc
7
+
8
+ # yard generated
9
+ doc
10
+ .yardoc
11
+
12
+ # bundler
13
+ .bundle
14
+
15
+ # jeweler generated
16
+ pkg
17
+
18
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
19
+ #
20
+ # * Create a file at ~/.gitignore
21
+ # * Include files you want ignored
22
+ # * Run: git config --global core.excludesfile ~/.gitignore
23
+ #
24
+ # After doing this, these files will be ignored in all your git projects,
25
+ # saving you from having to 'pollute' every project you touch with them
26
+ #
27
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
28
+ #
29
+ # For MacOS:
30
+ #
31
+ #.DS_Store
32
+
33
+ # For TextMate
34
+ #*.tmproj
35
+ #tmtags
36
+
37
+ # For emacs:
38
+ #*~
39
+ #\#*
40
+ #.\#*
41
+
42
+ # For vim:
43
+ #*.swp
44
+
45
+ # For redcar:
46
+ #.redcar
47
+
48
+ # For rubinius:
49
+ #*.rbc
50
+
51
+ /.DS_Store
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in zipping.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ zipping (0.2.1)
5
+ rubyzip (>= 1.0.0)
6
+ zip-zip (~> 0.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ rake (10.0.3)
12
+ rubyzip (1.1.0)
13
+ zip-zip (0.2)
14
+ rubyzip (>= 1.0.0)
15
+
16
+ PLATFORMS
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ bundler (~> 1.3)
21
+ rake
22
+ zipping!
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Misaki Kawamura
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ zipping
2
+ =======
3
+
4
+ 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.
5
+
6
+ Getting Started
7
+ --
8
+ Add the following line to your Gemfile:
9
+
10
+ gem 'zipping'
11
+
12
+ Usage
13
+ --
14
+ simple:
15
+
16
+ ```ruby
17
+ require 'zipping'
18
+
19
+ Zipping.files_to_zip my_stream, '/path/to/file'
20
+ ```
21
+
22
+ You can pass multiple files.
23
+
24
+ ```ruby
25
+ Zipping.files_to_zip my_stream2, ['/path/to/file', '/another/path']
26
+ ```
27
+
28
+ If you pass a folder, zipping compresses all files in the folder.
29
+
30
+ ```ruby
31
+ Zipping.files_to_zip my_stream3, ['/path/to/folder', '/path/to/other/file']
32
+ ```
33
+
34
+ For example, you have files below:
35
+
36
+ /text/foo.txt
37
+ /text/bar/baz.txt
38
+ /images/abc.png
39
+
40
+ and you run command:
41
+
42
+ ```ruby
43
+ file = File.open '/my.zip', 'wb'
44
+ Zipping.files_to_zip file, ['/text', '/images/abc.png']
45
+ file.close
46
+ ```
47
+
48
+ Then, you get a zip file, and you find entries below in it.
49
+
50
+ text/
51
+ text/foo.txt
52
+ text/bar/
53
+ text/bar/baz.txt
54
+ abc.png
55
+
56
+ To get binary data of zip instead of saving as a file, prepare an 'ASCII-8bit'-encoded empty String object.
57
+
58
+ ```ruby
59
+ zip_data = ''.force_encoding('ASCII-8bit')
60
+ Zipping.files_to_zip zip_data, ['/text', '/images/abc.png']
61
+ ```
62
+
63
+ Then, you get zip binary data in `zip_data`.
64
+
65
+ Notice
66
+ --
67
+ With older(<1.0.0) rubyzip, use 0.2.0.
68
+
69
+ ---
70
+
71
+ Copyright (c) 2013 [Nekojarashi Inc.](http://www.nekojarashi.com)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.1
@@ -0,0 +1,3 @@
1
+ module Zipping
2
+ VERSION = "0.2.1"
3
+ end
data/lib/zipping.rb ADDED
@@ -0,0 +1,731 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require "zipping/version"
3
+ require 'zip'
4
+
5
+ module Zipping
6
+
7
+
8
+ #
9
+ # Constants
10
+ #
11
+
12
+ ZS_c_pk0102 = 0x02014b50
13
+ ZS_c_pk0304 = 0x04034b50
14
+ ZS_c_pk0506 = 0x06054b50
15
+ ZS_c_pk0708 = 0x08074b50
16
+ ZS_c_ver_dir = 0x000a
17
+ ZS_c_ver_file = 0x0014
18
+ ZS_c_ver_made = 0x0315
19
+ ZS_c_opt_none = 0x0000
20
+ ZS_c_opt_nosize = 0x0008
21
+ ZS_c_comp_deflate = 0x0008
22
+ ZS_c_int2_zero = 0x0000
23
+ ZS_c_int4_zero = 0x00000000
24
+
25
+ ZS_c_oattr_dir = 0x41ed4000
26
+ ZS_c_oattr_file = 0x81a44000
27
+
28
+
29
+
30
+ #
31
+ # API methods
32
+ #
33
+
34
+ def self.files_to_zip(output_stream, files, encoding = :utf8, file_division_size = 1048576)
35
+ builder = ZipBuilder.new output_stream, files, encoding, file_division_size
36
+ builder.pack
37
+ end
38
+
39
+ # Deprecated.
40
+ # Recommended to use 'files_to_zip' instead.
41
+ def self.directory_to_zip(output_stream, target_path, usesjis = true, file_division_size = 1048576)
42
+ self.create_zip_file_with_files_and_directories(output_stream, target_path, usesjis, file_division_size)
43
+ end
44
+
45
+ # Deprecated.
46
+ # Recommended to use 'files_to_zip' instead.
47
+ def self.file_to_zip(output_stream, target_path, usesjis = false, file_division_size = 1048576)
48
+ self.create_zip_file_with_files_and_directories(output_stream, target_path, usesjis, file_division_size)
49
+ end
50
+
51
+
52
+
53
+ #
54
+ # Internal Classes
55
+ #
56
+
57
+ # Output stream wrapper that measures size of stream passing through.
58
+ class StreamMeter
59
+ def initialize(output_stream)
60
+ @output_stream = output_stream
61
+ @size = 0
62
+ end
63
+ def << (data)
64
+ @size += data.length
65
+ @output_stream << data
66
+ end
67
+ attr_reader :size
68
+ end
69
+
70
+ class ZipBuilder
71
+
72
+ # Initialize ZipBuilder.
73
+ # 'files' must be a String(file or directory path), a Hash(entity), or an Array of Strings and/or Hashes.
74
+ def initialize(output_stream, files, encoding = :utf8, file_division_size = 1048576)
75
+ @o = output_stream
76
+ @f = ZipBuilder.to_entities files
77
+ @e = encoding
78
+ @s = file_division_size
79
+ end
80
+
81
+ # Create an Array of entity Hashes.
82
+ def self.to_entities(files)
83
+ if files.is_a? Hash
84
+ ret = [files]
85
+ elsif files.is_a? String
86
+ entity = ZipBuilder.to_entity(files)
87
+ ret = entity.nil? ? [] : [entity]
88
+ elsif files.is_a? Array
89
+ ret = []
90
+ files.each do |f|
91
+ entity = ZipBuilder.to_entity(f)
92
+ ret << entity unless entity.nil?
93
+ end
94
+ else
95
+ ret = []
96
+ end
97
+ ret
98
+ end
99
+
100
+ # Create an entity Hash with a path String
101
+ def self.to_entity(path)
102
+ return path if path.is_a?(Hash)
103
+ return nil unless path.is_a?(String) && File.exists?(path)
104
+ ret = {
105
+ :path => path,
106
+ :name => File.basename(path)
107
+ }
108
+ end
109
+
110
+ # Create ASCII-8bits string. Also convert encoding if needed.
111
+ def self.to_bytes(str, encoding = :utf8)
112
+ unless encoding.nil? || encoding == :utf8
113
+ case encoding
114
+ when :shift_jis
115
+ begin
116
+ str = str.encode 'Shift_JIS', :invalid => :replace, :undef => :replace, :replace => '??'
117
+ rescue => e
118
+ end
119
+ end
120
+ end
121
+ [str].pack('a*')
122
+ end
123
+
124
+ # Pack file and directory entities and output to stream.
125
+ def pack
126
+ return if @f.empty?
127
+
128
+ # directory entities found but not packed
129
+ @pending_dirs = []
130
+
131
+ # current directory
132
+ @current_dir = ''
133
+ @current_dir_created_at = Time.now
134
+
135
+ # data of positions necessary to create headers
136
+ @dp = []
137
+
138
+ o = StreamMeter.new @o
139
+
140
+ # pack file entities, store directory entities into @pending_dirs
141
+ pack_file_entities o
142
+
143
+ # retrieve and pack stored directories
144
+ until @pending_dirs.empty? do
145
+
146
+ current_dir = @pending_dirs.shift
147
+ @current_dir = current_dir[:name] << '/'
148
+ @current_dir_created_at = current_dir[:time]
149
+
150
+ # write directory entry
151
+ pack_directory o, current_dir[:time]
152
+
153
+ begin
154
+ # get files in the directory
155
+ files = Dir.glob(current_dir[:path] + '/*')
156
+
157
+ # pack files, store directories as entities into @pending_dirs
158
+ pack_files o, files, current_dir
159
+ rescue => e
160
+ end
161
+ end
162
+
163
+ # prepare to create headers
164
+ @header_offset = o.size
165
+ o = StreamMeter.new(@o)
166
+
167
+ # write headers
168
+ write_central_dir_headers o
169
+ @header_size = o.size
170
+ write_end_central_dir_header
171
+ end
172
+
173
+ # Pack file entities and output to stream. Directory entities are not packed but stored.
174
+ def pack_file_entities(o = @o, files = @f, dir = @current_dir, dirs = @pending_dirs, data_positions = @dp, encoding = @e, file_division_size = @s)
175
+ files.each do |file|
176
+ next unless file.is_a?(Hash) && file[:path].present?
177
+
178
+ f = file[:path]
179
+ if File.directory? f
180
+ dirs << file
181
+ next
182
+ end
183
+ next unless File.file? f
184
+ data_offset = o.size
185
+ file_path = f
186
+ fname = dir.clone << (file[:name] || File.basename(file_path))
187
+ bfname = ZipBuilder.to_bytes(fname, encoding)
188
+ filetime = Zip::DOSTime.at(file[:time] || File.mtime(file_path))
189
+ filesize = File.size(file_path)
190
+ pk = [
191
+ ZS_c_pk0304,
192
+ ZS_c_ver_file,
193
+ ZS_c_opt_nosize,
194
+ ZS_c_comp_deflate,
195
+ filetime.to_binary_dos_time,
196
+ filetime.to_binary_dos_date,
197
+ ZS_c_int4_zero,
198
+ ZS_c_int4_zero,
199
+ ZS_c_int4_zero,
200
+ bfname.length,
201
+ ZS_c_int2_zero
202
+ ]
203
+ bin = pk.pack('VvvvvvVVVvv')
204
+ o << bin
205
+ bin = bfname
206
+ o << bin
207
+
208
+ m = StreamMeter.new(o)
209
+ d = Zip::Deflater.new(m)
210
+ File.open(file_path) do |f|
211
+ cur_filesize = filesize
212
+ while cur_filesize > 0
213
+ if cur_filesize >= file_division_size
214
+ d << f.read(file_division_size)
215
+ cur_filesize -= file_division_size
216
+ else
217
+ d << f.read(cur_filesize)
218
+ cur_filesize = 0
219
+ end
220
+ end
221
+ end
222
+ d.finish
223
+
224
+ pk = [
225
+ ZS_c_pk0708,
226
+ d.crc,
227
+ m.size,
228
+ d.size
229
+ ]
230
+ bin = pk.pack('VVVV')
231
+ o << bin
232
+ data_positions << {
233
+ :folder => false,
234
+ :file => fname,
235
+ :file_dos_time => filetime.to_binary_dos_time,
236
+ :file_dos_date => filetime.to_binary_dos_date,
237
+ :binary_fname => bfname,
238
+ :offset => data_offset,
239
+ :crc => d.crc,
240
+ :complen => m.size,
241
+ :uncomplen => d.size
242
+ }
243
+ end
244
+ end
245
+
246
+ # Pack directory and output to stream.
247
+ def pack_directory(o = @o, time_created_at = @current_dir_created_at, dir = @current_dir, data_positions = @dp, encoding = @e)
248
+ bdir = ZipBuilder.to_bytes(dir, encoding)
249
+ data_offset = o.size
250
+ filetime = Zip::DOSTime.at(time_created_at);
251
+ filesize = 0
252
+ pk = [
253
+ ZS_c_pk0304,
254
+ ZS_c_ver_dir,
255
+ ZS_c_opt_none,
256
+ ZS_c_comp_deflate,
257
+ filetime.to_binary_dos_time,
258
+ filetime.to_binary_dos_date,
259
+ ZS_c_int4_zero,
260
+ ZS_c_int4_zero,
261
+ ZS_c_int4_zero,
262
+ bdir.length,
263
+ ZS_c_int2_zero
264
+ ]
265
+ bin = pk.pack('VvvvvvVVVvv')
266
+ o << bin
267
+ bin = bdir
268
+ o << bin
269
+ data_positions << {
270
+ :folder => true,
271
+ :file => dir,
272
+ :file_dos_time => filetime.to_binary_dos_time,
273
+ :file_dos_date => filetime.to_binary_dos_date,
274
+ :binary_fname => bdir,
275
+ :offset => data_offset,
276
+ :crc => ZS_c_int4_zero,
277
+ :complen => ZS_c_int4_zero,
278
+ :uncomplen => ZS_c_int4_zero
279
+ }
280
+ end
281
+
282
+ # Pack files and output to stream. Directories are not packed but stored.
283
+ def pack_files(o, files, dir, dirs = @pending_dirs, data_positions = @dp, encoding = @e, file_division_size = @s)
284
+ files.each do |f|
285
+ if File.directory? f
286
+ dirs << {
287
+ :path => f,
288
+ :name => dir[:name] + File.basename(f),
289
+ :time => dir[:time]
290
+ }
291
+ next
292
+ end
293
+ next unless File.file? f
294
+ data_offset = o.size
295
+ file_path = f
296
+ file = dir[:name] + File.basename(file_path)
297
+ bfile = ZipBuilder.to_bytes(file, encoding)
298
+ filetime = Zip::DOSTime.at(dir[:time] || File.mtime(file_path))
299
+ filesize = File.size(file_path)
300
+ pk = [
301
+ ZS_c_pk0304,
302
+ ZS_c_ver_file,
303
+ ZS_c_opt_nosize,
304
+ ZS_c_comp_deflate,
305
+ filetime.to_binary_dos_time,
306
+ filetime.to_binary_dos_date,
307
+ ZS_c_int4_zero,
308
+ ZS_c_int4_zero,
309
+ ZS_c_int4_zero,
310
+ bfile.length,
311
+ ZS_c_int2_zero
312
+ ]
313
+ bin = pk.pack('VvvvvvVVVvv')
314
+ o << bin
315
+ bin = bfile
316
+ o << bin
317
+
318
+ m = StreamMeter.new(o)
319
+ d = Zip::Deflater.new(m)
320
+ File.open(file_path) do |f|
321
+ cur_filesize = filesize
322
+ while cur_filesize > 0
323
+ if cur_filesize >= file_division_size
324
+ d << f.read(file_division_size)
325
+ cur_filesize -= file_division_size
326
+ else
327
+ d << f.read(cur_filesize)
328
+ cur_filesize = 0
329
+ end
330
+ end
331
+ end
332
+ d.finish
333
+
334
+ pk = [
335
+ ZS_c_pk0708,
336
+ d.crc,
337
+ m.size,
338
+ d.size
339
+ ]
340
+ bin = pk.pack('VVVV')
341
+ o << bin
342
+ data_positions << {
343
+ :folder => false,
344
+ :file => file,
345
+ :file_dos_time => filetime.to_binary_dos_time,
346
+ :file_dos_date => filetime.to_binary_dos_date,
347
+ :binary_fname => bfile,
348
+ :offset => data_offset,
349
+ :crc => d.crc,
350
+ :complen => m.size,
351
+ :uncomplen => d.size
352
+ }
353
+ end
354
+ end
355
+
356
+ # Write central directories.
357
+ def write_central_dir_headers(o = @o, data_positions = @dp)
358
+ data_positions.each do |dp|
359
+ pk = [
360
+ ZS_c_pk0102,
361
+ ZS_c_ver_made,
362
+ (dp[:folder] ? ZS_c_ver_dir : ZS_c_ver_file),
363
+ ZS_c_opt_nosize,
364
+ ZS_c_comp_deflate,
365
+ dp[:file_dos_time],
366
+ dp[:file_dos_date],
367
+ dp[:crc],
368
+ dp[:complen],
369
+ dp[:uncomplen],
370
+ dp[:binary_fname].length,
371
+ ZS_c_int2_zero,
372
+ ZS_c_int2_zero,
373
+ ZS_c_int2_zero,
374
+ ZS_c_int2_zero,
375
+ (dp[:folder] ? ZS_c_oattr_dir : ZS_c_oattr_file),
376
+ dp[:offset]
377
+ ]
378
+ bin = pk.pack('VvvvvvvVVVvvvvvVV')
379
+ o << bin
380
+ bin = dp[:binary_fname]
381
+ o << bin
382
+ end
383
+ end
384
+
385
+ # Write end of central directory.
386
+ def write_end_central_dir_header(o = @o, entry_count = @dp.length, dirsize = @header_size, offset = @header_offset)
387
+ pk = [
388
+ ZS_c_pk0506,
389
+ ZS_c_int2_zero,
390
+ ZS_c_int2_zero,
391
+ entry_count,
392
+ entry_count,
393
+ dirsize,
394
+ offset,
395
+ ZS_c_int2_zero
396
+ ]
397
+ o << pk.pack('VvvvvVVv')
398
+ end
399
+ end
400
+
401
+
402
+ #
403
+ # Internal method
404
+ #
405
+
406
+ # Pack files into zip.
407
+ # 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'.
408
+ # Optionally, you may add :time(time created at) to the entity Hashes.
409
+ def self.create_zip_file_with_file_entities(outputStream, target_files, usesjis = true, file_division_size = 1048576)
410
+ begin
411
+
412
+ # prepare entry list for zip
413
+ target_files = [target_files] unless target_files.instance_of? Array
414
+ entries = []
415
+ target_files.each do |file|
416
+ if file.is_a? String
417
+ begin
418
+ entry = {
419
+ :path => file,
420
+ :name => File.basename(file),
421
+ :time => File.mtime(file)
422
+ }
423
+ rescue => e
424
+ next
425
+ end
426
+ elsif file.is_a? Hash
427
+ next unless file[:path].present? && file[:path].is_a?(String)
428
+ entry = file
429
+ path = entry[:path]
430
+ entry[:name] = File.basename(path) unless entry[:name].present? && entry[:name].is_a?(String)
431
+ entry[:time] = File.mtime(path) unless entry[:time].present? && entry[:time].is_a?(Time)
432
+ else
433
+ next
434
+ end
435
+ entries << entry
436
+ end
437
+ return if entries.empty?
438
+
439
+ # prepare to measure stream size
440
+ o = StreamMeter.new(outputStream)
441
+
442
+ # container to collect header info
443
+ data_positions = []
444
+
445
+ # compress entries
446
+ self.compress_entries(o, entries, data_positions, usesjis, file_division_size)
447
+
448
+ # measure stream size
449
+ pk0102_offset = o.size
450
+
451
+ # prepare to measure header size
452
+ m = StreamMeter.new(o)
453
+
454
+ # write headers
455
+ self.create_central_dir_headers(m, data_positions)
456
+
457
+ # measure header size
458
+ pk0102_size = m.size
459
+
460
+ # write tail header
461
+ self.create_end_cent_dir_header(o, data_positions.length, pk0102_size, pk0102_offset)
462
+ rescue => e
463
+ end
464
+ end
465
+
466
+ # Pack files and folders into zip.
467
+ # All files in folders are also packed into zip, and the structure inside folders are conserved.
468
+ def self.create_zip_file_with_files_and_directories(outputStream, target_files, usesjis = true, file_division_size = 1048576)
469
+ begin
470
+
471
+ target_files = [target_files] unless target_files.instance_of? Array
472
+ return if target_files.empty?
473
+
474
+ dirs = []
475
+ o = StreamMeter.new(outputStream)
476
+ data_positions = []
477
+
478
+ self.compress_file_list(o, target_files, "", dirs, data_positions, usesjis, file_division_size)
479
+
480
+ while dirs.length > 0 do
481
+ current_directory = dirs.shift
482
+ dir_path = current_directory[0]
483
+ dir = current_directory[1]
484
+ dir << '/'
485
+ self.create_directory_entry(o, dir_path, dir, data_positions, usesjis, file_division_size)
486
+
487
+ files = Dir.glob(dir_path << '/*')
488
+ self.compress_file_list(o, files, dir, dirs, data_positions, usesjis, file_division_size)
489
+
490
+ end
491
+
492
+ pk0102_offset = o.size
493
+ m = StreamMeter.new(o)
494
+ self.create_central_dir_headers(m, data_positions)
495
+
496
+ self.create_end_cent_dir_header(o, data_positions.length, m.size, pk0102_offset)
497
+ rescue => e
498
+ end
499
+ end
500
+
501
+ # Get file name as ASCII-8bits.
502
+ # If needed, also convert encoding.
503
+ def self.get_binary_fname(str, usesjis)
504
+ if usesjis
505
+ begin
506
+ str = str.encode 'Shift_JIS', :invalid => :replace, :replace => '??'
507
+ rescue => e
508
+ end
509
+ end
510
+ return [str].pack('a*')
511
+ end
512
+
513
+ # Write directory entry.
514
+ #
515
+ def self.create_directory_entry(o, dir_path, dir, data_positions, usesjis = false, file_division_size = 1048576)
516
+ bdir = self.get_binary_fname(dir, usesjis)
517
+ data_offset = o.size
518
+ filetime = Zip::DOSTime.at(File.mtime(dir_path))
519
+ filesize = 0
520
+ pk = [
521
+ ZS_c_pk0304,
522
+ ZS_c_ver_dir,
523
+ ZS_c_opt_none,
524
+ ZS_c_comp_deflate,
525
+ filetime.to_binary_dos_time,
526
+ filetime.to_binary_dos_date,
527
+ ZS_c_int4_zero,
528
+ ZS_c_int4_zero,
529
+ ZS_c_int4_zero,
530
+ bdir.length,
531
+ ZS_c_int2_zero
532
+ ]
533
+ bin = pk.pack('VvvvvvVVVvv')
534
+ o << bin
535
+ bin = bdir
536
+ o << bin
537
+ data_positions << {
538
+ :folder => true,
539
+ :file => dir,
540
+ :file_dos_time => filetime.to_binary_dos_time,
541
+ :file_dos_date => filetime.to_binary_dos_date,
542
+ :binary_fname => bdir,
543
+ :offset => data_offset,
544
+ :crc => ZS_c_int4_zero,
545
+ :complen => ZS_c_int4_zero,
546
+ :uncomplen => ZS_c_int4_zero
547
+ }
548
+ end
549
+
550
+ def self.compress_entries(o, entries, data_positions, usesjis = false, file_division_size = 1048576)
551
+ # `o' must be an output stream which has `size' method
552
+
553
+ entries.each do |entry|
554
+ path = entry[:path]
555
+ name = entry[:name]
556
+ mtime = entry[:time]
557
+
558
+ next if File.directory? path
559
+ next unless File.file? path
560
+ data_offset = o.size
561
+ b_name = self.get_binary_fname(name, usesjis)
562
+ filesize = File.size(path)
563
+ pk = [
564
+ ZS_c_pk0304,
565
+ ZS_c_ver_file,
566
+ ZS_c_opt_nosize,
567
+ ZS_c_comp_deflate,
568
+ mtime.to_binary_dos_time,
569
+ mtime.to_binary_dos_date,
570
+ ZS_c_int4_zero,
571
+ ZS_c_int4_zero,
572
+ ZS_c_int4_zero,
573
+ b_name.length,
574
+ ZS_c_int2_zero
575
+ ]
576
+ bin = pk.pack('VvvvvvVVVvv')
577
+ o << bin
578
+ bin = b_name
579
+ o << bin
580
+
581
+ m = StreamMeter.new(o)
582
+ d = Zip::Deflater.new(m)
583
+ File.open(path) do |f|
584
+ cur_filesize = filesize
585
+ while cur_filesize > 0
586
+ if cur_filesize >= file_division_size
587
+ d << f.read(file_division_size)
588
+ cur_filesize -= file_division_size
589
+ else
590
+ d << f.read(cur_filesize)
591
+ cur_filesize = 0
592
+ end
593
+ end
594
+ end
595
+ d.finish
596
+
597
+ pk = [
598
+ ZS_c_pk0708,
599
+ d.crc,
600
+ m.size,
601
+ d.size
602
+ ]
603
+ bin = pk.pack('VVVV')
604
+ o << bin
605
+ data_positions << {
606
+ :folder => false,
607
+ :file => name,
608
+ :file_dos_time => filetime.to_binary_dos_time,
609
+ :file_dos_date => filetime.to_binary_dos_date,
610
+ :binary_fname => b_name,
611
+ :offset => data_offset,
612
+ :crc => d.crc,
613
+ :complen => m.size,
614
+ :uncomplen => d.size
615
+ }
616
+ end
617
+
618
+ end
619
+
620
+ def self.compress_file_list(o, files, dir, dirs, data_positions, usesjis = false, file_division_size = 1048576)
621
+ files.each do |f|
622
+ if File.directory? f
623
+ dirs << [f, dir.clone << File.basename(f)]
624
+ next
625
+ end
626
+ next unless File.file? f
627
+ data_offset = o.size
628
+ file_path = f
629
+ file = dir.clone << File.basename(file_path)
630
+ bfile = self.get_binary_fname(file, usesjis)
631
+ filetime = Zip::DOSTime.at(File.mtime(file_path))
632
+ filesize = File.size(file_path)
633
+ pk = [
634
+ ZS_c_pk0304,
635
+ ZS_c_ver_file,
636
+ ZS_c_opt_nosize,
637
+ ZS_c_comp_deflate,
638
+ filetime.to_binary_dos_time,
639
+ filetime.to_binary_dos_date,
640
+ ZS_c_int4_zero,
641
+ ZS_c_int4_zero,
642
+ ZS_c_int4_zero,
643
+ bfile.length,
644
+ ZS_c_int2_zero
645
+ ]
646
+ bin = pk.pack('VvvvvvVVVvv')
647
+ o << bin
648
+ bin = bfile
649
+ o << bin
650
+
651
+ m = StreamMeter.new(o)
652
+ d = Zip::Deflater.new(m)
653
+ File.open(file_path) do |f|
654
+ cur_filesize = filesize
655
+ while cur_filesize > 0
656
+ if cur_filesize >= file_division_size
657
+ d << f.read(file_division_size)
658
+ cur_filesize -= file_division_size
659
+ else
660
+ d << f.read(cur_filesize)
661
+ cur_filesize = 0
662
+ end
663
+ end
664
+ end
665
+ d.finish
666
+
667
+ pk = [
668
+ ZS_c_pk0708,
669
+ d.crc,
670
+ m.size,
671
+ d.size
672
+ ]
673
+ bin = pk.pack('VVVV')
674
+ o << bin
675
+ data_positions << {
676
+ :folder => false,
677
+ :file => file,
678
+ :file_dos_time => filetime.to_binary_dos_time,
679
+ :file_dos_date => filetime.to_binary_dos_date,
680
+ :binary_fname => bfile,
681
+ :offset => data_offset,
682
+ :crc => d.crc,
683
+ :complen => m.size,
684
+ :uncomplen => d.size
685
+ }
686
+ end
687
+
688
+ end
689
+
690
+ def self.create_central_dir_headers(o, data_positions)
691
+ data_positions.each do |dp|
692
+ pk = [
693
+ ZS_c_pk0102,
694
+ ZS_c_ver_made,
695
+ (dp[:folder] ? ZS_c_ver_dir : ZS_c_ver_file),
696
+ ZS_c_opt_nosize,
697
+ ZS_c_comp_deflate,
698
+ dp[:file_dos_time],
699
+ dp[:file_dos_date],
700
+ dp[:crc],
701
+ dp[:complen],
702
+ dp[:uncomplen],
703
+ dp[:binary_fname].length,
704
+ ZS_c_int2_zero,
705
+ ZS_c_int2_zero,
706
+ ZS_c_int2_zero,
707
+ ZS_c_int2_zero,
708
+ (dp[:folder] ? ZS_c_oattr_dir : ZS_c_oattr_file),
709
+ dp[:offset]
710
+ ]
711
+ bin = pk.pack('VvvvvvvVVVvvvvvVV')
712
+ o << bin
713
+ bin = dp[:binary_fname]
714
+ o << bin
715
+ end
716
+ end
717
+
718
+ def self.create_end_cent_dir_header(o, entry_count, dirsize, offset)
719
+ pk = [
720
+ ZS_c_pk0506,
721
+ ZS_c_int2_zero,
722
+ ZS_c_int2_zero,
723
+ entry_count,
724
+ entry_count,
725
+ dirsize,
726
+ offset,
727
+ ZS_c_int2_zero
728
+ ]
729
+ o << pk.pack('VvvvvVVv')
730
+ end
731
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'zipping'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Zipping" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
data/zipping.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'zipping/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "zipping"
8
+ spec.version = Zipping::VERSION
9
+ spec.authors = ["Shuntaro Shitasako"]
10
+ spec.email = ["info@nekojarashi.com.com"]
11
+ spec.description = "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."
12
+ spec.summary = "Compress files as a zip and output it to a stream."
13
+ spec.homepage = "http://www.nekojarashi.com"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency("rubyzip", [">= 1.0.0"])
25
+ spec.add_dependency("zip-zip", ["~> 0.2"])
26
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zipping
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Shuntaro Shitasako
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubyzip
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: zip-zip
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
69
+ description: This gem is for compressing files as a zip and outputting to a stream
70
+ (or a stream-like interface object). The output to a stream proceeds little by little,
71
+ as files are compressed.
72
+ email:
73
+ - info@nekojarashi.com.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - .rspec
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - VERSION
86
+ - lib/zipping.rb
87
+ - lib/zipping/version.rb
88
+ - spec/spec_helper.rb
89
+ - spec/zipping_spec.rb
90
+ - zipping.gemspec
91
+ homepage: http://www.nekojarashi.com
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.0.3
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: Compress files as a zip and output it to a stream.
115
+ test_files:
116
+ - spec/spec_helper.rb
117
+ - spec/zipping_spec.rb