Class: Ole::Storage::AllocationTable

Inherits:
Array
  • Object
show all
Defined in:
lib/ole/storage/base.rb

Overview

AllocationTable‘s hold the chains corresponding to files. Given an initial index, AllocationTable#chain follows the chain, returning the blocks that make up that file.

There are 2 allocation tables, the bbat, and sbat, for big and small blocks respectively. The block chain should be loaded using either Storage#read_big_blocks or Storage#read_small_blocks as appropriate.

Whether or not big or small blocks are used for a file depends on whether its size is over the Header#threshold level.

An Ole::Storage document is serialized as a series of directory objects, which are stored in blocks throughout the file. The blocks are either big or small, and are accessed using the AllocationTable.

The bbat allocation table’s data is stored in the spare room in the header block, and in extra blocks throughout the file as referenced by the meta bat. That chain is linear, as there is no higher level table.

AllocationTable.new is used to create an empty table. It can parse a string with the #load method. Serialization is accomplished with the #to_s method.

Direct Known Subclasses

Big, Small

Defined Under Namespace

Classes: Big, Small

Constant Summary collapse

AVAIL =

a free block (I don’t currently leave any blocks free), although I do pad out the allocation table with AVAIL to the block size.

0xffffffff
EOC =

end of a chain

0xfffffffe
BAT =

these blocks are used for storing the allocation table chains

0xfffffffd
META_BAT =
0xfffffffc

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ole) ⇒ AllocationTable

Returns a new instance of AllocationTable.



434
435
436
437
438
# File 'lib/ole/storage/base.rb', line 434

def initialize ole
	@ole = ole
	@sparse = true
	super()
end

Instance Attribute Details

#block_sizeObject (readonly)

Returns the value of attribute block_size.



433
434
435
# File 'lib/ole/storage/base.rb', line 433

def block_size
  @block_size
end

#ioObject (readonly)

Returns the value of attribute io.



433
434
435
# File 'lib/ole/storage/base.rb', line 433

def io
  @io
end

#oleObject (readonly)

Returns the value of attribute ole.



433
434
435
# File 'lib/ole/storage/base.rb', line 433

def ole
  @ole
end

Instance Method Details

#[]=(idx, val) ⇒ Object



524
525
526
527
# File 'lib/ole/storage/base.rb', line 524

def []= idx, val
	@sparse = true if val == AVAIL
	super
end

#blocks_to_ranges(chain, size = nil) ⇒ Object

Turn a chain (an array given by chain) of blocks (optionally truncated to size) into an array of arrays describing the stretches of bytes in the file that it belongs to.

The blocks are Big or Small blocks depending on the table type.



486
487
488
489
490
491
492
493
494
# File 'lib/ole/storage/base.rb', line 486

def blocks_to_ranges chain, size=nil
	# truncate the chain if required
	chain = chain[0, (size.to_f / block_size).ceil] if size
	# convert chain to ranges of the block size
	ranges = chain.map { |i| [block_size * i, block_size] }
	# truncate final range if required
	ranges.last[1] -= (ranges.length * block_size - size) if ranges.last and size
	ranges
end

#chain(idx) ⇒ Object

rewrote this to be non-recursive as it broke on a large attachment chain with a stack error



470
471
472
473
474
475
476
477
478
479
# File 'lib/ole/storage/base.rb', line 470

def chain idx
	a = []
	until idx >= META_BAT
		raise FormatError, "broken allocationtable chain" if idx < 0 || idx > length
		a << idx
		idx = self[idx]
	end
	Log.warn "invalid chain terminator #{idx}" unless idx == EOC
	a
end

#free_blockObject



529
530
531
532
533
534
535
536
# File 'lib/ole/storage/base.rb', line 529

def free_block
	if @sparse
		i = index(AVAIL) and return i
		@sparse = false
	end
	push AVAIL
	length - 1
end

#load(data) ⇒ Object



440
441
442
# File 'lib/ole/storage/base.rb', line 440

def load data
	replace data.unpack('V*')
end

#open(chain, size = nil, &block) ⇒ Object

quick shortcut. chain can be either a head (in which case the table is used to turn it into a chain), or a chain. it is converted to ranges, then to rangesio.



503
504
505
# File 'lib/ole/storage/base.rb', line 503

def open chain, size=nil, &block
	RangesIO.open @io, :ranges => ranges(chain, size), &block
end

#ranges(chain, size = nil) ⇒ Object



496
497
498
499
# File 'lib/ole/storage/base.rb', line 496

def ranges chain, size=nil
	chain = self.chain(chain) unless Array === chain
	blocks_to_ranges chain, size
end

#read(chain, size = nil) ⇒ Object



507
508
509
# File 'lib/ole/storage/base.rb', line 507

def read chain, size=nil
	open chain, size, &:read
end

#resize_chain(blocks, size) ⇒ Object

must return first_block. modifies blocks in place



539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/ole/storage/base.rb', line 539

def resize_chain blocks, size
	new_num_blocks = (size / block_size.to_f).ceil
	old_num_blocks = blocks.length
	if new_num_blocks < old_num_blocks
		# de-allocate some of our old blocks. TODO maybe zero them out in the file???
		(new_num_blocks...old_num_blocks).each { |i| self[blocks[i]] = AVAIL }
		self[blocks[new_num_blocks-1]] = EOC if new_num_blocks > 0
		blocks.slice! new_num_blocks..-1
	elsif new_num_blocks > old_num_blocks
		# need some more blocks.
		last_block = blocks.last
		(new_num_blocks - old_num_blocks).times do
			block = free_block
			# connect the chain. handle corner case of blocks being [] initially
			self[last_block] = block if last_block
			blocks << block
			last_block = block
			self[last_block] = EOC
		end
	end
	# update ranges, and return that also now
	blocks
end

#to_sObject



458
459
460
461
462
463
464
465
466
# File 'lib/ole/storage/base.rb', line 458

def to_s
	table = truncate
	# pad it out some
	num = @ole.bbat.block_size / 4
	# do you really use AVAIL? they probably extend past end of file, and may shortly
	# be used for the bat. not really good.
	table += [AVAIL] * (num - (table.length % num)) if (table.length % num) != 0
	table.pack 'V*'
end

#truncateObject



444
445
446
447
448
449
450
451
452
# File 'lib/ole/storage/base.rb', line 444

def truncate
	# this strips trailing AVAILs. come to think of it, this has the potential to break
	# bogus ole. if you terminate using AVAIL instead of EOC, like I did before. but that is
	# very broken. however, if a chain ends with AVAIL, it should probably be fixed to EOC
	# at load time.
	temp = reverse
	not_avail = temp.find { |b| b != AVAIL } and temp = temp[temp.index(not_avail)..-1]
	temp.reverse
end

#truncate!Object



454
455
456
# File 'lib/ole/storage/base.rb', line 454

def truncate!
	replace truncate
end