railsware-gcal4ruby 0.5.5

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,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
+