lib/ole/storage/base.rb
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!
			@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
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!
			@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
the flush method is the main "save" method. all file contents are always
 written directly to the file by the RangesIO objects, all this method does
 is write out all the file meta data - dirents, allocation tables, file header
 etc.

 maybe add an option to zero the padding, and any remaining avail blocks in the
 allocation table.

 TODO: long and overly complex. simplify and test better. eg, perhaps move serialization
 of bbat to AllocationTable::Big.