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.

Parameters:

  • raw (Hash)


164
165
166
# File 'lib/mapi/property_set.rb', line 164

def initialize raw
	@raw = raw
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object



232
233
234
235
236
237
238
239
240
# File 'lib/mapi/property_set.rb', line 232

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

#rawHash (readonly)

Returns:

  • (Hash)


158
159
160
# File 'lib/mapi/property_set.rb', line 158

def raw
  @raw
end

Instance Method Details

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



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

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

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



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

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

#bodyString?

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

Returns:

  • (String, nil)


268
269
270
271
272
273
274
275
276
277
# File 'lib/mapi/property_set.rb', line 268

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 = decode_ansi_str(RTF::Converter.rtf2text body_rtf) rescue nil
	end
	@body
end

#body_htmlString?

for providing rtf to html extraction or conversion

Returns:

  • (String, nil)


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
# File 'lib/mapi/property_set.rb', line 298

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
			#https://github.com/l3akage/ruby-msg/commit/90865e091b21c4b738dcba45015ddbbf7b3d3fb3
			#@body_html = RTF.rtf2html body_rtf
			@body_html = decode_ansi_str(RTF.rtf2html body_rtf)
		rescue => e
			Log.warn "unable to extract html from rtf: #{e.class} #{e.message}"
		end
		if !@body_html
			# Log.warn 'creating html body from rtf'
			begin
				#https://github.com/l3akage/ruby-msg/commit/90865e091b21c4b738dcba45015ddbbf7b3d3fb3
				#@body_html = RTF::Converter.rtf2text body_rtf, :html
				@body_html = decode_ansi_str(RTF::Converter.rtf2text body_rtf, :html)
			rescue
				Log.warn "unable to convert rtf to html #{e.class} #{e.message}"
			end
		end
	end
	#https://github.com/l3akage/ruby-msg/commit/90865e091b21c4b738dcba45015ddbbf7b3d3fb3
	#@body_html.force_encoding("iso-8859-1").encode('utf-8') if @body_html && @body_html.respond_to?(:encoding)
	@body_html
end

#body_rtfString?

for providing rtf decompression

Returns:

  • (String, nil)


282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/mapi/property_set.rb', line 282

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

#decode_ansi_str(str) ⇒ Object



253
254
255
256
257
258
259
# File 'lib/mapi/property_set.rb', line 253

def decode_ansi_str str
	if defined? raw.helper
		raw.helper.convert_ansi_str(str)
	else
		str
	end
end

#inspectObject



246
247
248
249
250
251
# File 'lib/mapi/property_set.rb', line 246

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



212
213
214
# File 'lib/mapi/property_set.rb', line 212

def keys
	sym_to_key.keys
end

#resolve(arg, guid = nil) ⇒ Key

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

Parameters:

  • arg (Symbol)
  • guid (Ole::Types::Clsid, nil) (defaults to: nil)

Returns:



174
175
176
177
178
179
180
181
182
183
# File 'lib/mapi/property_set.rb', line 174

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_keyHash{Symbol => Key}

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.

Returns:

  • (Hash{Symbol => Key})


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/mapi/property_set.rb', line 191

def sym_to_key
	return @sym_to_key if defined? @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



242
243
244
# File 'lib/mapi/property_set.rb', line 242

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

#valuesObject



216
217
218
# File 'lib/mapi/property_set.rb', line 216

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