:: :: :: :::: /hoon/talk/app :: :: :: :: :: :: ::TODO master changes ::TODO char57 comments as line comments when regarding code. ::TODO avoid lark where possible ::TODO document what user-facing messages actually mean! ::TODO ::> to :> etc. :: ::TODO we can't do away with the default mailbox because we need it for things :: like invite notifications etc. can we do better than request that apps :: don't use it frivolously? :: ::TODO ".. the importance of the CQRS pattern (command-query separation) behind :: Urbit's separation of %poke and %peer. Pokes (messages) are one-way :: commands, not queries. Peers (subscriptions) have no effect on the :: server state." :: but we *do* change state on-subscribe! is that a problem? :: ::TODO crash on pokes/peers we do not expect ::TODO keep both a folks and nicks maps. (actual profiles and local nicknames.) :: /? 151 ::< hoon version /- talk ::< structures /+ talk, time-to-id ::< libraries /= seed /~ !>(.) !: :::: :: [. talk] => ::> || ::> || %arch ::> || ::> data structures :: |% ++ state ::> broker state $: stories/(map knot story) ::< convercirions readers/(map bone (set knot)) ::< our message readers outbox/(pair @ud (map @ud thought)) ::< urbit outbox log/(map knot @ud) ::< logged to clay folks/(map ship human) ::< human identities nik/(map (set partner) char) ::< bound circle glyphs nak/(jug char (set partner)) ::< circle glyph lookup == :: ++ story ::> wire content $: count/@ud ::< (lent grams) grams/(list telegram) ::< all messages locals/group ::< local presence remotes/(map partner group) ::< remote presence shape/config ::< configuration mirrors/(map circle config) ::< remote config ::TODO never gets updated. :: sequence/(map partner @ud) ::< partners heard known/(map serial @ud) ::< messages heard followers/(map bone river) ::< subscribers == :: ++ river (pair point point) ::< stream definition ++ point ::> stream endpoint $% {$ud p/@ud} ::< by number {$da p/@da} ::< by date == :: ++ move (pair bone card) ::< all actions ++ lime ::> diff fruit $% {$talk-report report} :: {$talk-lowdown lowdown} :: {$talk-reaction reaction} :: == :: ++ pear ::> poke fruit $% {$talk-command command} :: {$write-comment spur ship cord} :: {$write-fora-post spur ship cord cord} :: == :: ++ card ::> general card $% {$diff lime} :: {$info wire ship term nori} :: {$peer wire dock path} :: {$poke wire dock pear} :: {$pull wire dock $~} :: {$quit $~} :: == :: ++ weir ::> parsed wire $% {$repeat num/@ud hos/ship nom/knot} ::< messaging wire {$friend nom/knot cir/circle} ::< subscription wire == :: -- :: ::> || ::> || %work ::> || ::> functional cores and arms. :: |_ {bol/bowl state} :: ++ prep ::< prepare state ::> adapts state. :: |= old/(unit state) ^- (quip move ..prep) ?~ old ta-done:ta-init:ta [~ ..prep(+<+ u.old)] :: ::> || ::> || %utility ::> || ::> small utility functions. ::+| :: ++ strap |*({k/* v/*} (~(put by *(map _k _v)) k v)) ::< map key-value pair :: ::> || ::> || %engines ::> || ::> main cores. ::+| :: ++ ta ::< per transaction ::> for every transaction/event (poke, peer, etc.) ::> talk-guardian receives, the ++ta transaction core ::> is called. ::> in processing transactions, ++ta may modify app ::> state, or create moves. these moves get produced ::> upon finalizing the core's work with ++ta-done. ::> when making changes to stories, the ++so core is ::> used. :: |_ ::> moves: moves created by core operations. :: moves/(list move) :: ++ ta-done ::< resolve core ::> produces the moves stored in ++ta's moves. ::> they are produced in reverse order because ::> ++ta-emil and ++ta-emit add them to the head of ::> the {moves}. :: ^- (quip move +>) [(flop moves) +>] :: ::> || ::> || %emitters ::> || ::> arms that create outward changes. ::+| :: ++ ta-emil ::< emit move list ::> adds multiple moves to the head of {moves}. ::> flops to stay consistent with ++ta-emit. :: |= mol/(list move) %_(+> moves (welp (flop mol) moves)) :: ++ ta-emit ::< emit a move ::> adds a move to the head of {moves}. :: |= mov/move %_(+> moves [mov moves]) :: ++ ta-inform ::< broadcast lowdown ::> sends a talk-lowdown diff to all readers. :: |= low/lowdown %- ta-emil %- ~(rep by readers) |= {{b/bone *} l/(list move)} [[b %diff %talk-lowdown low] l] :: ++ ta-react ::< send reaction ::> sends a talk-reaction diff to a reader. :: |= {red/bone rac/reaction} %- ta-emit [red %diff %talk-reaction rac] :: ++ ta-note ::< tell user ::> sends {msg} as an %app message to the user's ::> inbox. :: |= msg/cord %^ ta-action 0 %phrase :- [[%& our.bol (main our.bol)] ~ ~] [%app %talk-guardian msg]~ :: ++ ta-evil ::< emit error ::> tracing printf and crash. :: |= msg/cord ~| [%talk-ta-evil msg] !! :: ::> || ::> || %data ::> || ::> utility functions for data retrieval. ::+| :: ++ ta-know ::< story monad ::> produces a gill that takes a gate. ::> if the story {nom} exists, calls the gate with ::> a story core. if it doesn't, does nothing. :: |= nom/knot |= fun/$-(_so _ta) ^+ +>+> =+ pur=(~(get by stories) nom) ?~ pur ::TODO crash instead? %- ta-note (crip "unknown story '{(trip nom)}'") (fun ~(. so nom ~ u.pur)) :: ++ ta-human ::< look up person ::> get {her} identity. if we need to, create one. :: |= her/ship ^- (quid human +>) =^ who folks =+ who=(~(get by folks) her) ?^ who [u.who folks] =+ who=`human`[~ `(scot %p her)] :: XX do right [who (~(put by folks) her who)] [who +>.$] :: ::> || ::> || %interaction-events ::> || ::> arms that apply events we received. ::+| :: ++ ta-init ::< initialize app ::> populate state on first boot. ::> creates our default mailbox and journal. :: %+ roll ^- (list {security knot cord}) :~ [%brown (main our.bol) 'default home'] [%green ~.public 'visible activity'] == |= {{typ/security nom/knot des/cord} _ta} %+ ta-action ost.bol [%create nom des typ] :: ++ ta-apply ::< apply command ::> applies the command sent by {src}. :: |= {src/ship cod/command} ^+ +> ?- -.cod ::> %review commands prompt us (as a circle host) ::> to verify and distribute messages. $review (ta-think | src +.cod) ::> %burden commands ask us to add the sender as a ::> federator for the specified story, taking the ::> state it sent into account. $burden (ta-burden src +.cod) ::> %relief commands prompt us to relieve the ::> specified federators of their duty. $relief (ta-relieve src +.cod) == :: ++ ta-burden ::< accept federator ::> adds {src} as a federator to story {nom}, ::> integrating its state into the story. :: |= {src/ship nom/knot cof/lobby pes/crowd gaz/(list telegram)} %- (ta-know nom) |= sor/_so =< so-done (so-burden:sor src cof pes gaz) :: ++ ta-relieve ::< remove federator ::> removes {who} as federators from story {nom}. :: |= {src/ship nom/knot who/(set ship)} %- (ta-know nom) |= sor/_so =< so-done (so-relieve:sor src who) :: ++ ta-action ::< apply reader action ::> performs action sent by a reader. :: |= {red/bone act/action} ^+ +> =< work ::> || ::> || %actions ::> || ::> action processing core ::> ::> ++work calls the appropriate action processing ::> arm. most use ++affect to retrieve the affected ::> story, reacting if it doesn't exist. |% :: || %utility ::+| ++ work ::< perform action ^+ ..ta-action ?- -.act :: circle configuration $create (action-create +.act) $source (action-source +.act) $depict (action-depict +.act) $permit (action-permit +.act) $delete (action-delete +.act) $enlist (action-enlist +.act) $burden (action-burden +.act) :: messaging $convey (action-convey +.act) $phrase (action-phrase +.act) :: personal metadata $status (action-status +.act) :: changing shared ui $human (action-human +.act) $glyph (action-glyph +.act) == :: ++ affect ::< affect story ::> produces a gill that takes a gate. ::> if the story {nom} exists, calls the gate with ::> a story core and the story itself. ::> if it doesn't, reacts accordingly. :: |= nom/knot |= fec/$-({_so story} _ta) ^+ ta =+ pur=(~(get by stories) nom) ?^ pur (fec ~(. so nom ~ u.pur) u.pur) %+ ta-react red [%fail (crip "no story {(trip nom)}") `act] :: ::> || %circle-configuration ::+| ++ action-create ::< create story ::> creates a story with the specified parameters. :: |= {nom/knot des/cord typ/security} ^+ ..ta-action ?. (~(has in stories) nom) %+ ta-config nom :* [[%& our.bol nom] ~ ~] des [typ ~] [[our.bol ~ ~] [our.bol ~ ~]] == (ta-react red %fail (crip "{(trip nom)}: already exists") `act) :: ++ action-delete ::< delete + announce ::> delete story {nom}, optionally announcing the ::> event with message {mes}. :: |= {nom/knot mes/(unit cord)} ^+ ..ta-action %- (affect nom) |= * =. ..ta-action ::TODO =? ?~ mes ..ta-action %+ action-phrase [[%& our.bol nom] ~ ~] [%lin | u.mes]~ (ta-unconfig nom) :: ++ action-depict ::< change description ::> change description of story {nom} to {des}. :: |= {nom/knot des/cord} ^+ ..ta-action %- (affect nom) |= {sor/_so soy/story} =. cap.shape.soy des (ta-config nom shape.soy) :: ++ action-permit ::< invite/banish ::> invite to/banish from story {nom} all {sis}. :: |= {nom/knot inv/? sis/(set ship)} ^+ ..ta-action %- (affect nom) |= {sor/_so *} =< so-done (so-permit:sor inv sis) :: ++ action-source ::< un/sub p to/from r ::> add/remove {pas} as sources for story {nom}. :: |= {nom/knot sub/? pas/(set partner)} ^+ ..ta-action %- (affect nom) |= {sor/_so soy/story} =. src.shape.soy %. pas ?: sub ~(uni in src.shape.soy) ~(dif in src.shape.soy) (ta-config nom shape.soy) :: ++ action-enlist ::< dis/allow federation ::> adds {sis} to story {nom}'s list of allowed ::> federators. :: |= {nom/knot fed/? sis/(set ship)} ^+ ..ta-action %- (affect nom) |= {sor/_so soy/story} ::TODO maybe so-enlist, with message note and everything =. may.fed.shape.soy %. sis ?: fed ~(uni in may.fed.shape.soy) ~(dif in may.fed.shape.soy) (ta-config nom shape.soy) :: ++ action-burden ::< help federate ::> starts federating the specified circle. create ::> it locally if it doesn't yet exist. :: |= {hos/ship nom/knot} ^+ ..ta-action :: update federation config. =+ soy=(fall (~(get by stories) nom) *story) =. may.fed.shape.soy (~(put in may.fed.shape.soy) hos) =. fes.fed.shape.soy (~(put in fes.fed.shape.soy) hos) =. src.shape.soy (~(put in src.shape.soy) [%& hos nom]) =. ..ta-action (ta-config nom shape.soy) :: send %burden command with story's current state. %- ta-emit :* ost.bol %poke /burden [hos %talk-guardian] :* %talk-command %burden nom [shape.soy mirrors.soy] [locals.soy remotes.soy] grams.soy == == :: ::> || %messaging ::+| ++ action-convey ::< post exact ::> sends the messages provided in the action. :: |= tos/(list thought) ^+ ..ta-action (ta-think & our.bol tos) :: ++ action-phrase ::< post easy ::> sends the message contents provided in the ::> action, constructing the audience, generating a ::> serial and setting a timestamp. :: |= {pas/(set partner) ses/(list speech)} ^+ ..ta-action =- (ta-think & our.bol tos) |- ^- tos/(list thought) ?~ ses ~ =^ sir eny.bol (uniq eny.bol) :_ $(ses t.ses) :+ sir %- ~(gas by *audience) %+ turn (~(tap in pas)) |=(p/partner [p *envelope %pending]) [now.bol ~ i.ses] :: ::> || %personal-metadata ::+| ++ action-status ::< our status update ::> for every story in the set, update our status. :: |= {nos/(set knot) sat/status} ^+ ..ta-action %- ~(rep in nos) |= {k/knot _ta} %- (affect k) |= {sor/_so *} so-done:(so-notify:sor our.bol sat) :: ::> || %changing-shared-ui ::+| ++ action-human ::< new identity ::> assigns a new local identity ("nickname") to the ::> target ship. :: |= {sip/ship nic/human} ^+ ..ta-action ?. =((~(get by folks) sip) `nic) ..ta-action ::< no change =. folks ?~ han.nic (~(del by folks) sip) (~(put by folks) sip nic) %+ ta-inform %names ::TODO think long and hard, do we need unit for delition or is a human :: with [~ ~] good enough? if the latter, agent's $names will change. (strap sip ?~(han.nic ~ `nic)) :: ++ action-glyph ::< bind a glyph ::> un/bind glyph {lif} to partners {pas}. :: |= {lif/char pas/(set partner) bin/?} =. ..ta-action ?: bin %= ..ta-action ::< bind nik (~(put by nik) pas lif) nak (~(put ju nak) lif pas) == =/ ole/(set (set partner)) ::< unbind ?. =(pas ~) [pas ~ ~] (~(get ju nak) lif) |- ^+ ..ta-action ?~ ole ..ta-action =. ..ta-action $(ole l.ole) =. ..ta-action $(ole r.ole) %= ..ta-action nik (~(del by nik) pas) nak (~(del ju nak) lif pas) == (ta-inform %glyph nak) -- :: ++ ta-diff-report ::< subscription update ::> process a talk report from {cir} into story {nom}. :: |= {nom/knot cir/circle ret/report} %- (ta-know nom) |= sor/_so =< so-done (so-diff-report:sor cir ret) :: ::> || ::> || %subscription-events ::> || ::> arms that react to subscription events. ::+| :: ++ ta-subscribe ::< listen to ::> start {her} susbcription on {pax}. ::> for readers, we forward to ++ta-welcome. ::> for foreign brokers, we check if their desired ::> story exists and if they have permission to ::> subscribe to it, before sending them all data. :: |= {her/ship pax/path} ^+ +> :: reader subscription. ?: ?=({$reader *} pax) ?. (team our.bol her) %- ta-note (crip "foreign reader {(scow %p her)}") (ta-welcome ost.bol t.pax) :: weird subscription path. ?. ?=({@ *} pax) (ta-evil %bad-path) =+ pur=(~(get by stories) i.pax) ?~ pur ::TODO send this to the subscriber! make them unsub! %- ta-note (crip "bad subscribe story '{(trip i.pax)}'") =+ soy=~(. so i.pax `(list action)`~ u.pur) :: nest-fail if no cast :: she needs read permissions to subscribe. ?. (so-visible:soy her) (ta-evil %no-story) ::TODO? or (so-sauce ost.bol [%quit ~]~) ? =^ who +>.$ (ta-human her) :: send current data to bring her up to date. =. soy (so-report-lobby:soy ost.bol ~ ~) =. soy (so-report-crowd:soy ost.bol ~ ~) =. soy (so-start:soy her t.pax) ::< also adds story sub =. soy (so-notify:soy her %hear who) ::< add her status so-done:soy ::< apply story changes :: ++ ta-welcome ::< reader subscription ::> brings new reader up to date. susbcribes it to the specified story, ::TODO or shared ui state if no story was specified. ::TODO maybe also send a list of knots again? (basically a %house lowdown) :: |= {new/bone pax/path} =/ sor/knot ?: ?=({@tas *} pax) i.pax (main our.bol) :: default to mailbox ::> new reader? send shared ui state. =. +>.$ ::TODO =? ?: (~(has by readers) new) +>.$ %- ta-emil :~ ::> bound glyphs [new %diff %talk-lowdown %glyph nak] ::> nicknames [new %diff %talk-lowdown %names (~(run by folks) some)] == ::> send story state. =. +>.$ ::TODO =? ?. (~(has by stories) sor) +>.$ =+ soy=(~(got by stories) sor) %- ta-emil :~ ::> configurations :* new %diff %talk-lowdown %confs `shape.soy (~(run by mirrors.soy) some) == ::> presences [new %diff %talk-lowdown %precs locals.soy remotes.soy] ::> messages [new %diff %talk-lowdown %grams 0 grams.soy] == ::> add this subscription to {readers}. %= +>.$ readers %+ ~(put by readers) new (~(put in (fall (~(get by readers) new) ~)) sor) == :: ++ ta-retry ::< subscription resend ::> re-subscribes {cir} to story {nom}. :: |= {nom/knot cir/circle} %- (ta-know nom) |= sor/_so =< so-done (so-acquire:sor [%& cir]~) :: ++ ta-quit ::< subscription failed ::> removes {cir} from story {nom}'s followers. :: |= {nom/knot cir/circle} %- (ta-know nom) |= sor/_so =< so-done (so-quit:sor %& cir) :: ++ ta-cancel ::< unsubscribe ::> drops {src}'s subscription. deduce the right way ::> to do this from the subscription path {pax}. :: |= {src/ship pax/path} ^+ +> :: remove a reader subscription. ?: ?=({$reader *} pax) =/ nom/knot ?: ?=({@tas *} t.pax) i.pax (main our.bol) ::TODO change when ++ta-welcome changes. =/ nes/(set knot) %. nom ~(del in (~(got by readers) ost.bol)) +>.$(readers (~(put by readers) ost.bol nes)) :: weird subscription path. ?. ?=({@ @ *} pax) %- ta-note (crip "ta-cancel weird path {~(ram re >pax<)}") :: remove a regular subscription, set ship status to %gone. %- (ta-know i.pax) |= sor/_so =< so-done (so-notify:so-cancel:sor src %gone *human) :: ::> || %fora ::> fora things. ::TODO move outside of guardian... ::+| :: ++ ta-base-hart ::x produces our ship's host desk's web address as a hart. :: .^(hart %e /(scot %p our.bol)/host/(scot %da now.bol)) :: ++ ta-fora-post ::x sends a fora post. if we don't have a channel for posts yet, create one :: |= {pax/path sup/spur hed/@t txt/@t} ::x tell %hood to submit a fora post. =. ..ta-emit %+ ta-emit ost.bol :* %poke /fora-post [our.bol %hood] [%write-fora-post sup src.bol hed txt] == =+ man=%posts ::x if we have a %posts story, go ahead and consume. ?: (~(has by stories) man) (ta-consume-fora-post man pax hed txt) ::x if we have no %posts story, first create it, then consume. =; new (ta-consume-fora-post:new man pax hed txt) =. ..ta-action %+ ta-action ost.bol [%create man 'towards a community' %brown] ::x send informative message to our mailbox. %^ ta-consume & our.bol :^ (shaf %init eny.bol) ::< serial (my [[%& our.bol (main our.bol)] *envelope %pending] ~) ::< audience ::> statement now.bol [~ %app %tree 'receiving forum posts, ;join %posts for details'] :: ++ ta-consume-fora-post ::x add a message for a fora post to the man story. :: |= {man/knot pax/path hed/@t txt/@t} ^+ +> =. pax (welp pax /posts/(crip "{}~")) %^ ta-consume | src.bol :* (shaf %comt eny.bol) (my [[%& our.bol man] *envelope %pending] ~) now.bol (sy /fora-post eyre+pax ~) :- %mor :~ [%fat text+(lore txt) [%url [ta-base-hart `pax ~] ~]] [%app %tree (crip "forum post: '{(trip hed)}'")] == == :: ++ ta-comment ::x sends a comment. if we don't have a channel for them yet, creates one. :: |= {pax/path sup/spur txt/@t} =. ..ta-emit %+ ta-emit ost.bol :* %poke /comment [our.bol %hood] [%write-comment sup src.bol txt] == =+ man=%comments ?: (~(has by stories) man) (ta-consume-comment man pax sup txt) =; new (ta-consume-comment:new man pax sup txt) =. ..ta-action %+ ta-action ost.bol [%create man 'letters to the editor' %brown] %^ ta-consume & our.bol :^ (shaf %init eny.bol) (my [[%& our.bol (main our.bol)] *envelope %pending] ~) now.bol [~ %app %tree 'receiving comments, ;join %comments for details'] :: ++ ta-consume-comment ::x adds a message for a comment to the man story. :: |= {man/knot pax/path sup/spur txt/@t} ^+ +> =+ nam=?~(sup "" (trip i.sup)) :: file name =+ fra=(crip (time-to-id now.bol)) :: url fragment %^ ta-consume | src.bol :* (shaf %comt eny.bol) (my [[%& our.bol man] *envelope %pending] ~) now.bol (sy /comment eyre+pax ~) :- %mor :~ [%fat text+(lore txt) [%url [ta-base-hart `pax ~] `fra]] [%app %tree (crip "comment on /{nam}")] == == :: ::> || ::> || %messaging ::> || ::> arms for sending and processing messages. ::+| :: ++ ta-think ::< publish or review ::> consumes each thought. :: |= {pub/? aut/ship tos/(list thought)} ^+ +> ?~ tos +> $(tos t.tos, +> (ta-consume pub aut i.tos)) :: ++ ta-sane ::< sanitize ::> sanitize %lin speech, enforce lowercase and no special characters. ::TODO make configurable per-circle. :: |= tot/thought ^- thought ?. ?=({$lin *} sep.sam.tot) tot %_ tot msg.sep.sam %- crip %+ scag 64 %- tufa %+ turn (tuba (trip msg.sep.sam.tot)) |= a/@c ?: &((gte a 'A') (lte a 'Z')) (add a 32) ?: |((lth a 32) (gth a 126)) `@`'?' a == :: ++ ta-consume ::< to each audience ::> conducts thought {tot} to each partner in its audience. :: |= {pub/? aut/ship tot/thought} =. tot (ta-sane tot) =+ aud=(~(tap by aud.tot)) |- ^+ +>.^$ ?~ aud +>.^$ $(aud t.aud, +>.^$ (ta-conduct pub aut p.i.aud tot)) :: ++ ta-conduct ::< thought to partner ::> either publishes or records a thought. :: |= {pub/? aut/ship pan/partner tot/thought} ^+ +> ?- -.pan $& ::< circle partner ?: pub ?. (team our.bol aut) %- ta-note (crip "strange author {(scow %p aut)}") =. aut our.bol ?: =(aut hos.p.pan) (ta-record nom.p.pan hos.p.pan tot) (ta-transmit p.pan tot) ?. =(our.bol hos.p.pan) +> (ta-record nom.p.pan aut tot) :: $| !! ::< passport partner == :: ++ ta-record ::< add to story ::> add or update telegram {gam} in story {nom}. :: |= {nom/knot gam/telegram} %- (ta-know nom) |= sor/_so =< so-done (so-learn:sor gam) :: ++ ta-transmit ::< send message ::> sends thought {tot} to {cir}. ::> stores it to the outbox to await confirmation. :: |= {cir/circle tot/thought} ^+ +> =. +> %+ ta-emit ost.bol :* %poke /repeat/(scot %ud p.outbox)/(scot %p hos.cir)/[nom.cir] [hos.cir %talk-guardian] [%talk-command [%review tot ~]] == +>(p.outbox +(p.outbox), q.outbox (~(put by q.outbox) p.outbox tot)) :: ++ ta-coup-repeat ::< remove from outbox ::> assemble partner and call ++ta-repeat. :: |= {{num/@ud src/ship nom/knot} fal/(unit tang)} (ta-repeat num [%& src nom] fal) :: ++ ta-repeat ::< remove from outbox ::> take message out of outbox, mark it as received ::> or rejected, based on the existence of error ::> message {fal}. :: |= {num/@ud pan/partner fal/(unit tang)} =+ oot=(~(get by q.outbox) num) ?~ oot ~|([%ta-repeat-none num] !!) =. q.outbox (~(del by q.outbox) num) =. aud.u.oot =+ olg=(~(got by aud.u.oot) pan) %+ ~(put by aud.u.oot) pan :- -.olg ?~ fal %received ~> %slog.[0 u.fal] %rejected (ta-think | our.bol u.oot ~) :: ::> || ::> || %stories ::> || ::> arms for modifying stories. ::+| :: ++ ta-config ::< configure story ::> configures story {nom}, creating it if needed. ::> if it's a whitelist config, and we're creating ::> the story, makes sure we're in it. :: |= {nom/knot con/config} ^+ +> ::> neu: is new story ::> pur: the story ::> wyt: will be white =+ :+ neu=!(~(has by stories) nom) pur=(fall (~(get by stories) nom) *story) wyt=?=(?($white $green) sec.con.con) =. ses.con.con ::TODO =? ?: &(neu wyt) [our.bol ~ ~] ses.con.con so-done:(~(so-reform so nom ~ pur) con) :: ++ ta-unconfig ::< delete story ::> calls the story core to delete story {nom}. :: |= nom/knot ^+ +> %- (ta-know nom) |= sor/_so =< so-done so-reform-gone:so :: ++ so ::< story core ::> story core, used for doing work on a story. :: |_ ::> nom: story name in {stories}. ::> acs: talk actions issued due to changes. ::> story is faceless to ease data access. :: $: nom/knot acs/(list action) story == :: ++ so-done ::< apply changes ::> put changed story back into the map and apply ::> actions. :: ^+ +> =. +> +>(stories (~(put by stories) nom +<+>)) =. acs (flop acs) |- ^+ +>+ ?~ acs +>+ =. +>+ (ta-action ost.bol i.acs) $(acs t.acs) :: ::> || ::> || %emitters ::> || ::> arms that create outward changes. ::+| :: ++ so-sauce ::< send backward ::> cards to moves, prepend to {moves} reversed. :: |= {ost/bone cub/(list card)} %_ +>.$ moves (welp (flop (turn cub |=(a/card [ost a]))) moves) == :: ++ so-act ::< send action ::> stores a talk action. :: |= act/action ^+ +> +>(acs [act acs]) :: ++ so-note ::< tell user ::> sends {msg} as an %app message to the user's ::> inbox. :: |= msg/cord ^+ +> %+ so-act %phrase :- [[%& our.bol (main our.bol)] ~ ~] [%app %talk-guardian msg]~ :: ++ so-inform ::< send lowdown ::> sends lowdown to all interested readers. :: |= low/lowdown =. moves %+ weld ^- (list move) %+ murn (~(tap by readers)) |= {b/bone s/(set knot)} ^- (unit move) ?. (~(has in s) nom) ~ `[b %diff %talk-lowdown low] moves +>.$ :: ++ so-report ::< send update ::> sends report to all bones. :: |= {bos/(set bone) ret/report} ^+ +> ?~ bos +> =. +> $(bos l.bos) =. +> $(bos r.bos) (so-sauce n.bos [%diff %talk-report ret]~) :: ++ so-report-crowd ::< presence update ::> send local and remote presences in a report. :: |= bos/(set bone) (so-report bos %crowd locals so-remotes) :: ++ so-report-lobby ::< config update ::> send local and remote configs in a report. :: |= bos/(set bone) (so-report bos %lobby shape mirrors) :: ::> || ::> || %data ::> || ::> utility functions for data retrieval. ::+| :: ++ so-followers ::< follower bones ::> turns the keys of {followers} into a set. :: ^- (set bone) %- ~(gas in *(set bone)) %+ turn (~(tap by followers)) |= {b/bone *} b :: ++ so-unearth ::< ships' bones ::> find the bones in {followers} that belong to ::> a ship in {sis}. :: |= sis/(set ship) ^- (set bone) %- ~(rep in sup.bol) |= {{b/bone s/ship p/path} c/(set bone)} ?. ?& (~(has in sis) s) (~(has by followers) b) ?=({@tas *} p) =(i.p nom) == c (~(put in c) b) :: ++ so-remotes ::< remote presences ::> produces {remotes}, with all our local ::> presences replaced by the versions from their ::> stories. :: %- ~(urn by remotes) :: XX performance |= {pan/partner gop/group} ^- group ?. &(?=($& -.pan) =(our.bol hos.p.pan)) gop =+ soy=(~(get by stories) nom.p.pan) ?~ soy gop locals.u.soy :: ++ so-right ::< is federator? ::> checks whether partner {pan} has authority ::> over this story. :: |= pan/partner ?& ?=($& -.pan) =(nom nom.p.pan) (~(has in fes.fed.shape) hos.p.pan) == :: ::> || ::> || %interaction-events ::> || ::> arms that apply events we received. ::+| :: ++ so-burden ::< accept federator ::> if {src} is allowed to, have it federate this ::> story. ::> starts by assimilating {src}'s state into our ::> own (giving priority to local state), removing ::> redundant data, then sending updated state to ::> all followers. :: |= {src/ship cof/lobby pes/crowd gaz/(list telegram)} ^+ +> :: continue if permitted and not yet done. ?. (~(has in may.fed.shape) src) +> ?: (~(has in fes.fed.shape) src) +> :: assimilate config. =. +> =+ nec=shape :: adopt security list if they're similar. =. ses.con.nec ::TODO =? ?. .= ?=(?($white $green) sec.con.nec) ?=(?($white $green) sec.con.loc.cof) ses.con.nec (~(uni in ses.con.nec) ses.con.loc.cof) =. fes.fed.nec (~(put in fes.fed.nec) src) =. src.nec (~(put in src.nec) [%& src nom]) (so-reform nec) :: assimilate presence and remotes. =. locals (~(uni by loc.pes) locals) =. remotes (~(uni by rem.pes) remotes) =. mirrors (~(uni by rem.cof) mirrors) :: remove redundant remotes. =. remotes %- ~(gas by *_remotes) %+ murn (~(tap by remotes)) |= {p/partner g/group} ^- (unit {partner group}) ?: ?& ?=($& -.p) =(nom.p.p nom) (~(has in fes.fed.shape) hos.p.p) == ~ `[p g] :: remove redundant mirrors. =. mirrors %- ~(gas by *_mirrors) %+ murn (~(tap by mirrors)) |= {c/circle f/config} ^- (unit {circle config}) ?: ?& =(nom.c nom) (~(has in fes.fed.shape) hos.c) == ~ `[c f] :: send updates to followers. =. +>.$ (so-report-crowd so-followers) =. +>.$ (so-report-lobby so-followers) :: finally, learn all grams. (so-lesson gaz) :: ++ so-relieve ::< remove federator ::> if {src} is allowed to, removes {who} as ::> federators from this story. :: |= {src/ship who/(set ship)} ^+ +> ?. (~(has in fes.fed.shape) src) +> =+ nes=shape =. fes.fed.nes (~(dif in fes.fed.nes) who) =. src.nes %- ~(dif in src.nes) ^- (set partner) %- ~(run in who) |=(s/ship [%& s nom]) ?: =(nes shape) +>.$ ::TODO propogate %relief to other federators. (so-reform nes) :: ++ so-diff-report ::< process update ::> process a talk report from {cir}. ::> if we didn't expect it, ignore. :: |= {cir/circle ret/report} ^+ +> ?: =(cir [our.bol nom]) +> ?. (~(has in src.shape) [%& cir]) %- so-note %- crip ;: weld "so-diff unexpected " (scow %p hos.cir) "/" (trip nom.cir) " %" (scow %tas -.ret) " in " (scow %tas nom) == ?- -.ret $lobby (so-lobby cir +.ret) $crowd (so-remind [%& cir] +.ret) $grams (so-lesson gaz.ret) == :: ++ so-lobby ::< update config ::> add circle's config to our remote config map. :: ::TODO when do we care about rem? |= {cir/circle con/config rem/(map circle config)} ^+ +> :: if they're a federator, set new shape. =. +> ::TODO =? ?. ?& =(nom nom.cir) (~(has in fes.fed.shape) hos.cir) == +> :: to prevent removal of federators during initial setup, only allow :: federator deletion through explicit action. =. fes.fed.con (~(uni in fes.fed.con) fes.fed.shape) =. may.fed.con (~(uni in may.fed.con) may.fed.shape) =. src.con ::TODO is this needed? %- ~(uni in src.con) ^- (set partner) %- ~(run in fes.fed.con) |= s/ship ^- partner [%& s nom] (so-reform con) =+ old=mirrors =. mirrors (~(put by mirrors) cir con) ?: =(mirrors old) +>.$ =. +>.$ (so-inform %confs `shape (strap cir `con)) (so-report-lobby so-followers) :: ++ so-remind ::< remote presence ::> adds {tay}'s {loc} to our remote presence map, ::> after merging with {rem}. ::> if this changes anything, send a report. :: |= {pan/partner loc/group rem/(map partner group)} :: if they're a federator, set new locals. =/ buk (~(uni by remotes) rem) =. buk (~(put by buk) pan loc) :: seperate federation presences and true remotes. =/ nep/crowd %+ roll (~(tap by buk)) |= {{p/partner g/group} c/crowd} ?: (so-right p) [(~(uni by loc.c) g) rem.c] [loc.c (~(put by rem.c) p g)] :: ensure we have the latest. =. loc.nep ?. (so-right pan) (~(uni by locals) loc.nep) (~(uni by (~(uni by locals) loc.nep)) loc) :: if nothing changed, we're done. ?: ?& =(loc.nep locals) =(rem.nep remotes) == +>.$ :: reader update. =. +>.$ ::TODO =? %^ so-inform %precs (~(dif in loc.nep) locals) :: locals :: per-partner diff for remotes. %- ~(urn by rem.nep) |= {p/partner a/group} =+ o=(~(get by remotes) p) ?~(o a (~(dif in a) u.o)) :: subscriber update. (so-report-crowd(locals loc.nep, remotes rem.nep) so-followers) :: ::> || ::> || %changes ::> || ::> arms that make miscelaneous changes to this story. ::+| :: ++ so-reform ::< reconfigure ::> changes the config of this story and notify ::> our followers. ::> subscribes to new sources, unsubs from removed ::> ones. :: |= cof/config ?: =(cof shape) +> =. +>.$ (so-inform %confs `cof ~) =/ dif/(pair (list partner) (list partner)) =+ old=`(list partner)`(~(tap in src.shape) ~) =+ new=`(list partner)`(~(tap in src.cof) ~) :- (skip new |=(a/partner (~(has in src.shape) a))) (skip old |=(a/partner (~(has in src.cof) a))) =. +>.$ (so-acquire p.dif) =. +>.$ (so-abjure q.dif) =. shape cof (so-report-lobby so-followers) :: ++ so-reform-gone ::< delete story ::> deletes this story. removes it from {stories} ::> and unsubscribes from all src. :: =. stories (~(del by stories) nom) =. . (so-inform %confs ~ ~) =. . (so-report-lobby so-followers) (so-abjure (~(tap in src.shape))) :: ++ so-notify ::< local presence ::> add {her} status to this story's presence map. ::> if this changes it, send a report. :: |= {her/ship sat/status} ^+ +> =/ nol (~(put by locals) her sat) ?: =(nol locals) +>.$ =. +>.$ (so-inform %precs (strap her sat) ~) (so-report-crowd(locals nol) so-followers) :: ::> || ::> || %subscriptions ::> || ::> arms for starting and ending subscriptions ::+| :: ++ so-acquire ::< subscribe us ::> subscribes this story to each partner. :: |= pas/(list partner) %+ so-sauce 0 :: subscription is caused by this app %- zing %+ turn pas |= pan/partner ^- (list card) ::> subscribe starting at the last message we got, ::> or if we haven't gotten any yet, messages ::> from up to a day ago. =+ num=(~(get by sequence) pan) =+ old=(sub now.bol ~d1) :: XX full backlog =+ ini=?^(num (scot %ud u.num) (scot %da old)) ?- -.pan $| !! ::< passport partner :: $& ::< circle partner :_ ~ :* %peer /friend/show/[nom]/(scot %p hos.p.pan)/[nom.p.pan] [hos.p.pan %talk-guardian] /[nom.p.pan]/[ini] == == :: ++ so-abjure ::< unsubscribe us ::> unsubscribes this story from each partner. :: |= pas/(list partner) %+ so-sauce 0 :: subscription is caused by this app %- zing %+ turn pas |= pan/partner ^- (list card) ?- -.pan $| !! ::< passport partner :: $& ::< circle partner :_ ~ :* %pull /friend/show/[nom]/(scot %p hos.p.pan)/[nom.p.pan] [hos.p.pan %talk-guardian] ~ == == :: ++ so-quit ::< subscription failed ::> delete {pan} from our subscriptions, then send ::> an updated capal report. :: |= pan/partner ^+ +> ?. (~(has in src.shape) pan) +> =. src.shape (~(del in src.shape) pan) =. +> (so-inform %confs `shape ~) (so-report-lobby so-followers) :: ++ so-start ::< subscribe follower ::> called upon subscribe. deduces the range of ::> {her} subscription from {pax}, then sends ::> the currently available part of it. :: |= {her/ship pax/path} ^+ +> :: read permissions ?. (so-visible her) =. +> (so-sauce ost.bol [%quit ~]~) %- so-note %- crip "so-start permission denied {(scow %p her)}" :: find grams range =/ ruv/(unit river) :: collapse unit list %+ biff %- zl:jo %+ turn pax ;~(biff slay |=(a/coin `(unit dime)`?~(-.a a ~))) |= paf/(list dime) ?~ paf $(paf [%ud (sub (max 64 count) 64)]~) ?~ t.paf $(t.paf [%da (dec (bex 128))]~) ?. ?=({{?($ud $da) @} {?($ud $da) @} $~} paf) ~ `[[?+(- . $ud .)]:i.paf [?+(- . $ud .)]:i.t.paf] ::XX arvo issue #366 ?~ ruv =. +>.$ (so-sauce ost.bol [%quit ~]~) %- so-note %- crip "so-start malformed path {~(ram re >pax<)}" (so-first-grams u.ruv) :: ++ so-first-grams ::< beginning of stream ::> find all grams that fall within the river and ::> send them in a grams report to {ost.bol}. :: |= riv/river ^+ +> =; lab/{dun/? end/@u zeg/(list telegram)} =. +>.$ (so-sauce ost.bol [[%diff %talk-report %grams end.lab zeg.lab] ~]) ?: dun.lab (so-sauce ost.bol [[%quit ~] ~]) +>.$(followers (~(put by followers) ost.bol riv)) =+ [end=count gaz=grams dun=| zeg=*(list telegram)] |- ^- (trel ? @ud (list telegram)) ?~ gaz [dun end zeg] ?: ?- -.q.riv :: after the end $ud (lte p.q.riv end) $da (lte p.q.riv wen.sam.tot.i.gaz) == :: if past the river, continue back, mark as done. $(end (dec end), gaz t.gaz, dun &) ?: ?- -.p.riv :: before the start $ud (lth end p.p.riv) $da (lth wen.sam.tot.i.gaz p.p.riv) == :: if before the river, we're done searching. [dun end zeg] :: if in the river, add this gram and continue. $(end (dec end), gaz t.gaz, zeg [i.gaz zeg]) :: ++ so-cancel ::< unsubscribe follower ::> removes {ost.bol} from our followers. :: .(followers (~(del by followers) ost.bol)) :: ++ so-eject ::< unsubscribe ships ::> removes ships {sis} from {followers}. :: |= sis/(set ship) %= +> followers %- ~(rep in (so-unearth sis)) |= {b/bone f/_followers} =. f ?~(f followers f) ::TODO =? (~(del by f) b) == :: ::> || ::> || %messaging ::> || ::> arms for adding to this story's messages. ::+| :: ++ so-refresh ::< update to listeners ::> called when messages get added or changed. ::> calculates the changes and sends them to all ::> followers. ::> any followers that are no longer interested ::> get removed. :: |= {num/@ud gam/telegram} ^+ +> ::> notify the interested readers. =. +> (so-inform %grams num gam ~) ::> notify only the followers who are currently interested. =/ moy ^- (pair (list bone) (list move)) %- ~(rep in followers) |= {f/(pair bone river) r/(pair (list bone) (list move))} :: after the end: unsubscribe ?: ?- -.q.q.f $ud (lte p.q.q.f num) $da (lte p.q.q.f wen.sam.tot.gam) == [[p.f p.r] [[p.f %quit ~] q.r]] :: before the start: ignore ?: ?- -.p.q.f $ud (gth p.p.q.f num) $da (gth p.p.q.f wen.sam.tot.gam) == r :: in the river: send gram :- p.r [[p.f %diff %talk-report %grams num gam ~] q.r] =. moves (welp q.moy moves) |- ^+ +>.^$ ?~ p.moy +>.^$ $(p.moy t.p.moy, followers (~(del by followers) i.p.moy)) :: ++ so-lesson ::< learn messages ::> learn all telegrams in a list. :: |= gaz/(list telegram) ^+ +> ?~ gaz +> $(gaz t.gaz, +> (so-learn i.gaz)) :: ++ so-learn ::< save/update message ::> store an incoming telegram, updating if it ::> already exists. :: |= gam/telegram ^+ +> ?. (so-admire aut.gam) ::< write permissions +>.$ =. aud.tot.gam ::> if we are in the audience, mark as received. =+ ole=(~(get by aud.tot.gam) [%& our.bol nom]) ?^ ole (~(put by aud.tot.gam) [%& our.bol nom] -.u.ole %received) ::> federated circles need to pretend ~src/nom ::> is also ~our/nom. ::TODO pass src through explicitly instead of :: relying on src.bol. =+ ole=(~(get by aud.tot.gam) [%& src.bol nom]) ?~ ole aud.tot.gam ::> as described above, fake src into our. =. aud.tot.gam (~(del by aud.tot.gam) [%& src.bol nom]) (~(put by aud.tot.gam) [%& our.bol nom] -.u.ole %received) =+ old=(~(get by known) uid.tot.gam) ?~ old (so-append gam) ::< add (so-revise u.old gam) ::< modify :: ++ so-append ::< append message ::> add gram to our story, send report to subs. :: |= gam/telegram ^+ +> %+ %= so-refresh grams [gam grams] count +(count) known (~(put by known) uid.tot.gam count) == count gam :: ++ so-revise ::< revise message ::> modify gram in our story, send report to subs. :: |= {num/@ud gam/telegram} ::> dex: index in grams list to insert message at. =+ dex=(sub count num) ?: =(gam (snag (dec dex) grams)) +>.$ ::< no change =. grams %+ welp (scag (dec dex) grams) [gam (slag dex grams)] (so-refresh num gam) :: ::> || ::> || %permissions ::> || ::> arms relating to story permissions. ::+| :: ++ so-permit ::< invite/banish ::> update config to dis/allow ships permission. :: |= {inv/? sis/(set ship)} ^+ +> ::> wyt: whitelist? ::> add: add to list? =/ wyt/? ?=(?($white $green) sec.con.shape) =/ add/? =(inv wyt) =. +>.$ ::TODO =? ?: inv +>.$ (so-eject sis) =. +>.$ %- so-act :- %phrase %- ~(rep in sis) |= {s/ship a/(set partner) t/(list speech)} :- (~(put in a) [%& s (main s)]) [[%inv inv [our.bol nom]] t] %- so-reform %= shape ses.con %. sis ?: add ~(uni in ses.con.shape) ~(dif in ses.con.shape) == :: ++ so-admire ::< accept from ::> checks {her} write permissions. :: |= her/ship ^- ? ?- sec.con.shape $black !(~(has in ses.con.shape) her) ::< channel, blacklist $white (~(has in ses.con.shape) her) ::< village, whitelist $green (~(has in ses.con.shape) her) ::< journal, whitelist $brown !(~(has in ses.con.shape) her) ::< mailbox, blacklist == :: ++ so-visible ::< display to ::> checks {her} read permissions. :: |= her/ship ^- ? ?- sec.con.shape $black !(~(has in ses.con.shape) her) ::< channel, blacklist $white (~(has in ses.con.shape) her) ::< village, whitelist $green & ::< journal, all $brown (team our.bol her) ::< mailbox, our team == -- -- :: ::> || ::> || %wire-parsing ::> || ::+| :: ++ etch ::< parse wire ::> parses {wir}} to obtain either %friend with story ::> and circle or %repeat with message number, ::> source ship and story. :: |= wir/wire ^- weir ?+ -.wir !! $friend ?> ?=({$show @ @ @ $~} t.wir) :^ %friend i.t.t.wir (slav %p i.t.t.t.wir) i.t.t.t.t.wir :: $repeat ?> ?=({@ @ @ $~} t.wir) :^ %repeat (slav %ud i.t.wir) (slav %p i.t.t.wir) i.t.t.t.wir == :: ++ etch-friend ::< parse /friend wire ::> parses a /friend wire, call a gate with the result. :: |= $: wir/wire $= fun $- {nom/knot cir/circle} {(list move) _.} == =+ wer=(etch wir) ?>(?=($friend -.wer) (fun nom.wer cir.wer)) :: ++ etch-repeat ::< parse /repeat wire ::> parses a /repeat wire, call gate with the result. :: |= $: wir/wire $= fun $- {num/@ud src/ship nom/knot} {(list move) _.} == =+ wer=(etch wir) ?>(?=($repeat -.wer) (fun num.wer hos.wer nom.wer)) :: ::> || ::> || %poke-events ::> || ::+| :: ++ poke-talk-command ::< accept command ::> incoming talk command. process it and update logs. :: |= cod/command ^- (quip move +>) =^ mos +>.$ ta-done:(ta-apply:ta src.bol cod) =^ mow +>.$ log-all-to-file [(welp mos mow) +>.$] :: ++ poke-talk-action ::< accept action ::> incoming talk action. process it. :: |= act/action ^- (quip move +>) ?. (team src.bol our.bol) =< ta-done %- ta-note:ta %- crip "talk-action stranger {(scow %p src.bol)}" ta-done:(ta-action:ta ost.bol act) :: ++ poke-talk-comment ::< do comment ::> sends a comment. :: |= {pax/path sup/spur txt/@t} ^- (quip move +>) ta-done:(ta-comment:ta pax sup txt) :: ++ poke-talk-fora-post ::< do fora post ::> sends a fora post. :: |= {pax/path sup/spur hed/@t txt/@t} ^- (quip move +>) ta-done:(ta-fora-post:ta pax sup hed txt) :: ::> || ::> || %subscription-events ::> || ::+| :: ++ diff-talk-report ::< accept report ::> incoming talk-report. process it and update logs. :: |= {wir/wire ret/report} ^- (quip move +>) =^ mos +>.$ %+ etch-friend wir |= {nom/knot cir/circle} ta-done:(ta-diff-report:ta nom cir ret) =^ mow +>.$ log-all-to-file [(welp mos mow) +>.$] :: ++ peer ::< accept subscription ::> incoming subscription on {pax}. :: |= pax/path ^- (quip move +>) ?: ?=({$sole *} pax) ~&(%talk-broker-no-sole !!) ta-done:(ta-subscribe:ta src.bol pax) :: ++ pull ::< unsubscribe ::> unsubscribes. :: |= pax/path ^- (quip move +>) ta-done:(ta-cancel:ta src.bol pax) :: ++ reap-friend ::< subscription n/ack ::> if subscribing failed, update state to reflect ::> that. :: ::TODO this should deal with /reader subscriptions too. |= {wir/wire fal/(unit tang)} ^- (quip move +>) ?~ fal [~ +>] %+ etch-friend [%friend wir] |= {nom/knot cir/circle} =. u.fal [>%reap-friend-fail nom cir< u.fal] %- (slog (flop u.fal)) ta-done:(ta-quit:ta nom cir) :: ++ quit-friend ::< dropped subscription ::> gall dropped our subscription. resubscribe. :: |= wir/wire ^- (quip move +>) %+ etch-friend [%friend wir] |= {nom/knot cir/circle} ta-done:(ta-retry:ta nom cir) :: ++ coup-repeat ::< message n/ack ::> ack from ++ta-transmit. mark the message as ::> received or rejected. :: |= {wir/wire fal/(unit tang)} ^- (quip move +>) %+ etch-repeat [%repeat wir] |= {num/@ud src/ship nom/knot} ta-done:(ta-coup-repeat:ta [num src nom] fal) :: ::> || ::> || %logging ::> || ::+| :: ++ poke-talk-save ::< save as log ::> stores the telegrams of story {nom} in a log file, ::> to be re-loaded by ++poke-talk-load. :: |= nom/knot ^- (quip move +>) =/ paf/path /(scot %p our.bol)/home/(scot %da now.bol)/talk/[nom]/talk-telegrams =+ grams:(~(got by stories) nom) :_ +>.$ :_ ~ :* ost.bol %info /jamfile our.bol (foal paf [%talk-telegrams !>(-)]) == :: ++ poke-talk-load ::< load from log ::> loads the telegrams of story {nom} into our state, ::> as saved in ++poke-talk-save. :: |= nom/knot ^- (quip move +>) =/ grams .^ (list telegram) %cx /(scot %p our.bol)/home/(scot %da now.bol)/talk/[nom]/talk-telegrams == =+ soy=(~(got by stories) nom) :- ~ %= +>.$ stories %+ ~(put by stories) nom soy(grams grams, count (lent grams)) == :: ++ poke-talk-log ::< start logging ::> starts logging story {nom}'s messages. :: |= nom/knot ~& %talk-poke-log ^- (quip move +>) :- [(log-to-file nom) ~] %= +>.$ log %+ ~(put by log) nom count:(~(got by stories) nom) == :: ++ poke-talk-unlog ::< stop logging ::> stops logging story {nom}'s messages. :: |= nom/knot ^- (quip move +>) :- ~ +>.$(log (~(del by log) nom)) :: ++ log-all-to-file ::< update stories logs ::> for every story we're logging, (over)writes all ::> their grams to log files if new ones have arrived. :: ::TODO re-enable and test. ^- (quip move .) ?: & [~ .] :: XXX!!!! :_ %_ . log %- ~(urn by log) |= {nom/knot len/@ud} count:(~(got by stories) nom) == %+ murn (~(tap by log)) |= {nom/knot len/@ud} ^- (unit move) ?: (gte len count:(~(got by stories) nom)) ~ `(log-to-file nom) :: ++ log-to-file ::< update story log ::> logs all grams of story {nom} to a file. :: |= nom/knot ^- move =+ ^- paf/path =+ day=(year %*(. (yore now.bol) +.t +:*tarp)) %+ tope [our.bol %home da+now.bol] /talk-telegrams/(scot %da day)/[nom]/talk =+ grams:(~(got by stories) nom) [ost.bol %info /jamfile our.bol (foal paf [%talk-telegrams !>(-)])] --