railsware-gcal4ruby 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,446 @@
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 = "http://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
+ STATUS = {:confirmed => "http://schemas.google.com/g/2005#event.confirmed",
98
+ :tentative => "http://schemas.google.com/g/2005#event.tentative",
99
+ :cancelled => "http://schemas.google.com/g/2005#event.canceled"}
100
+
101
+ TRANSPARENCY = {:free => "http://schemas.google.com/g/2005#event.transparent",
102
+ :busy => "http://schemas.google.com/g/2005#event.opaque"}
103
+
104
+ #The content for the event
105
+ attr_accessor :content
106
+ #The location of the event
107
+ attr_accessor :where
108
+ #A flag for whether the event show as :free or :busy
109
+ attr_accessor :transparency
110
+ #A flag indicating the status of the event. Values can be :confirmed, :tentative or :cancelled
111
+ attr_accessor :status
112
+ #Flag indicating whether it is an all day event
113
+ attr_reader :all_day
114
+ #The reminder settings for the event, returned as a hash
115
+ attr_reader :reminder
116
+ #The date the event was last edited
117
+ attr_reader :edited
118
+ #Id of the parent calendar
119
+ attr_reader :calendar_id
120
+
121
+ #Creates a new Event. Accepts a valid Service object and optional attributes hash.
122
+ def initialize(service, attributes = {})
123
+ super(service, attributes)
124
+ @xml = EVENT_XML
125
+ @transparency ||= :busy
126
+ @status ||= :confirmed
127
+ @attendees ||= []
128
+ @all_day ||= false
129
+ @reminder = []
130
+ attributes.each do |key, value|
131
+ self.send("#{key}=", value)
132
+ end
133
+ end
134
+
135
+ #Sets the reminder options for the event. Parameter must be a hash a :minutes key with a value of 5 up to 40320 (4 weeks)
136
+ #and a :method key of with a value of one the following:
137
+ #alert:: causes an alert to appear when a user is viewing the calendar in a browser
138
+ #email:: sends the user an email message
139
+ def reminder=(r)
140
+ @reminder = r
141
+ end
142
+
143
+ #Returns the current event's Recurrence information
144
+ def recurrence
145
+ @recurrence
146
+ end
147
+
148
+ #Returns an array of the current attendees
149
+ def attendees
150
+ @attendees
151
+ end
152
+
153
+ def all_day=(value)
154
+ puts 'all_day value = '+value.to_s if service.debug
155
+ if value.is_a? String
156
+ @all_day = true if value.downcase == 'true'
157
+ @all_day = false if value.downcase == 'false'
158
+ else
159
+ @all_day = value
160
+ end
161
+ puts 'after all_day value = '+@all_day.to_s if service.debug
162
+ @all_day
163
+ end
164
+
165
+ #Accepts an array of email address/name pairs for attendees.
166
+ # [{:name => 'Mike Reich', :email => 'mike@seabourneconsulting.com'}]
167
+ #The email address is requried, but the name is optional
168
+ def attendees=(a)
169
+ raise ArgumentError, "Attendees must be an Array of email/name hash pairs" if not a.is_a?(Array)
170
+ @attendees = a
171
+ end
172
+
173
+ #Sets the event's recurrence information to a Recurrence object. Returns the recurrence if successful,
174
+ #false otherwise
175
+ def recurrence=(r)
176
+ raise ArgumentError, 'Recurrence must be a Recurrence object' if not r.is_a?(Recurrence)
177
+ @recurrence = r
178
+ end
179
+
180
+ #Returns a duplicate of the current event as a new Event object
181
+ def copy()
182
+ e = Event.new(service)
183
+ e.load(to_xml)
184
+ e.calendar = @calendar
185
+ return e
186
+ end
187
+
188
+ #Sets the start time of the Event. Must be a Time object or a parsable string representation
189
+ #of a time.
190
+ def start_time=(str)
191
+ raise ArgumentError, "Start Time must be either Time or String" if not str.is_a?String and not str.is_a?Time
192
+ @start_time = if str.is_a?String
193
+ Time.parse(str)
194
+ elsif str.is_a?Time
195
+ str
196
+ end
197
+ end
198
+
199
+ #Sets the end time of the Event. Must be a Time object or a parsable string representation
200
+ #of a time.
201
+ def end_time=(str)
202
+ raise ArgumentError, "End Time must be either Time or String" if not str.is_a?String and not str.is_a?Time
203
+ @end_time = if str.is_a?String
204
+ Time.parse(str)
205
+ elsif str.is_a?Time
206
+ str
207
+ end
208
+ end
209
+
210
+ #The event start time. If a recurring event, the recurrence start time.
211
+ def start_time
212
+ return @start_time ? @start_time : @recurrence ? @recurrence .start_time : nil
213
+ end
214
+
215
+ #The event end time. If a recurring event, the recurrence end time.
216
+ def end_time
217
+ return @end_time ? @end_time : @recurrence ? @recurrence.end_time : nil
218
+ end
219
+
220
+
221
+ #If the event does not exist on the Google Calendar service, save creates it. Otherwise
222
+ #updates the existing event data. Returns true on success, false otherwise.
223
+ def save
224
+ raise CalendarNotEditable if not calendar.editable
225
+ super
226
+ end
227
+
228
+ #Creates a new event
229
+ def create
230
+ service.send_request(GData4Ruby::Request.new(:post, @parent_calendar.content_uri, to_xml))
231
+ end
232
+
233
+ #Returns an XML representation of the event.
234
+ def to_xml()
235
+ xml = REXML::Document.new(super)
236
+ xml.root.elements.each(){}.map do |ele|
237
+ case ele.name
238
+ when "content"
239
+ ele.text = @content
240
+ when "when"
241
+ if not @recurrence
242
+ puts 'all_day = '+@all_day.to_s if service.debug
243
+ if @all_day
244
+ puts 'saving as all-day event' if service.debug
245
+ else
246
+ puts 'saving as timed event' if service.debug
247
+ end
248
+ ele.attributes["startTime"] = @all_day ? @start_time.strftime("%Y-%m-%d") : @start_time.utc.xmlschema
249
+ ele.attributes["endTime"] = @all_day ? @end_time.strftime("%Y-%m-%d") : @end_time.utc.xmlschema
250
+ set_reminder(ele)
251
+ else
252
+ xml.root.delete_element("/entry/gd:when")
253
+ ele = xml.root.add_element("gd:recurrence")
254
+ ele.text = @recurrence.to_recurrence_string
255
+ set_reminder(ele) if @reminder
256
+ end
257
+ when "eventStatus"
258
+ ele.attributes["value"] = STATUS[@status]
259
+ when "transparency"
260
+ ele.attributes["value"] = TRANSPARENCY[@transparency]
261
+ when "where"
262
+ ele.attributes["valueString"] = @where
263
+ when "recurrence"
264
+ puts 'recurrence element found' if service.debug
265
+ if @recurrence
266
+ puts 'setting recurrence' if service.debug
267
+ ele.text = @recurrence.to_recurrence_string
268
+ else
269
+ puts 'no recurrence, adding when' if service.debug
270
+ w = xml.root.add_element("gd:when")
271
+ xml.root.delete_element("/entry/gd:recurrence")
272
+ w.attributes["startTime"] = @all_day ? @start_time.strftime("%Y-%m-%d") : @start_time.xmlschema
273
+ w.attributes["endTime"] = @all_day ? @end_time.strftime("%Y-%m-%d") : @end_time.xmlschema
274
+ set_reminder(w)
275
+ end
276
+ end
277
+ end
278
+ if not @attendees.empty?
279
+ xml.root.elements.delete_all "gd:who"
280
+ @attendees.each do |a|
281
+ xml.root.add_element("gd:who", {"email" => a[:email], "valueString" => a[:name], "rel" => "http://schemas.google.com/g/2005#event.attendee"})
282
+ end
283
+ end
284
+ xml.to_s
285
+ end
286
+
287
+ #The event's parent calendar
288
+ def calendar
289
+ @parent_calendar = Calendar.find(service, {:id => @calendar_id}) if not @parent_calendar and @calendar_id
290
+ return @parent_calendar
291
+ end
292
+
293
+ #Sets the event's calendar
294
+ def calendar=(p)
295
+ raise ArgumentError, 'Value must be a valid Calendar object' if not p.is_a? Calendar
296
+ @parent_calendar = p
297
+ end
298
+
299
+ #Loads the event info from an XML string.
300
+ def load(string)
301
+ super(string)
302
+ @xml = string
303
+ @exists = true
304
+ xml = REXML::Document.new(string)
305
+ @etag = xml.root.attributes['etag']
306
+ xml.root.elements.each(){}.map do |ele|
307
+ case ele.name
308
+ when 'id'
309
+ @calendar_id, @id = @feed_uri.gsub("http://www.google.com/calendar/feeds/", "").split("/events/")
310
+ @id = "#{@calendar_id}/private/full/#{@id}"
311
+ when 'edited'
312
+ @edited = Time.parse(ele.text)
313
+ when 'content'
314
+ @content = ele.text
315
+ when "when"
316
+ @start_time = Time.parse(ele.attributes['startTime'])
317
+ @end_time = Time.parse(ele.attributes['endTime'])
318
+ @all_day = !ele.attributes['startTime'].include?('T')
319
+ @reminder = []
320
+ ele.elements.each("gd:reminder") do |r|
321
+ rem = {}
322
+ rem[:minutes] = r.attributes['minutes'] if r.attributes['minutes']
323
+ rem[:method] = r.attributes['method'] if r.attributes['method']
324
+ @reminder << rem
325
+ end
326
+ when "where"
327
+ @where = ele.attributes['valueString']
328
+ when "link"
329
+ if ele.attributes['rel'] == 'edit'
330
+ @edit_feed = ele.attributes['href']
331
+ end
332
+ when "who"
333
+ @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.", "") : ""}
334
+ when "eventStatus"
335
+ @status = ele.attributes["value"].gsub("http://schemas.google.com/g/2005#event.", "").to_sym
336
+ when 'recurrence'
337
+ @recurrence = Recurrence.new(ele.text)
338
+ when "transparency"
339
+ @transparency = case ele.attributes["value"]
340
+ when "http://schemas.google.com/g/2005#event.transparent" then :free
341
+ when "http://schemas.google.com/g/2005#event.opaque" then :busy
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ #Reloads the event data from the Google Calendar Service. Returns true if successful,
348
+ #false otherwise.
349
+ def reload
350
+ return false if not @exists
351
+ t = Event.find(service, {:id => @id})
352
+ if t and load(t.to_xml)
353
+ return true
354
+ end
355
+ return false
356
+ end
357
+
358
+ #Finds an Event based on a text query or by an id. Parameters are:
359
+ #*service*:: A valid Service object to search.
360
+ #*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.
361
+ #*args*:: a hash containing optional additional query paramters to use. Limit a search to a single calendar by passing 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:
362
+ # {'max-results' => '100'}
363
+ #If an ID is specified, a single instance of the event is returned if found, otherwise false.
364
+ #If a query term or title text is specified, and array of matching results is returned, or an empty array if nothing
365
+ #was found.
366
+ def self.find(service, query, args = {})
367
+ raise ArgumentError, 'query must be a hash or string' if not query.is_a? Hash and not query.is_a? String
368
+ if query.is_a? Hash and query[:id]
369
+ id = query[:id]
370
+ puts "id passed, finding event by id" if service.debug
371
+ puts "id = "+id if service.debug
372
+ d = service.send_request(GData4Ruby::Request.new(:get, "http://www.google.com/calendar/feeds/"+id, {"If-Not-Match" => "*"}))
373
+ puts d.inspect if service.debug
374
+ if d
375
+ return get_instance(service, d)
376
+ end
377
+ else
378
+ results = []
379
+ if query.is_a?(Hash)
380
+ args["q"] = query[:query] if query[:query]
381
+ args['title'] = query[:title] if query[:title]
382
+ else
383
+ args["q"] = CGI::escape(query) if query != ''
384
+ end
385
+ if args[:calendar]
386
+ cal = Calendar.find(service, {:id => args[:calendar]})
387
+ args.delete(:calendar)
388
+ ret = service.send_request(GData4Ruby::Request.new(:get, cal.content_uri, nil, nil, args))
389
+ xml = REXML::Document.new(ret.body).root
390
+ xml.elements.each("entry") do |e|
391
+ results << get_instance(service, e)
392
+ end
393
+ else
394
+ service.calendars.each do |cal|
395
+ ret = service.send_request(GData4Ruby::Request.new(:get, cal.content_uri, nil, nil, args))
396
+ xml = REXML::Document.new(ret.body).root
397
+ xml.elements.each("entry") do |e|
398
+ results << get_instance(service, e)
399
+ end
400
+ end
401
+ end
402
+ return results
403
+ end
404
+ return false
405
+ end
406
+
407
+ #Returns true if the event exists on the Google Calendar Service.
408
+ def exists?
409
+ return @exists
410
+ end
411
+
412
+ private
413
+ def set_reminder(ele)
414
+ num = ele.elements.delete_all "gd:reminder"
415
+ puts 'num = '+num.size.to_s if service.debug
416
+ if @reminder
417
+ @reminder.each do |reminder|
418
+ puts 'reminder added' if service.debug
419
+ e = ele.add_element("gd:reminder")
420
+ e.attributes['minutes'] = reminder[:minutes].to_s if reminder[:minutes]
421
+ if reminder[:method]
422
+ e.attributes['method'] = reminder[:method]
423
+ else
424
+ e.attributes['method'] = 'email'
425
+ end
426
+ end
427
+ end
428
+ end
429
+
430
+ def self.get_instance(service, d)
431
+ if d.is_a? Net::HTTPOK
432
+ xml = REXML::Document.new(d.read_body).root
433
+ if xml.name == 'feed'
434
+ xml = xml.elements.each("entry"){}[0]
435
+ end
436
+ else
437
+ xml = d
438
+ end
439
+ ele = GData4Ruby::Utils::add_namespaces(xml)
440
+ e = Event.new(service)
441
+ e.load(ele.to_s)
442
+ e
443
+ end
444
+ end
445
+ end
446
+