diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index 9ee7f17cb0..43cb39bdb0 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -48,7 +48,7 @@ +$ card card:agent:gall :: +$ current-state - $: %5 + $: %6 allowed=notifications-allowed:a =indices:a =activity:a @@ -135,11 +135,31 @@ =? old ?=(%2 -.old) (state-2-to-3 old) =? old ?=(%3 -.old) (state-3-to-4 old) =? old ?=(%4 -.old) (state-4-to-5 old) - ?> ?=(%5 -.old) + =? old ?=(%5 -.old) (state-5-to-6 old) + ?> ?=(%6 -.old) =. state old - sync-reads - +$ versioned-state $%(state-5 state-4 state-3 state-2 state-1) - +$ state-5 current-state + (emit %pass /fix-init-unreads %agent [our.bowl dap.bowl] %poke noun+!>(%fix-init-unreads)) + +$ versioned-state + $% state-6 + state-5 + state-4 + state-3 + state-2 + state-1 + == + +$ state-6 current-state + +$ state-5 _%*(. *state-6 - %5) + ++ state-5-to-6 + |= old=state-5 + ^- state-6 + =/ [=indices:a =activity:a] + (sync-reads indices.old activity.old volume-settings.old) + :* %6 + allowed.old + indices + activity + volume-settings.old + == +$ state-4 $: %4 allowed=notifications-allowed:a @@ -180,7 +200,7 @@ :* %4 allowed.old new-indices - (activity-3-to-4 new-indices) + (activity-3-to-4 new-indices volume-settings.old) volume-settings.old == ++ indices-3-to-4 @@ -188,14 +208,14 @@ ^- indices:a (~(run by indices) |=([=stream:a =reads:a] [stream reads *@da])) ++ activity-3-to-4 - |= =indices:a + |= [=indices:a vs=volume-settings:a] ^- activity:a =/ sources (sort-sources:src ~(tap in ~(key by indices))) %+ roll sources |= [=source:a =activity:a] =/ index (~(got by indices) source) %+ ~(put by activity) source - (summarize-unreads source index) + (~(summarize-unreads urd indices activity vs log) source index) +$ state-2 $: %2 allowed=notifications-allowed:a @@ -231,16 +251,19 @@ ?+ mark ~|(bad-poke+mark !!) %noun ?+ q.vase ~|(bad-poke+mark !!) + %refresh-activity refresh-all-summaries + %clean-keys correct-dm-keys + %fix-init-unreads fix-init-unreads + :: + %sync-reads + =^ indices activity + (sync-reads indices activity volume-settings) + cor(indices indices) + :: %migrate =. state *current-state =. allowed %all migrate - %refresh-activity - refresh-all-summaries - %clean-keys - correct-dm-keys - %sync-reads - sync-reads == :: %activity-action @@ -797,102 +820,63 @@ =. allowed na (give-update [%allow-notifications na] [%hose ~]) ++ 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) -++ stream-to-unreads - |= [=source:a =index:a children=(list source:a) top=time] - ^- activity-summary:a - =/ cs=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) - == - %- (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) - =* supported |(is-msg is-init is-flag) - ?. 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) + ~(summarize-unreads urd indices activity volume-settings log) :: +:: when we migrated from chat and channels, we always added an init event +:: so that we can mark what's been joined and have something affect the +:: recency so that it ends up in the correct place on the sidebar. +:: however, we forgot to mark these as read, so we need to do that now. +:: otherwise we end up with a bunch of dms/channels that are +:: incorrectly unread. +++ fix-init-unreads + =+ .^(=channels:c %gx (scry-path %channels /v2/channels/full/noun)) + =. indices (fix-channel-init-unreads indices channels) + =+ .^ [dms=(map ship dm:ch) clubs=(map id:club:ch club:ch)] + %gx (scry-path %chat /full/noun) + == + =. indices (fix-dm-init-unreads indices dms clubs) + refresh-all-summaries +++ fix-channel-init-unreads + |= [=indices:a =channels:c] + %- ~(urn by indices) + |= [=source:a =index:a] + :: if we're a channel with only the %chan-init event, we need to set + :: the floor to the last-read time + ?. ?& ?=(%channel -.source) + ?=([[* [[%chan-init *] *]] ~ ~] stream.index) + == + index + ?~ channel=(~(get by channels) nest.source) index + index(floor.reads last-read.remark.u.channel) +++ fix-dm-init-unreads + |= [=indices:a dms=(map ship dm:ch) clubs=(map id:club:ch club:ch)] + ^- indices:a + %- ~(urn by indices) + |= [=source:a =index:a] + :: if we're a DM with only the %dm-invite event, we need to set + :: the floor to the last-read time + ?. ?& ?=(%dm -.source) + ?=([[* [[%dm-invite *] *]] ~ ~] stream.index) + == + index + ?- -.whom.source + %ship + ?~ dm=(~(get by dms) p.whom.source) index + index(floor.reads last-read.remark.u.dm) + :: + %club + ?~ club=(~(get by clubs) p.whom.source) index + index(floor.reads last-read.remark.u.club) + == :: previously we used items as a way to track individual reads because :: floors were not local, but we have reverted to local floors and not :: tracking individual reads :: ++ sync-reads + |= [=indices:a =activity:a vs=volume-settings:a] =/ sources (sort-sources:src ~(tap in ~(key by indices))) |- - ?~ sources cor + ?~ sources [indices activity] =/ =source:a i.sources =/ =index:a (~(got by indices) source) =/ old-floor floor.reads.index @@ -908,13 +892,18 @@ [~ %.n ?:((gth time-id st) time-id st)] =. reads.index [new-floor ~] :: with new reads, update our index and summary - =. cor (refresh-index source index) - =/ new=(unit activity-summary:a) (~(get by activity) source) - ?: !=(?~(old ~ u.old(reads ~)) ?~(new ~ u.new(reads ~))) + =. indices (~(put by indices) source index) + =/ new-summary + %+ ~(summarize-unreads urd indices activity vs log) + source + index + =. activity + (~(put by activity) source new-summary) + ?: !=(?~(old ~ u.old(reads ~)) new-summary(reads ~)) ~& "%sync-reads: WARNING old and new summaries differ {}" ~& "old floor: {} new floor: {}" ~& "old: {}" - ~& "new: {}" + ~& "new: {<`new-summary>}" $(sources t.sources) $(sources t.sources) :: @@ -1062,7 +1051,8 @@ =/ entries ~(tap by unreads) =; events=(list [time incoming-event:a]) |- - ?~ events cor + ?~ events + cor(indices (fix-channel-init-unreads indices channels)) =. cor (%*(. add-event start-time -.i.events) +.i.events) $(events t.events) |- ^- (list [time incoming-event:a]) @@ -1123,7 +1113,8 @@ =/ entries ~(tap by unreads) =; events=(list [time incoming-event:a]) |- - ?~ events cor + ?~ events + cor(indices (fix-dm-init-unreads indices dms clubs)) =. cor (%*(. add-event start-time -.i.events) +.i.events) $(events t.events) |- ^- (list [time incoming-event:a]) diff --git a/desk/lib/activity.hoon b/desk/lib/activity.hoon index 114ff50e46..8788bbe43c 100644 --- a/desk/lib/activity.hoon +++ b/desk/lib/activity.hoon @@ -172,6 +172,101 @@ :: -- :: +++ 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) + =* supported |(is-msg is-init is-flag) + ?. 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 diff --git a/desk/lib/test-agent.hoon b/desk/lib/test-agent.hoon index 5b499ed1e8..a2d58ab1db 100644 --- a/desk/lib/test-agent.hoon +++ b/desk/lib/test-agent.hoon @@ -352,7 +352,7 @@ :: %+ skip caz |= =card - ?=([%give %fact [[%verb %events ~] ~] *] card) + ?=([%give %fact [[%verb ?(%events %events-plus) ~] ~] *] card) =/ m (mare ,~) ^- form:m |= s=state diff --git a/desk/tests/app/activity.hoon b/desk/tests/app/activity.hoon index bf1e180b5b..ce3147830d 100644 --- a/desk/tests/app/activity.hoon +++ b/desk/tests/app/activity.hoon @@ -1,22 +1,22 @@ -/- a=activity, g=groups, c=channels +/- a=activity, g=groups, c=channels, ch=chat /+ *activity, *test-agent /= activity-agent /app/activity |% ++ dap %activity ++ test-sync-reads-0 - =+ state-0 + =+ state-0:sync-reads (run-sync-reads pre-sync post-sync activity 5) :: ++ test-sync-reads-1 - =+ state-1 + =+ state-1:sync-reads (run-sync-reads pre-sync post-sync activity 5) :: ++ test-sync-reads-2 - =+ state-2 + =+ state-2:sync-reads (run-sync-reads pre-sync post-sync activity 5) :: ++ test-sync-reads-3 - =+ state-3 + =+ state-3:sync-reads (run-sync-reads pre-sync post-sync activity 9) ++ run-sync-reads |= [pre=indices:a post=indices:a =activity:a count=@ud] @@ -29,445 +29,645 @@ ;< * bind:m (ex-equal !>(~(wyt by pre)) !>(count)) ;< new=vase bind:m get-save =/ want-indices post - =+ !<(new-state=current-state new) + =+ !<(=new-state new) =/ new-indices indices.new-state (ex-equal !>(new-indices) !>(want-indices)) :: -+$ current-state - $: %5 +++ test-fix-init + =+ state-0:fix-init + %- eval-mare + =/ m (mare ,~) + ^- form:m + ;< * bind:m (set-scry-gate scries) + ;< * bind:m (do-init dap activity-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~zod, src ~zod))) + =/ start-state [%6 %some indices pre-fix volumes] + ;< caz=(list card:agent:gall) bind:m (do-load activity-agent `!>(start-state)) + ;< * bind:m (ex-equal !>(~(wyt by pre-fix)) !>(11)) + =/ cage noun+!>(%fix-init-unreads) + ;< bowl bind:m get-bowl + ;< * bind:m + %+ ex-cards caz + ~[(ex-poke /fix-init-unreads [~zod dap] cage)] + ;< * bind:m (do-poke cage) + ;< new=vase bind:m get-save + =+ !<(=new-state new) + (ex-equal !>(activity.new-state) !>(post-fix)) ++$ new-state + $: %6 allowed=notifications-allowed:a =indices:a =activity:a =volume-settings:a == +$ index-pair [=source:a =index:a] -+$ source-set - $: thrd1=index-pair - thrd2=index-pair - chnl=index-pair - grp=index-pair - base=index-pair - == -:: in this case, we test when a child has unreads before any of the parents, -:: resulting in a large list of reads and a very early floor -++ state-0 ++$ dms (map ship dm:ch) ++$ clubs (map id:club:ch club:ch) +++ sync-reads |% - ++ sources - ^- source-set - =/ =reads:a - :- d-1 - %+ gas:on-read-items:a *read-items:a - :~ [d1 ~] - [d2 ~] - [d3 ~] - [d4 ~] - [d5 ~] - == - =/ thrd1 (thread-source-1 flag nest i0) - =/ thrd2 (thread-source-2 flag nest i0) - =/ chnl (channel-source flag nest reads index.thrd1 index.thrd2 i0) - =/ grp (group-source flag index.chnl) - :* thrd1 - thrd2 - chnl - grp - (base-source index.grp) - == - ++ pre-sync - ^- indices:a - %- ~(gas by *indices:a) - => sources - .(base [base ~]) - ++ post-sync - ^- indices:a - =+ sources - :: only care about the index - %- my - :~ thrd1 - thrd2 - chnl(reads.index [d4 ~]) - grp(reads.index [d-1 ~]) - base(reads.index [d-1 ~]) + +$ source-set + $: thrd1=index-pair + thrd2=index-pair + chnl=index-pair + grp=index-pair + base=index-pair == - ++ activity - ^- activity:a - =+ sources - =/ chan-sum=activity-summary:a - :* newest=d5 - count=0 - notify-count=0 - notify=| - unread=~ - children=(sy (get-children:src pre-sync source.chnl)) - reads=[d-1 (my [d1 ~] [d2 ~] [d3 ~] [d4 ~] [d5 ~] ~)] + :: in this case, we test when a child has unreads before any of the parents, + :: resulting in a large list of reads and a very early floor + ++ state-0 + |% + ++ sources + ^- source-set + =/ =reads:a + :- d-1 + %+ gas:on-read-items:a *read-items:a + :~ [d1 ~] + [d2 ~] + [d3 ~] + [d4 ~] + [d5 ~] + == + =/ thrd1 (thread-source-1 flag nest i0) + =/ thrd2 (thread-source-2 flag nest i0) + =/ chnl + %+ merge-children (channel-source flag nest reads i0) + ~[stream.index.thrd1 stream.index.thrd2] + =/ grp + %+ merge-children (group-source flag reads.index.chnl) + ~[stream.index.chnl] + :* thrd1 + thrd2 + chnl + grp + (merge-children (base-source reads.index.grp) ~[stream.index.grp]) + == + ++ pre-sync + ^- indices:a + %- ~(gas by *indices:a) + => sources + .(base [base ~]) + ++ post-sync + ^- indices:a + =+ sources + :: only care about the index + %- my + :~ thrd1 + thrd2 + chnl(reads.index [d4 ~]) + grp(reads.index [d-1 ~]) + base(reads.index [d-1 ~]) == - %- my - :~ :- source.thrd1 + ++ activity + ^- activity:a + =+ sources + =/ chan-sum=activity-summary:a :* newest=d5 - count=2 - notify-count=0 - notify=| - unread=`[[[~dev d0] d0] 2 |] - children=(sy (get-children:src pre-sync source.thrd1)) - reads=[d-1 ~] - == - :: - :- source.thrd2 - :* newest=d2 count=0 notify-count=0 notify=| unread=~ - children=(sy (get-children:src pre-sync source.thrd2)) - reads=[d2 ~] + children=(sy (get-children:src pre-sync source.chnl)) + reads=[d-1 (my [d1 ~] [d2 ~] [d3 ~] [d4 ~] [d5 ~] ~)] == - :: - [source.chnl chan-sum] - :: - :- source.grp - chan-sum(children (sy (get-children:src pre-sync source.grp))) - :: - :- source.base - chan-sum(children ~) - == - -- -:: in this case, we test when a child has unreads later than any of the -:: parents, but all other children are read, resulting in a late floor -:: and no reads -++ state-1 - |% - ++ sources - ^- source-set - =/ thrd1 (thread-source-2 flag nest i0) - =/ thrd2 (thread-source-3 flag nest i0) - =/ =reads:a [d-1 (my [d1 ~] [d2 ~] [d3 ~] ~)] - =/ chnl (channel-source flag nest reads index.thrd1 index.thrd2 i0) - =/ grp (group-source flag index.chnl) - :* thrd1 - thrd2 - chnl - grp - (base-source index.grp) - == - ++ pre-sync - ^- indices:a - %- ~(gas by *indices:a) - => sources - .(base [base ~]) - ++ post-sync - ^- indices:a - =+ sources - %- my - :~ thrd1 - thrd2 - chnl(reads.index [d3 ~]) - grp(reads.index [d-1 ~]) - base(reads.index [d-1 ~]) - == - ++ activity - ^- activity:a - =+ sources - =/ chan-sum=activity-summary:a - :* newest=d5 - count=1 - notify-count=0 - notify=| - unread=`[[[~dev d4] d4] 1 |] - children=(sy (get-children:src pre-sync source.chnl)) - reads=[d-1 (my [d1 ~] [d2 ~] [d3 ~] ~)] + %- my + :~ :- source.thrd1 + :* newest=d5 + count=2 + notify-count=0 + notify=| + unread=`[[[~dev d0] d0] 2 |] + children=(sy (get-children:src pre-sync source.thrd1)) + reads=[d-1 ~] + == + :: + :- source.thrd2 + :* newest=d2 + count=0 + notify-count=0 + notify=| + unread=~ + children=(sy (get-children:src pre-sync source.thrd2)) + reads=[d2 ~] + == + :: + [source.chnl chan-sum] + :: + :- source.grp + chan-sum(children (sy (get-children:src pre-sync source.grp))) + :: + :- source.base + chan-sum(children ~) == - %- my - :~ :- source.thrd1 - :* newest=d2 - count=0 - notify-count=0 - notify=| - unread=~ - children=(sy (get-children:src pre-sync source.thrd1)) - reads=[d-1 ~] - == - :: - :- source.thrd2 + -- + :: in this case, we test when a child has unreads later than any of the + :: parents, but all other children are read, resulting in a late floor + :: and no reads + ++ state-1 + |% + ++ sources + ^- source-set + =/ thrd1 (thread-source-2 flag nest i0) + =/ thrd2 (thread-source-3 flag nest i0) + =/ =reads:a [d-1 (my [d1 ~] [d2 ~] [d3 ~] ~)] + =/ chnl + %+ merge-children (channel-source flag nest reads i0) + ~[stream.index.thrd1 stream.index.thrd2] + =/ grp + %+ merge-children (group-source flag reads.index.chnl) + ~[stream.index.chnl] + :* thrd1 + thrd2 + chnl + grp + (merge-children (base-source reads.index.grp) ~[stream.index.grp]) + == + ++ pre-sync + ^- indices:a + %- ~(gas by *indices:a) + => sources + .(base [base ~]) + ++ post-sync + ^- indices:a + =+ sources + %- my + :~ thrd1 + thrd2 + chnl(reads.index [d3 ~]) + grp(reads.index [d-1 ~]) + base(reads.index [d-1 ~]) + == + ++ activity + ^- activity:a + =+ sources + =/ chan-sum=activity-summary:a :* newest=d5 count=1 notify-count=0 notify=| - unread=`[[[~dev d5] d5] 1 |] - children=(sy (get-children:src pre-sync source.thrd2)) - reads=[d0 ~] - == - :: - [source.chnl chan-sum] - :: - :- source.grp - %= chan-sum - unread ~ - children (sy (get-children:src pre-sync source.grp)) + unread=`[[[~dev d4] d4] 1 |] + children=(sy (get-children:src pre-sync source.chnl)) + reads=[d-1 (my [d1 ~] [d2 ~] [d3 ~] ~)] == - :: - :- source.base - %= chan-sum - unread ~ - children ~ - == - == - -- -:: in this case we test a parent having mixed reads with children that -:: are all read, resulting in a floor up to the parent reads and one -:: read item that comes later -++ state-2 - |% - ++ sources - ^- source-set - =/ thrd1 (thread-source-2 flag nest i0) - =/ thrd2 (thread-source-3 flag nest i0) - =. thrd2 thrd2(reads.index [d5 ~]) - =/ =reads:a [d3 (my [d5 ~] ~)] - =/ chnl (channel-source flag nest reads index.thrd1 index.thrd2 i0) - =/ grp (group-source flag index.chnl) - :* thrd1 - thrd2 - chnl - grp - (base-source index.grp) - == - ++ pre-sync - ^- indices:a - %- ~(gas by *indices:a) - => sources - .(base [base ~]) - ++ post-sync - ^- indices:a - =+ sources - %- my - :~ thrd1 - thrd2 - chnl(reads.index [d3 ~]) - grp(reads.index [d3 ~]) - base(reads.index [d3 ~]) - == - ++ activity - ^- activity:a - =+ sources - =/ chan-sum=activity-summary:a - :* newest=d5 - count=1 - notify-count=0 - notify=| - unread=`[[[~dev d4] d4] 1 |] - children=(sy (get-children:src pre-sync source.chnl)) - reads=[d3 (my [d3 ~] [d5 ~] ~)] + %- my + :~ :- source.thrd1 + :* newest=d2 + count=0 + notify-count=0 + notify=| + unread=~ + children=(sy (get-children:src pre-sync source.thrd1)) + reads=[d-1 ~] + == + :: + :- source.thrd2 + :* newest=d5 + count=1 + notify-count=0 + notify=| + unread=`[[[~dev d5] d5] 1 |] + children=(sy (get-children:src pre-sync source.thrd2)) + reads=[d0 ~] + == + :: + [source.chnl chan-sum] + :: + :- source.grp + %= chan-sum + unread ~ + children (sy (get-children:src pre-sync source.grp)) + == + :: + :- source.base + %= chan-sum + unread ~ + children ~ + == == - %- my - :~ :- source.thrd1 - :* newest=d2 - count=0 - notify-count=0 - notify=| - unread=~ - children=(sy (get-children:src pre-sync source.thrd1)) - reads=[d2 ~] - == - :: - :- source.thrd2 + -- + :: in this case we test a parent having mixed reads with children that + :: are all read, resulting in a floor up to the parent reads and one + :: read item that comes later + ++ state-2 + |% + ++ sources + ^- source-set + =/ thrd1 (thread-source-2 flag nest i0) + =/ thrd2 (thread-source-3 flag nest i0) + =. thrd2 thrd2(reads.index [d5 ~]) + =/ =reads:a [d3 (my [d5 ~] ~)] + =/ chnl + %+ merge-children (channel-source flag nest reads i0) + ~[stream.index.thrd1 stream.index.thrd2] + =/ grp + %+ merge-children (group-source flag reads.index.chnl) + ~[stream.index.chnl] + :* thrd1 + thrd2 + chnl + grp + (merge-children (base-source reads.index.grp) ~[stream.index.grp]) + == + ++ pre-sync + ^- indices:a + %- ~(gas by *indices:a) + => sources + .(base [base ~]) + ++ post-sync + ^- indices:a + =+ sources + %- my + :~ thrd1 + thrd2 + chnl(reads.index [d3 ~]) + grp(reads.index [d3 ~]) + base(reads.index [d3 ~]) + == + ++ activity + ^- activity:a + =+ sources + =/ chan-sum=activity-summary:a :* newest=d5 - count=0 + count=1 notify-count=0 notify=| - unread=~ - children=(sy (get-children:src pre-sync source.thrd2)) - reads=[d0 ~] + unread=`[[[~dev d4] d4] 1 |] + children=(sy (get-children:src pre-sync source.chnl)) + reads=[d3 (my [d3 ~] [d5 ~] ~)] == - :: - [source.chnl chan-sum] - :: - :- source.grp - %= chan-sum - unread ~ - children (sy (get-children:src pre-sync source.grp)) + %- my + :~ :- source.thrd1 + :* newest=d2 + count=0 + notify-count=0 + notify=| + unread=~ + children=(sy (get-children:src pre-sync source.thrd1)) + reads=[d2 ~] + == + :: + :- source.thrd2 + :* newest=d5 + count=0 + notify-count=0 + notify=| + unread=~ + children=(sy (get-children:src pre-sync source.thrd2)) + reads=[d0 ~] + == + :: + [source.chnl chan-sum] + :: + :- source.grp + %= chan-sum + unread ~ + children (sy (get-children:src pre-sync source.grp)) + == + :: + :- source.base + %= chan-sum + unread ~ + children ~ + == + == + -- + :: in this case we test multiple parents one with mixed reads and one + :: with all reads + ++ state-3 + |% + ++ sources + =/ thrd1-1 (thread-source-1 flag nest i0) + =/ thrd1-2 (thread-source-2 flag nest i0) + =/ r1=reads:a + :- d-1 + %+ gas:on-read-items:a *read-items:a + :~ [d1 ~] + [d2 ~] + [d3 ~] + [d4 ~] == + =/ chnl1 + %+ merge-children (channel-source flag nest r1 i0) + ~[stream.index.thrd1-1 stream.index.thrd1-2] + =/ grp1 + %+ merge-children (group-source flag reads.index.chnl1) + ~[stream.index.chnl1] :: - :- source.base - %= chan-sum - unread ~ - children ~ - == - == - -- -:: in this case we test multiple parents one with mixed reads and one -:: with all reads -++ state-3 - |% - ++ sources - =/ thrd1-1 (thread-source-1 flag nest i0) - =/ thrd1-2 (thread-source-2 flag nest i0) - =/ r1=reads:a - :- d-1 - %+ gas:on-read-items:a *read-items:a - :~ [d1 ~] - [d2 ~] - [d3 ~] - [d4 ~] - == - =/ chnl1 (channel-source flag nest r1 index.thrd1-1 index.thrd1-2 i0) - =/ grp1 (group-source flag index.chnl1) - :: - =/ second-grp=flag:g [~dev %urbit] - =/ second-chnl=nest:c [%chat ~dev %lobby] - =/ thrd2-1 (thread-source-2 second-grp second-chnl i1) - =/ thrd2-2 (thread-source-3 second-grp second-chnl i1) - =/ r2=reads:a [(add d3 i1) ~] - =/ chnl2 - (channel-source second-grp second-chnl r2 index.thrd2-1 index.thrd2-2 i1) - =/ grp2 (group-source second-grp index.chnl2) - =/ base - %- base-source - :* (uni:on-event:a stream.index.grp1 stream.index.grp2) - :: + =/ second-grp=flag:g [~dev %urbit] + =/ second-chnl=nest:c [%chat ~dev %lobby] + =/ thrd2-1 (thread-source-2 second-grp second-chnl i1) + =/ thrd2-2 (thread-source-3 second-grp second-chnl i1) + =/ r2=reads:a [(add d3 i1) ~] + =/ chnl2 + %+ merge-children (channel-source second-grp second-chnl r2 i1) + ~[stream.index.thrd2-1 stream.index.thrd2-2] + =/ grp2 + %+ merge-children (group-source second-grp reads.index.chnl2) + ~[stream.index.chnl2] + =/ base + %+ merge-children + %- base-source :- d-1 %+ gas:on-read-items:a items.r1 :~ [(add d1 i1) ~] [(add d2 i1) ~] [(add d3 i1) ~] == - :: - *@da - == - :: - :* thrd1-1=thrd1-1 - thrd1-2=thrd1-2 - thrd2-1=thrd2-1 - thrd2-2=thrd2-2 - chnl1=chnl1 - chnl2=chnl2 - grp1=grp1 - grp2=grp2 - base=base - == - ++ pre-sync - ^- indices:a - %- ~(gas by *indices:a) - => sources - .(base [base ~]) - ++ post-sync - ^- indices:a - =+ sources - %- my - :~ thrd1-1 - thrd1-2 - thrd2-1 - thrd2-2 - chnl1(reads.index [d4 ~]) - chnl2 - grp1(reads.index [d-1 ~]) - grp2 - base(reads.index [d-1 ~]) - == - ++ activity - ^- activity:a - =+ sources - =/ chan-sum1=activity-summary:a - :* newest=d5 - count=0 - notify-count=0 - notify=| - unread=~ - children=(sy (get-children:src pre-sync source.chnl1)) - reads=[d-1 (my [d1 ~] [d2 ~] [d3 ~] [d4 ~] ~)] + ~[stream.index.grp1 stream.index.grp2] + :: + :* thrd1-1=thrd1-1 + thrd1-2=thrd1-2 + thrd2-1=thrd2-1 + thrd2-2=thrd2-2 + chnl1=chnl1 + chnl2=chnl2 + grp1=grp1 + grp2=grp2 + base=base == - =/ time-chan2 (add d4 i1) - =/ time2-2 (add d5 i1) - =/ chan-sum2=activity-summary:a - :* newest=time2-2 - count=1 - notify-count=0 - notify=| - unread=`[[[~dev time-chan2] time-chan2] 1 |] - children=(sy (get-children:src pre-sync source.chnl2)) - reads=[(add d3 i1) ~] + ++ pre-sync + ^- indices:a + %- ~(gas by *indices:a) + => sources + .(base [base ~]) + ++ post-sync + ^- indices:a + =+ sources + %- my + :~ thrd1-1 + thrd1-2 + thrd2-1 + thrd2-2 + chnl1(reads.index [d4 ~]) + chnl2 + grp1(reads.index [d-1 ~]) + grp2 + base(reads.index [d-1 ~]) == - %- my - :~ :- source.thrd1-1 + ++ activity + ^- activity:a + =+ sources + =/ chan-sum1=activity-summary:a :* newest=d5 - count=2 - notify-count=0 - notify=| - unread=`[[[~dev d0] d0] 2 |] - children=(sy (get-children:src pre-sync source.thrd1-1)) - reads=[d-1 ~] - == - :: - :- source.thrd1-2 - :* newest=d2 count=0 notify-count=0 notify=| unread=~ - children=(sy (get-children:src pre-sync source.thrd1-2)) - reads=[d2 ~] + children=(sy (get-children:src pre-sync source.chnl1)) + reads=[d-1 (my [d1 ~] [d2 ~] [d3 ~] [d4 ~] ~)] == - :: - :- source.thrd2-1 - :* newest=(add d2 i1) - count=0 - notify-count=0 - notify=| - unread=~ - children=(sy (get-children:src pre-sync source.thrd2-1)) - reads=[(add d2 i1) ~] - == - :: - :- source.thrd2-2 + =/ time-chan2 (add d4 i1) + =/ time2-2 (add d5 i1) + =/ chan-sum2=activity-summary:a :* newest=time2-2 count=1 notify-count=0 notify=| - unread=`[[[~dev time2-2] time2-2] 1 |] - children=(sy (get-children:src pre-sync source.thrd2-2)) - reads=[(add d0 i1) ~] - == - :: - [source.chnl1 chan-sum1] - [source.chnl2 chan-sum2] - :: - :- source.grp1 - %= chan-sum1 - unread ~ - children (sy (get-children:src pre-sync source.grp1)) - == - :: - :- source.grp2 - %= chan-sum2 - unread ~ - children (sy (get-children:src pre-sync source.grp2)) - == - :: - :- source.base - %= chan-sum2 - unread ~ - children ~ + unread=`[[[~dev time-chan2] time-chan2] 1 |] + children=(sy (get-children:src pre-sync source.chnl2)) + reads=[(add d3 i1) ~] == + %- my + :~ :- source.thrd1-1 + :* newest=d5 + count=2 + notify-count=0 + notify=| + unread=`[[[~dev d0] d0] 2 |] + children=(sy (get-children:src pre-sync source.thrd1-1)) + reads=[d-1 ~] + == + :: + :- source.thrd1-2 + :* newest=d2 + count=0 + notify-count=0 + notify=| + unread=~ + children=(sy (get-children:src pre-sync source.thrd1-2)) + reads=[d2 ~] + == + :: + :- source.thrd2-1 + :* newest=(add d2 i1) + count=0 + notify-count=0 + notify=| + unread=~ + children=(sy (get-children:src pre-sync source.thrd2-1)) + reads=[(add d2 i1) ~] + == + :: + :- source.thrd2-2 + :* newest=time2-2 + count=1 + notify-count=0 + notify=| + unread=`[[[~dev time2-2] time2-2] 1 |] + children=(sy (get-children:src pre-sync source.thrd2-2)) + reads=[(add d0 i1) ~] + == + :: + [source.chnl1 chan-sum1] + [source.chnl2 chan-sum2] + :: + :- source.grp1 + %= chan-sum1 + unread ~ + children (sy (get-children:src pre-sync source.grp1)) + == + :: + :- source.grp2 + %= chan-sum2 + unread ~ + children (sy (get-children:src pre-sync source.grp2)) + == + :: + :- source.base + %= chan-sum2 + unread ~ + children ~ + == + == + -- + -- +++ fix-init + |% + +$ source-set + $: thrd1=index-pair + thrd2=index-pair + chnl=index-pair + bad-chnl-migration=index-pair + grp=index-pair + dm-thread=index-pair + read-dm=index-pair + unread-dm=index-pair + dm-invite=index-pair + bad-dm-migration=index-pair + base=index-pair == + :: we want to test multiple scenarios in one go. we have a dm that's + :: completely read, a dm that's unread, and a dm with only an invite. + :: we also have a channel with threads that is completely read. + ++ state-0 + |% + ++ sources + ^- source-set + =/ thrd1 (thread-source-2 flag nest i0) + =/ thrd2 (thread-source-3 flag nest i0) + =. thrd2 thrd2(reads.index [d5 ~]) + =/ =reads:a [d4 ~] + =/ chnl + %+ merge-children (channel-source flag nest reads i0) + ~[stream.index.thrd1 stream.index.thrd2] + =/ bad-chnl=index-pair + :- [%channel [%chat ~syx %hi] flag] + :_ [[d0 ~] d0] + %+ gas:on-event:a *stream:a + ~[[d1 [[%chan-init [%chat ~syx %hi] flag] | |]]] + =/ grp + %+ merge-children (group-source flag reads) + ~[stream.index.chnl stream.index.bad-chnl] + =/ dm-thread (dm-thread-source [[~dev d0] d0] [%ship ~rus]) + =/ read-dm + %+ merge-children (dm-source [%ship ~rus] [d3 ~]) + ~[stream.index.dm-thread] + =/ unread-dm + (dm-source [%ship ~dyl] [d0 ~]) + =/ dm-invite=index-pair + :- [%dm [%club 0v1d.u717i.b92pp.aa9oh.u044i.joqln]] + :_ [[d0 ~] d0] + %+ gas:on-event:a *stream:a + ~[[d1 [[%dm-invite [%club 0v1d.u717i.b92pp.aa9oh.u044i.joqln]] & |]]] + =/ bad-dm-migration=index-pair + :- [%dm [%ship ~syx]] + :_ [[d0 ~] d0] + %+ gas:on-event:a *stream:a + ~[[d1 [[%dm-invite [%ship ~syx]] & |]]] + :* thrd1 + thrd2 + chnl + bad-chnl + grp + dm-thread + read-dm + unread-dm + dm-invite + bad-dm-migration + %+ merge-children (base-source [d0 ~]) + :~ stream.index.grp + stream.index.read-dm + stream.index.unread-dm + stream.index.dm-invite + stream.index.bad-dm-migration + == + == + ++ scries + |= =path + ^- (unit vase) + ?+ path ~ + [%gx @ %chat @ %full *] `!>(chat-scry) + [%gx @ %channels @ %v2 %channels %full *] `!>(channels-scry) + == + ++ channels-scry + ^- channels:c + =+ sources + =| empty=channel:c + %- ~(gas by *channels:c) + :~ :- (get-nest source.chnl) + empty(remark [d5 d4 & ~]) + :- (get-nest source.bad-chnl-migration) + empty(remark [d1 d1 & ~]) + == + ++ chat-scry + ^- [dms clubs] + =+ sources + =| empty-club=club:ch + =| empty-dm=dm:ch + =/ =dms + %- ~(gas by *dms) + :~ :- (get-ship source.read-dm) + empty-dm(remark [d4 d3 & ~]) + :- (get-ship source.unread-dm) + empty-dm(remark [d3 d0 & ~]) + :- (get-ship source.bad-dm-migration) + empty-dm(remark [d1 d1 & ~]) + == + =/ =clubs + %+ ~(put by *clubs) (get-club source.dm-invite) + empty-club(remark [d0 d0 & ~]) + [dms clubs] + ++ get-nest + |= =source:a + ^- nest:c + ?> ?=(%channel -.source) + nest.source + ++ get-ship + |= =source:a + ^- ship + ?> ?=(%dm -.source) + ?> ?=(%ship -.whom.source) + p.whom.source + ++ get-club + |= =source:a + ^- id:club:ch + ?> ?=(%dm -.source) + ?> ?=(%club -.whom.source) + p.whom.source + ++ volumes + %+ roll + ~(tap by indices) + |= [[=source:a =index:a] =volume-settings:a] + %+ ~(put by volume-settings) source + default-volumes:a + ++ indices + %- ~(gas by *indices:a) + => sources + .(base [base ~]) + ++ pre-fix + ^- activity:a + %+ roll + ~(tap by indices) + |= [[=source:a =index:a] =activity:a] + %+ ~(put by activity) source + (~(summarize-unreads urd indices *activity:a volumes fake-log) source index) + ++ post-fix + ^- activity:a + =+ sources + %- ~(uni by pre-fix) + %- ~(gas by *activity:a) + :~ :- source.bad-chnl-migration + [d1 0 0 | ~ ~ ~] + :- source.chnl + [d5 0 0 | ~ (sy source.thrd1 source.thrd2 ~) ~] + :- source.grp + [d5 0 0 | ~ (sy source.chnl source.bad-chnl-migration ~) ~] + :- source.read-dm + [d4 0 0 | ~ (sy source.dm-thread ~) ~] + :- source.bad-dm-migration + [d1 0 0 | ~ ~ ~] + :- source.base + [d5 2 1 & ~ ~ ~] + == + -- -- :: base ++ base-source - |= group-idx=index:a + |= =reads:a ^- index-pair :- [%base ~] - group-idx(stream (child-stream stream.group-idx)) + [*stream:a reads d0] :: the group has no posts of its own only from children, it is "unread" ++ group-source - |= [=flag:g channel-idx=index:a] + |= [=flag:g =reads:a] ^- index-pair :- [%group flag] - channel-idx(stream (child-stream stream.channel-idx)) + [*stream:a reads d0] :: the channel only has two posts, and it is completely read ++ channel-source - |= [=flag:g =nest:c =reads:a thrd1=index:a thrd2=index:a interval=@dr] + |= [=flag:g =nest:c =reads:a interval=@dr] ^- index-pair :- [%channel nest flag] - :* %+ uni:on-event:a (child-stream stream.thrd1) - %+ gas:on-event:a (child-stream stream.thrd2) + :* %+ gas:on-event:a *stream:a =/ key1 (mod [[~dev d3] d3] interval) =/ key2 (mod [[~dev d4] d4] interval) :~ [time.key1 [[%post key1 nest flag *story:c |] | |]] @@ -476,7 +676,16 @@ :: reads :: - *@da + d0 + == +++ dm-source + |= [=whom:ch =reads:a] + ^- index-pair + :- [%dm whom] + :_ [reads d0] + %+ gas:on-event:a *stream:a + :~ [d2 [[%dm-post [[~rus d2] d2] whom *story:c |] | |]] + [d3 [[%dm-post [[~rus d3] d3] whom *story:c |] | |]] == :: :: this thread has two messages and is unread @@ -521,6 +730,15 @@ :: *@da == +++ dm-thread-source + |= [parent=message-key:a =whom:ch] + ^- index-pair + :- [%dm-thread parent whom] + :_ [[d4 ~] d0] + %+ gas:on-event:a *stream:a + :~ [d4 [[%dm-reply [[~rus d4] d4] parent whom *story:c |] | |]] + [d1 [[%dm-reply [[~rus d1] d1] parent whom *story:c |] | |]] + == ++ create-reply-pair |= [key=message-key:a parent=message-key:a interval=@dr] =. key (mod key interval) @@ -528,9 +746,24 @@ ++ mod |= [key=message-key:a interval=@dr] key(time (add time.key interval), q.id (add q.id.key interval)) +++ merge-children + |= [index-pair children=(list stream:a)] + ^- index-pair + :- source + %= index + stream + %+ roll + children + |= [=stream:a acc=_stream.index] + (uni:on-event:a acc (child-stream stream)) + == ++ child-stream |= =stream:a (run:on-event:a stream |=(=event:a event(child &))) +++ fake-log + |= msg=(trap tape) + same + :: (slog leaf+"%activity {(msg)}" ~) ++ i0 *@dr ++ i1 (add i0 ~s1) ++ d-1 (dec *@da)