viewpoint 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -6
  3. data/lib/ews/calendar_accessors.rb +37 -33
  4. data/lib/ews/connection.rb +152 -125
  5. data/lib/ews/connection_helper.rb +36 -33
  6. data/lib/ews/convert_accessors.rb +58 -54
  7. data/lib/ews/errors.rb +59 -53
  8. data/lib/ews/ews_client.rb +92 -79
  9. data/lib/ews/exceptions/exceptions.rb +61 -61
  10. data/lib/ews/folder_accessors.rb +235 -234
  11. data/lib/ews/impersonation.rb +29 -23
  12. data/lib/ews/item_accessors.rb +215 -213
  13. data/lib/ews/mailbox_accessors.rb +82 -79
  14. data/lib/ews/meeting_accessors.rb +38 -31
  15. data/lib/ews/message_accessors.rb +83 -81
  16. data/lib/ews/push_subscription_accessors.rb +29 -27
  17. data/lib/ews/room_accessors.rb +48 -46
  18. data/lib/ews/roomlist_accessors.rb +47 -45
  19. data/lib/ews/soap/builders/ews_builder.rb +1191 -1176
  20. data/lib/ews/soap/ews_response.rb +68 -73
  21. data/lib/ews/soap/ews_soap_availability_response.rb +57 -57
  22. data/lib/ews/soap/ews_soap_free_busy_response.rb +115 -117
  23. data/lib/ews/soap/ews_soap_response.rb +99 -101
  24. data/lib/ews/soap/ews_soap_room_response.rb +45 -45
  25. data/lib/ews/soap/ews_soap_roomlist_response.rb +45 -46
  26. data/lib/ews/soap/exchange_availability.rb +54 -52
  27. data/lib/ews/soap/exchange_data_services.rb +693 -711
  28. data/lib/ews/soap/exchange_notification.rb +123 -128
  29. data/lib/ews/soap/exchange_synchronization.rb +82 -82
  30. data/lib/ews/soap/exchange_time_zones.rb +41 -44
  31. data/lib/ews/soap/exchange_user_configuration.rb +30 -27
  32. data/lib/ews/soap/exchange_web_service.rb +237 -233
  33. data/lib/ews/soap/parsers/ews_parser.rb +46 -42
  34. data/lib/ews/soap/parsers/ews_sax_document.rb +63 -58
  35. data/lib/ews/soap/response_message.rb +69 -68
  36. data/lib/ews/soap/responses/create_attachment_response_message.rb +45 -45
  37. data/lib/ews/soap/responses/create_item_response_message.rb +26 -25
  38. data/lib/ews/soap/responses/find_item_response_message.rb +76 -78
  39. data/lib/ews/soap/responses/get_events_response_message.rb +52 -51
  40. data/lib/ews/soap/responses/send_notification_response_message.rb +53 -51
  41. data/lib/ews/soap/responses/subscribe_response_message.rb +37 -34
  42. data/lib/ews/soap/responses/sync_folder_hierarchy_response_message.rb +38 -35
  43. data/lib/ews/soap/responses/sync_folder_items_response_message.rb +38 -35
  44. data/lib/ews/soap.rb +27 -26
  45. data/lib/ews/templates/calendar_item.rb +64 -60
  46. data/lib/ews/templates/forward_item.rb +19 -17
  47. data/lib/ews/templates/message.rb +61 -58
  48. data/lib/ews/templates/reply_to_item.rb +20 -18
  49. data/lib/ews/templates/task.rb +55 -55
  50. data/lib/ews/types/attachment.rb +75 -74
  51. data/lib/ews/types/attendee.rb +27 -25
  52. data/lib/ews/types/calendar_folder.rb +58 -59
  53. data/lib/ews/types/calendar_item.rb +133 -131
  54. data/lib/ews/types/contact.rb +11 -5
  55. data/lib/ews/types/contacts_folder.rb +10 -5
  56. data/lib/ews/types/copied_event.rb +47 -48
  57. data/lib/ews/types/created_event.rb +22 -20
  58. data/lib/ews/types/deleted_event.rb +22 -20
  59. data/lib/ews/types/distribution_list.rb +11 -5
  60. data/lib/ews/types/event.rb +60 -59
  61. data/lib/ews/types/export_items_response_message.rb +54 -50
  62. data/lib/ews/types/file_attachment.rb +63 -61
  63. data/lib/ews/types/folder.rb +58 -57
  64. data/lib/ews/types/free_busy_changed_event.rb +22 -20
  65. data/lib/ews/types/generic_folder.rb +348 -372
  66. data/lib/ews/types/item.rb +401 -397
  67. data/lib/ews/types/item_attachment.rb +87 -80
  68. data/lib/ews/types/item_field_uri_map.rb +201 -200
  69. data/lib/ews/types/mailbox_user.rb +146 -154
  70. data/lib/ews/types/meeting_cancellation.rb +11 -5
  71. data/lib/ews/types/meeting_message.rb +11 -5
  72. data/lib/ews/types/meeting_request.rb +11 -5
  73. data/lib/ews/types/meeting_response.rb +11 -5
  74. data/lib/ews/types/message.rb +11 -5
  75. data/lib/ews/types/modified_event.rb +43 -45
  76. data/lib/ews/types/moved_event.rb +47 -48
  77. data/lib/ews/types/new_mail_event.rb +22 -20
  78. data/lib/ews/types/out_of_office.rb +145 -144
  79. data/lib/ews/types/post_item.rb +11 -5
  80. data/lib/ews/types/search_folder.rb +10 -5
  81. data/lib/ews/types/status_event.rb +37 -36
  82. data/lib/ews/types/task.rb +39 -33
  83. data/lib/ews/types/tasks_folder.rb +31 -25
  84. data/lib/ews/types.rb +134 -139
  85. data/lib/viewpoint/logging/config.rb +19 -19
  86. data/lib/viewpoint/logging.rb +23 -17
  87. data/lib/viewpoint/string_utils.rb +31 -32
  88. data/lib/viewpoint.rb +18 -18
  89. metadata +50 -25
  90. data/Changelog.txt +0 -1004
