Implement subscriptions API.
2 files changed, 190 insertions(+), 14 deletions(-)

M HOWTO.org
M wispwot/server.w
M HOWTO.org +70 -2
@@ 44,7 44,6 @@ GET http://127.0.0.1:4280/health
 #+RESULTS:
 : OK
 
-
 ** Put trust values for an ID
 
 Put trust values as CSV for a given ID. OTHERID and all the other IDs

          
@@ 292,7 291,76 @@ data=saved
 Using a PUT, because saving data without change in between does not
 change the data.
 
-** Data structures
+** Subscription handling
+
+Subscriptions allow guiding information retrieval and communication
+between many different services.
+
+Get subscriptions to check for updates, separated by empty lines. The
+ID as first line of each block, furthers data in format key: value,
+one per line.
+
+#+begin_src http :pretty :exports both
+GET http://127.0.0.1:4280/subscriptions/SOMEID
+#+end_src
+
+#+RESULTS:
+
+
+When a subscription yields an update, put it with metadata; wispwot
+will keep track of it and provide it when the subscription comes up
+again. First get your ID key, then PUT the ID.
+
+#+begin_src http :pretty :exports both
+GET http://127.0.0.1:4280/key/SOMEID
+#+end_src
+
+#+RESULTS:
+: 0000
+
+Then put to the otherid (not key: the key is ownly needed for your
+own ID to keep the URL sane). The metadata must be URL-encoded as
+usual for form-data.
+
+There is no result, the success-code is 204 (no data).
+
+#+begin_src http :pretty :exports both
+PUT http://127.0.0.1:4280/subscription-updated/ownkey/0000/otherid/SOMEONEELSE
+Content-type: application/x-www-form-urlencoded
+
+link=http://example.org
+#+end_src
+
+#+RESULTS:
+
+Save trust-relevant information as usual (from which ID you found the
+ID). If there is no trust yet, wispwot will record trust 0 from your
+ownkey.
+
+Add one more update, then see the subscriptions:
+
+#+begin_src http :pretty :exports both
+PUT http://127.0.0.1:4280/subscription-updated/ownkey/0000/otherid/STRANGER
+Content-type: application/x-www-form-urlencoded
+
+link=http://example.com
+#+end_src
+
+#+RESULTS:
+
+
+#+begin_src http :pretty :exports both
+GET http://127.0.0.1:4280/subscriptions/SOMEID
+#+end_src
+
+#+RESULTS:
+: SOMEONEELSE
+: 
+: STRANGER
+
+
+
+* Data structures
 
 - =store/known-identities= contains the list of IDs.
 - =store/trust/ab/cd= contains the trust values given by the ID with index abcd (base 16 formatted number).

          
M wispwot/server.w +120 -12
@@ 421,6 421,22 @@ define-handler 'GET "/id/" : get-id-hand
               . #:code 404
             . "404 not found"
 
+define : get-score wotstate ownkey otherkey
+    define ownidx : key->index ownkey
+    define ownid : index->identity wotstate ownidx
+    define wotstate-with-cache : add-wotcache wotstate ownidx ownid
+    define scores
+        wotcache-scores
+            update-wotcache-from-wotstate! wotstate-with-cache ownid
+    define otherindex : key->index otherkey
+    cond 
+      : equal? ownkey otherkey
+        . "100" ;; trust of own ID is always 100
+      {otherindex >= (vector-length scores)}
+        . #f
+      else
+        and=> (vector-ref scores otherindex) number->string
+
 define-handler 'GET "/score/ownkey/" : get-score-handler request body wotstate
     . "Get the score for a given key as seen from the ownkey.
 

          
@@ 430,18 446,8 @@ define-handler 'GET "/score/ownkey/" : g
              =>  50"
     define path : split-and-decode-uri-path : uri-path : request-uri request
     define ownkey : third path
-    define ownidx : key->index ownkey
-    define ownid : index->identity wotstate ownidx
-    define wotstate-with-cache : add-wotcache wotstate ownidx ownid
-    define scores
-        wotcache-scores
-            update-wotcache-from-wotstate! wotstate-with-cache ownid
     define otherkey : fifth path
-    define otherindex : key->index otherkey
-    define score
-        if : equal? ownkey otherkey
-           . "100" ;; trust of own ID is always 100
-           and=> (vector-ref scores otherindex) number->string
+    define score : get-score wotstate ownkey otherkey
     define code : if score 200 204 ;; 200 ok or 204 no content
     values
         build-response

          
