h13ronim-gcal4ruby 0.2.6

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,484 @@
1
+ require 'gcal4ruby/recurrence'
2
+
3
+ module GCal4Ruby
4
+ #The Event Class represents a remote event in calendar.
5
+ #
6
+ #=Usage
7
+ #All usages assume a successfully authenticated Service and valid Calendar.
8
+ #1. Create a new Event
9
+ # event = Event.new(calendar)
10
+ # event.title = "Soccer Game"
11
+ # event.start = Time.parse("12-06-2009 at 12:30 PM")
12
+ # event.end = Time.parse("12-06-2009 at 1:30 PM")
13
+ # event.where = "Merry Playfields"
14
+ # event.save
15
+ #
16
+ #2. Find an existing Event
17
+ # event = Event.find(cal, "Soccer Game", {:scope => :first})
18
+ #
19
+ #3. Find all events containing the search term
20
+ # event = Event.find(cal, "Soccer Game")
21
+ #
22
+ #4. Create a recurring event for every saturday
23
+ # event = Event.new(calendar)
24
+ # event.title = "Baseball Game"
25
+ # event.where = "Municipal Stadium"
26
+ # event.recurrence = Recurrence.new
27
+ # event.recurrence.start = Time.parse("13-06-2009 at 4:30 PM")
28
+ # event.recurrence.end = Time.parse("13-06-2009 at 6:30 PM")
29
+ # event.recurrence.frequency = {"weekly" => ["SA"]}
30
+ # event.save
31
+ #
32
+ #5. Create an event with a 15 minute email reminder
33
+ # event = Event.new(calendar)
34
+ # event.title = "Dinner with Kate"
35
+ # event.start = Time.parse("20-06-2009 at 5 pm")
36
+ # event.end = Time.parse("20-06-209 at 8 pm")
37
+ # event.where = "Luigi's"
38
+ # event.reminder = {:minutes => 15, :method => 'email'}
39
+ # event.save
40
+ #
41
+ #6. Create an event with attendees
42
+ # event = Event.new(calendar)
43
+ # event.title = "Dinner with Kate"
44
+ # event.start = Time.parse("20-06-2009 at 5 pm")
45
+ # event.end = Time.parse("20-06-209 at 8 pm")
46
+ # event.attendees => {:name => "Kate", :email => "kate@gmail.com"}
47
+ # event.save
48
+ #
49
+ #After an event object has been created or loaded, you can change any of the
50
+ #attributes like you would any other object. Be sure to save the event to write changes
51
+ #to the Google Calendar service.
52
+ class Event
53
+ #The event title
54
+ attr_accessor :title
55
+ #The content for the event
56
+ attr_accessor :content
57
+ #The location of the event
58
+ attr_accessor :where
59
+ #A flag for whether the event show as :free or :busy
60
+ attr_accessor :transparency
61
+ #A flag indicating the status of the event. Values can be :confirmed, :tentative or :cancelled
62
+ attr_accessor :status
63
+ #The unique event ID
64
+ attr_accessor :id
65
+ #Flag indicating whether it is an all day event
66
+ attr_accessor :all_day
67
+
68
+ @attendees
69
+
70
+ #The event start time
71
+ attr_reader :start
72
+ #The event end time
73
+ attr_reader :end
74
+ #The reminder settings for the event, returned as a hash
75
+ attr_reader :reminder
76
+ #The date the event was created
77
+ attr_reader :published
78
+ #The date the event was last updated
79
+ attr_reader :updated
80
+ #The date the event was last edited
81
+ attr_reader :edited
82
+
83
+ #Sets the reminder options for the event. Parameter must be a hash containing one of
84
+ #:hours, :minutes and :days, which are simply the number of each before the event start date you'd like to
85
+ #receive the reminder.
86
+ #
87
+ #:method can be one of the following:
88
+ #- <b>'alert'</b>: causes an alert to appear when a user is viewing the calendar in a browser
89
+ #- <b>'email'</b>: sends the user an email message
90
+ def reminder=(r)
91
+ @reminder = r
92
+ end
93
+
94
+ #Returns the current event's Recurrence information
95
+ def recurrence
96
+ @recurrence
97
+ end
98
+
99
+ #Returns an array of the current attendees
100
+ def attendees
101
+ @attendees
102
+ end
103
+
104
+ #Accepts an array of email address/name pairs for attendees.
105
+ # [{:name => 'Mike Reich', :email => 'mike@seabourneconsulting.com'}]
106
+ #The email address is requried, but the name is optional
107
+ def attendees=(a)
108
+ if a.is_a?(Array)
109
+ @attendees = a
110
+ else
111
+ raise "Attendees must be an Array of email/name hash pairs"
112
+ end
113
+ end
114
+
115
+ #Sets the event's recurrence information to a Recurrence object. Returns the recurrence if successful,
116
+ #false otherwise
117
+ def recurrence=(r)
118
+ if r.is_a?(Recurrence)
119
+ r.event = self
120
+ @recurrence = r
121
+ else
122
+ return false
123
+ end
124
+ end
125
+
126
+ #Returns a duplicate of the current event as a new Event object
127
+ def copy()
128
+ e = Event.new()
129
+ e.load(to_xml)
130
+ e.calendar = @calendar
131
+ return e
132
+ end
133
+
134
+ #Sets the start time of the Event. Must be a Time object or a parsable string representation
135
+ #of a time.
136
+ def start=(str)
137
+ if str.is_a?String
138
+ @start = Time.parse(str)
139
+ elsif str.is_a?Time
140
+ @start = str
141
+ else
142
+ raise "Start Time must be either Time or String"
143
+ end
144
+ end
145
+
146
+ #Sets the end time of the Event. Must be a Time object or a parsable string representation
147
+ #of a time.
148
+ def end=(str)
149
+ if str.is_a?String
150
+ @end = Time.parse(str)
151
+ elsif str.is_a?Time
152
+ @end = str
153
+ else
154
+ raise "End Time must be either Time or String"
155
+ end
156
+ end
157
+
158
+ #Deletes the event from the Google Calendar Service. All values are cleared.
159
+ def delete
160
+ if @exists
161
+ if @calendar.service.send_delete(@edit_feed, {"If-Match" => @etag})
162
+ @exists = false
163
+ @deleted = true
164
+ @title = nil
165
+ @content = nil
166
+ @id = nil
167
+ @start = nil
168
+ @end = nil
169
+ @transparency = nil
170
+ @status = nil
171
+ @where = nil
172
+ return true
173
+ else
174
+ return false
175
+ end
176
+ else
177
+ return false
178
+ end
179
+ end
180
+
181
+ #Creates a new Event. Accepts a valid Calendar object.
182
+ def initialize(calendar)
183
+ if not calendar.editable
184
+ raise CalendarNotEditable
185
+ end
186
+ super()
187
+ @xml = EVENT_XML
188
+ @calendar = calendar
189
+ @title = nil
190
+ @content = nil
191
+ @start = nil
192
+ @end = nil
193
+ @where = nil
194
+ @transparency = "http://schemas.google.com/g/2005#event.opaque"
195
+ @status = "http://schemas.google.com/g/2005#event.confirmed"
196
+ @attendees = []
197
+ @reminder = nil
198
+ @all_day = false
199
+ end
200
+
201
+ #If the event does not exist on the Google Calendar service, save creates it. Otherwise
202
+ #updates the existing event data. Returns true on success, false otherwise.
203
+ def save
204
+ if @deleted
205
+ return false
206
+ end
207
+ if @exists
208
+ ret = @calendar.service.send_put(@edit_feed, to_xml, {'Content-Type' => 'application/atom+xml', "If-Match" => @etag})
209
+ else
210
+ ret = @calendar.service.send_post(@calendar.event_feed, to_xml, {'Content-Type' => 'application/atom+xml'})
211
+ end
212
+ if !@exists
213
+ if load(ret.read_body)
214
+ return true
215
+ else
216
+ raise EventSaveFailed
217
+ end
218
+ end
219
+ reload
220
+ return true
221
+ end
222
+
223
+ #Returns an XML representation of the event.
224
+ def to_xml()
225
+ xml = REXML::Document.new(@xml)
226
+ xml.root.elements.each(){}.map do |ele|
227
+ case ele.name
228
+ when 'id'
229
+ ele.text = @id
230
+ when "title"
231
+ ele.text = @title
232
+ when "content"
233
+ ele.text = @content
234
+ when "when"
235
+ if not @recurrence
236
+ ele.attributes["startTime"] = @all_day ? @start.strftime("%Y-%m-%d") : @start.xmlschema
237
+ ele.attributes["endTime"] = @all_day ? @end.strftime("%Y-%m-%d") : @end.xmlschema
238
+ set_reminder(ele)
239
+ else
240
+ if not @reminder
241
+ xml.root.delete_element("/entry/gd:when")
242
+ xml.root.add_element("gd:recurrence").text = @recurrence.to_s
243
+ else
244
+ ele.delete_attribute('startTime')
245
+ ele.delete_attribute('endTime')
246
+ set_reminder(ele)
247
+ end
248
+ end
249
+ when "eventStatus"
250
+ ele.attributes["value"] = case @status
251
+ when :confirmed
252
+ "http://schemas.google.com/g/2005#event.confirmed"
253
+ when :tentative
254
+ "http://schemas.google.com/g/2005#event.tentative"
255
+ when :cancelled
256
+ "http://schemas.google.com/g/2005#event.canceled"
257
+ else
258
+ "http://schemas.google.com/g/2005#event.confirmed"
259
+ end
260
+ when "transparency"
261
+ ele.attributes["value"] = case @transparency
262
+ when :free
263
+ "http://schemas.google.com/g/2005#event.transparent"
264
+ when :busy
265
+ "http://schemas.google.com/g/2005#event.opaque"
266
+ else
267
+ "http://schemas.google.com/g/2005#event.opaque"
268
+ end
269
+ when "where"
270
+ ele.attributes["valueString"] = @where
271
+ end
272
+ end
273
+ if not @attendees.empty?
274
+ @attendees.each do |a|
275
+ xml.root.add_element("gd:who", {"email" => a[:email], "valueString" => a[:name], "rel" => "http://schemas.google.com/g/2005#event.attendee"})
276
+ end
277
+ end
278
+ xml.to_s
279
+ end
280
+
281
+ #Loads the event info from an XML string.
282
+ def load(string)
283
+ @xml = string
284
+ @exists = true
285
+ xml = REXML::Document.new(string)
286
+ @etag = xml.root.attributes['etag']
287
+ xml.root.elements.each(){}.map do |ele|
288
+ case ele.name
289
+ when 'updated'
290
+ @updated = ele.text
291
+ when 'published'
292
+ @published = ele.text
293
+ when 'edited'
294
+ @edited = ele.text
295
+ when 'id'
296
+ @id, @edit_feed = ele.text
297
+ when 'title'
298
+ @title = ele.text
299
+ when 'content'
300
+ @content = ele.text
301
+ when "when"
302
+ @start = Time.parse(ele.attributes['startTime'])
303
+ @end = Time.parse(ele.attributes['endTime'])
304
+ ele.elements.each("gd:reminder") do |r|
305
+ @reminder = {:minutes => r.attributes['minutes'] ? r.attributes['minutes'] : 0, :hours => r.attributes['hours'] ? r.attributes['hours'] : 0, :days => r.attributes['days'] ? r.attributes['days'] : 0, :method => r.attributes['method'] ? r.attributes['method'] : ''}
306
+ end
307
+ when "where"
308
+ @where = ele.attributes['valueString']
309
+ when "link"
310
+ if ele.attributes['rel'] == 'edit'
311
+ @edit_feed = ele.attributes['href']
312
+ end
313
+ when "who"
314
+ if ele.attributes['rel'] == "http://schemas.google.com/g/2005#event.attendee"
315
+ n = {}
316
+ ele.attributes.each do |name, value|
317
+ case name
318
+ when "email"
319
+ n[:email] = value
320
+ when "valueString"
321
+ n[:name] = value
322
+ end
323
+ end
324
+ @attendees << n
325
+ end
326
+ when "eventStatus"
327
+ case ele.attributes["value"]
328
+ when "http://schemas.google.com/g/2005#event.confirmed"
329
+ @status = :confirmed
330
+ when "http://schemas.google.com/g/2005#event.tentative"
331
+ @status = :tentative
332
+ when "http://schemas.google.com/g/2005#event.confirmed"
333
+ @status = :cancelled
334
+ end
335
+ when "transparency"
336
+ case ele.attributes["value"]
337
+ when "http://schemas.google.com/g/2005#event.transparent"
338
+ @transparency = :free
339
+ when "http://schemas.google.com/g/2005#event.opaque"
340
+ @transparency = :busy
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ #Reloads the event data from the Google Calendar Service. Returns true if successful,
347
+ #false otherwise.
348
+ def reload
349
+ t = Event.find(@calendar, @id)
350
+ if t
351
+ if load(t.to_xml)
352
+ return true
353
+ else
354
+ return false
355
+ end
356
+ else
357
+ return false
358
+ end
359
+ end
360
+
361
+ #Finds the event that matches a query term in the event title or description.
362
+ #
363
+ #'query' is a string to perform the search on or an event id.
364
+ #
365
+ #The params hash can contain the following hash values
366
+ #* *scope*: may be :all or :first, indicating whether to return the first record found or an array of all records that match the query. Default is :all.
367
+ #* *range*: a hash including a :start and :end time to constrain the search by
368
+ #* *max_results*: an integer indicating the number of results to return. Default is 25.
369
+ #* *sort_order*: either 'ascending' or 'descending'.
370
+ #* *single_events*: either 'true' to return all recurring events as a single entry, or 'false' to return all recurring events as a unique event for each recurrence.
371
+ #* *ctz*: the timezone to return the event times in
372
+ def self.find(calendar, query = '', params = {})
373
+ query_string = ''
374
+
375
+ begin
376
+ test = URI.parse(query).scheme
377
+ rescue Exception => e
378
+ test = nil
379
+ end
380
+
381
+ if test
382
+ puts "id passed, finding event by id" if calendar.service.debug
383
+ es = calendar.service.send_get("http://www.google.com/calendar/feeds/#{calendar.id}/private/full")
384
+ REXML::Document.new(es.read_body).root.elements.each("entry"){}.map do |entry|
385
+ puts "element = "+entry.name if calendar.service.debug
386
+ id = ''
387
+ entry.elements.each("id") {|v| id = v.text}
388
+ puts "id = #{id}" if calendar.service.debug
389
+ if id == query
390
+ entry.attributes["xmlns:gCal"] = "http://schemas.google.com/gCal/2005"
391
+ entry.attributes["xmlns:gd"] = "http://schemas.google.com/g/2005"
392
+ entry.attributes["xmlns:app"] = "http://www.w3.org/2007/app" #Per http://twitter.com/mgornick
393
+ entry.attributes["xmlns"] = "http://www.w3.org/2005/Atom"
394
+ event = Event.new(calendar)
395
+ event.load("<?xml version='1.0' encoding='UTF-8'?>#{entry.to_s}")
396
+ return event
397
+ end
398
+ end
399
+ end
400
+
401
+
402
+ #parse params hash for values
403
+ range = params[:range] || nil
404
+ max_results = params[:max_results] || nil
405
+ sort_order = params[:sortorder] || nil
406
+ single_events = params[:singleevents] || nil
407
+ timezone = params[:ctz] || nil
408
+
409
+ #set up query string
410
+ query_string += "q=#{CGI.escape(query)}" if query
411
+ if range
412
+ if not range.is_a? Hash or (range.size > 0 and (not range[:start].is_a? Time or not range[:end].is_a? Time))
413
+ raise "The date range must be a hash including the :start and :end date values as Times"
414
+ else
415
+ date_range = ''
416
+ if range.size > 0
417
+ query_string += "&start-min=#{CGI::escape(range[:start].xmlschema)}&start-max=#{CGI::escape(range[:end].xmlschema)}"
418
+ end
419
+ end
420
+ end
421
+ query_string += "&max-results=#{max_results}" if max_results
422
+ query_string += "&sortorder=#{sort_order}" if sort_order
423
+ query_string += "&ctz=#{timezone.gsub(" ", "_")}" if timezone
424
+ query_string += "&singleevents#{single_events}" if single_events
425
+ if query_string
426
+ events = calendar.service.send_get("http://www.google.com/calendar/feeds/#{calendar.id}/private/full?"+query_string)
427
+ ret = []
428
+ REXML::Document.new(events.read_body).root.elements.each("entry"){}.map do |entry|
429
+ entry.attributes["xmlns:gCal"] = "http://schemas.google.com/gCal/2005"
430
+ entry.attributes["xmlns:gd"] = "http://schemas.google.com/g/2005"
431
+ entry.attributes["xmlns:app"] = "http://www.w3.org/2007/app"
432
+ entry.attributes["xmlns"] = "http://www.w3.org/2005/Atom"
433
+ event = Event.new(calendar)
434
+ event.load("<?xml version='1.0' encoding='UTF-8'?>#{entry.to_s}")
435
+ ret << event
436
+ end
437
+ end
438
+ if params[:scope] == :first
439
+ return ret[0]
440
+ else
441
+ return ret
442
+ end
443
+ end
444
+
445
+ #Returns true if the event exists on the Google Calendar Service.
446
+ def exists?
447
+ return @exists
448
+ end
449
+
450
+ private
451
+ @exists = false
452
+ @calendar = nil
453
+ @xml = nil
454
+ @etag = nil
455
+ @recurrence = nil
456
+ @deleted = false
457
+ @edit_feed = ''
458
+
459
+ def set_reminder(ele)
460
+ ele.delete_element("gd:reminder")
461
+ if @reminder
462
+ e = ele.add_element("gd:reminder")
463
+ used = false
464
+ if @reminder[:minutes]
465
+ e.attributes['minutes'] = @reminder[:minutes]
466
+ used = true
467
+ elsif @reminder[:hours] and not used
468
+ e.attributes['hours'] = @reminder[:hours]
469
+ used = true
470
+ elsif @reminder[:days] and not used
471
+ e.attributes['days'] = @reminder[:days]
472
+ end
473
+ if @reminder[:method]
474
+ e.attributes['method'] = @reminder[:method]
475
+ else
476
+ e.attributes['method'] = 'email'
477
+ end
478
+ else
479
+ ele.delete_element("gd:reminder")
480
+ end
481
+ end
482
+ end
483
+ end
484
+
@@ -0,0 +1,164 @@
1
+ class Time
2
+ #Returns a ISO 8601 complete formatted string of the time
3
+ def complete
4
+ self.utc.strftime("%Y%m%dT%H%M%S")
5
+ end
6
+ end
7
+
8
+ module GCal4Ruby
9
+ #The Recurrence class stores information on an Event's recurrence. The class implements
10
+ #the RFC 2445 iCalendar recurrence description.
11
+ class Recurrence
12
+ #The event start date/time
13
+ attr_reader :start
14
+ #The event end date/time
15
+ attr_reader :end
16
+ #the event reference
17
+ attr_reader :event
18
+ #The date until which the event will be repeated
19
+ attr_reader :repeat_until
20
+ #The event frequency
21
+ attr_reader :frequency
22
+ #True if the event is all day (i.e. no start/end time)
23
+ attr_accessor :all_day
24
+
25
+ #Returns a new Recurrence object
26
+ def initialize
27
+ @start = nil
28
+ @end = nil
29
+ @event = nil
30
+ @day_of_week = nil
31
+ @repeat_until = nil
32
+ @frequency = nil
33
+ @all_day = false
34
+ end
35
+
36
+ #Returns a string with the correctly formatted ISO 8601 recurrence rule
37
+ def to_s
38
+
39
+ output = ''
40
+ if @all_day
41
+ output += "DTSTART;VALUE=DATE:#{@start.utc.strftime("%Y%m%d")}\n"
42
+ else
43
+ output += "DTSTART;VALUE=DATE-TIME:#{@start.complete}\n"
44
+ end
45
+ if @all_day
46
+ output += "DTEND;VALUE=DATE:#{@end.utc.strftime("%Y%m%d")}\n"
47
+ else
48
+ output += "DTEND;VALUE=DATE-TIME:#{@end.complete}\n"
49
+ end
50
+ output += "RRULE:"
51
+ if @frequency
52
+ f = 'FREQ='
53
+ i = ''
54
+ by = ''
55
+ @frequency.each do |key, v|
56
+ if v.is_a?(Array)
57
+ if v.size > 0
58
+ value = v.join(",")
59
+ else
60
+ value = nil
61
+ end
62
+ else
63
+ value = v
64
+ end
65
+ f += "#{key.upcase};" if key != 'interval'
66
+ case key.downcase
67
+ when "secondly"
68
+ by += "BYSECOND=#{value};"
69
+ when "minutely"
70
+ by += "BYMINUTE=#{value};"
71
+ when "hourly"
72
+ by += "BYHOUR=#{value};"
73
+ when "weekly"
74
+ by += "BYDAY=#{value};" if value
75
+ when "monthly"
76
+ by += "BYDAY=#{value};"
77
+ when "yearly"
78
+ by += "BYYEARDAY=#{value}"
79
+ when 'interval'
80
+ i += "INTERVAL=#{value};"
81
+ end
82
+ end
83
+ output += f+i+by
84
+ end
85
+ if @repeat_until
86
+ output += ";UNTIL=#{@repeat_until.strftime("%Y%m%d")}"
87
+ end
88
+
89
+ output += "\n"
90
+ end
91
+
92
+ #Sets the start date/time. Must be a Time object.
93
+ def start=(s)
94
+ if not s.is_a?(Time)
95
+ raise RecurrenceValueError, "Start must be a date or a time"
96
+ else
97
+ @start = s
98
+ end
99
+ end
100
+
101
+ #Sets the end Date/Time. Must be a Time object.
102
+ def end=(e)
103
+ if not e.is_a?(Time)
104
+ raise RecurrenceValueError, "End must be a date or a time"
105
+ else
106
+ @end = e
107
+ end
108
+ end
109
+
110
+ #Sets the parent event reference
111
+ def event=(e)
112
+ if not e.is_a?(Event)
113
+ raise RecurrenceValueError, "Event must be an event"
114
+ else
115
+ @event = e
116
+ end
117
+ end
118
+
119
+ #Sets the end date for the recurrence
120
+ def repeat_until=(r)
121
+ if not r.is_a?(Date)
122
+ raise RecurrenceValueError, "Repeat_until must be a date"
123
+ else
124
+ @repeat_until = r
125
+ end
126
+ end
127
+
128
+ #Sets the frequency of the recurrence. Should be a hash with one of
129
+ #"SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY" as the key,
130
+ #and as the value, an array containing zero to n of the following:
131
+ #- *Secondly*: A value between 0 and 59. Causes the event to repeat on that second of each minut.
132
+ #- *Minutely*: A value between 0 and 59. Causes the event to repeat on that minute of every hour.
133
+ #- *Hourly*: A value between 0 and 23. Causes the even to repeat on that hour of every day.
134
+ #- *Daily*: No value needed - will cause the event to repeat every day until the repeat_until date.
135
+ #- *Weekly*: A value of the first two letters of a day of the week. Causes the event to repeat on that day.
136
+ #- *Monthly*: A value of a positive or negative integer (i.e. +1) prepended to a day-of-week string ('TU') to indicate the position of the day within the month. E.g. +1TU would be the first tuesday of the month.
137
+ #- *Yearly*: A value of 1 to 366 indicating the day of the year. May be negative to indicate counting down from the last day of the year.
138
+ #
139
+ #Optionally, you may specific a second hash pair to set the internal the even repeats:
140
+ # "interval" => '2'
141
+ #If the interval is missing, it is assumed to be 1.
142
+ #
143
+ #===Examples
144
+ #Repeat event every Tuesday:
145
+ # frequency = {"Weekly" => ["TU"]}
146
+ #
147
+ #Repeat every first and third Monday of the month
148
+ # frequency = {"Monthly" => ["+1MO", "+3MO"]}
149
+ #
150
+ #Repeat on the last day of every year
151
+ # frequency = {"Yearly" => [366]}
152
+ #
153
+ #Repeat every other week on Friday
154
+ # frequency = {"Weekly" => ["FR"], "interval" => "2"}
155
+
156
+ def frequency=(f)
157
+ if f.is_a?(Hash)
158
+ @frequency = f
159
+ else
160
+ raise RecurrenceValueError, "Frequency must be a hash (see documentation)"
161
+ end
162
+ end
163
+ end
164
+ end