From ded2e68a795ed09150c64766f91a0b2872bfd987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Paraniak?= Date: Mon, 11 Nov 2024 19:20:00 +0800 Subject: [PATCH 1/2] contacts: implement activity --- desk/app/contacts.hoon | 54 ++++- desk/lib/activity.hoon | 375 ++++++++++++++++++++++++++++++++++ desk/lib/contacts.hoon | 10 + desk/lib/contacts/json-1.hoon | 3 +- desk/sur/activity.hoon | 368 +++++++++++++++++++++++++++++++++ desk/sur/contacts-0.hoon | 15 ++ 6 files changed, 814 insertions(+), 11 deletions(-) create mode 100644 desk/lib/activity.hoon create mode 100644 desk/sur/activity.hoon diff --git a/desk/app/contacts.hoon b/desk/app/contacts.hoon index 189fcb70..02a345d9 100644 --- a/desk/app/contacts.hoon +++ b/desk/app/contacts.hoon @@ -1,4 +1,4 @@ -/+ default-agent, dbug, verb, neg=negotiate +/+ default-agent, dbug, verb, neg=negotiate, activity /+ *contacts :: :: performance, keep warm @@ -100,6 +100,12 @@ :: +| %operations :: + ++ pass-activity + |= [who=ship field=(pair @tas value)] + ^- card + =/ =cage activity-action+!>([%add %contact who field]) + [%pass /activity %agent [our.bowl %activity] %poke cage] + :: :: +pub: publication management :: :: - /v1/news: local updates to our profile and rolodex @@ -344,15 +350,42 @@ 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) + ?. =(-.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 :: @@ -735,18 +768,21 @@ ++ agent |= [=wire =sign:agent:gall] ^+ cor - ?+ wire ~|(evil-agent+wire !!) + ?+ wire ~|(evil-agent+wire !!) [%contact ~] si-abet:(si-take:(sub src.bowl) wire sign) :: - [%migrate ~] - ?> ?=(%poke-ack -.sign) - ?~ p.sign cor - %- (slog leaf/"{} failed" u.p.sign) + [%migrate ~] + ?> ?=(%poke-ack -.sign) + ?~ p.sign cor + %- (slog leaf/"{} failed" u.p.sign) cor :: - [%epic ~] - cor + [%activity ~] + cor + :: + [%epic ~] + cor == :: ++ arvo diff --git a/desk/lib/activity.hoon b/desk/lib/activity.hoon new file mode 100644 index 00000000..50e8dc3e --- /dev/null +++ b/desk/lib/activity.hoon @@ -0,0 +1,375 @@ +/- a=activity +|% +++ src + |% + ++ get-parents + |= =source:a + ^- (list source:a) + ?: ?=(%base -.source) ~ + ?< ?=(%base -.source) + =- (snoc - [%base ~]) + ^- (list source:a) + ?+ -.source ~ + %channel ~[[%group group.source]] + %dm-thread ~[[%dm whom.source]] + :: + %thread + :~ [%channel channel.source group.source] + [%group group.source] + == + == + :: + ++ get-parent + |= [=indices:a =source:a] + ^- (unit source:a) + ?: ?=(%base -.source) ~ + ?< ?=(%base -.source) + =/ parent + ?- -.source + %dm [%base ~] + %contact [%base ~] + %group [%base ~] + %channel [%group group.source] + %dm-thread [%dm whom.source] + %thread [%channel channel.source group.source] + == + ?. (~(has by indices) parent) ~ + `parent + ++ get-children :: direct children only + |= [=indices:a =source:a] + ^- (list source:a) + ?: ?=(?(%thread %dm-thread %contact) -.source) ~ + %+ skim + ~(tap in ~(key by indices)) + |= src=source:a + ?- -.source + %base ?=(?(%group %dm) -.src) + %group &(?=(%channel -.src) =(flag.source group.src)) + %channel &(?=(%thread -.src) =(nest.source channel.src)) + %dm &(?=(%dm-thread -.src) =(whom.source whom.src)) + == + :: + ++ get-order + |= =source:a + %. -.source + %~ got by + ^~ + %- my + :~ [%contact 7] + [%thread 6] + [%dm-thread 5] + [%channel 4] + [%group 3] + [%dm 2] + [%base 1] + == + ++ get-volumes + |= [vs=volume-settings:a =source:a] + ^- volume-map:a + =/ target (~(get by vs) source) + ?^ target u.target + ?- -.source + %base *volume-map:a + %group (get-volumes vs %base ~) + %dm (get-volumes vs %base ~) + %dm-thread (get-volumes vs %dm whom.source) + %channel (get-volumes vs %group group.source) + %thread (get-volumes vs %channel channel.source group.source) + %contact (get-volumes vs %base ~) + == + :: + ++ sort-sources + |= sources=(list source:a) + :: sort children first in order so we only have to make one pass + :: of summarization aka not repeatedly updating the same source + :: + %+ sort + sources + |= [asrc=source:a bsrc=source:a] + (gth (get-order:src asrc) (get-order:src bsrc)) + :: + -- +++ stm + |% + ++ get-reads + |= [=stream:a start=(unit time) end=(unit time) ignore-children=?] + %+ murn + %- tap:on-event:a + %^ lot:on-event:a stream + ?~(start ~ `(sub u.start 1)) + ?~(end ~ `(add u.end 1)) + |= [=time =event:a] + :: ignore child events if enabled + ?: &(ignore-children child.event) ~ + `[time ~] + :: + -- +++ idx + |_ =index:a + ++ find-floor + |= [orig=stream:a =reads:a] + ^- (unit time) + :: starting at the last-known first-unread location (floor), walk towards + :: the present, to find the new first-unread location (new floor) + :: + :: slice off the earlier part of the stream, for efficiency + :: + =/ =stream:a (lot:on-event:a orig `floor.reads ~) + =| new-floor=(unit time) + |- + ?~ stream new-floor + :: + =/ [[=time =event:a] rest=stream:a] (pop:on-event:a stream) + =; is-read=? + :: if we found something that's unread, we need look no further + :: + ?. is-read $(stream ~) + :: otherwise, continue our walk towards the present + :: + $(new-floor `time, stream rest) + :: treat all other events as read + ?+ -<.event & + ?(%dm-post %dm-reply %post %reply) + ?=(^ (get:on-read-items:a items.reads time)) + == + :: + -- +++ evt + |% + ++ source + |= event=incoming-event:a + ^- source:a + ?- -.event + %chan-init [%channel channel.event group.event] + %post [%channel channel.event group.event] + %reply [%thread parent.event channel.event group.event] + %dm-invite [%dm whom.event] + %dm-post [%dm whom.event] + %dm-reply [%dm-thread parent.event whom.event] + %group-invite [%group group.event] + %group-kick [%group group.event] + %group-join [%group group.event] + %group-role [%group group.event] + %group-ask [%group group.event] + %flag-post [%group group.event] + %flag-reply [%group group.event] + %contact [%contact who.event] + == + :: + ++ event-type + |= event=incoming-event:a + ^- event-type:a + ?+ -.event -.event + %post ?:(mention.event %post-mention %post) + %reply ?:(mention.event %reply-mention %reply) + %dm-post ?:(mention.event %dm-post-mention %dm-post) + %dm-reply ?:(mention.event %dm-reply-mention %dm-reply) + == + :: + ++ is-allowed + |= [allowed=notifications-allowed:a =incoming-event:a] + ?: ?=(%all allowed) & + ?: ?=(%none allowed) | + =/ type (event-type incoming-event) + ?+ type | + %post & + %reply & + %contact & + %dm-post & + %dm-reply & + %dm-invite & + %group-invite & + %post-mention & + %reply-mention & + %dm-post-mention & + %dm-reply-mention & + == + :: + ++ get-volume + |= [vs=volume-settings:a event=incoming-event:a] + ^- volume:a + =/ source (source:evt event) + =/ loudness=volume-map:a (get-volumes:src vs source) + (~(gut by loudness) (event-type event) [unreads=& notify=|]) + :: + -- +:: +++ urd + |_ [=indices:a =activity:a =volume-settings:a log=$-((trap tape) _same)] + ++ summarize-unreads + |= [=source:a =index:a] + ^- activity-summary:a + %- (log |.("summarizing unreads for: {}")) + =/ top=time -:(fall (ram:on-event:a stream.index) [*@da ~]) + =/ unread-stream=stream:a + :: all base's events are from children so we can ignore + ?: ?=(%base -.source) ~ + :: we don't need to take child events into account when summarizing + :: the activity, so we filter them out + :: TODO: measure performance vs gas+murn+tap+lot + =- -> + %^ (dip:on-event:a @) + (lot:on-event:a stream.index `floor.reads.index ~) + ~ + |= [st=@ =time-id:a =event:a] + :_ [%.n st] + ?: child.event ~ + `event + =/ children (get-children:src indices source) + %- (log |.("children: {}")) + (stream-to-unreads source index(stream unread-stream) children top) + ++ sum-children + |= children=(list source:a) + ^- activity-summary:a + %+ roll + children + |= [=source:a sum=activity-summary:a] + =/ =index:a (~(gut by indices) source *index:a) + =/ as=activity-summary:a + ?~ summary=(~(get by activity) source) + => (summarize-unreads source index) + .(children ~) + u.summary(children ~) + %= sum + count (add count.sum count.as) + notify |(notify.sum notify.as) + newest (max newest.as newest.sum) + notify-count (add notify-count.sum notify-count.as) + == + ++ stream-to-unreads + |= [=source:a =index:a children=(list source:a) top=time] + ^- activity-summary:a + =/ cs=activity-summary:a + (sum-children children) + %- (log |.("children summary: {}")) + =/ newest=time :(max newest.cs floor.reads.index bump.index top) + =/ total + :: if we're a channel, we only want thread notify counts, not totals + :: + ?: ?=(%channel -.source) + notify-count.cs + count.cs + =/ notify-count notify-count.cs + =/ main 0 + =/ notified=? notify.cs + =/ main-notified=? | + =* stream stream.index + =| last=(unit message-key:a) + :: for each event + :: update count and newest + :: if reply, update thread state + |- + ?~ stream + :* newest + total + notify-count + notified + ?~(last ~ `[u.last main main-notified]) + ?:(?=(%base -.source) ~ (sy children)) + ~ + == + =/ [[=time =event:a] rest=stream:a] (pop:on-event:a stream) + =/ volume (get-volume:evt volume-settings -.event) + ::TODO support other event types + =* is-msg ?=(?(%dm-post %dm-reply %post %reply) -<.event) + =* is-init ?=(?(%dm-invite %chan-init) -<.event) + =* is-flag ?=(?(%flag-post %flag-reply) -<.event) + =* is-group ?=(%group-ask -<.event) + =* supported |(is-msg is-init is-flag is-group) + ?. supported $(stream rest) + =? notified &(notify.volume notified.event) & + =? notify-count &(notify.volume notified.event) +(notify-count) + =. newest (max newest time) + ?. &(unreads.volume ?=(?(%dm-post %dm-reply %post %reply) -<.event)) + $(stream rest) + =. total +(total) + =. main +(main) + =? main-notified &(notify:volume notified.event) & + =. last + ?~ last `key.event + last + $(stream rest) + -- +++ convert-to + |% + ++ v4 + |% + ++ feed + |= =feed:a + ^- feed:v4:old:a + feed.feed + -- + ++ v3 + |% + ++ activity + |= =activity:a + ^- activity:v3:old:a + %- ~(run by activity) + |= as=activity-summary:a + (activity-summary as activity) + ++ activity-summary + |= [as=activity-summary:a =activity:a] + ^- activity-summary:v3:old:a + :* newest.as + count.as + notify-count.as + notify.as + unread.as + :: + ?: =(~ children.as) ~ + :- ~ + %- ~(gas by *activity:v3:old:a) + %+ turn + ~(tap in children.as) + |= =source:a + =/ sum (~(got by activity) source) + :- source + (activity-summary sum(children ~) ~) + :: + [*@da ~] + == + ++ update + |= [=update:a =activity:a] + ^- update:v3:old:a + ?+ -.update update + %activity !! + %read + [%read source.update (activity-summary activity-summary.update activity)] + == + -- + ++ v2 + |% + ++ activity + |= =activity:a + ^- activity:v2:old:a + %- ~(run by activity) + |= as=activity-summary:a + (activity-summary as activity) + ++ activity-summary + |= [as=activity-summary:a =activity:a] + ^- activity-summary:v2:old:a + :* newest.as + count.as + notify.as + unread.as + :: + :- ~ + %- ~(gas by *activity:v2:old:a) + %+ turn + ~(tap in children.as) + |= =source:a + =/ sum (~(got by activity) source) + :- source + (activity-summary sum(children ~) ~) + == + ++ update + |= [=update:a =activity:a] + ^- update:v2:old:a + ?+ -.update update + %activity !! + %read + [%read source.update (activity-summary activity-summary.update activity)] + == + -- + -- +-- diff --git a/desk/lib/contacts.hoon b/desk/lib/contacts.hoon index 853b6191..428f7d38 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 =(0 (met 3 p.val)) + %look =(0 (met 3 p.val)) + %set ?=(~ p.val) + == :: +cy: contact map engine :: ++ cy diff --git a/desk/lib/contacts/json-1.hoon b/desk/lib/contacts/json-1.hoon index cbf368e0..a65fc33c 100644 --- a/desk/lib/contacts/json-1.hoon +++ b/desk/lib/contacts/json-1.hoon @@ -112,8 +112,7 @@ ++ value ^- $-(json value:c) |= jon=json - :: XX is there a way to do it in one go? - :: + ?~ jon ~ =/ [type=@tas val=json] %. jon (ot type+(se %tas) value+json ~) diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon new file mode 100644 index 00000000..8f5d1f0f --- /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 a019da82..91ba0d33 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 -- From 0889599961e21754b6bb9e6d410dbc4fca657523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Paraniak?= Date: Wed, 20 Nov 2024 22:33:54 +0800 Subject: [PATCH 2/2] contacts: refactor activity --- desk/app/contacts.hoon | 22 +-- desk/lib/activity.hoon | 375 ----------------------------------------- desk/lib/contacts.hoon | 4 +- 3 files changed, 14 insertions(+), 387 deletions(-) delete mode 100644 desk/lib/activity.hoon diff --git a/desk/app/contacts.hoon b/desk/app/contacts.hoon index 02a345d9..74c7b1fe 100644 --- a/desk/app/contacts.hoon +++ b/desk/app/contacts.hoon @@ -1,4 +1,5 @@ -/+ default-agent, dbug, verb, neg=negotiate, activity +/- activity +/+ default-agent, dbug, verb, neg=negotiate /+ *contacts :: :: performance, keep warm @@ -103,7 +104,7 @@ ++ pass-activity |= [who=ship field=(pair @tas value)] ^- card - =/ =cage activity-action+!>([%add %contact who field]) + =/ =cage activity-action+!>(`action:activity`[%add %contact who field]) [%pass /activity %agent [our.bowl %activity] %poke cage] :: :: +pub: publication management @@ -377,6 +378,7 @@ ?~ 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) @@ -772,17 +774,17 @@ [%contact ~] si-abet:(si-take:(sub src.bowl) wire sign) :: - [%migrate ~] - ?> ?=(%poke-ack -.sign) - ?~ p.sign cor - %- (slog leaf/"{} failed" u.p.sign) + [%migrate ~] + ?> ?=(%poke-ack -.sign) + ?~ p.sign cor + %- (slog leaf/"{} failed" u.p.sign) cor :: - [%activity ~] - cor + [%activity ~] + cor :: - [%epic ~] - cor + [%epic ~] + cor == :: ++ arvo diff --git a/desk/lib/activity.hoon b/desk/lib/activity.hoon deleted file mode 100644 index 50e8dc3e..00000000 --- a/desk/lib/activity.hoon +++ /dev/null @@ -1,375 +0,0 @@ -/- a=activity -|% -++ src - |% - ++ get-parents - |= =source:a - ^- (list source:a) - ?: ?=(%base -.source) ~ - ?< ?=(%base -.source) - =- (snoc - [%base ~]) - ^- (list source:a) - ?+ -.source ~ - %channel ~[[%group group.source]] - %dm-thread ~[[%dm whom.source]] - :: - %thread - :~ [%channel channel.source group.source] - [%group group.source] - == - == - :: - ++ get-parent - |= [=indices:a =source:a] - ^- (unit source:a) - ?: ?=(%base -.source) ~ - ?< ?=(%base -.source) - =/ parent - ?- -.source - %dm [%base ~] - %contact [%base ~] - %group [%base ~] - %channel [%group group.source] - %dm-thread [%dm whom.source] - %thread [%channel channel.source group.source] - == - ?. (~(has by indices) parent) ~ - `parent - ++ get-children :: direct children only - |= [=indices:a =source:a] - ^- (list source:a) - ?: ?=(?(%thread %dm-thread %contact) -.source) ~ - %+ skim - ~(tap in ~(key by indices)) - |= src=source:a - ?- -.source - %base ?=(?(%group %dm) -.src) - %group &(?=(%channel -.src) =(flag.source group.src)) - %channel &(?=(%thread -.src) =(nest.source channel.src)) - %dm &(?=(%dm-thread -.src) =(whom.source whom.src)) - == - :: - ++ get-order - |= =source:a - %. -.source - %~ got by - ^~ - %- my - :~ [%contact 7] - [%thread 6] - [%dm-thread 5] - [%channel 4] - [%group 3] - [%dm 2] - [%base 1] - == - ++ get-volumes - |= [vs=volume-settings:a =source:a] - ^- volume-map:a - =/ target (~(get by vs) source) - ?^ target u.target - ?- -.source - %base *volume-map:a - %group (get-volumes vs %base ~) - %dm (get-volumes vs %base ~) - %dm-thread (get-volumes vs %dm whom.source) - %channel (get-volumes vs %group group.source) - %thread (get-volumes vs %channel channel.source group.source) - %contact (get-volumes vs %base ~) - == - :: - ++ sort-sources - |= sources=(list source:a) - :: sort children first in order so we only have to make one pass - :: of summarization aka not repeatedly updating the same source - :: - %+ sort - sources - |= [asrc=source:a bsrc=source:a] - (gth (get-order:src asrc) (get-order:src bsrc)) - :: - -- -++ stm - |% - ++ get-reads - |= [=stream:a start=(unit time) end=(unit time) ignore-children=?] - %+ murn - %- tap:on-event:a - %^ lot:on-event:a stream - ?~(start ~ `(sub u.start 1)) - ?~(end ~ `(add u.end 1)) - |= [=time =event:a] - :: ignore child events if enabled - ?: &(ignore-children child.event) ~ - `[time ~] - :: - -- -++ idx - |_ =index:a - ++ find-floor - |= [orig=stream:a =reads:a] - ^- (unit time) - :: starting at the last-known first-unread location (floor), walk towards - :: the present, to find the new first-unread location (new floor) - :: - :: slice off the earlier part of the stream, for efficiency - :: - =/ =stream:a (lot:on-event:a orig `floor.reads ~) - =| new-floor=(unit time) - |- - ?~ stream new-floor - :: - =/ [[=time =event:a] rest=stream:a] (pop:on-event:a stream) - =; is-read=? - :: if we found something that's unread, we need look no further - :: - ?. is-read $(stream ~) - :: otherwise, continue our walk towards the present - :: - $(new-floor `time, stream rest) - :: treat all other events as read - ?+ -<.event & - ?(%dm-post %dm-reply %post %reply) - ?=(^ (get:on-read-items:a items.reads time)) - == - :: - -- -++ evt - |% - ++ source - |= event=incoming-event:a - ^- source:a - ?- -.event - %chan-init [%channel channel.event group.event] - %post [%channel channel.event group.event] - %reply [%thread parent.event channel.event group.event] - %dm-invite [%dm whom.event] - %dm-post [%dm whom.event] - %dm-reply [%dm-thread parent.event whom.event] - %group-invite [%group group.event] - %group-kick [%group group.event] - %group-join [%group group.event] - %group-role [%group group.event] - %group-ask [%group group.event] - %flag-post [%group group.event] - %flag-reply [%group group.event] - %contact [%contact who.event] - == - :: - ++ event-type - |= event=incoming-event:a - ^- event-type:a - ?+ -.event -.event - %post ?:(mention.event %post-mention %post) - %reply ?:(mention.event %reply-mention %reply) - %dm-post ?:(mention.event %dm-post-mention %dm-post) - %dm-reply ?:(mention.event %dm-reply-mention %dm-reply) - == - :: - ++ is-allowed - |= [allowed=notifications-allowed:a =incoming-event:a] - ?: ?=(%all allowed) & - ?: ?=(%none allowed) | - =/ type (event-type incoming-event) - ?+ type | - %post & - %reply & - %contact & - %dm-post & - %dm-reply & - %dm-invite & - %group-invite & - %post-mention & - %reply-mention & - %dm-post-mention & - %dm-reply-mention & - == - :: - ++ get-volume - |= [vs=volume-settings:a event=incoming-event:a] - ^- volume:a - =/ source (source:evt event) - =/ loudness=volume-map:a (get-volumes:src vs source) - (~(gut by loudness) (event-type event) [unreads=& notify=|]) - :: - -- -:: -++ urd - |_ [=indices:a =activity:a =volume-settings:a log=$-((trap tape) _same)] - ++ summarize-unreads - |= [=source:a =index:a] - ^- activity-summary:a - %- (log |.("summarizing unreads for: {}")) - =/ top=time -:(fall (ram:on-event:a stream.index) [*@da ~]) - =/ unread-stream=stream:a - :: all base's events are from children so we can ignore - ?: ?=(%base -.source) ~ - :: we don't need to take child events into account when summarizing - :: the activity, so we filter them out - :: TODO: measure performance vs gas+murn+tap+lot - =- -> - %^ (dip:on-event:a @) - (lot:on-event:a stream.index `floor.reads.index ~) - ~ - |= [st=@ =time-id:a =event:a] - :_ [%.n st] - ?: child.event ~ - `event - =/ children (get-children:src indices source) - %- (log |.("children: {}")) - (stream-to-unreads source index(stream unread-stream) children top) - ++ sum-children - |= children=(list source:a) - ^- activity-summary:a - %+ roll - children - |= [=source:a sum=activity-summary:a] - =/ =index:a (~(gut by indices) source *index:a) - =/ as=activity-summary:a - ?~ summary=(~(get by activity) source) - => (summarize-unreads source index) - .(children ~) - u.summary(children ~) - %= sum - count (add count.sum count.as) - notify |(notify.sum notify.as) - newest (max newest.as newest.sum) - notify-count (add notify-count.sum notify-count.as) - == - ++ stream-to-unreads - |= [=source:a =index:a children=(list source:a) top=time] - ^- activity-summary:a - =/ cs=activity-summary:a - (sum-children children) - %- (log |.("children summary: {}")) - =/ newest=time :(max newest.cs floor.reads.index bump.index top) - =/ total - :: if we're a channel, we only want thread notify counts, not totals - :: - ?: ?=(%channel -.source) - notify-count.cs - count.cs - =/ notify-count notify-count.cs - =/ main 0 - =/ notified=? notify.cs - =/ main-notified=? | - =* stream stream.index - =| last=(unit message-key:a) - :: for each event - :: update count and newest - :: if reply, update thread state - |- - ?~ stream - :* newest - total - notify-count - notified - ?~(last ~ `[u.last main main-notified]) - ?:(?=(%base -.source) ~ (sy children)) - ~ - == - =/ [[=time =event:a] rest=stream:a] (pop:on-event:a stream) - =/ volume (get-volume:evt volume-settings -.event) - ::TODO support other event types - =* is-msg ?=(?(%dm-post %dm-reply %post %reply) -<.event) - =* is-init ?=(?(%dm-invite %chan-init) -<.event) - =* is-flag ?=(?(%flag-post %flag-reply) -<.event) - =* is-group ?=(%group-ask -<.event) - =* supported |(is-msg is-init is-flag is-group) - ?. supported $(stream rest) - =? notified &(notify.volume notified.event) & - =? notify-count &(notify.volume notified.event) +(notify-count) - =. newest (max newest time) - ?. &(unreads.volume ?=(?(%dm-post %dm-reply %post %reply) -<.event)) - $(stream rest) - =. total +(total) - =. main +(main) - =? main-notified &(notify:volume notified.event) & - =. last - ?~ last `key.event - last - $(stream rest) - -- -++ convert-to - |% - ++ v4 - |% - ++ feed - |= =feed:a - ^- feed:v4:old:a - feed.feed - -- - ++ v3 - |% - ++ activity - |= =activity:a - ^- activity:v3:old:a - %- ~(run by activity) - |= as=activity-summary:a - (activity-summary as activity) - ++ activity-summary - |= [as=activity-summary:a =activity:a] - ^- activity-summary:v3:old:a - :* newest.as - count.as - notify-count.as - notify.as - unread.as - :: - ?: =(~ children.as) ~ - :- ~ - %- ~(gas by *activity:v3:old:a) - %+ turn - ~(tap in children.as) - |= =source:a - =/ sum (~(got by activity) source) - :- source - (activity-summary sum(children ~) ~) - :: - [*@da ~] - == - ++ update - |= [=update:a =activity:a] - ^- update:v3:old:a - ?+ -.update update - %activity !! - %read - [%read source.update (activity-summary activity-summary.update activity)] - == - -- - ++ v2 - |% - ++ activity - |= =activity:a - ^- activity:v2:old:a - %- ~(run by activity) - |= as=activity-summary:a - (activity-summary as activity) - ++ activity-summary - |= [as=activity-summary:a =activity:a] - ^- activity-summary:v2:old:a - :* newest.as - count.as - notify.as - unread.as - :: - :- ~ - %- ~(gas by *activity:v2:old:a) - %+ turn - ~(tap in children.as) - |= =source:a - =/ sum (~(got by activity) source) - :- source - (activity-summary sum(children ~) ~) - == - ++ update - |= [=update:a =activity:a] - ^- update:v2:old:a - ?+ -.update update - %activity !! - %read - [%read source.update (activity-summary activity-summary.update activity)] - == - -- - -- --- diff --git a/desk/lib/contacts.hoon b/desk/lib/contacts.hoon index 428f7d38..be6afe1d 100644 --- a/desk/lib/contacts.hoon +++ b/desk/lib/contacts.hoon @@ -8,8 +8,8 @@ |= val=value ^- ? ?+ -.val | - %text =(0 (met 3 p.val)) - %look =(0 (met 3 p.val)) + %text =('' p.val) + %look =('' p.val) %set ?=(~ p.val) == :: +cy: contact map engine