Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended props #230

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
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