Skip to content
Open
64 changes: 48 additions & 16 deletions lib/model/generic_folder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def self.find_folders(root = :msgfolderroot, traversal = 'Shallow', shape = 'Def
if( folder_type.nil? )
resp = (Viewpoint::EWS::EWS.instance).ews.find_folder( [normalize_id(root)], traversal, {:base_shape => shape} )
else
restr = {:restriction =>
restr = {:restriction =>
{:is_equal_to => [{:field_uRI => {:field_uRI=>'folder:FolderClass'}}, {:field_uRI_or_constant=>{:constant => {:value => folder_type}}}]}
}
resp = (Viewpoint::EWS::EWS.instance).ews.find_folder( [normalize_id(root)], traversal, {:base_shape => shape}, restr)
Expand Down Expand Up @@ -109,7 +109,7 @@ def self.get_folder_by_name(name, shape = 'Default')
# For now the :field_uRI and :field_uRI_or_constant must be in an Array for Ruby 1.8.7 because Hashes
# are not positional at insertion until 1.9
restr = {:restriction =>
{:is_equal_to =>
{:is_equal_to =>
[{:field_uRI => {:field_uRI=>'folder:DisplayName'}}, {:field_uRI_or_constant =>{:constant => {:value=>name}}}]}}
resp = (Viewpoint::EWS::EWS.instance).ews.find_folder([:msgfolderroot], 'Deep', {:base_shape => shape}, restr)
if(resp.status == 'Success')
Expand All @@ -127,12 +127,12 @@ def self.get_folder_by_name(name, shape = 'Default')

def initialize(ews_item)
super() # Calls initialize in Model (creates @ews_methods Array)
@ews_item = ews_item
@folder_id = ews_item[:folder_id][:id]
@ews_methods << :folder_id
@ews_methods << :id
@change_key = ews_item[:folder_id][:change_key]
@ews_methods << :change_key
@ews_item = ews_item
@folder_id = ews_item[:folder_id][:id]
@ews_methods << :folder_id
@ews_methods << :id
@change_key = ews_item[:folder_id][:change_key]
@ews_methods << :change_key
unless ews_item[:parent_folder_id].nil?
@parent_id = ews_item[:parent_folder_id]
@ews_methods << :parent_id
Expand All @@ -142,11 +142,12 @@ def initialize(ews_item)
# @todo Handle:
# <EffectiveRights/>, <ExtendedProperty/>, <ManagedFolderInformation/>, <PermissionSet/>

@sync_state = nil # Base-64 encoded sync data
@synced = false # Whether or not the synchronization process is complete
@subscription_id = nil
@watermark = nil
@shallow = true
@tagspace = (Viewpoint::EWS::EWS.instance).tagspace
@sync_state = nil # Base-64 encoded sync data
@synced = false # Whether or not the synchronization process is complete
@subscription_id = nil
@watermark = nil
@shallow = true
end

# Subscribe this folder to events. This method initiates an Exchange pull
Expand Down Expand Up @@ -217,7 +218,26 @@ def get_events
def find_items(opts = {})
opts = opts.clone # clone the passed in object so we don't modify it in case it's being used in a loop
item_shape = opts.has_key?(:item_shape) ? opts.delete(:item_shape) : {:base_shape => 'Default'}
item_shape[:additional_properties] = {:field_uRI => ['item:ParentFolderId']}
tagspace = opts.delete(:tagspace) || @tagspace
if item_shape.has_key?(:additional_properties)
aprops = item_shape[:additional_properties]
if aprops.has_key?(:field_uRI)
raise EwsBadArgumentError, ":field_uRI val should be an Array instead of #{aprops[:field_uRI].class.name}" unless aprops[:field_uRI].is_a?(Array)
aprops[:field_uRI] << ['item:ParentFolderId']
else
aprops[:field_uRI] = ['item:ParentFolderId']
end
if aprops.has_key?(:extended_field_uRI)
raise EwsBadArgumentError, ":extended_field_uRI val should be an Array instead of #{aprops[:extended_field_uRI].class.name}" unless aprops[:extended_field_uRI].is_a?(Array)
aprops[:extended_field_uRI] << [{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}]
else
aprops[:extended_field_uRI] = [{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}]
end
else
item_shape[:additional_properties] = {}
item_shape[:additional_properties][:field_uRI] = ['item:ParentFolderId']
item_shape[:additional_properties][:extended_field_uRI] = [{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}]
end
resp = (Viewpoint::EWS::EWS.instance).ews.find_item([@folder_id], 'Shallow', item_shape, opts)
if(resp.status == 'Success')
parms = resp.items.shift
Expand All @@ -232,6 +252,18 @@ def find_items(opts = {})
end
end

# Find Items with a specific tag
def find_items_with_tag(tag, opts = {})
tagspace = opts[:tagspace] || @tagspace

restrict = { :restriction => {
:is_equal_to => [ {:extended_field_uRI=>{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}},
{:field_uRI_or_constant => {:constant => {:value=>tag}}} ]
} }

find_items(opts.merge(restrict))
end

# Fetch only items from today (since midnight)
def todays_items(opts = {})
#opts = {:query_string => ["Received:today"]}
Expand All @@ -243,7 +275,7 @@ def todays_items(opts = {})
# @param [DateTime] date_time the time to fetch Items since.
def items_since(date_time, opts = {})
restr = {:restriction =>
{:is_greater_than_or_equal_to =>
{:is_greater_than_or_equal_to =>
[{:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
{:field_uRI_or_constant =>{:constant => {:value=>date_time}}}]
}}
Expand All @@ -255,7 +287,7 @@ def items_since(date_time, opts = {})
# @param [DateTime] end_date the time to stop fetching Items from
def items_between(start_date, end_date, opts={})
restr = {:restriction => {:and => [
{:is_greater_than_or_equal_to =>
{:is_greater_than_or_equal_to =>
[{:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
{:field_uRI_or_constant=>{:constant => {:value =>start_date}}}]},
{:is_less_than_or_equal_to =>
Expand Down
102 changes: 93 additions & 9 deletions lib/model/item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,27 @@ def self.add_attachments(parent_id, attachments)
attachments.each do |a|
b64attach << {:name => {:text =>(File.basename a.path)}, :content => {:text => Base64.encode64(a.read)}}
end
resp = conn.ews.create_attachment(parent_id, b64attach)
resp = conn.ews.create_attachment(parent_id, b64attach)
(resp.status == 'Success') || (raise EwsError, "Could not create attachments. #{resp.code}: #{resp.message}")
{:id => resp.items.first[:attachment_id][:root_item_id], :change_key => resp.items.first[:attachment_id][:root_item_change_key]}
end

attr_reader :item_id, :change_key
attr_reader :item_id, :change_key, :tags
alias :id :item_id

# Initialize an Exchange Web Services item
# @param [Hash] ews_item A hash representing this item
# @param [Boolean] shallow Whether or not we have retrieved all the elements for this object
def initialize(ews_item, shallow = true)
super() # Calls initialize in Model (creates @ews_methods Array)
@ews_item = ews_item
@shallow = shallow
@item_id = ews_item[:item_id][:id]
@ews_item = ews_item
@shallow = shallow
@item_id = ews_item[:item_id][:id]
@change_key = ews_item[:item_id][:change_key]
@text_only = false
@updates = {}
@text_only = false
@updates = {}
@tags = parse_tags
@tagspace = (Viewpoint::EWS::EWS.instance).tagspace

init_methods
end
Expand Down Expand Up @@ -182,7 +184,7 @@ def deepen!
return true unless @shallow
conn = Viewpoint::EWS::EWS.instance
shape = {:base_shape => 'AllProperties', :body_type => (@text_only ? 'Text' : 'Best')}
resp = conn.ews.get_item([@item_id], shape)
resp = conn.ews.get_item([@item_id], shape)
resp = resp.items.shift
@ews_item = resp[resp.keys.first]
@shallow = false
Expand Down Expand Up @@ -249,7 +251,7 @@ def attachments
end

# Delete this item
# @param [Boolean] soft Whether or not to do a soft delete. By default EWS will do a
# @param [Boolean] soft Whether or not to do a soft delete. By default EWS will do a
# hard delete of this item. See the MSDN docs for more info:
# http://msdn.microsoft.com/en-us/library/aa562961.aspx
# @return [Boolean] Whether or not the item was deleted
Expand Down Expand Up @@ -278,7 +280,69 @@ def parent_folder
GenericFolder.get_folder @parent_folder_id
end

# Use ExtendedProperties to create tags
# @param [String] tag a tag to add to this item
# @param [Hash] opts options to pass to add_tag!
# @option opts [String] :tagspace the namespace to add the tag to. (default: 'viewpoint_tags')
def add_tag!(tag, opts={})
@tags |= [tag.downcase]
set_tags!(@tags, opts)
end

# @param [String] tag a tag to delete from this item
# @param [Hash] opts options to pass to remove_tag!
# @option opts [String] :tagspace the namespace to add the tag to. (default: 'viewpoint_tags')
def remove_tag!(tag, opts={})
@tags -= [tag.downcase]
if(@tags.blank?)
clear_all_tags!(opts)
else
set_tags!(@tags, opts)
end
end

# @param [Hash] opts options to pass to clear_all_tags!
# @option opts [String] :tagspace the namespace to add the tag to. (default: 'viewpoint_tags')
def clear_all_tags!(opts={})
tagspace = opts[:tagspace] || @tagspace
vtag = {:preformatted => []}
vtag[:preformatted] << {:delete_item_field =>
{:extended_field_uRI=>{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}}
}

if(self.update_attribs!(vtag))
@tags = []
true
else
false
end
end

# @param [Array<String>] tags viewpoint_tags to set on this item
# @param [Hash] opts options to pass to set_tags!
# @option opts [String] :tagspace the namespace to add the tag to. (default: 'viewpoint_tags')
def set_tags!(tags, opts={})
tagspace = opts[:tagspace] || @tagspace
i_type = self.class.name.split(/::/).last.ruby_case.to_sym

tag_vals = []
tags.each do |t|
tag_vals << {:value => {:text => t}}
end

vtag = {:preformatted => []}
vtag[:preformatted] << {:set_item_field => [
{:extended_field_uRI=>{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}},
{i_type => [
{:extended_property => [
{:extended_field_uRI=>{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}},
{:values => tag_vals}
]}
]}
]}

self.update_attribs!(vtag)
end

private

Expand Down Expand Up @@ -310,6 +374,26 @@ def method_missing(m, *args, &block)
end
end

def parse_tags(opts={})
tagspace = opts[:tagspace] || @tagspace

return [] unless(@ews_item.has_key?(:extended_property) &&
@ews_item[:extended_property].has_key?(:extended_field_u_r_i) &&
@ews_item[:extended_property][:extended_field_u_r_i].has_key?(:property_name) &&
@ews_item[:extended_property][:extended_field_u_r_i][:property_name] == tagspace)

tags = []
vals = @ews_item[:extended_property][:values][:value]
if vals.is_a?(Array)
vals.each do |v|
tags << v[:text]
end
else
tags << vals[:text]
end
tags
end

end # Item
end # EWS
end # Viewpoint
26 changes: 21 additions & 5 deletions lib/soap/handsoap/builders/ews_build_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ def folder_shape!(node, folder_shape)
end
end

# This isn't exactly pretty for AdditionalProperties, but it works. The incoming Hash should be formulated like so:
# @example
# :additional_properties => {
# :extended_field_uRI => [{:distinguished_property_set_id=>"PublicStrings", :property_name=>"viewpoint_tags", :property_type=>"StringArray"}]
# }
# @todo Finish AdditionalProperties implementation
def item_shape!(node, item_shape)
node.add("#{NS_EWS_MESSAGES}:ItemShape") do |is|
Expand All @@ -157,11 +162,22 @@ def item_shape!(node, item_shape)
is.add("#{NS_EWS_TYPES}:FilterHtmlContent", item_shape[:filter_html_content]) if item_shape.has_key?(:filter_html_content)
is.add("#{NS_EWS_TYPES}:ConvertHtmlCodePageToUTF8", item_shape[:convert_html_code_page_to_utf8]) if item_shape.has_key?(:convert_html_code_page_to_utf8)
unless( item_shape[:additional_properties].nil? )
unless( item_shape[:additional_properties][:field_uRI].nil? )
is.add("#{NS_EWS_TYPES}:AdditionalProperties") do |addprops|
item_shape[:additional_properties][:field_uRI].each do |uri|
addprops.add("#{NS_EWS_TYPES}:FieldURI") { |furi| furi.set_attr('FieldURI', uri) }
end
is.add("#{NS_EWS_TYPES}:AdditionalProperties") do |addprops|
item_shape[:additional_properties].each_pair do |prop_t,prop_v|
case prop_t
when :field_uRI
prop_v.each do |uri|
addprops.add("#{NS_EWS_TYPES}:FieldURI") { |furi| furi.set_attr('FieldURI', uri) }
end
when :extended_field_uRI
prop_v.each do |uri|
addprops.add("#{NS_EWS_TYPES}:ExtendedFieldURI") do |furi|
uri.each_pair do |attr,val|
furi.set_attr(attr.to_s.camel_case, val)
end
end
end
end #when
end
end
end
Expand Down
5 changes: 5 additions & 0 deletions lib/viewpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,15 @@ module EWS
# @attr_reader [Viewpoint::EWS::SOAP::ExchangeWebService] :ews The EWS object used
# to make SOAP calls. You typically don't need to use this, but if you want to
# play around with the SOAP back-end it's available.
# @attr_accessor [String] :tagspace the name of the tagspace to collect tags in (default: viewpoint_tags)
# Set the tagspace used for creating tags on the Model objects. Under the covers this uses an Exchange
# StringArray and Exchange extended properties.
class EWS
include Singleton
include Viewpoint

attr_reader :ews
attr_accessor :tagspace

# Set the endpoint for Exchange Web Services.
# @param [String] endpoint The URL of the endpoint. This should end in
Expand Down Expand Up @@ -122,6 +126,7 @@ def self.set_trust_ca(ca_path)
end

def initialize
@tagspace ||= 'viewpoint_tags'
@ews = SOAP::ExchangeWebService.new
end

Expand Down