viewpoint 1.0.0.beta.1 → 1.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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +16 -11
  3. data/lib/ews/connection.rb +15 -6
  4. data/lib/ews/convert_accessors.rb +56 -0
  5. data/lib/ews/errors.rb +56 -0
  6. data/lib/ews/ews_client.rb +33 -3
  7. data/lib/ews/exceptions/exceptions.rb +2 -0
  8. data/lib/ews/folder_accessors.rb +66 -1
  9. data/lib/ews/impersonation.rb +30 -0
  10. data/lib/ews/item_accessors.rb +77 -3
  11. data/lib/ews/mailbox_accessors.rb +6 -1
  12. data/lib/ews/message_accessors.rb +9 -2
  13. data/lib/ews/room_accessors.rb +48 -0
  14. data/lib/ews/roomlist_accessors.rb +47 -0
  15. data/lib/ews/soap.rb +1 -0
  16. data/lib/ews/soap/builders/ews_builder.rb +303 -48
  17. data/lib/ews/soap/ews_response.rb +2 -1
  18. data/lib/ews/soap/ews_soap_free_busy_response.rb +13 -3
  19. data/lib/ews/soap/ews_soap_response.rb +1 -1
  20. data/lib/ews/soap/ews_soap_room_response.rb +53 -0
  21. data/lib/ews/soap/ews_soap_roomlist_response.rb +54 -0
  22. data/lib/ews/soap/exchange_data_services.rb +49 -11
  23. data/lib/ews/soap/exchange_synchronization.rb +93 -0
  24. data/lib/ews/soap/exchange_time_zones.rb +56 -0
  25. data/lib/ews/soap/exchange_web_service.rb +36 -66
  26. data/lib/ews/soap/parsers/ews_sax_document.rb +8 -4
  27. data/lib/ews/soap/response_message.rb +3 -1
  28. data/lib/ews/soap/responses/create_attachment_response_message.rb +2 -1
  29. data/lib/ews/soap/responses/send_notification_response_message.rb +2 -1
  30. data/lib/{extensions/string.rb → ews/soap/responses/sync_folder_hierarchy_response_message.rb} +18 -17
  31. data/lib/ews/soap/responses/sync_folder_items_response_message.rb +36 -0
  32. data/lib/ews/templates/calendar_item.rb +78 -0
  33. data/lib/ews/templates/message.rb +8 -1
  34. data/lib/ews/templates/task.rb +74 -0
  35. data/lib/ews/types.rb +59 -11
  36. data/lib/ews/types/calendar_folder.rb +42 -0
  37. data/lib/ews/types/calendar_item.rb +93 -1
  38. data/lib/ews/types/export_items_response_message.rb +52 -0
  39. data/lib/ews/types/generic_folder.rb +70 -2
  40. data/lib/ews/types/item.rb +64 -4
  41. data/lib/ews/types/item_attachment.rb +41 -3
  42. data/lib/ews/types/item_field_uri_map.rb +1 -1
  43. data/lib/ews/types/task.rb +30 -0
  44. data/lib/ews/types/tasks_folder.rb +19 -0
  45. data/lib/viewpoint.rb +14 -14
  46. data/lib/viewpoint/logging.rb +27 -0
  47. data/lib/viewpoint/logging/config.rb +24 -0
  48. data/lib/viewpoint/string_utils.rb +76 -0
  49. metadata +82 -76
@@ -4,5 +4,47 @@ module Viewpoint::EWS::Types
4
4
  include Viewpoint::EWS::Types
5
5
  include Viewpoint::EWS::Types::GenericFolder
6
6
 
