Class: Palm::PDB

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

Overview

PDB handles reading and writing raw Palm PDB records and resources. For most cases, users will probably want to extend this class class, overriding pack_entry and unpack_entry to support their record types.

Records are simply stored as an array in data, so polish up on your enumerable tricks. The created_at, modified_at, and backed_up_at attributes are all stored as Times. Note that modified_at is not automatically updated.

Direct Known Subclasses

WabaDB

Constant Summary collapse

HEADER_LENGTH =

Size of database header

32+2+2+(9*4)
RECORD_INDEX_HEADER_LEN =

Size of record index header

6
INDEX_RECORD_LENGTH =

Length of record index entry

8
INDEX_RESOURCE_LENGTH =

Length of resource index entry

10
ATTRIBUTE_CODES =
{
	"resource"	=>			0x0001,
	"read-only" =>			0x0002,
	"AppInfo dirty" =>	0x0004,
	"backup"	=>				0x0008,
	"OK newer"	=>			0x0010,
	"reset"	 =>					0x0020,
	"launchable"	=>		0x0200,
	"open"	=>					0x8000,
	
	# PalmOS 5.0 attribute names
	"ResDB" =>						0x0001,
	"ReadOnly" =>					0x0002,
	"AppInfoDirty" =>			0x0004,
	"Backup"	=>					0x0008,
	"OKToInstallNewer" =>	0x0010,
	"ResetAfterInstall"=> 0x0020,
	"LaunchableData"	=>	0x0200,
	"Recyclable"	=>			0x0400,
	"Bundle"	=>					0x0800,
	"Open"	=>						0x8000,
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(from = nil) ⇒ PDB

Creates a new PDB. If from is passed a String, a file will be loaded from that path (see load_file). If a IO object is passed in, then it will be used to load the palm data (see load).



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/palm/pdb.rb', line 68

def initialize(from = nil)
	@attributes = {}
	@data = []
	@appinfo_block = nil
	@sort_block = nil
	@backed_up_at = @created_at = @modified_at = Time.now
	
	case from
	when NilClass
		now = Time.now
		@created_at		= now
		@modified_at	= now
		@version	= 0
		@modnum		= 0
		@type		= "\0\0\0\0"
		@creator	= "\0\0\0\0"
		@unique_id_seed = 0
	when String
		load(open(from))
	when IO
		load(from)
	else
		raise ArgumentError.new("Unknown value to load from #{from.inspect}.  Use a String or IO object.")
	end
end

Instance Attribute Details

#attributesObject

Returns the value of attribute attributes.



31
32
33
# File 'lib/palm/pdb.rb', line 31

def attributes
  @attributes
end

#backed_up_atObject

Returns the value of attribute backed_up_at.



32
33
34
# File 'lib/palm/pdb.rb', line 32

def backed_up_at
  @backed_up_at
end

#created_atObject

Returns the value of attribute created_at.



32
33
34
# File 'lib/palm/pdb.rb', line 32

def created_at
  @created_at
end

#creatorObject

Returns the value of attribute creator.



33
34
35
# File 'lib/palm/pdb.rb', line 33

def creator
  @creator
end

#dataObject

Returns the value of attribute data.



35
36
37
# File 'lib/palm/pdb.rb', line 35

def data
  @data
end

#modified_atObject

Returns the value of attribute modified_at.



32
33
34
# File 'lib/palm/pdb.rb', line 32

def modified_at
  @modified_at
end

#modnumObject

Returns the value of attribute modnum.



33
34
35
# File 'lib/palm/pdb.rb', line 33

def modnum
  @modnum
end

#nameObject

Returns the value of attribute name.



31
32
33
# File 'lib/palm/pdb.rb', line 31

def name
  @name
end

#typeObject

Returns the value of attribute type.



33
34
35
# File 'lib/palm/pdb.rb', line 33

def type
  @type
end

#unique_id_seedObject

Returns the value of attribute unique_id_seed.



34
35
36
# File 'lib/palm/pdb.rb', line 34

def unique_id_seed
  @unique_id_seed
end

#versionObject

Returns the value of attribute version.



31
32
33
# File 'lib/palm/pdb.rb', line 31

def version
  @version
end

Instance Method Details

#load(io) ⇒ Object

Loads the PDB from the given IO source.



107
108
109
110
111
112
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
# File 'lib/palm/pdb.rb', line 107

def load(io)
	# Set to binary mode for windows environment
	io.binmode if io.respond_to? :binmode
	
	start_postion = io.pos
	io.seek(0, IO::SEEK_END)
	io_size = io.pos
	io.seek(start_postion)
	
	appinfo_offset, sort_offset = unpack_header(io.read(HEADER_LENGTH))
	
	# parse the record index
	record_index = io.read(RECORD_INDEX_HEADER_LEN)
	next_index, record_count = record_index.unpack("N n")
	
	# load the indexes, gather information about offsets and
	# record lengths
	indexes = nil
	if resource?
		indexes = load_resource_index(io, next_index, record_count)
	else
		indexes = load_record_index(io, next_index, record_count)
	end
	# Add the final offset as a Datablock for the end of the file
	indexes << DataBlock.new(io_size, 0)
	# Fill in the lengths for each of these index entries 
	indexes.each_cons(2){|starts, ends| starts.record_length = ends.offset - starts.offset }
	# Calculate where the data starts (or end of file if empty)
	data_offset = indexes.first.offset
	
	# Pop the last entry back off.  We pushed it on make it easier to calculate the lengths
	# of each entry.
	indexes.pop
	
	# Load optional chunks
	load_appinfo_block(io, appinfo_offset, sort_offset, data_offset) if appinfo_offset > 0
	load_sort_block(io, sort_offset, data_offset) if sort_offset > 0
	
	# Load data
	load_data(io, indexes)
	io.close
end

#load_file(path) ⇒ Object

Loads the PDB from a file path



100
101
102
103
104
# File 'lib/palm/pdb.rb', line 100

def load_file(path)
	open path, "r" do |io|
		load io
	end
end

#resource?Boolean

Returns true if the PDB is a set of resources, false if it is a set of records

Returns:

  • (Boolean)


95
96
97
# File 'lib/palm/pdb.rb', line 95

def resource?
	@attributes['resource'] || @attributes['ResDB']
end

#write(io) ⇒ Object

Writes PDB to an IO object



270
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
# File 'lib/palm/pdb.rb', line 270

def write(io)		
	io.binmode if io.respond_to? :binmode
	
	# Track the current offset for each section
	offset_position = HEADER_LENGTH + 2 #(2: Index Header length)
	
	index_length = RECORD_INDEX_HEADER_LEN + 
		@data.length * (resource? ? INDEX_RESOURCE_LENGTH : INDEX_RECORD_LENGTH )
	
	offset_position += index_length	# Advance for the index
	
	packed_entries = @data.map{|e| pack_entry(e)}
	
	packed_app_info = pack_app_info_block()
	packed_sort = 		pack_sort_block()
	
	# Calculate AppInfo block offset
	app_info_offset = 0
	if packed_app_info and !packed_app_info.empty?
		app_info_offset = offset_position
		offset_position += packed_app_info.length	# Advance for the app_info_block
	end
	
	# Calculate sort block offset
	sort_offset = 0
	if packed_sort and !packed_sort.empty?
		sort_offset = offset_position
		offset_position += packed_sort.length	# Advance for the sort_block
	end
	
	packed_header = pack_header(app_info_offset, sort_offset)
	
	index_header = [0, @data.length ].pack "N n"

	packed_index = @data.zip(packed_entries).map do |entry, packed|
		index = nil
		if resource?
			index = [entry.record_type, entry.record_id, offset_position].pack "a4 n N"
		else
			index = [
				offset_position, entry.packed_attributes,
				(entry.record_id >> 16) & 0xff,
				(entry.record_id >> 8) & 0xff,
				entry.record_id & 0xff
			].pack "N C C3"
		end
		offset_position += packed.length
		index
	end
	
	# Write to IO stream
	io << packed_header
	io << index_header
	io << packed_index.join
	io << "\0\0" # 2 null byte separator
	io << @app_info_block unless app_info_offset == 0
	io << @sort_block unless sort_offset == 0
	io << packed_entries.join
end

#write_file(path) ⇒ Object

Writes to the given path



263
264
265
266
267
# File 'lib/palm/pdb.rb', line 263

def write_file(path)
	open(path, "w") do |io| 
		write io
	end
end