viewpoint 0.1.27 → 1.0.0.beta.1

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