7
+ # Fetch items between a given time period
8
+ # @param [DateTime] start_date the time to start fetching Items from
9
+ # @param [DateTime] end_date the time to stop fetching Items from
10
+ def items_between(start_date, end_date, opts={})
11
+ items do |obj|
12
+ obj.restriction = { :and =>
13
+ [
14
+ {:is_greater_than_or_equal_to =>
15
+ [
16
+ {:field_uRI => {:field_uRI=>'calendar:Start'}},
17
+ {:field_uRI_or_constant=>{:constant => {:value =>start_date}}}
18
+ ]
19
+ },
20
+ {:is_less_than_or_equal_to =>
21
+ [
22
+ {:field_uRI => {:field_uRI=>'calendar:End'}},
23
+ {:field_uRI_or_constant=>{:constant => {:value =>end_date}}}
24
+ ]
25
+ }
26
+ ]
27
+ }
28
+ end
29
+ end
30
+
31
+ # Creates a new appointment
32
+ # @param attributes [Hash] Parameters of the calendar item. Some example attributes are listed below.
33
+ # @option attributes :subject [String]
34
+ # @option attributes :start [Time]
35
+ # @option attributes :end [Time]
36
+ # @return [CalendarItem]
37
+ # @see Template::CalendarItem
38
+ def create_item(attributes)
39
+ template = Viewpoint::EWS::Template::CalendarItem.new attributes
40
+ template.saved_item_folder_id = {id: self.id, change_key: self.change_key}
41
+ rm = ews.create_item(template.to_ews_create).response_messages.first
42
+ if rm && rm.success?
43
+ CalendarItem.new ews, rm.items.first[:calendar_item][:elems].first
44
+ else
45
+ raise EwsCreateItemError, "Could not create item in folder. #{rm.code}: #{rm.message_text}" unless rm
46
+ end
47
+ end
48
+
7
49
  end
8
50
  end
@@ -3,23 +3,115 @@ module Viewpoint::EWS::Types
3
3
  include Viewpoint::EWS
4
4
  include Viewpoint::EWS::Types
5
5
  include Viewpoint::EWS::Types::Item
6
+ include Viewpoint::StringUtils
6
7
 
7
8
  CALENDAR_ITEM_KEY_PATHS = {
8
9
  recurring?: [:is_recurring, :text],
9
10
  meeting?: [:is_meeting, :text],
10
11
  cancelled?: [:is_cancelled, :text],
11
- }
12
+ duration: [:duration, :text],
13
+ time_zone: [:time_zone, :text],
14
+ start: [:start, :text],
15
+ end: [:end, :text],
16
+ location: [:location, :text],
17
+ all_day?: [:is_all_day_event, :text],
18
+ my_response_type: [:my_response_type, :text],
19
+ organizer: [:organizer, :elems, 0, :mailbox, :elems],
20
+ optional_attendees: [:optional_attendees, :elems ],
21
+ required_attendees: [:required_attendees, :elems ],
22
+ recurrence: [:recurrence, :elems ],
23
+ deleted_occurrences: [:deleted_occurrences, :elems ],
24
+ modified_occurrences: [:modified_occurrences, :elems ]
25
+ }
12
26
 
13
27
  CALENDAR_ITEM_KEY_TYPES = {
28
+ start: ->(str){DateTime.parse(str)},
29
+ end: ->(str){DateTime.parse(str)},
14
30
  recurring?: ->(str){str.downcase == 'true'},
15
31
  meeting?: ->(str){str.downcase == 'true'},
16
32
  cancelled?: ->(str){str.downcase == 'true'},
33
+ all_day?: ->(str){str.downcase == 'true'},
34
+ organizer: :build_mailbox_user,
35
+ optional_attendees: :build_attendees_users,
36
+ required_attendees: :build_attendees_users,
37
+ deleted_occurrences: :build_deleted_occurrences,
38
+ modified_occurrences: :build_modified_occurrences
17
39
  }
18
40
  CALENDAR_ITEM_KEY_ALIAS = {}
19
41
 
