diff --git a/desk/app/contacts.hoon b/desk/app/contacts.hoon index 1d1f7df..74c7b1f 100644 --- a/desk/app/contacts.hoon +++ b/desk/app/contacts.hoon @@ -1,3 +1,4 @@ +/- activity /+ default-agent, dbug, verb, neg=negotiate /+ *contacts :: @@ -16,7 +17,7 @@ :: +| %types +$ card card:agent:gall -+$ state-1 $: %1 ++$ state-2 $: %2 rof=profile =book =peers @@ -30,7 +31,7 @@ %- agent:dbug %+ verb | ^- agent:gall -=| state-1 +=| state-2 =* state - =< |_ =bowl:gall +* this . @@ -100,6 +101,12 @@ :: +| %operations :: + ++ pass-activity + |= [who=ship field=(pair @tas value)] + ^- card + =/ =cage activity-action+!>(`action:activity`[%add %contact who field]) + [%pass /activity %agent [our.bowl %activity] %poke cage] + :: :: +pub: publication management :: :: - /v1/news: local updates to our profile and rolodex @@ -132,7 +139,10 @@ ++ subs ^- (set path) %- ~(rep by sup.bowl) - |= [[duct ship pat=path] acc=(set path)] + :: default .acc prevents invalid empty fact path in the case + :: of no subscribers + :: + |= [[duct ship pat=path] acc=_(sy `path`/v1/contact ~)] ?.(?=([%v1 %contact *] pat) acc (~(put in acc) pat)) ++ fact |= [pat=(set path) u=update] @@ -208,7 +218,6 @@ |= con=contact =/ p=profile [(mono wen.rof now.bowl) con] =. rof p - :: =. cor (p-news-0 our.bowl (contact:to-0 con)) =. cor @@ -239,8 +248,10 @@ ++ p-init |= wen=(unit @da) ?~ wen (give (fact ~ full+rof)) - ?: (gte u.wen wen.rof) cor - (give (fact ~ full+rof)) + ?: =(u.wen wen.rof) cor + :: + :: no future subs + ?>((lth u.wen wen.rof) (give (fact ~ full+rof))) :: +p-news-0: [legacy] publish news :: ++ p-news-0 @@ -340,15 +351,43 @@ cor =. cor (p-news-0:pub who (contact:to-0 con.u)) =/ page=(unit page) (~(get by book) who) - :: update peer contact page + :: update contact book and send notification :: =? cor ?=(^ page) ?: =(con.u.page con.u) cor =. book (~(put by book) who u.page(con con.u)) + =. cor (emil (send-activity u con.u.page)) (p-response:pub %page who con.u mod.u.page) (p-response:pub %peer who con.u) == :: + ++ send-activity + |= [u=update con=contact] + ^- (list card) + ?. .^(? %gu /(scot %p our.bowl)/activity/(scot %da now.bowl)/$) + ~ + %- ~(rep by con.u) + |= [field=(pair @tas value) cards=(list card)] + ?> ?=(^ q.field) + :: do not broadcast empty changes + :: + ?: (is-value-empty q.field) + cards + :: + =/ val=(unit value) (~(get by con) p.field) + ?~ val + [(pass-activity who field) cards] + ?< ?=(~ u.val) + ::NOTE currently shouldn't happen in practice + ?. =(-.q.field -.u.val) cards + ?: =(p.q.field p.u.val) cards + ?. ?=(%set -.q.field) + [(pass-activity who field) cards] + =/ diff=(set value) + (~(dif in p.q.field) ?>(?=(%set -.u.val) p.u.val)) + ?~ diff cards + [(pass-activity who p.field set+diff) cards] + :: ++ si-meet ^+ si-cor :: @@ -442,6 +481,7 @@ +| %implementation :: ++ init + =. wen.rof now.bowl (emit %pass /migrate %agent [our dap]:bowl %poke noun+!>(%migrate)) :: ++ load @@ -451,60 +491,80 @@ =? cor !=(okay cool) l-epic ?- -.old :: - %1 - =. state old - =/ cards - %+ roll ~(tap by peers) - |= [[who=ship foreign] caz=(list card)] - :: intent to connect, resubscribe - :: - ?: ?& =(%want sag) - !(~(has by wex.bowl) [/contact who dap.bowl]) - == - =/ =path [%v1 %contact ?~(for / /at/(scot %da wen.for))] - :_ caz - [%pass /contact %agent [who dap.bowl] %watch path] - caz - (emil cards) + %2 + =. state old + inflate-io :: - %0 - =. rof ?~(rof.old *profile (profile:from-0 rof.old)) - :: migrate peers. for each peer - :: 1. leave /epic, if any - :: 2. subscribe if desired - :: 3. put into peers + %1 + =. state old(- %2) + :: fix incorrectly bunted timestamp for + :: an empty profile migrated from %0 + :: + =? cor &(=(*@da wen.rof) ?=(~ con.rof)) + (p-commit-self:pub ~) + inflate-io :: - =^ caz=(list card) peers - %+ roll ~(tap by rol.old) - |= [[who=ship foreign-0:c0] caz=(list card) =_peers] - :: leave /epic if any + %0 + =. rof ?~(rof.old *profile (profile:from-0 rof.old)) + :: migrate peers. for each peer + :: 1. leave /epic, if any + :: 2. subscribe if desired + :: 3. put into peers :: - =? caz (~(has by wex.bowl) [/epic who dap.bowl]) + =^ caz=(list card) peers + %+ roll ~(tap by rol.old) + |= [[who=ship foreign-0:c0] caz=(list card) =_peers] + :: leave /epic if any + :: + =? caz (~(has by wex.bowl) [/epic who dap.bowl]) + :_ caz + [%pass /epic %agent [who dap.bowl] %leave ~] + =/ fir=$@(~ profile) + ?~ for ~ + (profile:from-0 for) + :: no intent to connect + :: + ?: =(~ sag) + :- caz + (~(put by peers) who fir ~) + :_ (~(put by peers) who fir %want) + ?: (~(has by wex.bowl) [/contact who dap.bowl]) + caz + =/ =path [%v1 %contact ?~(fir / /at/(scot %da wen.fir))] :_ caz - [%pass /epic %agent [who dap.bowl] %leave ~] - =/ fir=$@(~ profile) - ?~ for ~ - (profile:from-0 for) - :: no intent to connect - :: - ?: =(~ sag) - :- caz - (~(put by peers) who fir ~) - :_ (~(put by peers) who fir %want) - ?: (~(has by wex.bowl) [/contact who dap.bowl]) - caz - =/ =path [%v1 %contact ?~(fir / /at/(scot %da wen.fir))] - :_ caz - [%pass /contact %agent [who dap.bowl] %watch path] - (emil caz) + [%pass /contact %agent [who dap.bowl] %watch path] + (emil caz) == +$ state-0 [%0 rof=$@(~ profile-0:c0) rol=rolodex:c0] + +$ state-1 $: %1 + rof=profile + =^book + =^peers + retry=(map ship @da) :: retry sub at time + == +$ versioned-state - $% state-0 + $% state-2 state-1 + state-0 == :: ++ l-epic (give %fact [/epic ~] epic+!>(okay)) + :: + ++ inflate-io + ^+ cor + =/ cards + %+ roll ~(tap by peers) + |= [[who=ship foreign] caz=(list card)] + :: intent to connect, resubscribe + :: + ?: ?& =(%want sag) + !(~(has by wex.bowl) [/contact who dap.bowl]) + == + =/ =path [%v1 %contact ?~(for / /at/(scot %da wen.for))] + :_ caz + [%pass /contact %agent [who dap.bowl] %watch path] + caz + (emil cards) -- :: ++ poke @@ -710,7 +770,7 @@ ++ agent |= [=wire =sign:agent:gall] ^+ cor - ?+ wire ~|(evil-agent+wire !!) + ?+ wire ~|(evil-agent+wire !!) [%contact ~] si-abet:(si-take:(sub src.bowl) wire sign) :: @@ -719,6 +779,9 @@ ?~ p.sign cor %- (slog leaf/"{} failed" u.p.sign) cor + :: + [%activity ~] + cor :: [%epic ~] cor diff --git a/desk/lib/contacts.hoon b/desk/lib/contacts.hoon index 853b619..be6afe1 100644 --- a/desk/lib/contacts.hoon +++ b/desk/lib/contacts.hoon @@ -2,6 +2,16 @@ |% :: +| %contact +:: +is-value-empty: is value considered empty +:: +++ is-value-empty + |= val=value + ^- ? + ?+ -.val | + %text =('' p.val) + %look =('' p.val) + %set ?=(~ p.val) + == :: +cy: contact map engine :: ++ cy diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon new file mode 100644 index 0000000..8f5d1f0 --- /dev/null +++ b/desk/sur/activity.hoon @@ -0,0 +1,368 @@ +/- c=channels, t=contacts, ch=chat, g=groups +/+ mp=mop-extensions +|% ++| %collections +:: $stream: the activity stream comprised of events from various agents ++$ stream ((mop time event) lte) +:: $indices: the stream and its read data split into various indices ++$ indices + $~ [[[%base ~] *index] ~ ~] + (map source index) +:: $volume-settings: the volume settings for each source ++$ volume-settings (map source volume-map) +:: $activity: the current state of activity for each source ++$ activity (map source activity-summary) +:: $full-info: the full state of the activity stream ++$ full-info [=indices =activity =volume-settings] +:: $volume-map: how to badge and notify for each event type ++$ volume-map + $~ default-volumes + (map event-type volume) +:: $feed: a set of grouped events and the summaries of their sources ++$ feed + $: feed=(list activity-bundle) + summaries=activity + == ++$ feed-init + $: all=(list activity-bundle) + mentions=(list activity-bundle) + replies=(list activity-bundle) + summaries=activity + == ++| %actions +:: $action: how to interact with our activity stream +:: +:: actions are only ever performed for and by our selves +:: +:: %add: add an event to the stream +:: %bump: mark a source as having new activity from myself +:: %del: remove a source and all its activity +:: %read: mark an event as read +:: %adjust: adjust the volume of an source +:: %allow-notifications: change which notifications are allowed +:: ++$ action + $% [%add =incoming-event] + [%bump =source] + [%del =source] + [%del-event =source event=incoming-event] + [%read =source =read-action] + [%adjust =source =(unit volume-map)] + [%allow-notifications allow=notifications-allowed] + == +:: +:: $read-action: mark activity read +:: +:: %item: (DEPRECATED) mark an individual activity as read, indexed by id +:: %event: (DEPRECATED) mark an individual activity as read, indexed by the event itself +:: %all: mark _everything_ as read for this source, and possibly children +:: ++$ read-action + $% [%item id=time-id] + [%event event=incoming-event] + [%all time=(unit time) deep=?] + == +:: ++| %updates +:: +:: $update: what we hear after an action +:: +:: %add: an event was added to the stream +:: %del: a source and its activity were removed +:: %read: a source's activity state was updated +:: %activity: the activity state was updated +:: %adjust: the volume of a source was adjusted +:: %allow-notifications: the allowed notifications were changed +:: ++$ update + $% [%add =source time-event] + [%del =source] + [%read =source =activity-summary] + [%activity =activity] + [%adjust =source volume-map=(unit volume-map)] + [%allow-notifications allow=notifications-allowed] + == +:: ++| %basics +:: $event: a single point of activity, from one of our sources +:: +:: $incoming-event: the event that was sent to us +:: .notified: if this event has been notified +:: .child: if this event is from a child source +:: ++$ event + $: incoming-event + notified=? + child=? + == ++$ incoming-event + $% [%post post-event] + [%reply reply-event] + [%dm-invite =whom] + [%dm-post dm-post-event] + [%dm-reply dm-reply-event] + [%group-ask group=flag:g =ship] + [%group-kick group=flag:g =ship] + [%group-join group=flag:g =ship] + [%group-invite group=flag:g =ship] + [%chan-init channel=nest:c group=flag:g] + [%group-role group=flag:g =ship roles=(set sect:g)] + [%flag-post key=message-key channel=nest:c group=flag:g] + [%flag-reply key=message-key parent=message-key channel=nest:c group=flag:g] + [%contact contact-event] + == +:: ++$ post-event + $: key=message-key + channel=nest:c + group=flag:g + content=story:c + mention=? + == +:: ++$ reply-event + $: key=message-key + parent=message-key + channel=nest:c + group=flag:g + content=story:c + mention=? + == +:: ++$ dm-post-event + $: key=message-key + =whom + content=story:c + mention=? + == +:: ++$ dm-reply-event + $: key=message-key + parent=message-key + =whom + content=story:c + mention=? + == +:: ++$ contact-event + $: who=ship + update=(pair @tas value:t) + == +:: +:: $source: where the activity is happening ++$ source + $% [%base ~] + [%group =flag:g] + [%channel =nest:c group=flag:g] + [%thread key=message-key channel=nest:c group=flag:g] + [%dm =whom] + [%dm-thread key=message-key =whom] + [%contact who=ship] + == +:: +:: $index: the stream of activity and read state for a source ++$ index [=stream =reads bump=time] +:: +:: $reads: the read state for a source +:: +:: $floor: the time of the latest event that was read +:: $items: the set of events above the floor that have been read +:: ++$ reads + $: floor=time + items=read-items + == ++$ read-items ((mop time-id ,~) lte) +:: $activity-summary: the summary of activity for a source +:: +:: $newest: the time of the latest activity read or unread +:: $count: the total number of unread events including children +:: $notify-count: the number of unreads that are notifications +:: including children +:: $notify: if there are any notifications here or in children +:: $unread: if the main stream of source is unread: which starting +:: message, how many there are, and if any are notifications +:: $children: the sources nested under this source +:: ++$ activity-summary + $~ [*@da 0 0 | ~ ~ ~] + $: newest=time + count=@ud + notify-count=@ud + notify=_| + unread=(unit unread-point) + children=(set source) + reads=* :: DO NOT USE, 🚨 ⚠️ REMOVE + == ++$ unread-point [message-key count=@ud notify=_|] ++$ volume [unreads=? notify=?] ++$ notifications-allowed ?(%all %some %none) ++$ activity-bundle + $: =source + latest=time + events=(list time-event) + == +:: ++| %primitives ++$ whom + $% [%ship p=ship] + [%club p=id:club:ch] + == ++$ time-id time ++$ message-id (pair ship time-id) ++$ message-key [id=message-id =time] +:: ++$ event-type + $? %chan-init + %post + %post-mention + %reply + %reply-mention + %dm-invite + %dm-post + %dm-post-mention + %dm-reply + %dm-reply-mention + %group-invite + %group-kick + %group-join + %group-ask + %group-role + %flag-post + %flag-reply + %contact + == ++| %helpers ++$ time-event [=time =event] +++ on-event ((on time event) lte) +++ ex-event ((mp time event) lte) +++ on-read-items ((on time ,~) lte) ++| %constants +++ default-volumes + ^~ + ^- (map event-type volume) + %- my + :~ [%post & &] + [%reply & |] + [%dm-reply & &] + [%post-mention & &] + [%reply-mention & &] + [%dm-invite & &] + [%dm-post & &] + [%dm-post-mention & &] + [%dm-reply-mention & &] + [%group-invite & &] + [%group-ask & &] + [%flag-post & &] + [%flag-reply & &] + [%group-kick & |] + [%group-join & |] + [%group-role & |] + ::XX remove notify? + [%contact & &] + == +++ old-volumes + ^~ + %- my + :~ [%soft (~(put by default-volumes) %post [& |])] + [%loud (~(run by default-volumes) |=([u=? *] [u &]))] + [%hush (~(run by default-volumes) |=([u=? *] [u |]))] + == +++ mute + ^~ + (~(run by default-volumes) |=(* [| |])) +:: ++| %old-types +++ old + |% + ++ v7 + |% + +$ stream ((mop time event) lte) + +$ event + $: incoming-event + notified=? + child=? + == + +$ incoming-event + $% [%post post-event] + [%reply reply-event] + [%dm-invite =whom] + [%dm-post dm-post-event] + [%dm-reply dm-reply-event] + [%group-ask group=flag:g =ship] + [%group-kick group=flag:g =ship] + [%group-join group=flag:g =ship] + [%group-invite group=flag:g =ship] + [%chan-init channel=nest:c group=flag:g] + [%group-role group=flag:g =ship roles=(set sect:g)] + [%flag-post key=message-key channel=nest:c group=flag:g] + [%flag-reply key=message-key parent=message-key channel=nest:c group=flag:g] + == + +$ source + $% [%base ~] + [%group =flag:g] + [%channel =nest:c group=flag:g] + [%thread key=message-key channel=nest:c group=flag:g] + [%dm =whom] + [%dm-thread key=message-key =whom] + == + +$ index [=stream =reads bump=time] + -- + ++ v4 + |% + +$ feed (list activity-bundle) + -- + ++ v3 + |% + +$ index [=stream =reads] + +$ indices (map source index) + +$ update + $% [%add =source time-event] + [%del =source] + [%read =source =activity-summary] + [%adjust =source volume-map=(unit volume-map)] + [%allow-notifications allow=notifications-allowed] + == + +$ full-info + $: =indices + =activity + =volume-settings + == + +$ activity (map source activity-summary) + +$ activity-summary + $~ [*@da 0 0 | ~ ~ [*@da ~]] + $: newest=time + count=@ud + notify-count=@ud + notify=_| + unread=(unit unread-point) + children=(unit activity) + =reads + == + -- + ++ v2 + |% + +$ update + $% [%add =source time-event] + [%del =source] + [%read =source =activity-summary] + [%adjust =source volume-map=(unit volume-map)] + [%allow-notifications allow=notifications-allowed] + == + +$ full-info + $: =indices:v3 + activity=activity + =volume-settings + == + +$ activity (map source activity-summary) + +$ activity-summary + $~ [*@da 0 | ~ ~] + $: newest=time + count=@ud + notify=_| + unread=(unit unread-point) + children=(unit activity) + == + -- + -- +-- diff --git a/desk/sur/contacts-0.hoon b/desk/sur/contacts-0.hoon index a019da8..91ba0d3 100644 --- a/desk/sur/contacts-0.hoon +++ b/desk/sur/contacts-0.hoon @@ -57,4 +57,19 @@ :: +$ news-0 [who=ship con=$@(~ contact-0)] +:: +++ get-contact + |= [=bowl:gall who=@p] + => :_ ..get-contact + [who=who our=our.bowl now=now.bowl] + ~+ ^- (unit contact-0) + =/ base=path /(scot %p our)/contacts/(scot %da now) + ?. ~+ .^(? %gu (weld base /$)) + ~ + =+ ~+ .^(rol=rolodex %gx (weld base /all/contact-rolodex)) + ?~ for=(~(get by rol) who) + ~ + ?. ?=([[@ ^] *] u.for) + ~ + `con.for.u.for --