@@ -1,418 +1,394 @@
1
- =begin
2
- This file is part of Viewpoint; the Ruby library for Microsoft Exchange Web Services.
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Viewpoint; the Ruby library for Microsoft Exchange Web Services.
4
+ #
5
+ # Copyright © 2011 Dan Wanek <dan.wanek@gmail.com>
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
3
18
 
4
- Copyright © 2011 Dan Wanek <dan.wanek@gmail.com>
5
-
6
- Licensed under the Apache License, Version 2.0 (the "License");
7
- you may not use this file except in compliance with the License.
8
- You may obtain a copy of the License at
19
+ require 'ews/item_accessors'
9
20
 
10
- http://www.apache.org/licenses/LICENSE-2.0
21
+ module Viewpoint
22
+ module EWS
23
+ module Types
24
+ # Generic Folder EWS data type.
25
+ module GenericFolder
26
+ include Viewpoint::EWS
27
+ include Viewpoint::EWS::Types
28
+ include Viewpoint::EWS::ItemAccessors
29
+ include Viewpoint::StringUtils
30
+
31
+ GFOLDER_KEY_PATHS = {
32
+ folder_id: %i[folder_id attribs],
33
+ id: %i[folder_id attribs id],
34
+ change_key: %i[folder_id attribs change_key],
35
+ parent_folder_id: %i[parent_folder_id attribs id],
36
+ parent_folder_change_key: %i[parent_folder_id attribs change_key],
37
+ folder_class: %i[folder_class text],
38
+ total_count: %i[total_count text],
39
+ child_folder_count: %i[child_folder_count text],
40
+ display_name: %i[display_name text]
41
+ }.freeze
42
+
43
+ GFOLDER_KEY_TYPES = {
44
+ total_count: lambda(&:to_i),
45
+ child_folder_count: lambda(&:to_i)
46
+ }.freeze
47
+
48
+ GFOLDER_KEY_ALIAS = {
49
+ name: :display_name,
50
+ ckey: :change_key
51
+ }.freeze
52
+
53
+ attr_accessor :subscription_id, :watermark, :sync_state
54
+
55
+ # @param [SOAP::ExchangeWebService] ews the EWS reference
56
+ # @param [Hash] ews_item the EWS parsed response document
57
+ def initialize(ews, ews_item)
58
+ super
59
+ simplify!
60
+ @sync_state = nil
61
+ @synced = false
62
+ end
11
63
 
12
- Unless required by applicable law or agreed to in writing, software
13
- distributed under the License is distributed on an "AS IS" BASIS,
14
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- See the License for the specific language governing permissions and
16
- limitations under the License.
17
- =end
64
+ def delete!
65
+ opts = {
66
+ folder_ids: [{ id: id }],
67
+ delete_type: 'HardDelete'
68
+ }
69
+ resp = @ews.delete_folder(opts)
70
+ resp.success? || raise(EwsError, "Could not delete folder. #{resp.code}: #{resp.message}")
71
+ end
18
72
 
19
- require 'ews/item_accessors'
73
+ def items(opts = {})
74
+ args = items_args(opts.clone)
75
+ obj = OpenStruct.new(opts: args, restriction: {})
76
+ yield obj if block_given?
77
+ merge_restrictions! obj
78
+ resp = ews.find_item(args)
79
+ items_parser resp
80
+ end
20
81
 
