# 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