viewpoint 0.1.27 → 1.0.0.beta.1

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 (93) hide show
  1. data/README.md +196 -0
  2. data/lib/ews/calendar_accessors.rb +34 -0
  3. data/lib/ews/connection.rb +117 -0
  4. data/lib/ews/connection_helper.rb +35 -0
  5. data/lib/ews/ews_client.rb +71 -0
  6. data/lib/ews/exceptions/exceptions.rb +59 -0
  7. data/lib/ews/folder_accessors.rb +199 -0
  8. data/lib/ews/item_accessors.rb +157 -0
  9. data/lib/ews/mailbox_accessors.rb +87 -0
  10. data/lib/ews/message_accessors.rb +86 -0
  11. data/lib/ews/push_subscription_accessors.rb +33 -0
  12. data/lib/ews/soap.rb +63 -0
  13. data/lib/ews/soap/builders/ews_builder.rb +1011 -0
  14. data/lib/ews/soap/ews_response.rb +83 -0
  15. data/lib/ews/soap/ews_soap_availability_response.rb +58 -0
  16. data/lib/ews/soap/ews_soap_free_busy_response.rb +109 -0
  17. data/lib/ews/soap/ews_soap_response.rb +103 -0
  18. data/lib/ews/soap/exchange_availability.rb +61 -0
  19. data/lib/ews/soap/exchange_data_services.rb +742 -0
  20. data/lib/ews/soap/exchange_notification.rb +146 -0
  21. data/lib/ews/soap/exchange_user_configuration.rb +33 -0
  22. data/lib/ews/soap/exchange_web_service.rb +294 -0
  23. data/lib/{model/attendee.rb → ews/soap/parsers/ews_parser.rb} +20 -14
  24. data/lib/ews/soap/parsers/ews_sax_document.rb +66 -0
  25. data/lib/ews/soap/response_message.rb +78 -0
  26. data/lib/ews/soap/responses/create_attachment_response_message.rb +46 -0
  27. data/lib/{model/meeting_message.rb → ews/soap/responses/create_item_response_message.rb} +7 -10
  28. data/lib/ews/soap/responses/find_item_response_message.rb +80 -0
  29. data/lib/ews/soap/responses/get_events_response_message.rb +53 -0
  30. data/lib/ews/soap/responses/send_notification_response_message.rb +58 -0
  31. data/lib/{model/attachment.rb → ews/soap/responses/subscribe_response_message.rb} +17 -13
  32. data/lib/ews/templates/forward_item.rb +24 -0
  33. data/lib/ews/templates/message.rb +66 -0
  34. data/lib/ews/templates/reply_to_item.rb +25 -0
  35. data/lib/ews/types.rb +146 -0
  36. data/lib/ews/types/attachment.rb +77 -0
  37. data/lib/{model/meeting_cancellation.rb → ews/types/attendee.rb} +9 -8
  38. data/lib/ews/types/calendar_folder.rb +8 -0
  39. data/lib/ews/types/calendar_item.rb +37 -0
  40. data/lib/ews/types/contact.rb +7 -0
  41. data/lib/ews/types/contacts_folder.rb +8 -0
  42. data/lib/ews/types/copied_event.rb +51 -0
  43. data/lib/{soap/handsoap/builder.rb → ews/types/created_event.rb} +5 -2
  44. data/lib/{model/meeting_response.rb → ews/types/deleted_event.rb} +6 -6
  45. data/lib/ews/types/distribution_list.rb +7 -0
  46. data/lib/ews/types/event.rb +62 -0
  47. data/lib/ews/types/file_attachment.rb +65 -0
  48. data/lib/ews/types/folder.rb +60 -0
  49. data/lib/{model/distribution_list.rb → ews/types/free_busy_changed_event.rb} +6 -6
  50. data/lib/ews/types/generic_folder.rb +352 -0
  51. data/lib/ews/types/item.rb +381 -0
  52. data/lib/ews/types/item_attachment.rb +46 -0
  53. data/lib/{model → ews/types}/item_field_uri_map.rb +2 -2
  54. data/lib/ews/types/mailbox_user.rb +156 -0
  55. data/lib/ews/types/meeting_cancellation.rb +7 -0
  56. data/lib/ews/types/meeting_message.rb +7 -0
  57. data/lib/ews/types/meeting_request.rb +7 -0
  58. data/lib/ews/types/meeting_response.rb +7 -0
  59. data/lib/ews/types/message.rb +7 -0
  60. data/lib/ews/types/modified_event.rb +48 -0
  61. data/lib/{model/item_attachment.rb → ews/types/moved_event.rb} +33 -15
  62. data/lib/ews/types/new_mail_event.rb +24 -0
  63. data/lib/ews/types/out_of_office.rb +147 -0
  64. data/lib/ews/types/search_folder.rb +8 -0
  65. data/lib/ews/types/status_event.rb +39 -0
  66. data/lib/ews/types/task.rb +7 -0
  67. data/lib/ews/types/tasks_folder.rb +8 -0
  68. data/lib/viewpoint.rb +82 -106
  69. metadata +99 -67
  70. data/README +0 -114
  71. data/lib/exceptions/exceptions.rb +0 -46
  72. data/lib/model/calendar_folder.rb +0 -67
  73. data/lib/model/calendar_item.rb +0 -267
  74. data/lib/model/contact.rb +0 -238
  75. data/lib/model/contacts_folder.rb +0 -46
  76. data/lib/model/event.rb +0 -116
  77. data/lib/model/file_attachment.rb +0 -53
  78. data/lib/model/folder.rb +0 -47
  79. data/lib/model/generic_folder.rb +0 -471
  80. data/lib/model/item.rb +0 -313
  81. data/lib/model/mailbox_user.rb +0 -146
  82. data/lib/model/meeting_request.rb +0 -39
  83. data/lib/model/message.rb +0 -142
  84. data/lib/model/model.rb +0 -269
  85. data/lib/model/search_folder.rb +0 -48
  86. data/lib/model/task.rb +0 -121
  87. data/lib/model/tasks_folder.rb +0 -44
  88. data/lib/soap/handsoap/builders/ews_build_helpers.rb +0 -383
  89. data/lib/soap/handsoap/builders/ews_builder.rb +0 -146
  90. data/lib/soap/handsoap/ews_service.rb +0 -813
  91. data/lib/soap/handsoap/parser.rb +0 -104
  92. data/lib/soap/handsoap/parsers/ews_parser.rb +0 -246
  93. data/lib/soap/soap_provider.rb +0 -64
