Class: Async::Caldav::Client
- Inherits:
-
Object
- Object
- Async::Caldav::Client
show all
- Defined in:
- lib/async/caldav/client.rb,
lib/async/caldav/client/calendar.rb,
lib/async/caldav/client/addressbook.rb
Defined Under Namespace
Classes: Addressbook, Calendar, Conflict, Error, InvalidSyncToken, NotFound, PreconditionFailed, Unauthorized
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
-
#addressbook(name) ⇒ Object
-
#addressbooks ⇒ Object
-
#calendar(name) ⇒ Object
-
#calendars ⇒ Object
-
#close ⇒ Object
-
#create_addressbook(name, displayname: nil) ⇒ Object
-
#create_calendar(name, displayname: nil, description: nil, color: nil) ⇒ Object
-
#initialize(base_url, user:, password: nil, headers: {}) ⇒ Client
constructor
A new instance of Client.
-
#parse_collection_props(xml) ⇒ Object
-
#parse_multistatus_items(xml, data_tag:) ⇒ Object
— Response parsing (used by Calendar/Addressbook) —.
-
#parse_sync_items(xml) ⇒ Object
-
#principal ⇒ Object
-
#request(method, path, body: nil, headers: {}) ⇒ Object
Constructor Details
#initialize(base_url, user:, password: nil, headers: {}) ⇒ Client
Returns a new instance of Client.
25
26
27
28
29
30
31
|
# File 'lib/async/caldav/client.rb', line 25
def initialize(base_url, user:, password: nil, headers: {})
@uri = URI.parse(base_url)
@user = user
@password = password
@extra_headers =
@http = nil
end
|
Instance Attribute Details
#user ⇒ Object
Returns the value of attribute user.
23
24
25
|
# File 'lib/async/caldav/client.rb', line 23
def user
@user
end
|
Class Method Details
.open(base_url, **opts) ⇒ Object
33
34
35
36
37
38
39
40
41
42
|
# File 'lib/async/caldav/client.rb', line 33
def self.open(base_url, **opts)
client = new(base_url, **opts)
return client unless block_given?
begin
yield client
ensure
client.close
end
end
|
Instance Method Details
#addressbook(name) ⇒ Object
77
78
79
|
# File 'lib/async/caldav/client.rb', line 77
def addressbook(name)
Addressbook.new(self, "/addressbooks/#{@user}/#{name}/")
end
|
#addressbooks ⇒ Object
65
66
67
68
69
70
71
|
# File 'lib/async/caldav/client.rb', line 65
def addressbooks
path = "/addressbooks/#{@user}/"
status, _, body = request('PROPFIND', path, headers: { 'Depth' => '1' })
raise Error, "PROPFIND failed: #{status}" unless status == 207
parse_collections(body, path, type: :addressbook)
end
|
#calendar(name) ⇒ Object
73
74
75
|
# File 'lib/async/caldav/client.rb', line 73
def calendar(name)
Calendar.new(self, "/calendars/#{@user}/#{name}/")
end
|
#calendars ⇒ Object
57
58
59
60
61
62
63
|
# File 'lib/async/caldav/client.rb', line 57
def calendars
path = "/calendars/#{@user}/"
status, _, body = request('PROPFIND', path, headers: { 'Depth' => '1' })
raise Error, "PROPFIND failed: #{status}" unless status == 207
parse_collections(body, path, type: :calendar)
end
|
#close ⇒ Object
44
45
46
47
|
# File 'lib/async/caldav/client.rb', line 44
def close
@http&.finish if @http&.started?
@http = nil
end
|
#create_addressbook(name, displayname: nil) ⇒ Object
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
# File 'lib/async/caldav/client.rb', line 102
def create_addressbook(name, displayname: nil)
path = "/addressbooks/#{@user}/#{name}/"
body = <<~XML
<?xml version="1.0" encoding="UTF-8"?>
<d:mkcol xmlns:d="DAV:" xmlns:cr="urn:ietf:params:xml:ns:carddav">
<d:set><d:prop>
<d:resourcetype><d:collection/><cr:addressbook/></d:resourcetype>
<d:displayname>#{Protocol::Caldav::Xml.escape(displayname || name)}</d:displayname>
</d:prop></d:set>
</d:mkcol>
XML
status, = request('MKCOL', path, body: body, headers: { 'Content-Type' => 'text/xml' })
raise Error, "MKCOL failed: #{status}" unless status == 201
Addressbook.new(self, path, displayname: displayname || name)
end
|
#create_calendar(name, displayname: nil, description: nil, color: nil) ⇒ Object
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
# File 'lib/async/caldav/client.rb', line 83
def create_calendar(name, displayname: nil, description: nil, color: nil)
path = "/calendars/#{@user}/#{name}/"
props = []
props << "<d:displayname>#{Protocol::Caldav::Xml.escape(displayname || name)}</d:displayname>" if displayname || name
props << "<c:calendar-description>#{Protocol::Caldav::Xml.escape(description)}</c:calendar-description>" if description
body = <<~XML
<?xml version="1.0" encoding="UTF-8"?>
<c:mkcalendar xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
<d:set><d:prop>#{props.join}</d:prop></d:set>
</c:mkcalendar>
XML
status, = request('MKCALENDAR', path, body: body, headers: { 'Content-Type' => 'text/xml' })
raise Error, "MKCALENDAR failed: #{status}" unless status == 201
Calendar.new(self, path, displayname: displayname || name, description: description, color: color)
end
|
#parse_collection_props(xml) ⇒ Object
176
177
178
179
180
181
182
183
184
|
# File 'lib/async/caldav/client.rb', line 176
def parse_collection_props(xml)
props = {}
props[:displayname] = Protocol::Caldav::Xml.(xml, 'displayname')
props[:description] = Protocol::Caldav::Xml.(xml, 'calendar-description')
props[:color] = Protocol::Caldav::Xml.(xml, 'calendar-color')
ctag = Protocol::Caldav::Xml.(xml, 'getctag')
props[:ctag] = ctag if ctag
props
end
|
#parse_multistatus_items(xml, data_tag:) ⇒ Object
— Response parsing (used by Calendar/Addressbook) —
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
# File 'lib/async/caldav/client.rb', line 138
def parse_multistatus_items(xml, data_tag:)
items = []
xml.scan(/<[^>]*response[^>]*>(.*?)<\/[^>]*response>/m).each do |match|
resp = match[0]
href = resp.match(/<[^>]*href[^>]*>([^<]+)</)[1]&.strip rescue nil
next unless href
etag = resp.match(/<[^>]*getetag[^>]*>([^<]+)</)[1]&.strip rescue nil
data = resp.match(/<[^>]*#{data_tag}[^>]*>(.*?)<\/[^>]*#{data_tag}>/m)
body = data ? data[1].strip : nil
if body
body = body.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"')
end
items << { path: href, body: body, etag: etag }
end
items
end
|
#parse_sync_items(xml) ⇒ Object
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# File 'lib/async/caldav/client.rb', line 159
def parse_sync_items(xml)
items = []
xml.scan(/<[^>]*response[^>]*>(.*?)<\/[^>]*response>/m).each do |match|
resp = match[0]
href = resp.match(/<[^>]*href[^>]*>([^<]+)</)[1]&.strip rescue nil
next unless href
if resp.include?('404')
items << { path: href, status: 404 }
else
etag = resp.match(/<[^>]*getetag[^>]*>([^<]+)</)[1]&.strip rescue nil
items << { path: href, status: 200, etag: etag }
end
end
items
end
|
#principal ⇒ Object
51
52
53
54
55
|
# File 'lib/async/caldav/client.rb', line 51
def principal
_, _, body = request('PROPFIND', '/', headers: { 'Depth' => '0' })
match = body.match(/<[^>]*current-user-principal[^>]*>\s*<[^>]*href[^>]*>([^<]+)</)
match ? match[1].strip : "/#{@user}/"
end
|
#request(method, path, body: nil, headers: {}) ⇒ Object
122
123
124
125
126
127
128
129
130
131
132
133
134
|
# File 'lib/async/caldav/client.rb', line 122
def request(method, path, body: nil, headers: {})
http = connect
req = build_request(method, path, body, )
response = http.request(req)
status = response.code.to_i
= {}
response. { |k, v| [k] = v }
raise Unauthorized, "401 Unauthorized" if status == 401
[status, , response.body || '']
end
|