agcaldav 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +29 -5
- data/lib/agcaldav/client.rb +153 -146
- data/lib/agcaldav/event.rb +121 -0
- data/lib/agcaldav/format.rb +51 -55
- data/lib/agcaldav/version.rb +1 -1
- data/lib/agcaldav.rb +1 -9
- metadata +2 -1
data/README.md
CHANGED
@@ -27,23 +27,47 @@ Alternatively, the proxy parameters can be specified:
|
|
27
27
|
|
28
28
|
result = cal.create_event(:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "sdkvjsdf sdkf sdkfj sdkf dsfj")
|
29
29
|
|
30
|
-
|
30
|
+
Analyze result:
|
31
|
+
|
32
|
+
>> result.class
|
33
|
+
=> Icalendar::Calendar
|
34
|
+
|
35
|
+
>> result.events.count
|
36
|
+
=> 1
|
37
|
+
|
38
|
+
>> result.events.first
|
39
|
+
=> #<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"}>
|
31
40
|
|
32
|
-
|
41
|
+
get UID of this Event:
|
42
|
+
>> result.events.first.uid
|
43
|
+
=> "e795c480-34e0-0130-7d1d-109add70606c"
|
33
44
|
|
34
45
|
|
35
46
|
Find Event:
|
36
47
|
|
37
|
-
r = cal.find_event(
|
48
|
+
r = cal.find_event("e795c480-34e0-0130-7d1d-109add70606c")
|
49
|
+
|
50
|
+
>> result.class
|
51
|
+
=> Icalendar::Calendar
|
38
52
|
|
39
53
|
|
40
54
|
Find Events within time interval:
|
41
55
|
|
42
56
|
result = cal.find_events(:start => "2012-10-01 08:00", :end => "2013-01-01")
|
43
|
-
#TODO.... (no XML -> Icalendar with multiple events....)
|
44
57
|
|
45
58
|
|
59
|
+
####TODO's
|
60
|
+
|
61
|
+
###############################################################
|
62
|
+
# #
|
63
|
+
# TODO : #
|
64
|
+
# 1. find and notify if overlapping events #
|
65
|
+
# 2. "create_event" check for UUID is really unique #
|
66
|
+
# 3. errorhandling & code cleanup #
|
67
|
+
# #
|
68
|
+
###############################################################
|
46
69
|
|
47
|
-
... next tomorrow ...
|
48
70
|
|
71
|
+
####Licence
|
49
72
|
|
73
|
+
-> MIT
|
data/lib/agcaldav/client.rb
CHANGED
@@ -1,152 +1,159 @@
|
|
1
1
|
module AgCalDAV
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
def todo
|
127
|
-
res = nil
|
128
|
-
__create_http.start {|http|
|
129
|
-
req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
|
130
|
-
req.basic_auth @user, @password
|
131
|
-
req.body = AgCalDAV::Request::ReportVTODO.new.to_xml
|
132
|
-
res = http.request( req )
|
133
|
-
}
|
134
|
-
# FIXME: process HTTP code
|
135
|
-
format.parse_todo( res.body )
|
136
|
-
end
|
137
|
-
|
138
|
-
def filterTimezone( vcal )
|
139
|
-
data = ""
|
140
|
-
inTZ = false
|
141
|
-
vcal.split("\n").each{ |l|
|
142
|
-
inTZ = true if l.index("BEGIN:VTIMEZONE")
|
143
|
-
data << l+"\n" unless inTZ
|
144
|
-
inTZ = false if l.index("END:VTIMEZONE")
|
145
|
-
}
|
146
|
-
return data
|
147
|
-
end
|
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
|
+
uri = URI(data[:uri])
|
21
|
+
@host = uri.host
|
22
|
+
@port = uri.port.to_i
|
23
|
+
@url = uri.path
|
24
|
+
@user = data[:user]
|
25
|
+
@password = data[:password]
|
26
|
+
@ssl = uri.scheme == 'https'
|
27
|
+
end
|
28
|
+
|
29
|
+
def __create_http
|
30
|
+
if @proxy_uri.nil?
|
31
|
+
http = Net::HTTP.new(@host, @port)
|
32
|
+
else
|
33
|
+
http = Net::HTTP.new(@host, @port, @proxy_host, @proxy_port)
|
34
|
+
end
|
35
|
+
if @ssl
|
36
|
+
http.use_ssl = @ssl
|
37
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
38
|
+
end
|
39
|
+
http
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_events data
|
43
|
+
result = ""
|
44
|
+
res = nil
|
45
|
+
__create_http.start {|http|
|
46
|
+
req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
|
47
|
+
req.basic_auth @user, @password
|
48
|
+
req.body = AgCalDAV::Request::ReportVEVENT.new(DateTime.parse(data[:start]).strftime("%Y%m%dT%H%M"),
|
49
|
+
DateTime.parse(data[:end]).strftime("%Y%m%dT%H%M") ).to_xml
|
50
|
+
res = http.request(req)
|
51
|
+
s = res.body
|
52
|
+
result = ""
|
53
|
+
xml = REXML::Document.new(s)
|
54
|
+
REXML::XPath.each( xml, '//c:calendar-data/', {"c"=>"urn:ietf:params:xml:ns:caldav"} ){|c| result << c.text}
|
55
|
+
r = Icalendar.parse(result)
|
56
|
+
r.first
|
57
|
+
|
58
|
+
}
|
59
|
+
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_event 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
|
+
raise AuthenticationError if res.code.to_i == 401
|
71
|
+
raise APIError if res.code.to_i >= 500
|
72
|
+
r = Icalendar.parse(res.body)
|
73
|
+
r.first
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete_event uuid
|
77
|
+
__create_http.start {|http|
|
78
|
+
req = Net::HTTP::Delete.new("#{@url}/#{uuid}.ics")
|
79
|
+
req.basic_auth @user, @password
|
80
|
+
res = http.request( req )
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_event event
|
85
|
+
c = Calendar.new
|
86
|
+
uuid = UUID.new.generate
|
87
|
+
c.event do
|
88
|
+
uid uuid # still a BUG
|
89
|
+
dtstart DateTime.parse(event[:start])
|
90
|
+
dtend DateTime.parse(event[:end])
|
91
|
+
duration event[:duration]
|
92
|
+
summary event[:title]
|
93
|
+
description event[:description]
|
94
|
+
klass event[:accessibility] #PUBLIC, PRIVATE, CONFIDENTIAL
|
95
|
+
location event[:location]
|
96
|
+
geo_location event[:geo_location]
|
97
|
+
status event[:status]
|
98
|
+
end
|
99
|
+
c.publish
|
100
|
+
c.event.uid = uuid
|
101
|
+
cstring = c.to_ical
|
102
|
+
res = nil
|
103
|
+
http = Net::HTTP.new(@host, @port)
|
104
|
+
__create_http.start { |http|
|
105
|
+
req = Net::HTTP::Put.new("#{@url}/#{uuid}.ics")
|
106
|
+
req['Content-Type'] = 'text/calendar'
|
107
|
+
req.basic_auth @user, @password
|
108
|
+
req.body = cstring
|
109
|
+
res = http.request( req )
|
110
|
+
}
|
111
|
+
raise AuthenticationError if res.code.to_i == 401
|
112
|
+
raise APIError if res.code.to_i >= 500
|
113
|
+
find_event uuid
|
114
|
+
#{:uid => uuid, :cal => c, :cal_string => cstring, :response_code => res.code} #TODO
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_alarm tevent, altCal="Calendar"
|
118
|
+
# FIXME create icalendar event -> cal.event.new (tevent)
|
119
|
+
|
120
|
+
# TODO
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
148
125
|
end
|
149
126
|
|
127
|
+
def update event
|
128
|
+
# FIXME old one not neat
|
129
|
+
|
130
|
+
# TODO
|
131
|
+
end
|
132
|
+
|
133
|
+
def todo
|
134
|
+
res = nil
|
135
|
+
__create_http.start {|http|
|
136
|
+
req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
|
137
|
+
req.basic_auth @user, @password
|
138
|
+
req.body = AgCalDAV::Request::ReportVTODO.new.to_xml
|
139
|
+
res = http.request( req )
|
140
|
+
}
|
141
|
+
# FIXME: process HTTP code
|
142
|
+
format.parse_todo( res.body )
|
143
|
+
end
|
144
|
+
|
145
|
+
def filterTimezone( vcal )
|
146
|
+
data = ""
|
147
|
+
inTZ = false
|
148
|
+
vcal.split("\n").each{ |l|
|
149
|
+
inTZ = true if l.index("BEGIN:VTIMEZONE")
|
150
|
+
data << l+"\n" unless inTZ
|
151
|
+
inTZ = false if l.index("END:VTIMEZONE")
|
152
|
+
}
|
153
|
+
return data
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
150
157
|
class AgCalDAVError < StandardError
|
151
158
|
end
|
152
159
|
class AuthenticationError < AgCalDAVError; 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
|
data/lib/agcaldav/format.rb
CHANGED
@@ -1,64 +1,60 @@
|
|
1
1
|
module AgCalDAV
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
8
11
|
|
9
|
-
|
10
|
-
|
12
|
+
class Pretty < Raw
|
13
|
+
def parse_calendar(s)
|
14
|
+
result = ""
|
15
|
+
xml = REXML::Document.new(s)
|
16
|
+
puts "-"*10
|
17
|
+
p s
|
18
|
+
puts "-"*10
|
19
|
+
REXML::XPath.each( xml, '//c:calendar-data/', {"c"=>"urn:ietf:params:xml:ns:caldav"} ){|c| result << c.text}
|
20
|
+
r = Icalendar.parse(result)
|
11
21
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
result += parse_events( c.text )
|
18
|
-
}
|
19
|
-
return result
|
20
|
-
end
|
22
|
+
puts "."*10
|
23
|
+
p r
|
24
|
+
puts "."*10
|
25
|
+
r
|
26
|
+
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
def parse_todo( body )
|
29
|
+
result = []
|
30
|
+
xml = REXML::Document.new( body )
|
31
|
+
REXML::XPath.each( xml, '//c:calendar-data/', { "c"=>"urn:ietf:params:xml:ns:caldav"} ){ |c|
|
32
|
+
p c.text
|
33
|
+
p parse_tasks( c.text )
|
34
|
+
result += parse_tasks( c.text )
|
35
|
+
}
|
36
|
+
return result
|
37
|
+
end
|
32
38
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
def parse_tasks( vcal )
|
40
|
+
return_tasks = Array.new
|
41
|
+
cals = Icalendar.parse(vcal)
|
42
|
+
cals.each { |tcal|
|
43
|
+
tcal.todos.each { |ttask| # FIXME
|
44
|
+
return_tasks << ttask
|
45
|
+
}
|
46
|
+
}
|
47
|
+
return return_tasks
|
48
|
+
end
|
43
49
|
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
50
|
+
def parse_events( vcal )
|
51
|
+
Icalendar.parse(vcal)
|
52
|
+
end
|
56
53
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
54
|
+
def parse_single( body )
|
55
|
+
# FIXME: parse event/todo/vcard
|
56
|
+
parse_events( body )
|
57
|
+
end
|
62
58
|
end
|
59
|
+
end
|
63
60
|
end
|
64
|
-
|
data/lib/agcaldav/version.rb
CHANGED
data/lib/agcaldav.rb
CHANGED
@@ -6,14 +6,6 @@ require 'icalendar'
|
|
6
6
|
require 'time'
|
7
7
|
require 'date'
|
8
8
|
|
9
|
-
['client.rb', 'request.rb', 'net.rb', 'query.rb', 'filter.rb', 'format.rb'].each do |f|
|
9
|
+
['client.rb', 'request.rb', 'net.rb', 'query.rb', 'filter.rb', 'event.rb', 'format.rb'].each do |f|
|
10
10
|
require File.join( File.dirname(__FILE__), 'agcaldav', f )
|
11
|
-
end
|
12
|
-
|
13
|
-
class Event
|
14
|
-
attr_accessor :uid, :created, :dtstart, :dtend, :lastmodified, :summary, :description, :name, :action, :to_ical
|
15
|
-
end
|
16
|
-
|
17
|
-
class Todo
|
18
|
-
attr_accessor :uid, :created, :summary, :dtstart, :status, :completed
|
19
11
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: agcaldav
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -86,6 +86,7 @@ files:
|
|
86
86
|
- agcaldav.gemspec
|
87
87
|
- lib/agcaldav.rb
|
88
88
|
- lib/agcaldav/client.rb
|
89
|
+
- lib/agcaldav/event.rb
|
89
90
|
- lib/agcaldav/filter.rb
|
90
91
|
- lib/agcaldav/format.rb
|
91
92
|
- lib/agcaldav/net.rb
|