gcal4ruby 0.1.0

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