Class: Async::Caldav::Client
- Inherits:
-
Object
- Object
- Async::Caldav::Client
- 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
-
#user ⇒ Object
readonly
Returns the value of attribute user.
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
— Create —.
-
#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
— Discovery —.
-
#request(method, path, body: nil, headers: {}) ⇒ Object
— HTTP transport —.
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 = headers @http = nil end |
Instance Attribute Details
#user ⇒ Object (readonly)
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 119 |
# File 'lib/async/caldav/client.rb', line 102 def create_addressbook(name, displayname: nil) path = "/addressbooks/#{@user}/#{name}/" x = Builder::XmlMarkup.new x.instruct! :xml, version: "1.0", encoding: "UTF-8" x.tag!("d:mkcol", "xmlns:d" => "DAV:", "xmlns:cr" => "urn:ietf:params:xml:ns:carddav") do x.tag!("d:set") do x.tag!("d:prop") do x.tag!("d:resourcetype") { x.tag!("d:collection"); x.tag!("cr:addressbook") } x.tag!("d:displayname", displayname || name) end end end status, = request('MKCOL', path, body: x.target!, 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
— Create —
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}/" x = Builder::XmlMarkup.new x.instruct! :xml, version: "1.0", encoding: "UTF-8" x.tag!("c:mkcalendar", "xmlns:d" => "DAV:", "xmlns:c" => "urn:ietf:params:xml:ns:caldav") do x.tag!("d:set") do x.tag!("d:prop") do x.tag!("d:displayname", displayname || name) if displayname || name x.tag!("c:calendar-description", description) if description end end end status, = request('MKCALENDAR', path, body: x.target!, 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
177 178 179 180 181 182 183 184 185 |
# File 'lib/async/caldav/client.rb', line 177 def parse_collection_props(xml) props = {} props[:displayname] = Protocol::Caldav::Xml.extract_value(xml, 'displayname') props[:description] = Protocol::Caldav::Xml.extract_value(xml, 'calendar-description') props[:color] = Protocol::Caldav::Xml.extract_value(xml, 'calendar-color') ctag = Protocol::Caldav::Xml.extract_value(xml, 'getctag') props[:ctag] = ctag if ctag props end |
#parse_multistatus_items(xml, data_tag:) ⇒ Object
— Response parsing (used by Calendar/Addressbook) —
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/async/caldav/client.rb', line 139 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 # Unescape XML entities in the body if body body = body.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"') end items << { path: href, body: body, etag: etag } end items end |
#parse_sync_items(xml) ⇒ Object
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/async/caldav/client.rb', line 160 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
— Discovery —
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
— HTTP transport —
123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/async/caldav/client.rb', line 123 def request(method, path, body: nil, headers: {}) http = connect req = build_request(method, path, body, headers) response = http.request(req) status = response.code.to_i resp_headers = {} response.each_header { |k, v| resp_headers[k] = v } raise Unauthorized, "401 Unauthorized" if status == 401 [status, resp_headers, response.body || ''] end |