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