agcaldav 0.1
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/.gitignore +1 -0
- data/README.rdoc +3 -0
- data/bin/caldav +7 -0
- data/caldavtest.rb +100 -0
- data/lib/caldav.rb +19 -0
- data/lib/caldav/caldaver.rb +103 -0
- data/lib/caldav/client.rb +220 -0
- data/lib/caldav/filter.rb +78 -0
- data/lib/caldav/format.rb +64 -0
- data/lib/caldav/net.rb +16 -0
- data/lib/caldav/query.rb +51 -0
- data/lib/caldav/request.rb +76 -0
- data/lib/caldav/version.rb +3 -0
- data/ruby-caldav.gemspec +20 -0
- metadata +124 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
caldaver-test.sh
|
data/README.rdoc
ADDED
data/bin/caldav
ADDED
data/caldavtest.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require './lib/caldav.rb'
|
2
|
+
|
3
|
+
class CalDAVTester
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
$caldav = {
|
7
|
+
:host => 'localhost',
|
8
|
+
:port => 5232,
|
9
|
+
:url => '/user/calendar/',
|
10
|
+
:user => 'user',
|
11
|
+
:password => 'yourpassword'
|
12
|
+
}
|
13
|
+
begin
|
14
|
+
require '~/.caldav_cred.rb'
|
15
|
+
rescue LoadError
|
16
|
+
end
|
17
|
+
|
18
|
+
@cal = CalDAV::Client.new $caldav[:host], $caldav[:port], $caldav[:url], $caldav[:user], $caldav[:password]
|
19
|
+
end
|
20
|
+
|
21
|
+
attr :cal
|
22
|
+
|
23
|
+
def test_create_event
|
24
|
+
myevent = Event.new
|
25
|
+
myevent.dtstart = DateTime.parse( '2012/08/11 09:45')
|
26
|
+
myevent.dtend = DateTime.parse( '2012/08/11 10:45')
|
27
|
+
myevent.summary = "Jo?"
|
28
|
+
|
29
|
+
uuid, response = cal.create myevent
|
30
|
+
|
31
|
+
puts "CREATE Response #{response.code} #{response.message}: #{response.body}"
|
32
|
+
puts "UUID: #{uuid}"
|
33
|
+
|
34
|
+
return uuid
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_delete_event( uuid )
|
38
|
+
response = cal.delete uuid
|
39
|
+
puts "DETELE Response #{response.code} #{response.message}: #{response.body}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_get_event( uuid )
|
43
|
+
#ev, response = cal.get uuid
|
44
|
+
ev = cal.get( uuid )
|
45
|
+
#puts "GET Response #{response.code} #{response.message}: #{response.body}"
|
46
|
+
puts ev
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_read_todo
|
50
|
+
puts '*' * 20 + ' TODO ' + '*' * 20
|
51
|
+
res = cal.todo
|
52
|
+
|
53
|
+
res.each{ |todo|
|
54
|
+
p todo
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_report
|
59
|
+
puts '*' * 20 + ' EVENTS ' + '*' * 20
|
60
|
+
p cal.report "20111201T000000", "20131231T000000"
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_query0
|
64
|
+
puts CalDAV::Query.event.to_xml
|
65
|
+
|
66
|
+
time1 = DateTime.parse('2011/08/08 09:45')
|
67
|
+
time2 = DateTime.parse('2012/08/08 10:45')
|
68
|
+
puts CalDAV::Query.event(time1..time2).to_xml
|
69
|
+
puts CalDAV::Query.event.uid( UUID.generate ).to_xml
|
70
|
+
#puts CalDAV::Query.todo.alarm(time1..time2).to_xml
|
71
|
+
#puts CalDAV::Query.event.attendee(email).partstat('NEEDS-ACTION').to_xml
|
72
|
+
#puts CalDAV::Query.todo.completed(false).status(:cancelled => false).to_xml
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_query
|
76
|
+
p cal.query( CalDAV::Query.event ) #=> All events
|
77
|
+
p cal.query( CalDAV::Query.event(time1..time2) )
|
78
|
+
p cal.query( CalDAV::Query.event.uid("UID") )
|
79
|
+
p cal.query( CalDAV::Query.todo.alarm(time1..time2) )
|
80
|
+
p cal.query( CalDAV::Query.event.attendee(email).partstat('NEEDS-ACTION') )
|
81
|
+
p cal.query( CalDAV::Query.todo.completed(false).status(:cancelled => false) )
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#r = CalDAV::Request::Report.new( "20111201T000000", "20131231T000000" )
|
86
|
+
#puts r.to_xml
|
87
|
+
#exit
|
88
|
+
|
89
|
+
t = CalDAVTester.new
|
90
|
+
|
91
|
+
t.test_query0
|
92
|
+
|
93
|
+
uuid = t.test_create_event
|
94
|
+
t.test_get_event( uuid )
|
95
|
+
t.test_delete_event( uuid )
|
96
|
+
|
97
|
+
t.test_report
|
98
|
+
t.test_read_todo
|
99
|
+
#t.test_query
|
100
|
+
|
data/lib/caldav.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uuid'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'rexml/xpath'
|
5
|
+
require 'date'
|
6
|
+
require 'icalendar'
|
7
|
+
require 'time'
|
8
|
+
|
9
|
+
['client.rb', 'request.rb', 'net.rb', 'query.rb', 'filter.rb', 'caldaver.rb', 'format.rb'].each do |f|
|
10
|
+
require File.join( File.dirname(__FILE__), 'caldav', f )
|
11
|
+
end
|
12
|
+
|
13
|
+
class Event
|
14
|
+
attr_accessor :uid, :created, :dtstart, :dtend, :lastmodified, :summary, :description, :name, :action
|
15
|
+
end
|
16
|
+
|
17
|
+
class Todo
|
18
|
+
attr_accessor :uid, :created, :summary, :dtstart, :status, :completed
|
19
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
# caldav --user USER --password PASSWORD --uri URI --command COMMAND
|
4
|
+
# caldav --user martin@solnet.cz --password test --uri https://mail.solnet.cz/caldav.php/martin.povolny@solnet.cz/test --command create_event
|
5
|
+
|
6
|
+
# caldav report --begin 2012-01-01 --end 2012-07-01 --format raw --user USER --password PASSWORD --uri https://in.solnet.cz/caldav.php/martin.povolny@solnet.cz/public
|
7
|
+
# caldav get --user USER --password PASSWD --uri https://mail.solnet.cz/caldav.php/martin.povolny%40solnet.cz/public/ --uuid 64d0b933-e916-4755-9e56-2f4d0d9068cb
|
8
|
+
|
9
|
+
module CalDAV
|
10
|
+
|
11
|
+
class CalDAVer
|
12
|
+
|
13
|
+
def create_object( options )
|
14
|
+
if options[:raw]
|
15
|
+
# STDIN
|
16
|
+
return STDIN.read(nil)
|
17
|
+
else
|
18
|
+
# options[:subject]
|
19
|
+
# options[:login]
|
20
|
+
# options[:summary]
|
21
|
+
# options[:begin]
|
22
|
+
# options[:end]
|
23
|
+
# options[:due]
|
24
|
+
case options[:what].intern
|
25
|
+
when :task
|
26
|
+
# FIXME
|
27
|
+
when :event
|
28
|
+
# FIXME
|
29
|
+
when :contact
|
30
|
+
# FIXME
|
31
|
+
else
|
32
|
+
print_help_and_exit if options[:command].to_s.empty? or options[:uri].to_s.empty?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def print_help_and_exit
|
38
|
+
puts @o.help
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
def run_args( args )
|
43
|
+
options = {}
|
44
|
+
@o = OptionParser.new do |o|
|
45
|
+
o.banner = "Usage: caldaver [command] [options]"
|
46
|
+
o.on('-p', '--password [STRING]', String, 'Password') { |p| options[:password] = p }
|
47
|
+
o.on('-u', '--user [STRING}', String, 'User (login)') { |l| options[:login] = l }
|
48
|
+
o.on('--uri [STRING]', String, 'Calendar URI') { |uri| options[:uri] = uri }
|
49
|
+
o.on('--format [STRING]', String, 'Format of output: raw,pretty,[debug]') {
|
50
|
+
|fmt| options[:fmt] = fmt }
|
51
|
+
##o.on('--command [STRING]', String, 'Command') { |c| options[:command] = c }
|
52
|
+
o.on('--uuid [STRING]', String, 'UUID') { |u| options[:uuid] = u }
|
53
|
+
# what to create
|
54
|
+
o.on('--what [STRING]', String, 'Event/task/contact') { |c| options[:command] = command }
|
55
|
+
o.on('--raw', 'Read raw data (event/task/contact) from STDIN') { |raw| options[:raw] = true }
|
56
|
+
# report and event options
|
57
|
+
o.on('--begin [DATETIME]', String, 'Start time') { |dt| options[:begin] = dt }
|
58
|
+
o.on('--end [DATETIME]', String, 'End time') { |dt| options[:end] = dt }
|
59
|
+
o.on('--due [DATETIME]', String, 'Due time') { |dt| options[:due] = dt }
|
60
|
+
#o.on('--begin [DATETIME]', DateTime, 'Start time') { |dt| options[:begin] = dt }
|
61
|
+
#o.on('--end [DATETIME]', DateTime, 'End time') { |dt| options[:end] = dt }
|
62
|
+
# event options
|
63
|
+
o.on('--summary [string]', String, 'summary of event/task') { |s| options[:summary] = s }
|
64
|
+
o.on('--location [string]', String, 'location of event/task') { |s| options[:location] = s }
|
65
|
+
o.on('--subject [string]', String, 'subject of event/task') { |s| options[:subject] = s }
|
66
|
+
o.on('-h') { print_help_and_exit }
|
67
|
+
end
|
68
|
+
|
69
|
+
options[:command] = @o.parse( args )[0]
|
70
|
+
|
71
|
+
print_help_and_exit if options[:command].to_s.empty? or options[:uri].to_s.empty?
|
72
|
+
cal = CalDAV::Client.new( options[:uri], options[:login], options[:password] )
|
73
|
+
|
74
|
+
if options[:format]
|
75
|
+
cal.format = case options[:format].intern
|
76
|
+
when :raw
|
77
|
+
CalDAV::Format::Raw.new
|
78
|
+
when :pretty
|
79
|
+
CalDAV::Format::Pretty.new
|
80
|
+
when :debug
|
81
|
+
CalDAV::Format::Debug.new
|
82
|
+
else
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
case options[:command].intern
|
88
|
+
when :create
|
89
|
+
obj = create_object( options )
|
90
|
+
when :delete
|
91
|
+
when :modify
|
92
|
+
obj = create_object( options )
|
93
|
+
when :get
|
94
|
+
puts cal.get( options[:uuid] )
|
95
|
+
when :report
|
96
|
+
puts cal.report( options[:begin], options[:end] )
|
97
|
+
else
|
98
|
+
print_help_and_exit
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
module CalDAV
|
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( *args )
|
15
|
+
case args.length
|
16
|
+
when 3
|
17
|
+
__init_from_uri( *args )
|
18
|
+
when 5
|
19
|
+
__init_from_host_port( *args )
|
20
|
+
else
|
21
|
+
raise "#{self.class.to_s}: invalid number of arguments: #{args.length}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def __init_from_uri( suri, user, password )
|
26
|
+
uri = URI( suri )
|
27
|
+
@host = uri.host
|
28
|
+
@port = uri.port
|
29
|
+
@url = [ uri.scheme, '://', uri.host, uri.path ].join('') # FIXME: port?
|
30
|
+
@user = user
|
31
|
+
@password = password
|
32
|
+
@ssl = uri.scheme == 'https'
|
33
|
+
end
|
34
|
+
|
35
|
+
def __init_from_host_port( host, port, url, user, password )
|
36
|
+
@host = host
|
37
|
+
@port = port
|
38
|
+
@url = url
|
39
|
+
@user = user
|
40
|
+
@password = password
|
41
|
+
@ssl = port == 443
|
42
|
+
end
|
43
|
+
|
44
|
+
def __create_http
|
45
|
+
http = Net::HTTP.new(@host, @port)
|
46
|
+
http.use_ssl = @ssl
|
47
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
48
|
+
#http.set_debug_output $stderr
|
49
|
+
http
|
50
|
+
end
|
51
|
+
|
52
|
+
def report start, stop
|
53
|
+
res = nil
|
54
|
+
__create_http.start {|http|
|
55
|
+
req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
|
56
|
+
req.basic_auth @user, @password
|
57
|
+
req.body = CalDAV::Request::ReportVEVENT.new( start, stop ).to_xml
|
58
|
+
res = http.request( req )
|
59
|
+
}
|
60
|
+
format.parse_calendar( res.body )
|
61
|
+
end
|
62
|
+
|
63
|
+
def get uuid
|
64
|
+
res = nil
|
65
|
+
__create_http.start {|http|
|
66
|
+
req = Net::HTTP::Get.new("#{@url}/#{uuid}.ics")
|
67
|
+
req.basic_auth @user, @password
|
68
|
+
res = http.request( req )
|
69
|
+
}
|
70
|
+
|
71
|
+
# FIXME: process HTTP code
|
72
|
+
format.parse_single( res.body )
|
73
|
+
end
|
74
|
+
|
75
|
+
def delete uuid
|
76
|
+
__create_http.start {|http|
|
77
|
+
req = Net::HTTP::Delete.new("#{@url}/#{uuid}.ics")
|
78
|
+
req.basic_auth @user, @password
|
79
|
+
res = http.request( req )
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def create event
|
84
|
+
nowstr = DateTime.now.strftime "%Y%m%dT%H%M%SZ"
|
85
|
+
uuid = UUID.generate
|
86
|
+
dings = """BEGIN:VCALENDAR
|
87
|
+
PRODID:Caldav.rb
|
88
|
+
VERSION:2.0
|
89
|
+
BEGIN:VEVENT
|
90
|
+
CREATED:#{nowstr}
|
91
|
+
UID:#{uuid}
|
92
|
+
SUMMARY:#{event.summary}
|
93
|
+
DTSTART:#{event.dtstart.strftime("%Y%m%dT%H%M%S")}
|
94
|
+
DTEND:#{event.dtend.strftime("%Y%m%dT%H%M%S")}
|
95
|
+
END:VEVENT
|
96
|
+
END:VCALENDAR"""
|
97
|
+
|
98
|
+
res = nil
|
99
|
+
http = Net::HTTP.new(@host, @port)
|
100
|
+
__create_http.start { |http|
|
101
|
+
req = Net::HTTP::Put.new("#{@url}/#{uuid}.ics")
|
102
|
+
req['Content-Type'] = 'text/calendar'
|
103
|
+
req.basic_auth @user, @password
|
104
|
+
req.body = dings
|
105
|
+
res = http.request( req )
|
106
|
+
}
|
107
|
+
return uuid, res
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_alarm tevent, altCal="Calendar"
|
111
|
+
#[#<Icalendar::Alarm:0x10b9d1b90 @name=\"VALARM\", @components={}, @properties={\"trigger\"=>\"-PT5M\", \"action\"=>\"DISPLAY\", \"description\"=>\"\"}>]
|
112
|
+
dtstart_string = ( Time.parse(tevent.dtstart.to_s) + Time.now.utc_offset.to_i.abs ).strftime "%Y%m%dT%H%M%S"
|
113
|
+
dtend_string = ( Time.parse(tevent.dtend.to_s) + Time.now.utc_offset.to_i.abs ).strftime "%Y%m%dT%H%M%S"
|
114
|
+
alarmText = <<EOL
|
115
|
+
BEGIN:VCALENDAR
|
116
|
+
VERSION:2.0
|
117
|
+
PRODID:Ruby iCalendar
|
118
|
+
BEGIN:VEVENT
|
119
|
+
UID:#{tevent.uid}
|
120
|
+
SUMMARY:#{tevent.summary}
|
121
|
+
DESCRIPTION:#{tevent.description}
|
122
|
+
DTSTART:#{dtstart_string}
|
123
|
+
DTEND:#{dtend_string}
|
124
|
+
BEGIN:VALARM
|
125
|
+
ACTION:DISPLAY
|
126
|
+
TRIGGER;RELATED=START:-PT5M
|
127
|
+
DESCRIPTION:Reminder
|
128
|
+
END:VALARM
|
129
|
+
BEGIN:VALARM
|
130
|
+
TRIGGER:-PT5M
|
131
|
+
ACTION:EMAIL
|
132
|
+
ATTENDEE:#{tevent.organizer}
|
133
|
+
SUMMARY:#{tevent.summary}
|
134
|
+
DESCRIPTION:#{tevent.description}
|
135
|
+
TRIGGER:-PT5M
|
136
|
+
END:VALARM
|
137
|
+
END:VEVENT
|
138
|
+
END:VCALENDAR
|
139
|
+
EOL
|
140
|
+
p alarmText
|
141
|
+
res = nil
|
142
|
+
puts "#{@url}/#{tevent.uid}.ics"
|
143
|
+
thttp = __create_http.start
|
144
|
+
#thttp.set_debug_output $stderr
|
145
|
+
req = Net::HTTP::Put.new("#{@url}/#{tevent.uid}.ics", initheader = {'Content-Type'=>'text/calendar'} )
|
146
|
+
req.basic_auth @user, @password
|
147
|
+
req.body = alarmText
|
148
|
+
res = thttp.request( req )
|
149
|
+
p res.inspect
|
150
|
+
|
151
|
+
return tevent.uid
|
152
|
+
end
|
153
|
+
|
154
|
+
def update event
|
155
|
+
dings = """BEGIN:VCALENDAR
|
156
|
+
PRODID:Caldav.rb
|
157
|
+
VERSION:2.0
|
158
|
+
|
159
|
+
BEGIN:VTIMEZONE
|
160
|
+
TZID:/Europe/Vienna
|
161
|
+
X-LIC-LOCATION:Europe/Vienna
|
162
|
+
BEGIN:DAYLIGHT
|
163
|
+
TZOFFSETFROM:+0100
|
164
|
+
TZOFFSETTO:+0200
|
165
|
+
TZNAME:CEST
|
166
|
+
DTSTART:19700329T020000
|
167
|
+
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
|
168
|
+
END:DAYLIGHT
|
169
|
+
BEGIN:STANDARD
|
170
|
+
TZOFFSETFROM:+0200
|
171
|
+
TZOFFSETTO:+0100
|
172
|
+
TZNAME:CET
|
173
|
+
DTSTART:19701025T030000
|
174
|
+
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
|
175
|
+
END:STANDARD
|
176
|
+
END:VTIMEZONE
|
177
|
+
|
178
|
+
BEGIN:VEVENT
|
179
|
+
CREATED:#{event.created}
|
180
|
+
UID:#{event.uid}
|
181
|
+
SUMMARY:#{event.summary}
|
182
|
+
DTSTART;TZID=Europe/Vienna:#{event.dtstart}
|
183
|
+
DTEND;TZID=Europe/Vienna:#{event.dtend.rfc3339}
|
184
|
+
END:VEVENT
|
185
|
+
END:VCALENDAR"""
|
186
|
+
|
187
|
+
res = nil
|
188
|
+
__create_http.start {|http|
|
189
|
+
req = Net::HTTP::Put.new("#{@url}/#{event.uid}.ics", initheader = {'Content-Type'=>'text/calendar'} )
|
190
|
+
req.basic_auth @user, @password
|
191
|
+
req.body = dings
|
192
|
+
res = http.request( req )
|
193
|
+
}
|
194
|
+
return event.uid
|
195
|
+
end
|
196
|
+
|
197
|
+
def todo
|
198
|
+
res = nil
|
199
|
+
__create_http.start {|http|
|
200
|
+
req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
|
201
|
+
req.basic_auth @user, @password
|
202
|
+
req.body = CalDAV::Request::ReportVTODO.new.to_xml
|
203
|
+
res = http.request( req )
|
204
|
+
}
|
205
|
+
# FIXME: process HTTP code
|
206
|
+
format.parse_todo( res.body )
|
207
|
+
end
|
208
|
+
|
209
|
+
def filterTimezone( vcal )
|
210
|
+
data = ""
|
211
|
+
inTZ = false
|
212
|
+
vcal.split("\n").each{ |l|
|
213
|
+
inTZ = true if l.index("BEGIN:VTIMEZONE")
|
214
|
+
data << l+"\n" unless inTZ
|
215
|
+
inTZ = false if l.index("END:VTIMEZONE")
|
216
|
+
}
|
217
|
+
return data
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module CalDAV
|
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,64 @@
|
|
1
|
+
module CalDAV
|
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( body )
|
14
|
+
result = []
|
15
|
+
xml = REXML::Document.new( res.body )
|
16
|
+
REXML::XPath.each( xml, '//c:calendar-data/', { "c"=>"urn:ietf:params:xml:ns:caldav"} ){ |c|
|
17
|
+
result += parse_events( c.text )
|
18
|
+
}
|
19
|
+
return result
|
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
|
+
return_events = Array.new
|
46
|
+
cals = Icalendar.parse(vcal)
|
47
|
+
cals.each { |tcal|
|
48
|
+
tcal.events.each { |tevent|
|
49
|
+
if tevent.recurrence_id.to_s.empty? # skip recurring events
|
50
|
+
return_events << tevent
|
51
|
+
end
|
52
|
+
}
|
53
|
+
}
|
54
|
+
return return_events
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_single( body )
|
58
|
+
# FIXME: parse event/todo/vcard
|
59
|
+
parse_events( body )
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/lib/caldav/net.rb
ADDED
@@ -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
|
+
|
data/lib/caldav/query.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module CalDAV
|
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", CalDAV::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 CalDAV
|
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
|
data/ruby-caldav.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../lib/caldav/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "agcaldav"
|
5
|
+
s.version = CalDAV::VERSION
|
6
|
+
s.summary = "Ruby CalDAV client"
|
7
|
+
s.description = "Ruby client for CalDAV calendar and tasks."
|
8
|
+
s.homepage = "https://github.com/agilastic/ruby-caldav"
|
9
|
+
s.authors = ["Alex Ebeling-Hoppe"]
|
10
|
+
s.email = ["ebeling-hoppe@agilastic.de"]
|
11
|
+
|
12
|
+
s.add_runtime_dependency 'nokogiri'
|
13
|
+
s.add_runtime_dependency 'icalendar'
|
14
|
+
s.add_runtime_dependency 'uuid'
|
15
|
+
s.add_runtime_dependency 'builder'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: agcaldav
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alex Ebeling-Hoppe
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: icalendar
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: uuid
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: builder
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Ruby client for CalDAV calendar and tasks.
|
79
|
+
email:
|
80
|
+
- ebeling-hoppe@agilastic.de
|
81
|
+
executables:
|
82
|
+
- caldav
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- README.rdoc
|
88
|
+
- bin/caldav
|
89
|
+
- caldavtest.rb
|
90
|
+
- lib/caldav.rb
|
91
|
+
- lib/caldav/caldaver.rb
|
92
|
+
- lib/caldav/client.rb
|
93
|
+
- lib/caldav/filter.rb
|
94
|
+
- lib/caldav/format.rb
|
95
|
+
- lib/caldav/net.rb
|
96
|
+
- lib/caldav/query.rb
|
97
|
+
- lib/caldav/request.rb
|
98
|
+
- lib/caldav/version.rb
|
99
|
+
- ruby-caldav.gemspec
|
100
|
+
homepage: https://github.com/agilastic/ruby-caldav
|
101
|
+
licenses: []
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.8.24
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: Ruby CalDAV client
|
124
|
+
test_files: []
|