gcal4ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,403 @@
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", :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
+
66
+ @attendees
67
+
68
+ #The event start time
69
+ attr_reader :start
70
+ #The event end time
71
+ attr_reader :end
72
+ #The reminder settings for the event, returned as a hash
73
+ attr_reader :reminder
74
+
75
+ #Sets the reminder options for the event. Parameter must be a hash containing one of
76
+ #:hours, :minutes and :days, which are simply the number of each before the event start date you'd like to
77
+ #receive the reminder.
78
+ #
79
+ #:method can be one of the following:
80
+ #- <b>'alert'</b>: causes an alert to appear when a user is viewing the calendar in a browser
81
+ #- <b>'email'</b>: sends the user an email message
82
+ def reminder=(r)
83
+ @reminder = r
84
+ end
85
+
86
+ #Returns the current event's Recurrence information
87
+ def recurrence
88
+ @recurrence
89
+ end
90
+
91
+ #Returns an array of the current attendees
92
+ def attendees
93
+ @attendees
94
+ end
95
+
96
+ #Accepts an array of email address/name pairs for attendees.
97
+ # [{:name => 'Mike Reich', :email => 'mike@seabourneconsulting.com'}]
98
+ #The email address is requried, but the name is optional
99
+ def attendees=(a)
100
+ if a.is_a?(Array)
101
+ @attendees = a
102
+ else
103
+ raise "Attendees must be an Array of email/name hash pairs"
104
+ end
105
+ end
106
+
107
+ #Sets the event's recurrence information to a Recurrence object. Returns the recurrence if successful,
108
+ #false otherwise
109
+ def recurrence=(r)
110
+ if r.is_a?(Recurrence)
111
+ r.event = self
112
+ @recurrence = r
113
+ else
114
+ return false
115
+ end
116
+ end
117
+
118
+ #Returns a duplicate of the current event as a new Event object
119
+ def copy()
120
+ e = Event.new()
121
+ e.load(to_xml)
122
+ e.calendar = @calendar
123
+ return e
124
+ end
125
+
126
+ #Sets the start time of the Event. Must be a Time object or a parsable string representation
127
+ #of a time.
128
+ def start=(str)
129
+ if str.class == String
130
+ @start = Time.parse(str)
131
+ elsif str.class == Time
132
+ @start = str
133
+ else
134
+ raise "Start Time must be either Time or String"
135
+ end
136
+ end
137
+
138
+ #Sets the end time of the Event. Must be a Time object or a parsable string representation
139
+ #of a time.
140
+ def end=(str)
141
+ if str.class == String
142
+ @end = Time.parse(str)
143
+ elsif str.class == Time
144
+ @end = str
145
+ else
146
+ raise "End Time must be either Time or String"
147
+ end
148
+ end
149
+
150
+ #Deletes the event from the Google Calendar Service. All values are cleared.
151
+ def delete
152
+ if @exists
153
+ if @calendar.service.send_delete(@edit_feed, {"If-Match" => @etag})
154
+ @exists = false
155
+ @deleted = true
156
+ @title = nil
157
+ @content = nil
158
+ @id = nil
159
+ @start = nil
160
+ @end = nil
161
+ @transparency = nil
162
+ @status = nil
163
+ @where = nil
164
+ return true
165
+ else
166
+ return false
167
+ end
168
+ else
169
+ return false
170
+ end
171
+ end
172
+
173
+ #Creates a new Event. Accepts a valid Calendar object.
174
+ def initialize(calendar)
175
+ super()
176
+ @xml = EVENT_XML
177
+ @calendar = calendar
178
+ @title = nil
179
+ @content = nil
180
+ @start = nil
181
+ @end = nil
182
+ @where = nil
183
+ @transparency = "http://schemas.google.com/g/2005#event.opaque"
184
+ @status = "http://schemas.google.com/g/2005#event.confirmed"
185
+ @attendees = []
186
+ @reminder = nil
187
+ end
188
+
189
+ #If the event does not exist on the Google Calendar service, save creates it. Otherwise
190
+ #updates the existing event data. Returns true on success, false otherwise.
191
+ def save
192
+ if @deleted
193
+ return false
194
+ end
195
+ if @exists
196
+ ret = @calendar.service.send_put(@edit_feed, to_xml, {'Content-Type' => 'application/atom+xml', "If-Match" => @etag})
197
+ else
198
+ ret = @calendar.service.send_post(@calendar.event_feed, to_xml, {'Content-Type' => 'application/atom+xml'})
199
+ end
200
+ if !@exists
201
+ if load(ret.read_body)
202
+ return true
203
+ else
204
+ raise EventSaveFailed
205
+ end
206
+ end
207
+ return true
208
+ end
209
+
210
+ #Returns an XML representation of the event.
211
+ def to_xml()
212
+ xml = REXML::Document.new(@xml)
213
+ xml.root.elements.each(){}.map do |ele|
214
+ case ele.name
215
+ when 'id'
216
+ ele.text = @id
217
+ when "title"
218
+ ele.text = @title
219
+ when "content"
220
+ ele.text = @content
221
+ when "when"
222
+ if not @recurrence
223
+ ele.attributes["startTime"] = @start.xmlschema
224
+ ele.attributes["endTime"] = @end.xmlschema
225
+ set_reminder(ele)
226
+ else
227
+ if not @reminder
228
+ xml.root.delete_element("/entry/gd:when")
229
+ xml.root.add_element("gd:recurrence").text = @recurrence.to_s
230
+ else
231
+ ele.delete_attribute('startTime')
232
+ ele.delete_attribute('startTime')
233
+ set_reminder(ele)
234
+ end
235
+ end
236
+ when "eventStatus"
237
+ ele.attributes["value"] = case @status
238
+ when :confirmed
239
+ "http://schemas.google.com/g/2005#event.confirmed"
240
+ when :tentative
241
+ "http://schemas.google.com/g/2005#event.tentative"
242
+ when :cancelled
243
+ "http://schemas.google.com/g/2005#event.canceled"
244
+ else
245
+ "http://schemas.google.com/g/2005#event.confirmed"
246
+ end
247
+ when "transparency"
248
+ ele.attributes["value"] = case @transparency
249
+ when :free
250
+ "http://schemas.google.com/g/2005#event.transparent"
251
+ when :busy
252
+ "http://schemas.google.com/g/2005#event.opaque"
253
+ else
254
+ "http://schemas.google.com/g/2005#event.opaque"
255
+ end
256
+ when "where"
257
+ ele.attributes["valueString"] = @where
258
+ end
259
+ end
260
+ if not @attendees.empty?
261
+ @attendees.each do |a|
262
+ xml.root.add_element("gd:who", {"email" => a[:email], "valueString" => a[:name], "rel" => "http://schemas.google.com/g/2005#event.attendee"})
263
+ end
264
+ end
265
+ xml.to_s
266
+ end
267
+
268
+ #Loads the event info from an XML string.
269
+ def load(string)
270
+ @xml = string
271
+ @exists = true
272
+ xml = REXML::Document.new(string)
273
+ @etag = xml.root.attributes['etag']
274
+ xml.root.elements.each(){}.map do |ele|
275
+ case ele.name
276
+ when 'id'
277
+ @id, @edit_feed = ele.text
278
+ when 'title'
279
+ @title = ele.text
280
+ when 'content'
281
+ @content = ele.text
282
+ when "when"
283
+ @start = Time.parse(ele.attributes['startTime'])
284
+ @end = Time.parse(ele.attributes['endTime'])
285
+ ele.elements.each("gd:reminder") do |r|
286
+ @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'] : ''}
287
+ end
288
+ when "where"
289
+ @where = ele.attributes['valueString']
290
+ when "link"
291
+ if ele.attributes['rel'] == 'edit'
292
+ @edit_feed = ele.attributes['href']
293
+ end
294
+ when "who"
295
+ if ele.attributes['rel'] == "http://schemas.google.com/g/2005#event.attendee"
296
+ n = {}
297
+ ele.attributes.each do |name, value|
298
+ case name
299
+ when "email"
300
+ n[:email] = value
301
+ when "valueString"
302
+ n[:name] = value
303
+ end
304
+ end
305
+ @attendees << n
306
+ end
307
+ when "eventStatus"
308
+ case ele.attributes["value"]
309
+ when "http://schemas.google.com/g/2005#event.confirmed"
310
+ @status = :confirmed
311
+ when "http://schemas.google.com/g/2005#event.tentative"
312
+ @status = :tentative
313
+ when "http://schemas.google.com/g/2005#event.confirmed"
314
+ @status = :cancelled
315
+ end
316
+ when "transparency"
317
+ case ele.attributes["value"]
318
+ when "http://schemas.google.com/g/2005#event.transparent"
319
+ @transparency = :free
320
+ when "http://schemas.google.com/g/2005#event.opaque"
321
+ @transparency = :busy
322
+ end
323
+ end
324
+ end
325
+ end
326
+
327
+ #Reloads the event data from the Google Calendar Service. Returns true if successful,
328
+ #false otherwise.
329
+ def reload
330
+ t = Event.find(@calendar, @id, :first)
331
+ if t
332
+ if load(t.xml)
333
+ return true
334
+ else
335
+ return false
336
+ end
337
+ else
338
+ return false
339
+ end
340
+ end
341
+
342
+ #Finds the event that matches search_term in title or description full text search. The scope parameter can
343
+ #be either :all to return an array of all matches, or :first to return the first match as an Event.
344
+ def self.find(calendar, search_term, scope = :all)
345
+ events = calendar.service.send_get("http://www.google.com/calendar/feeds/#{calendar.id}/private/full?q="+CGI.escape(search_term))
346
+ ret = []
347
+ REXML::Document.new(events.read_body).root.elements.each("entry"){}.map do |entry|
348
+ entry.attributes["xmlns:gCal"] = "http://schemas.google.com/gCal/2005"
349
+ entry.attributes["xmlns:gd"] = "http://schemas.google.com/g/2005"
350
+ entry.attributes["xmlns"] = "http://www.w3.org/2005/Atom"
351
+ event = Event.new(calendar)
352
+ event.load("<?xml version='1.0' encoding='UTF-8'?>#{entry.to_s}")
353
+ ret << event
354
+ end
355
+ if scope == :all
356
+ return ret
357
+ elsif scope == :first
358
+ return ret[0]
359
+ end
360
+ return false
361
+ end
362
+
363
+ #Returns true if the event exists on the Google Calendar Service.
364
+ def exists?
365
+ return @exists
366
+ end
367
+
368
+ private
369
+ @exists = false
370
+ @calendar = nil
371
+ @xml = nil
372
+ @etag = nil
373
+ @recurrence = nil
374
+ @deleted = false
375
+ @edit_feed = ''
376
+
377
+ def set_reminder(ele)
378
+ ele.delete_element("gd:reminder")
379
+ if @reminder
380
+ e = ele.add_element("gd:reminder")
381
+ used = false
382
+ if @reminder[:minutes]
383
+ e.attributes['minutes'] = @reminder[:minutes]
384
+ used = true
385
+ elsif @reminder[:hours] and not used
386
+ e.attributes['hours'] = @reminder[:hours]
387
+ used = true
388
+ elsif @reminder[:days] and not used
389
+ e.attributes['days'] = @reminder[:days]
390
+ end
391
+ if @reminder[:method]
392
+ e.attributes['method'] = @reminder[:method]
393
+ else
394
+ e.attributes['method'] = 'email'
395
+ end
396
+ else
397
+ ele.delete_element("gd:reminder")
398
+ end
399
+ end
400
+ end
401
+
402
+ end
403
+
@@ -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
@@ -0,0 +1,61 @@
1
+ require 'gcal4ruby/base'
2
+ require 'gcal4ruby/calendar'
3
+
4
+ module GCal4Ruby
5
+
6
+ #The service class is the main handler for all direct interactions with the
7
+ #Google Calendar API. A service represents a single user account. Each user
8
+ #account can have multiple calendars, so you'll need to find the calendar you
9
+ #want from the service, using the Calendar#find class method.
10
+ #=Usage
11
+ #
12
+ #1. Authenticate
13
+ # service = Service.new
14
+ # service.authenticate("user@gmail.com", "password")
15
+ #
16
+ #2. Get Calendar List
17
+ # calendars = service.calendars
18
+ #
19
+
20
+ class Service < Base
21
+ #Convenience attribute contains the currently authenticated account name
22
+ attr_reader :account
23
+
24
+ # The token returned by the Google servers, used to authorize all subsequent messages
25
+ attr_reader :auth_token
26
+
27
+ # The authenticate method passes the username and password to google servers.
28
+ # If authentication succeeds, returns true, otherwise raises the AuthenticationFailed error.
29
+ def authenticate(username, password)
30
+ ret = nil
31
+ ret = send_post(AUTH_URL, "Email=#{username}&Passwd=#{password}&source=GCal4Ruby&service=cl")
32
+ if ret.class == Net::HTTPOK
33
+ @auth_token = ret.read_body.to_a[2].gsub("Auth=", "").strip
34
+ @account = username
35
+ return true
36
+ else
37
+ raise AuthenticationFailed
38
+ end
39
+ end
40
+
41
+ #Returns an array of Calendar objects for each calendar associated with
42
+ #the authenticated account.
43
+ def calendars
44
+ if not @auth_token
45
+ raise NotAuthenticated
46
+ end
47
+ ret = send_get(CALENDAR_LIST_FEED)
48
+ cals = []
49
+ REXML::Document.new(ret.body).root.elements.each("entry"){}.map do |entry|
50
+ entry.attributes["xmlns:gCal"] = "http://schemas.google.com/gCal/2005"
51
+ entry.attributes["xmlns:gd"] = "http://schemas.google.com/g/2005"
52
+ entry.attributes["xmlns"] = "http://www.w3.org/2005/Atom"
53
+ cal = Calendar.new(self)
54
+ cal.load("<?xml version='1.0' encoding='UTF-8'?>#{entry.to_s}")
55
+ cals << cal
56
+ end
57
+ return cals
58
+ end
59
+ end
60
+
61
+ end
data/lib/gcal4ruby.rb ADDED
@@ -0,0 +1 @@
1
+ require "gcal4ruby/service"