cs210-gcal4ruby 0.5.8.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.is_a? Net::HTTPOK
442
+ xml = REXML::Document.new(d.read_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
+