1
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
require "digest"
require "pro_tacts/contact"
module ProTacts
# The address book collection: every contact under the contacts
# directory, plus the collection-wide state derived from those members.
# The ctag changes when any card is added, removed, or changed and
# nothing else, so a client comparing two of them learns whether a
# resync is needed — never what changed.
#
# The signature lives in sig/pro_tacts/addressbook.rbs, for the same
# reason Contact's does.
# @rbs skip
class Addressbook < Data.define(:contacts)
def self.load(directory)
new(contacts: Contact.all(directory))
end
# Sorting the id-and-etag lines makes the value independent of
# directory listing order while staying sensitive to membership and
# content.
def ctag
Digest::SHA256.hexdigest(contacts.map { "#{it.id} #{it.etag}" }.sort.join("\n"))
end
# Sync tokens are opaque to the client (RFC 6578 section 3); the URI
# form is conventional. Built on the ctag so a client polling either
# one sees changes at the same points.
def sync_token
"http://pro-tacts/sync/#{ctag}"
end
end
end