21
- module Viewpoint::EWS::Types
22
- module GenericFolder
23
- include Viewpoint::EWS
24
- include Viewpoint::EWS::Types
25
- include Viewpoint::EWS::ItemAccessors
26
- include Viewpoint::StringUtils
27
-
28
- GFOLDER_KEY_PATHS = {
29
- :folder_id => [:folder_id, :attribs],
30
- :id => [:folder_id, :attribs, :id],
31
- :change_key => [:folder_id, :attribs, :change_key],
32
- :parent_folder_id => [:parent_folder_id, :attribs, :id],
33
- :parent_folder_change_key => [:parent_folder_id, :attribs, :change_key],
34
- :folder_class => [:folder_class, :text],
35
- :total_count => [:total_count, :text],
36
- :child_folder_count => [:child_folder_count, :text],
37
- :display_name => [:display_name, :text],
38
- }
39
-
40
- GFOLDER_KEY_TYPES = {
41
- :total_count => ->(str){str.to_i},
42
- :child_folder_count => ->(str){str.to_i},
43
- }
44
-
45
- GFOLDER_KEY_ALIAS = {
46
- :name => :display_name,
47
- :ckey => :change_key,
48
- }
49
-
50
- attr_accessor :subscription_id, :watermark, :sync_state
51
-
52
- # @param [SOAP::ExchangeWebService] ews the EWS reference
53
- # @param [Hash] ews_item the EWS parsed response document
54
- def initialize(ews, ews_item)
55
- super
56
- simplify!
57
- @sync_state = nil
58
- @synced = false
59
- end
82
+ # Fetch items since a give DateTime
83
+ # @param [DateTime] date_time the time to fetch Items since.
84
+ def items_since(date_time, opts = {})
85
+ opts = opts.clone
86
+ raise EwsBadArgumentError, 'First argument must be a Date or DateTime' unless date_time.is_a?(Date)
87
+
88
+ restr = { restriction: { is_greater_than_or_equal_to: [
89
+ { field_uRI: { field_uRI: 'item:DateTimeReceived' } },
90
+ { field_uRI_or_constant: { constant: { value: date_time.to_datetime } } }
91
+ ] } }
92
+ items(opts.merge(restr))
93
+ end
60
94
 
61
- def delete!
62
- opts = {
63
- :folder_ids => [:id => id],
64
- :delete_type => 'HardDelete'
65
- }
66
- resp = @ews.delete_folder(opts)
67
- if resp.success?
68
- true
69
- else
70
- raise EwsError, "Could not delete folder. #{resp.code}: #{resp.message}"
71
- end
72
- end
95
+ # Fetch only items from today (since midnight)
96
+ def todays_items(_opts = {})
97
+ items_since(Date.today)
98
+ end
73
99
 
74
- def items(opts = {})
75
- args = items_args(opts.clone)
76
- obj = OpenStruct.new(opts: args, restriction: {})
77
- yield obj if block_given?
78
- merge_restrictions! obj
79
- resp = ews.find_item(args)
80
- items_parser resp
81
- end
100
+ # Fetch items between a given time period
101
+ # @param [DateTime] start_date the time to start fetching Items from
102
+ # @param [DateTime] end_date the time to stop fetching Items from
103
+ def items_between(start_date, end_date, _opts = {})
104
+ items do |obj|
105
+ obj.restriction = { and: [
106
+ { is_greater_than_or_equal_to: [
107
+ { field_uRI: { field_uRI: 'item:DateTimeReceived' } },
108
+ { field_uRI_or_constant: { constant: { value: start_date } } }
109
+ ] },
110
+ { is_less_than_or_equal_to: [
111
+ { field_uRI: { field_uRI: 'item:DateTimeReceived' } },
112
+ { field_uRI_or_constant: { constant: { value: end_date } } }
113
+ ] }
114
+ ] }
115
+ end
116
+ end
82
117
 
