Class: Zipping::ZipBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/zipping.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output_stream, files, file_division_size = 1048576, encoding = :utf8) ⇒ ZipBuilder

Initialize ZipBuilder. ‘files’ must be a String(file or directory path), a Hash(entity), or an Array of Strings and/or Hashes.



61
62
63
64
65
66
# File 'lib/zipping.rb', line 61

def initialize(output_stream, files, file_division_size = 1048576, encoding = :utf8)
  @o = output_stream
  @f = ZipBuilder.to_entities files
  @e = encoding
  @s = file_division_size
end

Class Method Details

.to_bytes(str, encoding = :utf8) ⇒ Object

Create ASCII-8bits string. Also convert encoding if needed.



99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/zipping.rb', line 99

def self.to_bytes(str, encoding = :utf8)
  unless encoding.nil? || encoding == :utf8
    case encoding
    when :shift_jis
      begin
        str = str.encode 'Shift_JIS', :invalid => :replace, :undef => :replace, :replace => '??'
      rescue => e
      end
    end
  end
  [str].pack('a*')
end

.to_entities(files) ⇒ Object

Create an Array of entity Hashes.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/zipping.rb', line 69

def self.to_entities(files)
  if files.is_a? Hash
    ret = [files]
  elsif files.is_a? String
    entity = ZipBuilder.to_entity(files)
    ret = entity.nil? ? [] : [entity]
  elsif files.is_a? Array
    ret = []
    files.each do |f|
      entity = ZipBuilder.to_entity(f)
      ret << entity unless entity.nil?
    end
  else
    ret = []
  end
  ret
end

.to_entity(path) ⇒ Object

Create an entity Hash with a path String



88
89
90
91
92
93
94
95
96
# File 'lib/zipping.rb', line 88

def self.to_entity(path)
  return path if path.is_a?(Hash)
  return nil unless path.is_a?(String) && File.exists?(path)
  ret = {
    :path => path,
    :name => File.basename(path),
    :time => Time.now
  }
end

Instance Method Details

#packObject

Pack file and directory entities and output to stream.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/zipping.rb', line 113

def pack
  return if @f.empty?

  # directory entities found but not packed
  @pending_dirs = []

  # current directory
  @current_dir = ''
  @current_dir_created_at = Time.now

  # data of positions necessary to create headers
  @dp = []

  o = StreamMeter.new @o

  # pack file entities, store directory entities into @pending_dirs
  pack_file_entities o

  # retrieve and pack stored directories
  until @pending_dirs.empty? do

    current_dir = @pending_dirs.shift
    @current_dir = current_dir[:name] << '/'
    @current_dir_created_at = current_dir[:time]

    # write directory entry
    pack_directory o, current_dir[:time]

    begin
      # get files in the directory
      files = Dir.glob(current_dir[:path] + '/*')

      # pack files, store directories as entities into @pending_dirs
      pack_files o, files, current_dir
    rescue => e
    end
  end

  # prepare to create headers
  @header_offset = o.size
  o = StreamMeter.new(@o)

  # write headers
  write_central_dir_headers o
  @header_size = o.size
  write_end_central_dir_header
end

#pack_directory(o = @o, time_created_at = @current_dir_created_at, dir = @current_dir, data_positions = @dp, encoding = @e) ⇒ Object

Pack directory and output to stream.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/zipping.rb', line 235

def pack_directory(o = @o, time_created_at = @current_dir_created_at, dir = @current_dir, data_positions = @dp, encoding = @e)
  bdir = ZipBuilder.to_bytes(dir, encoding)
  data_offset = o.size
  filetime = Zip::DOSTime.at(time_created_at);
  filesize = 0
  pk = [
    ZS_c_pk0304,
    ZS_c_ver_dir,
    ZS_c_opt_none,
    ZS_c_comp_deflate,
    filetime.to_binary_dos_time,
    filetime.to_binary_dos_date,
    ZS_c_int4_zero,
    ZS_c_int4_zero,
    ZS_c_int4_zero,
    bdir.length,
    ZS_c_int2_zero
  ]
  bin = pk.pack('VvvvvvVVVvv')
  o << bin
  bin = bdir
  o << bin
  data_positions << {
    :folder => true,
    :file => dir,
    :file_dos_time => filetime.to_binary_dos_time,
    :file_dos_date => filetime.to_binary_dos_date,
    :binary_fname => bdir,
    :offset => data_offset,
    :crc => ZS_c_int4_zero,
    :complen => ZS_c_int4_zero,
    :uncomplen => ZS_c_int4_zero
  }
end

#pack_file_entities(o = @o, files = @f, dir = @current_dir, dirs = @pending_dirs, data_positions = @dp, encoding = @e, file_division_size = @s) ⇒ Object

Pack file entities and output to stream. Directory entities are not packed but stored.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/zipping.rb', line 162

