clio-gcal4ruby 0.3.2
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.
- data/CHANGELOG +55 -0
- data/README.markdown +136 -0
- data/lib/gcal4ruby.rb +1 -0
- data/lib/gcal4ruby/base.rb +339 -0
- data/lib/gcal4ruby/calendar.rb +320 -0
- data/lib/gcal4ruby/event.rb +501 -0
- data/lib/gcal4ruby/recurrence.rb +213 -0
- data/lib/gcal4ruby/service.rb +100 -0
- data/test/unit.rb +180 -0
- metadata +78 -0
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'gcal4ruby/event'
|
2
|
+
|
3
|
+
module GCal4Ruby
|
4
|
+
class Calendar
|
5
|
+
GOOGLE_PARAM_NAMES = {
|
6
|
+
:mode => "mode",
|
7
|
+
:height => "height",
|
8
|
+
:width => "width",
|
9
|
+
:bg_color => "bgcolor",
|
10
|
+
:color => "color",
|
11
|
+
:show_title => "showTitle",
|
12
|
+
:show_nav => "showNav",
|
13
|
+
:show_date => "showDate",
|
14
|
+
:show_print => "showPrint",
|
15
|
+
:show_tabs => "showTabs",
|
16
|
+
:show_calendars => "showCalendars",
|
17
|
+
:show_timezone => "showTimezone"
|
18
|
+
}
|
19
|
+
|
20
|
+
IFRAME_DEFAULTS = {
|
21
|
+
:mode => "WEEK",
|
22
|
+
:height => "600",
|
23
|
+
:width => "600",
|
24
|
+
:bg_color => "#FFFFFF",
|
25
|
+
:color => "#2852A3",
|
26
|
+
:show_title => false,
|
27
|
+
:show_nav => true,
|
28
|
+
:show_date => true,
|
29
|
+
:show_print => true,
|
30
|
+
:show_tabs => true,
|
31
|
+
:show_calendars => true,
|
32
|
+
:show_timezone => true
|
33
|
+
}
|
34
|
+
|
35
|
+
CALENDAR_FEED = "http://www.google.com/calendar/feeds/default/owncalendars/full"
|
36
|
+
|
37
|
+
attr_accessor :title, :summary, :hidden, :timezone, :color, :where, :selected
|
38
|
+
attr_reader :service, :id, :event_feed, :editable, :edit_feed
|
39
|
+
|
40
|
+
def initialize(service, attributes = {})
|
41
|
+
super()
|
42
|
+
@service = service
|
43
|
+
attributes.each do |key, value|
|
44
|
+
self.send("#{key}=", value)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @xml ||= CALENDAR_XML
|
48
|
+
# @exists = false
|
49
|
+
# @title ||= ""
|
50
|
+
# @summary ||= ""
|
51
|
+
# @public ||= false
|
52
|
+
# @hidden ||= false
|
53
|
+
# @timezone ||= "America/Los_Angeles"
|
54
|
+
# @color ||= "#2952A3"
|
55
|
+
# @where ||= ""
|
56
|
+
# return true
|
57
|
+
end
|
58
|
+
|
59
|
+
def exists?
|
60
|
+
@exists
|
61
|
+
end
|
62
|
+
|
63
|
+
def public?
|
64
|
+
@public
|
65
|
+
end
|
66
|
+
|
67
|
+
#Returns an array of Event objects corresponding to each event in the calendar.
|
68
|
+
def events(query_hash = nil)
|
69
|
+
url = @event_feed
|
70
|
+
if query_hash
|
71
|
+
url += "?#{query_hash.to_query}"
|
72
|
+
end
|
73
|
+
events = []
|
74
|
+
ret = @service.send_get(url)
|
75
|
+
REXML::Document.new(ret.body).root.elements.each("entry"){}.map do |entry|
|
76
|
+
entry.attributes["xmlns:gCal"] = "http://schemas.google.com/gCal/2005"
|
77
|
+
entry.attributes["xmlns:gd"] = "http://schemas.google.com/g/2005"
|
78
|
+
entry.attributes["xmlns:app"] = "http://www.w3.org/2007/app"
|
79
|
+
entry.attributes["xmlns"] = "http://www.w3.org/2005/Atom"
|
80
|
+
entry.attributes["xmlns:georss"] = "http://www.georss.org/georss"
|
81
|
+
entry.attributes["xmlns:gml"] = "http://www.opengis.net/gml"
|
82
|
+
e = Event.new(self)
|
83
|
+
if e.load(entry.to_s)
|
84
|
+
events << e
|
85
|
+
end
|
86
|
+
end
|
87
|
+
return events
|
88
|
+
end
|
89
|
+
|
90
|
+
def events_since(time)
|
91
|
+
events({ 'updated-min' => time.xmlschema })
|
92
|
+
end
|
93
|
+
|
94
|
+
def public=(param)
|
95
|
+
permissions = param ? 'http://schemas.google.com/gCal/2005#read' : 'none'
|
96
|
+
|
97
|
+
path = "http://www.google.com/calendar/feeds/#{@id}/acl/full/default"
|
98
|
+
request = REXML::Document.new(ACL_XML) # What/Where is ACL_XML???
|
99
|
+
request.root.elements.each() do |ele|
|
100
|
+
if ele.name == 'role'
|
101
|
+
ele.attributes['value'] = permissions
|
102
|
+
end
|
103
|
+
end
|
104
|
+
@public = @service.send_put(path, request.to_s, {"Content-Type" => "application/atom+xml", "Content-Length" => request.length.to_s})
|
105
|
+
end
|
106
|
+
|
107
|
+
#Deletes a calendar. If successful, returns true, otherwise false. If successful, the
|
108
|
+
#calendar object is cleared.
|
109
|
+
def delete
|
110
|
+
if @exists
|
111
|
+
if @service.send_delete(CALENDAR_FEED+"/"+@id)
|
112
|
+
@exists = false
|
113
|
+
@title = nil
|
114
|
+
@summary = nil
|
115
|
+
@public = false
|
116
|
+
@id = nil
|
117
|
+
@hidden = false
|
118
|
+
@timezone = nil
|
119
|
+
@color = nil
|
120
|
+
@where = nil
|
121
|
+
return true
|
122
|
+
else
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
else
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#If the calendar does not exist, creates it, otherwise updates the calendar info. Returns
|
131
|
+
#true if the save is successful, otherwise false.
|
132
|
+
def save
|
133
|
+
if @exists
|
134
|
+
ret = service.send_put(@edit_feed, to_xml(), {'Content-Type' => 'application/atom+xml'})
|
135
|
+
else
|
136
|
+
ret = service.send_post(CALENDAR_FEED, to_xml(), {'Content-Type' => 'application/atom+xml'})
|
137
|
+
end
|
138
|
+
if !@exists
|
139
|
+
if load(ret.read_body)
|
140
|
+
return true
|
141
|
+
else
|
142
|
+
raise CalendarSaveFailed
|
143
|
+
end
|
144
|
+
end
|
145
|
+
return true
|
146
|
+
end
|
147
|
+
|
148
|
+
#Class method for querying the google service for specific calendars. The service parameter
|
149
|
+
#should be an appropriately authenticated Service. The term parameter can be any string. The
|
150
|
+
#scope parameter may be either :all to return an array of matches, or :first to return
|
151
|
+
#the first match as a Calendar object.
|
152
|
+
def self.find(service, query_term=nil, params = {})
|
153
|
+
t = query_term.downcase if query_term
|
154
|
+
cals = service.calendars
|
155
|
+
ret = []
|
156
|
+
cals.each do |cal|
|
157
|
+
title = cal.title || ""
|
158
|
+
summary = cal.summary || ""
|
159
|
+
id = cal.id || ""
|
160
|
+
if id == query_term
|
161
|
+
return cal
|
162
|
+
end
|
163
|
+
if title.downcase.match(t) or summary.downcase.match(t)
|
164
|
+
if params[:scope] == :first
|
165
|
+
return cal
|
166
|
+
else
|
167
|
+
ret << cal
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
ret
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.get(service, id)
|
175
|
+
url = 'http://www.google.com/calendar/feeds/default/allcalendars/full/'+id
|
176
|
+
ret = service.send_get(url)
|
177
|
+
puts "==return=="
|
178
|
+
puts ret.body
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.query(service, query_term)
|
182
|
+
url = 'http://www.google.com/calendar/feeds/default/allcalendars/full'+"?q="+CGI.escape(query_term)
|
183
|
+
ret = service.send_get(url)
|
184
|
+
puts "==return=="
|
185
|
+
puts ret.body
|
186
|
+
end
|
187
|
+
|
188
|
+
#Reloads the calendar objects information from the stored server version. Returns true
|
189
|
+
#if successful, otherwise returns false. Any information not saved will be overwritten.
|
190
|
+
def reload
|
191
|
+
if not @exists
|
192
|
+
return false
|
193
|
+
end
|
194
|
+
t = Calendar.find(service, @id, :first)
|
195
|
+
if t
|
196
|
+
load(t.to_xml)
|
197
|
+
else
|
198
|
+
return false
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
#Returns the xml representation of the Calenar.
|
203
|
+
def to_xml
|
204
|
+
xml = REXML::Document.new(@xml)
|
205
|
+
xml.root.elements.each(){}.map do |ele|
|
206
|
+
case ele.name
|
207
|
+
when "title"
|
208
|
+
ele.text = @title
|
209
|
+
when "summary"
|
210
|
+
ele.text = @summary
|
211
|
+
when "timezone"
|
212
|
+
ele.attributes["value"] = @timezone
|
213
|
+
when "hidden"
|
214
|
+
ele.attributes["value"] = @hidden.to_s
|
215
|
+
when "color"
|
216
|
+
ele.attributes["value"] = @color
|
217
|
+
when "selected"
|
218
|
+
ele.attributes["value"] = @selected.to_s
|
219
|
+
end
|
220
|
+
end
|
221
|
+
xml.to_s
|
222
|
+
end
|
223
|
+
|
224
|
+
#Loads the Calendar with returned data from Google Calendar feed. Returns true if successful.
|
225
|
+
def load(string)
|
226
|
+
@exists = true
|
227
|
+
@xml = string
|
228
|
+
xml = REXML::Document.new(string)
|
229
|
+
xml.root.elements.each(){}.map do |ele|
|
230
|
+
case ele.name
|
231
|
+
when "id"
|
232
|
+
@id = ele.text.gsub("http://www.google.com/calendar/feeds/default/calendars/", "")
|
233
|
+
when 'title'
|
234
|
+
@title = ele.text
|
235
|
+
when 'summary'
|
236
|
+
@summary = ele.text
|
237
|
+
when "color"
|
238
|
+
@color = ele.attributes['value']
|
239
|
+
when 'hidden'
|
240
|
+
@hidden = ele.attributes["value"] == "true" ? true : false
|
241
|
+
when 'timezone'
|
242
|
+
@timezone = ele.attributes["value"]
|
243
|
+
when "selected"
|
244
|
+
@selected = ele.attributes["value"] == "true" ? true : false
|
245
|
+
when "link"
|
246
|
+
href = ele.attributes['href']
|
247
|
+
case ele.attributes['rel']
|
248
|
+
when "edit" then @edit_feed = href
|
249
|
+
when "http://schemas.google.com/gCal/2005#eventFeed" then @event_feed = href
|
250
|
+
when "http://schemas.google.com/acl/2007#accessControlList" then @acl_feed = href
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
if @service.check_public
|
256
|
+
puts "Getting ACL Feed" if @service.debug
|
257
|
+
|
258
|
+
#rescue error on shared calenar ACL list access
|
259
|
+
begin
|
260
|
+
ret = @service.send_get(@acl_feed)
|
261
|
+
rescue Exception => e
|
262
|
+
@public = false
|
263
|
+
@editable = false
|
264
|
+
return true
|
265
|
+
end
|
266
|
+
@editable = true
|
267
|
+
r = REXML::Document.new(ret.read_body)
|
268
|
+
r.root.elements.each("entry") do |ele|
|
269
|
+
ele.elements.each do |e|
|
270
|
+
#puts "e = "+e.to_s if @service.debug
|
271
|
+
#puts "previous element = "+e.previous_element.to_s if @service.debug
|
272
|
+
#added per eruder http://github.com/h13ronim/gcal4ruby/commit/3074ebde33bd3970500f6de992a66c0a4578062a
|
273
|
+
if e.name == 'role' and e.previous_element and e.previous_element.name == 'scope' and e.previous_element.attributes['type'] == 'default'
|
274
|
+
if e.attributes['value'].match('#read')
|
275
|
+
@public = true
|
276
|
+
else
|
277
|
+
@public = false
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
else
|
283
|
+
@public = false
|
284
|
+
@editable = true
|
285
|
+
end
|
286
|
+
return true
|
287
|
+
end
|
288
|
+
|
289
|
+
def to_iframe(params = {})
|
290
|
+
raise "The calendar must exist and be saved before you can use this method." unless self.id
|
291
|
+
GCal4Ruby::Calendar.to_iframe(self.id, params)
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.to_iframe(id, params = {})
|
295
|
+
raise "Calendar ID is required" unless id
|
296
|
+
options = build_options_set(params)
|
297
|
+
url_options = options.join("&")
|
298
|
+
"<iframe src='http://www.google.com/calendar/embed?src=#{id}&#{output}' width='#{options[:width]}' height='#{options[:height]}' frameborder='0' scrolling='no'></iframe>"
|
299
|
+
end
|
300
|
+
|
301
|
+
def build_options_set(params)
|
302
|
+
IFRAME_DEFAULTS.merge(params).collect do |key, value|
|
303
|
+
if IFRAME_DEFAULTS.keys.include?(key)
|
304
|
+
[GOOGLE_PARAM_NAMES[key], raw_value_to_param_value(value)].join("=")
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
|
311
|
+
def raw_value_to_param_value(value)
|
312
|
+
case value
|
313
|
+
when true then "1"
|
314
|
+
when false then "0"
|
315
|
+
else value
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
end
|
@@ -0,0 +1,501 @@
|
|
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", {:scope => :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
|
+
#Flag indicating whether it is an all day event
|
66
|
+
attr_accessor :all_day
|
67
|
+
|
68
|
+
@attendees
|
69
|
+
|
70
|
+
#The event start time
|
71
|
+
attr_reader :start
|
72
|
+
#The event end time
|
73
|
+
attr_reader :end
|
74
|
+
#The reminder settings for the event, returned as a hash
|
75
|
+
attr_reader :reminder
|
76
|
+
#The date the event was created
|
77
|
+
attr_reader :published
|
78
|
+
#The date the event was last updated
|
79
|
+
attr_reader :updated
|
80
|
+
#The date the event was last edited
|
81
|
+
attr_reader :edited
|
82
|
+
|
83
|
+
#Sets the reminder options for the event. Parameter must be a hash containing one of
|
84
|
+
#:hours, :minutes and :days, which are simply the number of each before the event start date you'd like to
|
85
|
+
#receive the reminder.
|
86
|
+
#
|
87
|
+
#:method can be one of the following:
|
88
|
+
#- <b>'alert'</b>: causes an alert to appear when a user is viewing the calendar in a browser
|
89
|
+
#- <b>'email'</b>: sends the user an email message
|
90
|
+
def reminder=(r)
|
91
|
+
@reminder = r
|
92
|
+
end
|
93
|
+
|
94
|
+
#Returns the current event's Recurrence information
|
95
|
+
def recurrence
|
96
|
+
@recurrence
|
97
|
+
end
|
98
|
+
|
99
|
+
#Returns an array of the current attendees
|
100
|
+
def attendees
|
101
|
+
@attendees
|
102
|
+
end
|
103
|
+
|
104
|
+
#Accepts an array of email address/name pairs for attendees.
|
105
|
+
# [{:name => 'Mike Reich', :email => 'mike@seabourneconsulting.com'}]
|
106
|
+
#The email address is requried, but the name is optional
|
107
|
+
def attendees=(a)
|
108
|
+
if a.is_a?(Array)
|
109
|
+
@attendees = a
|
110
|
+
else
|
111
|
+
raise "Attendees must be an Array of email/name hash pairs"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
#Sets the event's recurrence information to a Recurrence object. Returns the recurrence if successful,
|
116
|
+
#false otherwise
|
117
|
+
def recurrence=(r)
|
118
|
+
if r.is_a?(Recurrence) or r.nil?
|
119
|
+
r.event = self unless r.nil?
|
120
|
+
@recurrence = r
|
121
|
+
else
|
122
|
+
return false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
#Returns a duplicate of the current event as a new Event object
|
127
|
+
def copy()
|
128
|
+
e = Event.new()
|
129
|
+
e.load(to_xml)
|
130
|
+
e.calendar = @calendar
|
131
|
+
return e
|
132
|
+
end
|
133
|
+
|
134
|
+
#Sets the start time of the Event. Must be a Time object or a parsable string representation
|
135
|
+
#of a time.
|
136
|
+
def start=(str)
|
137
|
+
if str.is_a?String
|
138
|
+
@start = Time.parse(str)
|
139
|
+
elsif str.is_a?Time
|
140
|
+
@start = str
|
141
|
+
else
|
142
|
+
raise "Start Time must be either Time or String"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
#Sets the end time of the Event. Must be a Time object or a parsable string representation
|
147
|
+
#of a time.
|
148
|
+
def end=(str)
|
149
|
+
if str.is_a?String
|
150
|
+
@end = Time.parse(str)
|
151
|
+
elsif str.is_a?Time
|
152
|
+
@end = str
|
153
|
+
else
|
154
|
+
raise "End Time must be either Time or String"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
#Deletes the event from the Google Calendar Service. All values are cleared.
|
159
|
+
def delete
|
160
|
+
if @exists
|
161
|
+
if @calendar.service.send_delete(@edit_feed, {"If-Match" => @etag})
|
162
|
+
@exists = false
|
163
|
+
@deleted = true
|
164
|
+
@title = nil
|
165
|
+
@content = nil
|
166
|
+
@id = nil
|
167
|
+
@start = nil
|
168
|
+
@end = nil
|
169
|
+
@transparency = nil
|
170
|
+
@status = nil
|
171
|
+
@where = nil
|
172
|
+
return true
|
173
|
+
else
|
174
|
+
return false
|
175
|
+
end
|
176
|
+
else
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
#Creates a new Event. Accepts a valid Calendar object and optional attributes hash.
|
182
|
+
def initialize(calendar, attributes = {})
|
183
|
+
if not calendar.editable
|
184
|
+
raise CalendarNotEditable
|
185
|
+
end
|
186
|
+
super()
|
187
|
+
attributes.each do |key, value|
|
188
|
+
self.send("#{key}=", value)
|
189
|
+
end
|
190
|
+
@xml ||= EVENT_XML
|
191
|
+
@calendar ||= calendar
|
192
|
+
@transparency ||= "http://schemas.google.com/g/2005#event.opaque"
|
193
|
+
@status ||= "http://schemas.google.com/g/2005#event.confirmed"
|
194
|
+
@attendees ||= []
|
195
|
+
@all_day ||= false
|
196
|
+
end
|
197
|
+
|
198
|
+
#If the event does not exist on the Google Calendar service, save creates it. Otherwise
|
199
|
+
#updates the existing event data. Returns true on success, false otherwise.
|
200
|
+
def save
|
201
|
+
if @deleted
|
202
|
+
return false
|
203
|
+
end
|
204
|
+
if @exists
|
205
|
+
ret = @calendar.service.send_put(@edit_feed, to_xml, {'Content-Type' => 'application/atom+xml', "If-Match" => @etag})
|
206
|
+
else
|
207
|
+
ret = @calendar.service.send_post(@calendar.event_feed, to_xml, {'Content-Type' => 'application/atom+xml'})
|
208
|
+
end
|
209
|
+
if !@exists
|
210
|
+
if load(ret.read_body)
|
211
|
+
return true
|
212
|
+
else
|
213
|
+
raise EventSaveFailed
|
214
|
+
end
|
215
|
+
end
|
216
|
+
reload
|
217
|
+
return true
|
218
|
+
end
|
219
|
+
|
220
|
+
#Returns an XML representation of the event.
|
221
|
+
def to_xml()
|
222
|
+
xml = REXML::Document.new(@xml)
|
223
|
+
xml.root.elements.each(){}.map do |ele|
|
224
|
+
case ele.name
|
225
|
+
when 'id'
|
226
|
+
ele.text = @id
|
227
|
+
when "title"
|
228
|
+
ele.text = @title
|
229
|
+
when "content"
|
230
|
+
ele.text = @content
|
231
|
+
when "when"
|
232
|
+
if not @recurrence
|
233
|
+
ele.attributes["startTime"] = @all_day ? @start.strftime("%Y-%m-%d") : @start.xmlschema
|
234
|
+
ele.attributes["endTime"] = @all_day ? @end.strftime("%Y-%m-%d") : @end.xmlschema
|
235
|
+
set_reminder(ele)
|
236
|
+
else
|
237
|
+
if not @reminder
|
238
|
+
xml.root.delete_element("/entry/gd:when")
|
239
|
+
xml.root.add_element("gd:recurrence").text = @recurrence.to_s
|
240
|
+
else
|
241
|
+
ele.delete_attribute('startTime')
|
242
|
+
ele.delete_attribute('endTime')
|
243
|
+
set_reminder(ele)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
when "eventStatus"
|
247
|
+
ele.attributes["value"] = case @status
|
248
|
+
when :confirmed
|
249
|
+
"http://schemas.google.com/g/2005#event.confirmed"
|
250
|
+
when :tentative
|
251
|
+
"http://schemas.google.com/g/2005#event.tentative"
|
252
|
+
when :cancelled
|
253
|
+
"http://schemas.google.com/g/2005#event.canceled"
|
254
|
+
else
|
255
|
+
"http://schemas.google.com/g/2005#event.confirmed"
|
256
|
+
end
|
257
|
+
when "transparency"
|
258
|
+
ele.attributes["value"] = case @transparency
|
259
|
+
when :free
|
260
|
+
"http://schemas.google.com/g/2005#event.transparent"
|
261
|
+
when :busy
|
262
|
+
"http://schemas.google.com/g/2005#event.opaque"
|
263
|
+
else
|
264
|
+
"http://schemas.google.com/g/2005#event.opaque"
|
265
|
+
end
|
266
|
+
when "where"
|
267
|
+
ele.attributes["valueString"] = @where
|
268
|
+
when "recurrence"
|
269
|
+
puts 'recurrence element found' if @calendar.service.debug
|
270
|
+
if @recurrence
|
271
|
+
puts 'setting recurrence' if @calendar.service.debug
|
272
|
+
ele.text = @recurrence.to_s
|
273
|
+
else
|
274
|
+
puts 'no recurrence, adding when' if @calendar.service.debug
|
275
|
+
w = xml.root.add_element("gd:when")
|
276
|
+
xml.root.delete_element("/entry/gd:recurrence")
|
277
|
+
w.attributes["startTime"] = @all_day ? @start.strftime("%Y-%m-%d") : @start.xmlschema
|
278
|
+
w.attributes["endTime"] = @all_day ? @end.strftime("%Y-%m-%d") : @end.xmlschema
|
279
|
+
set_reminder(w)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
if not @attendees.empty?
|
284
|
+
@attendees.each do |a|
|
285
|
+
xml.root.add_element("gd:who", {"email" => a[:email], "valueString" => a[:name], "rel" => "http://schemas.google.com/g/2005#event.attendee"})
|
286
|
+
end
|
287
|
+
end
|
288
|
+
xml.to_s
|
289
|
+
end
|
290
|
+
|
291
|
+
#Loads the event info from an XML string.
|
292
|
+
def load(string)
|
293
|
+
@xml = string
|
294
|
+
@exists = true
|
295
|
+
xml = REXML::Document.new(string)
|
296
|
+
@etag = xml.root.attributes['etag']
|
297
|
+
xml.root.elements.each(){}.map do |ele|
|
298
|
+
case ele.name
|
299
|
+
when 'updated'
|
300
|
+
@updated = ele.text
|
301
|
+
when 'published'
|
302
|
+
@published = ele.text
|
303
|
+
when 'edited'
|
304
|
+
@edited = ele.text
|
305
|
+
when 'id'
|
306
|
+
@id, @edit_feed = ele.text
|
307
|
+
when 'title'
|
308
|
+
@title = ele.text
|
309
|
+
when 'content'
|
310
|
+
@content = ele.text
|
311
|
+
when "when"
|
312
|
+
@start = Time.parse(ele.attributes['startTime'])
|
313
|
+
@end = Time.parse(ele.attributes['endTime'])
|
314
|
+
ele.elements.each("gd:reminder") do |r|
|
315
|
+
@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'] : ''}
|
316
|
+
end
|
317
|
+
when "where"
|
318
|
+
@where = ele.attributes['valueString']
|
319
|
+
when "link"
|
320
|
+
if ele.attributes['rel'] == 'edit'
|
321
|
+
@edit_feed = ele.attributes['href']
|
322
|
+
end
|
323
|
+
when "who"
|
324
|
+
if ele.attributes['rel'] == "http://schemas.google.com/g/2005#event.attendee"
|
325
|
+
n = {}
|
326
|
+
ele.attributes.each do |name, value|
|
327
|
+
case name
|
328
|
+
when "email"
|
329
|
+
n[:email] = value
|
330
|
+
when "valueString"
|
331
|
+
n[:name] = value
|
332
|
+
end
|
333
|
+
end
|
334
|
+
@attendees << n
|
335
|
+
end
|
336
|
+
when "eventStatus"
|
337
|
+
case ele.attributes["value"]
|
338
|
+
when "http://schemas.google.com/g/2005#event.confirmed"
|
339
|
+
@status = :confirmed
|
340
|
+
when "http://schemas.google.com/g/2005#event.tentative"
|
341
|
+
@status = :tentative
|
342
|
+
when "http://schemas.google.com/g/2005#event.cancelled"
|
343
|
+
@status = :cancelled
|
344
|
+
end
|
345
|
+
when 'recurrence'
|
346
|
+
@recurrence = ele.text #Recurrence.new(ele.text)
|
347
|
+
when "transparency"
|
348
|
+
case ele.attributes["value"]
|
349
|
+
when "http://schemas.google.com/g/2005#event.transparent"
|
350
|
+
@transparency = :free
|
351
|
+
when "http://schemas.google.com/g/2005#event.opaque"
|
352
|
+
@transparency = :busy
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
#Reloads the event data from the Google Calendar Service. Returns true if successful,
|
359
|
+
#false otherwise.
|
360
|
+
def reload
|
361
|
+
t = Event.find(@calendar, @id)
|
362
|
+
if t
|
363
|
+
if load(t.to_xml)
|
364
|
+
return true
|
365
|
+
else
|
366
|
+
return false
|
367
|
+
end
|
368
|
+
else
|
369
|
+
return false
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
#Finds the event that matches a query term in the event title or description.
|
374
|
+
#
|
375
|
+
#'query' is a string to perform the search on or an event id.
|
376
|
+
#
|
377
|
+
#The params hash can contain the following hash values
|
378
|
+
#* *scope*: may be :all or :first, indicating whether to return the first record found or an array of all records that match the query. Default is :all.
|
379
|
+
#* *range*: a hash including a :start and :end time to constrain the search by
|
380
|
+
#* *max_results*: an integer indicating the number of results to return. Default is 25.
|
381
|
+
#* *sort_order*: either 'ascending' or 'descending'.
|
382
|
+
#* *single_events*: either 'true' to return all recurring events as a single entry, or 'false' to return all recurring events as a unique event for each recurrence.
|
383
|
+
#* *ctz*: the timezone to return the event times in
|
384
|
+
def self.find(calendar, query = '', params = {})
|
385
|
+
query_string = ''
|
386
|
+
|
387
|
+
begin
|
388
|
+
test = URI.parse(query).scheme
|
389
|
+
rescue Exception => e
|
390
|
+
test = nil
|
391
|
+
end
|
392
|
+
|
393
|
+
if test
|
394
|
+
puts "id passed, finding event by id" if calendar.service.debug
|
395
|
+
puts "id = "+query if calendar.service.debug
|
396
|
+
event_id = query.gsub("/events/","/private/full/") #fix provided by groesser3
|
397
|
+
|
398
|
+
es = calendar.service.send_get(event_id)
|
399
|
+
puts es.inspect if calendar.service.debug
|
400
|
+
if es
|
401
|
+
entry = REXML::Document.new(es.read_body).root
|
402
|
+
puts 'event found' if calendar.service.debug
|
403
|
+
Event.define_xml_namespaces(entry)
|
404
|
+
event = Event.new(calendar)
|
405
|
+
event.load("<?xml version='1.0' encoding='UTF-8'?>#{entry.to_s}")
|
406
|
+
return event
|
407
|
+
end
|
408
|
+
return nil
|
409
|
+
end
|
410
|
+
|
411
|
+
|
412
|
+
#parse params hash for values
|
413
|
+
range = params[:range] || nil
|
414
|
+
max_results = params[:max_results] || nil
|
415
|
+
sort_order = params[:sortorder] || nil
|
416
|
+
single_events = params[:singleevents] || nil
|
417
|
+
timezone = params[:ctz] || nil
|
418
|
+
|
419
|
+
#set up query string
|
420
|
+
query_string += "q=#{CGI.escape(query)}" if query
|
421
|
+
if range
|
422
|
+
if not range.is_a? Hash or (range.size > 0 and (not range[:start].is_a? Time or not range[:end].is_a? Time))
|
423
|
+
raise "The date range must be a hash including the :start and :end date values as Times"
|
424
|
+
else
|
425
|
+
date_range = ''
|
426
|
+
if range.size > 0
|
427
|
+
#Added via patch from Fabio Inguaggiato
|
428
|
+
query_string += "&start-min=#{CGI::escape(range[:start].xmlschema)}&start-max=#{CGI::escape(range[:end].xmlschema)}"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
query_string += "&max-results=#{max_results}" if max_results
|
433
|
+
query_string += "&sortorder=#{sort_order}" if sort_order
|
434
|
+
query_string += "&ctz=#{timezone.gsub(" ", "_")}" if timezone
|
435
|
+
query_string += "&singleevents=#{single_events}" if single_events
|
436
|
+
if query_string
|
437
|
+
events = calendar.service.send_get("http://www.google.com/calendar/feeds/#{calendar.id}/private/full?"+query_string)
|
438
|
+
ret = []
|
439
|
+
REXML::Document.new(events.read_body).root.elements.each("entry"){}.map do |entry|
|
440
|
+
Event.define_xml_namespaces(entry)
|
441
|
+
event = Event.new(calendar)
|
442
|
+
event.load("<?xml version='1.0' encoding='UTF-8'?>#{entry.to_s}")
|
443
|
+
ret << event
|
444
|
+
end
|
445
|
+
end
|
446
|
+
if params[:scope] == :first
|
447
|
+
return ret[0]
|
448
|
+
else
|
449
|
+
return ret
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
#Returns true if the event exists on the Google Calendar Service.
|
454
|
+
def exists?
|
455
|
+
return @exists
|
456
|
+
end
|
457
|
+
|
458
|
+
private
|
459
|
+
@exists = false
|
460
|
+
@calendar = nil
|
461
|
+
@xml = nil
|
462
|
+
@etag = nil
|
463
|
+
@recurrence = nil
|
464
|
+
@deleted = false
|
465
|
+
@edit_feed = ''
|
466
|
+
|
467
|
+
def self.define_xml_namespaces(entry)
|
468
|
+
entry.attributes["xmlns:gCal"] = "http://schemas.google.com/gCal/2005"
|
469
|
+
entry.attributes["xmlns:gd"] = "http://schemas.google.com/g/2005"
|
470
|
+
entry.attributes["xmlns:app"] = "http://www.w3.org/2007/app"
|
471
|
+
entry.attributes["xmlns"] = "http://www.w3.org/2005/Atom"
|
472
|
+
entry.attributes["xmlns:georss"] = "http://www.georss.org/georss"
|
473
|
+
entry.attributes["xmlns:gml"] = "http://www.opengis.net/gml"
|
474
|
+
end
|
475
|
+
|
476
|
+
def set_reminder(ele)
|
477
|
+
ele.delete_element("gd:reminder")
|
478
|
+
if @reminder
|
479
|
+
e = ele.add_element("gd:reminder")
|
480
|
+
used = false
|
481
|
+
if @reminder[:minutes]
|
482
|
+
e.attributes['minutes'] = @reminder[:minutes]
|
483
|
+
used = true
|
484
|
+
elsif @reminder[:hours] and not used
|
485
|
+
e.attributes['hours'] = @reminder[:hours]
|
486
|
+
used = true
|
487
|
+
elsif @reminder[:days] and not used
|
488
|
+
e.attributes['days'] = @reminder[:days]
|
489
|
+
end
|
490
|
+
if @reminder[:method]
|
491
|
+
e.attributes['method'] = @reminder[:method]
|
492
|
+
else
|
493
|
+
e.attributes['method'] = 'email'
|
494
|
+
end
|
495
|
+
else
|
496
|
+
ele.delete_element("gd:reminder")
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|