Class: Tilia::VObject::ITip::Broker

Inherits:
Object
  • Object
show all
Defined in:
lib/tilia/v_object/i_tip/broker.rb

Overview

The ITipBroker class is a utility class that helps with processing so-called iTip messages.

iTip is defined in rfc5546, stands for iCalendar Transport-Independent Interoperability Protocol, and describes the underlying mechanism for using iCalendar for scheduling for for example through email (also known as IMip) and CalDAV Scheduling.

This class helps by:

  1. Creating individual invites based on an iCalendar event for each attendee.

  2. Generating invite updates based on an iCalendar update. This may result in new invites, updates and cancellations for attendees, if that list changed.

  3. On the receiving end, it can create a local iCalendar event based on a received invite.

  4. It can also process an invite update on a local event, ensuring that any overridden properties from attendees are retained.

  5. It can create a accepted or declined iTip reply based on an invite.

  6. It can process a reply from an invite and update an events attendee

    status based on a reply.
    

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBroker

Sets instance variables



911
912
913
914
915
916
917
918
919
920
921
922
923
# File 'lib/tilia/v_object/i_tip/broker.rb', line 911

def initialize
  @schedule_agent_server_rules = true
  @significant_change_properties = [
    'DTSTART',
    'DTEND',
    'DURATION',
    'DUE',
    'RRULE',
    'RDATE',
    'EXDATE',
    'STATUS'
  ]
end

Instance Attribute Details

#schedule_agent_server_rulesBoolean

This setting determines whether the rules for the SCHEDULE-AGENT parameter should be followed.

This is a parameter defined on ATTENDEE properties, introduced by RFC

  1. This parameter allows a caldav client to tell the server ‘Don’t do

any scheduling operations’.

If this setting is turned on, any attendees with SCHEDULE-AGENT set to CLIENT will be ignored. This is the desired behavior for a CalDAV server, but if you’re writing an iTip application that doesn’t deal with CalDAV, you may want to ignore this parameter.

Returns:

  • (Boolean)


41
42
43
# File 'lib/tilia/v_object/i_tip/broker.rb', line 41

def schedule_agent_server_rules
  @schedule_agent_server_rules
end

#significant_change_propertiesArray<String>

The broker will try during ‘parseEvent’ figure out whether the change was significant.

It uses a few different ways to do this. One of these ways is seeing if certain properties changed values. This list of specified here.

This list is taken from:

Returns:

  • (Array<String>)


53
54
55
# File 'lib/tilia/v_object/i_tip/broker.rb', line 53

def significant_change_properties
  @significant_change_properties
end

Instance Method Details

#parse_event(calendar, user_href, old_calendar = nil) ⇒ Array

This function parses a VCALENDAR object and figure out if any messages need to be sent.

A VCALENDAR object will be created from the perspective of either an attendee, or an organizer. You must pass a string identifying the current user, so we can figure out who in the list of attendees or the organizer we are sending this message on behalf of.

It’s possible to specify the current user as an array, in case the user has more than one identifying href (such as multiple emails).

It old_calendar is specified, it is assumed that the operation is updating an existing event, which means that we need to look at the differences between events, and potentially send old attendees cancellations, and current attendees updates.

If calendar is null, but old_calendar is specified, we treat the operation as if the user has deleted an event. If the user was an organizer, this means that we need to send cancellation notices to people. If the user was an attendee, we need to make sure that the organizer gets the ‘declined’ message.

Parameters:

  • calendar (VCalendar, String)
  • user_href (String, Array<String>)
  • old_calendar (VCalendar, String, nil) (defaults to: nil)

Returns:

  • (Array)


126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/tilia/v_object/i_tip/broker.rb', line 126