42
+ # Updates the specified item attributes
43
+ #
44
+ # Uses `SetItemField` if value is present and `DeleteItemField` if value is nil
45
+ # @param updates [Hash] with (:attribute => value)
46
+ # @param options [Hash]
47
+ # @option options :conflict_resolution [String] one of 'NeverOverwrite', 'AutoResolve' (default) or 'AlwaysOverwrite'
48
+ # @option options :send_meeting_invitations_or_cancellations [String] one of 'SendToNone' (default), 'SendOnlyToAll',
49
+ # 'SendOnlyToChanged', 'SendToAllAndSaveCopy' or 'SendToChangedAndSaveCopy'
50
+ # @return [CalendarItem, false]
51
+ # @example Update Subject and Body
52
+ # item = #...
53
+ # item.update_item!(subject: 'New subject', body: 'New Body')
54
+ # @see http://msdn.microsoft.com/en-us/library/exchange/aa580254.aspx
55
+ # @todo AppendToItemField updates not implemented
56
+ def update_item!(updates, options = {})
57
+ item_updates = []
58
+ updates.each do |attribute, value|
59
+ item_field = FIELD_URIS[attribute][:text] if FIELD_URIS.include? attribute
60
+ field = {field_uRI: {field_uRI: item_field}}
61
+
62
+ if value.nil? && item_field
63
+ # Build DeleteItemField Change
64
+ item_updates << {delete_item_field: field}
65
+ elsif item_field
66
+ # Build SetItemField Change
67
+ item = Viewpoint::EWS::Template::CalendarItem.new(attribute => value)
68
+
69
+ # Remap attributes because ews_builder #dispatch_field_item! uses #build_xml!
70
+ item_attributes = item.to_ews_item.map do |name, value|
71
+ if value.is_a? String
72
+ {name => {text: value}}
73
+ elsif value.is_a? Hash
74
+ node = {name => {}}
75
+ value.each do |attrib_key, attrib_value|
76
+ attrib_key = camel_case(attrib_key) unless attrib_key == :text
77
+ node[name][attrib_key] = attrib_value
78
+ end
79
+ node
80
+ else
81
+ {name => value}
82
+ end
83
+ end
84
+
85
+ item_updates << {set_item_field: field.merge(calendar_item: {sub_elements: item_attributes})}
86
+ else
87
+ # Ignore unknown attribute
88
+ end
89
+ end
90
+
91
+ if item_updates.any?
92
+ data = {}
93
+ data[:conflict_resolution] = options[:conflict_resolution] || 'AutoResolve'
94
+ data[:send_meeting_invitations_or_cancellations] = options[:send_meeting_invitations_or_cancellations] || 'SendToNone'
95
+ data[:item_changes] = [{item_id: self.item_id, updates: item_updates}]
96
+ rm = ews.update_item(data).response_messages.first
97
+ if rm && rm.success?
98
+ self.get_all_properties!
99
+ self
100
+ else
101
+ raise EwsCreateItemError, "Could not update calendar item. #{rm.code}: #{rm.message_text}" unless rm
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ def duration_in_seconds
108
+ iso8601_duration_to_seconds(duration)
109
+ end
110
+
20
111
 
21
112
  private
22
113
 
114
+
23
115
  def key_paths
24
116
  super.merge(CALENDAR_ITEM_KEY_PATHS)
25
117
  end
@@ -0,0 +1,52 @@
1
+ module Viewpoint::EWS::Types
2
+
3
+ class ExportItemsResponseMessage
4
+ include Viewpoint::EWS
5
+ include Viewpoint::EWS::Types
6
+ include Viewpoint::EWS::Types::Item
7
+
8
+ BULK_KEY_PATHS = {
9
+ :id => [:item_id, :attribs, :id],
10
+ :change_key => [:item_id, :attribs, :change_key],
11
+ :data => [:data, :text]
12
+ }
13
+
14
+ BULK_KEY_TYPES = { }
15
+
16
+ BULK_KEY_ALIAS = { }
17
+
18
+ def initialize(ews, bulk_item)
19
+ super(ews, bulk_item)
20
+ @item = bulk_item
21
+ @ews = ews
22
+ end
23
+
24
+ def id
25
+ @item[:item_id][:attribs][:id]
26
+ end
27
+
28
+ def change_key
29
+ @item[:item_id][:attribs][:change_key]
30
+ end
31
+
32
+ def data
33
+ @item[:data][:text]
34
+ end
35
+
36
+
37
+ private
38
+
39
+ def key_paths
40
+ @key_paths ||= BULK_KEY_PATHS
41
+ end
42
+
43
+ def key_types
44
+ @key_types ||= BULK_KEY_TYPES
45
+ end
46
+
47
+ def key_alias
48
+ @key_alias ||= BULK_KEY_ALIAS
49
+ end
50
+
51
+ end
52
+ end
@@ -1,11 +1,32 @@
1
+ =begin
2
+ This file is part of Viewpoint; the Ruby library for Microsoft Exchange Web Services.
3
+
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
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
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
18
+
1
19
  require 'ews/item_accessors'
20
+
2
21
  module Viewpoint::EWS::Types
3
22
  module GenericFolder
