caldav-icloud 0.3

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,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: []