def pack_file_entities(o = @o, files = @f, dir = @current_dir, dirs = @pending_dirs, data_positions = @dp, encoding = @e, file_division_size = @s)
  files.each do |file|
    next unless file.is_a?(Hash) && file[:path]

    f = file[:path]
    if File.directory? f
      dirs << file
      next
    end
    next unless File.file? f
    data_offset = o.size
    file_path = f
    fname = dir.clone << (file[:name] || File.basename(file_path))
    bfname = ZipBuilder.to_bytes(fname, encoding)
    filetime = Zip::DOSTime.at(file[:time] || File.mtime(file_path))
    filesize = File.size(file_path)
    pk = [
      ZS_c_pk0304,
      ZS_c_ver_file,
      ZS_c_opt_nosize,
      ZS_c_comp_deflate,
      filetime.to_binary_dos_time,
      filetime.to_binary_dos_date,
      ZS_c_int4_zero,
      ZS_c_int4_zero,
      ZS_c_int4_zero,
      bfname.length,
      ZS_c_int2_zero
    ]
    bin = pk.pack('VvvvvvVVVvv')
    o << bin
    bin = bfname
    o << bin

    m = StreamMeter.new(o)
    d = Zip::Deflater.new(m)
    File.open(file_path) do |f|
      cur_filesize = filesize
      while cur_filesize > 0
        if cur_filesize >= file_division_size
          d << f.read(file_division_size)
          cur_filesize -= file_division_size
        else
          d << f.read(cur_filesize)
          cur_filesize = 0
        end
      end
    end
    d.finish

    pk = [
      ZS_c_pk0708,
      d.crc,
      m.size,
      d.size
    ]
    bin = pk.pack('VVVV')
    o << bin
    data_positions << {
      :folder => false,
      :file => fname,
      :file_dos_time => filetime.to_binary_dos_time,
      :file_dos_date => filetime.to_binary_dos_date,
      :binary_fname => bfname,
      :offset => data_offset,
      :crc => d.crc,
      :complen => m.size,
      :uncomplen => d.size
    }
  end
end

#pack_files(o, files, dir, dirs = @pending_dirs, data_positions = @dp, encoding = @e, file_division_size = @s) ⇒ Object

Pack files and output to stream. Directories are not packed but stored.



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/zipping.rb', line 271

def pack_files(o, files, dir, dirs = @pending_dirs, data_positions = @dp, encoding = @e, file_division_size = @s)
  files.each do |f|
    if File.directory? f
      dirs << {
        :path => f,
        :name => dir[:name] + File.basename(f),
        :time => dir[:time]
      }
      next
    end
    next unless File.file? f
    data_offset = o.size
    file_path = f
    file = dir[:name] + File.basename(file_path)
    bfile = ZipBuilder.to_bytes(file, encoding)
    filetime = Zip::DOSTime.at(dir[:time] || File.mtime(file_path))
    filesize = File.size(file_path)
    pk = [
      ZS_c_pk0304,
      ZS_c_ver_file,
      ZS_c_opt_nosize,
      ZS_c_comp_deflate,
      filetime.to_binary_dos_time,
      filetime.to_binary_dos_date,
      ZS_c_int4_zero,
      ZS_c_int4_zero,
      ZS_c_int4_zero,
      bfile.length,
      ZS_c_int2_zero
    ]
    bin = pk.pack('VvvvvvVVVvv')
    o << bin
    bin = bfile
    o << bin

    m = StreamMeter.new(o)
    d = Zip::Deflater.new(m)
    File.open(file_path) do |f|
      cur_filesize = filesize
      while cur_filesize > 0
        if cur_filesize >= file_division_size
          d << f.read(file_division_size)
          cur_filesize -= file_division_size
        else
          d << f.read(cur_filesize)
          cur_filesize = 0
        end
      end
    end
    d.finish

    pk = [
      ZS_c_pk0708,
      d.crc,
      m.size,
      d.size
    ]
    bin = pk.pack('VVVV')
    o << bin
    data_positions << {
      :folder => false,
      :file => file,
      :file_dos_time => filetime.to_binary_dos_time,
      :file_dos_date => filetime.to_binary_dos_date,
      :binary_fname => bfile,
      :offset => data_offset,
      :crc => d.crc,
      :complen => m.size,
      :uncomplen => d.size
    }
  end
end

#write_central_dir_headers(o = @o, data_positions = @dp) ⇒ Object

Write central directories.



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/zipping.rb', line 345

def write_central_dir_headers(o = @o, data_positions = @dp)
  data_positions.each do |dp|
    pk = [
      ZS_c_pk0102,
      ZS_c_ver_made,
      (dp[:folder] ? ZS_c_ver_dir : ZS_c_ver_file),
      ZS_c_opt_nosize,
      ZS_c_comp_deflate,
      dp[:file_dos_time],
      dp[:file_dos_date],
      dp[:crc],
      dp[:complen],
      dp[:uncomplen],
      dp[:binary_fname].length,
      ZS_c_int2_zero,
      ZS_c_int2_zero,
      ZS_c_int2_zero,
      ZS_c_int2_zero,
      (dp[:folder] ? ZS_c_oattr_dir : ZS_c_oattr_file),
      dp[:offset]
    ]
    bin = pk.pack('VvvvvvvVVVvvvvvVV')
    o << bin
    bin = dp[:binary_fname]
    o << bin
  end
end

#write_end_central_dir_header(o = @o, entry_count = @dp.length, dirsize = @header_size, offset = @header_offset) ⇒ Object

Write end of central directory.



374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/zipping.rb', line 374

def write_end_central_dir_header(o = @o, entry_count = @dp.length, dirsize = @header_size, offset = @header_offset)
  pk = [
    ZS_c_pk0506,
    ZS_c_int2_zero,
    ZS_c_int2_zero,
    entry_count,
    entry_count,
    dirsize,
    offset,
    ZS_c_int2_zero
  ]
  o << pk.pack('VvvvvVVv')
end