Class: Mapi::PropertySet

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/mapi/property_set.rb

Overview

The Mapi::PropertySet class is used to wrap the lower level Msg or Pst property stores, and provide a consistent and more friendly interface. It allows you to just say:

properties.subject

instead of:

properites.raw[0x0037, PS_MAPI]

The underlying store can be just a hash, or lazily loading directly from the file. A good compromise is to cache all the available keys, and just return the values on demand, rather than load up many possibly unwanted values.

Defined Under Namespace

Modules: Constants Classes: Key

Constant Summary collapse

NAMES =
{
	oleguid['00020328'] => 'PS_MAPI',
	oleguid['00020329'] => 'PS_PUBLIC_STRINGS',
	oleguid['00020380'] => 'PS_ROUTING_EMAIL_ADDRESSES',
	oleguid['00020381'] => 'PS_ROUTING_ADDRTYPE',
	oleguid['00020382'] => 'PS_ROUTING_DISPLAY_NAME',
	oleguid['00020383'] => 'PS_ROUTING_ENTRYID',
	oleguid['00020384'] => 'PS_ROUTING_SEARCH_KEY',
	# string properties in this namespace automatically get added to the internet headers
	oleguid['00020386'] => 'PS_INTERNET_HEADERS',
	# theres are bunch of outlook ones i think
	# http://blogs.msdn.com/stephen_griffin/archive/2006/05/10/outlook-2007-beta-documentation-notification-based-indexing-support.aspx
	# IPM.Appointment
	oleguid['00062002'] => 'PSETID_Appointment',
	# IPM.Task
	oleguid['00062003'] => 'PSETID_Task',
	# used for IPM.Contact
	oleguid['00062004'] => 'PSETID_Address',
	oleguid['00062008'] => 'PSETID_Common',
	# didn't find a source for this name. it is for IPM.StickyNote
	oleguid['0006200e'] => 'PSETID_Note',
	# for IPM.Activity. also called the journal?
	oleguid['0006200a'] => 'PSETID_Log',
}
SUPPORT_DIR =

duplicated here for now

File.dirname(__FILE__) + '/../..'
TAGS =

data files that provide for the code to symbolic name mapping guids in named_map are really constant references to the above

YAML.load_file "#{SUPPORT_DIR}/data/mapitags.yaml"
NAMED_MAP =
YAML.load_file("#{SUPPORT_DIR}/data/named_map.yaml").inject({}) do |hash, (key, value)|
	hash.update Key.new(key[0], const_get(key[1])) => value
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(raw) ⇒ PropertySet

raw should be an hash-like object that maps Keys to values. Should respond_to? [], keys, values, each, and optionally []=, and delete.



149
150
151
# File 'lib/mapi/property_set.rb', line 149

def initialize raw
	@raw = raw
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object



211
212
213
214
215
216
217
218
219
# File 'lib/mapi/property_set.rb', line 211

def method_missing name, *args
	if name.to_s !~ /\=$/ and args.empty?
		self[name]
	elsif name.to_s =~ /(.*)\=$/ and args.length == 1
		self[$1] = args[0]
	else
		super
	end
end

Instance Attribute Details

#rawObject (readonly)

Returns the value of attribute raw.



145
146
147
# File 'lib/mapi/property_set.rb', line 145

def raw
  @raw
end

Instance Method Details

#[](arg, guid = nil) ⇒ Object



199
200
201
# File 'lib/mapi/property_set.rb', line 199

def [] arg, guid=nil
	raw[resolve(arg, guid)]
end

#[]=(arg, *args) ⇒ Object



203
204
205
206
207
208
209
# File 'lib/mapi/property_set.rb', line 203

def []= arg, *args
	args.unshift nil if args.length == 1
	guid, value = args
	# FIXME this won't really work properly. it would need to go
	# to TAGS to resolve, as it often won't be there already...
	raw[resolve(arg, guid)] = value
