caldav-icloud 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZTZjYTUyZGJmODQ4MDUyYTEzMmZhMmJlYWViZmVmMDA5ZTk1NWM1NA==
5
+ data.tar.gz: !binary |-
6
+ MGY5YjUyOGVlNThmNmQzN2M1N2ZmNTQ0NjVhN2E0ZDRmZGM2YWExZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZGQ1YmM0NTQ5MWUzZDg3ZDdjODcxZTkxOWNiNmE5NTRiY2RlMmI4ZWZjYTA4
10
+ MjFjM2QxMWJjYzRmYzVhMWNhYzM5NWYyMWZkMGYyOTNjYWIxOTZmNmNmOWM5
11
+ N2RmZjNiYzBhYzk3ZTNjZWY3YzdiZmE4ODQ5OWU1ZjVhMzkyMjc=
12
+ data.tar.gz: !binary |-
13
+ MGRkZDQzM2Q2OTY3NWVmOTBiZGNkOGVjZWU1N2Y4MmI1Y2Q3NjMzNjJmZTYy
14
+ YjQwMTNlYzUxNDgzNWVkNTFkNWZlODRhZDNmNDQyZTU2OTA0NjdjOWYyZjgz
15
+ YjNhZTU4M2Y3ZTBlOWMyYjc2MGYxMTM0ZDljODU1OGM0ZTZhZTA=
@@ -0,0 +1,3 @@
1
+ caldaver-test.sh
2
+ *.gem
3
+ *.tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,12 @@
1
+ = v0.3
2
+
3
+ * Made to work with iCloud
4
+
5
+ = v0.2.5.3
6
+
7
+ * Fixed gemspec
8
+
9
+ = v0.2.5.2
10
+
11
+ * client.update_event *args* changed. UID has to be provided
12
+ now -> client.update_event(:uid => "123", ...)
@@ -0,0 +1,109 @@
1
+ #Ruby CalDAV library for iCloud Caldav
2
+ **caldav-icloud is based on agilastic/agcaldav, modified to work for Apple's iCloud**
3
+
4
+ **caldav-icloud is still under development and is not finished...**
5
+
6
+ ##Usage Events
7
+
8
+ First, you've to install the gem
9
+
10
+ gem install caldav-icloud
11
+
12
+ and require it
13
+
14
+ require "caldav-icloud"
15
+
16
+ Now you can e.g. create a new CalDAViCloud-Client:
17
+
18
+ cal = CalDAViCloud::Client.new(:uri => "https://pYY-caldav.icloud.com/XXXXXXXXX/calendars/ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ/", :user => "icloudusername@email.com" , :password => "icloudpassword")
19
+
20
+ Where X, Y, and Z (corresponding to your closest icloud caldav server, icloud unique id, and calendar uuid path respectively) can be found with these links:
21
+
22
+ https://www.google.com/search?client=safari&rls=en&q=finding+your+icloud+user+id&ie=UTF-8&oe=UTF-8#q=finding+your+icloud+unique+id+caldav+path&rls=en
23
+
24
+ http://apple.stackexchange.com/questions/67021/how-can-i-configure-custom-repeat-intervals-for-reminders
25
+
26
+ ####Find Events within time interval
27
+
28
+ result = cal.find_events(:start => "2012-10-01 08:00", :end => "2013-01-01")
29
+
30
+ >> result
31
+ => [#<Icalendar::Event:0x007f8ad11cfdf0 @name="VEVENT", @components={}, @properties={"sequence"=>0, "dtstamp"=>#<DateTime: 2012-12-31T13:44:10+00:00 (4244474429/1728,0/1,2299161)>, "description"=>"sdkvjsdf sdkf sdkfj sdkf dsfj", "dtend"=>#<DateTime: 2012-12-30T12:00:00+00:00 (2456292/1,0/1,2299161)>, "dtstart"=>#<DateTime: 2012-12-29T10:00:00+00:00 (29475491/12,0/1,2299161)>, "summary"=>"12345", "uid"=>"b2c45e20-3575-0130-7d2e-109add70606c", "x-radicale_name"=>"b2c45e20-3575-0130-7d2e-109add70606c.ics"}>, #<Icalendar::Event:0x007f8ad10d7dd0 @name="VEVENT", @components={}, @properties={"sequence"=>0, "dtstamp"=>#<DateTime: 2012-12-31T13:44:10+00:00 (4244474429/1728,0/1,2299161)>, "uid"=>"b2c45e20-3575-0130-7d2e-109add70606c", "x-radicale_name"=>"b2c45e20-3575-0130-7d2e-109add70606c.ics"}>]
32
+
33
+ >> result.class
34
+ => Array
35
+
36
+ >> result.count
37
+ => 2
38
+
39
+ ####Create an Event
40
+
41
+ result = cal.create_event(:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "12345 12345")
42
+
43
+ Analyze result:
44
+
45
+ >> result
46
+ => #<Icalendar::Event:0x007ff653b47520 @name="VEVENT", @components={}, @properties={"sequence"=>0, "dtstamp"=>#<DateTime: 2012-12-30T19:59:04+00:00 (26527957193/10800,0/1,2299161)>, "description"=>"sdkvjsdf sdkf sdkfj sdkf dsfj", "dtend"=>#<DateTime: 2012-12-30T12:00:00+00:00 (2456292/1,0/1,2299161)>, "dtstart"=>#<DateTime: 2012-12-29T10:00:00+00:00 (29475491/12,0/1,2299161)>, "summary"=>"12345", "uid"=>"e795c480-34e0-0130-7d1d-109add70606c", "x-radicale_name"=>"e795c480-34e0-0130-7d1d-109add70606c.ics"}>
47
+
48
+ >> result.class
49
+ => Icalendar::Event
50
+
51
+
52
+ get UID of this Event:
53
+
54
+ >> result.uid
55
+ => "e795c480-34e0-0130-7d1d-109add70606c"
56
+
57
+
58
+ ####Find an Event (via UUID)
59
+
60
+ result = cal.find_event("e795c480-34e0-0130-7d1d-109add70606c")
61
+
62
+ >> result.class
63
+ => Icalendar::Event
64
+
65
+
66
+
67
+
68
+ ####Delete Event
69
+
70
+ cal.delete_event("e795c480-34e0-0130-7d1d-109add70606c")
71
+
72
+
73
+ ####Update Event
74
+
75
+ **Not tested**
76
+
77
+ event = {:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "sdkvjsdf sdkf sdkfj sdkf dsfj"}
78
+ # set UUID
79
+ event[:uid] => "e795c480-34e0-0130-7d1d-109add70606c"
80
+ c = cal.update_event(event)
81
+
82
+
83
+
84
+ ##Usage ToDo
85
+
86
+ Not tested yet
87
+
88
+ ##Work to be done ...
89
+
90
+ 1. find and notify if overlapping events
91
+ 2. code cleanup -> more ActiveRecord style
92
+
93
+
94
+
95
+ ##Contributors
96
+
97
+ [Check all contributors][c]
98
+
99
+
100
+ 1. Fork it.
101
+ 2. Create a branch (`git checkout -b my_feature_branch`)
102
+ 3. Commit your changes (`git commit -am "bugfixed abc..."`)
103
+ 4. Push to the branch (`git push origin my_feature_branch`)
104
+ 5. Open a [Pull Request][1]
105
+ 6. Enjoy a refreshing Club Mate and wait
106
+
107
+ [c]: https://github.com/n8vision/caldav-icloud/contributors
108
+ [1]: https://github.com/n8vision/caldav-icloud/pulls/
109
+
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new('spec')
4
+
5
+ # If you want to make this the default task
6
+ task :default => :spec
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require File.expand_path('../lib/caldav-icloud/version', __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "caldav-icloud"
7
+ s.version = CalDAViCloud::VERSION
8
+ s.summary = "Ruby CalDAV client"
9
+ s.description = "yet another great Ruby client for CalDAV calendar and tasks."
10
+
11
+ s.required_ruby_version = '>= 1.9.2'
12
+
13
+ s.license = 'MIT'
14
+
15
+ s.homepage = %q{https://github.com/n8vision/caldav-icloud}
16
+ s.authors = [%q{Nick Adams}]
17
+ s.email = [%q{n8vision@gmail.com}]
18
+ s.add_runtime_dependency 'icalendar'
19
+ s.add_runtime_dependency 'uuid'
20
+ s.add_runtime_dependency 'builder'
21
+ s.add_runtime_dependency 'net-http-digest_auth'
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "fakeweb"
24
+
25
+
26
+
27
+ s.description = <<-DESC
28
+ caldav-icloud is a Ruby client for CalDAV calendar made to work with Apple's iCloud. It is based on the icalendar gem.
29
+ DESC
30
+ s.post_install_message = <<-POSTINSTALL
31
+ Changelog: https://github.com/n8vision/caldav-icloud/blob/master/CHANGELOG.rdoc
32
+ Examples: https://github.com/n8vision/caldav-icloud
33
+ POSTINSTALL
34
+
35
+
36
+ s.files = `git ls-files`.split("\n")
37
+ s.require_paths = ["lib"]
38
+ end
@@ -0,0 +1,12 @@
1
+ require 'net/https'
2
+ require 'net/http/digest_auth'
3
+ require 'uuid'
4
+ require 'rexml/document'
5
+ require 'rexml/xpath'
6
+ require 'icalendar'
7
+ require 'time'
8
+ require 'date'
9
+
10
+ ['client.rb', 'request.rb', 'net.rb', 'query.rb', 'filter.rb', 'event.rb', 'todo.rb', 'format.rb'].each do |f|
11
+ require File.join( File.dirname(__FILE__), 'caldav-icloud', f )
12
+ end
@@ -0,0 +1,328 @@
1
+ module CalDAViCloud
2
+ class Client
3
+ include Icalendar
4
+ attr_accessor :host, :port, :url, :user, :password, :ssl
5
+
6
+ def format=( fmt )
7
+ @format = fmt
8
+ end
9
+
10
+ def format
11
+ @format ||= Format::Debug.new
12
+ end
13
+
14
+ def initialize( data )
15
+ unless data[:proxy_uri].nil?
16
+ proxy_uri = URI(data[:proxy_uri])
17
+ @proxy_host = proxy_uri.host
18
+ @proxy_port = proxy_uri.port.to_i
19
+ end
20
+
21
+ uri = URI(data[:uri])
22
+ @host = uri.host
23
+ @port = uri.port.to_i
24
+ @url = uri.path
25
+ @user = data[:user]
26
+ @password = data[:password]
27
+ @ssl = uri.scheme == 'https'
28
+
29
+ unless data[:authtype].nil?
30
+ @authtype = data[:authtype]
31
+ if @authtype == 'digest'
32
+
33
+ @digest_auth = Net::HTTP::DigestAuth.new
34
+ @duri = URI.parse data[:uri]
35
+ @duri.user = @user
36
+ @duri.password = @password
37
+
38
+ elsif @authtype == 'basic'
39
+ #Don't Raise or do anything else
40
+ else
41
+ raise "Authentication Type Specified Is Not Valid. Please use basic or digest"
42
+ end
43
+ else
44
+ @authtype = 'basic'
45
+ end
46
+ end
47
+
48
+ def __create_http
49
+ if @proxy_uri.nil?
50
+ http = Net::HTTP.new(@host, @port)
51
+ else
52
+ http = Net::HTTP.new(@host, @port, @proxy_host, @proxy_port)
53
+ end
54
+ if @ssl
55
+ http.use_ssl = @ssl
56
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
57
+ end
58
+ http
59
+ end
60
+
61
+ def find_events data
62
+ result = ""
63
+ events = []
64
+ res = nil
65
+ __create_http.start {|http|
66
+
67
+ req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
68
+
69
+ if not @authtype == 'digest'
70
+ req.basic_auth @user, @password
71
+ else
72
+ req.add_field 'Authorization', digestauth('REPORT')
73
+ end
74
+ if data[:start].is_a? Integer
75
+ req.body = CalDAViCloud::Request::ReportVEVENT.new(Time.at(data[:start]).utc.strftime("%Y%m%dT%H%M%S"),
76
+ Time.at(data[:end]).utc.strftime("%Y%m%dT%H%M%S") ).to_xml
77
+ else
78
+ req.body = CalDAViCloud::Request::ReportVEVENT.new(Time.parse(data[:start]).utc.strftime("%Y%m%dT%H%M%S"),
79
+ Time.parse(data[:end]).utc.strftime("%Y%m%dT%H%M%S") ).to_xml
80
+ end
81
+ res = http.request(req)
82
+ }
83
+ errorhandling res
84
+ result = ""
85
+ #puts res.body
86
+ xml = REXML::Document.new(res.body)
87
+ REXML::XPath.each( xml, '//c:calendar-data/', {"c"=>"urn:ietf:params:xml:ns:caldav"} ){|c| result << c.text}
88
+ r = Icalendar.parse(result)
89
+ unless r.empty?
90
+ r.each do |calendar|
91
+ calendar.events.each do |event|
92
+ events << event
93
+ end
94
+ end
95
+ events
96
+ else
97
+ return false
98
+ end
99
+ end
100
+
101
+ def find_event uuid
102
+ res = nil
103
+ __create_http.start {|http|
104
+ req = Net::HTTP::Get.new("#{@url}/#{uuid}.ics")
105
+ if not @authtype == 'digest'
106
+ req.basic_auth @user, @password
107
+ else
108
+ req.add_field 'Authorization', digestauth('GET')
109
+ end
110
+ res = http.request( req )
111
+ }
112
+ errorhandling res
113
+ begin
114
+ r = Icalendar.parse(res.body)
115
+ rescue
116
+ return false
117
+ else
118
+ r.first.events.first
119
+ end
120
+
121
+
122
+ end
123
+
124
+ def delete_event uuid
125
+ res = nil
126
+ __create_http.start {|http|
127
+ req = Net::HTTP::Delete.new("#{@url}/#{uuid}.ics")
128
+ if not @authtype == 'digest'
129
+ req.basic_auth @user, @password
130
+ else
131
+ req.add_field 'Authorization', digestauth('DELETE')
132
+ end
133
+ res = http.request( req )
134
+ }
135
+ errorhandling res
136
+ # accept any success code
137
+ if res.code.to_i.between?(200,299)
138
+ return true
139
+ else
140
+ return false
141
+ end
142
+ end
143
+
144
+ def create_event event
145
+ c = Calendar.new
146
+ c.events = []
147
+ uuid = UUID.new.generate
148
+ raise DuplicateError if entry_with_uuid_exists?(uuid)
149
+ c.event do
150
+ uid uuid
151
+ dtstart DateTime.parse(event[:start])
152
+ dtend DateTime.parse(event[:end])
153
+ categories event[:categories]# Array
154
+ contacts event[:contacts] # Array
155
+ attendees event[:attendees]# Array
156
+ duration event[:duration]
157
+ summary event[:title]
158
+ description event[:description]
159
+ klass event[:accessibility] #PUBLIC, PRIVATE, CONFIDENTIAL
160
+ location event[:location]
161
+ geo_location event[:geo_location]
162
+ status event[:status]
163
+ url event[:url]
164
+ end
165
+ cstring = c.to_ical
166
+ res = nil
167
+ http = Net::HTTP.new(@host, @port)
168
+ __create_http.start { |http|
169
+ req = Net::HTTP::Put.new("#{@url}/#{uuid}.ics")
170
+ req['Content-Type'] = 'text/calendar'
171
+ if not @authtype == 'digest'
172
+ req.basic_auth @user, @password
173
+ else
174
+ req.add_field 'Authorization', digestauth('PUT')
175
+ end
176
+ req.body = cstring
177
+ res = http.request( req )
178
+ }
179
+ errorhandling res
180
+ find_event uuid
181
+ end
182
+
183
+ def update_event event
184
+ #TODO... fix me
185
+ if delete_event event[:uid]
186
+ create_event event
187
+ else
188
+ return false
189
+ end
190
+ end
191
+
192
+ def add_alarm tevent, altCal="Calendar"
193
+
194
+ end
195
+
196
+ def find_todo uuid
197
+ res = nil
198
+ __create_http.start {|http|
199
+ req = Net::HTTP::Get.new("#{@url}/#{uuid}.ics")
200
+ if not @authtype == 'digest'
201
+ req.basic_auth @user, @password
202
+ else
203
+ req.add_field 'Authorization', digestauth('GET')
204
+ end
205
+ res = http.request( req )
206
+ }
207
+ errorhandling res
208
+ r = Icalendar.parse(res.body)
209
+ r.first.todos.first
210
+ end
211
+
212
+
213
+
214
+
215
+
216
+ def create_todo todo
217
+ c = Calendar.new
218
+ uuid = UUID.new.generate
219
+ raise DuplicateError if entry_with_uuid_exists?(uuid)
220
+ c.todo do
221
+ uid uuid
222
+ start DateTime.parse(todo[:start])
223
+ duration todo[:duration]
224
+ summary todo[:title]
225
+ description todo[:description]
226
+ klass todo[:accessibility] #PUBLIC, PRIVATE, CONFIDENTIAL
227
+ location todo[:location]
228
+ percent todo[:percent]
229
+ priority todo[:priority]
230
+ url todo[:url]
231
+ geo todo[:geo_location]
232
+ status todo[:status]
233
+ end
234
+ c.todo.uid = uuid
235
+ cstring = c.to_ical
236
+ res = nil
237
+ http = Net::HTTP.new(@host, @port)
238
+ __create_http.start { |http|
239
+ req = Net::HTTP::Put.new("#{@url}/#{uuid}.ics")
240
+ req['Content-Type'] = 'text/calendar'
241
+ if not @authtype == 'digest'
242
+ req.basic_auth @user, @password
243
+ else
244
+ req.add_field 'Authorization', digestauth('PUT')
245
+ end
246
+ req.body = cstring
247
+ res = http.request( req )
248
+ }
249
+ errorhandling res
250
+ find_todo uuid
251
+ end
252
+
253
+ def create_todo
254
+ res = nil
255
+ raise DuplicateError if entry_with_uuid_exists?(uuid)
256
+
257
+ __create_http.start {|http|
258
+ req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
259
+ if not @authtype == 'digest'
260
+ req.basic_auth @user, @password
261
+ else
262
+ req.add_field 'Authorization', digestauth('REPORT')
263
+ end
264
+ req.body = CalDAViCloud::Request::ReportVTODO.new.to_xml
265
+ res = http.request( req )
266
+ }
267
+ errorhandling res
268
+ format.parse_todo( res.body )
269
+ end
270
+
271
+ private
272
+
273
+ def digestauth method
274
+
275
+ h = Net::HTTP.new @duri.host, @duri.port
276
+ if @ssl
277
+ h.use_ssl = @ssl
278
+ h.verify_mode = OpenSSL::SSL::VERIFY_NONE
279
+ end
280
+ req = Net::HTTP::Get.new @duri.request_uri
281
+
282
+ res = h.request req
283
+ # res is a 401 response with a WWW-Authenticate header
284
+
285
+ auth = @digest_auth.auth_header @duri, res['www-authenticate'], method
286
+
287
+ return auth
288
+ end
289
+
290
+ def entry_with_uuid_exists? uuid
291
+ res = nil
292
+
293
+ __create_http.start {|http|
294
+ req = Net::HTTP::Get.new("#{@url}/#{uuid}.ics")
295
+ if not @authtype == 'digest'
296
+ req.basic_auth @user, @password
297
+ else
298
+ req.add_field 'Authorization', digestauth('GET')
299
+ end
300
+
301
+ res = http.request( req )
302
+
303
+ }
304
+ begin
305
+ errorhandling res
306
+ Icalendar.parse(res.body)
307
+ rescue
308
+ return false
309
+ else
310
+ return true
311
+ end
312
+ end
313
+ def errorhandling response
314
+ raise NotExistError if response.code.to_i == 404
315
+ raise AuthenticationError if response.code.to_i == 401
316
+ raise NotExistError if response.code.to_i == 410
317
+ raise APIError if response.code.to_i >= 500
318
+ end
319
+ end
320
+
321
+
322
+ class CalDAViCloudError < StandardError
323
+ end
324
+ class AuthenticationError < CalDAViCloudError; end
325
+ class DuplicateError < CalDAViCloudError; end
326
+ class APIError < CalDAViCloudError; end
327
+ class NotExistError < CalDAViCloudError; end
328
+ end
@@ -0,0 +1,121 @@
1
+
2
+
3
+ module Icalendar
4
+ # A Event calendar component is a grouping of component
5
+ # properties, and possibly including Alarm calendar components, that
6
+ # represents a scheduled amount of time on a calendar. For example, it
7
+ # can be an activity; such as a one-hour long, department meeting from
8
+ # 8:00 AM to 9:00 AM, tomorrow. Generally, an event will take up time
9
+ # on an individual calendar.
10
+ class Event < Component
11
+ ical_component :alarms
12
+
13
+ ## Single instance properties
14
+
15
+ # Access classification (PUBLIC, PRIVATE, CONFIDENTIAL...)
16
+ ical_property :ip_class, :klass
17
+
18
+ # Date & time of creation
19
+ ical_property :created
20
+
21
+ # Complete description of the calendar component
22
+ ical_property :description
23
+
24
+ attr_accessor :tzid
25
+
26
+ # Specifies date-time when calendar component begins
27
+ ical_property :dtstart, :start
28
+
29
+ # Latitude & longitude for specified activity
30
+ ical_property :geo, :geo_location
31
+
32
+ # Date & time this item was last modified
33
+ ical_property :last_modified
34
+
35
+ # Specifies the intended venue for this activity
36
+ ical_property :location
37
+
38
+ # Defines organizer of this item
39
+ ical_property :organizer
40
+
41
+ # Defines relative priority for this item (1-9... 1 = best)
42
+ ical_property :priority
43
+
44
+ # Indicate date & time when this item was created
45
+ ical_property :dtstamp, :timestamp
46
+
47
+ # Revision sequence number for this item
48
+ ical_property :sequence, :seq
49
+
50
+ # Defines overall status or confirmation of this item
51
+ ical_property :status
52
+ ical_property :summary
53
+ ical_property :transp, :transparency
54
+
55
+ # Defines a persistent, globally unique id for this item
56
+ ical_property :uid, :unique_id
57
+
58
+ # Defines a URL associated with this item
59
+ ical_property :url
60
+ ical_property :recurrence_id, :recurid
61
+
62
+ ## Single but mutually exclusive properties (Not testing though)
63
+
64
+ # Specifies a date and time that this item ends
65
+ ical_property :dtend, :end
66
+
67
+ # Specifies a positive duration time
68
+ ical_property :duration
69
+
70
+ ## Multi-instance properties
71
+
72
+ # Associates a URI or binary blob with this item
73
+ ical_multi_property :attach, :attachment, :attachments
74
+
75
+ # Defines an attendee for this calendar item
76
+ ical_multiline_property :attendee, :attendee, :attendees
77
+
78
+ # Defines the categories for a calendar component (school, work...)
79
+ ical_multi_property :categories, :category, :categories
80
+
81
+ # Simple comment for the calendar user.
82
+ ical_multi_property :comment, :comment, :comments
83
+
84
+ # Contact information associated with this item.
85
+ ical_multi_property :contact, :contact, :contacts
86
+ ical_multi_property :exdate, :exception_date, :exception_dates
87
+ ical_multi_property :exrule, :exception_rule, :exception_rules
88
+ ical_multi_property :rstatus, :request_status, :request_statuses
89
+
90
+ # Used to represent a relationship between two calendar items
91
+ ical_multi_property :related_to, :related_to, :related_tos
92
+ ical_multi_property :resources, :resource, :resources
93
+
94
+ # Used with the UID & SEQUENCE to identify a specific instance of a
95
+ # recurring calendar item.
96
+ ical_multi_property :rdate, :recurrence_date, :recurrence_dates
97
+ ical_multi_property :rrule, :recurrence_rule, :recurrence_rules
98
+
99
+ def initialize()
100
+ super("VEVENT")
101
+
102
+ # Now doing some basic initialization
103
+ sequence 0
104
+ timestamp DateTime.now
105
+ end
106
+
107
+ def alarm(&block)
108
+ a = Alarm.new
109
+ self.add a
110
+
111
+ a.instance_eval(&block) if block
112
+
113
+ a
114
+ end
115
+
116
+ def occurrences_starting(time)
117
+ recurrence_rules.first.occurrences_of_event_starting(self, time)
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,78 @@
1
+ module CalDAViCloud
2
+ module Filter
3
+ class Base
4
+ attr_accessor :parent, :child
5
+
6
+ def to_xml(xml = Builder::XmlMarkup.new(:indent => 2))
7
+ if parent
8
+ parent.to_xml
9
+ else
10
+ build_xml(xml)
11
+ end
12
+ end
13
+
14
+ def build_xml(xml)
15
+ #do nothing
16
+ end
17
+
18
+ def child=(child)
19
+ @child = child
20
+ child.parent = self
21
+ end
22
+ end
23
+
24
+ class Component < Base
25
+ attr_accessor :name
26
+
27
+ def initialize(name, parent = nil)
28
+ self.name = name
29
+ self.parent = parent
30
+ end
31
+
32
+ def time_range(range)
33
+ self.child = TimeRange.new(range, self)
34
+ end
35
+
36
+ def uid(uid)
37
+ self.child = Property.new("UID", uid, self)
38
+ end
39
+
40
+ def build_xml(xml)
41
+ xml.tag! "cal:comp-filter", :name => name do
42
+ child.build_xml(xml) unless child.nil?
43
+ end
44
+ end
45
+ end
46
+
47
+ class TimeRange < Base
48
+ attr_accessor :range
49
+
50
+ def initialize(range, parent = nil)
51
+ self.range = range
52
+ self.parent = parent
53
+ end
54
+
55
+ def build_xml(xml)
56
+ xml.tag! "cal:time-range",
57
+ :start => range.begin.to_ical,
58
+ :end => range.end.to_ical
59
+ end
60
+ end
61
+
62
+ class Property < Base
63
+ attr_accessor :name, :text
64
+
65
+ def initialize(name, text, parent = nil)
66
+ self.name = name
67
+ self.text = text
68
+ self.parent = parent
69
+ end
70
+
71
+ def build_xml(xml)
72
+ xml.tag! "cal:prop-filter", :name => self.name do
73
+ xml.tag! "cal:text-match", self.text, :collation => "i;octet"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,54 @@
1
+ module CalDAViCloud
2
+ module Format
3
+ class Raw
4
+ def method_missing(m, *args, &block)
5
+ return *args
6
+ end
7
+ end
8
+
9
+ class Debug < Raw
10
+ end
11
+
12
+ class Pretty < Raw
13
+ def parse_calendar(s)
14
+ result = ""
15
+ xml = REXML::Document.new(s)
16
+
17
+ REXML::XPath.each( xml, '//c:calendar-data/', {"c"=>"urn:ietf:params:xml:ns:caldav"} ){|c| result << c.text}
18
+ r = Icalendar.parse(result)
19
+ r
20
+ end
21
+
22
+ def parse_todo( body )
23
+ result = []
24
+ xml = REXML::Document.new( body )
25
+ REXML::XPath.each( xml, '//c:calendar-data/', { "c"=>"urn:ietf:params:xml:ns:caldav"} ){ |c|
26
+ p c.text
27
+ p parse_tasks( c.text )
28
+ result += parse_tasks( c.text )
29
+ }
30
+ return result
31
+ end
32
+
33
+ def parse_tasks( vcal )
34
+ return_tasks = Array.new
35
+ cals = Icalendar.parse(vcal)
36
+ cals.each { |tcal|
37
+ tcal.todos.each { |ttask| # FIXME
38
+ return_tasks << ttask
39
+ }
40
+ }
41
+ return return_tasks
42
+ end
43
+
44
+ def parse_events( vcal )
45
+ Icalendar.parse(vcal)
46
+ end
47
+
48
+ def parse_single( body )
49
+ # FIXME: parse event/todo/vcard
50
+ parse_events( body )
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ module Net
2
+ class HTTP
3
+ class Report < HTTPRequest
4
+ METHOD = 'REPORT'
5
+ REQUEST_HAS_BODY = true
6
+ RESPONSE_HAS_BODY = true
7
+ end
8
+
9
+ class Mkcalendar < HTTPRequest
10
+ METHOD = 'MKCALENDAR'
11
+ REQUEST_HAS_BODY = true
12
+ RESPONSE_HAS_BODY = true
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,51 @@
1
+ module CalDAViCloud
2
+ class Query
3
+ attr_accessor :child
4
+
5
+ #TODO: raise error if to_xml is called before child is assigned
6
+ def to_xml(xml = Builder::XmlMarkup.new(:indent => 2))
7
+ xml.instruct!
8
+ xml.tag! "cal:calendar-query", CalDAViCloud::NAMESPACES do
9
+ xml.tag! "dav:prop" do
10
+ xml.tag! "dav:getetag"
11
+ xml.tag! "cal:calendar-data"
12
+ end
13
+ xml.tag! "cal:filter" do
14
+ cal = Filter::Component.new("VCALENDAR", self)
15
+ cal.child = self.child
16
+ cal.build_xml(xml)
17
+ end
18
+ end
19
+ end
20
+
21
+ def event(param = nil)
22
+ self.child = Filter::Component.new("VEVENT")
23
+ if param.is_a? Range
24
+ self.child.time_range(param)
25
+ elsif param.is_a? String
26
+ self.child.uid(param)
27
+ else
28
+ self.child
29
+ end
30
+ end
31
+
32
+ def todo(param = nil)
33
+ self.child = Filter::Component.new("VTODO")
34
+ self.child
35
+ end
36
+
37
+ def child=(child)
38
+ child.parent = self
39
+ @child = child
40
+ end
41
+
42
+ def self.event( param=nil )
43
+ self.new.event( param )
44
+ end
45
+
46
+
47
+ def self.todo( param=nil )
48
+ self.new.todo
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ require 'builder'
2
+
3
+ module CalDAViCloud
4
+ NAMESPACES = { "xmlns:d" => 'DAV:', "xmlns:c" => "urn:ietf:params:xml:ns:caldav" }
5
+ module Request
6
+ class Base
7
+ def initialize
8
+ @xml = Builder::XmlMarkup.new(:indent => 2)
9
+ @xml.instruct!
10
+ end
11
+ attr :xml
12
+ end
13
+
14
+ class Mkcalendar < Base
15
+ attr_accessor :displayname, :description
16
+
17
+ def initialize(displayname = nil, description = nil)
18
+ @displayname = displayname
19
+ @description = description
20
+ end
21
+
22
+ def to_xml
23
+ xml.c :mkcalendar, NAMESPACES do
24
+ xml.d :set do
25
+ xml.d :prop do
26
+ xml.d :displayname, displayname unless displayname.to_s.empty?
27
+ xml.tag! "c:calendar-description", description, "xml:lang" => "en" unless description.to_s.empty?
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ class ReportVEVENT < Base
35
+ attr_accessor :tstart, :tend
36
+
37
+ def initialize( tstart=nil, tend=nil )
38
+ @tstart = tstart
39
+ @tend = tend
40
+ super()
41
+ end
42
+
43
+ def to_xml
44
+ xml.c 'calendar-query'.intern, NAMESPACES do
45
+ xml.d :prop do
46
+ #xml.d :getetag
47
+ xml.c 'calendar-data'.intern
48
+ end
49
+ xml.c :filter do
50
+ xml.c 'comp-filter'.intern, :name=> 'VCALENDAR' do
51
+ xml.c 'comp-filter'.intern, :name=> 'VEVENT' do
52
+ xml.c 'time-range'.intern, :start=> "#{tstart}Z", :end=> "#{tend}Z"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ class ReportVTODO < Base
61
+ def to_xml
62
+ xml.c 'calendar-query'.intern, NAMESPACES do
63
+ xml.d :prop do
64
+ xml.d :getetag
65
+ xml.c 'calendar-data'.intern
66
+ end
67
+ xml.c :filter do
68
+ xml.c 'comp-filter'.intern, :name=> 'VCALENDAR' do
69
+ xml.c 'comp-filter'.intern, :name=> 'VTODO'
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,63 @@
1
+ =begin
2
+ Copyright (C) 2005 Jeff Rose
3
+
4
+ This library is free software; you can redistribute it and/or modify it
5
+ under the same terms as the ruby language itself, see the file COPYING for
6
+ details.
7
+ =end
8
+ module Icalendar
9
+ # A Todo calendar component is a grouping of component
10
+ # properties and possibly Alarm calendar components that represent
11
+ # an action-item or assignment. For example, it can be used to
12
+ # represent an item of work assigned to an individual; such as "turn in
13
+ # travel expense today".
14
+ class Todo < Component
15
+ ical_component :alarms
16
+
17
+ # Single properties
18
+ ical_property :ip_class
19
+ ical_property :completed
20
+ ical_property :created
21
+ ical_property :description
22
+ ical_property :dtstamp, :timestamp
23
+ ical_property :dtstart, :start
24
+ ical_property :geo
25
+ ical_property :last_modified
26
+ ical_property :location
27
+ ical_property :organizer
28
+ ical_property :percent_complete, :percent
29
+ ical_property :priority
30
+ ical_property :recurid, :recurrence_id
31
+ ical_property :sequence, :seq
32
+ ical_property :status
33
+ ical_property :summary
34
+ ical_property :uid, :user_id
35
+ ical_property :url
36
+
37
+ # Single but mutually exclusive TODO: not testing anything yet
38
+ ical_property :due
39
+ ical_property :duration
40
+
41
+ # Multi-properties
42
+ ical_multi_property :attach, :attachment, :attachments
43
+ ical_multiline_property :attendee, :attendee, :attendees
44
+ ical_multi_property :categories, :category, :categories
45
+ ical_multi_property :comment, :comment, :comments
46
+ ical_multi_property :contact, :contact, :contacts
47
+ ical_multi_property :exdate, :exception_date, :exception_dates
48
+ ical_multi_property :exrule, :exception_rule, :exception_rules
49
+ ical_multi_property :rstatus, :request_status, :request_statuses
50
+ ical_multi_property :related_to, :related_to, :related_tos
51
+ ical_multi_property :resources, :resource, :resources
52
+ ical_multi_property :rdate, :recurrence_date, :recurrence_dates
53
+ ical_multi_property :rrule, :recurrence_rule, :recurrence_rules
54
+
55
+ def initialize()
56
+ super("VTODO")
57
+
58
+ sequence 0
59
+ timestamp DateTime.now
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module CalDAViCloud
2
+ VERSION="0.3"
3
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'fakeweb'
4
+
5
+ require 'caldav-icloud'
6
+
7
+ describe CalDAViCloud::Client do
8
+
9
+ before(:each) do
10
+ @c = CalDAViCloud::Client.new(:uri => "http://localhost:5232/user/calendar", :user => "user" , :password => "")
11
+ end
12
+
13
+ before(:all) do
14
+ class UUID
15
+ def generate
16
+ "360232b0-371c-0130-9e6b-001999638933"
17
+ end
18
+ end
19
+ end
20
+
21
+
22
+ it "check Class of new calendar" do
23
+ @c.class.to_s.should == "CalDAViCloud::Client"
24
+ end
25
+
26
+ it "create one event" do
27
+ uid = UUID.new.generate
28
+ FakeWeb.register_uri(:any, %r{http://user@localhost:5232/user/calendar/(.*).ics}, [{:body => "", :status => ["200", "OK"]},
29
+ {:body => "BEGIN:VCALENDAR\nPRODID:-//Radicale//NONSGML Radicale Server//EN\nVERSION:2.0\nBEGIN:VEVENT\nDESCRIPTION:12345 12ss345\nDTEND:20130101T110000\nDTSTAMP:20130101T161708\nDTSTART:20130101T100000\nSEQUENCE:0\nSUMMARY:123ss45\nUID:#{uid}\nX-RADICALE-NAME:#{uid}.ics\nEND:VEVENT\nEND:VCALENDAR", :status => ["200", "OK"]}])
30
+ r = @c.create_event(:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "12345 12345")
31
+ r.should_not be_nil
32
+ end
33
+
34
+ it "delete one events" do
35
+ uid = UUID.new.generate
36
+ FakeWeb.register_uri(:delete, %r{http://user@localhost:5232/user/calendar/(.*).ics}, [{:body => "1 deleted.", :status => ["200", "OK"]}, {:body => "not found", :status => ["404", "Not Found"]}])
37
+ r = @c.delete_event(uid)
38
+ r.should == true
39
+ # second time false
40
+ r = @c.delete_event(uid)
41
+ r.should == false
42
+ end
43
+
44
+
45
+ it "failed create one event DuplicateError" do
46
+ uid = "5385e2d0-3707-0130-9e49-0019996389cc"
47
+ FakeWeb.register_uri(:any, %r{http://user@localhost:5232/user/calendar/(.*).ics}, :body => "BEGIN:VCALENDAR\nPRODID:.....")
48
+ lambda{@c.create_event(:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "12345 12345")}.should raise_error(CalDAViCloud::DuplicateError)
49
+ end
50
+
51
+
52
+ it "update one event" do
53
+ # same as delete && create
54
+ #TODO
55
+ end
56
+
57
+ it "find one event" do
58
+ uid = "5385e2d0-3707-0130-9e49-001999638982"
59
+ FakeWeb.register_uri(:get, "http://user@localhost:5232/user/calendar/#{uid}.ics", :body => "BEGIN:VCALENDAR\nPRODID:-//Radicale//NONSGML Radicale Server//EN\nVERSION:2.0\nBEGIN:VEVENT\nDESCRIPTION:12345 12ss345\nDTEND:20130101T110000\nDTSTAMP:20130101T161708\nDTSTART:20130101T100000\nSEQUENCE:0\nSUMMARY:123ss45\nUID:#{uid}\nX-RADICALE-NAME:#{uid}.ics\nEND:VEVENT\nEND:VCALENDAR")
60
+ r = @c.find_event(uid)
61
+ r.should_not be_nil
62
+ r.uid.should == uid
63
+ end
64
+
65
+
66
+ it "find 2 events" do
67
+ module Net
68
+ # Fakeweb doesn't worke here HTTP-method "REPORT" is unknown
69
+ class HTTP
70
+ def request(req, body = nil, &block)
71
+ self
72
+ end
73
+ def code
74
+ "200"
75
+ end
76
+ def body
77
+ "<?xml version=\"1.0\"?>\n<multistatus xmlns=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n <response>\n <href>/user/calendar/960232b0-371c-0130-9e6b-001999638982.ics</href>\n <propstat>\n <prop>\n <getetag>\"-5984324385549365166\"</getetag>\n <C:calendar-data>BEGIN:VCALENDAR\nPRODID:-//Radicale//NONSGML Radicale Server//EN\nVERSION:2.0\nBEGIN:VEVENT\nDESCRIPTION:12345 12345\nDTEND:20010202T120000\nDTSTAMP:20130102T161119\nDTSTART:20010202T080000\nSEQUENCE:0\nSUMMARY:6789\nUID:960232b0-371c-0130-9e6b-001999638982\nX-RADICALE-NAME:960232b0-371c-0130-9e6b-001999638982.ics\nEND:VEVENT\nEND:VCALENDAR\n</C:calendar-data>\n </prop>\n <status>HTTP/1.1 200 OK</status>\n </propstat>\n </response>\n <response>\n <href>/user/calendar/98f067a0-371c-0130-9e6c-001999638982.ics</href>\n <propstat>\n <prop>\n <getetag>\"3611068816283260390\"</getetag>\n <C:calendar-data>BEGIN:VCALENDAR\nPRODID:-//Radicale//NONSGML Radicale Server//EN\nVERSION:2.0\nBEGIN:VEVENT\nDESCRIPTION:12345 12345\nDTEND:20010203T120000\nDTSTAMP:20130102T161124\nDTSTART:20010203T080000\nSEQUENCE:0\nSUMMARY:6789\nUID:98f067a0-371c-0130-9e6c-001999638982\nX-RADICALE-NAME:98f067a0-371c-0130-9e6c-001999638982.ics\nEND:VEVENT\nEND:VCALENDAR\n</C:calendar-data>\n </prop>\n <status>HTTP/1.1 200 OK</status>\n </propstat>\n </response>\n</multistatus>\n\n"
78
+ end
79
+ end
80
+ end
81
+ r = @c.find_events(:start => "2001-02-02 07:00", :end => "2000-02-03 23:59")
82
+ r.should_not be_nil
83
+ r.length.should == 2
84
+ end
85
+
86
+
87
+
88
+ end
@@ -0,0 +1,5 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require 'rubygems'
3
+ require 'caldav-icloud'
4
+
5
+ RSpec.configure do |config|
6
+ # some (optional) config here
7
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: caldav-icloud
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.3'
5
+ platform: ruby
6
+ authors:
7
+ - Nick Adams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: icalendar
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: uuid
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: builder
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: net-http-digest_auth
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: fakeweb
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: ! ' caldav-icloud is a Ruby client for CalDAV calendar made to work
98
+ with Apple''s iCloud. It is based on the icalendar gem.
99
+
100
+ '
101
+ email:
102
+ - n8vision@gmail.com
103
+ executables: []
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - .gitignore
108
+ - .rspec
109
+ - CHANGELOG.rdoc
110
+ - README.md
111
+ - Rakefile
112
+ - caldav-icloud.gemspec
113
+ - lib/caldav-icloud.rb
114
+ - lib/caldav-icloud/client.rb
115
+ - lib/caldav-icloud/event.rb
116
+ - lib/caldav-icloud/filter.rb
117
+ - lib/caldav-icloud/format.rb
118
+ - lib/caldav-icloud/net.rb
119
+ - lib/caldav-icloud/query.rb
120
+ - lib/caldav-icloud/request.rb
121
+ - lib/caldav-icloud/todo.rb
122
+ - lib/caldav-icloud/version.rb
123
+ - spec/caldav-icloud/client_spec.rb
124
+ - spec/spec.opts
125
+ - spec/spec_helper.rb
126
+ homepage: https://github.com/n8vision/caldav-icloud
127
+ licenses:
128
+ - MIT
129
+ metadata: {}
130
+ post_install_message: ! " Changelog: https://github.com/n8vision/caldav-icloud/blob/master/CHANGELOG.rdoc\n
131
+ \ Examples: https://github.com/n8vision/caldav-icloud\n"
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: 1.9.2
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ! '>='
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.2.0
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: Ruby CalDAV client
151
+ test_files: []