async-caldav 1.0.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 +7 -0
- data/lib/async/caldav/client/addressbook.rb +117 -0
- data/lib/async/caldav/client/calendar.rb +152 -0
- data/lib/async/caldav/client.rb +580 -0
- data/lib/async/caldav/forward_auth.rb +94 -0
- data/lib/async/caldav/handlers/delete.rb +60 -0
- data/lib/async/caldav/handlers/get.rb +87 -0
- data/lib/async/caldav/handlers/head.rb +36 -0
- data/lib/async/caldav/handlers/mkcol.rb +95 -0
- data/lib/async/caldav/handlers/move.rb +126 -0
- data/lib/async/caldav/handlers/options.rb +34 -0
- data/lib/async/caldav/handlers/propfind.rb +201 -0
- data/lib/async/caldav/handlers/proppatch.rb +121 -0
- data/lib/async/caldav/handlers/put.rb +163 -0
- data/lib/async/caldav/handlers/report.rb +257 -0
- data/lib/async/caldav/server.rb +1152 -0
- data/lib/async/caldav/storage/filesystem.rb +375 -0
- data/lib/async/caldav/storage/mock.rb +402 -0
- data/lib/async/caldav/version.rb +7 -0
- data/lib/async/caldav.rb +24 -0
- metadata +90 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "scampi"
|
|
5
|
+
require "async/caldav"
|
|
6
|
+
|
|
7
|
+
module Async
|
|
8
|
+
module Caldav
|
|
9
|
+
module Storage
|
|
10
|
+
class Mock < Protocol::Caldav::Storage
|
|
11
|
+
def initialize
|
|
12
|
+
@collections = {}
|
|
13
|
+
@items = {}
|
|
14
|
+
@sync_snapshots = {} # token => { collection_path => { item_path => etag } }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# --- Collections ---
|
|
18
|
+
|
|
19
|
+
def create_collection(path, props = {})
|
|
20
|
+
col = {
|
|
21
|
+
type: props[:type] || :collection,
|
|
22
|
+
displayname: props[:displayname],
|
|
23
|
+
description: props[:description],
|
|
24
|
+
color: props[:color],
|
|
25
|
+
props: props[:props] || {}
|
|
26
|
+
}
|
|
27
|
+
@collections[path] = col
|
|
28
|
+
col
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_collection(path)
|
|
32
|
+
@collections[path]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete_collection(path)
|
|
36
|
+
if @collections.delete(path)
|
|
37
|
+
@items.delete_if { |k, _| k.start_with?(path) }
|
|
38
|
+
true
|
|
39
|
+
else
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def list_collections(parent_path)
|
|
45
|
+
parent = parent_path.end_with?('/') ? parent_path : "#{parent_path}/"
|
|
46
|
+
@collections.select { |k, _| direct_child?(k, parent) }.to_a
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def update_collection(path, props)
|
|
50
|
+
col = @collections[path]
|
|
51
|
+
if col
|
|
52
|
+
col[:displayname] = props[:displayname] if props.key?(:displayname)
|
|
53
|
+
col[:description] = props[:description] if props.key?(:description)
|
|
54
|
+
col[:color] = props[:color] if props.key?(:color)
|
|
55
|
+
col[:props] = (col[:props] || {}).merge(props[:props]) if props.key?(:props)
|
|
56
|
+
col
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def collection_exists?(path)
|
|
61
|
+
@collections.key?(path)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# --- Items ---
|
|
65
|
+
|
|
66
|
+
def get_item(path)
|
|
67
|
+
@items[path]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def put_item(path, body, content_type)
|
|
71
|
+
etag = Protocol::Caldav::ETag.compute(body)
|
|
72
|
+
is_new = !@items.key?(path)
|
|
73
|
+
item = { body: body, content_type: content_type, etag: etag }
|
|
74
|
+
@items[path] = item
|
|
75
|
+
[item, is_new]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def delete_item(path)
|
|
79
|
+
!!@items.delete(path)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def list_items(collection_path)
|
|
83
|
+
prefix = collection_path.end_with?('/') ? collection_path : "#{collection_path}/"
|
|
84
|
+
@items.select { |k, _| k.start_with?(prefix) && k != collection_path }.to_a
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def move_item(from_path, to_path)
|
|
88
|
+
item = @items.delete(from_path)
|
|
89
|
+
if item
|
|
90
|
+
@items[to_path] = item
|
|
91
|
+
item
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def get_multi(paths)
|
|
96
|
+
paths.map { |p| [p, @items[p]] }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# --- Sync ---
|
|
100
|
+
|
|
101
|
+
def snapshot_sync(collection_path)
|
|
102
|
+
items = list_items(collection_path)
|
|
103
|
+
snapshot = {}
|
|
104
|
+
items.each { |path, data| snapshot[path] = data[:etag] }
|
|
105
|
+
|
|
106
|
+
# Compute a token from the current state
|
|
107
|
+
item_etags = items.map { |_, data| data[:etag] }
|
|
108
|
+
col = @collections[collection_path] || {}
|
|
109
|
+
ctag = Protocol::Caldav::CTag.compute(
|
|
110
|
+
path: collection_path,
|
|
111
|
+
displayname: col[:displayname],
|
|
112
|
+
description: col[:description],
|
|
113
|
+
color: col[:color],
|
|
114
|
+
item_etags: item_etags
|
|
115
|
+
)
|
|
116
|
+
token = "http://caldav.local/sync/#{ctag}"
|
|
117
|
+
|
|
118
|
+
@sync_snapshots[token] = { collection_path => snapshot }
|
|
119
|
+
token
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def sync_changes(collection_path, token)
|
|
123
|
+
old_snapshot_entry = @sync_snapshots[token]
|
|
124
|
+
return nil unless old_snapshot_entry
|
|
125
|
+
|
|
126
|
+
old_snapshot = old_snapshot_entry[collection_path] || {}
|
|
127
|
+
|
|
128
|
+
# Take new snapshot
|
|
129
|
+
new_token = snapshot_sync(collection_path)
|
|
130
|
+
current_items = list_items(collection_path)
|
|
131
|
+
current = {}
|
|
132
|
+
current_items.each { |path, data| current[path] = data[:etag] }
|
|
133
|
+
|
|
134
|
+
changes = []
|
|
135
|
+
|
|
136
|
+
# Items that are new or modified
|
|
137
|
+
current.each do |path, etag|
|
|
138
|
+
if !old_snapshot.key?(path) || old_snapshot[path] != etag
|
|
139
|
+
changes << [path, :modified]
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Items that were deleted
|
|
144
|
+
old_snapshot.each_key do |path|
|
|
145
|
+
unless current.key?(path)
|
|
146
|
+
changes << [path, :deleted]
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
[new_token, changes]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# --- General ---
|
|
154
|
+
|
|
155
|
+
def exists?(path)
|
|
156
|
+
@items.key?(path) || @collections.key?(path)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def etag(path)
|
|
160
|
+
item = @items[path]
|
|
161
|
+
item ? item[:etag] : nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def direct_child?(child, parent)
|
|
167
|
+
if child.start_with?(parent)
|
|
168
|
+
remainder = child[parent.length..]
|
|
169
|
+
remainder.chomp('/').count('/').zero? && !remainder.chomp('/').empty?
|
|
170
|
+
else
|
|
171
|
+
false
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
test do
|
|
181
|
+
describe "Async::Caldav::Storage::Mock" do
|
|
182
|
+
it "creates and retrieves a collection" do
|
|
183
|
+
s = Async::Caldav::Storage::Mock.new
|
|
184
|
+
s.create_collection("/calendars/admin/cal1/", type: :calendar, displayname: "Cal 1")
|
|
185
|
+
col = s.get_collection("/calendars/admin/cal1/")
|
|
186
|
+
col.should.not.be.nil
|
|
187
|
+
col[:displayname].should.equal "Cal 1"
|
|
188
|
+
col[:type].should.equal :calendar
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "deletes a collection and its items" do
|
|
192
|
+
s = Async::Caldav::Storage::Mock.new
|
|
193
|
+
s.create_collection("/calendars/admin/cal/")
|
|
194
|
+
s.put_item("/calendars/admin/cal/event.ics", "VCALENDAR", "text/calendar")
|
|
195
|
+
s.delete_collection("/calendars/admin/cal/")
|
|
196
|
+
s.get_collection("/calendars/admin/cal/").should.be.nil
|
|
197
|
+
s.get_item("/calendars/admin/cal/event.ics").should.be.nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "delete_collection does not affect siblings" do
|
|
201
|
+
s = Async::Caldav::Storage::Mock.new
|
|
202
|
+
s.create_collection("/calendars/admin/a/")
|
|
203
|
+
s.create_collection("/calendars/admin/b/")
|
|
204
|
+
s.delete_collection("/calendars/admin/a/")
|
|
205
|
+
s.get_collection("/calendars/admin/b/").should.not.be.nil
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it "delete_collection returns true/false" do
|
|
209
|
+
s = Async::Caldav::Storage::Mock.new
|
|
210
|
+
s.create_collection("/col/")
|
|
211
|
+
s.delete_collection("/col/").should.equal true
|
|
212
|
+
s.delete_collection("/col/").should.equal false
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it "lists direct child collections only" do
|
|
216
|
+
s = Async::Caldav::Storage::Mock.new
|
|
217
|
+
s.create_collection("/calendars/admin/a/")
|
|
218
|
+
s.create_collection("/calendars/admin/b/")
|
|
219
|
+
s.create_collection("/calendars/admin/a/nested/")
|
|
220
|
+
list = s.list_collections("/calendars/admin/")
|
|
221
|
+
list.length.should.equal 2
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "list_collections returns empty on no children" do
|
|
225
|
+
s = Async::Caldav::Storage::Mock.new
|
|
226
|
+
s.list_collections("/nope/").should.equal []
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it "updates collection properties, leaves others untouched" do
|
|
230
|
+
s = Async::Caldav::Storage::Mock.new
|
|
231
|
+
s.create_collection("/cal/", displayname: "Old", description: "Desc")
|
|
232
|
+
s.update_collection("/cal/", displayname: "New")
|
|
233
|
+
col = s.get_collection("/cal/")
|
|
234
|
+
col[:displayname].should.equal "New"
|
|
235
|
+
col[:description].should.equal "Desc"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it "update_collection returns nil for nonexistent" do
|
|
239
|
+
s = Async::Caldav::Storage::Mock.new
|
|
240
|
+
s.update_collection("/nope/", displayname: "X").should.be.nil
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it "put_item returns is_new true for new, false for existing" do
|
|
244
|
+
s = Async::Caldav::Storage::Mock.new
|
|
245
|
+
_, is_new1 = s.put_item("/cal/ev.ics", "body1", "text/calendar")
|
|
246
|
+
is_new1.should.equal true
|
|
247
|
+
_, is_new2 = s.put_item("/cal/ev.ics", "body2", "text/calendar")
|
|
248
|
+
is_new2.should.equal false
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it "put_item overwrites the body" do
|
|
252
|
+
s = Async::Caldav::Storage::Mock.new
|
|
253
|
+
s.put_item("/cal/ev.ics", "old", "text/calendar")
|
|
254
|
+
s.put_item("/cal/ev.ics", "new", "text/calendar")
|
|
255
|
+
s.get_item("/cal/ev.ics")[:body].should.equal "new"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it "get_item returns nil for nonexistent" do
|
|
259
|
+
s = Async::Caldav::Storage::Mock.new
|
|
260
|
+
s.get_item("/nope").should.be.nil
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
it "delete_item returns true/false" do
|
|
264
|
+
s = Async::Caldav::Storage::Mock.new
|
|
265
|
+
s.put_item("/cal/ev.ics", "d", "text/calendar")
|
|
266
|
+
s.delete_item("/cal/ev.ics").should.equal true
|
|
267
|
+
s.delete_item("/cal/ev.ics").should.equal false
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "list_items returns only items, not collections" do
|
|
271
|
+
s = Async::Caldav::Storage::Mock.new
|
|
272
|
+
s.create_collection("/cal/")
|
|
273
|
+
s.put_item("/cal/ev.ics", "data", "text/calendar")
|
|
274
|
+
items = s.list_items("/cal/")
|
|
275
|
+
items.length.should.equal 1
|
|
276
|
+
items[0][0].should.equal "/cal/ev.ics"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it "list_items on empty collection returns empty" do
|
|
280
|
+
s = Async::Caldav::Storage::Mock.new
|
|
281
|
+
s.list_items("/empty/").should.equal []
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
it "move_item removes source, creates destination" do
|
|
285
|
+
s = Async::Caldav::Storage::Mock.new
|
|
286
|
+
s.put_item("/cal/a.ics", "data", "text/calendar")
|
|
287
|
+
s.move_item("/cal/a.ics", "/cal/b.ics")
|
|
288
|
+
s.get_item("/cal/a.ics").should.be.nil
|
|
289
|
+
s.get_item("/cal/b.ics")[:body].should.equal "data"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
it "move_item returns nil when source missing" do
|
|
293
|
+
s = Async::Caldav::Storage::Mock.new
|
|
294
|
+
s.move_item("/nope", "/dest").should.be.nil
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it "get_multi returns results in input order with nils for missing" do
|
|
298
|
+
s = Async::Caldav::Storage::Mock.new
|
|
299
|
+
s.put_item("/cal/a.ics", "A", "text/calendar")
|
|
300
|
+
result = s.get_multi(["/cal/a.ics", "/cal/nope.ics"])
|
|
301
|
+
result.length.should.equal 2
|
|
302
|
+
result[0][1][:body].should.equal "A"
|
|
303
|
+
result[1][1].should.be.nil
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "get_multi with empty input returns empty" do
|
|
307
|
+
s = Async::Caldav::Storage::Mock.new
|
|
308
|
+
s.get_multi([]).should.equal []
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it "exists? for items, collections, and nonexistent" do
|
|
312
|
+
s = Async::Caldav::Storage::Mock.new
|
|
313
|
+
s.exists?("/nope").should.equal false
|
|
314
|
+
s.create_collection("/col/")
|
|
315
|
+
s.exists?("/col/").should.equal true
|
|
316
|
+
s.put_item("/col/x.ics", "d", "text/calendar")
|
|
317
|
+
s.exists?("/col/x.ics").should.equal true
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it "etag returns item etag, nil for nonexistent" do
|
|
321
|
+
s = Async::Caldav::Storage::Mock.new
|
|
322
|
+
s.put_item("/cal/ev.ics", "body", "text/calendar")
|
|
323
|
+
s.etag("/cal/ev.ics").should.not.be.nil
|
|
324
|
+
s.etag("/nope").should.be.nil
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
it "etag changes when body changes" do
|
|
328
|
+
s = Async::Caldav::Storage::Mock.new
|
|
329
|
+
s.put_item("/cal/ev.ics", "body1", "text/calendar")
|
|
330
|
+
e1 = s.etag("/cal/ev.ics")
|
|
331
|
+
s.put_item("/cal/ev.ics", "body2", "text/calendar")
|
|
332
|
+
e2 = s.etag("/cal/ev.ics")
|
|
333
|
+
e1.should.not.equal e2
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
it "snapshot_sync returns a token" do
|
|
337
|
+
s = Async::Caldav::Storage::Mock.new
|
|
338
|
+
s.create_collection("/cal/", type: :calendar)
|
|
339
|
+
s.put_item("/cal/ev.ics", "body", "text/calendar")
|
|
340
|
+
token = s.snapshot_sync("/cal/")
|
|
341
|
+
token.should.not.be.nil
|
|
342
|
+
token.should.include "http://caldav.local/sync/"
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
it "sync_changes returns empty changes when nothing changed" do
|
|
346
|
+
s = Async::Caldav::Storage::Mock.new
|
|
347
|
+
s.create_collection("/cal/", type: :calendar)
|
|
348
|
+
s.put_item("/cal/ev.ics", "body", "text/calendar")
|
|
349
|
+
token = s.snapshot_sync("/cal/")
|
|
350
|
+
new_token, changes = s.sync_changes("/cal/", token)
|
|
351
|
+
changes.should.equal []
|
|
352
|
+
new_token.should.equal token
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it "sync_changes detects added items" do
|
|
356
|
+
s = Async::Caldav::Storage::Mock.new
|
|
357
|
+
s.create_collection("/cal/", type: :calendar)
|
|
358
|
+
token = s.snapshot_sync("/cal/")
|
|
359
|
+
s.put_item("/cal/new.ics", "body", "text/calendar")
|
|
360
|
+
_, changes = s.sync_changes("/cal/", token)
|
|
361
|
+
changes.length.should.equal 1
|
|
362
|
+
changes[0][0].should.equal "/cal/new.ics"
|
|
363
|
+
changes[0][1].should.equal :modified
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
it "sync_changes detects deleted items" do
|
|
367
|
+
s = Async::Caldav::Storage::Mock.new
|
|
368
|
+
s.create_collection("/cal/", type: :calendar)
|
|
369
|
+
s.put_item("/cal/ev.ics", "body", "text/calendar")
|
|
370
|
+
token = s.snapshot_sync("/cal/")
|
|
371
|
+
s.delete_item("/cal/ev.ics")
|
|
372
|
+
_, changes = s.sync_changes("/cal/", token)
|
|
373
|
+
changes.length.should.equal 1
|
|
374
|
+
changes[0][0].should.equal "/cal/ev.ics"
|
|
375
|
+
changes[0][1].should.equal :deleted
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
it "sync_changes detects modified items" do
|
|
379
|
+
s = Async::Caldav::Storage::Mock.new
|
|
380
|
+
s.create_collection("/cal/", type: :calendar)
|
|
381
|
+
s.put_item("/cal/ev.ics", "body1", "text/calendar")
|
|
382
|
+
token = s.snapshot_sync("/cal/")
|
|
383
|
+
s.put_item("/cal/ev.ics", "body2", "text/calendar")
|
|
384
|
+
_, changes = s.sync_changes("/cal/", token)
|
|
385
|
+
changes.length.should.equal 1
|
|
386
|
+
changes[0][1].should.equal :modified
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
it "sync_changes returns nil for invalid token" do
|
|
390
|
+
s = Async::Caldav::Storage::Mock.new
|
|
391
|
+
s.create_collection("/cal/", type: :calendar)
|
|
392
|
+
s.sync_changes("/cal/", "bogus-token").should.be.nil
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
it "same body produces same etag" do
|
|
396
|
+
s = Async::Caldav::Storage::Mock.new
|
|
397
|
+
s.put_item("/a.ics", "same", "text/calendar")
|
|
398
|
+
s.put_item("/b.ics", "same", "text/calendar")
|
|
399
|
+
s.etag("/a.ics").should.equal s.etag("/b.ics")
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
data/lib/async/caldav.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'protocol/caldav'
|
|
4
|
+
require_relative 'caldav/version'
|
|
5
|
+
require_relative 'caldav/forward_auth'
|
|
6
|
+
require_relative 'caldav/storage/mock'
|
|
7
|
+
require_relative 'caldav/storage/filesystem'
|
|
8
|
+
require_relative 'caldav/handlers/options'
|
|
9
|
+
require_relative 'caldav/handlers/get'
|
|
10
|
+
require_relative 'caldav/handlers/head'
|
|
11
|
+
require_relative 'caldav/handlers/put'
|
|
12
|
+
require_relative 'caldav/handlers/delete'
|
|
13
|
+
require_relative 'caldav/handlers/move'
|
|
14
|
+
require_relative 'caldav/handlers/mkcol'
|
|
15
|
+
require_relative 'caldav/handlers/propfind'
|
|
16
|
+
require_relative 'caldav/handlers/proppatch'
|
|
17
|
+
require_relative 'caldav/handlers/report'
|
|
18
|
+
require_relative 'caldav/server'
|
|
19
|
+
require_relative 'caldav/client'
|
|
20
|
+
|
|
21
|
+
module Async
|
|
22
|
+
module Caldav
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: async-caldav
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nathan K
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-01 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: protocol-caldav
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: scampi
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.1.7
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.1.7
|
|
40
|
+
description: |
|
|
41
|
+
Native server for CalDAV/CardDAV.
|
|
42
|
+
Built on protocol-caldav for wire-format concerns.
|
|
43
|
+
email:
|
|
44
|
+
- nathankidd@hey.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- lib/async/caldav.rb
|
|
50
|
+
- lib/async/caldav/client.rb
|
|
51
|
+
- lib/async/caldav/client/addressbook.rb
|
|
52
|
+
- lib/async/caldav/client/calendar.rb
|
|
53
|
+
- lib/async/caldav/forward_auth.rb
|
|
54
|
+
- lib/async/caldav/handlers/delete.rb
|
|
55
|
+
- lib/async/caldav/handlers/get.rb
|
|
56
|
+
- lib/async/caldav/handlers/head.rb
|
|
57
|
+
- lib/async/caldav/handlers/mkcol.rb
|
|
58
|
+
- lib/async/caldav/handlers/move.rb
|
|
59
|
+
- lib/async/caldav/handlers/options.rb
|
|
60
|
+
- lib/async/caldav/handlers/propfind.rb
|
|
61
|
+
- lib/async/caldav/handlers/proppatch.rb
|
|
62
|
+
- lib/async/caldav/handlers/put.rb
|
|
63
|
+
- lib/async/caldav/handlers/report.rb
|
|
64
|
+
- lib/async/caldav/server.rb
|
|
65
|
+
- lib/async/caldav/storage/filesystem.rb
|
|
66
|
+
- lib/async/caldav/storage/mock.rb
|
|
67
|
+
- lib/async/caldav/version.rb
|
|
68
|
+
homepage: https://github.com/n-at-han-k/async-caldav
|
|
69
|
+
licenses:
|
|
70
|
+
- Apache-2.0
|
|
71
|
+
metadata:
|
|
72
|
+
homepage_uri: https://github.com/n-at-han-k/async-caldav
|
|
73
|
+
rdoc_options: []
|
|
74
|
+
require_paths:
|
|
75
|
+
- lib
|
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: 3.2.0
|
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
requirements: []
|
|
87
|
+
rubygems_version: 3.7.2
|
|
88
|
+
specification_version: 4
|
|
89
|
+
summary: CalDAV/CardDAV server for the async ecosystem
|
|
90
|
+
test_files: []
|