exchanger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +53 -0
  3. data/lib/exchanger.rb +79 -0
  4. data/lib/exchanger/attributes.rb +61 -0
  5. data/lib/exchanger/boolean.rb +4 -0
  6. data/lib/exchanger/client.rb +19 -0
  7. data/lib/exchanger/config.rb +32 -0
  8. data/lib/exchanger/dirty.rb +239 -0
  9. data/lib/exchanger/element.rb +161 -0
  10. data/lib/exchanger/elements/attendee.rb +10 -0
  11. data/lib/exchanger/elements/base_folder.rb +61 -0
  12. data/lib/exchanger/elements/calendar_folder.rb +11 -0
  13. data/lib/exchanger/elements/calendar_item.rb +59 -0
  14. data/lib/exchanger/elements/complete_name.rb +16 -0
  15. data/lib/exchanger/elements/contact.rb +49 -0
  16. data/lib/exchanger/elements/contacts_folder.rb +17 -0
  17. data/lib/exchanger/elements/distribution_list.rb +8 -0
  18. data/lib/exchanger/elements/email_address.rb +16 -0
  19. data/lib/exchanger/elements/entry.rb +11 -0
  20. data/lib/exchanger/elements/folder.rb +9 -0
  21. data/lib/exchanger/elements/identifier.rb +7 -0
  22. data/lib/exchanger/elements/im_address.rb +20 -0
  23. data/lib/exchanger/elements/item.rb +86 -0
  24. data/lib/exchanger/elements/mailbox.rb +34 -0
  25. data/lib/exchanger/elements/meeting_cancellation.rb +4 -0
  26. data/lib/exchanger/elements/meeting_message.rb +16 -0
  27. data/lib/exchanger/elements/meeting_request.rb +6 -0
  28. data/lib/exchanger/elements/meeting_response.rb +4 -0
  29. data/lib/exchanger/elements/message.rb +24 -0
  30. data/lib/exchanger/elements/phone_number.rb +13 -0
  31. data/lib/exchanger/elements/physical_address.rb +15 -0
  32. data/lib/exchanger/elements/search_folder.rb +5 -0
  33. data/lib/exchanger/elements/single_recipient.rb +6 -0
  34. data/lib/exchanger/elements/task.rb +5 -0
  35. data/lib/exchanger/elements/tasks_folder.rb +4 -0
  36. data/lib/exchanger/field.rb +139 -0
  37. data/lib/exchanger/operation.rb +110 -0
  38. data/lib/exchanger/operations/create_item.rb +64 -0
  39. data/lib/exchanger/operations/expand_dl.rb +42 -0
  40. data/lib/exchanger/operations/find_folder.rb +55 -0
  41. data/lib/exchanger/operations/find_item.rb +54 -0
  42. data/lib/exchanger/operations/get_folder.rb +55 -0
  43. data/lib/exchanger/operations/get_item.rb +44 -0
  44. data/lib/exchanger/operations/resolve_names.rb +43 -0
  45. data/lib/exchanger/operations/update_item.rb +43 -0
  46. data/lib/exchanger/persistence.rb +27 -0
  47. data/spec/calendar_item_spec.rb +26 -0
  48. data/spec/client_spec.rb +11 -0
  49. data/spec/contact_spec.rb +68 -0
  50. data/spec/element_spec.rb +4 -0
  51. data/spec/field_spec.rb +118 -0
  52. data/spec/folder_spec.rb +13 -0
  53. data/spec/mailbox_spec.rb +9 -0
  54. data/spec/spec_helper.rb +6 -0
  55. metadata +208 -0