@@ 551,10 557,112 @@ define-handler 'POST "/addtrust/" : post
             check-pruning-stale-ids wotstate ownids
         values
           build-response
-            . #:headers `((content-type . (text/html)))
+            . #:headers `((content-type . (text/plain)))
           . someid
 
 
+
+
+;; subscription handling
+;; subscriptions to IDs
+;; ((ownid1 otherid1 otherid2 ...) (ownid2 otherid3 otherid4 ...))
+define rank1-subscriptions '()
+define rank2-subscriptions-most-recent '()
+define rank2-subscriptions-random '()
+define rank3+-subscriptions-most-recent '()
+define rank3+-subscriptions-random '()
+define subscriptions-updated '()
+define : add-for-ownid! subs ownid id
+    define index
+        list-index : λ (x) : equal? ownid : first x
+                   . subs
+    define entry : list-ref subs index
+    define updated
+        cons (car entry) : cons id : cdr entry
+    list-set! subs index updated
+define-syntax-rule : add-to-subscription! subs ownid id
+  begin
+    unless : assoc ownid subs
+      set! subs
+           alist-cons ownid '() subs
+    add-for-ownid! subs ownid id
+define : get-subscriptions ownid
+    ;; TODO: update subscription-lists from wot and retrieve the
+    ;; defined IDs.
+    or : assoc-ref subscriptions-updated ownid
+       . '()
+
+
+;; alist of hash-tables (one per ownid) with ids as key and alists of metadata as value
+;; ((ownid . #<hash:((id: ((key . value) (key . value))) ...)>
+define id-metadata '()
+define : set-metadata! ownid id metadata
+    unless : assoc ownid id-metadata
+        let : : table : make-hash-table 8
+          set! id-metadata
+               alist-cons ownid table id-metadata
+    let : : table : assoc-ref id-metadata ownid
+      hash-set! table id metadata
+define : get-metadata ownid id
+    unless : assoc ownid id-metadata
+        let : : table : make-hash-table 8
+          set! id-metadata
+               alist-cons ownid table id-metadata
+    let : : table : assoc-ref id-metadata ownid
+      or (hash-ref table id) '()
+
+define-handler 'PUT "/subscription-updated/ownkey/" : put-subscription-updated-handler request body wotstate
+    . "Endpoint: /subscription-updated/ownkey/OWN/otherid/ID
+    
+    Add metadata needed to check the ID as form-data.
+    "
+    define path : split-and-decode-uri-path : uri-path : request-uri request
+    define ownkey : third path
+    define ownidx : key->index ownkey
+    define ownid : index->identity wotstate ownidx
+    define path-rest : drop path 4
+    define otherid : string-join path-rest "/" ;; ID can contain /
+    define otherkey : id->key wotstate otherid
+    ;; Ensure that there is at least one trust relationship to the
+    ;; other ID, so it is in the WoT.
+    unless : and otherkey : get-score wotstate ownkey otherkey
+        add-trust-edge wotstate ownid otherid 0
+    let : : body-string : bytevector->string body "UTF-8"
+        define body-decoded : uri-decode body-string
+        define metadata
+               map (cut string-split <> #\=) : string-split body-decoded #\&
+        set-metadata! ownid otherid metadata
+        add-to-subscription! subscriptions-updated ownid otherid
+        define code 204 ;; 204 no content
+        values
+          build-response
+            . #:headers `((content-type . (text/plain)))
+          . ""
+    
+
+define-handler 'GET "/subscriptions/" : get-subscriptions-handler request body wotstate
+    . "Endpoint: /subscriptions/ownid
+    
+    Returns a list of IDs to check for updates, separated by empty lines."
+    define path-raw : uri-path : request-uri request
+    define path : string-join (split-and-decode-uri-path path-raw) "/"
+    define ownid : string-drop path : string-length "subscriptions/"
+    ;; TODO: get subscriptions for ownid
+    define subscriptions : get-subscriptions ownid
+    define : metadata-strings ownid id
+        define metadata : get-metadata ownid id
+        map : lambda (cel) : string-join (list (first cel) (second cel)) ": "
+            . metadata
+    define with-metadata
+        map : lambda (id) : string-join (cons id (metadata-strings ownid id)) "\n"
+            . subscriptions
+    values
+            build-response
+              . #:headers `((content-type . (text/plain)))
+            string-join with-metadata "\n\n"
+
+
+;; persistence handling
 define store-directory #f
 define-handler 'PUT "/store/state" : put-state-handler request body wotstate
         . "Endpoint: /store/state