viewpoint 1.0.0.beta.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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])