4
23
  include Viewpoint::EWS
5
24
  include Viewpoint::EWS::Types
6
25
  include Viewpoint::EWS::ItemAccessors
26
+ include Viewpoint::StringUtils
7
27
 
8
28
  GFOLDER_KEY_PATHS = {
29
+ :folder_id => [:folder_id, :attribs],
9
30
  :id => [:folder_id, :attribs, :id],
10
31
  :change_key => [:folder_id, :attribs, :change_key],
11
32
  :parent_folder_id => [:parent_folder_id, :attribs, :id],
@@ -26,13 +47,15 @@ module Viewpoint::EWS::Types
26
47
  :ckey => :change_key,
27
48
  }
28
49
 
29
- attr_accessor :subscription_id, :watermark
50
+ attr_accessor :subscription_id, :watermark, :sync_state
30
51
 
31
52
  # @param [SOAP::ExchangeWebService] ews the EWS reference
32
53
  # @param [Hash] ews_item the EWS parsed response document
33
54
  def initialize(ews, ews_item)
34
55
  super
35
56
  simplify!
57
+ @sync_state = nil
58
+ @synced = false
36
59
  end
37
60
 
38
61
  def delete!
@@ -148,6 +171,51 @@ module Viewpoint::EWS::Types
148
171
  #Base64.decode64 txt
149
172
  end
150
173
 
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
207
+ end
208
+ 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
218
+
151
219
  # Subscribe this folder to events. This method initiates an Exchange pull
152
220
  # type subscription.
153
221
  #
@@ -339,7 +407,7 @@ module Viewpoint::EWS::Types
339
407
  end
340
408
 
341
409
  events.collect do |ev|
342
- nev = ev.to_s.ruby_case
410
+ nev = ruby_case(ev)
343
411
  if nev.end_with?('_event')
344
412
  nev.to_sym
345
413
  else
@@ -4,7 +4,19 @@ module Viewpoint::EWS::Types
4
4
  include Viewpoint::EWS::Types
5
5
  include ItemFieldUriMap
6
6
 
7
+ def self.included(klass)
8
+ klass.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def init_simple_item(ews, id, change_key = nil, parent = nil)
13
+ ews_item = {item_id: {attribs: {id: id, change_key: change_key}}}
14
+ self.new ews, ews_item, parent
15
+ end
16
+ end
17
+
7
18
  ITEM_KEY_PATHS = {
19
+ item_id: [:item_id, :attribs],
8
20
  id: [:item_id, :attribs, :id],
9
21
  change_key: [:item_id, :attribs, :change_key],
10
22
  subject: [:subject, :text],
@@ -12,6 +24,7 @@ module Viewpoint::EWS::Types
12
24
  size: [:size, :text],
13
25
  date_time_sent: [:date_time_sent, :text],
14
26
  date_time_created: [:date_time_created, :text],
27
+ mime_content: [:mime_content, :text],
15
28
  has_attachments?:[:has_attachments, :text],
16
29
  is_associated?: [:is_associated, :text],
17
30
  is_read?: [:is_read, :text],
@@ -24,6 +37,7 @@ module Viewpoint::EWS::Types
24
37
  sender: [:sender, :elems, 0, :mailbox, :elems],
25
38
  from: [:from, :elems, 0, :mailbox, :elems],
26
39
  to_recipients: [:to_recipients, :elems],
40
+ cc_recipients: [:cc_recipients, :elems],
27
41
  attachments: [:attachments, :elems],
28
42
  importance: [:importance, :text],
29
43
  conversation_index: [:conversation_index, :text],
@@ -47,6 +61,8 @@ module Viewpoint::EWS::Types
47
61
  h[:internet_message_header][:text]} } },
48
62
  sender: :build_mailbox_user,
49
63
  from: :build_mailbox_user,
64
+ to_recipients: :build_mailbox_users,
65
+ cc_recipients: :build_mailbox_users,
50
66
  attachments: :build_attachments,
51
67
  }
52
68
 
@@ -69,6 +85,7 @@ module Viewpoint::EWS::Types
69
85
  simplify!
70
86
  @new_file_attachments = []
71
87
  @new_item_attachments = []
88
+ @new_inline_attachments = []
72
89
  end
73
90
 