@@ -1,46 +0,0 @@
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
-
19
- module Viewpoint
20
- module EWS
21
- class ContactsFolder < GenericFolder
22
-
23
- # Find folders of type Contact
24
- # @see GenericFolder.find_folders
25
- # @param [String,Symbol] root An folder id, either a DistinguishedFolderId (must me a Symbol)
26
- # or a FolderId (String)
27
- # @param [String] traversal Shallow/Deep/SoftDeleted
28
- # @param [String] shape the shape to return IdOnly/Default/AllProperties
29
- # @param [optional, String] folder_type an optional folder type to limit the search to like 'IPF.Task'
30
- # @return [Array] Returns an Array of Folder or subclasses of Folder
31
- def self.find_folders(root = :msgfolderroot, traversal = 'Deep', shape = 'Default', folder_type = 'IPF.Contact')
32
- super(root, traversal, shape, folder_type)
33
- end
34
-
35
-
36
- # initialize with an item of CalendarFolderType
37
- def initialize(folder)
38
- super(folder)
39
-
40
- # @todo Handle:
41
- # <SharingEffectiveRights/>
42
- end
43
-
44
- end # ContactsFolder
45
- end # EWS
46
- end # Viewpoint
@@ -1,116 +0,0 @@
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
-
19
- module Viewpoint
20
- module EWS
21
- class Event < Item
22
-
23
- attr_reader :subject, :organizer, :location, :start, :end, :cal_item_type
24
-
25
- # Initialize an Exchange Web Services item
26
- def initialize(ews_item, parent_folder, opts={})
27
- raise InvalidEWSItemError if ews_item.nil?
28
-
29
- @subject = ews_item.subject # String
30
- @location = ews_item.location if ews_item.location
31
- @cal_item_type = ews_item.calendarItemType
32
- @organizer = ews_item.organizer.mailbox.name if ews_item.organizer and ews_item.organizer.mailbox # String
33
- @start = ews_item.start if ews_item.start # DateTime
34
- @end = ews_item.v_end if ews_item.v_end # DateTime
35
- @busy_status = ews_item.legacyFreeBusyStatus if ews_item.legacyFreeBusyStatus # String
36
- @date_time_recieved = ews_item.dateTimeReceived if ews_item.dateTimeReceived # DateTime
37
-
38
- # This is where the event object gets loaded if it
39
- # is requested. Think of it like IMAP downloading the
40
- # body when the message is viewed.
41
- @message = nil
42
-
43
- super(ews_item, opts)
44
- end
45
-
46
-
47
-
48
- def body
49
- get_calitem if @message == nil
50
- @message.body
51
- end
52
-
53
- def sender
54
- @organizer
55
- end
56
-
57
-
58
- # Convert item to iCal format: http://www.ietf.org/rfc/rfc2445.txt
59
- # Returns Icalendar::Event object
60
- def to_ical_event
61
- get_calitem if @message == nil
62
- iev = Icalendar::Event.new
63
- iev.uid = @message.uID
64
- unless(@message.organizer == nil)
65
- iev.organizer = "CN=\"#{@message.organizer.mailbox.name}\"" if @message.organizer.mailbox.name
66
- iev.organizer += ":MAILTO:#{@message.organizer.mailbox.emailAddress}" if @message.organizer.mailbox.emailAddress
67
- end
68
- # TODO: Handle EWS Timezones better. TZ_HASH in viewpoint.rb is the start of this
69
- require 'time'
70
- #tzoffset = @message.timeZone.sub(/^\(GMT([^\)]+)\).*$/,'\1')
71
- dtstart = Time.at(@message.start.strftime('%s').to_i)
72
- dtend = Time.at(@message.v_end.strftime('%s').to_i)
73
- dtstamp = Time.at(@message.dateTimeStamp.strftime('%s').to_i)
74
- last_modified = Time.at(@message.lastModifiedTime.strftime('%s').to_i)
75
- timestr = "%Y%m%dT%H%M%S"
76
- iev.dtstart = dtstart.strftime(timestr)
77
- iev.dtend = dtend.strftime(timestr)
78
- iev.tzid = dtstart.strftime('%Z')
79
- iev.dtstamp = dtstamp.strftime(timestr)
80
- iev.last_modified = last_modified.strftime(timestr)
81
- iev.location = @message.location if @message.location
82
- iev.klass = @message.sensitivity if @message.sensitivity
83
- iev.summary = @message.subject if @message.subject
84
- iev.description = @message.body if @message.body
85
- iev.transp = @message.legacyFreeBusyStatus if @message.legacyFreeBusyStatus
86
- iev.duration = @message.duration if @message.duration
87
- iev.sequence = @message.appointmentSequenceNumber if @message.appointmentSequenceNumber
88
- iev.status = @message.myResponseType if @message.myResponseType
89
- #iev.attach @message.attachments if @message.hasAttachments
90
- @message.requiredAttendees.each do |pers|
91
- output = "ROLE=REQ-PARTICIPANT;CN=\"#{pers.mailbox.name}\"" if pers.mailbox.respond_to?(:name)
92
- output += ":MAILTO:#{pers.mailbox.emailAddress}" if pers.mailbox.respond_to?(:emailAddress)
93
- iev.attendees << output if output
94
- end if @message.requiredAttendees
95
- @message.optionalAttendees.each do |pers|
96
- output = "ROLE=OPT-PARTICIPANT;CN=\"#{pers.mailbox.name}\"" if pers.mailbox.respond_to?(:name)
97
- output += ":MAILTO:#{pers.mailbox.emailAddress}" if pers.mailbox.respond_to?(:emailAddress)
98
- iev.attendees << output if output
99
- end if @message.optionalAttendees
100
-
101
- #iev.categories = @message.categories if @message.categories
102
- #iev.resources = @message.resources if @message.resources
103
- return iev
104
- end
105
-
106
-
107
- # These methods are marked 'private' because they return EWS Types and I am trying to
108
- # hide those because they are not elegant and a bit too tedious for the public interface.
109
- private
110
-
111
- def get_calitem
112
- @message = @parent_folder.get_item(@item_id)
113
- end
114
- end # Event
115
- end # EWS
116
- end # Viewpoint
@@ -1,53 +0,0 @@
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
-
19
- module Viewpoint
20
- module EWS
21
- # This class represents a file attachment item. You can save this object
22
- # to a file withthe #save_to_file method.
23
- class FileAttachment < Attachment
24
-
25
- attr_reader :file_name, :content
26
- # @param [String] attachment_id The unique ID for the attachment.
27
- def initialize(attachment_id)
28
- @id = attachment_id
29
- conn = Viewpoint::EWS::EWS.instance
30
- resp = conn.ews.get_attachment([attachment_id])
31
- @file_name = resp.items.first[:file_attachment][:name][:text]
32
-
33
- # content in Base64
34
- @content = resp.items.first[:file_attachment][:content][:text]
35
- end
36
-
37
- # Save this FileAttachment object to a file. By default it saves
38
- # it to the original file's name in the current working directory.
39
- # @param [String,nil] base_dir the directory to save the file to. Pass nil if you
40
- # do not want to specify a directory or you are passing a full path name to file_name
41
- # @param [String] file_name The name of the file to save the content to.
42
- # Leave this blank to use the default attachment name.
43
- def save_to_file(base_dir = nil, file_name = @file_name)
44
- base_dir << '/' unless(base_dir.nil? or base_dir.end_with?('/'))
45
- File.open("#{base_dir}#{file_name}", 'w+') do |f|
46
- f.write(Base64.decode64(@content))
47
- end
48
- true
49
- end
50
-
51
- end # FileAttachment
52
- end # EWS
53
- end # Viewpoint
@@ -1,47 +0,0 @@
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
-
19
- module Viewpoint
20
- module EWS
21
- # This class represents a FolderType object in the Exchange Data store.
22
- # This is the type of folder that mail messages will be found in.
23
- class Folder < GenericFolder
24
-
25
- # Find folders of type Folder (message folders)
26
- # @see GenericFolder.find_folders
27
- # @param [String,Symbol] root An folder id, either a DistinguishedFolderId (must me a Symbol)
28
- # or a FolderId (String)
29
- # @param [String] traversal Shallow/Deep/SoftDeleted
30
- # @param [String] shape the shape to return IdOnly/Default/AllProperties
31
- # @param [optional, String] folder_type an optional folder type to limit the search to like 'IPF.Task'
32
- # @return [Array] Returns an Array of Folder or subclasses of Folder
33
- def self.find_folders(root = :msgfolderroot, traversal = 'Deep', shape = 'Default', folder_type = 'IPF.Note')
34
- super(root, traversal, shape, folder_type)
35
- end
36
-
37
-
38
- # Initialize with an item of FolderType. This is typically the folder
39
- # used to house mail messages.
40
- def initialize(folder)
41
- super(folder)
42
- define_int_var :unread_count
43
- end
44
-
45
- end # Folder
46
- end # EWS
47
- end # Viewpoint
@@ -1,471 +0,0 @@
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
-
19
- # This class is inherited by all folder subtypes such as Mail, Calendar,
20
- # Tasks and Search. It will serve as the brain for all of the methods that
21
- # each of these folder types have in common.
22
- module Viewpoint
23
- module EWS
24
- # This class is a generic folder that should typically not be instantiated
25
- # on it's own. It represents all the commonalities among folders found
26
- # in the Exchange Data Store of which there are many.
27
- # @see http://msdn.microsoft.com/en-us/library/aa564009.aspx
28
- class GenericFolder
29
- include Viewpoint
30
- include Model
31
-
32
- @@distinguished_folder_ids = %w{calendar contacts deleteditems drafts inbox journal
33
- notes outbox sentitems tasks msgfolderroot root junkemail searchfolders voicemail
34
- recoverableitemsroot recoverableitemsdeletions recoverableitemsversions
35
- recoverableitemspurges archiveroot archivemsgfolderroot archivedeleteditems
36
- archiverecoverableitemsroot archiverecoverableitemsdeletions
37
- archiverecoverableitemsversions archiverecoverableitemspurges publicfoldersroot}
38
-
39
- @@event_types = %w{CopiedEvent CreatedEvent DeletedEvent ModifiedEvent MovedEvent NewMailEvent}
40
-
41
- # Get a specific folder by its ID.
42
- # @param [String,Symbol] folder_id either a DistinguishedFolderID or simply a FolderID
43
- # @param [String,nil] act_as User to act on behalf as. This user must have been given
44
- # delegate access to this folder or else this operation will fail.
45
- # @param [Hash] folder_shape
46
- # @option folder_shape [String] :base_shape IdOnly/Default/AllProperties
47
- # @raise [EwsError] raised when the backend SOAP method returns an error.
48
- def self.get_folder(folder_id, act_as = nil, folder_shape = {:base_shape => 'Default'})
49
- resp = (Viewpoint::EWS::EWS.instance).ews.get_folder( [normalize_id(folder_id)], folder_shape, act_as )
50
- if(resp.status == 'Success')
51
- folder = resp.items.first
52
- f_type = folder.keys.first.to_s.camel_case
53
- return(eval "#{f_type}.new(folder[folder.keys.first])")
54
- else
55
- raise EwsError, "Could not retrieve folder. #{resp.code}: #{resp.message}"
56
- end
57
- end
58
-
59
- # Find subfolders of the passed root folder. If no parameters are passed
60
- # this method will search from the Root folder.
61
- # @param [String,Symbol] root An folder id, either a DistinguishedFolderId (must me a Symbol)
62
- # or a FolderId (String). This is where to start the search from. Usually :root,:msgfolderroot,:publicfoldersroot
63
- # @param [String] traversal Shallow/Deep/SoftDeleted
64
- # @param [String] shape the shape to return IdOnly/Default/AllProperties
65
- # @param [optional, String] folder_type an optional folder type to limit the search to like 'IPF.Task'
66
- # @return [Array] Returns an Array of Folder or subclasses of Folder
67
- # @raise [EwsError] raised when the backend SOAP method returns an error.
68
- def self.find_folders(root = :msgfolderroot, traversal = 'Shallow', shape = 'Default', folder_type = nil)
69
- if( folder_type.nil? )
70
- resp = (Viewpoint::EWS::EWS.instance).ews.find_folder( [normalize_id(root)], traversal, {:base_shape => shape} )
71
- else
72
- restr = {:restriction =>
73
- {:is_equal_to => [{:field_uRI => {:field_uRI=>'folder:FolderClass'}}, {:field_uRI_or_constant=>{:constant => {:value => folder_type}}}]}
74
- }
75
- resp = (Viewpoint::EWS::EWS.instance).ews.find_folder( [normalize_id(root)], traversal, {:base_shape => shape}, restr)
76
- end
77
-
78
- if(resp.status == 'Success')
79
- folders = []
80
- resp.items.each do |f|
81
- f_type = f.keys.first.to_s.camel_case
82
- folders << (eval "#{f_type}.new(f[f.keys.first])")
83
- end
84
- return folders
85
- else
86
- raise EwsError, "Could not retrieve folders. #{resp.code}: #{resp.message}"
87
- end
88
- end
89
-
90
- # Return a list of folder names
91
- # @param [String,Symbol] root An folder id, either a DistinguishedFolderId (must me a Symbol)
92
- # or a FolderId (String). This is where to start the search from. Usually :root,:msgfolderroot,:publicfoldersroot
93
- # @return [Array<String>] Return an Array of folder names.
94
- # @raise [EwsError] raised when the backend SOAP method returns an error.
95
- def self.folder_names(root = :msgfolderroot)
96
- resp = (Viewpoint::EWS::EWS.instance).ews.find_folder([root], 'Shallow')
97
- if(resp.status == 'Success')
98
- flds = []
99
- resp.items.each do |f|
100
- flds << f[f.keys.first][:display_name][:text]
101
- end
102
- flds
103
- else
104
- raise EwsError, "Could not retrieve folders. #{resp.code}: #{resp.message}"
105
- end
106
- end
107
-
108
- # Gets a folder by name. This name must match the folder name exactly.
109
- # @param [String] name The name of the folder to fetch.
110
- # @param [String,Symbol] root An folder id, either a DistinguishedFolderId (must me a Symbol)
111
- # or a FolderId (String). This is where to start the search from. Usually :root,:msgfolderroot,:publicfoldersroot
112
- # @param [String] shape the shape of the object to return IdOnly/Default/AllProperties
113
- # @return [GenericFolder] will return the folder by the given name.
114
- # @raise [EwsFolderNotFound] raised when a folder requested is not found
115
- # @raise [EwsError] raised when the backend SOAP method returns an error.
116
- def self.get_folder_by_name(name, root = :msgfolderroot, shape = 'Default', opts = {})
117
- opts[:traversal] = 'Deep' unless opts.has_key?(:traversal)
118
- # For now the :field_uRI and :field_uRI_or_constant must be in an Array for Ruby 1.8.7 because Hashes
119
- # are not positional at insertion until 1.9
120
- restr = {:restriction =>
121
- {:is_equal_to =>
122
- [{:field_uRI => {:field_uRI=>'folder:DisplayName'}}, {:field_uRI_or_constant =>{:constant => {:value=>name}}}]}}
123
- resp = (Viewpoint::EWS::EWS.instance).ews.find_folder([root], opts[:traversal], {:base_shape => shape}, restr)
124
- if(resp.status == 'Success')
125
- raise EwsFolderNotFound, "The folder requested is invalid or unavailable" if resp.items.empty?
126
- f = resp.items.first
127
- f_type = f.keys.first.to_s.camel_case
128
- eval "#{f_type}.new(f[f.keys.first])"
129
- else
130
- raise EwsError, "Could not retrieve folder. #{resp.code}: #{resp.message}"
131
- end
132
- end
133
-
134
- # Gets a folder by the given path. The default search path is :msgfolderroot so if you want to
135
- # specify a path at a different root change that parameter.
136
- # @param [String] path the fully qualified path to a folder at the given root
137
- # @example "/myfolders/folder a/personal calendar"
138
- # @param [String,Symbol] root An folder id, either a DistinguishedFolderId (must me a Symbol)
139
- # or a FolderId (String). This is where to start the search from. Usually :root,:msgfolderroot,:publicfoldersroot
140
- # @param [String] shape the shape of the object to return IdOnly/Default/AllProperties
141
- # @return [GenericFolder] will return the folder by the given path
142
- # @raise [EwsFolderNotFound] raised when a folder requested is not found
143
- # @raise [EwsError] raised when the backend SOAP method returns an error.
144
- def self.get_folder_by_path(path, root = :msgfolderroot, shape = 'Default')
145
- parts = path.split(/\//)
146
- parts = parts.slice(1..(parts.length)) if parts.first.empty?
147
- retfld = nil
148
- parts.each do |p|
149
- fld = self.get_folder_by_name(p, root, shape, {:traversal => 'Shallow'})
150
- root = fld.id
151
- retfld = fld if(fld.display_name.downcase == p.downcase)
152
- end
153
- retfld
154
- end
155
-
156
- attr_accessor :folder_id, :change_key, :parent_id, :sync_state
157
- attr_reader :subscription_id, :watermark
158
- alias :id :folder_id
159
-
160
- def initialize(ews_item)
161
- super() # Calls initialize in Model (creates @ews_methods Array)
162
- @ews_item = ews_item
163
- @folder_id = ews_item[:folder_id][:id]
164
- @ews_methods << :folder_id
165
- @ews_methods << :id
166
- @change_key = ews_item[:folder_id][:change_key]
167
- @ews_methods << :change_key
168
- unless ews_item[:parent_folder_id].nil?
169
- @parent_id = ews_item[:parent_folder_id]
170
- @ews_methods << :parent_id
171
- end
172
- define_str_var :display_name, :folder_class
173
- define_int_var :child_folder_count, :total_count
174
- # @todo Handle:
175
- # <EffectiveRights/>, <ExtendedProperty/>, <ManagedFolderInformation/>, <PermissionSet/>
176
-
177
- @sync_state = nil # Base-64 encoded sync data
178
- @synced = false # Whether or not the synchronization process is complete
179
- @subscription_id = nil
180
- @watermark = nil
181
- @shallow = true
182
- end
183
-
184
- # Subscribe this folder to events. This method initiates an Exchange pull
185
- # type subscription.
186
- #
187
- # @param [Array] event_types Which event types to subscribe to. By default
188
- # we subscribe to all Exchange event types: CopiedEvent, CreatedEvent,
189
- # DeletedEvent, ModifiedEvent, MovedEvent, NewMailEvent, FreeBusyChangedEvent
190
- # @return [Boolean] Did the subscription happen successfully?
191
- # @todo Add custom Exception for EWS
192
- def subscribe(event_types = @@event_types)
193
- # Refresh the subscription if already subscribed
194
- unsubscribe if subscribed?
195
-
196
- resp = (Viewpoint::EWS::EWS.instance).ews.subscribe([folder_id],event_types)
197
- if(resp.status == 'Success')
198
- @subscription_id = resp.items.first[:subscription_id][:text]
199
- @watermark = resp.items.first[:watermark][:text]
200
- return true
201
- else
202
- raise StandardError, "Error: #{resp.message}"
203
- end
204
- end
205
-
206
- # Check if there is a subscription for this folder.
207
- # @return [Boolean] Are we subscribed to this folder?
208
- def subscribed?
209
- ( @subscription_id.nil? or @watermark.nil? )? false : true
210
- end
211
-
212
-
213
- # Unsubscribe this folder from further Exchange events.
214
- # @return [Boolean] Did we unsubscribe successfully?
215
- # @todo Add custom Exception for EWS
216
- def unsubscribe
217
- return true if @subscription_id.nil?
218
-
219
- resp = (Viewpoint::EWS::EWS.instance).ews.unsubscribe(@subscription_id)
220
- if(resp.status == 'Success')
221
- @subscription_id, @watermark = nil, nil
222
- return true
223
- else
224
- raise StandardError, "Error: #{resp.message}"
225
- end
226
- end
227
-
228
- # Checks a subscribed folder for events
229
- # @return [Array] An array of Event items
230
- # @todo check for subscription expiry
231
- def get_events
232
- begin
233
- if(subscribed?)
234
- resp = (Viewpoint::EWS::EWS.instance).ews.get_events(@subscription_id, @watermark)
235
- parms = resp.items.shift
236
- @watermark = parms[:watermark]
237
- # @todo if parms[:more_events] # get more events
238
- return resp.items
239
- else
240
- raise StandardError, "Folder <#{self.display_name}> not subscribed to. Issue a Folder#subscribe before checking events."
241
- end
242
- rescue EwsSubscriptionTimeout => e
243
- @subscription_id, @watermark = nil, nil
244
- raise e
245
- end
246
- end
247
-
248
- # Find Items
249
- def find_items(opts = {})
250
- opts = opts.clone # clone the passed in object so we don't modify it in case it's being used in a loop
251
- item_shape = opts.has_key?(:item_shape) ? opts.delete(:item_shape) : {:base_shape => 'Default'}
252
- shallow = item_shape[:base_shape] != 'AllProperties'
253
- unless item_shape.has_key?(:additional_properties) # Don't overwrite if specified by caller
254
- item_shape[:additional_properties] = {:field_uRI => ['item:ParentFolderId']}
255
- end
256
- resp = (Viewpoint::EWS::EWS.instance).ews.find_item([@folder_id], 'Shallow', item_shape, opts)
257
- if(resp.status == 'Success')
258
- parms = resp.items.shift
259
- items = []
260
- resp.items.each do |i|
261
- i_type = i.keys.first
262
- items << (eval "#{i_type.to_s.camel_case}.new(i[i_type], :shallow => #{shallow})")
263
- end
264
- return items
265
- else
266
- raise EwsError, "Could not find items. #{resp.code}: #{resp.message}"
267
- end
268
- end
269
-
270
- # Fetch only items from today (since midnight)
271
- def todays_items(opts = {})
272
- #opts = {:query_string => ["Received:today"]}
273
- #This is a bit convoluted for pre-1.9.x ruby versions that don't support to_datetime
274
- items_since(DateTime.parse(Date.today.to_s), opts)
275
- end
276
-
277
- # Fetch items since a give DateTime
278
- # @param [DateTime] date_time the time to fetch Items since.
279
- def items_since(date_time, opts = {})
280
- restr = {:restriction =>
281
- {:is_greater_than_or_equal_to =>
282
- [{:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
283
- {:field_uRI_or_constant =>{:constant => {:value=>date_time}}}]
284
- }}
285
- find_items(opts.merge(restr))
286
- end
287
-
288
- # Fetch items between a given time period
289
- # @param [DateTime] start_date the time to start fetching Items from
290
- # @param [DateTime] end_date the time to stop fetching Items from
291
- def items_between(start_date, end_date, opts={})
292
- restr = {:restriction => {:and => [
293
- {:is_greater_than_or_equal_to =>
294
- [{:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
295
- {:field_uRI_or_constant=>{:constant => {:value =>start_date}}}]},
296
- {:is_less_than_or_equal_to =>
297
- [{:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
298
- {:field_uRI_or_constant=>{:constant => {:value =>end_date}}}]}
299
- ]}}
300
- find_items(opts.merge(restr))
301
- end
302
-
303
- # Search on the item subject
304
- # @param [String] match_str A simple string paramater to match against the subject. The search ignores
305
- # case and does not accept regexes... only strings.
306
- # @param [String,nil] exclude_str A string to exclude from matches against the subject. This is optional.
307
- def search_by_subject(match_str, exclude_str = nil)
308
- match = {:contains =>
309
- [
310
- {:containment_mode => 'Substring'},
311
- {:containment_comparison => 'IgnoreCase'},
312
- {:field_uRI => {:field_uRI=>'item:Subject'}},
313
- {:constant => {:value =>match_str}}
314
- ]
315
- }
316
- unless exclude_str.nil?
317
- excl = {:not =>
318
- {:contains =>
319
- [
320
- {:containment_mode => 'Substring'},
321
- {:containment_comparison => 'IgnoreCase'},
322
- {:field_uRI => {:field_uRI=>'item:Subject'}},
323
- {:constant => {:value =>exclude_str}}
324
- ]
325
- }
326
- }
327
-
328
- match[:and] = [{:contains => match.delete(:contains)}, excl]
329
- end
330
-
331
- find_items({:restriction => match})
332
- end
333
-
334
- # Get Item
335
- # @param [String] item_id the ID of the item to fetch
336
- # @param [String] change_key specify an optional change_key if you want to
337
- # make sure you are fetching a specific version of the object.
338
- def get_item(item_id, change_key = nil)
339
- item_shape = {:base_shape => 'Default', :additional_properties => {:field_uRI => ['item:ParentFolderId']}}
340
- resp = (Viewpoint::EWS::EWS.instance).ews.get_item([item_id], item_shape)
341
- if(resp.status == 'Success')
342
- item = resp.items.shift
343
- type = item.keys.first
344
- eval "#{type.to_s.camel_case}.new(item[type])"
345
- else
346
- raise EwsError, "Could not retrieve item. #{resp.code}: #{resp.message}"
347
- end
348
- end
349
-
350
- # Get Items
351
- # @param [String] item_ids is an array of Item IDs to fetch
352
- # @param [String] change_key specify an optional change_key if you want to
353
- # make sure you are fetching a specific version of the object.
354
- # @param [String] options specify an optional options hash. Supports the
355
- # key :item_shape that expects a hash value with :base_shape and other
356
- # optional parameters that specify the desired fields to return.
357
- def get_items(item_ids, change_key = nil, options={})
358
- item_shape = options[:item_shape] ||
359
- {:base_shape => 'Default', :additional_properties => {:field_uRI => ['item:ParentFolderId']}}
360
- shallow = item_shape[:base_shape] != 'AllProperties'
361
- resp = (Viewpoint::EWS::EWS.instance).ews.get_item(item_ids, item_shape)
362
- if(resp.status == 'Success')
363
- resp.items.map do |item|
364
- type = item.keys.first
365
- eval "#{type.to_s.camel_case}.new(item[type], :shallow => #{shallow})"
366
- end
367
- else
368
- raise EwsError, "Could not retrieve items. #{resp.code}: #{resp.message}"
369
- end
370
- end
371
-
372
-
373
- # Syncronize Items in this folder. If this method is issued multiple
374
- # times it will continue where the last sync completed.
375
- # @param [Integer] sync_amount The number of items to synchronize per sync
376
- # @param [Boolean] sync_all Whether to sync all the data by looping through.
377
- # The default is to just sync the first set. You can manually loop through
378
- # with multiple calls to #sync_items!
379
- # @return [Hash] Returns a hash with keys for each change type that ocurred.
380
- # Possible key values are (:create/:udpate/:delete). For create and update
381
- # changes the values are Arrays of Item or a subclass of Item. For deletes
382
- # an array of ItemIds are returned wich is a Hash in the form:
383
- # {:id=>"item id", :change_key=>"change key"}
384
- # See: http://msdn.microsoft.com/en-us/library/aa565609.aspx
385
- def sync_items!(sync_amount = 256, sync_all = false, opts = {})
386
- item_shape = opts.has_key?(:item_shape) ? opts.delete(:item_shape) : {:base_shape => 'Default'}
387
- shallow = item_shape[:base_shape] != 'AllProperties'
388
- resp = (Viewpoint::EWS::EWS.instance).ews.sync_folder_items(@folder_id, @sync_state, sync_amount, item_shape)
389
- parms = resp.items.shift
390
- @sync_state = parms[:sync_state]
391
- @synced = parms[:includes_last_item_in_range]
392
- items = {}
393
- resp.items.each do |i|
394
- key = i.keys.first
395
- items[key] = [] unless items[key].is_a?(Array)
396
- if(key == :delete || key == :read_flag_change)
397
- items[key] << i[key][:item_id]
398
- else
399
- i_type = i[key].keys.first
400
- next if !i_type
401
- items[key] << (eval "#{i_type.to_s.camel_case}.new(i[key][i_type], :shallow => #{shallow})")
402
- end
403
- end
404
- items
405
- end
406
-
407
- # Fetch current synced state (comes from synchronizing elements).
408
- # @return [Boolean] Current synchronization state, i.e. whether sync_items! needs to be called
409
- # again to finish the pending rounds of synchronization requests.
410
- def synced
411
- @synced
412
- end
413
-
414
- # This is basically a work-around for Microsoft's BPOS hosted Exchange, which does not support
415
- # subscriptions at the time of this writing. This is the best way I could think of to get
416
- # items from a specific period of time and track changes.
417
- # !! Before using this method I would suggest trying a GenericFolder#items_since then using
418
- # a subscription to track changes.
419
- # This method should be followed by subsequent calls to GenericFolder#sync_items! to fetch
420
- # additional items. Calling this method again will clear the sync_state and synchronize
421
- # everything again.
422
- # @return [Array<Item>] returns an array of Items
423
- def sync_items_since!(datetime, opts={})
424
- clear_sync_state!
425
-
426
- begin
427
- items = sync_items!
428
- end until items.empty?
429
-
430
- items_since(datetime, opts)
431
- end
432
-
433
- # Clears out the @sync_state so you can freshly synchronize this folder if needed
434
- def clear_sync_state!
435
- @sync_state = nil
436
- end
437
-
438
- # Create a subfolder of this folder
439
- #
440
- # @param [String] name The name of the new folder
441
- def add_subfolder(name)
442
- resp = (Viewpoint::EWS::EWS.instance).ews.create_folder(@folder_id, name)
443
- folder = resp.items.first
444
- ftype = folder.keys.first
445
- GenericFolder.get_folder(folder[ftype][:folder_id][:id])
446
- end
447
-
448
- # Deletes this folder from the Exchange Data Store
449
- # @param [Boolean] recycle_bin Send to the recycle bin instead of deleting (default: false)
450
- # @return [TrueClass] This will return true because if an issue occurs it
451
- # will be thrown in the SOAP Parser
452
- def delete!(recycle_bin = false)
453
- deltype = recycle_bin ? 'MoveToDeletedItems' : 'HardDelete'
454
- resp = (Viewpoint::EWS::EWS.instance).ews.delete_folder(@folder_id, deltype)
455
- true
456
- end
457
-
458
- private
459
-
460
- # Return the appropriate id based on whether it is a DistinguishedFolderId or
461
- # simply just a FolderId
462
- def self.normalize_id(folder_id)
463
- tfolder_id = folder_id.to_s.downcase
464
- # If the folder_id is a DistinguishedFolderId change it to a symbol so our SOAP
465
- # method does the right thing.
466
- @@distinguished_folder_ids.find_index(tfolder_id) ? tfolder_id.to_sym : folder_id
467
- end
468
-
469
- end # GenericFolder
470
- end # EWS
471
- end # Viewpoint