intouch-gcal4ruby 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,456 @@
1
+ # Author:: Mike Reich (mike@seabourneconsulting.com)
2
+ # Copyright:: Copyright (C) 2010 Mike Reich
3
+ # License:: GPL v2
4
+ #--
5
+ # Licensed under the General Public License (GPL), Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ #
15
+ # Feel free to use and update, but be sure to contribute your
16
+ # code back to the project and attribute as required by the license.
17
+ #++
18
+
19
+ require 'gcal4ruby/recurrence'
20
+
21
+ module GCal4Ruby
22
+ #The Event Class represents a remote event in calendar
23
+ #
24
+ #=Usage
25
+ #All usages assume a successfully authenticated Service and valid Calendar.
26
+ #1. Create a new Event
27
+ # event = Event.new(service, {:calendar => cal, :title => "Soccer Game", :start => Time.parse("12-06-2009 at 12:30 PM"), :end => Time.parse("12-06-2009 at 1:30 PM"), :where => "Merry Playfields"})
28
+ # event.save
29
+ #
30
+ #2. Find an existing Event by title
31
+ # event = Event.find(service, {:title => "Soccer Game"})
32
+ #
33
+ #3. Find an existing Event by ID
34
+ # event = Event.find(service, {:id => event.id})
35
+ #
36
+ #4. Find all events containing the search term
37
+ # event = Event.find(service, "Soccer Game")
38
+ #
39
+ #5. Find all events on a calendar containing the search term
40
+ # event = Event.find(service, "Soccer Game", {:calendar => cal.id})
41
+ #
42
+ #6. Find events within a date range
43
+ # event = Event.find(service, "Soccer Game", {'start-min' => Time.parse("01/01/2010").utc.xmlschema, 'start-max' => Time.parse("06/01/2010").utc.xmlschema})
44
+ #
45
+ #7. Create a recurring event for every saturday
46
+ # event = Event.new(service)
47
+ # event.title = "Baseball Game"
48
+ # event.calendar = cal
49
+ # event.where = "Municipal Stadium"
50
+ # event.recurrence = Recurrence.new
51
+ # event.recurrence.start_time = Time.parse("06/20/2009 at 4:30 PM")
52
+ # event.recurrence.end_time = Time.parse("06/20/2009 at 6:30 PM")
53
+ # event.recurrence.frequency = {"weekly" => ["SA"]}
54
+ # event.save
55
+ #
56
+ #8. Create an event with a 15 minute email reminder
57
+ # event = Event.new(service)
58
+ # event.calendar = cal
59
+ # event.title = "Dinner with Kate"
60
+ # event.start_time = Time.parse("06/20/2009 at 5 pm")
61
+ # event.end_time = Time.parse("06/20/2009 at 8 pm")
62
+ # event.where = "Luigi's"
63
+ # event.reminder = [{:minutes => 15, :method => 'email'}]
64
+ # event.save
65
+ #
66
+ #9. Create an event with attendees
67
+ # event = Event.new(service)
68
+ # event.calendar = cal
69
+ # event.title = "Dinner with Kate"
70
+ # event.start_time = Time.parse("06/20/2009 at 5 pm")
71
+ # event.end_time = Time.parse("06/20/2009 at 8 pm")
72
+ # event.attendees => {:name => "Kate", :email => "kate@gmail.com"}
73
+ # event.save
74
+ #
75
+ #After an event object has been created or loaded, you can change any of the
76
+ #attributes like you would any other object. Be sure to save the event to write changes
77
+ #to the Google Calendar service.
78
+
79
+ class Event < GData4Ruby::GDataObject
80
+ EVENT_QUERY_FEED = "https://www.google.com/calendar/feeds/default/private/full/"
81
+ EVENT_XML = "<entry xmlns='http://www.w3.org/2005/Atom'
82
+ xmlns:gd='http://schemas.google.com/g/2005'>
83
+ <category scheme='http://schemas.google.com/g/2005#kind'
84
+ term='http://schemas.google.com/g/2005#event'></category>
85
+ <title type='text'></title>
86
+ <content type='text'></content>
87
+ <gd:transparency
88
+ value='http://schemas.google.com/g/2005#event.opaque'>
89
+ </gd:transparency>
90
+ <gd:eventStatus
91
+ value='http://schemas.google.com/g/2005#event.confirmed'>
92
+ </gd:eventStatus>
93
+ <gd:where valueString=''></gd:where>
94
+ <gd:when startTime=''
95
+ endTime=''></gd:when>
96
+ </entry>"
97
+
98
+ STATUS = {:confirmed => "http://schemas.google.com/g/2005#event.confirmed",
99
+ :tentative => "http://schemas.google.com/g/2005#event.tentative",
100
+ :cancelled => "http://schemas.google.com/g/2005#event.canceled"}
101
+
102
+ TRANSPARENCY = {:free => "http://schemas.google.com/g/2005#event.transparent",
103
+ :busy => "http://schemas.google.com/g/2005#event.opaque"}
104
+
105
+ # The URI of a representation of an event feed takes the following form:
106
+ # https://www.google.com/calendar/feeds/userID/visibility/projection
107
+ # Returns a feed with 'private' visibility and 'full' projection.
108
+ # The Calendar ID can be used in place of the User ID to return events
109
+ # from a specific calendar.
110
+ def self.event_feed_uri(user_id)
111
+ "https://www.google.com/calendar/feeds/#{user_id}/private/full"
112
+ end
113
+
114
+ #The content for the event
115
+ attr_accessor :content
116
+ #The location of the event
117
+ attr_accessor :where
118
+ #A flag for whether the event show as :free or :busy
119
+ attr_accessor :transparency
120
+ #A flag indicating the status of the event. Values can be :confirmed, :tentative or :cancelled
121
+ attr_accessor :status
122
+ #Flag indicating whether it is an all day event
123
+ attr_reader :all_day
124
+ #An array of reminders. Each item in the array is a hash representing the event reminder.
125
+ attr_reader :reminder
126
+ #The date the event was last edited
127
+ attr_reader :edited
128
+ #Id of the parent calendar
129
+ attr_reader :calendar_id
130
+
131
+ #Creates a new Event. Accepts a valid Service object and optional attributes hash.
132
+ def initialize(service, attributes = {})
133
+ super(service, attributes)
134
+ @xml = EVENT_XML
135
+ @transparency ||= :busy
136
+ @status ||= :confirmed
137
+ @attendees ||= []
138
+ @all_day ||= false
139
+ @reminder = []
140
+ attributes.each do |key, value|
141
+ self.send("#{key}=", value)
142
+ end
143
+ end
144
+
145
+ #Sets the reminder options for the event. Parameter must be an array of hashes containing a :minutes key with a value of 5 up to 40320 (4 weeks)
146
+ #and a :method key of with a value of one the following:
147
+ #alert:: causes an alert to appear when a user is viewing the calendar in a browser
148
+ #email:: sends the user an email message
149
+ def reminder=(r)
150
+ @reminder = r
151
+ end
152
+
153
+ #Returns the current event's Recurrence information
154
+ def recurrence
155
+ @recurrence
156
+ end
157
+
158
+ #Returns an array of the current attendees
159
+ def attendees
160
+ @attendees
161
+ end
162
+
163
+ def all_day=(value)
164
+ puts 'all_day value = '+value.to_s if service.debug
165
+ if value.is_a? String
166
+ @all_day = true if value.downcase == 'true'
167
+ @all_day = false if value.downcase == 'false'
168
+ else
169
+ @all_day = value
170
+ end
171
+ puts 'after all_day value = '+@all_day.to_s if service.debug
172
+ @all_day
173
+ end
174
+
175
+ #Accepts an array of email address/name pairs for attendees.
176
+ # [{:name => 'Mike Reich', :email => 'mike@seabourneconsulting.com'}]
177
+ #The email address is requried, but the name is optional
178
+ def attendees=(a)
179
+ raise ArgumentError, "Attendees must be an Array of email/name hash pairs" if not a.is_a?(Array)
180
+ @attendees = a
181
+ end
182
+
183
+ #Sets the event's recurrence information to a Recurrence object. Returns the recurrence if successful,
184
+ #false otherwise
185
+ def recurrence=(r)
186
+ raise ArgumentError, 'Recurrence must be a Recurrence object' if not r.is_a?(Recurrence)
187
+ @recurrence = r
188
+ end
189
+
190
+ #Returns a duplicate of the current event as a new Event object
191
+ def copy()
192
+ e = Event.new(service)
193
+ e.load(to_xml)
194
+ e.calendar = @calendar
195
+ return e
196
+ end
197
+
198
+ #Sets the start time of the Event. Must be a Time object or a parsable string representation
199
+ #of a time.
200
+ def start_time=(str)
201
+ raise ArgumentError, "Start Time must be either Time or String" if not str.is_a?String and not str.is_a?Time
202
+ @start_time = if str.is_a?String
203
+ Time.parse(str)
204
+ elsif str.is_a?Time
205
+ str
206
+ end
207
+ end
208
+
209
+ #Sets the end time of the Event. Must be a Time object or a parsable string representation
210
+ #of a time.
211
+ def end_time=(str)
212
+ raise ArgumentError, "End Time must be either Time or String" if not str.is_a?String and not str.is_a?Time
213
+ @end_time = if str.is_a?String
214
+ Time.parse(str)
215
+ elsif str.is_a?Time
216
+ str
217
+ end
218
+ end
219
+
220
+ #The event start time. If a recurring event, the recurrence start time.
221
+ def start_time
222
+ return @start_time ? @start_time : @recurrence ? @recurrence.start_time : nil
223
+ end
224
+
225
+ #The event end time. If a recurring event, the recurrence end time.
226
+ def end_time
227
+ return @end_time ? @end_time : @recurrence ? @recurrence.end_time : nil
228
+ end
229
+
230
+
231
+ #If the event does not exist on the Google Calendar service, save creates it. Otherwise
232
+ #updates the existing event data. Returns true on success, false otherwise.
233
+ def save
234
+ raise CalendarNotEditable if not calendar.editable
235
+ super
236
+ end
237
+
238
+ #Creates a new event
239
+ def create
240
+ service.send_request(GData4Ruby::Request.new(:post, @parent_calendar.content_uri, to_xml))
241
+ end
242
+
243
+ #Returns an XML representation of the event.
244
+ def to_xml()
245
+ xml = REXML::Document.new(super)
246
+ xml.root.elements.each(){}.map do |ele|
247
+ case ele.name
248
+ when "content"
249
+ ele.text = @content
250
+ when "when"
251
+ if not @recurrence
252
+ puts 'all_day = '+@all_day.to_s if service.debug
253
+ if @all_day
254
+ puts 'saving as all-day event' if service.debug
255
+ else
256
+ puts 'saving as timed event' if service.debug
257
+ end
258
+ ele.attributes["startTime"] = @all_day ? @start_time.strftime("%Y-%m-%d") : @start_time.utc.xmlschema
259
+ ele.attributes["endTime"] = @all_day ? @end_time.strftime("%Y-%m-%d") : @end_time.utc.xmlschema
260
+ set_reminder(ele)
261
+ else
262
+ xml.root.delete_element("/entry/gd:when")
263
+ ele = xml.root.add_element("gd:recurrence")
264
+ ele.text = @recurrence.to_recurrence_string
265
+ set_reminder(ele) if @reminder
266
+ end
267
+ when "eventStatus"
268
+ ele.attributes["value"] = STATUS[@status]
269
+ when "transparency"
270
+ ele.attributes["value"] = TRANSPARENCY[@transparency]
271
+ when "where"
272
+ ele.attributes["valueString"] = @where
273
+ when "recurrence"
274
+ puts 'recurrence element found' if service.debug
275
+ if @recurrence
276
+ puts 'setting recurrence' if service.debug
277
+ ele.text = @recurrence.to_recurrence_string
278
+ else
279
+ puts 'no recurrence, adding when' if service.debug
280
+ w = xml.root.add_element("gd:when")
281
+ xml.root.delete_element("/entry/gd:recurrence")
282
+ w.attributes["startTime"] = @all_day ? @start_time.strftime("%Y-%m-%d") : @start_time.xmlschema
283
+ w.attributes["endTime"] = @all_day ? @end_time.strftime("%Y-%m-%d") : @end_time.xmlschema
284
+ set_reminder(w)
285
+ end
286
+ end
287
+ end
288
+ if not @attendees.empty?
289
+ xml.root.elements.delete_all "gd:who"
290
+ @attendees.each do |a|
291
+ xml.root.add_element("gd:who", {"email" => a[:email], "valueString" => a[:name], "rel" => "http://schemas.google.com/g/2005#event.attendee"})
292
+ end
293
+ end
294
+ xml.to_s
295
+ end
296
+
297
+ #The event's parent calendar
298
+ def calendar
299
+ @parent_calendar = Calendar.find(service, {:id => @calendar_id}) if not @parent_calendar and @calendar_id
300
+ return @parent_calendar
301
+ end
302
+
303
+ #Sets the event's calendar
304
+ def calendar=(p)
305
+ raise ArgumentError, 'Value must be a valid Calendar object' if not p.is_a? Calendar
306
+ @parent_calendar = p
307
+ end
308
+
309
+ #Loads the event info from an XML string.
310
+ def load(string)
311
+ super(string)
312
+ @xml = string
313
+ @exists = true
314
+ xml = REXML::Document.new(string)
315
+ @etag = xml.root.attributes['etag']
316
+ xml.root.elements.each(){}.map do |ele|
317
+ case ele.name
318
+ when 'id'
319
+ @calendar_id, @id = @feed_uri.gsub("http://www.google.com/calendar/feeds/", "").split("/events/")
320
+ @id = "#{@calendar_id}/private/full/#{@id}"
321
+ when 'edited'
322
+ @edited = Time.parse(ele.text)
323
+ when 'content'
324
+ @content = ele.text
325
+ when "when"
326
+ @start_time = Time.parse(ele.attributes['startTime'])
327
+ @end_time = Time.parse(ele.attributes['endTime'])
328
+ @all_day = !ele.attributes['startTime'].include?('T')
329
+ @reminder = []
330
+ ele.elements.each("gd:reminder") do |r|
331
+ rem = {}
332
+ rem[:minutes] = r.attributes['minutes'] if r.attributes['minutes']
333
+ rem[:method] = r.attributes['method'] if r.attributes['method']
334
+ @reminder << rem
335
+ end
336
+ when "where"
337
+ @where = ele.attributes['valueString']
338
+ when "link"
339
+ if ele.attributes['rel'] == 'edit'
340
+ @edit_feed = ele.attributes['href']
341
+ end
342
+ when "who"
343
+ @attendees << {:email => ele.attributes['email'], :name => ele.attributes['valueString'], :role => ele.attributes['rel'].gsub("http://schemas.google.com/g/2005#event.", ""), :status => ele.elements["gd:attendeeStatus"] ? ele.elements["gd:attendeeStatus"].attributes['value'].gsub("http://schemas.google.com/g/2005#event.", "") : ""}
344
+ when "eventStatus"
345
+ @status = ele.attributes["value"].gsub("http://schemas.google.com/g/2005#event.", "").to_sym
346
+ when 'recurrence'
347
+ @recurrence = Recurrence.new(ele.text)
348
+ when "transparency"
349
+ @transparency = case ele.attributes["value"]
350
+ when "http://schemas.google.com/g/2005#event.transparent" then :free
351
+ when "http://schemas.google.com/g/2005#event.opaque" then :busy
352
+ end
353
+ end
354
+ end
355
+ end
356
+
357
+ #Reloads the event data from the Google Calendar Service. Returns true if successful,
358
+ #false otherwise.
359
+ def reload
360
+ return false if not @exists
361
+ t = Event.find(service, {:id => @id})
362
+ if t and load(t.to_xml)
363
+ return true
364
+ end
365
+ return false
366
+ end
367
+
368
+ #Finds an Event based on a text query or by an id. Parameters are:
369
+ #*service*:: A valid Service object to search.
370
+ #*query*:: either a string containing a text query to search by, or a hash containing an +id+ key with an associated id to find, or a +query+ key containint a text query to search for, or a +title+ key containing a title to search. All searches are case insensitive.
371
+ #*args*:: a hash containing optional additional query paramters to use. Limit a search to a single calendar by passing a calendar object as {:calender => calendar} or the calendar id as {:calendar => calendar.id}. See here[http://code.google.com/apis/calendar/data/2.0/developers_guide_protocol.html#RetrievingEvents] and here[http://code.google.com/apis/gdata/docs/2.0/reference.html#Queries] for a full list of possible values. Example:
372
+ # {'max-results' => '100'}
373
+ #If an ID is specified, a single instance of the event is returned if found, otherwise false.
374
+ #If a query term or title text is specified, and array of matching results is returned, or an empty array if nothing
375
+ #was found.
376
+ def self.find(service, query, args = {})
377
+ raise ArgumentError, 'query must be a hash or string' if not query.is_a? Hash and not query.is_a? String
378
+ if query.is_a? Hash and query[:id]
379
+ id = query[:id]
380
+ puts "id passed, finding event by id" if service.debug
381
+ puts "id = "+id if service.debug
382
+ d = service.send_request(GData4Ruby::Request.new(:get, "http://www.google.com/calendar/feeds/"+id, {"If-Not-Match" => "*"}))
383
+ puts d.inspect if service.debug
384
+ if d
385
+ return get_instance(service, d)
386
+ end
387
+ else
388
+ results = []
389
+ if query.is_a?(Hash)
390
+ args["q"] = query[:query] if query[:query]
391
+ args['title'] = query[:title] if query[:title]
392
+ else
393
+ args["q"] = CGI::escape(query) if query != ''
394
+ end
395
+ if args[:calendar]
396
+ feed_uri = args[:calendar].is_a?(Calendar) ? args[:calendar].content_uri : self.event_feed_uri(args[:calendar])
397
+ args.delete(:calendar)
398
+ ret = service.send_request(GData4Ruby::Request.new(:get, feed_uri, nil, nil, args))
399
+ xml = REXML::Document.new(ret.body).root
400
+ xml.elements.each("entry") do |e|
401
+ results << get_instance(service, e)
402
+ end
403
+ else
404
+ service.calendars.each do |cal|
405
+ ret = service.send_request(GData4Ruby::Request.new(:get, cal.content_uri, nil, nil, args))
406
+ xml = REXML::Document.new(ret.body).root
407
+ xml.elements.each("entry") do |e|
408
+ results << get_instance(service, e)
409
+ end
410
+ end
411
+ end
412
+ return results
413
+ end
414
+ return false
415
+ end
416
+
417
+ #Returns true if the event exists on the Google Calendar Service.
418
+ def exists?
419
+ return @exists
420
+ end
421
+
422
+ private
423
+ def set_reminder(ele)
424
+ num = ele.elements.delete_all "gd:reminder"
425
+ puts 'num = '+num.size.to_s if service.debug
426
+ if @reminder
427
+ @reminder.each do |reminder|
428
+ puts 'reminder added' if service.debug
429
+ e = ele.add_element("gd:reminder")
430
+ e.attributes['minutes'] = reminder[:minutes].to_s if reminder[:minutes]
431
+ if reminder[:method]
432
+ e.attributes['method'] = reminder[:method]
433
+ else
434
+ e.attributes['method'] = 'email'
435
+ end
436
+ end
437
+ end
438
+ end
439
+
440
+ def self.get_instance(service, d)
441
+ if d.success?
442
+ xml = REXML::Document.new(d.body).root
443
+ if xml.name == 'feed'
444
+ xml = xml.elements.each("entry"){}[0]
445
+ end
446
+ else
447
+ xml = d
448
+ end
449
+ ele = GData4Ruby::Utils::add_namespaces(xml)
450
+ e = Event.new(service)
451
+ e.load(ele.to_s)
452
+ e
453
+ end
454
+ end
455
+ end
456
+