calendav 0.3.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +79 -9
- data/lib/calendav/calendar.rb +2 -2
- data/lib/calendav/client.rb +10 -5
- data/lib/calendav/clients/calendars_client.rb +1 -1
- data/lib/calendav/clients/events_client.rb +14 -6
- data/lib/calendav/clients/todos_client.rb +73 -0
- data/lib/calendav/credentials/apple.rb +1 -1
- data/lib/calendav/credentials/fastmail.rb +1 -1
- data/lib/calendav/credentials/google.rb +1 -1
- data/lib/calendav/endpoint.rb +8 -10
- data/lib/calendav/error_handler.rb +34 -0
- data/lib/calendav/errors.rb +8 -2
- data/lib/calendav/event.rb +2 -2
- data/lib/calendav/parsers/todo_xml.rb +37 -0
- data/lib/calendav/requests/list_events.rb +13 -2
- data/lib/calendav/requests/list_todos.rb +49 -0
- data/lib/calendav/requests/make_calendar.rb +1 -0
- data/lib/calendav/todo.rb +60 -0
- data/lib/calendav.rb +4 -4
- metadata +9 -188
- data/.rspec +0 -3
- data/Gemfile +0 -9
- data/Rakefile +0 -12
- data/spec/acceptance/apple_spec.rb +0 -45
- data/spec/acceptance/google_spec.rb +0 -101
- data/spec/acceptance/radicale_spec.rb +0 -48
- data/spec/acceptance/shared.rb +0 -180
- data/spec/data/radicale/config.ini +0 -10
- data/spec/data/radicale/users +0 -1
- data/spec/spec_helper.rb +0 -18
- data/spec/support/encoded_matchers.rb +0 -29
- data/spec/support/event_helpers.rb +0 -39
- data/spec/support/vcr.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f622fc58cda7b2c94bf7c6eff0056db4532e281bfd1e556a43f16ba8edac222
|
4
|
+
data.tar.gz: 075b5acb58221171aec66d8e9d0234410fa3f30cd0c790776ca0f023187f0d27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 596ecccf27b7cd0aeb697b4863f3c10c79e9ec8aaf2ca66786e2f31a94e5cf865a5835a8942a4c0189f7bfe040f5cde540ee7f10a09e0ec3cb693ae152923d09
|
7
|
+
data.tar.gz: 0ff81da9e901dfe16081a31a4df72319a6462ef939aa4aa83ebcb74792684e9b26b9999430159300192816e9ce9fdd31edbf04f57c6b0b28aa0cd85e9d9fe726
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
## Unreleased
|
2
2
|
|
3
|
+
## 0.5.0 - 2025-08-02
|
4
|
+
|
5
|
+
* Support for todos (Josh Huckabee #8, #9)
|
6
|
+
* Improved documentation for development/testing environments (Josh Huckabee #7)
|
7
|
+
* Support for expanded recurring events (Sayooj Surendran #12)
|
8
|
+
* Support for OAuth authentication header (Ilya Nikitenkov #13)
|
9
|
+
* Updated tested Ruby versions to 3.1-3.4 (2.7 and 3.0 are no longer officially supported).
|
10
|
+
|
11
|
+
## 0.4.0 - 2023-02-27
|
12
|
+
|
13
|
+
* **Breaking**: calls to create/update events return event objects that only have a URL and etag populated (so: no calendar data), or nil if the request failed due to an etag precondition. This is instead of returning true on success or false on failure.
|
14
|
+
* Removed test files from the gem/gemspec.
|
15
|
+
* Increased nuance of error handler, raising a RedirectError (with location method) if the CalDAV server returns a redirect status.
|
16
|
+
|
3
17
|
## 0.3.0 - 2022-03-14
|
4
18
|
|
5
19
|
* Add location to event wrapper.
|
data/README.md
CHANGED
@@ -23,7 +23,7 @@ credentials = Calendav::Credentials::Standard.new(
|
|
23
23
|
host: "https://www.example.com/caldav",
|
24
24
|
username: "example",
|
25
25
|
password: "secret",
|
26
|
-
authentication: :basic_auth #
|
26
|
+
authentication: :basic_auth # :bearer_token and :oauth also supported
|
27
27
|
)
|
28
28
|
```
|
29
29
|
|
@@ -100,6 +100,16 @@ end
|
|
100
100
|
|
101
101
|
The returned events have a unique URL (just like calendars), an `etag` (which changes when the event changes), and `calendar_data`, which is stored in the [iCalendar](https://datatracker.ietf.org/doc/html/rfc5545) format. The event objects returned currently parse the summary out of the calendar data, but nothing else - further attributes may be made visible, but for full control it's recommended you use the [icalendar](https://github.com/icalendar/icalendar) gem.
|
102
102
|
|
103
|
+
To get recurring events, you can use the optional `expand_recurring_events` argument.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
events = client.events.list(
|
107
|
+
calendar_url, from: Time.new(2021, 1, 1), to: Time.new(2022, 1, 1),
|
108
|
+
expand_recurring_events: true
|
109
|
+
)
|
110
|
+
```
|
111
|
+
The recurrent events will be present in the `calendar_data`.
|
112
|
+
|
103
113
|
If you have an event's URL, you can fetch the details of just that event directly:
|
104
114
|
|
105
115
|
```ruby
|
@@ -121,7 +131,8 @@ end
|
|
121
131
|
ics.publish
|
122
132
|
|
123
133
|
identifier = "#{SecureRandom.uuid}.ics"
|
124
|
-
|
134
|
+
# The returned event has just the URL and the etag, no calendar data:
|
135
|
+
event_scaffold = client.events.create(calendar.url, identifier, ics.to_ical)
|
125
136
|
```
|
126
137
|
|
127
138
|
*Please note*: some providers (definitely Google, possibly others) will not keep your supplied event identifier, but generate a new one. So, if you need an ongoing reference to the event, save the returned URL (rather than combining the calendar URL and your identifier).
|
@@ -129,17 +140,17 @@ event_url = client.events.create(calendar.url, identifier, ics.to_ical)
|
|
129
140
|
Updating events is done in a similar manner - with the event's URL and the updated iCalendar content:
|
130
141
|
|
131
142
|
```ruby
|
132
|
-
client.events.update(event_url, ics.to_ical)
|
143
|
+
event_scaffold = client.events.update(event_url, ics.to_ical)
|
133
144
|
```
|
134
145
|
|
135
|
-
To ensure you're only changing a known version of an event, it's recommended you use that version's etag as a precondition check (the update call will return
|
146
|
+
To ensure you're only changing a known version of an event, it's recommended you use that version's etag as a precondition check (the update call will return nil if the event on the server has a different etag value):
|
136
147
|
|
137
148
|
```ruby
|
138
|
-
event = client.events.find(
|
149
|
+
event = client.events.find(event_scaffold.url)
|
139
150
|
|
140
151
|
# figure out the changes you want to make, generate the new ical data, and then:
|
141
152
|
|
142
|
-
client.events.update(
|
153
|
+
client.events.update(event_scaffold.url, modified_ical, etag: event.etag)
|
143
154
|
```
|
144
155
|
|
145
156
|
Deletion of events is done via the event's URL as well - some providers (including Apple) support the etag precondition check for this, but others (including Google) do not. In the latter situation, the deletion will happen regardless of the server event's etag value.
|
@@ -150,6 +161,55 @@ client.events.delete(event_url)
|
|
150
161
|
client.events.delete(event_url, etag: event.etag)
|
151
162
|
```
|
152
163
|
|
164
|
+
### Todos
|
165
|
+
|
166
|
+
You can also use a calendar's URL to retrieve its todos.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
todos = client.todos.list(calendar_url)
|
170
|
+
todos.each do |todo|
|
171
|
+
puts todo.url
|
172
|
+
puts todo.summary
|
173
|
+
puts todo.status
|
174
|
+
puts todo.due
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
Just like events, returned todos have a unique URL, an `etag` (which changes when the todo changes), and `calendar_data`. The todo objects returned currently parse the summary, status, and due date out of the calendar data.
|
179
|
+
|
180
|
+
If you have a todo's URL, you can fetch the details of just that todo directly:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
todo = client.todos.find(todo_url)
|
184
|
+
```
|
185
|
+
|
186
|
+
Creating todos, just like creating events, requires a unique identifier. You will also need to generate the iCalendar data - again, the [icalendar](https://github.com/icalendar/icalendar) gem is very helpful for this.
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
require "securerandom"
|
190
|
+
require "icalendar"
|
191
|
+
|
192
|
+
ics = Icalendar::Calendar.new
|
193
|
+
ics.todo do |todo|
|
194
|
+
# ...
|
195
|
+
end
|
196
|
+
ics.publish
|
197
|
+
|
198
|
+
identifier = "#{SecureRandom.uuid}.ics"
|
199
|
+
# The returned todo has just the URL and the etag, no calendar data:
|
200
|
+
todo_scaffold = client.todos.create(calendar.url, identifier, ics.to_ical)
|
201
|
+
```
|
202
|
+
|
203
|
+
Updating and deleting todos is similar to updating events:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
# Update the todo
|
207
|
+
client.todos.update(todo_url, ics.to_ical)
|
208
|
+
|
209
|
+
# Delete the todo
|
210
|
+
client.todos.delete(todo_url)
|
211
|
+
```
|
212
|
+
|
153
213
|
### Synchronising
|
154
214
|
|
155
215
|
If you are maintaining your own copy/history of events from a CalDAV server, then it's highly recommended you take advantage of the WebDAV-Sync protocol (should the calendar provider support it). Using Calendav, the suggested approach is:
|
@@ -197,7 +257,7 @@ While a lot of the core CalDAV functionality is covered and this gem is useful a
|
|
197
257
|
* Further iCalendar parsing for Event objects.
|
198
258
|
* Automated tests against FastMail and possibly other providers (Apple and Google are already covered).
|
199
259
|
* Locking/unlocking of events as per the WebCAL RFC.
|
200
|
-
* Support for
|
260
|
+
* Support for VJOURNAL, VFREEBUSY and any other components beyond VEVENT.
|
201
261
|
|
202
262
|
## Installation
|
203
263
|
|
@@ -231,11 +291,21 @@ The work done in previous Ruby CalDAV clients [RubyCaldav](https://github.com/di
|
|
231
291
|
|
232
292
|
## Development
|
233
293
|
|
234
|
-
After checking out the repo, run `bin/setup` to install dependencies.
|
294
|
+
After checking out the repo, run `bin/setup` to install dependencies and configure the required credentials. This will copy the `.env.example` file to a `.env` file. Make sure to configure the `.env` file with your own Apple and Google credentials.
|
295
|
+
|
296
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
235
297
|
|
236
298
|
## Tests
|
237
299
|
|
238
|
-
The test suite
|
300
|
+
The test suite depends on a locally running instance of a CalDAV server to work properly. We prefer [Radicale](https://radicale.org/v3.html) for this. You can run it locally with Docker and the provided configuration in `spec/data/radicale` with the following command:
|
301
|
+
|
302
|
+
docker run -v $PWD/spec/data/radicale:/var/radicale \
|
303
|
+
--name radicale \
|
304
|
+
--publish 8000:8000 \
|
305
|
+
--detach \
|
306
|
+
xlrl/radicale
|
307
|
+
|
308
|
+
With the CalDAV server running, you can now run `rake spec` to run the tests.
|
239
309
|
|
240
310
|
## Contributing
|
241
311
|
|
data/lib/calendav/calendar.rb
CHANGED
data/lib/calendav/client.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
5
|
-
require_relative "
|
6
|
-
require_relative "
|
7
|
-
require_relative "
|
3
|
+
require_relative "contextual_url"
|
4
|
+
require_relative "endpoint"
|
5
|
+
require_relative "clients/calendars_client"
|
6
|
+
require_relative "clients/events_client"
|
7
|
+
require_relative "clients/todos_client"
|
8
|
+
require_relative "requests/current_user_principal"
|
8
9
|
|
9
10
|
module Calendav
|
10
11
|
class Client
|
@@ -21,6 +22,10 @@ module Calendav
|
|
21
22
|
@events = Clients::EventsClient.new(self, endpoint, credentials)
|
22
23
|
end
|
23
24
|
|
25
|
+
def todos
|
26
|
+
@todos = Clients::TodosClient.new(self, endpoint, credentials)
|
27
|
+
end
|
28
|
+
|
24
29
|
def principal_url
|
25
30
|
@principal_url ||= begin
|
26
31
|
request = Requests::CurrentUserPrincipal.call
|
@@ -17,7 +17,10 @@ module Calendav
|
|
17
17
|
event_url = merged_url(calendar_url, event_identifier)
|
18
18
|
result = endpoint.put(ics, url: event_url, content_type: :ics)
|
19
19
|
|
20
|
-
|
20
|
+
Event.new(
|
21
|
+
url: result.headers["Location"] || event_url,
|
22
|
+
etag: result.headers["ETag"]
|
23
|
+
)
|
21
24
|
end
|
22
25
|
|
23
26
|
def delete(event_url, etag: nil)
|
@@ -36,8 +39,10 @@ module Calendav
|
|
36
39
|
)
|
37
40
|
end
|
38
41
|
|
39
|
-
def list(calendar_url, from: nil, to: nil)
|
40
|
-
request = Requests::ListEvents.call(
|
42
|
+
def list(calendar_url, from: nil, to: nil, expand_recurring_events: false)
|
43
|
+
request = Requests::ListEvents.call(
|
44
|
+
from: from, to: to, expand_recurring_events: expand_recurring_events
|
45
|
+
)
|
41
46
|
|
42
47
|
endpoint
|
43
48
|
.report(request.to_xml, url: calendar_url, depth: 1)
|
@@ -46,13 +51,16 @@ module Calendav
|
|
46
51
|
end
|
47
52
|
|
48
53
|
def update(event_url, ics, etag: nil)
|
49
|
-
endpoint.put(
|
54
|
+
result = endpoint.put(
|
50
55
|
ics, url: event_url, content_type: :ics, etag: etag
|
51
56
|
)
|
52
57
|
|
53
|
-
|
58
|
+
Event.new(
|
59
|
+
url: event_url,
|
60
|
+
etag: result.headers["ETag"]
|
61
|
+
)
|
54
62
|
rescue PreconditionError
|
55
|
-
|
63
|
+
nil
|
56
64
|
end
|
57
65
|
|
58
66
|
private
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../errors"
|
4
|
+
require_relative "../todo"
|
5
|
+
require_relative "../requests/list_todos"
|
6
|
+
|
7
|
+
module Calendav
|
8
|
+
module Clients
|
9
|
+
class TodosClient
|
10
|
+
def initialize(client, endpoint, credentials)
|
11
|
+
@client = client
|
12
|
+
@endpoint = endpoint
|
13
|
+
@credentials = credentials
|
14
|
+
end
|
15
|
+
|
16
|
+
def create(calendar_url, todo_identifier, ics)
|
17
|
+
todo_url = merged_url(calendar_url, todo_identifier)
|
18
|
+
result = endpoint.put(ics, url: todo_url, content_type: :ics)
|
19
|
+
|
20
|
+
Todo.new(
|
21
|
+
url: result.headers["Location"] || todo_url,
|
22
|
+
etag: result.headers["ETag"]
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(todo_url, etag: nil)
|
27
|
+
endpoint.delete(url: todo_url, etag: etag)
|
28
|
+
rescue PreconditionError
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def find(todo_url)
|
33
|
+
response = endpoint.get(url: todo_url)
|
34
|
+
|
35
|
+
Todo.new(
|
36
|
+
url: todo_url,
|
37
|
+
calendar_data: response.body.to_s,
|
38
|
+
etag: response.headers["ETag"]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def list(calendar_url)
|
43
|
+
request = Requests::ListTodos.call
|
44
|
+
|
45
|
+
endpoint
|
46
|
+
.report(request.to_xml, url: calendar_url, depth: 1)
|
47
|
+
.reject { |node| node.xpath(".//caldav:calendar-data").text.empty? }
|
48
|
+
.collect { |node| Todo.from_xml(calendar_url, node) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def update(todo_url, ics, etag: nil)
|
52
|
+
result = endpoint.put(
|
53
|
+
ics, url: todo_url, content_type: :ics, etag: etag
|
54
|
+
)
|
55
|
+
|
56
|
+
Todo.new(
|
57
|
+
url: todo_url,
|
58
|
+
etag: result.headers["ETag"]
|
59
|
+
)
|
60
|
+
rescue PreconditionError
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :client, :endpoint, :credentials
|
67
|
+
|
68
|
+
def merged_url(calendar_url, todo_identifier)
|
69
|
+
"#{calendar_url.delete_suffix('/')}/#{todo_identifier}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/calendav/endpoint.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
5
|
-
require_relative "
|
3
|
+
require_relative "contextual_url"
|
4
|
+
require_relative "error_handler"
|
5
|
+
require_relative "parsers/response_xml"
|
6
6
|
|
7
7
|
module Calendav
|
8
8
|
class Endpoint
|
@@ -16,7 +16,7 @@ module Calendav
|
|
16
16
|
@timeout = timeout
|
17
17
|
end
|
18
18
|
|
19
|
-
def delete(url:, etag: nil)
|
19
|
+
def delete(url:, etag: nil) # rubocop:disable Naming/PredicateMethod
|
20
20
|
request(:delete, url: url, http: with_headers(etag: etag))
|
21
21
|
.status
|
22
22
|
.success?
|
@@ -79,12 +79,14 @@ module Calendav
|
|
79
79
|
|
80
80
|
attr_reader :credentials, :timeout
|
81
81
|
|
82
|
-
def authenticated
|
82
|
+
def authenticated # rubocop:disable Metrics/MethodLength
|
83
83
|
case credentials.authentication
|
84
84
|
when :basic_auth
|
85
85
|
HTTP.basic_auth(user: credentials.username, pass: credentials.password)
|
86
86
|
when :bearer_token
|
87
87
|
HTTP.auth("Bearer #{credentials.password}")
|
88
|
+
when :oauth
|
89
|
+
HTTP.auth("OAuth #{credentials.password}")
|
88
90
|
else
|
89
91
|
raise "Unexpected authentication approach: " \
|
90
92
|
"#{credentials.authentication}"
|
@@ -109,11 +111,7 @@ module Calendav
|
|
109
111
|
verb, ContextualURL.call(credentials.host, url), body: body
|
110
112
|
)
|
111
113
|
|
112
|
-
|
113
|
-
|
114
|
-
raise PreconditionError, response if response.status.code == 412
|
115
|
-
|
116
|
-
raise RequestError, response
|
114
|
+
response.status.success? ? response : ErrorHandler.call(response)
|
117
115
|
end
|
118
116
|
|
119
117
|
def parse(response)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "errors"
|
4
|
+
|
5
|
+
module Calendav
|
6
|
+
class ErrorHandler
|
7
|
+
def self.call(...)
|
8
|
+
new(...).call
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(response)
|
12
|
+
@response = response
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
raise PreconditionError, response if status.code == 412
|
17
|
+
raise RedirectError, response if status.redirect?
|
18
|
+
|
19
|
+
raise RequestError, response
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :response
|
25
|
+
|
26
|
+
def headers
|
27
|
+
response.headers
|
28
|
+
end
|
29
|
+
|
30
|
+
def status
|
31
|
+
response.status
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/calendav/errors.rb
CHANGED
@@ -7,7 +7,7 @@ module Calendav
|
|
7
7
|
attr_reader :xml, :original
|
8
8
|
|
9
9
|
def initialize(xml, original)
|
10
|
-
super
|
10
|
+
super(original.message)
|
11
11
|
|
12
12
|
@xml = xml
|
13
13
|
@original = original
|
@@ -18,11 +18,17 @@ module Calendav
|
|
18
18
|
attr_reader :response
|
19
19
|
|
20
20
|
def initialize(response)
|
21
|
-
super
|
21
|
+
super(response.status.to_s)
|
22
22
|
|
23
23
|
@response = response
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
class RedirectError < RequestError
|
28
|
+
def location
|
29
|
+
response.headers["Location"]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
27
33
|
PreconditionError = Class.new(RequestError)
|
28
34
|
end
|
data/lib/calendav/event.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Calendav
|
4
|
+
module Parsers
|
5
|
+
class TodoXML
|
6
|
+
def self.call(...)
|
7
|
+
new(...).call
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(element)
|
11
|
+
@element = element
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
{
|
16
|
+
calendar_data: value(".//caldav:calendar-data"),
|
17
|
+
etag: value(".//dav:getetag")
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :element
|
24
|
+
|
25
|
+
def value(xpath)
|
26
|
+
node = element.xpath(xpath)
|
27
|
+
return nil if node.children.empty?
|
28
|
+
|
29
|
+
if node.children.any?(&:element?)
|
30
|
+
node.children.select(&:element?).collect(&:to_xml).join
|
31
|
+
else
|
32
|
+
node.children.text
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -11,9 +11,10 @@ module Calendav
|
|
11
11
|
new(...).call
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(from:, to:)
|
14
|
+
def initialize(from:, to:, expand_recurring_events:)
|
15
15
|
@from = from
|
16
16
|
@to = to
|
17
|
+
@expand_recurring_events = expand_recurring_events
|
17
18
|
end
|
18
19
|
|
19
20
|
def call
|
@@ -21,7 +22,13 @@ module Calendav
|
|
21
22
|
xml["caldav"].public_send("calendar-query", NAMESPACES) do
|
22
23
|
xml["dav"].prop do
|
23
24
|
xml["dav"].getetag
|
24
|
-
|
25
|
+
if expand_recurring_events? && range?
|
26
|
+
xml["caldav"].public_send(:"calendar-data") do
|
27
|
+
xml["caldav"].expand(start: from, end: to)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
xml["caldav"].public_send(:"calendar-data")
|
31
|
+
end
|
25
32
|
end
|
26
33
|
xml["caldav"].filter do
|
27
34
|
xml["caldav"].public_send(:"comp-filter", name: "VCALENDAR") do
|
@@ -40,6 +47,10 @@ module Calendav
|
|
40
47
|
|
41
48
|
private
|
42
49
|
|
50
|
+
def expand_recurring_events?
|
51
|
+
@expand_recurring_events
|
52
|
+
end
|
53
|
+
|
43
54
|
def from
|
44
55
|
return nil if @from.nil?
|
45
56
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require_relative "../namespaces"
|
6
|
+
|
7
|
+
module Calendav
|
8
|
+
module Requests
|
9
|
+
class ListTodos
|
10
|
+
def self.call(...)
|
11
|
+
new.call
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
Nokogiri::XML::Builder.new do |xml|
|
16
|
+
xml["caldav"].public_send("calendar-query", NAMESPACES) do
|
17
|
+
xml["dav"].prop do
|
18
|
+
xml["dav"].getetag
|
19
|
+
xml["caldav"].public_send(:"calendar-data")
|
20
|
+
end
|
21
|
+
xml["caldav"].filter do
|
22
|
+
xml["caldav"].public_send(:"comp-filter", name: "VCALENDAR") do
|
23
|
+
xml["caldav"].public_send(:"comp-filter", name: "VTODO")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def from
|
33
|
+
return nil if @from.nil?
|
34
|
+
|
35
|
+
@from.utc.iso8601.delete(":-")
|
36
|
+
end
|
37
|
+
|
38
|
+
def to
|
39
|
+
return nil if @to.nil?
|
40
|
+
|
41
|
+
@to.utc.iso8601.delete(":-")
|
42
|
+
end
|
43
|
+
|
44
|
+
def range?
|
45
|
+
to || from
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|