def parse_event(calendar, user_href, old_calendar = nil)
  if old_calendar
    if old_calendar.is_a?(String)
      old_calendar = Tilia::VObject::Reader.read(old_calendar)
    end
    unless old_calendar.key?('VEVENT')
      # We only support events at the moment
      return []
    end

    old_event_info = parse_event_info(old_calendar)
  else
    old_event_info = {
      'organizer'             => nil,
      'significant_change_hash' => '',
      'attendees'             => {}
    }
  end

  user_href = [user_href] unless user_href.is_a?(Array)

  if calendar
    if calendar.is_a?(String)
      calendar = Tilia::VObject::Reader.read(calendar)
    end

    unless calendar.key?('VEVENT')
      # We only support events at the moment
      return []
    end

    event_info = parse_event_info(calendar)
    if (!event_info['attendees'] || event_info['attendees'].empty?) &&
       (!old_event_info['attendees'] || old_event_info['attendees'].empty?)
      # If there were no attendees on either side of the equation,
      # we don't need to do anything.
      return []
    end

    if event_info['organizer'].blank? && old_event_info['organizer'].blank?
      # There was no organizer before or after the change.
      return []
    end

    base_calendar = calendar

    # If the new object didn't have an organizer, the organizer
    # changed the object from a scheduling object to a non-scheduling
    # object. We just copy the info from the old object.
    if event_info['organizer'].blank? && !old_event_info['organizer'].blank?
      event_info['organizer'] = old_event_info['organizer']
      event_info['organizer_name'] = old_event_info['organizer_name']
    end
  else
    # The calendar object got deleted, we need to process this as a
    # cancellation / decline.
    unless old_calendar
      # No old and no new calendar, there's no thing to do.
      return []
    end

    event_info = old_event_info.deep_dup

    if user_href.include?(event_info['organizer'])
      # This is an organizer deleting the event.
      event_info['attendees'] = {}

      # Increasing the sequence, but only if the organizer deleted
      # the event.
      event_info['sequence'] = event_info['sequence'].to_i + 1
    else
      # This is an attendee deleting the event.
      event_info['attendees'].each do |key, attendee|
        next unless user_href.include?(attendee['href'])

        event_info['attendees'][key]['instances'] = {
          'master' => { 'id' => 'master', 'partstat' => 'DECLINED' }
        }
      end
    end

    base_calendar = old_calendar
  end

  if user_href.include?(event_info['organizer'])
    return parse_event_for_organizer(base_calendar, event_info, old_event_info)
  elsif old_calendar
    # We need to figure out if the user is an attendee, but we're only
    # doing so if there's an oldCalendar, because we only want to
    # process updates, not creation of new events.
    event_info['attendees'].each do |_, attendee|
      if user_href.include?(attendee['href'])
        return parse_event_for_attendee(base_calendar, event_info, old_event_info, attendee['href'])
      end
    end
  end

  []
end

#process_message(itip_message, existing_object = nil) ⇒ VCalendar?

This method is used to process an incoming itip message.

Examples:

  1. A user is an attendee to an event. The organizer sends an updated

meeting using a new iTip message with METHOD:REQUEST. This function will process the message and update the attendee’s event accordingly.

  1. The organizer cancelled the event using METHOD:CANCEL. We will update

the users event to state STATUS:CANCELLED.

  1. An attendee sent a reply to an invite using METHOD:REPLY. We can

update the organizers event to update the ATTENDEE with its correct PARTSTAT.

The existing_object is updated in-place. If there is no existing object (because it’s a new invite for example) a new object will be created.

If an existing object does not exist, and the method was CANCEL or REPLY, the message effectively gets ignored, and no ‘existingObject’ will be created.

The updated existing_object is also returned from this function.

If the iTip message was not supported, we will always return false.

Parameters:

  • itip_message (Message)
  • existing_object (VCalendar) (defaults to: nil)

Returns:

  • (VCalendar, nil)


85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/tilia/v_object/i_tip/broker.rb', line 85

def process_message(itip_message, existing_object = nil)
  # We only support events at the moment.
  return false unless itip_message.component == 'VEVENT'

  case itip_message.method
  when 'REQUEST'
    process_message_request(itip_message, existing_object)
  when 'CANCEL'
    process_message_cancel(itip_message, existing_object)
  when 'REPLY'
    process_message_reply(itip_message, existing_object)
  end
end