Merge branch 'release/next-js' into la/release-2021-03-04

This commit is contained in:
Logan Allen 2021-03-08 14:11:54 -06:00
commit b2759b9f36
97 changed files with 1865 additions and 953 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9812a52d34be0d6d47ca60b23d3386e7db296ff61fac7c4b1f33a35806f8cb7c oid sha256:ec80a42446a1d80974f32bf87283435547441cb7ea3fcd340711df2ce6cbec81
size 9751012 size 9146390

View File

@ -593,10 +593,10 @@
%& (ship p.lane) %& (ship p.lane)
:: ::
%| %|
?~ l=((soft ,[=@tas =@if =@ud]) (cue p.lane)) %- tape
s+(scot %x p.lane) =/ ip=@if (end [0 32] p.lane)
=, u.l =/ pt=@ud (cut 0 [32 16] p.lane)
(tape "%{(trip tas)}, {(scow %if if)}, {(scow %ud ud)}") "{(scow %if ip)}:{((d-co:co 1) pt)} ({(scow %ux p.lane)})"
== ==
== ==
:: ::

View File

@ -5,7 +5,7 @@
easy-print=language-server-easy-print, easy-print=language-server-easy-print,
rune-snippet=language-server-rune-snippet, rune-snippet=language-server-rune-snippet,
build=language-server-build, build=language-server-build,
default-agent default-agent, verb
|% |%
+$ card card:agent:gall +$ card card:agent:gall
+$ lsp-req +$ lsp-req
@ -44,6 +44,7 @@
== ==
-- --
^- agent:gall ^- agent:gall
%+ verb |
=| state-zero =| state-zero
=* state - =* state -
=< =<
@ -69,7 +70,7 @@
|= old-state=vase |= old-state=vase
^- (quip card _this) ^- (quip card _this)
~& > %lsp-upgrade ~& > %lsp-upgrade
[~ this(state *state-zero)] [~ this(state !<(state-zero old-state))]
:: ::
++ on-poke ++ on-poke
^+ on-poke:*agent:gall ^+ on-poke:*agent:gall
@ -275,12 +276,14 @@
++ handle-did-open ++ handle-did-open
|= item=text-document-item:lsp-sur |= item=text-document-item:lsp-sur
^- (quip card _state) ^- (quip card _state)
=/ =path
(uri-to-path:build uri.item)
?: ?=(%sys -.path)
`state
=/ buf=wall =/ buf=wall
(to-wall (trip text.item)) (to-wall (trip text.item))
=. bufs =. bufs
(~(put by bufs) uri.item buf) (~(put by bufs) uri.item buf)
=/ =path
(uri-to-path:build uri.item)
:_ state :_ state
%+ weld %+ weld
(give-rpc-notification (get-diagnostics uri.item)) (give-rpc-notification (get-diagnostics uri.item))
@ -318,12 +321,12 @@
?~ p.tab-list ~ ?~ p.tab-list ~
?~ u.p.tab-list ~ ?~ u.p.tab-list ~
:- ~ :- ~
%- crip =- (crip :(weld "```hoon\0a" tape "\0a```"))
;: weld ^- =tape
"`" %- zing
~(ram re ~(duck easy-print detail.i.u.p.tab-list)) %+ join "\0a"
"`" %+ scag 40
== (~(win re ~(duck easy-print detail.i.u.p.tab-list)) 0 140)
:: ::
++ sync-buf ++ sync-buf
|= [buf=wall changes=(list change:lsp-sur)] |= [buf=wall changes=(list change:lsp-sur)]

View File

@ -21,7 +21,7 @@
|^ :- %kiln-merge |^ :- %kiln-merge
^- $@(~ [syd=desk her=ship sud=desk cas=case gem=?(germ %auto)]) ^- $@(~ [syd=desk her=ship sud=desk cas=case gem=?(germ %auto)])
?- arg ?- arg
~ ((slog (turn help-text |=(=@t leaf+(trip t)))) ~) ~ ((slog (turn `wain`help-text |=(=@t leaf+(trip t)))) ~)
[@ @ ~] [@ @ ~]
=+(arg [sud ?.(=(our her) her (sein:title p.bek now her)) sud (opt-case da+now) gem]) =+(arg [sud ?.(=(our her) her (sein:title p.bek now her)) sud (opt-case da+now) gem])
:: ::

View File

@ -61,14 +61,24 @@
::NOTE we only count graphs for now ::NOTE we only count graphs for now
?. &(=(%graph app-name.m) =(our creator.metadatum)) ~ ?. &(=(%graph app-name.m) =(our creator.metadatum)) ~
`[module.metadatum resource.m] `[module.metadatum resource.m]
:: for sanity checks
::
=/ real=(set resource:re)
=/ upd=update:ga
%+ scry update:ga
[%x %graph-store /keys/graph-update]
?> ?=(%keys -.q.upd)
resources.q.upd
:: count activity per channel :: count activity per channel
:: ::
=/ activity=(list [resource:re members=@ud (list [resource:re mod=term week=@ud authors=@ud])]) =/ activity=(list [resource:re members=@ud (list [resource:re mod=term week=@ud authors=@ud])])
%+ turn crowds %+ turn crowds
|= [g=resource:re m=@ud] |= [g=resource:re m=@ud]
:+ g m :+ g m
%+ turn (~(got by channels) g) %+ murn (~(got by channels) g)
|= [m=term r=resource:re] |= [m=term r=resource:re]
?. (~(has in real) r) ~
%- some
:+ r m :+ r m
::NOTE graph-store doesn't use the full resource-style path here! ::NOTE graph-store doesn't use the full resource-style path here!
=/ upd=update:ga =/ upd=update:ga

15
pkg/arvo/lib/gcp.hoon Normal file
View File

@ -0,0 +1,15 @@
/- *gcp
|%
++ token-to-json
|= =token
^- json
=, enjs:format
%+ frond %gcp-token
%: pairs
[%'accessKey' s+access-key.token]
:- %'expiresIn'
%- numb
(div (mul 1.000 expires-in.token) ~s1)
~
==
--

View File

@ -185,6 +185,7 @@
[%zpmc *] (both p.gen q.gen) [%zpmc *] (both p.gen q.gen)
[%zpts *] loop(gen p.gen) [%zpts *] loop(gen p.gen)
[%zppt *] (both q.gen r.gen) [%zppt *] (both q.gen r.gen)
[%zpgl *] (spec-and-hoon p.gen q.gen)
[%zpzp *] ~ [%zpzp *] ~
* *
=+ doz=~(open ap gen) =+ doz=~(open ap gen)
@ -245,15 +246,25 @@
^- (unit [term type]) ^- (unit [term type])
~ ~
:: ::
++ get-id ++ get-id-sym
|= [pos=@ud txt=tape] |= [pos=@ud =tape]
^- [forward=(unit @t) backward=(unit @t) id=(unit @t)] %^ get-id pos tape
=/ seek ^- $-(nail (like (unit @t)))
;~(sfix (punt sym) (star ;~(pose prn (just `@`10))))
::
++ get-id-cord
|= [pos=@ud =tape]
%^ get-id pos tape
^- $-(nail (like (unit @t)))
;~(sfix (punt (cook crip (star prn))) (star ;~(pose prn (just `@`10)))) ;~(sfix (punt (cook crip (star prn))) (star ;~(pose prn (just `@`10))))
::
++ get-id
|= [pos=@ud txt=tape seek=$-(nail (like (unit @t)))]
^- [forward=(unit @t) backward=(unit @t) id=(unit @t)]
=/ forward=(unit @t) =/ forward=(unit @t)
(scan (slag pos txt) seek) (scan (slag pos txt) seek)
=/ backward=(unit @t) =/ backward=(unit @t)
%- (lift |=(t=@tas (swp 3 t))) %- (lift |=(t=@t (swp 3 t)))
(scan (flop (scag pos txt)) seek) (scan (flop (scag pos txt)) seek)
=/ id=(unit @t) =/ id=(unit @t)
?~ forward ?~ forward
@ -272,7 +283,7 @@
^- [back-pos=@ud fore-pos=@ud txt=tape] ^- [back-pos=@ud fore-pos=@ud txt=tape]
:: Find beg-pos by searching backward to where the current term :: Find beg-pos by searching backward to where the current term
:: begins :: begins
=+ (get-id pos txt) =+ (get-id-sym pos txt)
=/ back-pos =/ back-pos
?~ backward ?~ backward
pos pos
@ -343,7 +354,7 @@
[%| p.res] [%| p.res]
:- %& :- %&
~? > debug %parsed-good ~? > debug %parsed-good
((cury tab-list-hoon sut) hoon.p.res) ((cury tab-list-hoon sut) hoon:`pile:clay`p.res)
:: ::
:: Generators :: Generators
++ tab-generators ++ tab-generators

View File

@ -5,75 +5,59 @@
++ pile-rule ++ pile-rule
|= pax=path |= pax=path
%- full %- full
%+ ifix [gay gay] %+ ifix
%+ cook |=(pile +<) :_ gay
;~ pfix
:: parse optional /? and ignore :: parse optional /? and ignore
:: ::
;~ pose ;~(plug gay (punt ;~(plug fas wut gap dem gap)))
(cold ~ ;~(plug fas wut gap dem gap)) |^
(easy ~)
==
::
;~ plug ;~ plug
;~ pose %+ cook (bake zing (list (list taut)))
;~ sfix %+ rune hep
%+ cook |=((list (list taut)) (zing +<))
%+ more gap
;~ pfix ;~(plug fas hep gap)
(most ;~(plug com gaw) taut-rule) (most ;~(plug com gaw) taut-rule)
==
gap
==
(easy ~)
==
:: ::
;~ pose %+ cook (bake zing (list (list taut)))
;~ sfix %+ rune lus
%+ cook |=((list (list taut)) (zing +<))
%+ more gap
;~ pfix ;~(plug fas lus gap)
(most ;~(plug com gaw) taut-rule) (most ;~(plug com gaw) taut-rule)
==
gap
==
(easy ~)
==
:: ::
;~ pose %+ rune tis
;~ sfix ;~(plug sym ;~(pfix gap fas (more fas urs:ab)))
%+ cook |=((list [face=term =path]) +<)
%+ more gap
;~ pfix ;~(plug fas tis gap)
%+ cook |=([term path] +<)
;~(plug sym ;~(pfix ;~(plug gap fas) (more fas urs:ab)))
==
gap
==
(easy ~)
==
:: ::
;~ pose %+ rune cen
;~ sfix ;~(plug sym ;~(pfix gap ;~(pfix cen sym)))
%+ cook |=((list [face=term =mark =path]) +<) ::
%+ more gap %+ rune buc
;~ pfix ;~(plug fas tar gap) ;~ (glue gap)
%+ cook |=([term mark path] +<)
;~ plug
sym sym
;~(pfix ;~(plug gap cen) sym) ;~(pfix cen sym)
;~(pfix ;~(plug gap fas) (more fas urs:ab)) ;~(pfix cen sym)
==
==
gap
==
(easy ~)
== ==
:: ::
%+ cook |=(huz=(list hoon) `hoon`tssg+huz) %+ rune tar
;~ (glue gap)
sym
;~(pfix cen sym)
;~(pfix fas (more fas urs:ab))
==
::
%+ stag %tssg
(most gap tall:(vang & pax)) (most gap tall:(vang & pax))
== ==
== ::
++ pant
|* fel=^rule
;~(pose fel (easy ~))
::
++ mast
|* [bus=^rule fel=^rule]
;~(sfix (more bus fel) bus)
::
++ rune
|* [bus=^rule fel=^rule]
%- pant
%+ mast gap
;~(pfix fas bus gap fel)
--
:: ::
++ taut-rule ++ taut-rule
%+ cook |=(taut +<) %+ cook |=(taut +<)

View File

@ -291,7 +291,7 @@
++ tab ++ tab
|= pos=@ud |= pos=@ud
^- (quip card _cli-state) ^- (quip card _cli-state)
=+ (get-id:auto pos (tufa buf.cli-state)) =+ (get-id-cord:auto pos (tufa buf.cli-state))
=/ needle=term =/ needle=term
(fall id %$) (fall id %$)
:: autocomplete empty command iff user at start of command :: autocomplete empty command iff user at start of command

View File

@ -468,9 +468,9 @@
(strand-fail %build-mark >arg< ~) (strand-fail %build-mark >arg< ~)
?> =(%dais p.r.u.riot) ?> =(%dais p.r.u.riot)
(pure:m !<(dais:clay q.r.u.riot)) (pure:m !<(dais:clay q.r.u.riot))
:: +build-cast: build a mark conversion gate ($tube) :: +build-tube: build a mark conversion gate ($tube)
:: ::
++ build-cast ++ build-tube
|= [[=ship =desk =case] =mars:clay] |= [[=ship =desk =case] =mars:clay]
=* arg +< =* arg +<
=/ m (strand ,tube:clay) =/ m (strand ,tube:clay)
@ -478,10 +478,37 @@
;< =riot:clay bind:m ;< =riot:clay bind:m
(warp ship desk ~ %sing %c case /[a.mars]/[b.mars]) (warp ship desk ~ %sing %c case /[a.mars]/[b.mars])
?~ riot ?~ riot
(strand-fail %build-cast >arg< ~) (strand-fail %build-tube >arg< ~)
?> =(%tube p.r.u.riot) ?> =(%tube p.r.u.riot)
(pure:m !<(tube:clay q.r.u.riot)) (pure:m !<(tube:clay q.r.u.riot))
:: ::
:: +build-nave: build a mark definition to a $nave
::
++ build-nave
|= [[=ship =desk =case] mak=mark]
=* arg +<
=/ m (strand ,vase)
^- form:m
;< =riot:clay bind:m
(warp ship desk ~ %sing %b case /[mak])
?~ riot
(strand-fail %build-nave >arg< ~)
?> =(%nave p.r.u.riot)
(pure:m q.r.u.riot)
:: +build-cast: build a mark conversion gate (static)
::
++ build-cast
|= [[=ship =desk =case] =mars:clay]
=* arg +<
=/ m (strand ,vase)
^- form:m
;< =riot:clay bind:m
(warp ship desk ~ %sing %f case /[a.mars]/[b.mars])
?~ riot
(strand-fail %build-cast >arg< ~)
?> =(%cast p.r.u.riot)
(pure:m q.r.u.riot)
::
:: Read from Clay :: Read from Clay
:: ::
++ warp ++ warp

View File

@ -11,6 +11,6 @@
-- --
++ grab :: convert from ++ grab :: convert from
|% |%
++ noun [path @] :: clam from %noun +$ noun [path @] :: clam from %noun
-- --
-- --

View File

@ -6,7 +6,7 @@
=, html =, html
|_ own=manx |_ own=manx
:: ::
++ grad %mime ++ grad %noun
++ grow :: convert to ++ grow :: convert to
|% |%
++ hymn ;html:(head body:"+{own}") :: convert to %hymn ++ hymn ;html:(head body:"+{own}") :: convert to %hymn

View File

@ -0,0 +1,13 @@
/+ *gcp
|_ tok=token
++ grad %noun
++ grow
|%
++ noun tok
++ json (token-to-json tok)
--
++ grab
|%
++ noun token
--
--

View File

@ -6,7 +6,7 @@
=, format =, format
|_ txt=cord |_ txt=cord
:: ::
++ grad %mime ++ grad %noun
++ grab :: convert from ++ grab :: convert from
|% |%
++ noun @t :: clam from %noun ++ noun @t :: clam from %noun

View File

@ -6,7 +6,6 @@
:::: compute :::: compute
:: ::
=, html =, html
^|
|_ htm=@t |_ htm=@t
++ grow :: convert to ++ grow :: convert to
^? ^?

View File

@ -6,7 +6,7 @@
=, html =, html
|_ own=manx |_ own=manx
:: ::
++ grad %mime ++ grad %noun
++ grow :: convert to ++ grow :: convert to
|% |%
++ html (crip (en-xml own)) :: convert to %html ++ html (crip (en-xml own)) :: convert to %html

View File

@ -4,7 +4,7 @@
++ grad %noun ++ grad %noun
++ grab ++ grab
|% |%
++ noun not ++ noun all:notification
++ json ++ json
|= jon=^json |= jon=^json
(notification:dejs:lsp-json jon) (notification:dejs:lsp-json jon)

View File

@ -8,7 +8,7 @@
-- --
++ grab ++ grab
|% |%
++ noun req ++ noun all:request
++ json ++ json
|= jon=^json |= jon=^json
(request:dejs:lsp-json jon) (request:dejs:lsp-json jon)

View File

@ -3,7 +3,6 @@
:: ::
/? 310 /? 310
:: ::
^|
|_ own=mime |_ own=mime
++ grow ++ grow
^? ^?
@ -14,7 +13,7 @@
++ grab :: convert from ++ grab :: convert from
^? ^?
|% |%
+$ noun mime :: clam from %noun ++ noun mime :: clam from %noun
++ tape ++ tape
|=(a=_"" [/application/x-urb-unknown (as-octt:mimes:html a)]) |=(a=_"" [/application/x-urb-unknown (as-octt:mimes:html a)])
-- --
@ -25,5 +24,9 @@
++ diff |=(mime +<) ++ diff |=(mime +<)
++ pact |=(mime +<) ++ pact |=(mime +<)
++ join |=([mime mime] `(unit mime)`~) ++ join |=([mime mime] `(unit mime)`~)
++ mash
|= [[ship desk mime] [ship desk mime]]
^- mime
~|(%mime-mash !!)
-- --
-- --

View File

@ -14,5 +14,6 @@
++ diff |=(* +<) ++ diff |=(* +<)
++ pact |=(* +<) ++ pact |=(* +<)
++ join |=([* *] *(unit *)) ++ join |=([* *] *(unit *))
++ mash |=([[ship desk *] [ship desk *]] `*`~|(%noun-mash !!))
-- --
-- --

View File

@ -15,7 +15,7 @@
++ mime ++ mime
|= (pair mite octs) |= (pair mite octs)
=+ o=(pair ,* ,*) :: ,*) =+ o=(pair ,* ,*) :: ,*)
=+ (,[boot-ova=* kernel-ova=(list o) userspace-ova=(list o)] (cue q.q)) =+ (,[%pill nam=term boot-ova=(list) kernel-ova=(list o) userspace-ova=(list o)] (cue q.q))
=/ convert =/ convert
|= ova=(list o) |= ova=(list o)
^- (list unix-event) ^- (list unix-event)
@ -30,7 +30,7 @@
:: =/ boot-ova (convert boot-ova) :: =/ boot-ova (convert boot-ova)
=/ kernel-ova (convert kernel-ova) =/ kernel-ova (convert kernel-ova)
=/ userspace-ova (convert userspace-ova) =/ userspace-ova (convert userspace-ova)
[boot-ova kernel-ova userspace-ova] [%pill nam boot-ova kernel-ova userspace-ova]
-- --
++ grad %mime ++ grad %mime
-- --

View File

@ -1,4 +1,4 @@
|_ dat=@t |_ dat=@
++ grow ++ grow
|% |%
++ mime [/image/png (as-octs:mimes:html dat)] ++ mime [/image/png (as-octs:mimes:html dat)]
@ -6,7 +6,7 @@
++ grab ++ grab
|% |%
++ mime |=([p=mite q=octs] q.q) ++ mime |=([p=mite q=octs] q.q)
++ noun @t ++ noun @
-- --
++ grad %mime ++ grad %mime
-- --

View File

@ -43,7 +43,7 @@
:: ::
=, mimes:html =, mimes:html
|_ [hed=marl tal=marl] |_ [hed=marl tal=marl]
++ grad %mime ++ grad %noun
:: ::
++ grow :: convert to ++ grow :: convert to
|% |%
@ -55,6 +55,7 @@
++ html (crip (en-xml hymn)) :: convert to %html ++ html (crip (en-xml hymn)) :: convert to %html
++ mime [/text/html (as-octs html)] :: convert to %mime ++ mime [/text/html (as-octs html)] :: convert to %mime
-- --
++ noun [hed tal]
-- --
++ grab |% :: convert from ++ grab |% :: convert from
++ noun ,[marl marl] :: clam from %noun ++ noun ,[marl marl] :: clam from %noun

View File

@ -6,7 +6,7 @@
=, html =, html
|_ own=manx |_ own=manx
:: ::
++ grad %mime ++ grad %noun
++ grow :: convert to ++ grow :: convert to
|% |%
++ hymn ;html:(head body:"+{own}") :: convert to %hymn ++ hymn ;html:(head body:"+{own}") :: convert to %hymn

6
pkg/arvo/sur/gcp.hoon Normal file
View File

@ -0,0 +1,6 @@
|%
+$ token
$: access-key=@t
expires-in=@dr
==
--

View File