83
- # Fetch items since a give DateTime
84
- # @param [DateTime] date_time the time to fetch Items since.
85
- def items_since(date_time, opts = {})
86
- opts = opts.clone
87
- unless date_time.kind_of?(Date)
88
- raise EwsBadArgumentError, "First argument must be a Date or DateTime"
89
- end
90
- restr = {:restriction =>
91
- {:is_greater_than_or_equal_to =>
92
- [{:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
93
- {:field_uRI_or_constant =>{:constant => {:value=>date_time.to_datetime}}}]
94
- }}
95
- items(opts.merge(restr))
96
- end
118
+ # Search on the item subject
119
+ # @param [String] match_str A simple string paramater to match against the
120
+ # subject. The search ignores case and does not accept regexes... only strings.
121
+ # @param [String,nil] exclude_str A string to exclude from matches against
122
+ # the subject. This is optional.
123
+ def search_by_subject(match_str, exclude_str = nil)
124
+ items do |obj|
125
+ match = { contains: {
126
+ containment_mode: 'Substring',
127
+ containment_comparison: 'IgnoreCase',
128
+ field_uRI: { field_uRI: 'item:Subject' },
129
+ constant: { value: match_str }
130
+ } }
131
+ unless exclude_str.nil?
132
+ excl = { not: { contains: {
133
+ containment_mode: 'Substring',
134
+ containment_comparison: 'IgnoreCase',
135
+ field_uRI: { field_uRI: 'item:Subject' },
136
+ constant: { value: exclude_str }
137
+ } } }
138
+
139
+ match[:and] = [{ contains: match.delete(:contains) }, excl]
140
+ end
141
+ obj.restriction = match
142
+ end
143
+ end
97
144
 
98
- # Fetch only items from today (since midnight)
99
- def todays_items(opts = {})
100
- items_since(Date.today)
101
- end
145
+ def get_all_properties!
146
+ @ews_item = get_folder(base_shape: 'AllProperties')
147
+ simplify!
148
+ end
102
149
 
103
- # Fetch items between a given time period
104
- # @param [DateTime] start_date the time to start fetching Items from
105
- # @param [DateTime] end_date the time to stop fetching Items from
106
- def items_between(start_date, end_date, opts={})
107
- items do |obj|
108
- obj.restriction = { :and =>
109
- [
110
- {:is_greater_than_or_equal_to =>
111
- [
112
- {:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
113
- {:field_uRI_or_constant=>{:constant => {:value =>start_date}}}
114
- ]
150
+ def available_categories
151
+ opts = {
152
+ user_config_name: {
153
+ name: 'CategoryList',
154
+ distinguished_folder_id: { id: :calendar }
115
155
  },
116
- {:is_less_than_or_equal_to =>
117
- [
118
- {:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
119
- {:field_uRI_or_constant=>{:constant => {:value =>end_date}}}
120
- ]
121
- }
122
- ]
123
- }
124
- end
125
- end
126
-
127
- # Search on the item subject
128
- # @param [String] match_str A simple string paramater to match against the
129
- # subject. The search ignores case and does not accept regexes... only strings.
130
- # @param [String,nil] exclude_str A string to exclude from matches against
131
- # the subject. This is optional.
132
- def search_by_subject(match_str, exclude_str = nil)
133
- items do |obj|
134
- match = {:contains => {
135
- :containment_mode => 'Substring',
136
- :containment_comparison => 'IgnoreCase',
137
- :field_uRI => {:field_uRI=>'item:Subject'},
138
- :constant => {:value =>match_str}
139
- }}
140
- unless exclude_str.nil?
141
- excl = {:not =>
142
- {:contains => {
143
- :containment_mode => 'Substring',
144
- :containment_comparison => 'IgnoreCase',
145
- :field_uRI => {:field_uRI=>'item:Subject'},
146
- :constant => {:value =>exclude_str}
147
- }}
156
+ user_config_props: 'XmlData'
148
157
  }
149
-
150
- match[:and] = [{:contains => match.delete(:contains)}, excl]
158
+ ews.get_user_configuration(opts)
159
+ # txt = resp.response_message[:elems][:get_user_configuration_response_message][:elems][1]
160
+ # [:user_configuration][:elems][1][:xml_data][:text]
161
+ # Base64.decode64 txt
151
162
  end
152
- obj.restriction = match
153
- end
154
- end
155
163
 
156
- def get_all_properties!
157
- @ews_item = get_folder(:base_shape => 'AllProperties')
158
- simplify!
159
- end
160
-
161
- def available_categories
162
- opts = {
163
- user_config_name: {
164
- name: 'CategoryList',
165
- distinguished_folder_id: {id: :calendar}
166
- },
167
- user_config_props: 'XmlData'
168
- }
169
- resp = ews.get_user_configuration(opts)
170
- #txt = resp.response_message[:elems][:get_user_configuration_response_message][:elems][1][:user_configuration][:elems][1][:xml_data][:text]
171
- #Base64.decode64 txt
172
- end
164
+ # Syncronize Items in this folder. If this method is issued multiple
165
+ # times it will continue where the last sync completed.
166
+ # @param [Integer] sync_amount The number of items to synchronize per sync
167
+ # @param [Boolean] sync_all Whether to sync all the data by looping through.
168
+ # The default is to just sync the first set. You can manually loop through
169
+ # with multiple calls to #sync_items!
170
+ # @return [Hash] Returns a hash with keys for each change type that ocurred.
171
+ # Possible key values are:
172
+ # (:create/:udpate/:delete/:read_flag_change).
173
+ # For :deleted and :read_flag_change items a simple hash with :id and
174
+ # :change_key is returned.
175
+ # See: http://msdn.microsoft.com/en-us/library/aa565609.aspx
176
+ # rubocop:disable Style/OptionalBooleanParameter -- public API
177
+ def sync_items!(sync_state = nil, sync_amount = 256, _sync_all = false, opts = {})
178
+ item_shape = opts.key?(:item_shape) ? opts.delete(:item_shape) : { base_shape: :default }
179
+ sync_state ||= @sync_state
180
+
181
+ resp = ews.sync_folder_items item_shape: item_shape, sync_folder_id: folder_id,
182
+ max_changes_returned: sync_amount, sync_state: sync_state
183
+ rmsg = resp.response_messages[0]
173
184
 
174
- # Syncronize Items in this folder. If this method is issued multiple
175
- # times it will continue where the last sync completed.
176
- # @param [Integer] sync_amount The number of items to synchronize per sync
177
- # @param [Boolean] sync_all Whether to sync all the data by looping through.
178
- # The default is to just sync the first set. You can manually loop through
179
- # with multiple calls to #sync_items!
180
- # @return [Hash] Returns a hash with keys for each change type that ocurred.
181
- # Possible key values are:
182
- # (:create/:udpate/:delete/:read_flag_change).
183
- # For :deleted and :read_flag_change items a simple hash with :id and
184
- # :change_key is returned.
185
- # See: http://msdn.microsoft.com/en-us/library/aa565609.aspx
186
- def sync_items!(sync_state = nil, sync_amount = 256, sync_all = false, opts = {})
187
- item_shape = opts.has_key?(:item_shape) ? opts.delete(:item_shape) : {:base_shape => :default}
188
- sync_state ||= @sync_state
189
-
190
- resp = ews.sync_folder_items item_shape: item_shape,
191
- sync_folder_id: self.folder_id, max_changes_returned: sync_amount, sync_state: sync_state
192
- rmsg = resp.response_messages[0]
193
-
194
- if rmsg.success?
195
- @synced = rmsg.includes_last_item_in_range?
196
- @sync_state = rmsg.sync_state
197
- rhash = {}
198
- rmsg.changes.each do |c|
199
- ctype = c.keys.first
200
- rhash[ctype] = [] unless rhash.has_key?(ctype)
201
- if ctype == :delete || ctype == :read_flag_change
202
- rhash[ctype] << c[ctype][:elems][0][:item_id][:attribs]
203
- else
204
- type = c[ctype][:elems][0].keys.first
205
- item = class_by_name(type).new(ews, c[ctype][:elems][0][type])
206
- rhash[ctype] << item
185
+ raise EwsError, "Could not synchronize: #{rmsg.code}: #{rmsg.message_text}" unless rmsg.success?
186
+
187
+ @synced = rmsg.includes_last_item_in_range?
188
+ @sync_state = rmsg.sync_state
189
+ rhash = {}
190
+ rmsg.changes.each do |c|
191
+ ctype = c.keys.first
192
+ rhash[ctype] = [] unless rhash.key?(ctype)
193
+ if %i[delete read_flag_change].include?(ctype)
194
+ rhash[ctype] << c[ctype][:elems][0][:item_id][:attribs]
195
+ else
196
+ type = c[ctype][:elems][0].keys.first
197
+ item = class_by_name(type).new(ews, c[ctype][:elems][0][type])
198
+ rhash[ctype] << item
199
+ end
207
200
  end
201
+ rhash
208
202
  end
209
- rhash
210
- else
211
- raise EwsError, "Could not synchronize: #{rmsg.code}: #{rmsg.message_text}"
212
- end
213
- end
214
-
215
- def synced?
216
- @synced
217
- end
203
+ # rubocop:enable Style/OptionalBooleanParameter
218
204
 
219
- # Subscribe this folder to events. This method initiates an Exchange pull
220
- # type subscription.
221
- #
222
- # @param event_types [Array] Which event types to subscribe to. By default
223
- # we subscribe to all Exchange event types: :all, :copied, :created,
224
- # :deleted, :modified, :moved, :new_mail, :free_busy_changed
225
- # @param watermark [String] pass a watermark if you wish to start the
226
- # subscription at a specific point.
227
- # @param timeout [Fixnum] the time in minutes that the subscription can
228
- # remain idle between calls to #get_events. default: 240 minutes
229
- # @return [Boolean] Did the subscription happen successfully?
230
- def subscribe(evtypes = [:all], watermark = nil, timeout = 240)
231
- # Refresh the subscription if already subscribed
232
- unsubscribe if subscribed?
233
-
234
- event_types = normalize_event_names(evtypes)
235
- folder = {id: self.id, change_key: self.change_key}
236
- resp = ews.pull_subscribe_folder(folder, event_types, timeout, watermark)
237
- rmsg = resp.response_messages.first
238
- if rmsg.success?
239
- @subscription_id = rmsg.subscription_id
240
- @watermark = rmsg.watermark
241
- true
242
- else
243
- raise EwsSubscriptionError, "Could not subscribe: #{rmsg.code}: #{rmsg.message_text}"
244
- end
245
- end
205
+ def synced?
206
+ @synced
207
+ end
246
208
 
247
- def push_subscribe(url, evtypes = [:all], watermark = nil, status_frequency = nil)
248
-
249
- event_types = normalize_event_names(evtypes)
250
- folder = {id: self.id, change_key: self.change_key}
251
- resp = ews.push_subscribe_folder(folder, event_types, url, status_frequency, watermark)
252
- rmsg = resp.response_messages.first
253
- if rmsg.success?
254
- @subscription_id = rmsg.subscription_id
255
- @watermark = rmsg.watermark
256
- true
257
- else
258
- raise EwsSubscriptionError, "Could not subscribe: #{rmsg.code}: #{rmsg.message_text}"
259
- end
260
- end
209
+ # Subscribe this folder to events. This method initiates an Exchange pull
210
+ # type subscription.
211
+ #
212
+ # @param event_types [Array] Which event types to subscribe to. By default
213
+ # we subscribe to all Exchange event types: :all, :copied, :created,
214
+ # :deleted, :modified, :moved, :new_mail, :free_busy_changed
215
+ # @param watermark [String] pass a watermark if you wish to start the
216
+ # subscription at a specific point.
217
+ # @param timeout [Fixnum] the time in minutes that the subscription can
218
+ # remain idle between calls to #get_events. default: 240 minutes
219
+ # @return [Boolean] Did the subscription happen successfully?
220
+ def subscribe(evtypes = [:all], watermark = nil, timeout = 240)
221
+ # Refresh the subscription if already subscribed
222
+ unsubscribe if subscribed?
223
+
224
+ event_types = normalize_event_names(evtypes)
225
+ folder = { id: id, change_key: change_key }
226
+ resp = ews.pull_subscribe_folder(folder, event_types, timeout, watermark)
227
+ rmsg = resp.response_messages.first
228
+ raise EwsSubscriptionError, "Could not subscribe: #{rmsg.code}: #{rmsg.message_text}" unless rmsg.success?
229
+
230
+ @subscription_id = rmsg.subscription_id
231
+ @watermark = rmsg.watermark
232
+ true
233
+ end
261
234
 
262
- # Check if there is a subscription for this folder.
263
- # @return [Boolean] Are we subscribed to this folder?
264
- def subscribed?
265
- ( @subscription_id.nil? or @watermark.nil? )? false : true
266
- end
235
+ def push_subscribe(url, evtypes = [:all], watermark = nil, status_frequency = nil)
236
+ event_types = normalize_event_names(evtypes)
237
+ folder = { id: id, change_key: change_key }
238
+ resp = ews.push_subscribe_folder(folder, event_types, url, status_frequency, watermark)
239
+ rmsg = resp.response_messages.first
240
+ raise EwsSubscriptionError, "Could not subscribe: #{rmsg.code}: #{rmsg.message_text}" unless rmsg.success?
267
241
 
268
- # Unsubscribe this folder from further Exchange events.
269
- # @return [Boolean] Did we unsubscribe successfully?
270
- def unsubscribe
271
- return true if @subscription_id.nil?
272
-
273
- resp = ews.unsubscribe(@subscription_id)
274
- rmsg = resp.response_messages.first
275
- if rmsg.success?
276
- @subscription_id, @watermark = nil, nil
277
- true
278
- else
279
- raise EwsSubscriptionError, "Could not unsubscribe: #{rmsg.code}: #{rmsg.message_text}"
280
- end
281
- end
242
+ @subscription_id = rmsg.subscription_id
243
+ @watermark = rmsg.watermark
244
+ true
245
+ end
282
246
 
283
- # Checks a subscribed folder for events
284
- # @return [Array] An array of Event items
285
- def get_events
286
- begin
287
- if subscribed?
288
- resp = ews.get_events(@subscription_id, @watermark)
289
- rmsg = resp.response_messages[0]
290
- @watermark = rmsg.new_watermark
291
- # @todo if parms[:more_events] # get more events
292
- rmsg.events.collect{|ev|
293
- type = ev.keys.first
294
- class_by_name(type).new(ews, ev[type])
295
- }
296
- else
297
- raise EwsSubscriptionError, "Folder <#{self.display_name}> not subscribed to. Issue a Folder#subscribe before checking events."
247
+ # Check if there is a subscription for this folder.
248
+ # @return [Boolean] Are we subscribed to this folder?
249
+ def subscribed?
250
+ !@subscription_id.nil? && !@watermark.nil?
298
251
  end
299
- rescue EwsSubscriptionTimeout => e
300
- @subscription_id, @watermark = nil, nil
301
- raise e
302
- end
303
- end
304
252
 
253
+ # Unsubscribe this folder from further Exchange events.
254
+ # @return [Boolean] Did we unsubscribe successfully?
255
+ def unsubscribe
256
+ return true if @subscription_id.nil?
305
257
 
306
- private
258
+ resp = ews.unsubscribe(@subscription_id)
259
+ rmsg = resp.response_messages.first
260
+ raise EwsSubscriptionError, "Could not unsubscribe: #{rmsg.code}: #{rmsg.message_text}" unless rmsg.success?
307
261
 
262
+ @subscription_id = nil
263
+ @watermark = nil
264
+ true
265
+ end
308
266
 
309
- def key_paths
310
- @key_paths ||= super.merge(GFOLDER_KEY_PATHS)
311
- end
267
+ # Checks a subscribed folder for events
268
+ # @return [Array] An array of Event items
269
+ def get_events # rubocop:disable Naming/AccessorMethodName -- public API name
270
+ if subscribed?
271
+ resp = ews.get_events(@subscription_id, @watermark)
272
+ rmsg = resp.response_messages[0]
273
+ @watermark = rmsg.new_watermark
274
+ # @todo if parms[:more_events] # get more events
275
+ rmsg.events.collect { |ev|
276
+ type = ev.keys.first
277
+ class_by_name(type).new(ews, ev[type])
278
+ }
279
+ else
280
+ raise EwsSubscriptionError,
281
+ "Folder <#{display_name}> not subscribed to. Issue a Folder#subscribe before checking events."
282
+ end
283
+ rescue EwsSubscriptionTimeout => e
284
+ @subscription_id = nil
285
+ @watermark = nil
286
+ raise e
287
+ end
312
288
 
313
- def key_types
314
- @key_types ||= super.merge(GFOLDER_KEY_TYPES)
315
- end
289
+ private
316
290
 
317
- def key_alias
318
- @key_alias ||= super.merge(GFOLDER_KEY_ALIAS)
319
- end
291
+ def key_paths
292
+ @key_paths ||= super.merge(GFOLDER_KEY_PATHS)
293
+ end
320
294
 
321
- def simplify!
322
- @ews_item = @ews_item[:elems].inject({}) do |o,i|
323
- key = i.keys.first
324
- if o.has_key?(key)
325
- if o[key].is_a?(Array)
326
- o[key] << i[key]
327
- else
328
- o[key] = [o.delete(key), i[key]]
295
+ def key_types
296
+ @key_types ||= super.merge(GFOLDER_KEY_TYPES)
297
+ end
298
+
299
+ def key_alias
300
+ @key_alias ||= super.merge(GFOLDER_KEY_ALIAS)
301
+ end
302
+
303
+ def simplify!
304
+ @ews_item = @ews_item[:elems].each_with_object({}) do |i, o|
305
+ key = i.keys.first
306
+ if o.key?(key)
307
+ if o[key].is_a?(Array)
308
+ o[key] << i[key]
309
+ else
310
+ o[key] = [o.delete(key), i[key]]
311
+ end
312
+ else
313
+ o[key] = i[key]
314
+ end
329
315
  end
330
- else
331
- o[key] = i[key]
332
316
  end
333
- o
334
- end
335
- end
336
317
 
337
- # Get a specific folder by its ID.
338
- # @param [Hash] opts Misc options to control request
339
- # @option opts [String] :base_shape IdOnly/Default/AllProperties
340
- # @raise [EwsError] raised when the backend SOAP method returns an error.
341
- def get_folder(opts = {})
342
- args = get_folder_args(opts)
343
- resp = ews.get_folder(args)
344
- get_folder_parser(resp)
345
- end
318
+ # Get a specific folder by its ID.
319
+ # @param [Hash] opts Misc options to control request
320
+ # @option opts [String] :base_shape IdOnly/Default/AllProperties
321
+ # @raise [EwsError] raised when the backend SOAP method returns an error.
322
+ def get_folder(opts = {})
323
+ args = get_folder_args(opts)
324
+ resp = ews.get_folder(args)
325
+ get_folder_parser(resp)
326
+ end
346
327
 
347
- # Build up the arguements for #get_folder
348
- # @todo: should we really pass the ChangeKey or do we want the freshest obj?
349
- def get_folder_args(opts)
350
- opts[:base_shape] ||= 'Default'
351
- default_args = {
352
- :folder_ids => [{:id => self.id, :change_key => self.change_key}],
353
- :folder_shape => {:base_shape => opts[:base_shape]}
354
- }
355
- default_args.merge(opts)
356
- end
328
+ # Build up the arguements for #get_folder
329
+ # @todo: should we really pass the ChangeKey or do we want the freshest obj?
330
+ def get_folder_args(opts)
331
+ opts[:base_shape] ||= 'Default'
332
+ default_args = {
333
+ folder_ids: [{ id: id, change_key: change_key }],
334
+ folder_shape: { base_shape: opts[:base_shape] }
335
+ }
336
+ default_args.merge(opts)
337
+ end
357
338
 
358
- def get_folder_parser(resp)
359
- if(resp.status == 'Success')
360
- f = resp.response_message[:elems][:folders][:elems][0]
361
- f.values.first
362
- else
363
- raise EwsError, "Could not retrieve folder. #{resp.code}: #{resp.message}"
364
- end
365
- end
339
+ def get_folder_parser(resp)
340
+ raise EwsError, "Could not retrieve folder. #{resp.code}: #{resp.message}" unless resp.status == 'Success'
366
341
 
367
- def items_args(opts)
368
- default_args = {
369
- :parent_folder_ids => [{:id => self.id}],
370
- :traversal => 'Shallow',
371
- :item_shape => {:base_shape => 'Default'}
372
- }.merge(opts)
373
- end
342
+ f = resp.response_message[:elems][:folders][:elems][0]
343
+ f.values.first
344
+ end
374
345
 
375
- def items_parser(resp)
376
- rm = resp.response_messages[0]
377
- if(rm.status == 'Success')
378
- items = []
379
- rm.root_folder.items.each do |i|
380
- type = i.keys.first
381
- items << class_by_name(type).new(ews, i[type], self)
346
+ def items_args(opts)
347
+ {
348
+ parent_folder_ids: [{ id: id }],
349
+ traversal: 'Shallow',
350
+ item_shape: { base_shape: 'Default' }
351
+ }.merge(opts)
382
352
  end
383
- items
384
- else
385
- raise EwsError, "Could not retrieve folder. #{rm.code}: #{rm.message_text}"
386
- end
387
- end
388
353
 
389
- def merge_restrictions!(obj, merge_type = :and)
390
- if obj.opts[:restriction] && !obj.opts[:restriction].empty? && !obj.restriction.empty?
391
- obj.opts[:restriction] = {
392
- merge_type => [
393
- obj.opts.delete(:restriction),
394
- obj.restriction
395
- ]
396
- }
397
- elsif !obj.restriction.empty?
398
- obj.opts[:restriction] = obj.restriction
399
- end
400
- end
354
+ def items_parser(resp)
355
+ rm = resp.response_messages[0]
356
+ raise EwsError, "Could not retrieve folder. #{rm.code}: #{rm.message_text}" unless rm.status == 'Success'
401
357
 
402
- def normalize_event_names(events)
403
- if events.include?(:all)
404
- events = [:copied, :created, :deleted, :modified, :moved, :new_mail, :free_busy_changed]
405
- end
358
+ items = []
359
+ rm.root_folder.items.each do |i|
360
+ type = i.keys.first
361
+ items << class_by_name(type).new(ews, i[type], self)
362
+ end
363
+ items
364
+ end
406
365
 
407
- events.collect do |ev|
408
- nev = ruby_case(ev)
409
- if nev.end_with?('_event')
410
- nev.to_sym
411
- else
412
- "#{nev}_event".to_sym
366
+ def merge_restrictions!(obj, merge_type = :and)
367
+ if obj.opts[:restriction] && !obj.opts[:restriction].empty? && !obj.restriction.empty?
368
+ obj.opts[:restriction] = {
369
+ merge_type => [
370
+ obj.opts.delete(:restriction),
371
+ obj.restriction
372
+ ]
373
+ }
374
+ elsif !obj.restriction.empty?
375
+ obj.opts[:restriction] = obj.restriction
376
+ end
377
+ end
378
+
379
+ def normalize_event_names(events)
380
+ events = %i[copied created deleted modified moved new_mail free_busy_changed] if events.include?(:all)
381
+
382
+ events.collect do |ev|
383
+ nev = ruby_case(ev)
384
+ if nev.end_with?('_event')
385
+ nev.to_sym
386
+ else
387
+ "#{nev}_event".to_sym
388
+ end
389
+ end
413
390
  end
414
391
  end
415
392
  end
416
-
417
393
  end
418
394
  end