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
# 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
      @body_html = decode_ansi_str(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 = decode_ansi_str(RTF::Converter.rtf2text body_rtf, :html)
      rescue
        Log.warn 'unable to convert rtf to html'
      end
    end
  end
  @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