@ -796,7 +796,7 @@
$: face=(unit term) $: face=(unit term)
file-path=term file-path=term
== ==
+$ care ?(%a %b %c %d %p %r %s %t %u %v %w %x %y %z) :: clay submode +$ care ?(%a %b %c %d %e %f %p %r %s %t %u %v %w %x %y %z) :: clay submode
+$ case :: ship desk case spur +$ case :: ship desk case spur
$% [%da p=@da] :: date $% [%da p=@da] :: date
[%tas p=@tas] :: label [%tas p=@tas] :: label
@ -928,12 +928,16 @@
:: /- sur-file :: surface imports from /sur :: /- sur-file :: surface imports from /sur
:: /+ lib-file :: library imports from /lib :: /+ lib-file :: library imports from /lib
:: /= face /path :: imports built hoon file at path :: /= face /path :: imports built hoon file at path
:: /% face %mark :: imports mark definition from /mar
:: /$ face %from %to :: imports mark converter from /mar
:: /* face %mark /path :: unbuilt file imports, as mark :: /* face %mark /path :: unbuilt file imports, as mark
:: ::
+$ pile +$ pile
$: sur=(list taut) $: sur=(list taut)
lib=(list taut) lib=(list taut)
raw=(list [face=term =path]) raw=(list [face=term =path])
maz=(list [face=term =mark])
caz=(list [face=term =mars])
bar=(list [face=term =mark =path]) bar=(list [face=term =mark =path])
=hoon =hoon
== ==
@ -942,9 +946,25 @@
+$ taut [face=(unit term) pax=term] +$ taut [face=(unit term) pax=term]
:: $mars: mark conversion request :: $mars: mark conversion request
:: $tube: mark conversion gate :: $tube: mark conversion gate
:: $nave: typed mark core
:: ::
+$ mars [a=mark b=mark] +$ mars [a=mark b=mark]
+$ tube $-(vase vase) +$ tube $-(vase vase)
++ nave
|$ [typ dif]
$_
^?
|%
++ bunt *typ
++ diff |~([old=typ new=typ] *dif)
++ form *mark
++ join |~([a=dif b=dif] *(unit (unit dif)))
++ mash
|~ [a=[ship desk dif] b=[ship desk dif]]
*(unit dif)
++ pact |~([typ dif] *typ)
++ vale |~(noun *typ)
--
:: $dais: processed mark core :: $dais: processed mark core
:: ::
+$ dais +$ dais
@ -959,7 +979,6 @@
*(unit vase) *(unit vase)
++ pact |~(diff=vase sam) ++ pact |~(diff=vase sam)
++ vale |~(noun sam) ++ vale |~(noun sam)
++ volt |~(noun sam)
-- --
:: ::
++ get-fit ++ get-fit

View File

@ -829,7 +829,7 @@
:: lifecycle arms; mostly pass-throughs to the contained adult ames :: lifecycle arms; mostly pass-throughs to the contained adult ames
:: ::
++ scry scry:adult-core ++ scry scry:adult-core
++ stay [%4 %larva queued-events ames-state.adult-gate] ++ stay [%5 %larva queued-events ames-state.adult-gate]
++ load ++ load
|= $= old |= $= old
$% $: %4 $% $: %4
@ -839,6 +839,13 @@
== ==
[%adult state=_ames-state.adult-gate] [%adult state=_ames-state.adult-gate]
== == == ==
$: %5
$% $: %larva
events=(qeu queued-event)
state=_ames-state.adult-gate
==
[%adult state=_ames-state.adult-gate]
== ==
== ==
?- old ?- old
[%4 %adult *] (load:adult-core %4 state.old) [%4 %adult *] (load:adult-core %4 state.old)
@ -848,6 +855,14 @@
=. queued-events events.old =. queued-events events.old
=. adult-gate (load:adult-core %4 state.old) =. adult-gate (load:adult-core %4 state.old)
larval-gate larval-gate
::
[%5 %adult *] (load:adult-core %5 state.old)
::
[%5 %larva *]
~> %slog.1^leaf/"ames: larva: load"
=. queued-events events.old
=. adult-gate (load:adult-core %5 state.old)
larval-gate
== ==
-- --
:: adult ames, after metamorphosis from larva :: adult ames, after metamorphosis from larva
@ -919,13 +934,38 @@
[moves ames-gate] [moves ames-gate]
:: +stay: extract state before reload :: +stay: extract state before reload
:: ::
++ stay [%4 %adult ames-state] ++ stay [%5 %adult ames-state]
:: +load: load in old state after reload :: +load: load in old state after reload
:: ::
++ load ++ load
|= old-state=[%4 ^ames-state] |= $= old-state
$% [%4 ^ames-state]
[%5 ^ames-state]
==
|^
^+ ames-gate ^+ ames-gate
=? old-state ?=(%4 -.old-state) %5^(state-4-to-5 +.old-state)
::
?> ?=(%5 -.old-state)
ames-gate(ames-state +.old-state) ames-gate(ames-state +.old-state)
::
++ state-4-to-5
|= =^ames-state
^- ^^ames-state
=. peers.ames-state
%- ~(run by peers.ames-state)
|= =ship-state
?. ?=(%known -.ship-state)
ship-state
=. snd.ship-state
%- ~(run by snd.ship-state)
|= =message-pump-state
=. num-live.metrics.packet-pump-state.message-pump-state
~(wyt in live.packet-pump-state.message-pump-state)
message-pump-state
ship-state
ames-state
--
:: +scry: dereference namespace :: +scry: dereference namespace
:: ::
++ scry ++ scry
@ -1904,6 +1944,11 @@
=/ =bone bone.shut-packet =/ =bone bone.shut-packet
:: ::
?: ?=(%& -.meat.shut-packet) ?: ?=(%& -.meat.shut-packet)
=+ ?~ dud ~
%. ~
%+ slog
leaf+"ames: {<her.channel>} fragment crashed {<mote.u.dud>}"
?.(msg.veb ~ tang.u.dud)
(run-message-sink bone %hear lane shut-packet ?=(~ dud)) (run-message-sink bone %hear lane shut-packet ?=(~ dud))
:: Just try again on error, printing trace :: Just try again on error, printing trace
:: ::
@ -1912,7 +1957,10 @@
:: ::
=+ ?~ dud ~ =+ ?~ dud ~
%. ~ %. ~
(slog leaf+"ames: crashed on message ack" >mote.u.dud< tang.u.dud) %+ slog leaf+"ames: {<her.channel>} ack crashed {<mote.u.dud>}"
?. msg.veb ~
:- >[bone=bone message-num=message-num meat=meat]:shut-packet<
tang.u.dud
(run-message-pump bone %hear [message-num +.meat]:shut-packet) (run-message-pump bone %hear [message-num +.meat]:shut-packet)
:: +on-memo: handle request to send message :: +on-memo: handle request to send message
:: ::
@ -2198,12 +2246,15 @@
?. ?=([%hear * * ok=%.n] task) ?. ?=([%hear * * ok=%.n] task)
:: fresh boon; give message to client vane :: fresh boon; give message to client vane
:: ::
%- (trace msg.veb |.("boon {<her.channel^bone=bone -.task>}")) %- %+ trace msg.veb
=/ dat [her.channel bone=bone message-num=message-num -.task]
|.("sink boon {<dat>}")
peer-core peer-core
:: we previously crashed on this message; notify client vane :: we previously crashed on this message; notify client vane
:: ::
%- %+ trace msg.veb %- %+ trace msg.veb
|.("crashed on boon {<her.channel^bone=bone -.task>}") =/ dat [her.channel bone=bone message-num=message-num -.task]
|.("crashed on sink boon {<dat>}")
boon-to-lost boon-to-lost
:: +boon-to-lost: convert all boons to losts :: +boon-to-lost: convert all boons to losts
:: ::
@ -2221,7 +2272,9 @@
++ on-sink-nack-trace ++ on-sink-nack-trace
|= [=message-num message=*] |= [=message-num message=*]
^+ peer-core ^+ peer-core
%- (trace msg.veb |.("nack trace {<her.channel^bone=bone>}")) %- %+ trace msg.veb
=/ dat [her.channel bone=bone message-num=message-num]
|.("sink naxplanation {<dat>}")
:: ::
=+ ;; =naxplanation message =+ ;; =naxplanation message
:: ack nack-trace message (only applied if we don't later crash) :: ack nack-trace message (only applied if we don't later crash)
@ -2238,7 +2291,9 @@
++ on-sink-plea ++ on-sink-plea
|= [=message-num message=*] |= [=message-num message=*]
^+ peer-core ^+ peer-core
%- (trace msg.veb |.("plea {<her.channel^bone=bone>}")) %- %+ trace msg.veb
=/ dat [her.channel bone=bone message-num=message-num]
|.("sink plea {<dat>}")
:: is this the first time we're trying to process this message? :: is this the first time we're trying to process this message?
:: ::
?. ?=([%hear * * ok=%.n] task) ?. ?=([%hear * * ok=%.n] task)

View File

@ -118,9 +118,11 @@
:: Ford cache :: Ford cache
:: ::
+$ ford-cache +$ ford-cache
$: vases=(map path [res=vase dez=(set path)]) $: files=(map path [res=vase dez=(set path)])
naves=(map mark [res=vase dez=(set path)])
marks=(map mark [res=dais dez=(set path)]) marks=(map mark [res=dais dez=(set path)])
casts=(map mars [res=tube dez=(set path)]) casts=(map mars [res=vase dez=(set path)])
tubes=(map mars [res=tube dez=(set path)])
== ==
:: $reef-cache: built system files :: $reef-cache: built system files
:: ::
@ -462,7 +464,9 @@
+$ build +$ build
$% [%file =path] $% [%file =path]
[%mark =mark] [%mark =mark]
[%dais =mark]
[%cast =mars] [%cast =mars]
[%tube =mars]
[%vale =path] [%vale =path]
== ==
+$ state +$ state
@ -494,8 +498,9 @@
=? stack.nub ?=(^ stack.nub) =? stack.nub ?=(^ stack.nub)
stack.nub(i (~(uni in i.stack.nub) top)) stack.nub(i (~(uni in i.stack.nub) top))
[top stack.nub] [top stack.nub]
:: +read-file: retrieve marked, validated file contents at path
:: ::
++ get-value ++ read-file
|= =path |= =path
^- [cage state] ^- [cage state]
~| %error-validating^path ~| %error-validating^path
@ -519,13 +524,13 @@
?< (~(has in deletes) path) ?< (~(has in deletes) path)
~| %file-not-found^path ~| %file-not-found^path
:_(nub (need (~(get an ankh) path))) :_(nub (need (~(get an ankh) path)))
:: +get-mark: build a mark definition :: +build-nave: build a statically typed mark core
:: ::
++ get-mark ++ build-nave
|= mak=mark |= mak=mark
^- [dais state] ^- [vase state]
~| %error-building-mark^mak ~| %error-building-mark^mak
?^ got=(~(get by marks.cache.nub) mak) ?^ got=(~(get by naves.cache.nub) mak)
=? stack.nub ?=(^ stack.nub) =? stack.nub ?=(^ stack.nub)
stack.nub(i (~(uni in i.stack.nub) dez.u.got)) stack.nub(i (~(uni in i.stack.nub) dez.u.got))
[res.u.got nub] [res.u.got nub]
@ -533,99 +538,128 @@
~|(cycle+mark+mak^stack.nub !!) ~|(cycle+mark+mak^stack.nub !!)
=. cycle.nub (~(put in cycle.nub) mark+mak) =. cycle.nub (~(put in cycle.nub) mark+mak)
=. stack.nub [~ stack.nub] =. stack.nub [~ stack.nub]
=; res=[=vase nub=state]
=. nub nub.res
=^ top stack.nub pop-stack
=. naves.cache.nub (~(put by naves.cache.nub) mak [vase.res top])
[vase.res nub]
=^ cor=vase nub (build-fit %mar mak)
=/ gad=vase (slap cor limb/%grad)
?@ q.gad
=+ !<(mok=mark gad)
=^ deg=vase nub $(mak mok)
=^ tub=vase nub (build-cast mak mok)
=^ but=vase nub (build-cast mok mak)
:_ nub
^- vase :: vase of nave
%+ slap
(with-faces deg+deg tub+tub but+but cor+cor nave+!>(nave) ~)
!, *hoon
=/ typ _+<.cor
=/ dif diff:deg
^- (nave typ dif)
|%
++ bunt +<.cor
++ diff
|= [old=typ new=typ]
^- dif
(diff:deg (tub old) (tub new))
++ form form:deg
++ join join:deg
++ mash mash:deg
++ pact
|= [v=typ d=dif]
^- typ
(but (pact:deg (tub v) d))
++ vale noun:grab:cor
--
:_ nub
^- vase :: vase of nave
%+ slap (slop (with-face cor+cor) bud)
!, *hoon
=/ typ _+<.cor
=/ dif _*diff:grad:cor
^- (nave:clay typ dif)
|%
++ bunt +<.cor
++ diff |=([old=typ new=typ] (diff:~(grad cor old) new))
++ form form:grad:cor
++ join
|= [a=dif b=dif]
^- (unit (unit dif))
?: =(a b)
~
`(join:grad:cor a b)
++ mash
|= [a=[=ship =desk =dif] b=[=ship =desk =dif]]
^- (unit dif)
?: =(dif.a dif.b)
~
`(mash:grad:cor a b)
++ pact |=([v=typ d=dif] (pact:~(grad cor v) d))
++ vale noun:grab:cor
--
:: +build-dais: build a dynamically typed mark definition
::
++ build-dais
|= mak=mark
^- [dais state]
~| %error-building-dais^mak
?^ got=(~(get by marks.cache.nub) mak)
=? stack.nub ?=(^ stack.nub)
stack.nub(i (~(uni in i.stack.nub) dez.u.got))
[res.u.got nub]
?: (~(has in cycle.nub) dais+mak)
~|(cycle+dais+mak^stack.nub !!)
=. cycle.nub (~(put in cycle.nub) dais+mak)
=. stack.nub [~ stack.nub]
=; res=[=dais nub=state] =; res=[=dais nub=state]
=. nub nub.res =. nub nub.res
=^ top stack.nub pop-stack =^ top stack.nub pop-stack
=. marks.cache.nub (~(put by marks.cache.nub) mak [dais.res top]) =. marks.cache.nub (~(put by marks.cache.nub) mak [dais.res top])
[dais.res nub] [dais.res nub]
=^ cor=vase nub (build-fit %mar mak) =^ nav=vase nub (build-nave mak)
=/ gad=vase (slap cor %limb %grad)
?@ q.gad
=+ !<(mok=mark gad)
=^ deg=dais nub $(mak mok)
=^ tub=tube nub (get-cast mak mok)
=^ but=tube nub (get-cast mok mak)
:_ nub :_ nub
^- dais ^- dais
|_ sam=vase |_ sam=vase
++ bunt (slap cor $+6) ++ bunt (slap nav limb/%bunt)
++ diff ++ diff
|= new=vase |= new=vase
^- vase (slam (slap nav limb/%diff) (slop sam new))
(~(diff deg (tub sam)) (tub new)) ++ form !<(mark (slap nav limb/%form))
++ form form:deg
++ join join:deg
++ mash mash:deg
++ pact
|= diff=vase
^+ sam
(but (~(pact deg (tub sam)) diff))
++ vale
|= =noun
^+ sam
(slam (slap cor !,(*hoon noun:grab)) !>(noun))
++ volt
|= =noun
^+ sam
[p:bunt noun]
--
:_ nub
=+ !<(fom=mark (slap gad %limb %form))
^- dais
|_ sam=vase
++ bunt (slap cor $+6)
++ diff
|= new=vase
^- vase
%+ slap
(with-faces cor+cor sam+sam new+new ~)
!, *hoon
(diff:~(grad cor sam) new)
++ form fom
++ join ++ join
|= [a=vase b=vase] |= [a=vase b=vase]
^- (unit (unit vase)) ^- (unit (unit vase))
?: =(q.a q.b) =/ res=vase (slam (slap nav limb/%join) (slop a b))
~ ?~ q.res ~
=; res `?~(q.res ~ `(slap res !,(*hoon ?~(. !! u)))) ?~ +.q.res [~ ~]
(slam (slap cor !,(*hoon join:grad)) (slop a b)) ``(slap res !,(*hoon ?>(?=([~ ~ *] .) u.u)))
++ mash ++ mash
|= [a=[=ship =desk diff=vase] b=[=ship =desk diff=vase]] |= [a=[=ship =desk diff=vase] b=[=ship =desk diff=vase]]
^- (unit vase) ^- (unit vase)
?: =(q.diff.a q.diff.b) =/ res=vase
~ %+ slam (slap nav limb/%mash)
:- ~
%+ slam (slap cor !,(*hoon mash:grad))
%+ slop %+ slop
:(slop !>(ship.a) !>(desk.a) diff.a) :(slop !>(ship.a) !>(desk.a) diff.a)
:(slop !>(ship.b) !>(desk.b) diff.b) :(slop !>(ship.b) !>(desk.b) diff.b)
?~ q.res
~
`(slap res !,(*hoon ?>((^ .) u)))
++ pact ++ pact
|= diff=vase |= diff=vase
^+ sam (slam (slap nav limb/%pact) (slop sam diff))
%+ slap
(with-faces cor+cor sam+sam diff+diff ~)
!, *hoon
(pact:~(grad cor sam) diff)
++ vale ++ vale
|= =noun |= =noun
^+ sam (slam (slap nav limb/%vale) noun/noun)
(slam (slap cor !,(*hoon noun:grab)) !>(noun))
++ volt
|= =noun
^+ sam
[p:bunt noun]
-- --
:: +get-cast: produce a $tube mark conversion gate from .a to .b :: +build-cast: produce gate to convert mark .a to, statically typed
:: ::
++ get-cast ++ build-cast
|= [a=mark b=mark] |= [a=mark b=mark]
^- [tube state] ^- [vase state]
~| error-building-cast+[a b] ~| error-building-cast+[a b]
?: =([%mime %hoon] [a b]) ?: =([%mime %hoon] [a b])
:_ nub :_(nub !>(|=(m=mime q.q.m)))
|= sam=vase
=+ !<(=mime sam)
!>(q.q.mime)
?^ got=(~(get by casts.cache.nub) [a b]) ?^ got=(~(get by casts.cache.nub) [a b])
=? stack.nub ?=(^ stack.nub) =? stack.nub ?=(^ stack.nub)
stack.nub(i (~(uni in i.stack.nub) dez.u.got)) stack.nub(i (~(uni in i.stack.nub) dez.u.got))
@ -633,11 +667,11 @@
?: (~(has in cycle.nub) cast+[a b]) ?: (~(has in cycle.nub) cast+[a b])
~|(cycle+cast+[a b]^stack.nub !!) ~|(cycle+cast+[a b]^stack.nub !!)
=. stack.nub [~ stack.nub] =. stack.nub [~ stack.nub]
=; res=[=tube nub=state] =; res=[=vase nub=state]
=. nub nub.res =. nub nub.res
=^ top stack.nub pop-stack =^ top stack.nub pop-stack
=. casts.cache.nub (~(put by casts.cache.nub) [a b] [tube.res top]) =. casts.cache.nub (~(put by casts.cache.nub) [a b] [vase.res top])
[tube.res nub] [vase.res nub]
:: try +grow; is there a +grow core with a .b arm? :: try +grow; is there a +grow core with a .b arm?
:: ::
=^ old=vase nub (build-fit %mar a) =^ old=vase nub (build-fit %mar a)
@ -649,47 +683,57 @@
:: +grow core has .b arm; use that :: +grow core has .b arm; use that
:: ::
:_ nub :_ nub
^- tube %+ slap (with-faces cor+old ~)
|= sam=vase ^- hoon
^- vase :+ %brcl !,(*hoon v=+<.cor)
%+ slap :+ %tsgl limb/b
(with-faces old+old sam+sam ~) !,(*hoon ~(grow cor v))
:+ %sgzp !,(*hoon old=old)
:+ %sgzp !,(*hoon sam=sam)
:+ %tsgl [%limb b]
!, *hoon
~(grow old sam)
:: try direct +grab :: try direct +grab
:: ::
=^ new=vase nub (build-fit %mar b) =^ new=vase nub (build-fit %mar b)
=/ rab =/ rab (mule |.((slap new tsgl/[limb/a limb/%grab])))
%- mule |.
%+ slap new
:+ %tsgl [%limb a]
[%limb %grab]
?: &(?=(%& -.rab) ?=(^ q.p.rab)) ?: &(?=(%& -.rab) ?=(^ q.p.rab))
:_(nub |=(sam=vase ~|([%grab a b] (slam p.rab sam)))) :_(nub p.rab)
:: try +jump :: try +jump
:: ::
=/ jum =/ jum (mule |.((slap old tsgl/[limb/b limb/%jump])))
%- mule |.
%+ slap old
:+ %tsgl [%limb b]
[%limb %jump]
?: ?=(%& -.jum) ?: ?=(%& -.jum)
(compose-casts a !<(mark p.jum) b) (compose-casts a !<(mark p.jum) b)
:: try indirect +grab
::
?: ?=(%& -.rab) ?: ?=(%& -.rab)
(compose-casts a !<(mark p.rab) b) (compose-casts a !<(mark p.rab) b)
?: ?=(%noun b)
:_(nub !>(|=(* +<)))
~|(no-cast-from+[a b] !!) ~|(no-cast-from+[a b] !!)
:: ::
++ compose-casts ++ compose-casts
|= [x=mark y=mark z=mark] |= [x=mark y=mark z=mark]
^- [vase state]
=^ uno=vase nub (build-cast x y)
=^ dos=vase nub (build-cast y z)
:_ nub
%+ slap
(with-faces uno+uno dos+dos cork+!>(cork) ~)
!,(*hoon (cork uno dos))
:: +build-tube: produce a $tube mark conversion gate from .a to .b
::
++ build-tube
|= [a=mark b=mark]
^- [tube state] ^- [tube state]
=^ uno=tube nub (get-cast x y) ~| error-building-tube+[a b]
=^ dos=tube nub (get-cast y z) ?^ got=(~(get by tubes.cache.nub) [a b])
:_(nub |=(sam=vase (dos (uno sam)))) =? stack.nub ?=(^ stack.nub)
stack.nub(i (~(uni in i.stack.nub) dez.u.got))
[res.u.got nub]
?: (~(has in cycle.nub) tube+[a b])
~|(cycle+tube+[a b]^stack.nub !!)
=. stack.nub [~ stack.nub]
=; res=[=tube nub=state]
=. nub nub.res
=^ top stack.nub pop-stack
=. tubes.cache.nub (~(put by tubes.cache.nub) [a b] [tube.res top])
[tube.res nub]
=^ gat=vase nub (build-cast a b)
:_(nub |=(v=vase (slam gat v)))
:: ::
++ lobe-to-page ++ lobe-to-page
|= =lobe |= =lobe
@ -713,7 +757,7 @@
?: =(mak p.page) ?: =(mak p.page)
(page-to-cage page) (page-to-cage page)
=^ [mark vax=vase] nub (page-to-cage page) =^ [mark vax=vase] nub (page-to-cage page)
=^ =tube nub (get-cast p.page mak) =^ =tube nub (build-tube p.page mak)
:_(nub [mak (tube vax)]) :_(nub [mak (tube vax)])
:: ::
++ page-to-cage ++ page-to-cage
@ -723,7 +767,7 @@
:_(nub [%hoon -:!>(*@t) q.page]) :_(nub [%hoon -:!>(*@t) q.page])
?: =(%mime p.page) ?: =(%mime p.page)
:_(nub [%mime !>(;;(mime q.page))]) :_(nub [%mime !>(;;(mime q.page))])
=^ =dais nub (get-mark p.page) =^ =dais nub (build-dais p.page)
:_(nub [p.page (vale:dais q.page)]) :_(nub [p.page (vale:dais q.page)])
:: ::
++ cast-path ++ cast-path
@ -731,10 +775,10 @@
^- [cage state] ^- [cage state]
=/ mok (head (flop path)) =/ mok (head (flop path))
~| error-casting-path+[path mok mak] ~| error-casting-path+[path mok mak]
=^ cag=cage nub (get-value path) =^ cag=cage nub (read-file path)
?: =(mok mak) ?: =(mok mak)
[cag nub] [cag nub]
=^ =tube nub (get-cast mok mak) =^ =tube nub (build-tube mok mak)
~| error-running-cast+[path mok mak] ~| error-running-cast+[path mok mak]
:_(nub [mak (tube q.cag)]) :_(nub [mak (tube q.cag)])
:: ::
@ -746,14 +790,14 @@
=+ ;;(dif=(urge cord) q.diff) =+ ;;(dif=(urge cord) q.diff)
=/ new=@t (of-wain:format (lurk:differ txt dif)) =/ new=@t (of-wain:format (lurk:differ txt dif))
:_(nub [%hoon !>(new)]) :_(nub [%hoon !>(new)])
=^ dys=dais nub (get-mark p.old) =^ dys=dais nub (build-dais p.old)
=^ syd=dais nub (get-mark p.diff) =^ syd=dais nub (build-dais p.diff)
:_(nub [p.old (~(pact dys (vale:dys q.old)) (vale:syd q.diff))]) :_(nub [p.old (~(pact dys (vale:dys q.old)) (vale:syd q.diff))])
:: ::
++ prelude ++ prelude
|= =path |= =path
^- vase ^- vase
=^ cag=cage nub (get-value path) =^ cag=cage nub (read-file path)
?> =(%hoon p.cag) ?> =(%hoon p.cag)
=/ tex=tape (trip !<(@t q.cag)) =/ tex=tape (trip !<(@t q.cag))
=/ =pile (parse-pile path tex) =/ =pile (parse-pile path tex)
@ -765,7 +809,7 @@
|= =path |= =path
^- [vase state] ^- [vase state]
~| %error-building^path ~| %error-building^path
?^ got=(~(get by vases.cache.nub) path) ?^ got=(~(get by files.cache.nub) path)
=? stack.nub ?=(^ stack.nub) =? stack.nub ?=(^ stack.nub)
stack.nub(i (~(uni in i.stack.nub) dez.u.got)) stack.nub(i (~(uni in i.stack.nub) dez.u.got))
[res.u.got nub] [res.u.got nub]
@ -773,13 +817,13 @@
~|(cycle+file+path^stack.nub !!) ~|(cycle+file+path^stack.nub !!)
=. cycle.nub (~(put in cycle.nub) file+path) =. cycle.nub (~(put in cycle.nub) file+path)
=. stack.nub [(sy path ~) stack.nub] =. stack.nub [(sy path ~) stack.nub]
=^ cag=cage nub (get-value path) =^ cag=cage nub (read-file path)
?> =(%hoon p.cag) ?> =(%hoon p.cag)
=/ tex=tape (trip !<(@t q.cag)) =/ tex=tape (trip !<(@t q.cag))
=/ =pile (parse-pile path tex) =/ =pile (parse-pile path tex)
=^ res=vase nub (run-pile pile) =^ res=vase nub (run-pile pile)
=^ top stack.nub pop-stack =^ top stack.nub pop-stack
=. vases.cache.nub (~(put by vases.cache.nub) path [res top]) =. files.cache.nub (~(put by files.cache.nub) path [res top])
[res nub] [res nub]
:: ::
++ run-pile ++ run-pile
@ -787,6 +831,8 @@
=^ sut=vase nub (run-tauts bud %sur sur.pile) =^ sut=vase nub (run-tauts bud %sur sur.pile)
=^ sut=vase nub (run-tauts sut %lib lib.pile) =^ sut=vase nub (run-tauts sut %lib lib.pile)
=^ sut=vase nub (run-raw sut raw.pile) =^ sut=vase nub (run-raw sut raw.pile)
=^ sut=vase nub (run-maz sut maz.pile)
=^ sut=vase nub (run-caz sut caz.pile)
=^ sut=vase nub (run-bar sut bar.pile) =^ sut=vase nub (run-bar sut bar.pile)
=/ res=vase (road |.((slap sut hoon.pile))) =/ res=vase (road |.((slap sut hoon.pile)))
[res nub] [res nub]
@ -807,75 +853,59 @@
++ pile-rule ++ pile-rule
|= pax=path |= pax=path
%- full %- full
%+ ifix [gay gay] %+ ifix
%+ cook |=(pile +<) :_ gay
;~ pfix
:: parse optional /? and ignore :: parse optional /? and ignore
:: ::
;~ pose ;~(plug gay (punt ;~(plug fas wut gap dem gap)))
(cold ~ ;~(plug fas wut gap dem gap)) |^
(easy ~)
==
::
;~ plug ;~ plug
;~ pose %+ cook (bake zing (list (list taut)))
;~ sfix %+ rune hep
%+ cook |=((list (list taut)) (zing +<))
%+ more gap
;~ pfix ;~(plug fas hep gap)
(most ;~(plug com gaw) taut-rule) (most ;~(plug com gaw) taut-rule)
==
gap
==
(easy ~)
==
:: ::
;~ pose %+ cook (bake zing (list (list taut)))
;~ sfix %+ rune lus
%+ cook |=((list (list taut)) (zing +<))
%+ more gap
;~ pfix ;~(plug fas lus gap)
(most ;~(plug com gaw) taut-rule) (most ;~(plug com gaw) taut-rule)
==
gap
==
(easy ~)
==
:: ::
;~ pose %+ rune tis
;~ sfix ;~(plug sym ;~(pfix gap fas (more fas urs:ab)))
%+ cook |=((list [face=term =path]) +<)
%+ more gap
;~ pfix ;~(plug fas tis gap)
%+ cook |=([term path] +<)
;~(plug sym ;~(pfix ;~(plug gap fas) (more fas urs:ab)))
==
gap
==
(easy ~)
==
:: ::
;~ pose %+ rune cen
;~ sfix ;~(plug sym ;~(pfix gap ;~(pfix cen sym)))
%+ cook |=((list [face=term =mark =path]) +<) ::
%+ more gap %+ rune buc
;~ pfix ;~(plug fas tar gap) ;~ (glue gap)
%+ cook |=([term mark path] +<)
;~ plug
sym sym
;~(pfix ;~(plug gap cen) sym) ;~(pfix cen sym)
;~(pfix ;~(plug gap fas) (more fas urs:ab)) ;~(pfix cen sym)
==
==
gap
==
(easy ~)
== ==
:: ::
%+ cook |=(huz=(list hoon) `hoon`tssg+huz) %+ rune tar
;~ (glue gap)
sym
;~(pfix cen sym)
;~(pfix fas (more fas urs:ab))
==
::
%+ stag %tssg
(most gap tall:(vang & pax)) (most gap tall:(vang & pax))
== ==
== ::
++ pant
|* fel=^rule
;~(pose fel (easy ~))
::
++ mast
|* [bus=^rule fel=^rule]
;~(sfix (more bus fel) bus)
::
++ rune
|* [bus=^rule fel=^rule]
%- pant
%+ mast gap
;~(pfix fas bus gap fel)
--
:: ::
++ taut-rule ++ taut-rule
%+ cook |=(taut +<) %+ cook |=(taut +<)
@ -901,6 +931,22 @@
=. p.pin [%face face.i.raw p.pin] =. p.pin [%face face.i.raw p.pin]
$(sut (slop pin sut), raw t.raw) $(sut (slop pin sut), raw t.raw)
:: ::
++ run-maz
|= [sut=vase maz=(list [face=term =mark])]
^- [vase state]
?~ maz [sut nub]
=^ pin=vase nub (build-nave mark.i.maz)
=. p.pin [%face face.i.maz p.pin]
$(sut (slop pin sut), maz t.maz)
::
++ run-caz
|= [sut=vase caz=(list [face=term =mars])]
^- [vase state]
?~ caz [sut nub]
=^ pin=vase nub (build-cast mars.i.caz)
=. p.pin [%face face.i.caz p.pin]
$(sut (slop pin sut), caz t.caz)
::
++ run-bar ++ run-bar
|= [sut=vase bar=(list [face=term =mark =path])] |= [sut=vase bar=(list [face=term =mark =path])]
^- [vase state] ^- [vase state]
@ -1527,9 +1573,11 @@
%+ turn (tail (spud pux)) :: lose leading '/' %+ turn (tail (spud pux)) :: lose leading '/'
|=(c=@tD `@tD`?:(=('/' c) '-' c)) :: convert '/' to '-' |=(c=@tD `@tD`?:(=('/' c) '-' c)) :: convert '/' to '-'
:: ::
:* ((invalidate path vase) vases.ford-cache invalid) :* ((invalidate path vase) files.ford-cache invalid)
((invalidate mark vase) naves.ford-cache invalid)
((invalidate mark dais) marks.ford-cache invalid) ((invalidate mark dais) marks.ford-cache invalid)
((invalidate mars tube) casts.ford-cache invalid) ((invalidate mars vase) casts.ford-cache invalid)
((invalidate mars tube) tubes.ford-cache invalid)
== ==
:: ::
++ invalidate ++ invalidate
@ -1640,24 +1688,26 @@
:: ::
++ checkout-changes ++ checkout-changes
|= [=ford=args:ford:fusion changes=(map path (each page lobe))] |= [=ford=args:ford:fusion changes=(map path (each page lobe))]
=/ cans=(list [=path change=(each page lobe)]) ~(tap by changes) ^- [(map path [=lobe =cage]) ford-cache]
|- ^- [(map path [=lobe =cage]) ford-cache] %+ roll `(list [path (each page lobe)])`~(tap by changes)
?~ cans |= $: [=path change=(each page lobe)]
[~ ford-cache.ford-args] [built=(map path [lobe cage]) cache=_ford-cache.ford-args]
==
^+ [built cache]
=. ford-cache.ford-args cache
=^ cage ford-cache.ford-args =^ cage ford-cache.ford-args
:: ~> %slog.[0 leaf+"clay: validating {(spud path.i.cans)}"] :: ~> %slog.[0 leaf/"clay: validating {(spud path)}"]
%- wrap:fusion %- wrap:fusion
(get-value:(ford:fusion ford-args) path.i.cans) (read-file:(ford:fusion ford-args) path)
=/ =lobe =/ =lobe
?- -.change.i.cans ?- -.change
%| p.change.i.cans %| p.change
:: Don't use p.change.i.cans because that's before casting to :: Don't use p.change.i.cans because that's before casting to
:: the correct mark. :: the correct mark.
:: ::
%& (page-to-lobe [p q.q]:cage) %& (page-to-lobe [p q.q]:cage)
== ==
=^ so-far ford-cache.ford-args $(cans t.cans) [(~(put by built) path [lobe cage]) ford-cache.ford-args]
[(~(put by so-far) path.i.cans lobe cage) ford-cache.ford-args]
:: ::
:: Update ankh :: Update ankh
:: ::
@ -2235,7 +2285,7 @@
^- dais ^- dais
=^ =dais fod.dom =^ =dais fod.dom
%- wrap:fusion %- wrap:fusion
(get-mark:(ford:fusion static-ford-args) mark) (build-dais:(ford:fusion static-ford-args) mark)
dais dais
:: ::
:: Diff two files on bob-desk :: Diff two files on bob-desk
@ -2665,6 +2715,8 @@
%b ~| %i-guess-you-ought-to-build-your-own-marks !! %b ~| %i-guess-you-ought-to-build-your-own-marks !!
%c ~| %casts-should-be-compiled-on-your-own-ship !! %c ~| %casts-should-be-compiled-on-your-own-ship !!
%d ~| %totally-temporary-error-please-replace-me !! %d ~| %totally-temporary-error-please-replace-me !!
%e ~| %yes-naves-also-shouldnt-cross-the-network !!
%f ~| %even-static-casts-should-be-built-locally !!
%p ~| %requesting-foreign-permissions-is-invalid !! %p ~| %requesting-foreign-permissions-is-invalid !!
%r ~| %no-cages-please-they-are-just-way-too-big !! %r ~| %no-cages-please-they-are-just-way-too-big !!
%s ~| %please-dont-get-your-takos-over-a-network !! %s ~| %please-dont-get-your-takos-over-a-network !!
@ -3418,7 +3470,7 @@
^- [(unit (unit (each cage lobe))) ford-cache] ^- [(unit (unit (each cage lobe))) ford-cache]
?. =(aeon let.dom) ?. =(aeon let.dom)
[~ fod.dom] [~ fod.dom]
=/ cached=(unit [=vase *]) (~(get by vases.fod.dom) path) =/ cached=(unit [=vase *]) (~(get by files.fod.dom) path)
?^ cached ?^ cached
:_(fod.dom [~ ~ %& %vase !>(vase.u.cached)]) :_(fod.dom [~ ~ %& %vase !>(vase.u.cached)])
=/ x (read-x aeon path) =/ x (read-x aeon path)
@ -3447,7 +3499,7 @@
:_(fod.dom [~ ~ %& %dais !>(dais.u.cached)]) :_(fod.dom [~ ~ %& %dais !>(dais.u.cached)])
=^ =dais fod.dom =^ =dais fod.dom
%- wrap:fusion %- wrap:fusion
(get-mark:(ford:fusion static-ford-args) i.path) (build-dais:(ford:fusion static-ford-args) i.path)
:_(fod.dom [~ ~ %& %dais !>(dais)]) :_(fod.dom [~ ~ %& %dais !>(dais)])
:: ::
++ read-c ++ read-c
@ -3458,14 +3510,46 @@
[~ fod.dom] [~ fod.dom]
?. ?=([@ @ ~] path) ?. ?=([@ @ ~] path)
[[~ ~] fod.dom] [[~ ~] fod.dom]
=/ cached=(unit [=tube *]) (~(get by casts.fod.dom) [i i.t]:path) =/ cached=(unit [=tube *]) (~(get by tubes.fod.dom) [i i.t]:path)
?^ cached ?^ cached
:_(fod.dom [~ ~ %& %tube !>(tube.u.cached)]) :_(fod.dom [~ ~ %& %tube !>(tube.u.cached)])
=^ =tube fod.dom =^ =tube fod.dom
%- wrap:fusion %- wrap:fusion
(get-cast:(ford:fusion static-ford-args) [i i.t]:path) (build-tube:(ford:fusion static-ford-args) [i i.t]:path)
:_(fod.dom [~ ~ %& %tube !>(tube)]) :_(fod.dom [~ ~ %& %tube !>(tube)])
:: ::
++ read-e
!.
|= [=aeon =path]
^- [(unit (unit (each cage lobe))) ford-cache]
?. =(aeon let.dom)
[~ fod.dom]
?. ?=([@ ~] path)
[[~ ~] fod.dom]
=/ cached=(unit [=vase *]) (~(get by naves.fod.dom) i.path)
?^ cached
:_(fod.dom [~ ~ %& %nave !>(vase.u.cached)])
=^ =vase fod.dom
%- wrap:fusion
(build-nave:(ford:fusion static-ford-args) i.path)
:_(fod.dom [~ ~ %& %nave !>(vase)])
::
++ read-f
!.
|= [=aeon =path]
^- [(unit (unit (each cage lobe))) ford-cache]
?. =(aeon let.dom)
[~ fod.dom]
?. ?=([@ @ ~] path)
[[~ ~] fod.dom]
=/ cached=(unit [=vase *]) (~(get by casts.fod.dom) [i i.t]:path)
?^ cached
:_(fod.dom [~ ~ %& %cast vase.u.cached])
=^ =vase fod.dom
%- wrap:fusion
(build-cast:(ford:fusion static-ford-args) [i i.t]:path)
:_(fod.dom [~ ~ %& %cast vase])
::
:: Gets the permissions that apply to a particular node. :: Gets the permissions that apply to a particular node.
:: ::
:: If the node has no permissions of its own, we use its parent's. :: If the node has no permissions of its own, we use its parent's.
@ -3810,7 +3894,8 @@
:: virtualize to catch and produce deterministic failures :: virtualize to catch and produce deterministic failures
:: ::
!. !.
=- ?:(?=(%& -<) p.- ((slog p.-) [[~ ~] fod])) =- ?: ?=(%& -<) p.-
((slog leaf+"gall: read-at-aeon fail {<mun>}" p.-) [[~ ~] fod])
%- mule |. %- mule |.
?- care.mun ?- care.mun
%d %d
@ -3826,6 +3911,8 @@
%a (read-a yon path.mun) %a (read-a yon path.mun)
%b (read-b yon path.mun) %b (read-b yon path.mun)
%c (read-c yon path.mun) %c (read-c yon path.mun)
%e (read-e yon path.mun)
%f (read-f yon path.mun)
%p :_(fod (read-p path.mun)) %p :_(fod (read-p path.mun))
%r :_(fod (bind (read-r yon path.mun) (lift |=(a=cage [%& a])))) %r :_(fod (bind (read-r yon path.mun) (lift |=(a=cage [%& a]))))
%s :_(fod (bind (read-s yon path.mun) (lift |=(a=cage [%& a])))) %s :_(fod (bind (read-s yon path.mun) (lift |=(a=cage [%& a]))))
@ -3871,7 +3958,7 @@
:: ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
=| :: instrument state =| :: instrument state
$: ver=%6 :: vane version $: ver=%7 :: vane version
ruf=raft :: revision tree ruf=raft :: revision tree
== :: == ::
|= [now=@da eny=@uvJ rof=roof] :: current invocation |= [now=@da eny=@uvJ rof=roof] :: current invocation
@ -4113,8 +4200,77 @@
== ==
:: ::
++ load ++ load
|= old=[%6 raft] => |%
..^$(ruf +.old) +$ raft-any
$% [%7 raft-7]
[%6 raft-6]
==
+$ raft-7 raft
+$ dojo-7 dojo
+$ ford-cache-7 ford-cache
+$ raft-6
$: rom=room-6 :: domestic
hoy=(map ship rung-6) :: foreign
ran=rang :: hashes
mon=(map term beam) :: mount points
hez=(unit duct) :: sync duct
cez=(map @ta crew) :: permission groups
pud=(unit [=desk =yoki]) :: pending update
== ::
+$ room-6 [hun=duct dos=(map desk dojo-6)]
+$ dojo-6
$: qyx=cult :: subscribers
dom=dome-6 :: desk state
per=regs :: read perms per path
pew=regs :: write perms per path
==
+$ dome-6
$: ank=ankh :: state
let=aeon :: top id
hit=(map aeon tako) :: versions by id
lab=(map @tas aeon) :: labels
mim=(map path mime) :: mime cache
fod=ford-cache-6 :: ford cache
fer=(unit reef-cache) :: reef cache
==
+$ rung-6
$: rus=(map desk rede-6)
==
+$ rede-6
$: lim=@da
ref=(unit rind)
qyx=cult
dom=dome-6
per=regs
pew=regs
==
+$ ford-cache-6 * :: discard old cache
--
|= old=raft-any
|^
=? old ?=(%6 -.old) 7+(raft-6-to-7 +.old)
?> ?=(%7 -.old)
..^^$(ruf +.old)
:: +raft-6-to-7: delete stale ford caches (they could all be invalid)
::
++ raft-6-to-7
|= raf=raft-6
^- raft-7
%= raf
dos.rom
%- ~(run by dos.rom.raf)
|= doj=dojo-6
^- dojo-7
doj(fod.dom *ford-cache-7)
::
hoy
%- ~(run by hoy.raf)
|= =rung-6
%- ~(run by rus.rung-6)
|= =rede-6
rede-6(dom dom.rede-6(fod *ford-cache-7))
==
--
:: ::
++ scry :: inspect ++ scry :: inspect
^- roon ^- roon
@ -4164,7 +4320,7 @@
dos.rom dos.rom
%- ~(run by dos.rom.ruf) %- ~(run by dos.rom.ruf)
|= =dojo |= =dojo
dojo(fod.dom [~ ~ ~]) dojo(fod.dom [~ ~ ~ ~ ~])
:: ::
hoy hoy
%- ~(run by hoy.ruf) %- ~(run by hoy.ruf)
@ -4173,7 +4329,7 @@
rus rus
%- ~(run by rus.rung) %- ~(run by rus.rung)
|= =rede |= =rede
rede(fod.dom [~ ~ ~]) rede(fod.dom [~ ~ ~ ~ ~])
== ==
== ==
:: ::
@ -4348,9 +4504,11 @@
:+ desk %| :+ desk %|
:~ ankh+&+ank.dom.dojo :~ ankh+&+ank.dom.dojo
mime+&+mim.dom.dojo mime+&+mim.dom.dojo
ford-vases+&+vases.fod.dom.dojo ford-files+&+files.fod.dom.dojo
ford-naves+&+naves.fod.dom.dojo
ford-marks+&+marks.fod.dom.dojo ford-marks+&+marks.fod.dom.dojo
ford-casts+&+casts.fod.dom.dojo ford-casts+&+casts.fod.dom.dojo
ford-tubes+&+tubes.fod.dom.dojo
== ==
:~ domestic+|+domestic :~ domestic+|+domestic
foreign+&+hoy.ruf foreign+&+hoy.ruf

View File

@ -680,12 +680,9 @@
:: note this should only happen on reverse bones, so only facts :: note this should only happen on reverse bones, so only facts
:: and kicks :: and kicks
:: ::
=/ sys-wire [%sys wire]
:: TODO: %drip %kick so app crash can't kill the remote %pull :: TODO: %drip %kick so app crash can't kill the remote %pull
:: ::
=/ =ames-request-all [%0 %u ~] =. mo-core (mo-send-foreign-request ship foreign-agent %leave ~)
=. mo-core
(mo-pass sys-wire %a %plea ship %g /ge/[foreign-agent] ames-request-all)
=. mo-core (mo-give %unto %kick ~) =. mo-core (mo-give %unto %kick ~)
mo-core mo-core
== ==
@ -942,15 +939,13 @@
=/ sky (rof ~ %cb [our %home case] /[mark.ames-response]) =/ sky (rof ~ %cb [our %home case] /[mark.ames-response])
?- sky ?- sky
?(~ [~ ~]) ?(~ [~ ~])
=/ ror "gall: ames mark fail {<mark.ames-response>}" (mean leaf+"gall: ames mark fail {<mark.ames-response>}" ~)
(mo-give %done `vale+[leaf+ror]~)
:: ::
[~ ~ *] [~ ~ *]
=+ !<(=dais:clay q.u.u.sky) =+ !<(=dais:clay q.u.u.sky)
=/ res (mule |.((vale:dais noun.ames-response))) =/ res (mule |.((vale:dais noun.ames-response)))
?: ?=(%| -.res) ?: ?=(%| -.res)
=/ ror "gall: ames vale fail {<mark.deal>}" (mean leaf+"gall: ames vale fail {<mark.ames-response>}" p.res)
(mo-give %done `vale+[leaf+ror p.res])
=. mo-core =. mo-core
%+ mo-pass /nowhere %+ mo-pass /nowhere
[%c %warp our %home ~ %sing %b case /[mark.ames-response]] [%c %warp our %home ~ %sing %b case /[mark.ames-response]]

View File

@ -3297,11 +3297,14 @@
%- flop %- flop
|- ^- ^tape |- ^- ^tape
?:(=(0 a) ~ [(add '0' (mod a 10)) $(a (div a 10))]) ?:(=(0 a) ~ [(add '0' (mod a 10)) $(a (div a 10))])
:: :: ++sect:enjs:format
++ sect :: s timestamp
|= a=^time
(numb (unt:chrono:userlib a))
:: :: ++time:enjs:format :: :: ++time:enjs:format
++ time :: ms timestamp ++ time :: ms timestamp
|= a=^time |= a=^time
=- (numb (div (mul - 1.000) ~s1)) (numb (unm:chrono:userlib a))
(add (div ~s1 2.000) (sub a ~1970.1.1))
:: :: ++path:enjs:format :: :: ++path:enjs:format
++ path :: string from path ++ path :: string from path
|= a=^path |= a=^path
@ -3365,10 +3368,10 @@
(poq (wit jon)) (poq (wit jon))
:: :: ++di:dejs:format :: :: ++di:dejs:format
++ di :: millisecond date ++ di :: millisecond date
%+ cu (cu from-unix-ms:chrono:userlib ni)
|= a=@u ^- @da :: :: ++du:dejs:format
(add ~1970.1.1 (div (mul ~s1 a) 1.000)) ++ du :: second date
ni (cu from-unix:chrono:userlib ni)
:: :: ++mu:dejs:format :: :: ++mu:dejs:format
++ mu :: true unit ++ mu :: true unit
|* wit=fist |* wit=fist
@ -3578,10 +3581,7 @@
(bind (stud:chrono:userlib p.jon) |=(a=date (year a))) (bind (stud:chrono:userlib p.jon) |=(a=date (year a)))
:: ::
++ di :: millisecond date ++ di :: millisecond date
%+ cu (cu from-unix-ms:chrono:userlib ni)
|= a=@u ^- @da
(add ~1970.1.1 (div (mul ~s1 a) 1.000))
ni
:: ::
++ mu :: true unit ++ mu :: true unit
|* wit=fist |* wit=fist
@ -5230,55 +5230,47 @@
=/ acc [stop=`?`%.n state=state] =/ acc [stop=`?`%.n state=state]
=< abet =< main =< abet =< main
|% |%
++ this .
++ abet [state.acc a] ++ abet [state.acc a]
:: +main: main recursive loop; performs a partial inorder traversal :: +main: main recursive loop; performs a partial inorder traversal
:: ::
++ main ++ main
^+ . ^+ this
:: stop if empty or we've been told to stop :: stop if empty or we've been told to stop
:: ::
?~ a . ?: =(~ a) this
?: stop.acc . ?: stop.acc this
:: inorder traversal: left -> node -> right, until .f sets .stop :: inorder traversal: left -> node -> right, until .f sets .stop
:: ::
=> left =. this left
?: stop.acc . ?: stop.acc this
=> node =^ del this node
?: stop.acc . =? this !stop.acc right
right =? a del (nip a)
this
:: +node: run .f on .n.a, updating .a, .state, and .stop :: +node: run .f on .n.a, updating .a, .state, and .stop
:: ::
++ node ++ node
^+ . ^+ [del=*? this]
:: run .f on node, updating .stop.acc and .state.acc :: run .f on node, updating .stop.acc and .state.acc
:: ::
=^ res acc
?> ?=(^ a) ?> ?=(^ a)
(f state.acc n.a) =^ res acc (f state.acc n.a)
:: apply update to .a from .f's product ?~ res
:: [del=& this]
=. a [del=| this(val.n.a u.res)]
:: if .f requested node deletion, merge and balance .l.a and .r.a
::
?~ res (nip a)
:: we kept the node; replace its .val; order is unchanged
::
?> ?=(^ a)
a(val.n u.res)
::
..node
:: +left: recurse on left subtree, copying mutant back into .l.a :: +left: recurse on left subtree, copying mutant back into .l.a
:: ::
++ left ++ left
^+ . ^+ this
?~ a . ?~ a this
=/ lef main(a l.a) =/ lef main(a l.a)
lef(a a(l a.lef)) lef(a a(l a.lef))
:: +right: recurse on right subtree, copying mutant back into .r.a :: +right: recurse on right subtree, copying mutant back into .r.a
:: ::
++ right ++ right
^+ . ^+ this
?~ a . ?~ a this
=/ rig main(a r.a) =/ rig main(a r.a)
rig(a a(r a.rig)) rig(a a(r a.rig))
-- --
@ -5408,13 +5400,20 @@
:: :::: :: ::::
++ chrono ^? ++ chrono ^?
|% |%
:: +from-unix: unix timestamp to @da :: +from-unix: unix seconds to @da
:: ::
++ from-unix ++ from-unix
|= timestamp=@ud |= timestamp=@ud
^- @da ^- @da
%+ add ~1970.1.1 %+ add ~1970.1.1
(mul timestamp ~s1) (mul timestamp ~s1)
:: +from-unix-ms: unix milliseconds to @da
::
++ from-unix-ms
|= timestamp=@ud
^- @da
%+ add ~1970.1.1
(div (mul ~s1 timestamp) 1.000)
:: :: ++dawn:chrono: :: :: ++dawn:chrono:
++ dawn :: Jan 1 weekday ++ dawn :: Jan 1 weekday
|= yer=@ud |= yer=@ud
@ -5533,9 +5532,13 @@
++ dd :: two digits ++ dd :: two digits
(bass 10 (stun 2^2 dit)) (bass 10 (stun 2^2 dit))
-- :: -- ::
:: :: ++unm:chrono:userlib
++ unm :: Urbit to Unix ms
|= a=@da
(div (mul (sub a ~1970.1.1) 1.000) ~s1)
:: :: ++unt:chrono:userlib :: :: ++unt:chrono:userlib
++ unt :: Urbit to Unix time ++ unt :: Urbit to Unix time
|= a=@ |= a=@da
(div (sub a ~1970.1.1) ~s1) (div (sub a ~1970.1.1) ~s1)
:: :: ++yu:chrono:userlib :: :: ++yu:chrono:userlib
++ yu :: UTC format constants ++ yu :: UTC format constants

View File

@ -74,13 +74,15 @@
=. next-timer ~ =. next-timer ~
=. this =. this
%- emit-aqua-events %- emit-aqua-events
?^ error
:: Should pass through errors to aqua, but doesn't
::
%- (slog leaf+"aqua-behn: timer failed" u.error)
~
:_ ~ :_ ~
^- aqua-event ^- aqua-event
:+ %event who :+ %event who
:- //behn/0v1n.2m9vh [//behn/0v1n.2m9vh [%wake ~]]
?~ error
[%wake ~]
[%crud %fail u.error]
..abet-pe ..abet-pe
-- --
-- --

View File

@ -9,5 +9,5 @@
?~ bem=(de-beam pax) ?~ bem=(de-beam pax)
(strand-fail:strand %path-not-beam >pax< ~) (strand-fail:strand %path-not-beam >pax< ~)
=/ =mars:clay [i i.t]:?>(?=([@ @ ~] s.u.bem) s.u.bem) =/ =mars:clay [i i.t]:?>(?=([@ @ ~] s.u.bem) s.u.bem)
;< =tube:clay bind:m (build-cast:strandio -.u.bem mars) ;< =vase bind:m (build-cast:strandio -.u.bem mars)
(pure:m !>(tube)) (pure:m vase)

View File

@ -0,0 +1,13 @@
/- spider
/+ strandio
=, strand=strand:spider
^- thread:spider
|= arg=vase
=/ m (strand ,vase)
^- form:m
=+ !<([~ pax=path] arg)
?~ bem=(de-beam pax)
(strand-fail:strand %path-not-beam >pax< ~)
=/ =mark (rear s.u.bem)
;< =vase bind:m (build-nave:strandio -.u.bem mark)
(pure:m vase)

View File

@ -0,0 +1,13 @@
/- spider
/+ strandio
=, strand=strand:spider
^- thread:spider
|= arg=vase
=/ m (strand ,vase)
^- form:m
=+ !<([~ pax=path] arg)
?~ bem=(de-beam pax)
(strand-fail:strand %path-not-beam >pax< ~)
=/ =mars:clay [i i.t]:?>(?=([@ @ ~] s.u.bem) s.u.bem)
;< =tube:clay bind:m (build-tube:strandio -.u.bem mars)
(pure:m !>(tube))

View File

@ -0,0 +1,144 @@
:: Gets a Google Storage access token.
::
:: This thread produces a pair of [access-key expires-in], where
:: access-key is a @t that can be used as a bearer token to talk
:: to the GCP Storage API on behalf of some service account, and
:: expires-in is a @dr after which the token will stop working and
:: need to be refreshed.
::
:: It expects settings-store to contain relevant fields from
:: a GCP service account JSON file, generally as poked by
:: sh/poke-gcp-account-json. Specifically, it depends on the
:: `token_uri`, `client_email`, `private_key_id`, and `private_key`
:: fields. If these fields are not in settings-store at the time
:: the thread is run, it will fail.
::
:: The thread works by first constructing a self-signed JWT using
:: the fields in settings-store. Then, it sends this JWT to the
:: specified token URI (usually https://oauth2.googleapis.com/token),
:: which responds with a bearer token and expiry.
::
::
/- gcp, spider, settings
/+ jose, pkcs, primitive-rsa, strandio
=, strand=strand:spider
=, rsa=primitive-rsa
^- thread:spider
|^
|= *
=/ m (strand ,vase)
^- form:m
;< =bowl:spider bind:m get-bowl:strandio
;< iss=@t bind:m (read-setting %client-email)
;< =key:rsa bind:m read-private-key
;< kid=@t bind:m (read-setting %private-key-id)
;< aud=@t bind:m (read-setting %token-uri)
=* scope
'https://www.googleapis.com/auth/devstorage.read_write'
=/ jot=@t
(make-jwt key kid iss scope aud now.bowl)
;< =token:gcp bind:m
(get-access-token jot aud)
(pure:m !>(token))
::
++ read-setting
|= key=term
=/ m (strand @t) ^- form:m
;< has=? bind:m
%+ scry:strandio ?
/gx/settings-store/has-entry/gcp-store/[key]/noun
?. has
(strand-fail:strandio (rap 3 %gcp-missing- key ~) ~)
;< =data:settings bind:m
%+ scry:strandio
data:settings
/gx/settings-store/entry/gcp-store/[key]/settings-data
?> ?=([%entry %s @] data)
(pure:m p.val.data)
::
++ read-private-key
=/ m (strand ,key:rsa) ^- form:m
;< dat=@t bind:m (read-setting %private-key)
%- pure:m
%. dat
;: cork
to-wain:format
ring:de:pem:pkcs8:pkcs
need
==
:: construct and return a self-signed JWT issued now, expiring in ~h1.
:: TODO: maybe move this into lib/jose/hoon
::
++ make-jwt
|= [=key:rsa kid=@t iss=@t scope=@t aud=@t iat=@da]
^- @t
=/ job=json
=, enjs:format
%^ sign:jws:jose key
:: the JWT's "header"
%: pairs
alg+s+'RS256'
typ+s+'JWT'
kid+s+kid
~
==
:: the JWT's "payload"
%: pairs
iss+s+iss
sub+s+iss :: per g.co, use iss for sub
scope+s+scope
aud+s+aud
iat+(sect iat)
exp+(sect (add iat ~h1))
~
==
=/ [pod=@t pad=@t sig=@t]
=, dejs:format
((ot 'protected'^so 'payload'^so 'signature'^so ~) job)
(rap 3 (join '.' `(list @t)`~[pod pad sig]))
:: RPC to get an access token. Probably only works with Google.
:: Described at:
:: https://developers.google.com/identity/protocols/oauth2/service-account
::
++ get-access-token
|= [jot=@t url=@t]
=/ m (strand ,token:gcp) ^- form:m
;< ~ bind:m
%: send-request:strandio
method=%'POST'
url=url
header-list=['Content-Type'^'application/json' ~]
^= body
%- some %- as-octt:mimes:html
%- en-json:html
%: pairs:enjs:format
:- 'grant_type'
s+'urn:ietf:params:oauth:grant-type:jwt-bearer'
assertion+s+jot
~
==
==
;< rep=client-response:iris bind:m
take-client-response:strandio
?> ?=(%finished -.rep)
?~ full-file.rep
(strand-fail:strandio %gcp-no-response ~)
=/ body=@t q.data.u.full-file.rep
=/ jon=(unit json) (de-json:html body)
?~ jon
~| body
(strand-fail:strandio %gcp-bad-body ~)
=* job u.jon
~| job
=, dejs:format
=/ [typ=@t =token:gcp]
%. job
%: ot
'token_type'^so
'access_token'^so
'expires_in'^(cu |=(a=@ (mul a ~s1)) ni)
~
==
?> =('Bearer' typ)
(pure:m token)
--

View File

@ -0,0 +1,49 @@
:: Tells whether GCP Storage appears to be configured.
::
:: Thread since it needs to be called from Landscape.
::
::
/- gcp, spider, settings
/+ strandio
=, strand=strand:spider
=, enjs:format
^- thread:spider
|^
|= *
=/ m (strand ,vase)
^- form:m
;< has=? bind:m
%: has-settings
%client-email
%private-key
%private-key-id
%token-uri
~
==
%- pure:m
!>
%+ frond %gcp-configured
b+has
::
++ has-settings
|= set=(list @tas)
=/ m (strand ?)
^- form:m
?~ set
(pure:m %.y)
;< has=? bind:m (has-setting i.set)
?. has
(pure:m %.n)
;< has=? bind:m (has-settings t.set)
(pure:m has)
::
++ has-setting
|= key=@tas
=/ m (strand ?)
^- form:m
;< has=? bind:m
%+ scry:strandio ?
/gx/settings-store/has-entry/gcp-store/[key]/noun
(pure:m has)
::
--

View File

@ -1,4 +1,5 @@
/- spider, graph-view, graph=graph-store, *metadata-store, *group, *metadata-store /- spider, graph-view, graph=graph-store,
met=metadata-store, *group, *metadata-store
/+ strandio, resource /+ strandio, resource
=> =>
|% |%
@ -28,11 +29,11 @@
:: ::
++ scry-metadatum ++ scry-metadatum
|= rid=resource |= rid=resource
=/ m (strand ,metadata) =/ m (strand ,metadatum:met)
^- form:m ^- form:m
=/ enc-path=@t (scot %t (spat (en-path:resource rid))) =/ enc-path=@t (scot %t (spat (en-path:resource rid)))
;< umeta=(unit metadata) bind:m ;< umeta=(unit metadatum:met) bind:m
%+ scry:strandio (unit metadata) %+ scry:strandio (unit metadatum:met)
%+ weld /gx/metadata-store/metadata %+ weld /gx/metadata-store/metadata
/[enc-path]/graph/[enc-path]/noun /[enc-path]/graph/[enc-path]/noun
?> ?=(^ umeta) ?> ?=(^ umeta)
@ -48,24 +49,25 @@
;< =group bind:m (scry-group rid.action) ;< =group bind:m (scry-group rid.action)
?. hidden.group ?. hidden.group
(strand-fail:strandio %bad-request ~) (strand-fail:strandio %bad-request ~)
;< =metadata bind:m (scry-metadatum rid.action) ;< =metadatum:met bind:m (scry-metadatum rid.action)
?~ to.action ?~ to.action
;< ~ bind:m ;< ~ bind:m
%+ poke-our %contact-view %+ poke-our %contact-view
:- %contact-view-action :- %contact-view-action
!>([%groupify rid.action title.metadata description.metadata]) !>([%groupify rid.action title.metadatum description.metadatum])
(pure:m !>(~)) (pure:m !>(~))
;< new=^group bind:m (scry-group u.to.action) ;< new=^group bind:m (scry-group u.to.action)
?< hidden.new ?< hidden.new
=/ new-path (en-path:resource u.to.action)
=/ app-path (en-path:resource rid.action)
=/ add-md=metadata-action
[%add new-path graph+app-path metadata]
;< ~ bind:m
(poke-our %metadata-store metadata-action+!>(add-md))
;< ~ bind:m ;< ~ bind:m
%+ poke-our %metadata-store %+ poke-our %metadata-store
metadata-action+!>([%remove app-path graph+app-path]) :- %metadata-action
!> ^- action:met
[%add u.to.action [%graph rid.action] metadatum]
;< ~ bind:m
%+ poke-our %metadata-store
:- %metadata-action
!> ^- action:met
[%remove rid.action [%graph rid.action]]
;< ~ bind:m ;< ~ bind:m
(poke-our %group-store %group-update !>([%remove-group rid.action ~])) (poke-our %group-store %group-update !>([%remove-group rid.action ~]))
(pure:m !>(~)) (pure:m !>(~))

View File

@ -1,4 +1,4 @@
/- spider, graph=graph-store, *metadata-store, *group, group-store /- spider, graph=graph-store, met=metadata-store, *group, group-store, push-hook
/+ strandio, resource, graph-view /+ strandio, resource, graph-view
=> =>
|% |%
@ -23,17 +23,22 @@
:: ::
:: Setup metadata :: Setup metadata
:: ::
=/ =metadata =/ =metadatum:met
%* . *metadata %* . *metadatum:met
title title title title
description description description description
date-created now.bowl date-created now.bowl
creator our.bowl creator our.bowl
module module module module
== ==
=/ act=metadata-action
[%add (en-path:resource group) graph+(en-path:resource rid) metadata]
;< ~ bind:m (poke-our %metadata-hook %metadata-action !>(act))
;< ~ bind:m ;< ~ bind:m
(poke-our %metadata-hook %metadata-hook-action !>([%add-owned (en-path:resource group)])) %+ poke-our %metadata-push-hook
:- %metadata-action
!> ^- action:met
[%add group [%graph rid] metadatum]
;< ~ bind:m
%+ poke-our %metadata-push-hook
:- %push-hook-action
!> ^- action:push-hook
[%add group]
(pure:m !>(~)) (pure:m !>(~))

View File

@ -1,84 +0,0 @@
/- spider, *metadata-store
/+ strandio
=, strand=strand:spider
^- thread:spider
|= arg=vase
=/ m (strand ,vase)
^- form:m
=/ [~ og-path=path ng-path=path] !<([~ path path] arg)
;< bol=bowl:spider bind:m get-bowl:strandio
|^
::
=/ og=(unit (set ship)) (scry-for (unit (set ship)) %group-store og-path)
?~ og
(pure:m !>("no such group: {<og-path>}"))
=/ ng=(unit (set ship)) (scry-for (unit (set ship)) %group-store ng-path)
?~ ng
(pure:m !>("no such group: {<ng-path>}"))
::
=/ assoc=associations (scry-for associations %metadata-store [%group og-path])
=/ assoc-list=(list [[group-path md-resource] metadata]) ~(tap by assoc)
::
|-
=* loop $
?~ assoc-list
;< ~ bind:m
(poke-our:strandio %group-store %group-action !>([%unbundle og-path]))
(pure:m !>("done"))
=/ [[g-path=group-path res=md-resource] meta=metadata] i.assoc-list
?. =(our.bol creator.meta)
loop(assoc-list t.assoc-list)
?> =(g-path og-path)
=/ output=(list card:agent:gall)
?+ app-name.res ~
::
?(%chat %link)
%- (slog leaf+"migrating {<app-name.res>} : {<app-path.res>}" ~)
:~ :* %pass /poke %agent
[our.bol %metadata-hook]
%poke %metadata-action
!>([%add ng-path res meta])
==
:* %pass /poke %agent
[our.bol %metadata-hook]
%poke %metadata-action
!>([%remove g-path res])
==
==
%publish
%- (slog leaf+"migrating {<app-name.res>} : {<app-path.res>}" ~)
=/ book (scry-for notebook %publish [%book app-path.res])
?> ?=([@tas @tas ~] app-path.res)
:~ :* %pass /poke %agent
[our.bol %publish]
%poke %publish-action
!>
:* %edit-book
i.t.app-path.res
title.book
description.book
comments.book
`[ng-path ~ %.y %.n]
==
==
:* %pass /poke %agent
[our.bol %metadata-hook]
%poke %metadata-action
!>([%remove g-path res])
==
==
==
::
;< ~ bind:m (send-raw-cards:strandio output)
loop(assoc-list t.assoc-list)
::
++ scry-for
|* [mol=mold app=term pax=path]
.^ mol
%gx
(scot %p our.bol)
app
(scot %da now.bol)
(snoc `path`pax %noun)
==
--

View File

@ -1,46 +0,0 @@
/- spider,
contact-view,
*resource
/+ *ph-io, strandio
=, strand=strand:spider
::
^- thread:spider
|= vase
=/ m (strand ,vase)
;< ~ bind:m start-simple
;< bol=bowl:spider bind:m get-bowl:strandio
::
:: group setup
:: - ~zod creates an open group
:: - ~zod creates and invite-only group, and invites ~bus and ~web
:: - ~bus and ~web join the first, but not the second group, to keep
:: invite-store populated
::
=/ group-1=contact-view-action:contact-view
:* %create
%group-1
[%open ~ ~]
'Group 1'
'this is group 1'
==
=/ group-2=contact-view-action:contact-view
:* %create
%group-2
[%invite (sy ~bus ~web ~)]
'Group 2'
'this is group 2'
==
=/ join=contact-view-action:contact-view [%join ~zod %group-1]
;< ~ bind:m (poke-app ~zod %contact-view %contact-view-action group-1)
;< ~ bind:m (wait-for-output ~zod ">=")
;< ~ bind:m (poke-app ~zod %contact-view %contact-view-action group-2)
;< ~ bind:m (wait-for-output ~zod ">=")
;< ~ bind:m (sleep ~s10)
;< ~ bind:m (poke-app ~bus %contact-view %contact-view-action join)
;< ~ bind:m (wait-for-output ~bus ">=")
;< ~ bind:m (poke-app ~web %contact-view %contact-view-action join)
;< ~ bind:m (wait-for-output ~web ">=")
;< ~ bind:m (send-hi ~bus ~zod)
;< ~ bind:m (send-hi ~web ~zod)
;< ~ bind:m (sleep ~s2)
(pure:m *vase)

View File

@ -1,24 +0,0 @@
/- spider,
contact-view,
*resource,
group-store
/+ *ph-io, strandio
=, strand=strand:spider
::
^- thread:spider
|= vase
=/ m (strand ,vase)
;< ~ bind:m start-simple
;< bol=bowl:spider bind:m get-bowl:strandio
::
=/ join-2=contact-view-action:contact-view [%join ~zod %group-2]
=/ add-members-1=action:group-store
[%add-members [~zod %group-1] (sy ~def ~ten ~)]
=/ add-members-2=action:group-store
[%add-members [~zod %group-2] (sy ~def ~ten ~)]
;< ~ bind:m (poke-app ~bus %contact-view %contact-view-action join-2)
;< ~ bind:m (poke-app ~web %contact-view %contact-view-action join-2)
;< ~ bind:m (poke-app ~zod %group-store %group-action add-members-1)
;< ~ bind:m (poke-app ~zod %group-store %group-action add-members-2)
::
(pure:m *vase)

View File

@ -1,61 +0,0 @@
/- spider,
contact-view,
contact-store,
group-store,
metadata-store,
post,
graph-store,
*resource
/+ *ph-io, strandio
=, strand=strand:spider
::
::
^- thread:spider
|= vase
=/ m (strand ,vase)
;< ~ bind:m start-simple
;< bol=bowl:spider bind:m get-bowl:strandio
::
:: test metadata import
::
=/ change-group-1=metadata-action:metadata-store
:* %add
/ship/~zod/group-1
[%contacts /ship/~zod/group-1]
'New Group 1 Title'
'new description'
0x0
now.bol
~zod
'fake'
==
=/ change-web-book=metadata-action:metadata-store
:* %add
/ship/~web/graph-3
[%graph /ship/~web/graph-3]
'New Graph 3 Title'
'new description'
0x0
now.bol
~web
'fake'
==
;< ~ bind:m (poke-app ~zod %metadata-hook %metadata-action change-group-1)
;< ~ bind:m (sleep ~s5)
;< ~ bind:m (poke-app ~web %metadata-hook %metadata-action change-web-book)
;< ~ bind:m (sleep ~s5)
::
:: test contacts import
::
=/ add-zod=contact-action:contact-store
:* %add /ship/~zod/group-1 ~zod
'ZOD' '' '' '' '' 0x0 ~
==
=/ add-bus=contact-action:contact-store
:* %add /ship/~zod/group-2 ~bus
'BUS' '' '' '' '' 0x0 ~
==
;< ~ bind:m (poke-app ~zod %contact-hook %contact-action add-zod)
;< ~ bind:m (sleep ~s5)
;< ~ bind:m (poke-app ~bus %contact-hook %contact-action add-bus)
(pure:m *vase)

View File

@ -1,9 +1,15 @@
/+ *test /+ *test
/= clay-raw /sys/vane/clay /= clay-raw /sys/vane/clay
/* hello-gen %hoon /gen/hello/hoon /* gen-hello %hoon /gen/hello/hoon
/* strandio-lib %hoon /lib/strandio/hoon /* lib-cram %hoon /lib/cram/hoon
/* strand-lib %hoon /lib/strand/hoon /* lib-strandio %hoon /lib/strandio/hoon
/* spider-sur %hoon /sur/spider/hoon /* lib-strand %hoon /lib/strand/hoon
/* sur-spider %hoon /sur/spider/hoon
/* mar-html %hoon /mar/html/hoon
/* mar-mime %hoon /mar/mime/hoon
/* mar-udon %hoon /mar/udon/hoon
/* mar-txt %hoon /mar/txt/hoon
/* mar-txt-diff %hoon /mar/txt-diff/hoon
:: ::
!: !:
=, format =, format
@ -14,32 +20,48 @@
:: ::
|% |%
++ test-parse-pile ^- tang ++ test-parse-pile ^- tang
=/ src "."
%+ expect-eq %+ expect-eq
!> ^- pile:fusion !> ^- pile:fusion
:* ~ ~ ~ ~ :* ~ ~ ~ ~ ~ ~
tssg+[%dbug [/sur/foo/hoon [[1 1] [1 2]]] [%cnts ~[[%.y 1]] ~]]~ tssg+[%dbug [/sur/foo/hoon [[1 1] [1 2]]] [%cnts ~[[%.y 1]] ~]]~
== ==
!> (parse-pile:(ford):fusion /sur/foo/hoon ".") !> (parse-pile:(ford):fusion /sur/foo/hoon src)
::
++ test-parse-fascen ^- tang
=/ src "/% moo %mime\0a."
%+ expect-eq
!> ^- pile:fusion
:* sur=~ lib=~ raw=~
maz=[face=%moo mark=%mime]~
caz=~ bar=~
tssg+[%dbug [/sur/foo/hoon [[2 1] [2 2]]] [%cnts ~[[%.y 1]] ~]]~
==
!> (parse-pile:(ford):fusion /sur/foo/hoon src)
::
++ test-parse-fasbuc ^- tang
=/ src "/$ goo %mime %txt\0a."
%+ expect-eq
!> ^- pile:fusion
:* sur=~ lib=~ raw=~ maz=~
caz=[face=%goo from=%mime to=%txt]~
bar=~
tssg+[%dbug [/sur/foo/hoon [[2 1] [2 2]]] [%cnts ~[[%.y 1]] ~]]~
==
!> (parse-pile:(ford):fusion /sur/foo/hoon src)
:: ::
++ test-parse-multiline-faslus ^- tang ++ test-parse-multiline-faslus ^- tang
=/ src =/ src
""" """
:: :: ::
:::: /hoon/hood/app :: ::
:: :: ::
/? 310 :: zuse version
/- *sole
/+ sole :: libraries
:: XX these should really be separate apps, as
:: none of them interact with each other in
:: any fashion; however, to reduce boot-time
:: complexity and work around the current
:: non-functionality of end-to-end acknowledgments,
:: they have been bundled into :hood
:: ::
:: |command handlers /? 310 :: zuse version
::
/- *sole
::
/+ sole :: libraries
::
/+ hood-helm, hood-kiln, hood-drum, hood-write /+ hood-helm, hood-kiln, hood-drum, hood-write
:: :: :: ::
. .
""" """
%+ expect-eq %+ expect-eq
@ -52,34 +74,21 @@
[`%hood-drum %hood-drum] [`%hood-drum %hood-drum]
[`%hood-write %hood-write] [`%hood-write %hood-write]
== ==
raw=~ bar=~ raw=~ maz=~ caz=~ bar=~
hoon=tssg+[p:(need q:(tall:(vang & /app/hood/hoon) [17 1] "."))]~ tssg+[%dbug [/sur/foo/hoon [[10 1] [10 2]]] [%cnts ~[[%.y 1]] ~]]~
== ==
!> (parse-pile:(ford):fusion /app/hood/hoon src) !> (parse-pile:(ford):fusion /sur/foo/hoon src)
:: ::
++ test-cycle ^- tang ++ test-cycle ^- tang
=/ source=@t =/ source=@t '/+ self\0a.'
'''
/+ self
.
'''
=/ =ankh:clay
:- fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %lib fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %self fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %hoon fil=`[*lobe:clay hoon+!>(source)] dir=~
== == ==
%- expect-fail %- expect-fail
|. |.
=/ ford =/ ford
%: ford:fusion %: ford:fusion
bud bud
ankh *ankh:clay
deletes=~ deletes=~
changes=~ changes=(my [/lib/self/hoon &+hoon+source]~)
file-store=~ file-store=~
*ford-cache:fusion *ford-cache:fusion
== ==
@ -89,22 +98,122 @@
%- expect-fail %- expect-fail
|. (parse-pile:(ford):fusion /sur/foo/hoon "[") |. (parse-pile:(ford):fusion /sur/foo/hoon "[")
:: ::
++ test-hello-gen ^- tang ++ test-mar-mime ^- tang
=/ =ankh:clay
:- fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %gen fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %hello fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %hoon fil=`[*lobe:clay hoon+!>(hello-gen)] dir=~
== == ==
=/ ford =/ ford
%: ford:fusion %: ford:fusion
bud bud
ankh *ankh:clay
deletes=~ deletes=~
changes=(my [/gen/hello/hoon &+hoon+hello-gen]~) changes=(my [/mar/mime/hoon &+hoon+mar-mime]~)
file-store=~
*ford-cache:fusion
==
=/ [res=vase nub=state:ford:fusion] (build-nave:ford %mime)
;: weld
%+ expect-eq
!>(*mime)
(slap res limb/%bunt)
::
%+ expect-eq
!> (~(gas in *(set path)) /mar/mime/hoon ~)
!> dez:(~(got by files.cache.nub) /mar/mime/hoon)
==
::
++ test-mar-udon ^- tang
=/ ford
%: ford:fusion
bud
*ankh:clay
deletes=~
^= changes
%- my
:~ [/mar/udon/hoon &+hoon+mar-udon]
[/lib/cram/hoon &+hoon+lib-cram]
[/mar/txt/hoon &+hoon+mar-txt]
[/mar/txt-diff/hoon &+hoon+mar-txt-diff]
==
file-store=~
*ford-cache:fusion
==
=/ [res=vase nub=state:ford:fusion] (build-nave:ford %udon)
;: weld
%+ expect-eq
!>(*@t)
(slap res limb/%bunt)
::
%+ expect-eq
!> (~(gas in *(set path)) /mar/udon/hoon /lib/cram/hoon ~)
!> dez:(~(got by files.cache.nub) /mar/udon/hoon)
==
::
++ test-cast-html-mime ^- tang
=/ changes
%- my
:~ [/mar/mime/hoon &+hoon+mar-mime]
[/mar/html/hoon &+hoon+mar-html]
==
=/ ford
%: ford:fusion
bud
*ankh:clay
deletes=~
changes
file-store=~
*ford-cache:fusion
==
=/ [res=vase nub=state:ford:fusion] (build-cast:ford %html %mime)
%+ expect-eq
(slam res !>('<html></html>'))
!> `mime`[/text/html 13 '<html></html>']
::
++ test-fascen ^- tang
=/ changes
%- my
:~ [/mar/mime/hoon &+hoon+mar-mime]
[/lib/foo/hoon &+hoon+'/% moo %mime\0abunt:moo']
==
=/ ford
%: ford:fusion
bud
*ankh:clay
deletes=~
changes
file-store=~
*ford-cache:fusion
==
=/ [res=vase nub=state:ford:fusion] (build-file:ford /lib/foo/hoon)
%+ expect-eq
res
!> *mime
::
++ test-fasbuc ^- tang
=/ changes
%- my
:~ [/mar/mime/hoon &+hoon+mar-mime]
[/mar/html/hoon &+hoon+mar-html]
[/lib/foo/hoon &+hoon+'/$ foo %mime %html\0a*foo']
==
=/ ford
%: ford:fusion
bud
*ankh:clay
deletes=~
changes
file-store=~
*ford-cache:fusion
==
=/ [res=vase nub=state:ford:fusion] (build-file:ford /lib/foo/hoon)
%+ expect-eq
res
!> ''
::
++ test-gen-hello ^- tang
=/ ford
%: ford:fusion
bud
*ankh:clay
deletes=~
changes=(my [/gen/hello/hoon &+hoon+gen-hello]~)
file-store=~ file-store=~
*ford-cache:fusion *ford-cache:fusion
== ==
@ -116,37 +225,21 @@
:: ::
%+ expect-eq %+ expect-eq
!> (~(gas in *(set path)) /gen/hello/hoon ~) !> (~(gas in *(set path)) /gen/hello/hoon ~)
!> dez:(~(got by vases.cache.nub) /gen/hello/hoon) !> dez:(~(got by files.cache.nub) /gen/hello/hoon)
== ==
:: ::
++ test-strandio-lib ^- tang ++ test-lib-strandio ^- tang
=/ =ankh:clay
:- fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %lib fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %strandio fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %hoon fil=`[*lobe:clay hoon+!>(strandio-lib)] dir=~
==
::
:+ %strand fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %hoon fil=`[*lobe:clay hoon+!>(strand-lib)] dir=~
== ==
::
:+ %sur fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %spider fil=~
%- ~(gas by *(map @tas ankh:clay))
:~ :+ %hoon fil=`[*lobe:clay hoon+!>(spider-sur)] dir=~
== == ==
=/ ford =/ ford
%: ford:fusion %: ford:fusion
bud bud
ankh *ankh:clay
deletes=~ deletes=~
changes=~ ^= changes
%- my
:~ [/lib/strand/hoon &+hoon+lib-strand]
[/lib/strandio/hoon &+hoon+lib-strandio]
[/sur/spider/hoon &+hoon+sur-spider]
==
file-store=~ file-store=~
*ford-cache:fusion *ford-cache:fusion
== ==
@ -161,7 +254,7 @@
/lib/strand/hoon /lib/strand/hoon
/sur/spider/hoon /sur/spider/hoon
== ==
!> dez:(~(got by vases.cache.nub) /lib/strandio/hoon) !> dez:(~(got by files.cache.nub) /lib/strandio/hoon)
== ==
:: ::
:: |utilities: helper functions for testing :: |utilities: helper functions for testing

View File

@ -2360,7 +2360,6 @@
++ mash !! ++ mash !!
++ pact !! ++ pact !!
++ vale |=(=noun !>(;;(json noun))) ++ vale |=(=noun !>(;;(json noun)))
++ volt !!
-- --
:: ::
?> =(%j view) ?> =(%j view)

View File

@ -170,6 +170,10 @@
%+ expect-eq %+ expect-eq
!> [%o (molt props)] !> [%o (molt props)]
!> (pairs props) !> (pairs props)
:: sect - stored as integer number of seconds since the unix epoch
%+ expect-eq
!> [%n '1']
!> (sect ~1970.1.1..0.0.1)
:: time - stored as integer number of milliseconds since the unix epoch :: time - stored as integer number of milliseconds since the unix epoch
:: ::
%+ expect-eq %+ expect-eq

View File

@ -129,6 +129,30 @@
!> -.b !> -.b
== ==
:: ::
++ test-ordered-map-traverse-delete-all ^- tang
;: weld
=/ q ((ordered-map ,@ ,~) lte)
=/ o (gas:q ~ ~[1/~ 2/~ 3/~])
=/ b ((traverse:q ,~) o ~ |=([~ key=@ ~] [~ %| ~]))
%+ expect-eq
!> [~ ~]
!> b
::
=/ c
:~ [[2.127 1] ~] [[2.127 2] ~] [[2.127 3] ~]
[[2.127 7] ~] [[2.127 8] ~] [[2.127 9] ~]
==
=/ compare
|= [[aa=@ ab=@] [ba=@ bb=@]]
?:((lth aa ba) %.y ?:((gth aa ba) %.n (lte ab bb)))
=/ q ((ordered-map ,[@ @] ,~) compare)
=/ o (gas:q ~ c)
=/ b ((traverse:q ,~) o ~ |=([~ key=[@ @] ~] [~ %| ~]))
%+ expect-eq
!> [~ ~]
!> b
==
::
++ test-ordered-map-uni ^- tang ++ test-ordered-map-uni ^- tang
:: ::
=/ a=(tree [@ud @tas]) (gas:atom-map ~ (scag 4 test-items)) =/ a=(tree [@ud @tas]) (gas:atom-map ~ (scag 4 test-items))

View File

@ -2264,6 +2264,13 @@
"url": "0.10.3", "url": "0.10.3",
"uuid": "3.3.2", "uuid": "3.3.2",
"xml2js": "0.4.19" "xml2js": "0.4.19"
},
"dependencies": {
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
}
} }
}, },
"babel-eslint": { "babel-eslint": {
@ -6692,6 +6699,14 @@
"requires": { "requires": {
"punycode": "1.3.2", "punycode": "1.3.2",
"querystring": "0.2.0" "querystring": "0.2.0"
},
"dependencies": {
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"dev": true
}
} }
} }
} }
@ -7443,9 +7458,9 @@
"dev": true "dev": true
}, },
"querystring": { "querystring": {
"version": "0.2.0", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg=="
}, },
"querystring-es3": { "querystring-es3": {
"version": "0.2.1", "version": "0.2.1",
@ -9620,6 +9635,13 @@
"requires": { "requires": {
"punycode": "1.3.2", "punycode": "1.3.2",
"querystring": "0.2.0" "querystring": "0.2.0"
},
"dependencies": {
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
}
} }
}, },
"url-parse": { "url-parse": {
@ -10378,6 +10400,14 @@
"requires": { "requires": {
"punycode": "1.3.2", "punycode": "1.3.2",
"querystring": "0.2.0" "querystring": "0.2.0"
},
"dependencies": {
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"dev": true
}
} }
} }
} }

View File

@ -29,6 +29,7 @@
"normalize-wheel": "1.0.1", "normalize-wheel": "1.0.1",
"oembed-parser": "^1.4.5", "oembed-parser": "^1.4.5",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"querystring": "^0.2.0",
"react": "^16.14.0", "react": "^16.14.0",
"react-codemirror2": "^6.0.1", "react-codemirror2": "^6.0.1",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",

View File

@ -0,0 +1,24 @@
import BaseApi from './base';
import {StoreState} from '../store/type';
import {GcpToken} from '../types/gcp-state';
export default class GcpApi extends BaseApi<StoreState> {
isConfigured() {
return this.spider('noun', 'json', 'gcp-is-configured', {})
.then((data) => {
this.store.handleEvent({
data
});
});
}
getToken() {
return this.spider('noun', 'gcp-token', 'gcp-get-token', {})
.then((token) => {
this.store.handleEvent({
data: token
});
});
}
};

View File

@ -10,6 +10,7 @@ import GroupsApi from './groups';
import LaunchApi from './launch'; import LaunchApi from './launch';
import GraphApi from './graph'; import GraphApi from './graph';
import S3Api from './s3'; import S3Api from './s3';
import GcpApi from './gcp';
import { HarkApi } from './hark'; import { HarkApi } from './hark';
import SettingsApi from './settings'; import SettingsApi from './settings';
@ -20,6 +21,7 @@ export default class GlobalApi extends BaseApi<StoreState> {
contacts = new ContactsApi(this.ship, this.channel, this.store); contacts = new ContactsApi(this.ship, this.channel, this.store);
groups = new GroupsApi(this.ship, this.channel, this.store); groups = new GroupsApi(this.ship, this.channel, this.store);
launch = new LaunchApi(this.ship, this.channel, this.store); launch = new LaunchApi(this.ship, this.channel, this.store);
gcp = new GcpApi(this.ship, this.channel, this.store);
s3 = new S3Api(this.ship, this.channel, this.store); s3 = new S3Api(this.ship, this.channel, this.store);
graph = new GraphApi(this.ship, this.channel, this.store); graph = new GraphApi(this.ship, this.channel, this.store);
hark = new HarkApi(this.ship, this.channel, this.store); hark = new HarkApi(this.ship, this.channel, this.store);

View File

@ -0,0 +1,67 @@
// Very simple GCP Storage client.
//
// It's designed to match a subset of the S3 client upload API. The upload
// function on S3 returns a ManagedUpload, which has a promise() method on
// it. We don't care about any of the other methods on ManagedUpload, so we
// just do the work in its promise() method.
//
import querystring from 'querystring';
import {
StorageAcl,
StorageClient,
StorageUpload,
UploadParams,
UploadResult
} from './StorageClient';
const ENDPOINT = 'storage.googleapis.com';
class GcpUpload implements StorageUpload {
#params: UploadParams;
#accessKey: string;
constructor(params: UploadParams, accessKey: string) {
this.#params = params;
this.#accessKey = accessKey;
}
async promise(): UploadResult {
const {Bucket, Key, ContentType, Body} = this.#params;
const urlParams = {
uploadType: 'media',
name: Key,
predefinedAcl: 'publicRead'
};
const url = `https://${ENDPOINT}/upload/storage/v1/b/${Bucket}/o?` +
querystring.stringify(urlParams);
const headers = new Headers();
headers.append('Authorization', `Bearer ${this.#accessKey}`);
headers.append('Content-Type', ContentType);
const response = await fetch(url, {
method: 'POST',
mode: 'cors',
cache: 'default',
headers,
referrerPolicy: 'no-referrer',
body: Body
});
if (!response.ok) {
console.error('GcpClient server error', await response.json());
throw new Error(`GcpClient: response ${response.status}`);
}
return {Location: `https://${ENDPOINT}/${Bucket}/${Key}`};
}
}
export default class GcpClient implements StorageClient {
#accessKey: string;
constructor(accessKey: string) {
this.#accessKey = accessKey;
}
upload(params: UploadParams): StorageUpload {
return new GcpUpload(params, this.#accessKey);
}
}

View File

@ -0,0 +1,32 @@
// Defines a StorageClient interface interoperable between S3 and GCP Storage.
//
// XX kind of gross. S3 needs 'public-read', GCP needs 'publicRead'.
// Rather than write a wrapper around S3, we offer this field here, which
// should always be passed, and will be replaced by 'publicRead' in the
// GCP client.
export enum StorageAcl {
PublicRead = 'public-read'
};
export interface UploadParams {
Bucket: string; // the bucket to upload the object to
Key: string; // the desired location within the bucket
ContentType: string; // the object's mime-type
ACL: StorageAcl; // ACL, always 'public-read'
Body: File; // the object itself
};
export interface UploadResult {
Location: string;
};
// Extra layer of indirection used by S3 client.
export interface StorageUpload {
promise(): Promise<UploadResult>;
};
export interface StorageClient {
upload(params: UploadParams): StorageUpload;
};

View File

@ -0,0 +1,142 @@
// Singleton that manages GCP token state.
//
// To use:
//
// 1. call configure with a GlobalApi and GlobalStore.
// 2. call start() to start the token refresh loop.
//
// If the ship does not have GCP storage configured, we don't try to get
// a token, but we keep checking at regular intervals to see if it gets
// configured. If GCP storage is configured, we try to invoke the GCP
// get-token thread on the ship until it gives us an access token. Once
// we have a token, we refresh it every hour or so according to its
// intrinsic expiry.
//
//
import GlobalApi from '../api/global';
import GlobalStore from '../store/store';
class GcpManager {
#api: GlobalApi | null = null;
#store: GlobalStore | null = null;
configure(api: GlobalApi, store: GlobalStore) {
this.#api = api;
this.#store = store;
}
#running = false;
#timeoutId: number | null = null;
start() {
if (this.#running) {
console.warn('GcpManager already running');
return;
}
if (!this.#api || !this.#store) {
console.error('GcpManager must have api and store set');
return;
}
this.#running = true;
this.refreshLoop();
}
stop() {
if (!this.#running) {
console.warn('GcpManager already stopped');
console.assert(this.#timeoutId === null);
return;
}
this.#running = false;
if (this.#timeoutId !== null) {
clearTimeout(this.#timeoutId);
this.#timeoutId = null;
}
}
restart() {
if (this.#running) {
this.stop();
}
this.start();
}
#consecutiveFailures: number = 0;
private isConfigured() {
return this.#store.state.storage.gcp.configured;
}
private refreshLoop() {
if (!this.isConfigured()) {
this.#api.gcp.isConfigured()
.then(() => {
if (this.isConfigured() === undefined) {
throw new Error("can't check whether GCP is configured?");
}
if (this.isConfigured()) {
this.refreshLoop();
} else {
this.refreshAfter(10_000);
}
})
.catch((reason) => {
console.error('GcpManager failure; stopping.', reason);
this.stop();
});
return;
}
this.#api.gcp.getToken()
.then(() => {
const token = this.#store.state.storage.gcp?.token;
if (token) {
this.#consecutiveFailures = 0;
const interval = this.refreshInterval(token.expiresIn);
console.log('GcpManager got token; refreshing after', interval);
this.refreshAfter(interval);
} else {
throw new Error('thread succeeded, but returned no token?');
}
})
.catch((reason) => {
this.#consecutiveFailures++;
console.warn('GcpManager token refresh failed; retrying with backoff');
this.refreshAfter(this.backoffInterval());
});
}
private refreshAfter(durationMs) {
if (!this.#running) {
return;
}
if (this.#timeoutId !== null) {
console.warn('GcpManager already has a timeout set');
return;
}
this.#timeoutId = setTimeout(() => {
this.#timeoutId = null;
this.refreshLoop();
}, durationMs);
}
private refreshInterval(expiresIn: number) {
// Give ourselves a minute for processing delays, but never refresh sooner
// than 30 minutes from now. (The expiry window should be about an hour.)
return Math.max(30 * 60_000, expiresIn - 60_000);
}
private backoffInterval() {
// exponential backoff.
const slotMs = 5_000;
const maxSlot = 60; // 5 minutes
const backoffSlots =
Math.floor(Math.random() * Math.min(maxSlot, this.#consecutiveFailures));
return slotMs * backoffSlots;
}
}
const instance = new GcpManager();
Object.freeze(instance);
export default instance;

View File

@ -1,41 +0,0 @@
import S3 from 'aws-sdk/clients/s3';
export default class S3Client {
constructor() {
this.s3 = null;
this.endpoint = '';
this.accessKeyId = '';
this.secretAccesskey = '';
}
setCredentials(endpoint, accessKeyId, secretAccessKey) {
this.endpoint = endpoint;
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
this.s3 = new S3({
endpoint: endpoint,
credentials: {
accessKeyId: this.accessKeyId,
secretAccessKey: this.secretAccessKey
}
});
}
async upload(bucket, filename, buffer) {
const params = {
Bucket: bucket,
Key: filename,
Body: buffer,
ACL: 'public-read',
ContentType: buffer.type
};
if(!this.s3) {
throw new Error('S3 not initialized');
}
return this.s3.upload(params).promise();
}
}

View File

@ -1,9 +1,16 @@
import { useCallback, useMemo, useEffect, useRef, useState } from 'react'; import { useCallback, useMemo, useEffect, useRef, useState } from 'react';
import { S3State } from '../../types/s3-update'; import {
GcpState,
S3State,
StorageState
} from '../../types';
import S3 from 'aws-sdk/clients/s3'; import S3 from 'aws-sdk/clients/s3';
import GcpClient from './GcpClient';
import { StorageClient, StorageAcl } from './StorageClient';
import { dateToDa, deSig } from './util'; import { dateToDa, deSig } from './util';
export interface IuseS3 {
export interface IuseStorage {
canUpload: boolean; canUpload: boolean;
upload: (file: File, bucket: string) => Promise<string>; upload: (file: File, bucket: string) => Promise<string>;
uploadDefault: (file: File) => Promise<string>; uploadDefault: (file: File) => Promise<string>;
@ -11,31 +18,43 @@ export interface IuseS3 {
promptUpload: () => Promise<string | undefined>; promptUpload: () => Promise<string | undefined>;
} }
const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => { const useStorage = ({gcp, s3}: StorageState,
{ accept = '*' } = { accept: '*' }): IuseStorage => {
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const client = useRef<S3 | null>(null); const client = useRef<StorageClient | null>(null);
useEffect(() => { useEffect(() => {
if (!s3.credentials) { // prefer GCP if available, else use S3.
if (gcp.token !== undefined) {
client.current = new GcpClient(gcp.token.accessKey);
} else {
// XX ships currently always have S3 credentials, but the fields are all
// set to '' if they are not configured.
if (!s3.credentials ||
!s3.credentials.accessKeyId ||
!s3.credentials.secretAccessKey) {
return; return;
} }
client.current = new S3({ client.current = new S3({
credentials: s3.credentials, credentials: s3.credentials,
endpoint: s3.credentials.endpoint endpoint: s3.credentials.endpoint
}); });
}, [s3.credentials]); }
}, [gcp.token, s3.credentials]);
const canUpload = useMemo( const canUpload = useMemo(
() => () =>
(client && s3.credentials && s3.configuration.currentBucket !== '') || false, ((gcp.token || (s3.credentials && s3.credentials.accessKeyId &&
[s3.credentials, s3.configuration.currentBucket, client] s3.credentials.secretAccessKey)) &&
s3.configuration.currentBucket !== '') || false,
[s3.credentials, gcp.token, s3.configuration.currentBucket]
); );
const upload = useCallback( const upload = useCallback(
async (file: File, bucket: string) => { async (file: File, bucket: string) => {
if (!client.current) { if (client.current === null) {
throw new Error('S3 not ready'); throw new Error('Storage not ready');
} }
const fileParts = file.name.split('.'); const fileParts = file.name.split('.');
@ -47,7 +66,7 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
Bucket: bucket, Bucket: bucket,
Key: `${window.ship}/${timestamp}-${fileName}.${fileExtension}`, Key: `${window.ship}/${timestamp}-${fileName}.${fileExtension}`,
Body: file, Body: file,
ACL: 'public-read', ACL: StorageAcl.PublicRead,
ContentType: file.type ContentType: file.type
}; };
@ -67,7 +86,7 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
throw new Error('current bucket not set'); throw new Error('current bucket not set');
} }
return upload(file, s3.configuration.currentBucket); return upload(file, s3.configuration.currentBucket);
}, [s3]); }, [s3, upload]);
const promptUpload = useCallback( const promptUpload = useCallback(
() => { () => {
@ -92,7 +111,7 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
[uploadDefault] [uploadDefault]
); );
return { canUpload, upload, uploadDefault, uploading, promptUpload }; return {canUpload, upload, uploadDefault, uploading, promptUpload};
}; };
export default useS3; export default useStorage;

View File

@ -0,0 +1,37 @@
import _ from 'lodash';
import {StoreState} from '../store/type';
import {GcpToken} from '../../types/gcp-state';
type GcpState = Pick<StoreState, 'gcp'>;
export default class GcpReducer<S extends GcpState>{
reduce(json: Cage, state: S) {
this.reduceConfigured(json, state);
this.reduceToken(json, state);
}
reduceConfigured(json, state) {
let data = json['gcp-configured'];
if (data !== undefined) {
state.storage.gcp.configured = data;
}
}
reduceToken(json: Cage, state: S) {
let data = json['gcp-token'];
if (data) {
this.setToken(data, state);
}
}
setToken(data: any, state: S) {
if (this.isToken(data)) {
state.storage.gcp.token = data;
}
}
isToken(token: any): token is GcpToken {
return (typeof(token.accessKey) === 'string' &&
typeof(token.expiresIn) === 'number');
}
}

View File

@ -23,14 +23,14 @@ export default class S3Reducer<S extends S3State> {
credentials(json: S3Update, state: S) { credentials(json: S3Update, state: S) {
const data = _.get(json, 'credentials', false); const data = _.get(json, 'credentials', false);
if (data) { if (data) {
state.s3.credentials = data; state.storage.s3.credentials = data;
} }
} }
configuration(json: S3Update, state: S) { configuration(json: S3Update, state: S) {
const data = _.get(json, 'configuration', false); const data = _.get(json, 'configuration', false);
if (data) { if (data) {
state.s3.configuration = { state.storage.s3.configuration = {
buckets: new Set(data.buckets), buckets: new Set(data.buckets),
currentBucket: data.currentBucket currentBucket: data.currentBucket
}; };
@ -39,44 +39,44 @@ export default class S3Reducer<S extends S3State> {
currentBucket(json: S3Update, state: S) { currentBucket(json: S3Update, state: S) {
const data = _.get(json, 'setCurrentBucket', false); const data = _.get(json, 'setCurrentBucket', false);
if (data && state.s3) { if (data && state.storage.s3) {
state.s3.configuration.currentBucket = data; state.storage.s3.configuration.currentBucket = data;
} }
} }
addBucket(json: S3Update, state: S) { addBucket(json: S3Update, state: S) {
const data = _.get(json, 'addBucket', false); const data = _.get(json, 'addBucket', false);
if (data) { if (data) {
state.s3.configuration.buckets = state.storage.s3.configuration.buckets =
state.s3.configuration.buckets.add(data); state.storage.s3.configuration.buckets.add(data);
} }
} }
removeBucket(json: S3Update, state: S) { removeBucket(json: S3Update, state: S) {
const data = _.get(json, 'removeBucket', false); const data = _.get(json, 'removeBucket', false);
if (data) { if (data) {
state.s3.configuration.buckets.delete(data); state.storage.s3.configuration.buckets.delete(data);
} }
} }
endpoint(json: S3Update, state: S) { endpoint(json: S3Update, state: S) {
const data = _.get(json, 'setEndpoint', false); const data = _.get(json, 'setEndpoint', false);
if (data && state.s3.credentials) { if (data && state.storage.s3.credentials) {
state.s3.credentials.endpoint = data; state.storage.s3.credentials.endpoint = data;
} }
} }
accessKeyId(json: S3Update , state: S) { accessKeyId(json: S3Update , state: S) {
const data = _.get(json, 'setAccessKeyId', false); const data = _.get(json, 'setAccessKeyId', false);
if (data && state.s3.credentials) { if (data && state.storage.s3.credentials) {
state.s3.credentials.accessKeyId = data; state.storage.s3.credentials.accessKeyId = data;
} }
} }
secretAccessKey(json: S3Update, state: S) { secretAccessKey(json: S3Update, state: S) {
const data = _.get(json, 'setSecretAccessKey', false); const data = _.get(json, 'setSecretAccessKey', false);
if (data && state.s3.credentials) { if (data && state.storage.s3.credentials) {
state.s3.credentials.secretAccessKey = data; state.storage.s3.credentials.secretAccessKey = data;
} }
} }
} }

View File

@ -16,6 +16,7 @@ import GroupReducer from '../reducers/group-update';
import LaunchReducer from '../reducers/launch-update'; import LaunchReducer from '../reducers/launch-update';
import ConnectionReducer from '../reducers/connection'; import ConnectionReducer from '../reducers/connection';
import SettingsReducer from '../reducers/settings-update'; import SettingsReducer from '../reducers/settings-update';
import GcpReducer from '../reducers/gcp-reducer';
import { OrderedMap } from '../lib/OrderedMap'; import { OrderedMap } from '../lib/OrderedMap';
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap'; import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
import { GroupViewReducer } from '../reducers/group-view'; import { GroupViewReducer } from '../reducers/group-view';
@ -29,6 +30,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
launchReducer = new LaunchReducer(); launchReducer = new LaunchReducer();
connReducer = new ConnectionReducer(); connReducer = new ConnectionReducer();
settingsReducer = new SettingsReducer(); settingsReducer = new SettingsReducer();
gcpReducer = new GcpReducer();
pastActions: Record<string, any> = {} pastActions: Record<string, any> = {}
@ -70,6 +72,8 @@ export default class GlobalStore extends BaseStore<StoreState> {
}, },
weather: {}, weather: {},
userLocation: null, userLocation: null,
storage: {
gcp: {},
s3: { s3: {
configuration: { configuration: {
buckets: new Set(), buckets: new Set(),
@ -77,6 +81,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
}, },
credentials: null credentials: null
}, },
},
isContactPublic: false, isContactPublic: false,
contacts: {}, contacts: {},
nackedContacts: new Set(), nackedContacts: new Set(),
@ -116,6 +121,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
HarkReducer(data, this.state); HarkReducer(data, this.state);
ContactReducer(data, this.state); ContactReducer(data, this.state);
this.settingsReducer.reduce(data); this.settingsReducer.reduce(data);
this.gcpReducer.reduce(data, this.state);
GroupViewReducer(data, this.state); GroupViewReducer(data, this.state);
} }
} }

View File

@ -3,7 +3,7 @@ import { Invites } from '@urbit/api/invite';
import { Associations } from '@urbit/api/metadata'; import { Associations } from '@urbit/api/metadata';
import { Rolodex } from '@urbit/api/contacts'; import { Rolodex } from '@urbit/api/contacts';
import { Groups } from '@urbit/api/groups'; import { Groups } from '@urbit/api/groups';
import { S3State } from '~/types/s3-update'; import { StorageState } from '~/types/storage-state';
import { LaunchState, WeatherState } from '~/types/launch-update'; import { LaunchState, WeatherState } from '~/types/launch-update';
import { ConnectionStatus } from '~/types/connection'; import { ConnectionStatus } from '~/types/connection';
import { Graphs } from '@urbit/api/graph'; import { Graphs } from '@urbit/api/graph';
@ -31,7 +31,7 @@ export interface StoreState {
groups: Groups; groups: Groups;
groupKeys: Set<Path>; groupKeys: Set<Path>;
nackedContacts: Set<Patp> nackedContacts: Set<Patp>
s3: S3State; storage: StorageState;
graphs: Graphs; graphs: Graphs;
graphKeys: Set<string>; graphKeys: Set<string>;

View File

@ -0,0 +1,9 @@
export interface GcpToken {
accessKey: string;
expiresIn: number;
};
export interface GcpState {
configured?: boolean;
token?: GcpToken
};

View File

@ -3,6 +3,8 @@ export * from './connection';
export * from './global'; export * from './global';
export * from './launch-update'; export * from './launch-update';
export * from './local-update'; export * from './local-update';
export * from './storage-state';
export * from './gcp-state';
export * from './s3-update'; export * from './s3-update';
export * from './workspace'; export * from './workspace';
export * from './util'; export * from './util';

View File

@ -0,0 +1,8 @@
import {GcpState} from './gcp-state';
import {S3State} from './s3-update';
export interface StorageState {
gcp: GcpState;
s3: S3State;
};

View File

@ -27,6 +27,7 @@ import GlobalSubscription from '~/logic/subscription/global';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util'; import { uxToHex } from '~/logic/lib/util';
import { foregroundFromBackground } from '~/logic/lib/sigil'; import { foregroundFromBackground } from '~/logic/lib/sigil';
import gcpManager from '~/logic/lib/gcpManager';
import { withLocalState } from '~/logic/state/local'; import { withLocalState } from '~/logic/state/local';
import { withSettingsState } from '~/logic/state/settings'; import { withSettingsState } from '~/logic/state/settings';
@ -78,6 +79,7 @@ class App extends React.Component {
this.appChannel = new window.channel(); this.appChannel = new window.channel();
this.api = new GlobalApi(this.ship, this.appChannel, this.store); this.api = new GlobalApi(this.ship, this.appChannel, this.store);
gcpManager.configure(this.api, this.store);
this.subscription = this.subscription =
new GlobalSubscription(this.store, this.api, this.appChannel); new GlobalSubscription(this.store, this.api, this.appChannel);
@ -97,6 +99,7 @@ class App extends React.Component {
this.api.local.getBaseHash(); this.api.local.getBaseHash();
this.api.settings.getAll(); this.api.settings.getAll();
this.store.rehydrate(); this.store.rehydrate();
gcpManager.start();
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => { Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
@ -116,8 +119,8 @@ class App extends React.Component {
faviconString() { faviconString() {
let background = '#ffffff'; let background = '#ffffff';
if (this.state.contacts.hasOwnProperty('/~/default')) { if (this.state.contacts.hasOwnProperty(`~${window.ship}`)) {
background = `#${uxToHex(this.state.contacts['/~/default'][window.ship].color)}`; background = `#${uxToHex(this.state.contacts[`~${window.ship}`].color)}`;
} }
const foreground = foregroundFromBackground(background); const foreground = foregroundFromBackground(background);
const svg = sigiljs({ const svg = sigiljs({

View File

@ -13,7 +13,6 @@ import { ShareProfile } from '~/views/apps/chat/components/ShareProfile';
import SubmitDragger from '~/views/components/SubmitDragger'; import SubmitDragger from '~/views/components/SubmitDragger';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState'; import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { Loading } from '~/views/components/Loading'; import { Loading } from '~/views/components/Loading';
import useS3 from '~/logic/lib/useS3';
import { isWriter, resourceFromPath } from '~/logic/lib/group'; import { isWriter, resourceFromPath } from '~/logic/lib/group';
import './css/custom.css'; import './css/custom.css';
@ -180,7 +179,7 @@ export function ChatResource(props: ChatResourceProps) {
(!showBanner && hasLoadedAllowed) ? contacts : modifiedContacts (!showBanner && hasLoadedAllowed) ? contacts : modifiedContacts
} }
onUnmount={appendUnsent} onUnmount={appendUnsent}
s3={props.s3} storage={props.storage}
placeholder="Message..." placeholder="Message..."
message={unsent[station] || ''} message={unsent[station] || ''}
deleteMessage={clearUnsent} deleteMessage={clearUnsent}

View File

@ -1,18 +1,19 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import ChatEditor from './chat-editor'; import ChatEditor from './chat-editor';
import { IuseS3 } from '~/logic/lib/useS3'; import { IuseStorage } from '~/logic/lib/useStorage';
import { uxToHex } from '~/logic/lib/util'; import { uxToHex } from '~/logic/lib/util';
import { Sigil } from '~/logic/lib/sigil'; import { Sigil } from '~/logic/lib/sigil';
import { createPost } from '~/logic/api/graph'; import { createPost } from '~/logic/api/graph';
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage'; import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { Envelope } from '~/types/chat-update'; import { Envelope } from '~/types/chat-update';
import { StorageState } from '~/types';
import { Contacts, Content } from '@urbit/api'; import { Contacts, Content } from '@urbit/api';
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react'; import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
import withS3 from '~/views/components/withS3'; import withStorage from '~/views/components/withStorage';
import { withLocalState } from '~/logic/state/local'; import { withLocalState } from '~/logic/state/local';
type ChatInputProps = IuseS3 & { type ChatInputProps = IuseStorage & {
api: GlobalApi; api: GlobalApi;
numMsgs: number; numMsgs: number;
station: unknown; station: unknown;
@ -20,7 +21,7 @@ type ChatInputProps = IuseS3 & {
envelopes: Envelope[]; envelopes: Envelope[];
contacts: Contacts; contacts: Contacts;
onUnmount(msg: string): void; onUnmount(msg: string): void;
s3: unknown; storage: StorageState;
placeholder: string; placeholder: string;
message: string; message: string;
deleteMessage(): void; deleteMessage(): void;
@ -207,4 +208,4 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
} }
} }
export default withLocalState(withS3(ChatInput, { accept: 'image/*' }), ['hideAvatars']); export default withLocalState(withStorage(ChatInput, { accept: 'image/*' }), ['hideAvatars']);

View File

@ -156,7 +156,8 @@ h2 {
blockquote { blockquote {
padding: 0 0 0 16px; padding: 0 0 0 16px;
margin: 0; margin: 0;
border-left: 1px solid black; color: inherit;
border-left: 1px solid;
} }
:root { :root {
@ -173,6 +174,7 @@ blockquote {
height: 100% !important; height: 100% !important;
width: 100% !important; width: 100% !important;
cursor: text; cursor: text;
color: inherit;
background: transparent; background: transparent;
} }
@ -308,9 +310,6 @@ pre.CodeMirror-placeholder.CodeMirror-line-like {
/* dark */ /* dark */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
blockquote {
border-left: 1px solid inherit;
}
/* codemirror */ /* codemirror */
.chat .cm-s-tlon.CodeMirror { .chat .cm-s-tlon.CodeMirror {

View File

@ -33,9 +33,9 @@ export function LinkResource(props: LinkResourceProps) {
associations, associations,
graphKeys, graphKeys,
unreads, unreads,
s3, pendingIndices,
history, storage,
pendingIndices history
} = props; } = props;
const rid = association.resource; const rid = association.resource;
@ -70,7 +70,7 @@ export function LinkResource(props: LinkResourceProps) {
return ( return (
<LinkWindow <LinkWindow
key={rid} key={rid}
s3={s3} storage={storage}
association={resource} association={resource}
contacts={contacts} contacts={contacts}
resource={resourcePath} resource={resourcePath}

View File

@ -1,21 +1,21 @@
import React, { useRef, useCallback, useEffect, useMemo } from 'react'; import React, {
useRef,
useCallback,
useEffect,
useMemo,
Component,
} from "react";
import { Col, Text } from '@tlon/indigo-react'; import { Col, Text } from "@tlon/indigo-react";
import bigInt from 'big-integer'; import bigInt from "big-integer";
import { import { Association, Graph, Unreads, Group, Rolodex } from "@urbit/api";
Association,
Graph,
Unreads,
Group,
Rolodex,
} from '@urbit/api';
import GlobalApi from '~/logic/api/global'; import GlobalApi from "~/logic/api/global";
import VirtualScroller from '~/views/components/VirtualScroller'; import VirtualScroller from "~/views/components/VirtualScroller";
import { LinkItem } from './components/LinkItem'; import { LinkItem } from "./components/LinkItem";
import LinkSubmit from './components/LinkSubmit'; import LinkSubmit from "./components/LinkSubmit";
import { isWriter } from '~/logic/lib/group'; import { isWriter } from "~/logic/lib/group";
import { S3State } from '~/types/s3-update'; import { StorageState } from "~/types";
interface LinkWindowProps { interface LinkWindowProps {
association: Association; association: Association;
@ -29,39 +29,93 @@ interface LinkWindowProps {
group: Group; group: Group;
path: string; path: string;
api: GlobalApi; api: GlobalApi;
s3: S3State; storage: StorageState;
} }
export function LinkWindow(props: LinkWindowProps) {
const { graph, api, association, pendingSize } = props;
const fetchLinks = useCallback(
async (newer: boolean) => {
return true;
/* stubbed, should we generalize the display of graphs in virtualscroller? */
}, []
);
const style = {
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
};
export class LinkWindow extends Component<LinkWindowProps, {}> {
fetchLinks = async () => true;
canWrite() {
const { group, association } = this.props;
return isWriter(group, association.resource);
}
renderItem = ({ index, scrollWindow }) => {
const { props } = this;
const { association, graph, api } = props;
const [, , ship, name] = association.resource.split("/");
const node = graph.get(index);
const first = graph.peekLargest()?.[0]; const first = graph.peekLargest()?.[0];
const [,,ship, name] = association.resource.split('/'); const post = node?.post;
const canWrite = isWriter(props.group, association.resource); if (!node || !post) {
return null;
const style = useMemo(() => }
({ const linkProps = {
height: '100%', ...props,
width: '100%', node,
display: 'flex', };
flexDirection: 'column', if (this.canWrite() && index.eq(first ?? bigInt.zero)) {
alignItems: 'center' return (
}), []); <React.Fragment key={index.toString()}>
<Col
key={index.toString()}
mx="auto"
mt="4"
maxWidth="768px"
width="100%"
flexShrink={0}
px={3}
>
<LinkSubmit
storage={props.storage}
name={name}
ship={ship.slice(1)}
api={api}
/>
</Col>
<LinkItem {...linkProps} />
</React.Fragment>
);
}
return <LinkItem key={index.toString()} {...linkProps} />;
};
render() {
const { graph, api, association, storage, pendingSize } = this.props;
const first = graph.peekLargest()?.[0];
const [, , ship, name] = association.resource.split("/");
if (!first) { if (!first) {
return ( return (
<Col key={0} mx="auto" mt="4" maxWidth="768px" width="100%" flexShrink={0} px={3}> <Col
{ canWrite ? ( key={0}
<LinkSubmit s3={props.s3} name={name} ship={ship.slice(1)} api={api} /> mx="auto"
mt="4"
maxWidth="768px"
width="100%"
flexShrink={0}
px={3}
>
{this.canWrite() ? (
<LinkSubmit
storage={storage}
name={name}
ship={ship.slice(1)}
api={api}
/>
) : ( ) : (
<Text>There are no links here yet. You do not have permission to post to this collection.</Text> <Text>
) There are no links here yet. You do not have permission to post to
} this collection.
</Text>
)}
</Col> </Col>
); );
} }
@ -70,36 +124,16 @@ export function LinkWindow(props: LinkWindowProps) {
<Col width="100%" height="100%" position="relative"> <Col width="100%" height="100%" position="relative">
<VirtualScroller <VirtualScroller
origin="top" origin="top"
offset={0}
style={style} style={style}
onStartReached={() => {}}
onScroll={() => {}}
data={graph} data={graph}
averageHeight={100} averageHeight={100}
size={graph.size} size={graph.size}
pendingSize={pendingSize} pendingSize={pendingSize}
renderer={({ index, scrollWindow }) => { renderer={this.renderItem}
const node = graph.get(index); loadRows={this.fetchLinks}
const post = node?.post;
if (!node || !post)
return null;
const linkProps = {
...props,
node,
};
if(canWrite && index.eq(first ?? bigInt.zero)) {
return (
<React.Fragment key={index.toString()}>
<Col key={index.toString()} mx="auto" mt="4" maxWidth="768px" width="100%" flexShrink={0} px={3}>
<LinkSubmit s3={props.s3} name={name} ship={ship.slice(1)} api={api} />
</Col>
<LinkItem {...linkProps} />
</React.Fragment>
);
}
return <LinkItem key={index.toString()} {...linkProps} />;
}}
loadRows={fetchLinks}
/> />
</Col> </Col>
); );
}
} }

View File

@ -2,21 +2,22 @@ import { BaseInput, Box, Button, LoadingSpinner, Text } from '@tlon/indigo-react
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { useFileDrag } from '~/logic/lib/useDrag'; import { useFileDrag } from '~/logic/lib/useDrag';
import useS3 from '~/logic/lib/useS3'; import useStorage from '~/logic/lib/useStorage';
import { S3State } from '@urbit/api'; import { StorageState } from '~/types';
import SubmitDragger from '~/views/components/SubmitDragger'; import SubmitDragger from '~/views/components/SubmitDragger';
import { createPost } from '~/logic/api/graph'; import { createPost } from '~/logic/api/graph';
import { hasProvider } from 'oembed-parser'; import { hasProvider } from 'oembed-parser';
interface LinkSubmitProps { interface LinkSubmitProps {
api: GlobalApi; api: GlobalApi;
s3: S3State; storage: StorageState;
name: string; name: string;
ship: string; ship: string;
} }
const LinkSubmit = (props: LinkSubmitProps) => { const LinkSubmit = (props: LinkSubmitProps) => {
const { canUpload, uploadDefault, uploading, promptUpload } = useS3(props.s3); const { canUpload, uploadDefault, uploading, promptUpload } =
useStorage(props.storage);
const [submitFocused, setSubmitFocused] = useState(false); const [submitFocused, setSubmitFocused] = useState(false);
const [urlFocused, setUrlFocused] = useState(false); const [urlFocused, setUrlFocused] = useState(false);

View File

@ -48,7 +48,7 @@ const emptyContact = {
}; };
export function ProfileHeaderImageEdit(props: any): ReactElement { export function ProfileHeaderImageEdit(props: any): ReactElement {
const { contact, s3, setFieldValue, handleHideCover } = { ...props }; const { contact, storage, setFieldValue, handleHideCover } = { ...props };
const [editCover, setEditCover] = useState(false); const [editCover, setEditCover] = useState(false);
const [removedCoverLabel, setRemovedCoverLabel] = useState('Remove Header'); const [removedCoverLabel, setRemovedCoverLabel] = useState('Remove Header');
const handleClear = (e) => { const handleClear = (e) => {
@ -63,7 +63,7 @@ export function ProfileHeaderImageEdit(props: any): ReactElement {
{contact?.cover ? ( {contact?.cover ? (
<div> <div>
{editCover ? ( {editCover ? (
<ImageInput id='cover' s3={s3} marginTop='-8px' /> <ImageInput id='cover' storage={storage} marginTop='-8px' />
) : ( ) : (
<Row> <Row>
<Button mr='2' onClick={() => setEditCover(true)}> <Button mr='2' onClick={() => setEditCover(true)}>
@ -76,14 +76,14 @@ export function ProfileHeaderImageEdit(props: any): ReactElement {
)} )}
</div> </div>
) : ( ) : (
<ImageInput id='cover' s3={s3} marginTop='-8px' /> <ImageInput id='cover' storage={storage} marginTop='-8px' />
)} )}
</> </>
); );
} }
export function EditProfile(props: any): ReactElement { export function EditProfile(props: any): ReactElement {
const { contact, ship, api, isPublic } = props; const { contact, storage, ship, api, isPublic } = props;
const [hideCover, setHideCover] = useState(false); const [hideCover, setHideCover] = useState(false);
const handleHideCover = (value) => { const handleHideCover = (value) => {
@ -179,7 +179,7 @@ export function EditProfile(props: any): ReactElement {
<ProfileImages hideCover={hideCover} contact={contact} ship={ship}> <ProfileImages hideCover={hideCover} contact={contact} ship={ship}>
<ProfileHeaderImageEdit <ProfileHeaderImageEdit
contact={contact} contact={contact}
s3={props.s3} storage={storage}
setFieldValue={setFieldValue} setFieldValue={setFieldValue}
handleHideCover={handleHideCover} handleHideCover={handleHideCover}
/> />
@ -193,14 +193,14 @@ export function EditProfile(props: any): ReactElement {
<ImageInput <ImageInput
id='avatar' id='avatar'
label='Overlay Avatar (may be hidden by other users)' label='Overlay Avatar (may be hidden by other users)'
s3={props.s3} storage={storage}
/> />
</Col> </Col>
</Row> </Row>
<Input id='nickname' label='Custom Name' mb={3} /> <Input id='nickname' label='Custom Name' mb={3} />
<Col width='100%'> <Col width='100%'>
<Text mb={2}>Description</Text> <Text mb={2}>Description</Text>
<MarkdownField id='bio' mb={3} s3={props.s3} /> <MarkdownField id='bio' mb={3} storage={storage} />
</Col> </Col>
<Checkbox mb={3} id='isPublic' label='Public Profile' /> <Checkbox mb={3} id='isPublic' label='Public Profile' />
<GroupSearch <GroupSearch

View File

@ -167,11 +167,11 @@ export function Profile(props: any): ReactElement {
<Center p={[0, 4]} height='100%' width='100%'> <Center p={[0, 4]} height='100%' width='100%'>
<Box ref={anchorRef} maxWidth='600px' width='100%' position='relative'> <Box ref={anchorRef} maxWidth='600px' width='100%' position='relative'>
<ViewProfile <ViewProfile
api={props.api}
nacked={nacked} nacked={nacked}
ship={ship} ship={ship}
contact={contact} contact={contact}
isPublic={isPublic} isPublic={isPublic}
api={props.api}
groups={props.groups} groups={props.groups}
associations={props.associations} associations={props.associations}
/> />
@ -187,7 +187,7 @@ export function Profile(props: any): ReactElement {
<EditProfile <EditProfile
ship={ship} ship={ship}
contact={contact} contact={contact}
s3={props.s3} storage={props.storage}
api={props.api} api={props.api}
groups={props.groups} groups={props.groups}
associations={props.associations} associations={props.associations}

View File

@ -46,7 +46,7 @@ export default function ProfileScreen(props: any) {
groups={props.groups} groups={props.groups}
contact={contact} contact={contact}
api={props.api} api={props.api}
s3={props.s3} storage={props.storage}
isEdit={isEdit} isEdit={isEdit}
isPublic={isPublic} isPublic={isPublic}
nackedContacts={props.nackedContacts} nackedContacts={props.nackedContacts}

View File

@ -35,7 +35,7 @@ export function PublishResource(props: PublishResourceProps) {
location={props.location} location={props.location}
unreads={props.unreads} unreads={props.unreads}
graphs={props.graphs} graphs={props.graphs}
s3={props.s3} storage={props.storage}
/> />
</Box> </Box>
); );

View File

@ -9,7 +9,7 @@ import { PostFormSchema, PostForm } from './NoteForm';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { getLatestRevision, editPost } from '~/logic/lib/publish'; import { getLatestRevision, editPost } from '~/logic/lib/publish';
import { useWaitForProps } from '~/logic/lib/useWaitForProps'; import { useWaitForProps } from '~/logic/lib/useWaitForProps';
import { S3State } from '~/types'; import { StorageState } from '~/types';
interface EditPostProps { interface EditPostProps {
ship: string; ship: string;
@ -17,11 +17,11 @@ interface EditPostProps {
note: GraphNode; note: GraphNode;
api: GlobalApi; api: GlobalApi;
book: string; book: string;
s3: S3State; storage: StorageState;
} }
export function EditPost(props: EditPostProps & RouteComponentProps): ReactElement { export function EditPost(props: EditPostProps & RouteComponentProps): ReactElement {
const { note, book, noteId, api, ship, history, s3 } = props; const { note, book, noteId, api, ship, history, storage } = props;
const [revNum, title, body] = getLatestRevision(note); const [revNum, title, body] = getLatestRevision(note);
const location = useLocation(); const location = useLocation();
@ -58,7 +58,7 @@ export function EditPost(props: EditPostProps & RouteComponentProps): ReactEleme
cancel cancel
history={history} history={history}
onSubmit={onSubmit} onSubmit={onSubmit}
s3={s3} storage={storage}
submitLabel="Update" submitLabel="Update"
loadingText="Updating..." loadingText="Updating..."
/> />

View File

@ -16,8 +16,8 @@ import 'codemirror/lib/codemirror.css';
import { Box } from '@tlon/indigo-react'; import { Box } from '@tlon/indigo-react';
import { useFileDrag } from '~/logic/lib/useDrag'; import { useFileDrag } from '~/logic/lib/useDrag';
import SubmitDragger from '~/views/components/SubmitDragger'; import SubmitDragger from '~/views/components/SubmitDragger';
import useS3 from '~/logic/lib/useS3'; import useStorage from '~/logic/lib/useStorage';
import { S3State } from '@urbit/api'; import { StorageState } from '~/types';
const MARKDOWN_CONFIG = { const MARKDOWN_CONFIG = {
name: 'markdown' name: 'markdown'
@ -28,7 +28,7 @@ interface MarkdownEditorProps {
value: string; value: string;
onChange: (s: string) => void; onChange: (s: string) => void;
onBlur?: (e: any) => void; onBlur?: (e: any) => void;
s3: S3State; storage: StorageState;
} }
const PromptIfDirty = () => { const PromptIfDirty = () => {
@ -74,7 +74,7 @@ export function MarkdownEditor(
[onBlur] [onBlur]
); );
const { uploadDefault, canUpload } = useS3(props.s3); const { uploadDefault, canUpload } = useStorage(props.storage);
const onFileDrag = useCallback( const onFileDrag = useCallback(
async (files: FileList | File[], e: DragEvent) => { async (files: FileList | File[], e: DragEvent) => {

View File

@ -6,7 +6,7 @@ import { MarkdownEditor } from './MarkdownEditor';
export const MarkdownField = ({ export const MarkdownField = ({
id, id,
s3, storage,
...rest ...rest
}: { id: string } & Parameters<typeof Box>[0]) => { }: { id: string } & Parameters<typeof Box>[0]) => {
const [{ value, onBlur }, { error, touched }, { setValue }] = useField(id); const [{ value, onBlur }, { error, touched }, { setValue }] = useField(id);
@ -28,6 +28,7 @@ export const MarkdownField = ({
width="100%" width="100%"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
color="black"
{...rest} {...rest}
> >
<MarkdownEditor <MarkdownEditor
@ -35,7 +36,7 @@ export const MarkdownField = ({
onBlur={handleBlur} onBlur={handleBlur}
value={value} value={value}
onChange={setValue} onChange={setValue}
s3={s3} storage={storage}
/> />
<ErrorLabel mt="2" hasError={Boolean(error && touched)}> <ErrorLabel mt="2" hasError={Boolean(error && touched)}>
{error} {error}

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Box, Text, Col } from '@tlon/indigo-react'; import { Box, Text, Col, Anchor } from '@tlon/indigo-react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import bigInt from 'big-integer'; import bigInt from 'big-integer';
@ -32,6 +32,14 @@ export function Note(props: NoteProps & RouteComponentProps) {
const { notebook, note, contacts, ship, book, api, rootUrl, baseUrl, group } = props; const { notebook, note, contacts, ship, book, api, rootUrl, baseUrl, group } = props;
const editCommentId = props.match.params.commentId; const editCommentId = props.match.params.commentId;
const renderers = {
link: ({ href, children }) => {
return (
<Anchor display="inline" target="_blank" href={href}>{children}</Anchor>
)
}
};
const deletePost = async () => { const deletePost = async () => {
setDeleting(true); setDeleting(true);
const indices = [note.post.index]; const indices = [note.post.index];
@ -107,7 +115,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
</Box> </Box>
</Col> </Col>
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}> <Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
<ReactMarkdown source={body} linkTarget={'_blank'} /> <ReactMarkdown source={body} linkTarget={'_blank'} renderers={renderers} />
</Box> </Box>
<NoteNavigation <NoteNavigation
notebook={notebook} notebook={notebook}

View File

@ -9,7 +9,7 @@ import {
import { AsyncButton } from '../../../components/AsyncButton'; import { AsyncButton } from '../../../components/AsyncButton';
import { Formik, Form, FormikHelpers } from 'formik'; import { Formik, Form, FormikHelpers } from 'formik';
import { MarkdownField } from './MarkdownField'; import { MarkdownField } from './MarkdownField';
import { S3State } from '@urbit/api'; import { StorageState } from '~/types';
interface PostFormProps { interface PostFormProps {
initial: PostFormSchema; initial: PostFormSchema;
@ -21,7 +21,7 @@ interface PostFormProps {
) => Promise<any>; ) => Promise<any>;
submitLabel: string; submitLabel: string;
loadingText: string; loadingText: string;
s3: S3State; storage: StorageState;
} }
const formSchema = Yup.object({ const formSchema = Yup.object({
@ -35,7 +35,7 @@ export interface PostFormSchema {
} }
export function PostForm(props: PostFormProps) { export function PostForm(props: PostFormProps) {
const { initial, onSubmit, submitLabel, loadingText, s3, cancel, history } = props; const { initial, onSubmit, submitLabel, loadingText, storage, cancel, history } = props;
return ( return (
<Col width="100%" height="100%" p={[2, 4]}> <Col width="100%" height="100%" p={[2, 4]}>
@ -67,7 +67,7 @@ export function PostForm(props: PostFormProps) {
>Cancel</Button>} >Cancel</Button>}
</Row> </Row>
</Row> </Row>
<MarkdownField flexGrow={1} id="body" s3={s3} /> <MarkdownField flexGrow={1} id="body" storage={storage} />
</Form> </Form>
</Formik> </Formik>
</Col> </Col>

View File

@ -6,7 +6,8 @@ import { RouteComponentProps } from 'react-router-dom';
import Note from './Note'; import Note from './Note';
import { EditPost } from './EditPost'; import { EditPost } from './EditPost';
import { GraphNode, Graph, Contacts, Association, S3State, Group } from '@urbit/api'; import { GraphNode, Graph, Contacts, Association, Group } from '@urbit/api';
import { StorageState } from '~/types';
interface NoteRoutesProps { interface NoteRoutesProps {
ship: string; ship: string;
@ -20,7 +21,7 @@ interface NoteRoutesProps {
baseUrl?: string; baseUrl?: string;
rootUrl?: string; rootUrl?: string;
group: Group; group: Group;
s3: S3State; storage: StorageState;
} }
export function NoteRoutes(props: NoteRoutesProps & RouteComponentProps) { export function NoteRoutes(props: NoteRoutesProps & RouteComponentProps) {

View File

@ -9,9 +9,9 @@ import {
Contacts, Contacts,
Rolodex, Rolodex,
Unreads, Unreads,
S3State
} from '@urbit/api'; } from '@urbit/api';
import { Center, LoadingSpinner } from '@tlon/indigo-react'; import { Center, LoadingSpinner } from '@tlon/indigo-react';
import { StorageState } from '~/types';
import bigInt from 'big-integer'; import bigInt from 'big-integer';
import Notebook from './Notebook'; import Notebook from './Notebook';
@ -30,7 +30,7 @@ interface NotebookRoutesProps {
rootUrl: string; rootUrl: string;
association: Association; association: Association;
associations: Associations; associations: Associations;
s3: S3State; storage: StorageState;
} }
export function NotebookRoutes( export function NotebookRoutes(
@ -77,7 +77,7 @@ export function NotebookRoutes(
association={props.association} association={props.association}
graph={graph} graph={graph}
baseUrl={baseUrl} baseUrl={baseUrl}
s3={props.s3} storage={props.storage}
/> />
)} )}
/> />
@ -109,7 +109,7 @@ export function NotebookRoutes(
contacts={contacts} contacts={contacts}
association={props.association} association={props.association}
group={group} group={group}
s3={props.s3} storage={props.storage}
{...routeProps} {...routeProps}
/> />
); );

View File

@ -6,7 +6,8 @@ import { RouteComponentProps } from 'react-router-dom';
import { PostForm, PostFormSchema } from './NoteForm'; import { PostForm, PostFormSchema } from './NoteForm';
import { createPost } from '~/logic/api/graph'; import { createPost } from '~/logic/api/graph';
import { Graph } from '@urbit/api/graph'; import { Graph } from '@urbit/api/graph';
import { Association, S3State } from '@urbit/api'; import { Association } from '@urbit/api';
import { StorageState } from '~/types';
import { newPost } from '~/logic/lib/publish'; import { newPost } from '~/logic/lib/publish';
interface NewPostProps { interface NewPostProps {
@ -16,7 +17,7 @@ interface NewPostProps {
graph: Graph; graph: Graph;
association: Association; association: Association;
baseUrl: string; baseUrl: string;
s3: S3State; storage: StorageState;
} }
export default function NewPost(props: NewPostProps & RouteComponentProps) { export default function NewPost(props: NewPostProps & RouteComponentProps) {
@ -50,7 +51,7 @@ export default function NewPost(props: NewPostProps & RouteComponentProps) {
onSubmit={onSubmit} onSubmit={onSubmit}
submitLabel="Publish" submitLabel="Publish"
loadingText="Posting..." loadingText="Posting..."
s3={props.s3} storage={props.storage}
/> />
); );
} }

View File

@ -41,6 +41,8 @@
cursor: text; cursor: text;
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
background: inherit;
color: inherit;
} }
.publish .CodeMirror * { .publish .CodeMirror * {

View File

@ -12,7 +12,7 @@ import {
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { ImageInput } from '~/views/components/ImageInput'; import { ImageInput } from '~/views/components/ImageInput';
import { ColorInput } from '~/views/components/ColorInput'; import { ColorInput } from '~/views/components/ColorInput';
import { S3State } from '~/types/s3-update'; import { StorageState } from '~/types';
export type BgType = 'none' | 'url' | 'color'; export type BgType = 'none' | 'url' | 'color';
@ -20,13 +20,13 @@ export function BackgroundPicker({
bgType, bgType,
bgUrl, bgUrl,
api, api,
s3 storage
}: { }: {
bgType: BgType; bgType: BgType;
bgUrl?: string; bgUrl?: string;
api: GlobalApi; api: GlobalApi;
s3: S3State; storage: StorageState;
}): ReactElement { }) {
const rowSpace = { my: 0, alignItems: 'center' }; const rowSpace = { my: 0, alignItems: 'center' };
const colProps = { my: 3, mr: 4, gapY: 1 }; const colProps = { my: 3, mr: 4, gapY: 1 };
return ( return (
@ -39,7 +39,7 @@ export function BackgroundPicker({
<ImageInput <ImageInput
ml="5" ml="5"
api={api} api={api}
s3={s3} storage={storage}
id="bgUrl" id="bgUrl"
placeholder="Drop or upload a file, or paste a link here" placeholder="Drop or upload a file, or paste a link here"
name="bgUrl" name="bgUrl"

View File

@ -11,7 +11,7 @@ import * as Yup from "yup";
import GlobalApi from "~/logic/api/global"; import GlobalApi from "~/logic/api/global";
import { uxToHex } from "~/logic/lib/util"; import { uxToHex } from "~/logic/lib/util";
import { S3State, BackgroundConfig } from "~/types"; import { S3State, BackgroundConfig, StorageState } from "~/types";
import { BackgroundPicker, BgType } from "./BackgroundPicker"; import { BackgroundPicker, BgType } from "./BackgroundPicker";
import useSettingsState, { SettingsState, selectSettingsState } from "~/logic/state/settings"; import useSettingsState, { SettingsState, selectSettingsState } from "~/logic/state/settings";
import {AsyncButton} from "~/views/components/AsyncButton"; import {AsyncButton} from "~/views/components/AsyncButton";
@ -36,13 +36,13 @@ interface FormSchema {
interface DisplayFormProps { interface DisplayFormProps {
api: GlobalApi; api: GlobalApi;
s3: S3State; storage: StorageState;
} }
const settingsSel = selectSettingsState(["display"]); const settingsSel = selectSettingsState(["display"]);
export default function DisplayForm(props: DisplayFormProps) { export default function DisplayForm(props: DisplayFormProps) {
const { api, s3 } = props; const { api, storage } = props;
const { const {
display: { display: {
@ -108,7 +108,7 @@ export default function DisplayForm(props: DisplayFormProps) {
bgType={props.values.bgType} bgType={props.values.bgType}
bgUrl={props.values.bgUrl} bgUrl={props.values.bgUrl}
api={api} api={api}
s3={s3} storage={storage}
/> />
<Label>Theme</Label> <Label>Theme</Label>
<Radio name="theme" id="light" label="Light"/> <Radio name="theme" id="light" label="Light"/>

View File

@ -15,6 +15,7 @@ import GlobalApi from "~/logic/api/global";
import { BucketList } from "./BucketList"; import { BucketList } from "./BucketList";
import { S3State } from '~/types/s3-update'; import { S3State } from '~/types/s3-update';
import { BackButton } from './BackButton'; import { BackButton } from './BackButton';
import {StorageState} from '~/types';
interface FormSchema { interface FormSchema {
s3bucket: string; s3bucket: string;
@ -26,11 +27,12 @@ interface FormSchema {
interface S3FormProps { interface S3FormProps {
api: GlobalApi; api: GlobalApi;
s3: S3State; storage: StorageState;
} }
export default function S3Form(props: S3FormProps): ReactElement { export default function S3Form(props: S3FormProps): ReactElement {
const { api, s3 } = props; const { api, storage } = props;
const { s3 } = storage;
const onSubmit = useCallback( const onSubmit = useCallback(
(values: FormSchema) => { (values: FormSchema) => {
@ -48,6 +50,7 @@ export default function S3Form(props: S3FormProps): ReactElement {
}, },
[api, s3] [api, s3]
); );
return ( return (
<> <>
<Col p="5" pt="4" borderBottom="1" borderBottomColor="washedGray"> <Col p="5" pt="4" borderBottom="1" borderBottomColor="washedGray">

View File

@ -116,10 +116,10 @@ export default function SettingsScreen(props: any) {
/> />
)} )}
{hash === "display" && ( {hash === "display" && (
<DisplayForm s3={props.s3} api={props.api} /> <DisplayForm storage={props.storage} api={props.api} />
)} )}
{hash === "s3" && ( {hash === "s3" && (
<S3Form s3={props.s3} api={props.api} /> <S3Form storage={props.storage} api={props.api} />
)} )}
{hash === "leap" && ( {hash === "leap" && (
<LeapSettings api={props.api} /> <LeapSettings api={props.api} />

View File

@ -67,6 +67,7 @@ export default class TermApp extends Component {
backgroundColor='white' backgroundColor='white'
width='100%' width='100%'
minHeight='0' minHeight='0'
minWidth='0'
color='washedGray' color='washedGray'
borderRadius='2' borderRadius='2'
mx={['0','3']} mx={['0','3']}

View File

@ -13,6 +13,7 @@ export class History extends Component {
<Box <Box
height='100%' height='100%'
minHeight='0' minHeight='0'
minWidth='0'
display='flex' display='flex'
flexDirection='column-reverse' flexDirection='column-reverse'
overflowY='scroll' overflowY='scroll'

View File

@ -11,20 +11,20 @@ import {
BaseInput BaseInput
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { S3State } from '~/types/s3-update'; import { StorageState } from '~/types';
import useS3 from '~/logic/lib/useS3'; import useStorage from '~/logic/lib/useStorage';
type ImageInputProps = Parameters<typeof Box>[0] & { type ImageInputProps = Parameters<typeof Box>[0] & {
id: string; id: string;
label: string; label: string;
s3: S3State; storage: StorageState;
placeholder?: string; placeholder?: string;
}; };
export function ImageInput(props: ImageInputProps): ReactElement { export function ImageInput(props: ImageInputProps): ReactElement {
const { id, label, s3, caption, placeholder } = props; const { id, label, storage, caption, placeholder } = props;
const { uploadDefault, canUpload, uploading } = useS3(s3); const { uploadDefault, canUpload, uploading } = useStorage(storage);
const [field, meta, { setValue, setError }] = useField(id); const [field, meta, { setValue, setError }] = useField(id);

View File

@ -24,20 +24,50 @@ interface RendererProps {
} }
interface VirtualScrollerProps<T> { interface VirtualScrollerProps<T> {
/**
* Start scroll from
*/
origin: 'top' | 'bottom'; origin: 'top' | 'bottom';
/**
* Load more of the graph
*
* @returns boolean whether or not the graph is now fully loaded
*/
loadRows(newer: boolean): Promise<boolean>; loadRows(newer: boolean): Promise<boolean>;
/**
* The data to iterate over
*/
data: BigIntOrderedMap<T>; data: BigIntOrderedMap<T>;
id: string; /**
* The component to render the items
*
* @remarks
*
* This component must be referentially stable, so either use `useCallback` or
* a instance method. It must also forward the DOM ref from its root DOM node
*/
renderer: (props: RendererProps) => JSX.Element | null; renderer: (props: RendererProps) => JSX.Element | null;
onStartReached?(): void; onStartReached?(): void;
onEndReached?(): void; onEndReached?(): void;
size: number; size: number;
pendingSize: number; pendingSize: number;
totalSize: number; totalSize: number;
/**
* Average height of a single rendered item
*
* @remarks
* This is used primarily to calculate how many items should be onscreen. If
* size is variable, err on the lower side.
*/
averageHeight: number; averageHeight: number;
/**
* The offset to begin rendering at, on load.
*
* @remarks
* This is only looked up once, on component creation. Subsequent changes to
* this prop will have no effect
*/
offset: number; offset: number;
onCalculateVisibleItems?(visibleItems: BigIntOrderedMap<T>): void;
onScroll?({ scrollTop, scrollHeight, windowHeight }): void;
style?: any; style?: any;
} }
@ -62,6 +92,12 @@ const ZONE_SIZE = IS_IOS ? 10 : 40;
// nb: in this file, an index refers to a BigInteger and an offset refers to a // nb: in this file, an index refers to a BigInteger and an offset refers to a
// number used to index a listified BigIntOrderedMap // number used to index a listified BigIntOrderedMap
/**
* A virtualscroller for a `BigIntOrderedMap`.
*
* VirtualScroller does not clean up or reset itself, so please use `key`
* to ensure a new instance is created for each BigIntOrderedMap
*/
export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T>, VirtualScrollerState<T>> { export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T>, VirtualScrollerState<T>> {
/** /**
* A reference to our scroll container * A reference to our scroll container
@ -88,8 +124,6 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
*/ */
private saveDepth = 0; private saveDepth = 0;
private isUpdating = false;
private scrollLocked = true; private scrollLocked = true;
private pageSize = 50; private pageSize = 50;
@ -98,7 +132,6 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
private scrollRef: HTMLElement | null = null; private scrollRef: HTMLElement | null = null;
private loaded = { private loaded = {
top: false, top: false,
bottom: false bottom: false
@ -186,7 +219,6 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
return; return;
} }
log('reflow', `from: ${this.startOffset()} to: ${newOffset}`); log('reflow', `from: ${this.startOffset()} to: ${newOffset}`);
this.isUpdating = true;
const { data, onCalculateVisibleItems } = this.props; const { data, onCalculateVisibleItems } = this.props;
const visibleItems = new BigIntOrderedMap<any>( const visibleItems = new BigIntOrderedMap<any>(
@ -195,14 +227,12 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
this.save(); this.save();
onCalculateVisibleItems ? onCalculateVisibleItems(visibleItems) : null;
this.setState({ this.setState({
visibleItems, visibleItems,
}, () => { }, () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.restore(); this.restore();
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.isUpdating = false;
}); });
}); });

View File

@ -1,12 +0,0 @@
import React from 'react';
import useS3 from '~/logic/lib/useS3';
const withS3 = (Component, params = {}) => {
return React.forwardRef((props: any, ref) => {
const s3 = useS3(props.s3, params);
return <Component ref={ref} {...s3} {...props} />;
});
};
export default withS3;

View File

@ -0,0 +1,12 @@
import React from 'react';
import useStorage from '~/logic/lib/useStorage';
const withStorage = (Component, params = {}) => {
return React.forwardRef((props: any, ref) => {
const storage = useStorage(props.storage, params);
return <Component ref={ref} {...storage} {...props} />;
});
};
export default withStorage;

View File

@ -21,7 +21,7 @@ import { ColorInput } from '~/views/components/ColorInput';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { uxToHex } from '~/logic/lib/util'; import { uxToHex } from '~/logic/lib/util';
import { ImageInput } from '~/views/components/ImageInput'; import { ImageInput } from '~/views/components/ImageInput';
import { S3State } from '~/types/s3-update'; import { StorageState } from '~/types';
interface FormSchema { interface FormSchema {
title: string; title: string;
@ -44,11 +44,11 @@ interface GroupAdminSettingsProps {
group: Group; group: Group;
association: Association; association: Association;
api: GlobalApi; api: GlobalApi;
s3: S3State; storage: StorageState;
} }
export function GroupAdminSettings(props: GroupAdminSettingsProps) { export function GroupAdminSettings(props: GroupAdminSettingsProps) {
const { group, association, s3 } = props; const { group, association, storage } = props;
const { metadata } = association; const { metadata } = association;
const history = useHistory(); const history = useHistory();
const currentPrivate = 'invite' in props.group.policy; const currentPrivate = 'invite' in props.group.policy;
@ -131,7 +131,7 @@ return null;
caption="A picture for your group" caption="A picture for your group"
placeholder="Enter URL" placeholder="Enter URL"
disabled={disabled} disabled={disabled}
s3={s3} storage={storage}
/> />
<Checkbox <Checkbox
id="isPrivate" id="isPrivate"

View File

@ -11,7 +11,7 @@ import { GroupPersonalSettings } from './Personal';
import { GroupChannelSettings } from './Channels'; import { GroupChannelSettings } from './Channels';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { resourceFromPath, roleForShip } from '~/logic/lib/group'; import { resourceFromPath, roleForShip } from '~/logic/lib/group';
import { S3State } from '~/types'; import { StorageState } from '~/types';
const Section = ({ children }) => ( const Section = ({ children }) => (
<Box boxShadow="inset 0px 1px 0px rgba(0, 0, 0, 0.2)">{children}</Box> <Box boxShadow="inset 0px 1px 0px rgba(0, 0, 0, 0.2)">{children}</Box>
@ -23,7 +23,7 @@ interface GroupSettingsProps {
associations: Associations; associations: Associations;
api: GlobalApi; api: GlobalApi;
notificationsGroupConfig: GroupNotificationsConfig; notificationsGroupConfig: GroupNotificationsConfig;
s3: S3State; storage: StorageState;
baseUrl: string; baseUrl: string;
} }
export function GroupSettings(props: GroupSettingsProps) { export function GroupSettings(props: GroupSettingsProps) {

View File

@ -70,7 +70,7 @@ export function GroupsPane(props: GroupsPaneProps) {
association={groupAssociation!} association={groupAssociation!}
group={group!} group={group!}
api={api} api={api}
s3={props.s3} storage={props.storage}
notificationsGroupConfig={props.notificationsGroupConfig} notificationsGroupConfig={props.notificationsGroupConfig}
associations={associations} associations={associations}

View File

@ -15,7 +15,7 @@ import { DeleteGroup } from './DeleteGroup';
import { resourceFromPath } from '~/logic/lib/group'; import { resourceFromPath } from '~/logic/lib/group';
import { ModalOverlay } from '~/views/components/ModalOverlay'; import { ModalOverlay } from '~/views/components/ModalOverlay';
import { SidebarItem } from '~/views/landscape/components/SidebarItem'; import { SidebarItem } from '~/views/landscape/components/SidebarItem';
import { S3State } from '~/types'; import { StorageState } from '~/types';
export function PopoverRoutes( export function PopoverRoutes(
props: { props: {
@ -24,7 +24,7 @@ export function PopoverRoutes(
group: Group; group: Group;
association: Association; association: Association;
associations: Associations; associations: Associations;
s3: S3State; storage: StorageState;
api: GlobalApi; api: GlobalApi;
notificationsGroupConfig: GroupNotificationsConfig; notificationsGroupConfig: GroupNotificationsConfig;
rootIdentity: Contact; rootIdentity: Contact;
@ -128,7 +128,7 @@ export function PopoverRoutes(
api={props.api} api={props.api}
notificationsGroupConfig={props.notificationsGroupConfig} notificationsGroupConfig={props.notificationsGroupConfig}
associations={props.associations} associations={props.associations}
s3={props.s3} storage={props.storage}
/> />
)} )}
{view === 'participants' && ( {view === 'participants' && (

53
sh/poke-gcp-account-json Executable file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env python3
import json
import re
import subprocess
import sys
def herb_poke_gcp_setting(pier, key, val):
"""
Poke a value into settings-store under the %gcp-store bucket.
This does not sanitize or check its inputs. Please make sure they are
correct before calling this function.
:pier: Pier of the ship to poke.
:key: Key to poke. Must be a @tas (i.e. include the '%').
:val: Value to poke. Must be a @t. (will be passed through crude_t.)
"""
print('herb_poke ' + key)
# XXX use +same because herb's cell parser is cursed.
poke_arg = "(same %put-entry %gcp-store {} %s {})".format(
key, crude_t(val))
return subprocess.run(['herb', pier, '-p', 'settings-store', '-d',
poke_arg, '-m', 'settings-event'],
check=True)
def crude_t(pin):
"""
Very crude, bad, dangerous, and evil @t transform.
Puts single quotes around the string. Escapes instances of single quote and
backslash within the string, and turns newlines into \0a.
"""
replaces = [(r'\\', r'\\\\'), ("'", r"\\'"), ("\n", r'\\0a')]
for pattern, replace in replaces:
pin = re.sub(pattern, replace, pin, flags=re.MULTILINE)
return "'{}'".format(pin)
def read_gcp_json(keyfile):
with open(keyfile, 'r') as f:
return json.loads(f.read())
def main():
pier, keyfile = sys.argv[1:]
obj = read_gcp_json(keyfile)
herb_poke_gcp_setting(pier, '%token-uri', obj['token_uri'])
herb_poke_gcp_setting(pier, '%client-email', obj['client_email'])
herb_poke_gcp_setting(pier, '%private-key-id', obj['private_key_id'])
herb_poke_gcp_setting(pier, '%private-key', obj['private_key'])
if __name__ == '__main__':
main()