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.
- data/README.md +196 -0
- data/lib/ews/calendar_accessors.rb +34 -0
- data/lib/ews/connection.rb +117 -0
- data/lib/ews/connection_helper.rb +35 -0
- data/lib/ews/ews_client.rb +71 -0
- data/lib/ews/exceptions/exceptions.rb +59 -0
- data/lib/ews/folder_accessors.rb +199 -0
- data/lib/ews/item_accessors.rb +157 -0
- data/lib/ews/mailbox_accessors.rb +87 -0
- data/lib/ews/message_accessors.rb +86 -0
- data/lib/ews/push_subscription_accessors.rb +33 -0
- data/lib/ews/soap.rb +63 -0
- data/lib/ews/soap/builders/ews_builder.rb +1011 -0
- data/lib/ews/soap/ews_response.rb +83 -0
- data/lib/ews/soap/ews_soap_availability_response.rb +58 -0
- data/lib/ews/soap/ews_soap_free_busy_response.rb +109 -0
- data/lib/ews/soap/ews_soap_response.rb +103 -0
- data/lib/ews/soap/exchange_availability.rb +61 -0
- data/lib/ews/soap/exchange_data_services.rb +742 -0
- data/lib/ews/soap/exchange_notification.rb +146 -0
- data/lib/ews/soap/exchange_user_configuration.rb +33 -0
- data/lib/ews/soap/exchange_web_service.rb +294 -0
- data/lib/{model/attendee.rb → ews/soap/parsers/ews_parser.rb} +20 -14
- data/lib/ews/soap/parsers/ews_sax_document.rb +66 -0
- data/lib/ews/soap/response_message.rb +78 -0
- data/lib/ews/soap/responses/create_attachment_response_message.rb +46 -0
- data/lib/{model/meeting_message.rb → ews/soap/responses/create_item_response_message.rb} +7 -10
- data/lib/ews/soap/responses/find_item_response_message.rb +80 -0
- data/lib/ews/soap/responses/get_events_response_message.rb +53 -0
- data/lib/ews/soap/responses/send_notification_response_message.rb +58 -0
- data/lib/{model/attachment.rb → ews/soap/responses/subscribe_response_message.rb} +17 -13
- data/lib/ews/templates/forward_item.rb +24 -0
- data/lib/ews/templates/message.rb +66 -0
- data/lib/ews/templates/reply_to_item.rb +25 -0
- data/lib/ews/types.rb +146 -0
- data/lib/ews/types/attachment.rb +77 -0
- data/lib/{model/meeting_cancellation.rb → ews/types/attendee.rb} +9 -8
- data/lib/ews/types/calendar_folder.rb +8 -0
- data/lib/ews/types/calendar_item.rb +37 -0
- data/lib/ews/types/contact.rb +7 -0
- data/lib/ews/types/contacts_folder.rb +8 -0
- data/lib/ews/types/copied_event.rb +51 -0
- data/lib/{soap/handsoap/builder.rb → ews/types/created_event.rb} +5 -2
- data/lib/{model/meeting_response.rb → ews/types/deleted_event.rb} +6 -6
- data/lib/ews/types/distribution_list.rb +7 -0
- data/lib/ews/types/event.rb +62 -0
- data/lib/ews/types/file_attachment.rb +65 -0
- data/lib/ews/types/folder.rb +60 -0
- data/lib/{model/distribution_list.rb → ews/types/free_busy_changed_event.rb} +6 -6
- data/lib/ews/types/generic_folder.rb +352 -0
- data/lib/ews/types/item.rb +381 -0
- data/lib/ews/types/item_attachment.rb +46 -0
- data/lib/{model → ews/types}/item_field_uri_map.rb +2 -2
- data/lib/ews/types/mailbox_user.rb +156 -0
- data/lib/ews/types/meeting_cancellation.rb +7 -0
- data/lib/ews/types/meeting_message.rb +7 -0
- data/lib/ews/types/meeting_request.rb +7 -0
- data/lib/ews/types/meeting_response.rb +7 -0
- data/lib/ews/types/message.rb +7 -0
- data/lib/ews/types/modified_event.rb +48 -0
- data/lib/{model/item_attachment.rb → ews/types/moved_event.rb} +33 -15
- data/lib/ews/types/new_mail_event.rb +24 -0
- data/lib/ews/types/out_of_office.rb +147 -0
- data/lib/ews/types/search_folder.rb +8 -0
- data/lib/ews/types/status_event.rb +39 -0
- data/lib/ews/types/task.rb +7 -0
- data/lib/ews/types/tasks_folder.rb +8 -0
- data/lib/viewpoint.rb +82 -106
- metadata +99 -67
- data/README +0 -114
- data/lib/exceptions/exceptions.rb +0 -46
- data/lib/model/calendar_folder.rb +0 -67
- data/lib/model/calendar_item.rb +0 -267
- data/lib/model/contact.rb +0 -238
- data/lib/model/contacts_folder.rb +0 -46
- data/lib/model/event.rb +0 -116
- data/lib/model/file_attachment.rb +0 -53
- data/lib/model/folder.rb +0 -47
- data/lib/model/generic_folder.rb +0 -471
- data/lib/model/item.rb +0 -313
- data/lib/model/mailbox_user.rb +0 -146
- data/lib/model/meeting_request.rb +0 -39
- data/lib/model/message.rb +0 -142
- data/lib/model/model.rb +0 -269
- data/lib/model/search_folder.rb +0 -48
- data/lib/model/task.rb +0 -121
- data/lib/model/tasks_folder.rb +0 -44
- data/lib/soap/handsoap/builders/ews_build_helpers.rb +0 -383
- data/lib/soap/handsoap/builders/ews_builder.rb +0 -146
- data/lib/soap/handsoap/ews_service.rb +0 -813
- data/lib/soap/handsoap/parser.rb +0 -104
- data/lib/soap/handsoap/parsers/ews_parser.rb +0 -246
- 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
|
data/lib/model/event.rb
DELETED
@@ -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
|
data/lib/model/folder.rb
DELETED
@@ -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
|
data/lib/model/generic_folder.rb
DELETED
@@ -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
|