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