end

#bodyObject

for providing rtf to plain text conversion. later, html to text too.



237
238
239
240
241
242
243
244
245
246
# File 'lib/mapi/property_set.rb', line 237

def body
	return @body if defined?(@body)
	@body = (self[:body] rescue nil)
	# last resort
	if !@body or @body.strip.empty?
		Log.warn 'creating text body from rtf'
		@body = (RTF::Converter.rtf2text body_rtf rescue nil)
	end
	@body
end

#body_htmlObject

for providing rtf to html extraction or conversion



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/mapi/property_set.rb', line 263

def body_html
	return @body_html if defined?(@body_html)
	@body_html = self[:body_html]
	# sometimes body_html is a stream, and sometimes a string
	@body_html = @body_html.read if @body_html.respond_to?(:read)
	@body_html = nil if @body_html.to_s.strip.empty?
	if body_rtf and !@body_html
		begin
			@body_html = RTF.rtf2html body_rtf
		rescue
			Log.warn 'unable to extract html from rtf'
		end
		if !@body_html
			Log.warn 'creating html body from rtf'
			begin
				@body_html = RTF::Converter.rtf2text body_rtf, :html
			rescue
				Log.warn 'unable to convert rtf to html'
			end
		end
	end
	@body_html
end

#body_rtfObject

for providing rtf decompression



249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/mapi/property_set.rb', line 249

def body_rtf
	return @body_rtf if defined?(@body_rtf)
	@body_rtf = nil
	if self[:rtf_compressed]
		begin
			@body_rtf = RTF.rtfdecompr self[:rtf_compressed].read
		rescue
			Log.warn 'unable to decompress rtf'
		end
	end
	@body_rtf
end

#inspectObject



225
226
227
228
229
230
# File 'lib/mapi/property_set.rb', line 225

def inspect
	"#<#{self.class} " + to_h.sort_by { |k, v| k.to_s }.map do |k, v|
		v = v.inspect
		"#{k}=#{v.length > 32 ? v[0..29] + '..."' : v}"
	end.join(' ') + '>'
end

#keysObject



191
192
193
# File 'lib/mapi/property_set.rb', line 191

def keys
	sym_to_key.keys
end

#resolve(arg, guid = nil) ⇒ Object

resolve arg (could be key, code, string, or symbol), and possible guid to a key. returns nil on failure



155
156
157
158
159
160
161
162
163
164
# File 'lib/mapi/property_set.rb', line 155

def resolve arg, guid=nil
	if guid;        Key.new arg, guid
	else
		case arg
		when Key;     arg
		when Integer; Key.new arg
		else          sym_to_key[arg.to_sym]
		end
	end
end

#sym_to_keyObject

this is the function that creates a symbol to key mapping. currently this works by making a pass through the raw properties, but conceivably you could map symbols to keys using the mapitags directly. problem with that would be that named properties wouldn’t map automatically, but maybe thats not too important.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/mapi/property_set.rb', line 170

def sym_to_key
	return @sym_to_key if @sym_to_key
	@sym_to_key = {}
	raw.keys.each do |key|
		sym = key.to_sym
		unless Symbol === sym
			Log.debug "couldn't find symbolic name for key #{key.inspect}" 
			next
		end
		if @sym_to_key[sym]
			Log.warn "duplicate key #{key.inspect}"
			# we give preference to PS_MAPI keys
			@sym_to_key[sym] = key if key.guid == PS_MAPI
		else
			# just assign
			@sym_to_key[sym] = key
		end
	end
	@sym_to_key
end

#to_hObject



221
222
223
# File 'lib/mapi/property_set.rb', line 221

def to_h
	sym_to_key.inject({}) { |hash, (sym, key)| hash.update sym => raw[key] }
end

#valuesObject



195
196
197
# File 'lib/mapi/property_set.rb', line 195

def values
	sym_to_key.values.map { |key| raw[key] }
end