@@ -0,0 +1,161 @@
1
+ module Exchanger
2
+ # General purpose element.
3
+ class Element
4
+ include Exchanger::Attributes
5
+ include Exchanger::Dirty
6
+ include Exchanger::Persistence
7
+
8
+ class_inheritable_accessor :elements
9
+ class_inheritable_accessor :keys
10
+ class_inheritable_accessor :field_uri_namespace
11
+ class_inheritable_accessor :identifier_name
12
+
13
+ # Exchanger expects elements to be in the same order as defined in types.xsd
14
+ self.elements = ActiveSupport::OrderedHash.new
15
+ self.keys = []
16
+
17
+ # Define a new child element.
18
+ def self.element(name, options = {})
19
+ options[:field_uri_namespace] ||= self.field_uri_namespace
20
+ elements[name] = Field.new(name, options)
21
+ create_element_accessors(name)
22
+ add_dirty_methods(name)
23
+ end
24
+
25
+ # Defina a new element attribute.
26
+ def self.key(name, options = {})
27
+ keys << name
28
+ create_element_accessors(name)
29
+ add_dirty_methods(name)
30
+ end
31
+
32
+ # Create child element accessors.
33
+ def self.create_element_accessors(name)
34
+ define_method(name) { read_attribute(name) }
35
+ define_method("#{name}=") { |value| write_attribute(name, value) }
36
+ define_method("#{name}?") { read_attribute(name).present? }
37
+ end
38
+
39
+ attr_writer :tag_name
40
+
41
+ def tag_name
42
+ @tag_name || self.class.name.demodulize
43
+ end
44
+
45
+ def initialize(attributes = {})
46
+ @attributes = {}
47
+ self.attributes = attributes
48
+ setup_modifications
49
+ end
50
+
51
+ # Performs equality checking on attributes
52
+ def ==(other)
53
+ self.class == other.class &&
54
+ self.attributes == other.attributes
55
+ end
56
+
57
+ def errors
58
+ @errors ||= []
59
+ end
60
+
61
+ def inspect
62
+ keys = self.class.elements.keys | attributes.keys.map(&:to_sym)
63
+ keys -= [:id, :change_key, self.class.identifier_name]
64
+ attrs = keys.map { |name, field| "#{name}: #{attributes[name.to_s].inspect}" }
65
+ str = "#<#{self.class.name}"
66
+ str << " id: #{id.inspect}, change_key: #{change_key.inspect}" if self.class.identifier_name || self.class.keys.include?(:id)
67
+ str << " #{attrs * ', '}" unless attrs.empty?
68
+ str << ">"
69
+ str
70
+ end
71
+
72
+ def self.new_from_xml(xml)
73
+ object = new
74
+ # Keys
75
+ xml.attributes.values.each do |attr|
76
+ name = attr.name.underscore.to_sym
77
+ value = attr.value
78
+ object.write_attribute(name, value)
79
+ end
80
+ # Fields
81
+ xml.children.each do |node|
82
+ name = node.name.underscore.to_sym
83
+ field = elements[name] || Field.new(name)
84
+ value = field.value_from_xml(node)
85
+ object.write_attribute(name, value)
86
+ end
87
+ object.send(:reset_modifications)
88
+ object
89
+ end
90
+
91
+ # Builds XML from elements and attributes
92
+ def to_xml(options = {})
93
+ doc = Nokogiri::XML::Document.new
94
+ root = doc.create_element(tag_name)
95
+ self.class.keys.each do |name|
96
+ value = read_attribute(name)
97
+ next if value.blank?
98
+ root[name.to_s.camelize] = value
99
+ end
100
+ self.class.elements.each do |name, field|
101
+ next if options[:only] && !options[:only].include?(name)
102
+ next if field.options[:readonly]
103
+ value = read_attribute(name)
104
+ next if field.type.is_a?(Array) && value.blank?
105
+ next if new_record? && field.type == Identifier
106
+ next if new_record? && value.blank?
107
+ if name == :text
108
+ root << value.to_s
109
+ else
110
+ root << field.to_xml(value)
111
+ end
112
+ end
113
+ root
114
+ end
115
+
116
+ # Builds XML Item/Folder change for update operations.
117
+ def to_xml_change
118
+ doc = Nokogiri::XML::Document.new
119
+ root = doc.create_element("#{identifier.tag_name.gsub(/Id$/, '')}Change")
120
+ root << identifier.to_xml
121
+ root << to_xml_updates
122
+ root
123
+ end
124
+
125
+ # Builds XML Set/Delete fields for update operations.
126
+ def to_xml_updates
127
+ doc = Nokogiri::XML::Document.new
128
+ root = doc.create_element("Updates")
129
+ self.class.elements.each do |name, field|
130
+ value = read_attribute(name)
131
+ # Create or update existing fields
132
+ if changes.include?(name.to_s)
133
+ field.to_xml_updates(value) do |field_uri_xml, element_xml|
134
+ # Exchanger does not like updating to nil, so delete those.
135
+ if element_xml.text.present?
136
+ set_item_field = doc.create_element("SetItemField")
137
+ set_item_field << field_uri_xml
138
+ set_item_field << doc.create_element(tag_name) << element_xml
139
+ root << set_item_field
140
+ else
141
+ delete_item_field = doc.create_element("DeleteItemField")
142
+ delete_item_field << field_uri_xml
143
+ root << delete_item_field
144
+ end
145
+ end
146
+ end
147
+ # Delete removed phone numbers, etc
148
+ if changes.include?(name.to_s) && value.is_a?(Array)
149
+ old_values, new_values = changes[name.to_s]
150
+ deleted_values = old_values - new_values
151
+ field.to_xml_updates(deleted_values) do |field_uri_xml, _|
152
+ delete_item_field = doc.create_element("DeleteItemField")
153
+ delete_item_field << field_uri_xml
154
+ root << delete_item_field
155
+ end
156
+ end
157
+ end
158
+ root
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,10 @@
1
+ module Exchanger
2
+ # The Attendee element represents attendees and resources for a meeting.
3
+ #
4
+ # http://msdn.microsoft.com/en-us/library/aa580339.aspx
5
+ class Attendee < Element
6
+ element :mailbox, :type => Mailbox
7
+ element :response_type # Unknown Organizer Tentative Accept Decline NoResponseReceived
8
+ element :last_response_time, :type => Time
9
+ end
10
+ end
@@ -0,0 +1,61 @@
1
+ module Exchanger
2
+ # Abstract folder class
3
+ class BaseFolder < Element
4
+ self.identifier_name = :folder_id
5
+ self.field_uri_namespace = :folder
6
+
7
+ element :folder_id, :type => Identifier
8
+ element :parent_folder_id, :type => Identifier
9
+ element :folder_class
10
+ element :display_name
11
+ element :total_count, :type => Integer
12
+ element :child_folder_count, :type => Integer
13
+ element :extended_property, :type => Element
14
+ element :managed_folder_information, :type => Element
15
+ element :effective_rights, :type => Element
16
+
17
+ # Folders that can be referenced by name.
18
+ # http://msdn.microsoft.com/en-us/library/aa580808.aspx
19
+ DISTINGUISHED_NAMES = [
20
+ :inbox, :outbox, :drafts, :deleteditems, :sentitems, :junkemail,
21
+ :calendar, :contacts, :tasks, :notes, :journal,
22
+ :msgfolderroot, :publicfoldersroot, :root,
23
+ :searchfolders, :voicemail
24
+ ]
25
+
26
+ def self.find(id, email_address = nil)
27
+ find_all([id], email_address).first
28
+ end
29
+
30
+ def self.find_all(ids, email_address = nil)
31
+ response = GetFolder.run(:folder_ids => ids, :email_address => email_address)
32
+ response.folders
33
+ end
34
+
35
+ def self.find_all_by_parent_id(parent_id)
36
+ response = FindFolder.run(:parent_folder_id => parent_id)
37
+ response.folders
38
+ end
39
+
40
+ attr_writer :parent_folder
41
+
42
+ def parent_folder
43
+ @parent_folder ||= if parent_folder_id
44
+ Folder.find(parent_folder_id.id)
45
+ end
46
+ end
47
+
48
+ # Sub folders
49
+ def folders
50
+ self.class.find_all_by_parent_id(id).each do |folder|
51
+ folder.parent_folder = self
52
+ end
53
+ end
54
+
55
+ def items
56
+ Item.find_all_by_folder_id(id).each do |item|
57
+ item.parent_folder = self
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,11 @@
1
+ module Exchanger
2
+ class CalendarFolder < BaseFolder
3
+ element :permission_set
4
+
5
+ def new_calendar_item(attributes = {})
6
+ contact = CalendarItem.new(attributes)
7
+ contact.parent_folder_id = folder_id
8
+ contact
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ module Exchanger
2
+ # The CalendarItem element represents an Exchanger calendar item.
3
+ #
4
+ # http://msdn.microsoft.com/en-us/library/aa564765.aspx
5
+ class CalendarItem < Item
6
+ self.field_uri_namespace = :calendar
7
+
8
+ # iCalendar properties
9
+ element :uid, :name => "UID"
10
+ element :recurrence_id, :type => Time
11
+ element :date_time_stamp, :type => Time
12
+ # Single and Occurrence only
13
+ element :start, :type => Time
14
+ element :end, :type => Time
15
+ # Occurrence only
16
+ element :original_start, :type => Time
17
+ # Other properties
18
+ element :is_all_day_event, :type => Boolean
19
+ element :legacy_free_busy_status
20
+ element :location
21
+ element :when
22
+ element :is_meeting, :type => Boolean
23
+ element :is_cancelled, :type => Boolean
24
+ element :is_recurring, :type => Boolean
25
+ element :meeting_request_was_sent, :type => Boolean
26
+ element :is_response_requested, :type => Boolean
27
+ element :calendar_item_type
28
+ element :my_response_type
29
+ element :organizer, :type => SingleRecipient
30
+ element :required_attendees, :type => [Attendee]
31
+ element :optional_attendees, :type => [Attendee]
32
+ element :resources, :type => [Attendee]
33
+ # Conflicting and adjacent meetings
34
+ element :conflicting_meeting_count, :type => Integer
35
+ element :adjacent_meeting_count, :type => Integer
36
+ element :conflicting_meetings, :type => [CalendarItem]
37
+ element :adjacent_meetings, :type => [CalendarItem]
38
+ # Duration
39
+ element :duration
40
+ element :time_zone
41
+ # Appointment
42
+ element :appointment_reply_time, :type => Time
43
+ element :appointment_sequence_number, :type => Integer
44
+ element :appointment_state, :type => Integer
45
+ # Recurrence specific data, only valid if CalendarItemType is RecurringMaster
46
+ element :recurrence #, :type => Recurrence
47
+ element :first_occurrence #, :type => OccurrenceInfo
48
+ element :last_occurrence #, :type => OccurrenceInfo
49
+ element :modified_occurrences #, :type => [OccurrenceInfo]
50
+ element :deleted_occurrences #, :type => [DeletedOccurrenceInfo]
51
+ element :meeting_time_zone #, :type => TimeZone
52
+ # Other properties
53
+ element :conference_type, :type => Integer
54
+ element :allow_new_time_proposal, :type => Boolean
55
+ element :is_online_meeting, :type => Boolean
56
+ element :meeting_workspace_url
57
+ element :net_show_url
58
+ end
59
+ end
@@ -0,0 +1,16 @@
1
+ module Exchanger
2
+ # The CompleteName element represents the complete name of a contact.
3
+ # This element is read only and will not be sent on create/update.
4
+ #
5
+ # http://msdn.microsoft.com/en-us/library/aa494294.aspx
6
+ class CompleteName < Element
7
+ element :title
8
+ element :first_name
9
+ element :middle_name
10
+ element :last_name
11
+ element :suffix
12
+ element :initials
13
+ element :full_name
14
+ element :nickname
15
+ end
16
+ end
@@ -0,0 +1,49 @@
1
+ module Exchanger
2
+ # The Contact element represents a contact item in the Exchanger store.
3
+ # http://msdn.microsoft.com/en-us/library/aa581315.aspx
4
+ class Contact < Item
5
+ # All the uri namespaces are singular (item, task, etc) except contacts.
6
+ self.field_uri_namespace = :contacts
7
+
8
+ element :file_as
9
+ element :file_as_mapping
10
+ element :display_name
11
+ element :given_name
12
+ element :initials
13
+ element :middle_name
14
+ element :nickname
15
+ element :complete_name, :type => CompleteName, :readonly => true
16
+ element :company_name
17
+ element :email_addresses, :type => [EmailAddress]
18
+ element :physical_addresses, :type => [PhysicalAddress]
19
+ element :phone_numbers, :type => [PhoneNumber]
20
+ element :assistant_name
21
+ element :birthday, :type => Time
22
+ element :business_home_page
23
+ element :children, :type => [String]
24
+ element :companies, :type => [String]
25
+ element :contact_source # ActiveDirectory, Store
26
+ element :department
27
+ element :generation
28
+ element :im_addresses, :type => [ImAddress]
29
+ element :job_title
30
+ element :manager
31
+ element :mileage
32
+ element :office_location
33
+ element :postal_address_index
34
+ element :profession
35
+ element :spouse_name
36
+ element :surname
37
+ element :wedding_anniversary, :type => Time
38
+
39
+ # Marked as private in Outlook?
40
+ def private?
41
+ sensitivity == "Private"
42
+ end
43
+
44
+ def self.search(name)
45
+ response = Exchanger::ResolveNames.run(:name => name)
46
+ response.contacts
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ module Exchanger
2
+ class ContactsFolder < BaseFolder
3
+ element :permission_set
4
+
5
+ def contacts
6
+ items.select do |item|
7
+ item.is_a?(Contact)
8
+ end
9
+ end
10
+
11
+ def new_contact(attributes = {})
12
+ contact = Contact.new(attributes)
13
+ contact.parent_folder_id = folder_id
14
+ contact
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Exchanger
2
+ # http://msdn.microsoft.com/en-us/library/aa566353.aspx
3
+ class DistributionList < Item
4
+ element :display_name
5
+ element :file_as
6
+ element :contact_source # ActiveDirectory, Store
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ module Exchanger
2
+ # The Entry element represents a single e-mail address for a contact.
3
+ #
4
+ # http://msdn.microsoft.com/en-us/library/aa564757.aspx
5
+ class EmailAddress < Entry
6
+ self.field_uri_namespace = :"contacts:EmailAddress"
7
+
8
+ key :key # EmailAddress1, EmailAddress2 or EmailAddress3
9
+ key :name
10
+ key :routing_type
11
+ key :mailbox_type
12
+
13
+ # A text value that represents the entry is required if this element is used.
14
+ element :text
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Exchanger
2
+ # Entry (EmailAddress),
3
+ # Entry (IMAddress),
4
+ # Entry (PhoneNumber),
5
+ # Entry (PhysicalAddress)
6
+ class Entry < Element
7
+ def tag_name
8
+ "Entry"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Exchanger
2
+ # The Folder element defines a folder to create, get, find, synchronize, or update.
3
+ #
4
+ # http://msdn.microsoft.com/en-us/library/aa494294.aspx
5
+ class Folder < BaseFolder
6
+ element :permission_set
7
+ element :unread_count, :type => Integer
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Exchanger
2
+ # ItemId, FolderId, etc
3
+ class Identifier < Element
4
+ key :id
5
+ key :change_key
6
+ end
7
+ end