# File lib/ole/storage/base.rb, line 178 def flush # update root dirent, and flatten dirent tree @root.name = 'Root Entry' @root.first_block = @sb_file.first_block @root.size = @sb_file.size @dirents = @root.flatten # serialize the dirents using the bbat RangesIOResizeable.open @bbat, 'w', :first_block => @header.dirent_start do |io| io.write @dirents.map { |dirent| dirent.to_s }.join padding = (io.size / @bbat.block_size.to_f).ceil * @bbat.block_size - io.size io.write 0.chr * padding @header.dirent_start = io.first_block end # serialize the sbat # perhaps the blocks used by the sbat should be marked with BAT? RangesIOResizeable.open @bbat, 'w', :first_block => @header.sbat_start do |io| io.write @sbat.to_s @header.sbat_start = io.first_block @header.num_sbat = @bbat.chain(@header.sbat_start).length end # create RangesIOResizeable hooked up to the bbat. use that to claim bbat blocks using # truncate. then when its time to write, convert that chain and some chunk of blocks at # the end, into META_BAT blocks. write out the chain, and those meta bat blocks, and its # done. # this is perhaps not good, as we reclaim all bat blocks here, which # may include the sbat we just wrote. FIXME @bbat.map! do |b| b == AllocationTable::BAT || b == AllocationTable::META_BAT ? AllocationTable::AVAIL : b end # currently we use a loop. this could be better, but basically, # the act of writing out the bat, itself requires blocks which get # recorded in the bat. # # i'm sure that there'd be some simpler closed form solution to this. solve # recursive func: # # num_mbat_blocks = ceil(max((mbat_len - 109) * 4 / block_size, 0)) # bbat_len = initial_bbat_len + num_mbat_blocks # mbat_len = ceil(bbat_len * 4 / block_size) # # the actual bbat allocation table is itself stored throughout the file, and that chain # is stored in the initial blocks, and the mbat blocks. num_mbat_blocks = 0 io = RangesIOResizeable.new @bbat, 'w', :first_block => AllocationTable::EOC # truncate now, so that we can simplify size calcs - the mbat blocks will be appended in a # contiguous chunk at the end. # hmmm, i think this truncate should be matched with a truncate of the underlying io. if you # delete a lot of stuff, and free up trailing blocks, the file size never shrinks. this can # be fixed easily, add an io truncate @bbat.truncate! before = @io.size @io.truncate @bbat.block_size * (@bbat.length + 1) while true # get total bbat size. equivalent to @bbat.to_s.length, but for the factoring in of # the mbat blocks. we can't just add the mbat blocks directly to the bbat, as as this iteration # progresses, more blocks may be needed for the bat itself (if there are no more gaps), and the # mbat must remain contiguous. bbat_data_len = ((@bbat.length + num_mbat_blocks) * 4 / @bbat.block_size.to_f).ceil * @bbat.block_size # now storing the excess mbat blocks also increases the size of the bbat: new_num_mbat_blocks = ([bbat_data_len / @bbat.block_size - 109, 0].max * 4 / (@bbat.block_size.to_f - 4)).ceil if new_num_mbat_blocks != num_mbat_blocks # need more space for the mbat. num_mbat_blocks = new_num_mbat_blocks elsif io.size != bbat_data_len # need more space for the bat # this may grow the bbat, depending on existing available blocks io.truncate bbat_data_len else break end end # now extract the info we want: ranges = io.ranges bbat_chain = @bbat.chain io.first_block io.close bbat_chain.each { |b| @bbat[b] = AllocationTable::BAT } # tack on the mbat stuff @header.num_bat = bbat_chain.length mbat_blocks = (0...num_mbat_blocks).map do block = @bbat.free_block @bbat[block] = AllocationTable::META_BAT block end @header.mbat_start = mbat_blocks.first || AllocationTable::EOC # now finally write the bbat, using a not resizable io. # the mode here will be 'r', which allows write atm. RangesIO.open(@io, :ranges => ranges) { |f| f.write @bbat.to_s } # this is the mbat. pad it out. bbat_chain += [AllocationTable::AVAIL] * [109 - bbat_chain.length, 0].max @header.num_mbat = num_mbat_blocks if num_mbat_blocks != 0 # write out the mbat blocks now. first of all, where are they going to be? mbat_data = bbat_chain[109..-1] # expand the mbat_data to include the linked list forward pointers. mbat_data = mbat_data.to_enum(:each_slice, @bbat.block_size / 4 - 1).to_a. zip(mbat_blocks[1..-1] + [nil]).map { |a, b| b ? a + [b] : a } # pad out the last one. mbat_data.last.push(*([AllocationTable::AVAIL] * (@bbat.block_size / 4 - mbat_data.last.length))) RangesIO.open @io, :ranges => @bbat.ranges(mbat_blocks) do |f| f.write mbat_data.flatten.pack('V*') end end # now seek back and write the header out @io.seek 0 @io.write @header.to_s + bbat_chain[0, 109].pack('V*') @io.flush end