74
91
  # Specify a body_type to fetch this item with if it hasn't already been fetched.
@@ -78,11 +95,12 @@ module Viewpoint::EWS::Types
78
95
  @body_type = body_type
79
96
  end
80
97
 
81
- def delete!(deltype = :hard)
98
+ def delete!(deltype = :hard, opts = {})
82
99
  opts = {
83
100
  :delete_type => delete_type(deltype),
84
101
  :item_ids => [{:item_id => {:id => id}}]
85
- }
102
+ }.merge(opts)
103
+
86
104
  resp = @ews.delete_item(opts)
87
105
  rmsg = resp.response_messages[0]
88
106
  unless rmsg.success?
@@ -164,6 +182,13 @@ module Viewpoint::EWS::Types
164
182
  @new_item_attachments << ia
165
183
  end
166
184
 
185
+ def add_inline_attachment(file)
186
+ fi = OpenStruct.new
187
+ fi.name = File.basename(file.path)
188
+ fi.content = Base64.encode64(file.read)
189
+ @new_inline_attachments << fi
190
+ end
191
+
167
192
  def submit!
168
193
  if draft?
169
194
  submit_attachments!
@@ -180,17 +205,19 @@ module Viewpoint::EWS::Types
180
205
  end
181
206
 
182
207
  def submit_attachments!
183
- return false unless draft? && !(@new_file_attachments.empty? && @new_item_attachments.empty?)
208
+ return false unless draft? && !(@new_file_attachments.empty? && @new_item_attachments.empty? && @new_inline_attachments.empty?)
184
209
 
185
210
  opts = {
186
211
  parent_id: {id: self.id, change_key: self.change_key},
187
212
  files: @new_file_attachments,
188
- items: @new_item_attachments
213
+ items: @new_item_attachments,
214
+ inline_files: @new_inline_attachments
189
215
  }
190
216
  resp = ews.create_attachment(opts)
191
217
  set_change_key resp.response_messages[0].attachments[0].parent_change_key
192
218
  @new_file_attachments = []
193
219
  @new_item_attachments = []
220
+ @new_inline_attachments = []
194
221
  end
195
222
 
196
223
  # If you want to add to the body set #new_body_content. If you set #body
@@ -261,6 +288,7 @@ module Viewpoint::EWS::Types
261
288
  end
262
289
 
263
290
  def simplify!
291
+ return unless @ews_item.has_key?(:elems)
264
292
  @ews_item = @ews_item[:elems].inject({}) do |o,i|
265
293
  key = i.keys.first
266
294
  if o.has_key?(key)
@@ -318,11 +346,43 @@ module Viewpoint::EWS::Types
318
346
  end
319
347
  end
320
348
 
349
+ def build_deleted_occurrences(occurrences)
350
+ occurrences.collect{|a| DateTime.parse a[:deleted_occurrence][:elems][0][:start][:text]}
351
+ end
352
+
353
+ def build_modified_occurrences(occurrences)
354
+ {}.tap do |h|
355
+ occurrences.collect do |a|
356
+ elems = a[:occurrence][:elems]
357
+
358
+ h[DateTime.parse(elems.find{|e| e[:original_start]}[:original_start][:text])] = {
359
+ start: elems.find{|e| e[:start]}[:start][:text],
360
+ end: elems.find{|e| e[:end]}[:end][:text]
361
+ }
362
+ end
363
+ end
364
+ end
365
+
321
366
  def build_mailbox_user(mbox_ews)
322
367
  MailboxUser.new(ews, mbox_ews)
323
368
  end
324
369
 
370
+ def build_mailbox_users(users)
371
+ return [] if users.nil?
372
+ users.collect{|u| build_mailbox_user(u[:mailbox][:elems])}
373
+ end
374
+
375
+ def build_attendees_users(users)
376
+ return [] if users.nil?
377
+ users.collect do |u|
378
+ u[:attendee][:elems].collect do |a|
379
+ build_mailbox_user(a[:mailbox][:elems]) if a[:mailbox]
380
+ end
381
+ end.flatten.compact
382
+ end
383
+
325
384
  def build_attachments(attachments)
385
+ return [] if attachments.nil?
326
386
  attachments.collect do |att|
327
387
  key = att.keys.first
328
388
  class_by_name(key).new(self, att[key])