Merge remote-tracking branch 'origin/release/next-userspace' into lf/join-cleanup

This commit is contained in:
Liam Fitzgerald 2021-01-29 14:22:07 +10:00
commit b7d832d467
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
173 changed files with 7825 additions and 4275 deletions

View File

@ -1,4 +1,4 @@
FROM jaredtobin/janeway:v0.13.1
FROM jaredtobin/janeway:v0.13.3
COPY entrypoint.sh /entrypoint.sh
EXPOSE 22/tcp
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -10,13 +10,7 @@ chmod 600 service-account
chmod 600 id_ssh
chmod 600 id_ssh.pub
LANDSCAPE_STREAM="development"
export LANDSCAPE_STREAM
LANDSCAPE_SHORTHASH="${GITHUB_SHA:0:7}"
export LANDSCAPE_SHORTHASH
janeway release glob --no-pill \
janeway release glob --dev --no-pill \
--credentials service-account \
--ssh-key id_ssh \
--do-it-live \

View File

@ -47,7 +47,19 @@ jobs:
steps:
- uses: actions/checkout@v2
# We only want the extra nix config on linux, where it is necessary
# for the docker build. We don't want in on Mac, where it isn't but
# it breaks the nix install. The two `if` clauses should be mutually
# exclusive
- uses: cachix/install-nix-action@v12
with:
extra_nix_config: |
system-features = nixos-test benchmark big-parallel kvm
if: ${{ matrix.os == 'ubuntu-latest' }}
- uses: cachix/install-nix-action@v12
if: ${{ matrix.os != 'ubuntu-latest' }}
- uses: cachix/cachix-action@v8
with:
name: ares
@ -58,6 +70,9 @@ jobs:
- if: ${{ matrix.os == 'ubuntu-latest' }}
run: nix-build -A urbit-tests
- if: ${{ matrix.os == 'ubuntu-latest' }}
run: nix-build -A docker-image
haskell:
strategy:
fail-fast: false

View File

@ -3,9 +3,6 @@ on:
push:
branches:
- 'release/next-js'
pull_request:
branches:
- 'release/next-js'
jobs:
glob:
runs-on: ubuntu-latest

17
.github/workflows/merge.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: merge
on:
push:
branches:
- 'master'
jobs:
merge-to-next-js:
runs-on: ubuntu-latest
name: "Merge master to release/next-js"
steps:
- uses: actions/checkout@v2
- uses: devmasx/merge-branch@v1.3.1
with:
type: now
target_branch: release/next-js
github_token: ${{ secrets.JANEWAY_BOT_TOKEN }}

51
.github/workflows/release-docker.yml vendored Normal file
View File

@ -0,0 +1,51 @@
name: release-docker
on:
release: null
push:
tags: ['urbit-v*']
jobs:
upload:
strategy:
matrix:
include:
- { os: ubuntu-latest, system: x86_64-linux }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v12
with:
extra_nix_config: |
system-features = nixos-test benchmark big-parallel kvm
- uses: cachix/cachix-action@v8
with:
name: ares
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- uses: docker/docker-login-action@v1.8.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: christian-korneck/update-container-description-action@v1
env:
DOCKER_USER: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_PASS: ${{ secrets.DOCKERHUB_TOKEN }}
with:
destination_container_repo: ${{ secrets.DOCKERHUB_USERNAME }}/urbit
provider: dockerhub
short_description: 'Urbit: a clean-slate OS and network for the 21st century'
readme_file: 'pkg/docker-image/README.md'
- run: |
version="$(cat ./pkg/urbit/version)"
image="$(nix-build -A docker-image)"
imageName="$(nix-instantiate --eval -A docker-image.imageName | cut -d'"' -f2)"
imageTag="$(nix-instantiate --eval -A docker-image.imageTag | cut -d'"' -f2)"
# Load the image from the nix-built tarball
docker load -i $image
docker tag "$imageName:$imageTag" ${{secrets.DOCKERHUB_USERNAME }}/urbit:v$version
docker tag "$imageName:$imageTag" ${{secrets.DOCKERHUB_USERNAME }}/urbit:latest
docker push ${{secrets.DOCKERHUB_USERNAME }}/urbit:v$version
docker push ${{secrets.DOCKERHUB_USERNAME }}/urbit:latest

View File

@ -20,7 +20,7 @@ jobs:
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
with:
name: ares
name: ${{ secrets.CACHIX_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- uses: google-github-actions/setup-gcloud@v0.2.0

View File

@ -180,9 +180,9 @@ new fakezod with `urbit -F zod -B bin/solid.pill -A pkg/arvo`). Run
`:glob|make`, and this will output a file in `fakezod/.urb/put/glob-0vXXX.glob`.
Upload this file to bootstrap.urbit.org, and modify `+hash` at the top of
`pkg/arvo/app/glob.hoon` to match the hash in the filename of the `.glob` file.
`pkg/arvo/app/glob.hoon` to match the hash in the filename of the `.glob` file.
Amend `pkg/arvo/app/landscape/index.html` to import the hashed JS bundle, instead
of the unversioned index.js. Do not commit the produced `index.js` and
of the unversioned index.js. Do not commit the produced `index.js` and
make sure it doesn't end up in your pills (they should be less than 10MB each).
### Tag the resulting commit
@ -306,6 +306,13 @@ $ herb zod -p hood -d "+hood/merge %kids our %home"
For Vere updates, this means simply shutting down each desired ship, installing
the new binary, and restarting the pier with it.
#### Continuous deployment
A subset of release branches are deployed continuously to the network. Thus far
this only includes `release/next-js`, which deploys livenet-compatible
JavaScript changes to select QA ships. Any push to master will automatically
merge master into `release/next-js` to keep the streams at parity.
### Announce the update
Post an announcement to urbit-dev. The tag annotation, basically, is fine here

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:17eb2f5a123f5ad29b0cc9ff9069540c349dd97c6133a9ea33cbf81e0bfa4d6b
size 8483784
oid sha256:271d575a87373f4ed73b195780973ed41cb72be21b428a645c42a49ab5f786ee
size 8873583

View File

@ -115,6 +115,8 @@ let
urbit = callPackage ./nix/pkgs/urbit { inherit enableStatic; };
docker-image = callPackage ./nix/pkgs/docker-image { };
hs = callPackage ./nix/pkgs/hs {
inherit enableStatic;
inherit (pkgsCross) haskell-nix;
@ -158,6 +160,8 @@ let
};
};
inherit (pkgsNative) skopeo;
# A convenience function for constructing a shell.nix for any of the
# pkgsLocal derivations by automatically propagating any dependencies
# to the nix-shell.

View File

@ -0,0 +1,68 @@
{ urbit, libcap, coreutils, bashInteractive, dockerTools, writeScriptBin, amesPort ? 34343 }:
let
startUrbit = writeScriptBin "start-urbit" ''
#!${bashInteractive}/bin/bash
set -eu
# If the container is not started with the `-i` flag
# then STDIN will be closed and we need to start
# Urbit/vere with the `-t` flag.
ttyflag=""
if [ ! -t 0 ]; then
echo "Running with no STDIN"
ttyflag="-t"
fi
# Check if there is a keyfile, if so boot a ship with its name, and then remove the key
if [ -e *.key ]; then
# Get the name of the key
keynames="*.key"
keys=( $keynames )
keyname=''${keys[0]}
mv $keyname /tmp
# Boot urbit with the key, exit when done booting
urbit $ttyflag -w $(basename $keyname .key) -k /tmp/$keyname -c $(basename $keyname .key) -p ${toString amesPort} -x
# Remove the keyfile for security
rm /tmp/$keyname
rm *.key || true
elif [ -e *.comet ]; then
cometnames="*.comet"
comets=( $cometnames )
cometname=''${comets[0]}
rm *.comet
urbit $ttyflag -c $(basename $cometname .comet) -p ${toString amesPort} -x
fi
# Find the first directory and start urbit with the ship therein
dirnames="*/"
dirs=( $dirnames )
dirname=''${dirnames[0]}
urbit $ttyflag -p ${toString amesPort} $dirname
'';
in dockerTools.buildImage {
name = "urbit";
tag = "v${urbit.version}";
contents = [ bashInteractive urbit startUrbit coreutils ];
runAsRoot = ''
#!${bashInteractive}
mkdir -p /urbit
mkdir -p /tmp
${libcap}/bin/setcap 'cap_net_bind_service=+ep' /bin/urbit
'';
config = {
Cmd = [ "/bin/start-urbit" ];
Env = [ "PATH=/bin" ];
WorkingDir = "/urbit";
Volumes = {
"/urbit" = {};
};
Expose = [ "80/tcp" "${toString amesPort}/udp" ];
};
}

View File

@ -278,7 +278,7 @@
=/ app-rid=resource
(path-to-resource path)
=/ group-rid=resource
(fall (group-from-app-resource:met %graph app-rid) [nobody %bad-group])
(fall (peek-group:met %graph app-rid) [nobody %bad-group])
=/ group=(unit group)
(scry-group:grp group-rid)
:- (add-graph app-rid mailbox)

View File

@ -186,6 +186,8 @@
?+ path (on-peek:def path)
[%x %export ~]
``noun+!>(state)
[%x %synced ~]
``noun+!>(~(key by synced))
==
++ on-arvo
|= [=wire =sign-arvo]

View File

@ -6,8 +6,7 @@
/-
inv=invite-store,
*contact-hook,
*metadata-store,
*metadata-hook,
metadata=metadata-store,
pull-hook,
push-hook
/+ *server, *contact-json, default-agent, dbug, verb,
@ -152,7 +151,7 @@
(contact-poke [%create path])
(contact-hook-poke [%add-owned path])
==
(create-metadata path title.act description.act)
(create-metadata rid title.act description.act)
?. ?=(%invite -.policy.act)
~
%+ turn
@ -200,7 +199,7 @@
(contact-poke [%create path])
(contact-hook-poke [%add-owned path])
==
(create-metadata path title.act description.act)
(create-metadata resource.act title.act description.act)
==
++ poke-handle-http-request
|= =inbound-request:eyre
@ -234,7 +233,7 @@
(de-path:resource path)
:~ (group-pull-poke [%add entity.rid rid])
(contact-hook-poke [%add-synced entity.rid path])
(sync-metadata entity.rid path)
(pull-metadata rid)
==
::
:: +utilities
@ -297,34 +296,36 @@
[%pass / %agent [our.bol %group-pull-hook] %poke %pull-hook-action !>(act)]
::
++ metadata-poke
|= act=metadata-action
|= =action:metadata
^- card
[%pass / %agent [our.bol %metadata-store] %poke %metadata-action !>(act)]
::
++ metadata-hook-poke
|= act=metadata-hook-action
^- card
[%pass / %agent [our.bol %metadata-hook] %poke %metadata-hook-action !>(act)]
::
++ sync-metadata
|= [=ship =path]
^- card
(metadata-hook-poke %add-synced ship path)
[%pass / %agent [our.bol %metadata-store] %poke metadata-action+!>(action)]
::
++ create-metadata
|= [=path title=@t description=@t]
|= [rid=resource title=@t description=@t]
^- (list card)
=/ =metadata
%* . *metadata
=/ =metadatum:metadata
%* . *metadatum:metadata
title title
description description
date-created now.bol
creator our.bol
==
:~ (metadata-poke [%add path [%contacts path] metadata])
(metadata-hook-poke [%add-owned path])
:~ (metadata-poke [%add rid [%contacts rid] metadatum])
(push-metadata rid)
==
::
++ push-metadata
|= rid=resource
^- card
=- [%pass / %agent [our.bol %metadata-push-hook] %poke -]
push-hook-action+!>([%add rid])
::
++ pull-metadata
|= rid=resource
^- card
=- [%pass / %agent [our.bol %metadata-pull-hook] %poke -]
pull-hook-action+!>([%add [entity .]:rid])
::
++ all-scry
^- rolodex
.^(rolodex %gx /(scot %p our.bol)/contact-store/(scot %da now.bol)/all/noun)

View File

@ -5,7 +5,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v5.hvt1e.ie7it.b7i7l.1r7jj.dn9ib
++ hash 0v7.ttn7o.50403.rf6oh.63hnc.hgpc9
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0

View File

@ -35,6 +35,7 @@
++ on-pull-nack
|= [=resource =tang]
^- (quip card _this)
~& nacked+resource
:_ this
?. (~(has in get-keys:gra) resource) ~
=- [%pass /pull-nack %agent [our.bowl %graph-store] %poke %graph-update -]~

View File

@ -25,12 +25,13 @@
^- ?
=/ grp ~(. group bowl)
=/ met ~(. metadata bowl)
=/ group-paths (groups-from-resource:met [%graph (en-path:res resource)])
?~ group-paths %.n
=/ group=(unit resource:res)
(peek-group:met %graph resource)
?~ group %.n
?: requires-admin
(is-admin:grp src.bowl i.group-paths)
?| (is-member:grp src.bowl i.group-paths)
(is-admin:grp src.bowl i.group-paths)
(is-admin:grp src.bowl u.group)
?| (is-member:grp src.bowl u.group)
(is-admin:grp src.bowl u.group)
==
::
++ is-allowed-remove
@ -92,27 +93,6 @@
%run-updates (is-allowed resource.q.update bowl %.y)
==
::
++ resource-for-update
|= =vase
^- (unit resource:res)
=/ =update:store !<(update:store vase)
?- -.q.update
%add-graph `resource.q.update
%remove-graph `resource.q.update
%add-nodes `resource.q.update
%remove-nodes `resource.q.update
%add-signatures `resource.uid.q.update
%remove-signatures `resource.uid.q.update
%archive-graph `resource.q.update
%unarchive-graph ~
%add-tag ~
%remove-tag ~
%keys ~
%tags ~
%tag-queries ~
%run-updates `resource.q.update
==
::
++ initial-watch
|= [=path =resource:res]
^- vase

View File

@ -230,7 +230,7 @@
?> ?=(%0 -.update)
=? p.update =(p.update *time) now.bowl
?- -.q.update
%add-graph (add-graph +.q.update)
%add-graph (add-graph p.update +.q.update)
%remove-graph (remove-graph +.q.update)
%add-nodes (add-nodes p.update +.q.update)
%remove-nodes (remove-nodes p.update +.q.update)
@ -247,7 +247,8 @@
==
::
++ add-graph
|= $: =resource:store
|= $: =time
=resource:store
=graph:store
mark=(unit mark:store)
overwrite=?
@ -258,9 +259,13 @@
!(~(has by graphs) resource)
== ==
?> (validate-graph graph mark)
=/ =logged-update:store
[%0 time %add-graph resource graph mark overwrite]
=/ =update-log:store
(gas:orm-log ~ [time logged-update] ~)
:_ %_ state
graphs (~(put by graphs) resource [graph mark])
update-logs (~(put by update-logs) resource (gas:orm-log ~ ~))
update-logs (~(put by update-logs) resource update-log)
archive (~(del by archive) resource)
::
validators
@ -418,43 +423,81 @@
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
(put:orm-log update-log time [%0 time [%remove-nodes resource indices]])
=/ [affected-indices=(set index:store) new-graph=graph:store]
(remove-indices resource graph (sort ~(tap in indices) by-lent))
::
:- (give [/updates]~ [%remove-nodes resource indices])
:- (give [/updates]~ [%remove-nodes resource (~(uni in indices) affected-indices)])
%_ state
update-logs (~(put by update-logs) resource update-log)
graphs
%+ ~(put by graphs)
resource
[(remove-indices resource graph ~(tap in indices)) mark]
[new-graph mark]
==
::
:: we always want to remove the deepest node first,
:: so we don't remove parents before children
++ by-lent
|* [a=(list) b=(list)]
^- ?
(gth (lent a) (lent b))
::
++ remove-indices
=| affected=(set index:store)
|= [=resource:store =graph:store indices=(list index:store)]
^- graph:store
?~ indices graph
^- [(set index:store) graph:store]
?~ indices [affected graph]
=^ new-affected graph
(remove-index graph i.indices)
%_ $
indices t.indices
graph (remove-index graph i.indices)
affected (~(uni in affected) new-affected)
==
::
++ get-descendants
|= =graph:store
=| indices=(list index:store)
=/ nodes (tap:orm:store graph)
%- ~(gas in *(set index:store))
|- =* tap-nodes $
^+ indices
%- zing
%+ turn nodes
|= [atom =node:store]
^- (list index:store)
%+ welp
index.post.node^~
?. ?=(%graph -.children.node)
~
%_ tap-nodes
nodes (tap:orm p.children.node)
==
::
++ remove-index
=| indices=(set index:store)
|= [=graph:store =index:store]
^- graph:store
?~ index graph
^- [(set index:store) graph:store]
?~ index [indices graph]
=* atom i.index
:: last index in list
::
?~ t.index
+:`[* graph:store]`(del:orm graph atom)
=^ rm-node graph (del:orm graph atom)
?~ rm-node `graph
?. ?=(%graph -.children.u.rm-node)
`graph
=/ new-indices
(get-descendants p.children.u.rm-node)
[(~(uni in indices) new-indices) graph]
=/ =node:store
~| "parent index does not exist to remove a node from!"
(need (get:orm graph atom))
~| "child index does not exist to remove a node from!"
?> ?=(%graph -.children.node)
%^ put:orm
graph
atom
node(p.children $(graph p.children.node, index t.index))
=^ new-indices p.children.node
$(graph p.children.node, index t.index)
:- (~(uni in indices) new-indices)
(put:orm graph atom node)
--
::
++ add-signatures
@ -605,6 +648,7 @@
%- graph-update
^- update:store
?- -.q.update
%add-graph update(resource.q resource)
%add-nodes update(resource.q resource)
%remove-nodes update(resource.q resource)
%add-signatures update(resource.uid.q resource)
@ -868,6 +912,15 @@
|= [=atom =node:store]
^- [index:store node:store]
[~[atom] node]
::
[%x %node-exists @ @ @ *]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ =index:store
(turn t.t.t.t.path (cury slav %ud))
=/ node=(unit node:store)
(get-node ship term index)
``noun+!>(?=(^ node))
::
[%x %node @ @ @ *]
=/ =ship (slav %p i.t.t.path)

View File

@ -36,7 +36,68 @@
++ on-init on-init:def
++ on-save !>(~)
++ on-load on-load:def
++ on-poke on-poke:def
++ on-poke
|= [=mark =vase]
^- (quip card _this)
|^
?. =(mark %sane)
(on-poke:def mark vase)
[sane this]
::
++ scry-sharing
.^ (set resource)
%gx
(scot %p our.bowl)
%group-push-hook
(scot %da now.bowl)
/sharing/noun
==
::
++ sane
^- (list card)
%+ murn
~(tap in scry-sharing)
|= rid=resource
^- (unit card)
=/ u-g=(unit group)
(scry-group:grp rid)
?~ u-g
`(poke-us %remove rid)
=* group u.u-g
=/ subs=(set ship)
(get-subscribers-for-group rid)
=/ to-remove=(set ship)
(~(dif in members.group) subs)
?~ to-remove ~
`(poke-store %remove-members rid to-remove)
::
++ poke-us
|= =action:push-hook
^- card
=- [%pass / %agent [our.bowl %group-push-hook] %poke -]
push-hook-action+!>(action)
::
++ poke-store
|= =update:store
^- card
=+ group-update+!>(update)
[%pass /sane %agent [our.bowl %group-store] %poke -]
::
++ get-subscribers-for-group
|= rid=resource
^- (set ship)
=/ target=path
(en-path:resource rid)
%- ~(gas in *(set ship))
%+ murn
~(val by sup.bowl)
|= [her=ship =path]
^- (unit ship)
?. =(path resource+target)
~
`her
--
++ on-agent on-agent:def
++ on-watch on-watch:def
++ on-leave on-leave:def
@ -81,15 +142,6 @@
==
--
::
++ resource-for-update
|= =vase
^- (unit resource)
=/ =update:store
!<(update:store vase)
?: ?=(%initial -.update)
~
`resource.update
::
++ take-update
|= =vase
^- [(list card) agent]

View File

@ -189,17 +189,7 @@
^- (unit (unit cage))
?+ path (on-peek:def path)
[%y %groups ~]
=/ =arch
:- ~
%- malt
%+ turn
~(tap by groups)
|= [rid=resource *]
^- [@ta ~]
=/ group=^path
(en-path:resource rid)
[(spat group) ~]
``noun+!>(arch)
``noun+!>(~(key by groups))
::
[%x %groups %ship @ @ ~]
=/ rid=(unit resource)

View File

@ -1,7 +1,7 @@
:: hark-graph-hook: notifications for graph-store [landscape]
::
/- post, group-store, metadata-store, hook=hark-graph-hook, store=hark-store
/+ resource, metadata, default-agent, dbug, graph-store, graph, grouplib=group, store=hark-store
/- post, group-store, metadata=metadata-store, hook=hark-graph-hook, store=hark-store
/+ resource, mdl=metadata, default-agent, dbug, graph-store, graph, grouplib=group, store=hark-store
::
::
~% %hark-graph-hook-top ..part ~
@ -9,11 +9,17 @@
+$ card card:agent:gall
+$ versioned-state
$% state-0
state-1
==
::
+$ state-0
$: %0
watching=(set [resource index:post])
[%0 base-state-0]
::
+$ state-1
[%1 base-state-0]
::
+$ base-state-0
$: watching=(set [resource index:post])
mentions=_&
watch-on-self=_&
==
@ -36,7 +42,7 @@
::
--
::
=| state-0
=| state-1
=* state -
::
=<
@ -47,7 +53,7 @@
+* this .
ha ~(. +> bowl)
def ~(. (default-agent this %|) bowl)
met ~(. metadata bowl)
met ~(. mdl bowl)
grp ~(. grouplib bowl)
gra ~(. graph bowl)
::
@ -57,13 +63,25 @@
::
++ on-save !>(state)
++ on-load
|= old=vase
|= =vase
^- (quip card _this)
:_ this(state !<(state-0 old))
=+ !<(old=versioned-state vase)
=| cards=(list card)
|-
?: ?=(%0 -.old)
%_ $
-.old %1
::
cards
:_ cards
[%pass / %agent [our dap]:bowl %poke noun+!>(%rewatch-dms)]
==
:_ this(state old)
=. cards (flop cards)
%+ welp
?: (~(has by wex.bowl) [/graph our.bowl %graph-store])
~
~[watch-graph:ha]
cards
[watch-graph:ha cards]
%+ turn
^- (list mark)
:~ %graph-validator-chat
@ -103,9 +121,23 @@
?+ mark (on-poke:def mark vase)
%hark-graph-hook-action
(hark-graph-hook-action !<(action:hook vase))
%noun
(poke-noun !<(* vase))
==
[cards this]
::
++ poke-noun
|= non=*
?> ?=(%rewatch-dms non)
=/ graphs=(list resource)
~(tap in get-keys:gra)
:- ~
%_ state
watching
%- ~(gas in watching)
(murn graphs |=(rid=resource ?:((should-watch:ha rid) `[rid ~] ~)))
==
::
++ hark-graph-hook-action
|= =action:hook
^- (quip card _state)
@ -167,16 +199,48 @@
::
?(%remove-graph %archive-graph)
(remove-graph resource.q.update)
::
%remove-nodes
(remove-nodes resource.q.update indices.q.update)
::
%add-nodes
=* rid resource.q.update
(check-nodes ~(val by nodes.q.update) rid)
==
:: this is awful, but notification kind should always switch
:: on the index, so hopefully doesn't matter
:: TODO: rethink this
++ remove-nodes
|= [rid=resource indices=(set index:graph-store)]
=/ to-remove
%- ~(gas by *(set [resource index:graph-store]))
(turn ~(tap in indices) (lead rid))
:_ state(watching (~(dif in watching) to-remove))
=/ =tube:clay
(get-conversion:ha rid)
%+ roll
~(tap in indices)
|= [=index:graph-store out=(list card)]
=| =indexed-post:graph-store
=. index.p.indexed-post index
=+ !<(u-notif-kind=(unit notif-kind) (tube !>(indexed-post)))
?~ u-notif-kind out
=* notif-kind u.u-notif-kind
=/ =stats-index:store
[%graph rid (scag parent-lent.notif-kind index)]
?. ?=(%each mode.notif-kind) out
:_ out
(poke-hark %read-each stats-index index)
::
++ poke-hark
|= =action:store
^- card
[%pass / %agent [our.bowl %hark-store] %poke hark-action+!>(action)]
::
++ remove-graph
|= rid=resource
=/ unwatched
%- ~(gas in *_watching)
%- ~(gas in *(set [resource index:graph-store]))
%+ skim ~(tap in watching)
|= [r=resource idx=index:graph-store]
=(r rid)
@ -191,23 +255,14 @@
++ add-graph
|= rid=resource
^- (quip card _state)
=/ group-rid=(unit resource)
(group-from-app-resource:met %graph rid)
?~ group-rid
~& no-group+rid
`state
=/ is-hidden=?
!(is-managed:grp u.group-rid)
=/ should-watch
|(is-hidden &(watch-on-self =(our.bowl entity.rid)))
?. should-watch
`state
=/ graph=graph:graph-store :: graph in subscription is bunted
(get-graph-mop:gra rid)
=/ node=(unit node:graph-store)
(bind (peek:orm:graph-store graph) |=([@ =node:graph-store] node))
=^ cards state
(check-nodes (drop node) rid)
?. (should-watch:ha rid)
[cards state]
:_ state(watching (~(put in watching) [rid ~]))
(weld cards (give:ha ~[/updates] %listen [rid ~]))
::
@ -217,14 +272,14 @@
rid=resource
==
=/ group=(unit resource)
(group-from-app-resource:met %graph rid)
(peek-group:met %graph rid)
?~ group
~& no-group+rid
`state
=/ metadata=(unit metadata:metadata-store)
(peek-metadata:met %graph u.group rid)
?~ metadata `state
abet:check:(abed:handle-update:ha rid nodes u.group module.u.metadata)
=/ metadatum=(unit metadatum:metadata)
(peek-metadatum:met %graph rid)
?~ metadatum `state
abet:check:(abed:handle-update:ha rid nodes u.group module.u.metadatum)
--
::
++ on-peek on-peek:def
@ -245,7 +300,19 @@
--
::
|_ =bowl:gall
+* met ~(. mdl bowl)
grp ~(. grouplib bowl)
gra ~(. graph bowl)
::
++ get-conversion
|= rid=resource
^- tube:clay
=+ %^ scry [our now]:bowl
,mark=(unit mark)
/gx/graph-store/graph-mark/(scot %p entity.rid)/[name.rid]/noun
?~ mark
|=(v=vase !>(~))
(scry-conversion [our now]:bowl q.byk.bowl u.mark)
::
++ give
|= [paths=(list path) =update:hook]
@ -273,6 +340,16 @@
%.y
$(contents t.contents)
::
++ should-watch
|= rid=resource
^- ?
=/ group-rid=(unit resource)
(peek-group:met %graph rid)
?~ group-rid %.n
?| !(is-managed:grp u.group-rid)
&(watch-on-self =(our.bowl entity.rid))
==
::
++ handle-update
|_ $: rid=resource :: input
updates=(list node:graph-store)
@ -288,13 +365,7 @@
update-core(rid r, updates upds, group grp, module mod)
::
++ get-conversion
^- tube:clay
=+ %^ scry [our now]:bowl
,mark=(unit mark)
/gx/graph-store/graph-mark/(scot %p entity.rid)/[name.rid]/noun
?~ mark
|=(v=vase !>(~))
(scry-conversion [our now]:bowl q.byk.bowl u.mark)
(^get-conversion rid)
::
++ abet
^- (quip card _state)

View File

@ -1,7 +1,7 @@
:: hark-group-hook: notifications for groups [landscape]
::
/- store=hark-store, post, group-store, metadata-store, hook=hark-group-hook
/+ resource, metadata, default-agent, dbug, graph-store
/- store=hark-store, post, group-store, metadata=metadata-store, hook=hark-group-hook
/+ resource, mdl=metadata, default-agent, dbug, graph-store
::
~% %hark-group-hook-top ..part ~
|%
@ -28,7 +28,7 @@
+* this .
ha ~(. +> bowl)
def ~(. (default-agent this %|) bowl)
met ~(. metadata bowl)
met ~(. mdl bowl)
::
++ on-init
:_ this
@ -115,7 +115,7 @@
::
%metadata-update
=^ cards state
(metadata-update !<(metadata-update:metadata-store q.cage.sign))
(metadata-update !<(update:metadata q.cage.sign))
[cards this]
==
==
@ -140,7 +140,7 @@
:: - We have no way of retrieving old metadata to e.g. get a
:: channel's old name when it is renamed
++ metadata-update
|= update=metadata-update:metadata-store
|= =update:metadata
^- (quip card _state)
[~ state]
::
@ -148,7 +148,9 @@
|= [=index:store =notification:store]
^- card
=- [%pass / %agent [our.bowl %hark-store] %poke -]
hark-action+!>([%add index notification])
:- %hark-action
!> ^- action:store
[%add-note index notification]
--
::
++ on-peek on-peek:def

View File

@ -21,13 +21,13 @@
$% state:state-zero:store
state:state-one:store
state-2
state-3
==
+$ unread-stats
[indices=(set index:graph-store) last=@da]
::
+$ state-2
$: %2
unreads-each=(jug stats-index:store index:graph-store)
+$ base-state
$: unreads-each=(jug stats-index:store index:graph-store)
unreads-count=(map stats-index:store @ud)
last-seen=(map stats-index:store @da)
=notifications:store
@ -36,14 +36,20 @@
dnd=_|
==
::
+$ state-2
[%2 base-state]
::
+$ state-3
[%3 base-state]
::
+$ inflated-state
$: state-2
$: state-3
cache
==
:: $cache: useful to have precalculated, but can be derived from state
:: albeit expensively
+$ cache
$: by-index=(jug stats-index:store @da)
$: by-index=(jug stats-index:store [time=@da =index:store])
~
==
::
@ -78,9 +84,19 @@
=| cards=(list card)
|^
?- -.old
%2
:- cards
%3
:- (flop cards)
this(-.state old, +.state (inflate-cache:ha old))
::
%2
%_ $
-.old %3
::
cards
:_ cards
[%pass / %agent [our dap]:bowl %poke noun+!>(%fix-dangling)]
==
::
%1
%_ $
@ -212,6 +228,7 @@
[%count count]
(~(gut by last-seen) stats-index *time)
==
::
++ give-each-unreads
^- (list [stats-index:store stats:store])
%+ turn
@ -264,10 +281,41 @@
=^ cards state
?+ mark (on-poke:def mark vase)
%hark-action (hark-action !<(action:store vase))
%noun ~& +.state [~ state]
%noun (poke-noun !<(* vase))
==
[cards this]
::
++ poke-noun
|= val=*
?+ val ~|(%bad-noun-poke !!)
%fix-dangling fix-dangling
%print ~&(+.state [~ state])
==
::
++ fix-dangling
=/ graphs get-keys:gra
:_ state
%+ roll
~(tap by unreads-each)
|= $: [=stats-index:store indices=(set index:graph-store)]
out=(list card)
==
?. ?=(%graph -.stats-index) out
?. (~(has in graphs) graph.stats-index)
:_(out (poke-us %remove-graph graph.stats-index))
%+ welp out
%+ turn
%+ skip
~(tap in indices)
|= =index:graph-store
(check-node-existence:gra graph.stats-index index)
|=(=index:graph-store (poke-us %read-each stats-index index))
::
++ poke-us
|= =action:store
^- card
[%pass / %agent [our dap]:bowl %poke hark-action+!>(action)]
::
++ hark-action
|= =action:store
^- (quip card _state)
@ -338,6 +386,9 @@
|= [read=? time=@da =index:store]
poke-core(+.state (^upd-cache read time index))
::
++ rebuild-cache
poke-core(+.state (inflate-cache -.state))
::
++ put-notifs
|= [time=@da =timebox:store]
poke-core(notifications (put:orm notifications time timebox))
@ -380,17 +431,28 @@
(~(put by archive-box) index notification(read %.y))
(give %archive time index)
::
:: if we detect cache inconsistencies, wipe and rebuild
++ change-read-status
|= [time=@da =index:store read=?]
^+ poke-core
=. poke-core (upd-cache read time index)
%_ poke-core
notifications
%^ jub-orm notifications time
|= =timebox:store
%+ ~(jab by timebox) index
|= n=notification:store
?>(!=(read read.n) n(read read))
==
=/ tib=(unit timebox:store)
(get:orm notifications time)
?~ tib poke-core
=/ not=(unit notification:store)
(~(get by u.tib) index)
?~ not poke-core
=? poke-core
:: cache is inconsistent iff we didn't directly
:: call this through %read-note or %unread-note
&(=(read read.u.not) !?=(?(%read-note %unread-note) -.in))
~& >> "Inconsistent hark cache, rebuilding"
rebuild-cache
=. u.tib
(~(put by u.tib) index u.not(read read))
=. notifications
(put:orm notifications time u.tib)
poke-core
::
++ read-note
|= [time=@da =index:store]
@ -416,19 +478,16 @@
::
++ read-index-each
|= [=stats-index:store ref=index:graph-store]
%+ read-index stats-index
%- read-indices
%+ skim
~(tap ^in (~(get ju by-index) stats-index))
|= time=@da
|= [time=@da =index:store]
=/ =timebox:store
(gut-orm notifications time)
%+ roll
~(tap ^in timebox)
|= [[=index:store not=notification:store] out=?]
?: out out
?. (stats-index-is-index:store stats-index index) out
?. ?=(%graph -.index) out
?. ?=(%graph -.contents.not) out
=/ not=notification:store
(~(got by timebox) index)
?. ?=(%graph -.index) %.n
?. ?=(%graph -.contents.not) %.n
(lien list.contents.not |=(p=post:post =(index.p ref)))
::
++ read-each
@ -456,31 +515,18 @@
++ read-count
|= =stats-index:store
=. unreads-count (~(put by unreads-count) stats-index 0)
=/ times=(list @da)
=/ times=(list [@da index:store])
~(tap ^in (~(get ju by-index) stats-index))
(give:(read-index stats-index times) %read-count stats-index)
(give:(read-indices times) %read-count stats-index)
::
++ read-index
|= [=stats-index:store times=(list @da)]
++ read-indices
|= times=(list [time=@da =index:store])
|-
?~ times poke-core
=/ core
(read-stats-index i.times stats-index)
(read-note i.times)
$(poke-core core, times t.times)
::
++ read-stats-index
|= [time=@da =stats-index:store]
=/ keys
~(tap ^in ~(key by (gut-orm notifications time)))
|- ^+ poke-core
?~ keys
poke-core
?. (stats-index-is-index:store stats-index i.keys)
$(keys t.keys)
=/ core
(read-note time i.keys)
$(poke-core core, keys t.keys)
::
++ seen-index
|= [time=@da =stats-index:store]
=/ new-time=@da
@ -505,7 +551,7 @@
=. last-seen
((dif-map-by-key ,@da) last-seen indices)
=. by-index
((dif-map-by-key ,(set @da)) by-index indices)
((dif-map-by-key ,(set [@da =index:store])) by-index indices)
poke-core
::
++ get-stats-indices
@ -538,10 +584,10 @@
~(tap ^in set)
|-
?~ indices poke-core
=/ times=(list @da)
=/ times=(list [time=@da =index:store])
~(tap ^in (~(get ju by-index) i.indices))
=. poke-core
(read-index i.indices times)
(read-indices times)
$(indices t.indices)
--
::
@ -629,14 +675,14 @@
%_ +.state
::
by-index
%. [(to-stats-index:store index) time]
%. [(to-stats-index:store index) time index]
?: read
~(del ju by-index)
~(put ju by-index)
==
::
++ inflate-cache
|= state-2
|= state-3
^+ +.state
=/ nots=(list [p=@da =timebox:store])
(tap:orm notifications)

View File

@ -1,7 +1,7 @@
<!doctype html>
<html>
<head>
<title>OS1</title>
<title>Landscape</title>
<meta charset="utf-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no,maximum-scale=1"/>
@ -12,8 +12,8 @@
<link rel="icon" type="image/png" href="/~landscape/img/Favicon.png">
<link rel="manifest"
href='data:application/manifest+json,{
"name": "OS1",
"short_name": "OS1",
"name": "Landscape",
"short_name": "Landscape",
"description": "An%20interface%20to%20your%20Urbit.",
"display": "standalone",
"background_color": "%23FFFFFF",
@ -24,6 +24,6 @@
<div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.2ddb586104e8758c6863.js"></script>
<script src="/~landscape/js/bundle/index.7d4248944fe1255cb74b.js"></script>
</body>
</html>

View File

@ -63,7 +63,7 @@ class Channel {
}
resetDebounceTimer() {
if(this.debounceTimer) {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
this.debounceTimer = null;
}
@ -182,19 +182,19 @@ class Channel {
// sends a JSON command command to the server.
//
sendJSONToChannel(j) {
if(!j && this.outstandingJSON.length === 0) {
return;
}
let req = new XMLHttpRequest();
req.open("PUT", this.channelURL());
req.setRequestHeader("Content-Type", "application/json");
if (this.lastEventId == this.lastAcknowledgedEventId) {
if(j) {
if (j) {
this.outstandingJSON.push(j);
}
let x = JSON.stringify(this.outstandingJSON);
req.send(x);
if (this.outstandingJSON.length > 0) {
let x = JSON.stringify(this.outstandingJSON);
req.send(x);
}
} else {
// we add an acknowledgment to clear the server side queue
//
@ -203,15 +203,15 @@ class Channel {
//
let payload = [
...this.outstandingJSON,
{action: "ack", "event-id": parseInt(this.lastEventId)}
{action: "ack", "event-id": this.lastEventId}
];
if(j) {
if (j) {
payload.push(j)
}
let x = JSON.stringify(payload);
req.send(x);
this.lastEventId = this.lastAcknowledgedEventId;
this.lastAcknowledgedEventId = this.lastEventId;
}
this.outstandingJSON = [];
@ -227,7 +227,7 @@ class Channel {
this.eventSource = new EventSource(this.channelURL(), {withCredentials:true});
this.eventSource.onmessage = e => {
this.lastEventId = e.lastEventId;
this.lastEventId = parseInt(e.lastEventId, 10);
let obj = JSON.parse(e.data);
let pokeFuncs = this.outstandingPokes.get(obj.id);

View File

@ -25,7 +25,6 @@
^- (list @tas)
:~ %group-store
%metadata-store
%metadata-hook
%contact-store
%contact-hook
%invite-store

View File

@ -6,13 +6,14 @@
:: /group/%group-path all updates related to this group
::
/- *metadata-store, *metadata-hook
/+ default-agent, dbug, verb, grpl=group, *migrate
/+ default-agent, dbug, verb, grpl=group, *migrate, resource
~% %metadata-hook-top ..part ~
|%
+$ card card:agent:gall
+$ versioned-state
$% state-zero
state-one
state-two
==
::
+$ state-zero
@ -23,298 +24,57 @@
$: %1
synced=(map group-path ship)
==
+$ state-two
[%2 ~]
--
=| state-one
=| state-two
=* state -
%- agent:dbug
%+ verb |
^- agent:gall
=<
|_ =bowl:gall
+* this .
hc ~(. +> bowl)
def ~(. (default-agent this %|) bowl)
::
++ on-init
[[%pass /updates %agent [our.bowl %metadata-store] %watch /updates]~ this]
::
++ on-save !>(state)
++ on-load
|= =vase
=/ old
!<(versioned-state vase)
?: ?=(%1 -.old)
`this(state old)
:: groups OTA did not migrate metadata syncs
:: we clear our syncs, and wait for metadata-store
:: to poke us with the syncs
`this
::
++ on-leave on-leave:def
++ on-peek
|= =path
^- (unit (unit cage))
?+ path (on-peek:def path)
[%x %export ~]
``noun+!>(state)
==
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
?. ?=([%try-rejoin @ @ *] wire)
(on-arvo:def wire sign-arvo)
=/ nack-count=@ud (slav %ud i.t.wire)
=/ who=@p (slav %p i.t.t.wire)
=/ pax t.t.t.wire
?> ?=([%behn %wake *] sign-arvo)
~? ?=(^ error.sign-arvo)
"behn errored in backoff timers, continuing anyway"
:_ this
[(try-rejoin:hc who pax +(nack-count))]~
::
++ on-fail on-fail:def
::
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?+ mark (on-poke:def mark vase)
%metadata-hook-action
=^ cards state
(poke-hook-action:hc !<(metadata-hook-action vase))
[cards this]
::
%metadata-action
[(poke-action:hc !<(metadata-action vase)) this]
::
%import
?> (team:title our.bowl src.bowl)
=^ cards state
(poke-import:hc q.vase)
[cards this]
==
::
++ on-watch
|= =path
^- (quip card _this)
?+ path (on-watch:def path)
[%group *] [(watch-group:hc t.path) this]
==
::
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
?+ -.sign (on-agent:def wire sign)
%kick =^(cards state (kick:hc wire) [cards this])
%watch-ack =^(cards state (watch-ack:hc wire p.sign) [cards this])
%fact
?+ p.cage.sign (on-agent:def wire sign)
%metadata-update
=^ cards state
(fact-metadata-update:hc wire !<(metadata-update q.cage.sign))
[cards this]
==
==
--
::
|_ =bowl:gall
+* grp ~(. grpl bowl)
++ poke-hook-action
|= act=metadata-hook-action
^- (quip card _state)
+* this .
def ~(. (default-agent *agent:gall %|) bowl)
++ on-init on-init:def
++ on-save !>(state)
++ on-load
|= =vase
=+ !<(old=versioned-state vase)
|^
?- -.act
%add-owned
?> (team:title our.bowl src.bowl)
:- ~
?: (~(has by synced) path.act) state
state(synced (~(put by synced) path.act our.bowl))
?: ?=(%2 -.old)
`this
:_ this
%+ murn
~(tap by synced.old)
|= [group=path =ship]
%+ bind
(de-path-soft:resource group)
|= rid=resource
?: =(our.bowl ship)
(push-metadata rid)
(pull-metadata rid ship)
::
%add-synced
?> (team:title our.bowl src.bowl)
=/ =path [%group path.act]
?: (~(has by synced) path.act) [~ state]
:_ state(synced (~(put by synced) path.act ship.act))
[%pass path %agent [ship.act %metadata-hook] %watch path]~
++ poke-our
|= [app=term =cage]
^- card
[%pass / %agent [our.bowl app] %poke cage]
::
%remove
=/ ship (~(get by synced) path.act)
?~ ship [~ state]
?: &(!=(u.ship src.bowl) ?!((team:title our.bowl src.bowl)))
[~ state]
:_ state(synced (~(del by synced) path.act))
%- zing
:~ (unsubscribe [%group path.act] u.ship)
[%give %kick ~[[%group path.act]] ~]~
==
==
++ push-metadata
|= rid=resource
^- card
(poke-our %metadata-push-hook push-hook-action+!>([%add rid]))
::
++ unsubscribe
|= [=path =ship]
^- (list card)
?: =(ship our.bowl)
[%pass path %agent [our.bowl %metadata-store] %leave ~]~
[%pass path %agent [ship %metadata-hook] %leave ~]~
++ pull-metadata
|= [rid=resource =ship]
^- card
(poke-our %metadata-pull-hook pull-hook-action+!>([%add ship rid]))
--
::
++ poke-action
|= act=metadata-action
^- (list card)
|^
?: (team:title our.bowl src.bowl)
?- -.act
%add (send group-path.act)
%remove (send group-path.act)
==
?> (is-member:grp src.bowl group-path.act)
?- -.act
%add (metadata-poke our.bowl %metadata-store)
%remove (metadata-poke our.bowl %metadata-store)
==
::
++ send
|= =group-path
^- (list card)
=/ =ship
%+ slav %p
(snag 1 group-path)
=/ app ?:(=(ship our.bowl) %metadata-store %metadata-hook)
(metadata-poke ship app)
::
++ metadata-poke
|= [=ship app=@tas]
^- (list card)
[%pass / %agent [ship app] %poke %metadata-action !>(act)]~
::
++ is-managed
|= =path
^- ?
?> ?=(^ path)
!=(i.path '~')
--
::
++ poke-import
|= arc=*
^- (quip card _state)
=/ sty=state-one
[%1 (remake-map ;;((tree [group-path ship]) +.arc))]
:_ sty
%+ murn ~(tap by synced.sty)
|= [=group-path =ship]
?: =(ship our.bowl)
~
=/ =path [%group group-path]
`(try-rejoin ship path 0)
::
++ try-rejoin
|= [who=@p pax=path nack-count=@ud]
^- card
=/ =wire
[%try-rejoin (scot %ud nack-count) (scot %p who) pax]
[%pass wire %agent [who %metadata-hook] %watch pax]
::
++ watch-group
|= =path
^- (list card)
|^
?> =(our.bowl (~(got by synced) path))
?> (is-member:grp src.bowl path)
%+ turn ~(tap by (metadata-scry path))
|= [[=group-path =md-resource] =metadata]
^- card
[%give %fact ~ %metadata-update !>([%add group-path md-resource metadata])]
::
++ metadata-scry
|= pax=^path
^- associations
=. pax
;: weld
/(scot %p our.bowl)/metadata-store/(scot %da now.bowl)/group
pax
/noun
==
.^(associations %gx pax)
--
::
++ fact-metadata-update
|= [wir=wire fact=metadata-update]
^- (quip card _state)
|^
[?:((team:title our.bowl src.bowl) handle-local handle-foreign) state]
::
++ handle-local
?+ -.fact ~
%add
?. (~(has by synced) group-path.fact) ~
(give group-path.fact fact)
::
%update-metadata
?. (~(has by synced) group-path.fact) ~
(give group-path.fact fact)
::
%remove
?. (~(has by synced) group-path.fact) ~
(give group-path.fact fact)
==
::
++ handle-foreign
?+ -.fact ~
%add
?. =(src.bowl (~(got by synced) group-path.fact)) ~
(poke fact)
::
%update-metadata
?. =(src.bowl (~(got by synced) group-path.fact)) ~
(poke [%add +.fact])
::
%remove
?. =(src.bowl (~(got by synced) group-path.fact)) ~
(poke fact)
==
::
++ give
|= [=path upd=metadata-update]
^- (list card)
[%give %fact ~[[%group path]] %metadata-update !>(upd)]~
::
++ poke
|= act=metadata-action
^- (list card)
[%pass / %agent [our.bowl %metadata-store] %poke %metadata-action !>(act)]~
--
::
++ kick
|= wir=wire
^- (quip card _state)
:_ state
|-
?+ wir !!
[%try-rejoin @ @ *]
$(wir t.t.t.wir)
::
[%updates ~]
[%pass /updates %agent [our.bowl %metadata-store] %watch /updates]~
::
[%group @ *]
?. (~(has by synced) t.wir) ~
=/ =ship (~(got by synced) t.wir)
?: =(ship our.bowl)
[%pass wir %agent [our.bowl %metadata-store] %watch wir]~
[%pass wir %agent [ship %metadata-hook] %watch wir]~
==
::
++ watch-ack
|= [wir=wire saw=(unit tang)]
^- (quip card _state)
?: ?=([%try-rejoin @ *] wir)
?~ saw
[~ state]
=/ nack-count=@ud (slav %ud i.t.wir)
=/ wakeup=@da
(add now.bowl (mul ~s1 (bex (min 19 nack-count))))
:_ state
[%pass wir %arvo %b %wait wakeup]~
?> ?=(^ wir)
[~ ?~(saw state state(synced (~(del by synced) t.wir)))]
::
++ on-poke on-poke:def
++ on-watch on-watch:def
++ on-peek on-peek:def
++ on-leave on-leave:def
++ on-agent on-agent:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
--

View File

@ -0,0 +1,147 @@
:: metadata-pull-hook [landscape]:
::
:: allow syncing group data from foreign paths to local paths
::
/- *group, invite-store, metadata=metadata-store
/+ default-agent, verb, dbug, store=group-store, grpl=group, pull-hook
/+ resource, mdl=metadata
~% %group-hook-top ..part ~
|%
+$ card card:agent:gall
::
++ config
^- config:pull-hook
:* %metadata-store
update:metadata
%metadata-update
%metadata-push-hook
==
+$ state-zero
[%0 previews=(map resource group-preview:metadata)]
::
--
::
::
%- agent:dbug
%+ verb |
^- agent:gall
%- (agent:pull-hook config)
^- (pull-hook:pull-hook config)
=| state-zero
=* state -
=> |_ =bowl:gall
++ def ~(. (default-agent state %|) bowl)
++ get-preview
|= rid=resource
^- card
=/ =path
preview+(en-path:resource rid)
=/ =dock
[entity.rid %metadata-push-hook]
=/ =cage
metadata-hook-update+!>([%req-preview rid])
[%pass path %agent dock %poke cage]
::
++ watch-invites
^- card
[%pass /invites %agent [our.bowl %invite-store] %watch /updates]
::
++ take-invites
|= =sign:agent:gall
^- (quip card _state)
?+ -.sign (on-agent:def /invites sign)
%fact
?> ?=(%invite-update p.cage.sign)
=+ !<(=update:invite-store q.cage.sign)
:_ state
?. ?=(%invite -.update) ~
?. =(%contacts term.update) ~
(get-preview resource.invite.update)^~
::
%kick [watch-invites^~ state]
==
--
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
dep ~(. (default:pull-hook this config) bowl)
met ~(. mdl bowl)
hc ~(. +> bowl)
::
++ on-init on-init:def
++ on-save !>(state)
++ on-load
|= =vase
=+ !<(old=state-zero vase)
:_ this(state old)
?: (~(has by wex.bowl) [/invites our.bowl %invite-store]) ~
~[watch-invites:hc]
::
++ on-poke
|= [=mark =vase]
?. ?=(%metadata-hook-update mark)
(on-poke:def mark vase)
=+ !<(=hook-update:metadata vase)
?. ?=(%preview -.hook-update)
(on-poke:def mark vase)
:_ this(previews (~(put by previews) group.hook-update +.hook-update))
=/ paths=(list path)
~[preview+(en-path:resource group.hook-update)]
:~ [%give %fact paths mark^vase]
[%give %kick paths ~]
==
::
++ on-agent
|= [=wire =sign:agent:gall]
=^ cards state
?+ wire (on-agent:def:hc wire sign)
[%invites ~] (take-invites:hc sign)
::
[%preview @ @ @ ~]
?. ?=(%poke-ack -.sign)
(on-agent:def:hc wire sign)
:_ state
?~ p.sign ~
:~ [%give %fact ~[wire] tang+!>(u.p.sign)]
[%give %kick ~[wire] ~]
==
==
[cards this]
::
++ on-watch
|= =path
?> (team:title [our src]:bowl)
?. ?=([%preview @ @ @ ~] path)
(on-watch:def path)
=/ rid=resource
(de-path:resource t.path)
=/ prev=(unit group-preview:metadata)
(~(get by previews) rid)
:_ this
?~ prev
(get-preview rid)^~
[%give %fact ~ metadata-hook-update+!>([%preview u.prev])]~
::
++ on-leave on-leave:def
++ on-peek on-peek:def
++ on-arvo on-arvo:def
::
++ on-fail on-fail:def
++ on-pull-nack
|= [=resource =tang]
^- (quip card _this)
=/ =associations:metadata
(metadata-for-group:met resource)
:_ this
%+ turn ~(tap by associations)
|= [=md-resource:metadata =association:metadata]
=- [%pass / %agent [our.bowl %metadata-store] %poke -]
:- %metadata-update
!> ^- update:metadata
[%remove resource md-resource]
::
++ on-pull-kick
|= =resource
^- (unit path)
`/
--

View File

@ -0,0 +1,104 @@
:: metadata-push-hook [landscape]:
::
/- *group, *invite-store, store=metadata-store
/+ default-agent, verb, dbug, grpl=group, push-hook,
resource, mdl=metadata, gral=graph
~% %group-hook-top ..part ~
|%
+$ card card:agent:gall
::
++ config
^- config:push-hook
:* %metadata-store
/all
update:store
%metadata-update
%metadata-pull-hook
==
::
+$ agent (push-hook:push-hook config)
--
::
::
%- agent:dbug
%+ verb |
^- agent:gall
%- (agent:push-hook config)
^- agent
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
grp ~(. grpl bowl)
met ~(. mdl bowl)
gra ~(. gral bowl)
::
++ on-init on-init:def
++ on-save !>(~)
++ on-load on-load:def
++ on-poke
|= [=mark =vase]
?. ?=(%metadata-hook-update mark)
(on-poke:def mark vase)
=+ !<(=hook-update:store vase)
?. ?=(%req-preview -.hook-update)
(on-poke:def mark vase)
=* rid group.hook-update
|^
?> =(entity.rid our.bowl)
?> (can-join:grp rid src.bowl)
=/ members
~(wyt in (members:grp rid))
=/ =metadatum:store
(need (peek-metadatum:met %contacts rid))
:_ this
=; =cage
[%pass / %agent [src.bowl %metadata-pull-hook] %poke cage]~
:- %metadata-hook-update
!> ^- hook-update:store
[%preview rid channels members channel-count metadatum]
::
++ channels
%- ~(gas by *associations:store)
%+ skim ~(tap by (app-metadata-for-group:met rid %graph))
|=([=md-resource:store group=resource =metadatum:store] preview.metadatum)
::
++ channel-count
~(wyt by (app-metadata-for-group:met rid %graph))
--
++ on-agent on-agent:def
++ on-watch on-watch:def
++ on-leave on-leave:def
++ on-peek on-peek:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
::
++ should-proxy-update
|= =vase
=+ !<(=update:store vase)
?. ?=(?(%add %remove %update) -.update)
%.n
=/ role=(unit (unit role-tag))
(role-for-ship:grp group.update src.bowl)
?~ role %.n
?~ u.role %.n
?=(?(%admin %moderator) u.u.role)
::
++ take-update
|= =vase
^- [(list card) agent]
`this
::
++ initial-watch
|= [=path rid=resource]
^- vase
=/ group
(scry-group:grp rid)
=/ =associations:store
(metadata-for-group:met rid)
?> ?=(^ group)
?> (~(has in members.u.group) src.bowl)
!> ^- update:store
[%initial-group rid associations]
::
--

View File

@ -3,11 +3,11 @@
:: data store for application metadata and mappings
:: between groups and resources within applications
::
:: group-paths are expected to be an existing group path
:: paths are expected to be an existing group path
:: resources are expected to correspond to existing app paths
::
:: note: when scrying for metadata, to make the arguments safe in paths,
:: encode group-path and app-path using (scot %t (spat group-path))
:: encode path and path using (scot %t (spat path))
::
:: +watch paths:
:: /all associations + updates
@ -19,22 +19,22 @@
:: /group-indices all group indices
:: /app-indices all app indices
:: /resource-indices all resource indices
:: /metadata/%group-path/%app-name/%app-path specific metadatum
:: /metadata/%path/%app-name/%path specific metadatum
:: /app-name/%app-name associations for app
:: /group/%group-path associations for group
:: /group/%path associations for group
::
/- *metadata-store, *metadata-hook
/- store=metadata-store
/+ *metadata-json, default-agent, verb, dbug, resource, *migrate
|%
+$ card card:agent:gall
+$ base-state-0
$: associations=associations-0
group-indices=(jug group-path md-resource)
app-indices=(jug app-name [group-path app-path])
resource-indices=(jug md-resource group-path)
group-indices=(jug path md-resource:store)
app-indices=(jug app-name:store [path path])
resource-indices=(jug md-resource:store path)
==
::
+$ associations-0 (map [group-path md-resource] metadata-0)
+$ associations-0 (map [path md-resource:store] metadata-0)
::
+$ metadata-0
$: title=@t
@ -44,11 +44,35 @@
creator=@p
==
::
+$ metadata-1
$: title=@t
description=@t
color=@ux
date-created=@da
creator=@p
module=term
==
::
+$ md-resource-1 [=app-name:store =path]
::
+$ associations-1 (map [path md-resource-1] metadata-1)
::
+$ base-state-1
$: associations=associations
group-indices=(jug group-path md-resource)
app-indices=(jug app-name [group-path app-path])
resource-indices=(jug md-resource group-path)
$: associations=associations-1
group-indices=(jug path md-resource-1)
app-indices=(jug app-name:store [path path])
resource-indices=(jug md-resource-1 path)
==
::
+$ cached-indices
$: group-indices=(jug resource md-resource:store)
app-indices=(jug app-name:store [group=resource =resource])
resource-indices=(map md-resource:store resource)
==
::
+$ base-state-2
$: =associations:store
~
==
::
+$ state-0 [%0 base-state-0]
@ -58,6 +82,7 @@
+$ state-4 [%4 base-state-1]
+$ state-5 [%5 base-state-1]
+$ state-6 [%6 base-state-1]
+$ state-7 [%7 base-state-2]
+$ versioned-state
$% state-0
state-1
@ -66,10 +91,16 @@
state-4
state-5
state-6
state-7
==
::
+$ inflated-state
$: state-7
cached-indices
==
--
::
=| state-6
=| inflated-state
=* state -
%+ verb |
%- agent:dbug
@ -81,7 +112,7 @@
def ~(. (default-agent this %|) bowl)
::
++ on-init on-init:def
++ on-save !>(state)
++ on-save !>(-.state)
++ on-load
|= =vase
^- (quip card _this)
@ -95,30 +126,13 @@
?> (team:title our.bowl src.bowl)
=^ cards state
?+ mark (on-poke:def mark vase)
%metadata-action
(poke-metadata-action:mc !<(metadata-action vase))
::
%noun
=/ val=(each [%cleanup path] tang)
(mule |.(!<([%cleanup path] vase)))
?. ?=(%& -.val)
(on-poke:def mark vase)
=/ group=path +.p.val
=/ res=(set md-resource) (~(get ju group-indices) group)
=. group-indices (~(del by group-indices) group)
:- ~
%+ roll ~(tap in res)
|= [r=md-resource out=_state]
=: resource-indices.out (~(del by resource-indices.out) r)
associations.out (~(del by associations.out) group r)
app-indices.out
%- ~(del ju app-indices.out)
[app-name.r group app-path.r]
==
out
?(%metadata-action %metadata-update)
(poke-metadata-update:mc !<(update:store vase))
::
%import
(poke-import:mc q.vase)
::
%noun ~& +.state `state
==
[cards this]
::
@ -136,7 +150,7 @@
~
::
[%app-name @ ~]
=/ =app-name i.t.path
=/ =app-name:store i.t.path
=/ app-indices (metadata-for-app:mc app-name)
(give %metadata-update !>([%associations app-indices]))
==
@ -157,25 +171,26 @@
[%y %resource-indices ~] ``noun+!>(resource-indices)
[%x %associations ~] ``noun+!>(associations)
[%x %app-name @ ~]
=/ =app-name i.t.t.path
=/ =app-name:store i.t.t.path
``noun+!>((metadata-for-app:mc app-name))
::
[%x %group *]
=/ =group-path t.t.path
``noun+!>((metadata-for-group:mc group-path))
=/ group=resource (de-path:resource t.t.path)
``noun+!>((metadata-for-group:mc group))
::
[%x %metadata @ @ @ ~]
=/ =group-path (stab (slav %t i.t.t.path))
=/ =md-resource [`term`i.t.t.t.path (stab (slav %t i.t.t.t.t.path))]
``noun+!>((~(get by associations) [group-path md-resource]))
[%x %metadata @ @ @ @ ~]
=/ =md-resource:store
[i.t.t.path (de-path:resource t.t.t.path)]
``noun+!>((~(get by associations) md-resource))
::
[%x %resource @ *]
=/ app=term i.t.t.path
=/ app-path=^path t.t.t.path
``noun+!>((~(get by resource-indices) app app-path))
=/ rid=resource (de-path:resource t.t.t.path)
``noun+!>((~(get by resource-indices) [app rid]))
::
[%x %export ~]
``noun+!>(state)
``noun+!>(-.state)
==
::
++ on-leave on-leave:def
@ -192,307 +207,239 @@
=/ old !<(versioned-state vase)
=| cards=(list card)
|^
?: ?=(%6 -.old)
=/ =^associations
(migrate-app-to-graph-store %chat associations.old)
=* loop $
?: ?=(%7 -.old)
:- cards
%_ state
associations associations
::
resource-indices
(rebuild-resource-indices associations)
%_ state
associations
associations.old
::
resource-indices
(rebuild-resource-indices associations.old)
::
group-indices
(rebuild-group-indices associations.old)
::
app-indices
(rebuild-app-indices associations)
::
group-indices
(rebuild-group-indices associations)
(rebuild-app-indices associations.old)
==
?: ?=(%6 -.old)
=/ old-assoc=associations-1
(migrate-app-to-graph-store %chat associations.old)
$(old [%7 (associations-1-to-2 old-assoc) ~])
::
?: ?=(%5 -.old)
=/ =^associations
=/ associations=associations-1
(migrate-app-to-graph-store %publish associations.old)
%_ $
-.old %6
associations.old associations
::
resource-indices.old
(rebuild-resource-indices associations)
::
app-indices.old
(rebuild-app-indices associations)
::
group-indices.old
(rebuild-group-indices associations)
==
?: ?=(%4 -.old)
%_ $
-.old %5
::
resource-indices.old
(rebuild-resource-indices associations.old)
::
app-indices.old
(rebuild-app-indices associations.old)
::
group-indices.old
(rebuild-group-indices associations.old)
:: pre-breach, can safely throw away
loop(old *state-7)
::
++ associations-1-to-2
|= assoc=associations-1
^- associations:store
%- ~(gas by *associations:store)
%+ murn
~(tap by assoc)
|= [[group=path m=md-resource-1] met=metadata-1]
%+ biff (de-path-soft:resource group)
|= g=resource
%+ bind (md-resource-1-to-2 m)
|= =md-resource:store
[md-resource g (metadata-1-to-2 met)]
::
++ md-resource-1-to-2
|= m=md-resource-1
^- (unit md-resource:store)
%+ bind (de-path-soft:resource path.m)
|=(rid=resource [app-name.m rid])
::
++ metadata-1-to-2
|= m=metadata-1
%* . *metadatum:store
title title.m
description description.m
color color.m
date-created date-created.m
creator creator.m
module module.m
==
?: ?=(%3 -.old)
$(old [%4 +.old])
?: ?=(%2 -.old)
=/ new-state=state-3
%* . *state-3
associations
%- malt
%+ murn ~(tap by associations.old)
|= [[=group-path =md-resource] m=metadata-0]
^- (unit [[^group-path ^md-resource] metadata])
?: =(app-name.md-resource %link) ~
`[[group-path md-resource] (old-md-to-new m)]
==
$(old new-state)
?: ?=(%1 -.old)
%_ $
old [%2 +.old]
::
cards
%+ murn ~(tap in ~(key by group-indices.old))
|= =group-path
^- (unit card)
=/ rid (de-path-soft:resource group-path)
?~ rid ~
?: =(our.bowl entity.u.rid)
`(poke-md-hook %add-owned group-path)
`(poke-md-hook %add-synced entity.u.rid group-path)
==
=/ new-state-1=state-1
%* . *state-1
associations (migrate-associations associations.old)
group-indices (migrate-group-indices group-indices.old)
app-indices (migrate-app-indices app-indices.old)
resource-indices (migrate-resource-indices resource-indices.old)
==
$(old new-state-1)
::
++ rebuild-resource-indices
|= =^associations
%- ~(gas ju *(jug md-resource group-path))
%+ turn ~(tap in ~(key by associations))
|= [g=group-path r=md-resource]
^- [md-resource group-path]
|= =associations:store
%- ~(gas by *(map md-resource:store resource))
%+ turn ~(tap by associations)
|= [r=md-resource:store g=resource =metadatum:store]
[r g]
::
++ rebuild-group-indices
|= =^associations
%- ~(gas ju *(jug group-path md-resource))
~(tap in ~(key by associations))
|= =associations:store
%- ~(gas ju *(jug resource md-resource:store))
%+ turn
~(tap by associations)
|= [r=md-resource:store g=resource =metadatum:store]
[g r]
::
++ rebuild-app-indices
|= =^associations
%- ~(gas ju *(jug app-name [group-path app-path]))
%+ turn ~(tap in ~(key by associations))
|= [g=group-path r=md-resource]
^- [app-name [group-path app-path]]
[app-name.r [g app-path.r]]
|= =associations:store
%- ~(gas ju *(jug app-name:store [group=resource resource]))
%+ turn ~(tap by associations)
|= [r=md-resource:store g=resource =metadatum:store]
[app-name.r g resource.r]
::
++ migrate-app-to-graph-store
|= [app=@tas =^associations]
^+ associations
|= [app=@tas associations=associations-1]
^- associations-1
%- malt
%+ turn ~(tap by associations)
|= [[=group-path =md-resource] m=metadata]
^- [[^group-path ^md-resource] metadata]
|= [[=path md-resource=md-resource-1] m=metadata-1]
^- [[^path md-resource-1] metadata-1]
?. =(app-name.md-resource app)
[[group-path md-resource] m]
=/ new-app-path=path
?. ?=([@ @ ~] app-path.md-resource)
app-path.md-resource
ship+app-path.md-resource
[[group-path [%graph new-app-path]] m(module app)]
::
++ poke-md-hook
|= act=metadata-hook-action
^- card
=/ =cage metadata-hook-action+!>(act)
[%pass / %agent [our.bowl %metadata-hook] %poke cage]
::
++ new-group-path
|= =group-path
ship+(new-app-path group-path)
::
++ new-app-path
|= =app-path
^- path
?> ?=(^ app-path)
?:(=('~' i.app-path) t.app-path app-path)
::
++ old-md-to-new
|= m=metadata-0
^- metadata
%* . *metadata
title title.m
description description.m
color color.m
date-created date-created.m
creator creator.m
module *term
==
::
++ migrate-md-resource
|= md-resource
^- md-resource
?: =(%chat app-name) [%chat (new-app-path app-path)]
?: =(%contacts app-name) [%contacts ship+app-path]
[app-name app-path]
::
++ migrate-resource-indices
|= resource-indices=(jug md-resource group-path)
^- (jug md-resource group-path)
%- malt
%+ turn ~(tap by resource-indices)
|= [=md-resource paths=(set group-path)]
:- (migrate-md-resource md-resource)
(~(run in paths) new-group-path)
::
++ migrate-app-indices
|= app-indices=(jug app-name [group-path app-path])
%- malt
%+ turn ~(tap by app-indices)
|= [app=term indices=(set [=group-path =app-path])]
:- app
%- ~(run in indices)
|= [=group-path =app-path]
:- (new-group-path group-path)
?: =(%chat app) (new-app-path app-path)
?: =(%contacts app) ship+app-path
app-path
::
++ migrate-group-indices
|= group-indices=(jug group-path md-resource)
%- malt
%+ turn ~(tap by group-indices)
|= [=group-path resources=(set md-resource)]
:- (new-group-path group-path)
%- sy
%+ turn ~(tap in resources)
migrate-md-resource
::
++ migrate-associations
|= associations=associations-0
%- malt
%+ turn ~(tap by associations)
|= [[g=group-path r=md-resource] m=metadata-0]
:_ m
[(new-group-path g) (migrate-md-resource r)]
[[path md-resource] m]
=/ new-path=^path
?. ?=([@ @ ~] path.md-resource)
path.md-resource
ship+path.md-resource
[[path [%graph new-path]] m(module app)]
--
++ poke-metadata-action
|= act=metadata-action
++ poke-metadata-update
|= upd=update:store
^- (quip card _state)
?> (team:title our.bowl src.bowl)
?- -.act
%add (handle-add group-path.act resource.act metadata.act)
%remove (handle-remove group-path.act resource.act)
?> (team:title [our src]:bowl)
?+ -.upd !!
%add (handle-add +.upd)
%remove (handle-remove +.upd)
%initial-group (handle-initial-group +.upd)
==
::
++ poke-import
|= arc=*
^- (quip card _state)
|^
(on-load !>([%5 (remake-metadata ;;(tree-metadata +.arc))]))
=^ cards state
(on-load !>([%7 (remake-metadata ;;(tree-metadata +.arc))]))
:_ state
%+ weld cards
%+ turn ~(tap in ~(key by group-indices))
|= rid=resource
%- poke-our
?: =(entity.rid our.bowl)
:- %metadata-push-hook
push-hook-action+!>([%add rid])
:- %metadata-pull-hook
pull-hook-action+!>([%add [entity .]:rid])
::
++ poke-our
|= [app=term =cage]
^- card
[%pass / %agent [our.bowl app] %poke cage]
::
+$ tree-metadata
$: associations=(tree [[group-path md-resource] metadata])
group-indices=(tree [group-path (tree md-resource)])
app-indices=(tree [app-name (tree [group-path app-path])])
resource-indices=(tree [md-resource (tree group-path)])
$: associations=(tree [md-resource:store [resource metadatum:store]])
~
==
::
++ remake-metadata
|= tm=tree-metadata
^- base-state-1
^- base-state-2
:* (remake-map associations.tm)
(remake-jug group-indices.tm)
(remake-jug app-indices.tm)
(remake-jug resource-indices.tm)
~
==
--
::
++ handle-add
|= [=group-path =md-resource =metadata]
|= [group=resource =md-resource:store =metadatum:store]
^- (quip card _state)
:- %+ send-diff app-name.md-resource
?: (~(has by resource-indices) md-resource)
[%update-metadata group-path md-resource metadata]
[%add group-path md-resource metadata]
[%add group md-resource metadatum]
%= state
associations
(~(put by associations) [group-path md-resource] metadata)
::
group-indices
(~(put ju group-indices) group-path md-resource)
(~(put by associations) md-resource [group metadatum])
::
app-indices
%+ ~(put ju app-indices)
app-name.md-resource
[group-path app-path.md-resource]
[group resource.md-resource]
::
resource-indices
(~(put ju resource-indices) md-resource group-path)
(~(put by resource-indices) md-resource group)
::
group-indices
(~(put ju group-indices) group md-resource)
==
::
++ handle-remove
|= [=group-path =md-resource]
|= [group=resource =md-resource:store]
^- (quip card _state)
:- (send-diff app-name.md-resource [%remove group-path md-resource])
:- (send-diff app-name.md-resource [%remove group md-resource])
%= state
associations
(~(del by associations) [group-path md-resource])
::
group-indices
(~(del ju group-indices) group-path md-resource)
(~(del by associations) md-resource)
::
app-indices
%+ ~(del ju app-indices)
app-name.md-resource
[group-path app-path.md-resource]
[group resource.md-resource]
::
resource-indices
(~(del ju resource-indices) md-resource group-path)
(~(del by resource-indices) md-resource)
::
group-indices
(~(del ju group-indices) group md-resource)
==
::
++ handle-initial-group
|= [group=resource =associations:store]
=/ assocs=(list [=md-resource:store grp=resource =metadatum:store])
~(tap by associations)
=| cards=(list card)
|-
?~ assocs
[cards state]
=, assocs
?> =(group grp.i)
=^ new-cards state
(handle-add group [md-resource metadatum]:i)
$(cards (weld cards new-cards), assocs t)
::
++ metadata-for-app
|= =app-name
^- ^associations
%- ~(gas by *^associations)
%+ turn ~(tap in (~(gut by app-indices) app-name ~))
|= [=group-path =app-path]
:- [group-path [app-name app-path]]
(~(got by associations) [group-path [app-name app-path]])
|= =app-name:store
^+ associations
%+ roll ~(tap in (~(gut by app-indices) app-name ~))
|= [[group=resource rid=resource] out=associations:store]
=/ =md-resource:store
[app-name rid]
=/ [resource =metadatum:store]
(~(got by associations) md-resource)
(~(put by out) md-resource [group metadatum])
::
++ metadata-for-group
|= =group-path
^- ^associations
%- ~(gas by *^associations)
%+ turn ~(tap in (~(gut by group-indices) group-path ~))
|= =md-resource
:- [group-path md-resource]
(~(got by associations) [group-path md-resource])
|= group=resource
=/ resources=(set md-resource:store)
(~(get ju group-indices) group)
%+ roll
~(tap in resources)
|= [=md-resource:store out=associations:store]
=/ [resource =metadatum:store]
(~(got by associations) md-resource)
(~(put by out) md-resource [group metadatum])
::
++ send-diff
|= [=app-name upd=metadata-update]
|= [=app-name:store =update:store]
^- (list card)
|^
%- zing
:~ (update-subscribers /all upd)
(update-subscribers /updates upd)
(update-subscribers [%app-name app-name ~] upd)
:~ (update-subscribers /all update)
(update-subscribers /updates update)
(update-subscribers [%app-name app-name ~] update)
==
::
++ update-subscribers
|= [pax=path upd=metadata-update]
|= [pax=path =update:store]
^- (list card)
[%give %fact ~[pax] %metadata-update !>(upd)]~
[%give %fact ~[pax] %metadata-update !>(update)]~
--
--

290
pkg/arvo/app/sane.hoon Normal file
View File

@ -0,0 +1,290 @@
:: %sane: sanity checker for the landscape suite of applications
::
:: Userspace currently uses certain identifiers as foreign keys, and
:: expects those foreign keys to exist in a number of locations.
::
:: These foreign key relationships are prone to breaking during OTAs
:: and there are enough of them that they rarely get tested for
:: manually. %sane is a gall app that will check the validity of
:: these relationships, and fix them if asked.
::
:: Sane has a companion thread, -sane, which should be run instead
:: of attempting :sane %fix directly from the dojo.
::
:: Pokes:
:: %fix - Find issues and fix them
:: %check - Find issues and print them
::
:: Currently validates:
:: - Entries in {contact,metadata,group} stores are in sync with
:: their hooks
:: - Each group has its associated metadata and contacts
:: - Each graph is being synced
:: - Each chat is being synced
::
/- *metadata-store, contacts=contact-store, *group
/+ default-agent, verb, dbug, resource, graph, mdl=metadata, group
~% %sane-app ..card ~
|%
+$ card card:agent:gall
::
+$ state-zero [%0 ~]
::
+$ issue
$% [%lib-pull-hook-desync app=term =resource]
[%lib-push-hook-desync app=term =resource]
[%contact-hook-desync =path]
[%dangling-md =resource]
==
::
+$ issues
(list issue)
::
+$ action ?(%check %fix)
--
::
=| state-zero
=* state -
::
%- agent:dbug
%+ verb |
^- agent:gall
=<
|_ =bowl:gall
+* this .
sane-core +>
sc ~(. sane-core bowl)
def ~(. (default-agent this %|) bowl)
::
++ on-init
^- (quip card _this)
`this
++ on-save !>(state)
::
++ on-load
|= =vase
`this
::
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?. =(%noun mark)
(on-poke:def mark vase)
=/ act=action !<(action vase)
=^ cards state
?- act
%fix fix-sane:sc
%check print-sane:sc
==
[cards this]
::
++ on-agent on-agent:def
++ on-watch on-watch:def
++ on-leave on-leave:def
::
++ on-peek
|= =path
^- (unit (unit cage))
?. ?=([%x %bad-path ~] path)
(on-peek:def path)
~
::
++ on-arvo on-arvo:def
++ on-fail on-fail:def
--
::
|_ =bowl:gall
::
++ gra ~(. graph bowl)
::
++ md ~(. mdl bowl)
::
++ grp ~(. group bowl)
::
++ foreign-keys
|_ =issues
++ fk-core .
::
++ abet
^+ issues
issues
::
++ abet-fix
^- (list card)
(zing (turn issues fix-issue))
::
++ report
|= =issue
fk-core(issues (snoc issues issue))
::
++ report-many
|= many=^issues
fk-core(issues (weld issues many))
::
++ check-all
=> (lib-hooks-desync %group scry-groups)
=> (lib-hooks-desync %graph get-keys:gra)
=> (lib-hooks-desync %metadata scry-groups)
=> groups
metadata
::
++ groups
^+ fk-core
=/ groups=(list resource)
~(tap in scry-groups)
|-
?~ groups
fk-core
=* group i.groups
=? fk-core &((is-managed:grp group) !(~(has in scry-contact-syncs) group))
(report %contact-hook-desync (en-path:resource group))
$(groups t.groups)
::
++ metadata
^+ fk-core
=/ md-groups=(list resource)
~(tap in ~(key by md-group-indices))
|-
?~ md-groups
fk-core
=? fk-core !(~(has in scry-groups) i.md-groups)
(report %dangling-md i.md-groups)
$(md-groups t.md-groups)
::
++ lib-hooks-desync
|= [app=term storing=(set resource)]
^+ fk-core
=/ tracking
(tracking-pull-hook (pull-hook-name app))
=/ sharing
(sharing-push-hook (push-hook-name app))
=/ resources
~(tap in storing)
|-
?~ resources
fk-core
=* rid i.resources
=? fk-core &(=(our.bowl entity.rid) !(~(has in sharing) rid))
(report %lib-push-hook-desync (push-hook-name app) rid)
=? fk-core &(!=(our.bowl entity.rid) !(~(has in tracking) rid))
(report %lib-pull-hook-desync (pull-hook-name app) rid)
$(resources t.resources)
--
::
++ pull-hook-name
|= app=term
:(join-cord app '-' %pull-hook)
::
++ push-hook-name
|= app=term
:(join-cord app '-' %push-hook)
::
++ fix-sane
^- (quip card _state)
=/ cards=(list card)
=> foreign-keys
=> check-all
abet-fix
[cards state]
::
++ print-sane
^- (quip card _state)
=/ =issues
=> foreign-keys
=> check-all
abet
~& issues
`state
::
++ fix-issue
|= =issue
|^
^- (list card)
?- -.issue
::
%lib-pull-hook-desync
=* rid resource.issue
(poke-our app.issue pull-hook-action+!>([%add entity.rid rid]))^~
::
%lib-push-hook-desync
(poke-our app.issue push-hook-action+!>([%add resource.issue]))^~
::
%contact-hook-desync
=/ rid=resource
(de-path:resource path.issue)
=/ act
?: =(entity.rid our.bowl)
[%add-owned path.issue]
[%add-synced entity.rid path.issue]
(poke-our %contact-hook contact-hook-action+!>(act))^~
::
%dangling-md
=/ app-indices
(~(get ju md-group-indices) resource.issue)
%+ turn
~(tap in app-indices)
|= =md-resource
^- card
(poke-our %metadata-store metadata-action+!>([%remove resource.issue md-resource]))
==
::
++ poke-our
|= [app=term =cage]
^- card
[%pass /fix %agent [our.bowl app] %poke cage]
--
::
++ join-cord
(cury cat 3)
::
++ scry-groups
(scry ,(set resource) /y/group-store/groups)
::
++ tracking-pull-hook
|= hook=term
%+ scry
,(set resource)
/x/[hook]/tracking/noun
::
++ sharing-push-hook
|= hook=term
%+ scry
,(set resource)
/x/[hook]/sharing/noun
::
++ scry-contact-syncs
^- (set resource)
=- (~(run in -) de-path:resource)
%+ scry
,(set path)
/x/contact-hook/synced/noun
::
++ scry-chat-syncs
^- (set path)
%+ scry
,(set path)
/x/chat-hook/synced/noun
::
++ scry-chats
^- (set path)
%+ scry
,(set path)
/x/chat-store/keys/noun
::
::
++ md-group-indices
(scry (jug resource md-resource) /y/metadata-store/group-indices)
::
++ scry
|* [=mold =path]
^- mold
?> ?=(^ path)
?> ?=(^ t.path)
.^ mold
(cat 3 %g i.path)
(scot %p our.bowl)
i.t.path
(scot %da now.bowl)
t.t.path
==
--

View File

@ -0,0 +1,167 @@
/- *settings
/+ verb, dbug, default-agent
|%
+$ card card:agent:gall
+$ versioned-state
$% state-0
==
+$ state-0
$: %0
=settings
==
--
=| state-0
=* state -
::
%- agent:dbug
%+ verb |
^- agent:gall
=<
|_ bol=bowl:gall
+* this .
do ~(. +> bol)
def ~(. (default-agent this %|) bol)
::
++ on-init on-init:def
::
++ on-save !>(state)
::
++ on-load
|= =old=vase
^- (quip card _this)
=/ old !<(versioned-state old-vase)
?- -.old
%0 [~ this(state old)]
==
::
++ on-poke
|= [mar=mark vas=vase]
^- (quip card _this)
?> (team:title our.bol src.bol)
?. ?=(%settings-event mar)
(on-poke:def mar vas)
=/ evt=event !<(event vas)
=^ cards state
?- -.evt
%put-bucket (put-bucket:do key.evt bucket.evt)
%del-bucket (del-bucket:do key.evt)
%put-entry (put-entry:do buc.evt key.evt val.evt)
%del-entry (del-entry:do buc.evt key.evt)
==
[cards this]
::
++ on-watch
|= pax=path
^- (quip card _this)
?> (team:title our.bol src.bol)
?+ pax (on-watch:def pax)
[%all ~]
[~ this]
::
[%bucket @ ~]
=* bucket-key i.t.pax
?> (~(has by settings) bucket-key)
[~ this]
::
[%entry @ @ ~]
=* bucket-key i.t.pax
=* entry-key i.t.t.pax
=/ bucket (~(got by settings) bucket-key)
?> (~(has by bucket) entry-key)
[~ this]
==
::
++ on-peek
|= pax=path
^- (unit (unit cage))
?+ pax (on-peek:def pax)
[%x %all ~]
``settings-data+!>(all+settings)
::
[%x %bucket @ ~]
=* buc i.t.t.pax
=/ bucket=(unit bucket) (~(get by settings) buc)
?~ bucket [~ ~]
``settings-data+!>(bucket+u.bucket)
::
[%x %entry @ @ ~]
=* buc i.t.t.pax
=* key i.t.t.t.pax
=/ =bucket (fall (~(get by settings) buc) ~)
=/ entry=(unit val) (~(get by bucket) key)
?~ entry [~ ~]
``settings-data+!>(entry+u.entry)
==
::
++ on-agent on-agent:def
++ on-leave on-leave:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
--
::
|_ bol=bowl:gall
::
:: +put-bucket: put a bucket in the top level settings map, overwriting if it
:: already exists
::
++ put-bucket
|= [=key =bucket]
^- (quip card _state)
=/ pas=(list path)
:~ /all
/bucket/[key]
==
:- [(give-event pas %put-bucket key bucket)]~
state(settings (~(put by settings) key bucket))
::
:: +del-bucket: delete a bucket from the top level settings map
::
++ del-bucket
|= =key
^- (quip card _state)
=/ pas=(list path)
:~ /all
/bucket/[key]
==
:- [(give-event pas %del-bucket key)]~
state(settings (~(del by settings) key))
::
:: +put-entry: put an entry in a bucket, overwriting if it already exists
:: if bucket does not yet exist, create it
::
++ put-entry
|= [buc=key =key =val]
^- (quip card _state)
=/ pas=(list path)
:~ /all
/bucket/[buc]
/entry/[buc]/[key]
==
=/ =bucket (fall (~(get by settings) buc) ~)
=. bucket (~(put by bucket) key val)
:- [(give-event pas %put-entry buc key val)]~
state(settings (~(put by settings) buc bucket))
::
:: +del-entry: delete an entry from a bucket, fail quietly if bucket does not
:: exist
::
++ del-entry
|= [buc=key =key]
^- (quip card _state)
=/ pas=(list path)
:~ /all
/bucket/[buc]
/entry/[buc]/[key]
==
=/ bucket=(unit bucket) (~(get by settings) buc)
?~ bucket
[~ state]
=. u.bucket (~(del by u.bucket) key)
:- [(give-event pas %del-entry buc key)]~
state(settings (~(put by settings) buc u.bucket))
::
++ give-event
|= [pas=(list path) evt=event]
^- card
[%give %fact pas %settings-event !>(evt)]
--

126
pkg/arvo/gen/tally.hoon Normal file
View File

@ -0,0 +1,126 @@
/- gr=group, md=metadata-store, ga=graph-store
/+ re=resource
!:
:- %say
|= $: [now=@da eny=@uvJ =beak]
args=?(~ [shy=? ~])
~
==
::
=/ shy=? ?~(args & shy.args)
=* our=@p p.beak
::
|^
=; out=(list @t)
:- %tang
%- flop ::NOTE tang is bottom-up
:* ''
'tallied your activity score! find the results below.'
::
?: shy
'to show non-anonymized resource identifiers, +tally |'
'showing plain resource identifiers, share with care.'
::
'counted from groups and channels that you are hosting.'
'groups are listed with their member count.'
'channels are listed with activity from the past week:'
' - amount of top-level content'
' - amount of unique authors'
''
(snoc out '')
==
:: gather local non-dm groups, sorted by size
::
=/ groups=(list [local=? resource:re members=@ud])
%+ murn
%~ tap in
%~ key by
dir:(scry arch %y %group-store /groups)
|= i=@ta
=/ r=resource:re (de-path:re (stab i))
=/ g=(unit group:gr)
%+ scry (unit group:gr)
[%x %group-store [%groups (snoc (en-path:re r) %noun)]]
?: |(?=(~ g) hidden.u.g)
~
`[=(our entity.r) r ~(wyt in members.u.g)]
=/ crowds=(list [resource:re @ud])
%+ sort (turn (skim groups head) tail)
|= [[* a=@ud] [* b=@ud]]
(gth a b)
:: gather local per-group channels
::
=/ channels=(map resource:re (list [module=term =resource:re]))
%- ~(gas by *(map resource:re (list [module=term =resource:re])))
%+ turn crowds
|= [r=resource:re *]
:- r
%+ murn
%~ tap by
%+ scry associations:md
[%x %metadata-store [%group (snoc (en-path:re r) %noun)]]
|= [[* m=md-resource:md] metadata:md]
::NOTE we only count graphs for now
?. &(=(%graph app-name.m) =(our creator)) ~
`[module (de-path:re app-path.m)]
:: count activity per channel
::
=/ activity=(list [resource:re members=@ud (list [resource:re mod=term week=@ud authors=@ud])])
%+ turn crowds
|= [g=resource:re m=@ud]
:+ g m
%+ turn (~(got by channels) g)
|= [m=term r=resource:re]
:+ r m
::NOTE graph-store doesn't use the full resource-style path here!
=/ upd=update:ga
%+ scry update:ga
[%x %graph-store /graph/(scot %p entity.r)/[name.r]/noun]
?> ?=(%add-graph -.q.upd)
=/ mo ((ordered-map atom node:ga) gth)
=/ week=(list [@da node:ga])
(tap:mo (subset:mo graph.q.upd ~ `(sub now ~d7)))
:- (lent week)
%~ wyt in
%+ roll week
|= [[* [author=ship *] *] a=(set ship)]
(~(put in a) author)
:: render results
::
:- (tac 'the date is ' (scot %da now))
:- :(tac 'you are in ' (render-number (lent groups)) ' group(s):')
:- =- (roll - tac)
%+ join ', '
%+ turn groups
|=([* r=resource:re *] (render-resource r))
:- :(tac 'you are hosting ' (render-number (lent crowds)) ' group(s):')
%- zing
%+ turn activity
|= [g=resource:re m=@ud chans=(list [resource:re term @ud @ud])]
^- (list @t)
:- :(tac 'group, ' (render-resource g) ', ' (render-number m))
%+ turn chans
|= [c=resource:re m=term w=@ud a=@ud]
;: tac ' chan, '
(render-resource c) ', '
m ', '
(render-number w) ', '
(render-number a)
==
::
++ scry
|* [=mold care=term app=term =path]
.^(mold (tac %g care) (scot %p our) app (scot %da now) path)
::
++ tac (cury cat 3)
::
++ render-resource
|= r=resource:re
?: shy
(crip ((x-co:co 8) (mug r)))
:(tac (scot %p entity.r) '/' name.r)
::
++ render-number
|= n=@ud
(crip ((d-co:co 1) n))
--

View File

@ -129,6 +129,27 @@
++ launch 4.601.630
++ public launch
--
::
:: Local contract addresses
::
:: These addresses are only reproducible if you use the deploy
:: script in bridge
::
++ local-contracts
|%
++ ecliptic
0x56db.68f2.9203.ff44.a803.faa2.404a.44ec.bb7a.7480
++ azimuth
0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381
++ delegated-sending
0xb71c.0b6c.ee1b.cae5.6dfe.95cd.9d3e.41dd.d7ea.fc43
++ linear-star-release
0x3c3.dc12.be65.8158.d1d7.f9e6.6e08.ec40.99c5.68e4
++ conditional-star-release
0x35eb.3b10.2d9c.1b69.ac14.69c1.b1fe.1799.850c.d3eb
++ launch 0
++ public 0
--
::
:: ++ azimuth 0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381 :: local bridge
:: hashes of ship event signatures

View File

@ -49,6 +49,14 @@
?> ?=(^ nodes.q.update)
q.n.nodes.q.update
::
++ check-node-existence
|= [res=resource =index:store]
^- ?
%+ scry-for ,?
%+ weld
/node-exists/(scot %p entity.res)/[name.res]
(turn index (cury scot %ud))
::
++ get-update-log
|= rid=resource
^- update-log:store

View File

@ -1,16 +1,18 @@
/- *group, *metadata-store
/- *group
/+ store=group-store, resource
::
|_ =bowl:gall
+$ card card:agent:gall
++ scry-for
|* [=mold =path]
=. path
(snoc path %noun)
.^ mold
%gx
(scot %p our.bowl)
%group-store
(scot %da now.bowl)
(snoc `^path`path %noun)
path
==
++ scry-tag
|= [rid=resource =tag]
@ -21,38 +23,27 @@
~
`(~(gut by tags.u.group) tag ~)
::
++ scry-group-path
|= =path
%+ scry-for
(unit group)
[%groups path]
::
++ scry-group
|= rid=resource
%- scry-group-path
(en-path:resource rid)
%+ scry-for ,(unit group)
`path`groups+(en-path:resource rid)
::
++ members
|= rid=resource
%- members-from-path
(en-path:resource rid)
::
++ members-from-path
|= =group-path
^- (set ship)
=- members:(fall - *group)
(scry-group-path group-path)
=; =group
members.group
(fall (scry-group rid) *group)
::
++ is-member
|= [=ship =group-path]
|= [=ship group=resource]
^- ?
=- (~(has in -) ship)
(members-from-path group-path)
(members group)
::
++ is-admin
|= [=ship =group-path]
|= [=ship group=resource]
^- ?
=/ tags tags:(fall (scry-group-path group-path) *group)
=/ tags tags:(fall (scry-group group) *^group)
=/ admins=(set ^ship) (~(gut by tags) %admin ~)
(~(has in admins) ship)
:: +role-for-ship: get role for user
@ -85,31 +76,18 @@
[~ ~]
~
::
++ can-join-from-path
|= [=path =ship]
%+ scry-for
?
%+ welp
[%groups path]
/join/[(scot %p ship)]
::
++ can-join
|= [rid=resource =ship]
%+ can-join-from-path
(en-path:resource rid)
ship
::
++ is-managed-path
|= =path
^- ?
=/ group=(unit group)
(scry-group-path path)
?~ group %.n
!hidden.u.group
%+ scry-for ,?
^- path
:- %groups
(weld (en-path:resource rid) /join/(scot %p ship))
::
++ is-managed
|= rid=resource
%- is-managed-path
(en-path:resource rid)
=/ group=(unit group)
(scry-group rid)
?~ group %.n
!hidden.u.group
::
--

View File

@ -245,11 +245,9 @@
|= =(list ^group-contents)
^- json
:- %a
%+ murn list
%+ turn list
|= =^group-contents
?. ?=(?(%add-members %remove-members) -.group-contents)
~
`(update:enjs:group-store group-contents)
(update:enjs:group-store group-contents)
--
::
++ indexed-notification

View File

@ -93,7 +93,6 @@
%contact-hook
%contact-view
%metadata-store
%metadata-hook
%s3-store
%file-server
%glob
@ -105,6 +104,8 @@
%hark-group-hook
%hark-chat-hook
%observe-hook
%metadata-push-hook
%metadata-pull-hook
==
::
++ deft-fish :: default connects
@ -247,6 +248,8 @@
=> (se-born | %home %hark-chat-hook)
=> (se-born | %home %hark-store)
=> (se-born | %home %observe-hook)
=> (se-born | %home %metadata-pull-hook)
=> (se-born | %home %metadata-push-hook)
(se-born | %home %herm)
..on-load
::

View File

@ -1,119 +0,0 @@
/- *metadata-store
|%
++ associations-to-json
|= =associations
=, enjs:format
^- json
%- pairs
%+ turn ~(tap by associations)
|= [[=group-path =md-resource] =metadata]
^- [cord json]
:-
%- crip
;: weld
(trip (spat group-path))
(weld "/" (trip app-name.md-resource))
(trip (spat app-path.md-resource))
==
%- pairs
:~ [%group-path (path group-path)]
[%app-name s+app-name.md-resource]
[%app-path (path app-path.md-resource)]
[%metadata (metadata-to-json metadata)]
==
::
++ json-to-action
|= jon=json
^- metadata-action
=, dejs:format
=< (parse-json jon)
|%
++ parse-json
%- of
:~ [%add add]
[%remove remove]
==
::
++ add
%- ot
:~ [%group-path pa]
[%resource md-resource]
[%metadata metadata]
==
++ remove
%- ot
:~ [%group-path pa]
[%resource md-resource]
==
::
++ nu
|= jon=json
?> ?=([%s *] jon)
(rash p.jon hex)
::
++ metadata
%- ot
:~ [%title so]
[%description so]
[%color nu]
[%date-created (se %da)]
[%creator (su ;~(pfix sig fed:ag))]
[%module so]
==
++ md-resource
%- ot
:~ [%app-name so]
[%app-path pa]
==
--
::
++ metadata-to-json
|= met=metadata
^- json
=, enjs:format
%- pairs
:~ [%title s+title.met]
[%description s+description.met]
[%color s+(scot %ux color.met)]
[%date-created s+(scot %da date-created.met)]
[%creator s+(scot %p creator.met)]
[%module s+module.met]
==
::
++ update-to-json
|= upd=metadata-update
^- json
=, enjs:format
%+ frond %metadata-update
%- pairs
:~ ?- -.upd
%add
:- %add
%- pairs
:~ [%group-path (path group-path.upd)]
[%app-name s+app-name.resource.upd]
[%app-path (path app-path.resource.upd)]
[%metadata (metadata-to-json metadata.upd)]
==
::
%update-metadata
:- %update-metadata
%- pairs
:~ [%group-path (path group-path.upd)]
[%app-name s+app-name.resource.upd]
[%app-path (path app-path.resource.upd)]
[%metadata (metadata-to-json metadata.upd)]
==
::
%remove
:- %remove
%- pairs
:~ [%group-path (path group-path.upd)]
[%app-name s+app-name.resource.upd]
[%app-path (path app-path.resource.upd)]
==
::
%associations
[%associations (associations-to-json associations.upd)]
== ==
--

View File

@ -0,0 +1,163 @@
/- sur=metadata-store
/+ resource
^?
=< [. sur]
=, sur
|%
++ enjs
=, enjs:format
|%
++ associations
|= =^associations
=, enjs:format
^- json
%- pairs
%+ turn ~(tap by associations)
|= [=md-resource [group=resource =^metadatum]]
^- [cord json]
:-
%- crip
;: weld
(trip (spat (en-path:resource group)))
(weld "/" (trip app-name.md-resource))
(trip (spat (en-path:resource resource.md-resource)))
==
%- pairs
:~ [%group s+(enjs-path:resource group)]
[%app-name s+app-name.md-resource]
[%resource s+(enjs-path:resource resource.md-resource)]
[%metadata (^metadatum metadatum)]
==
::
++ metadatum
|= met=^metadatum
^- json
%- pairs
:~ [%title s+title.met]
[%description s+description.met]
[%color s+(scot %ux color.met)]
[%date-created s+(scot %da date-created.met)]
[%creator s+(scot %p creator.met)]
[%module s+module.met]
[%picture s+picture.met]
[%preview b+preview.met]
[%vip s+`@t`vip.met]
==
::
++ update
|= upd=^update
^- json
%+ frond %metadata-update
%- pairs
:~ ?+ -.upd *[cord json]
%add
:- %add
%- pairs
:~ [%group s+(enjs-path:resource group.upd)]
[%app-name s+app-name.resource.upd]
[%resource s+(enjs-path:resource resource.resource.upd)]
[%metadata (metadatum metadatum.upd)]
==
%updated-metadata
:- %add
%- pairs
:~ [%group s+(enjs-path:resource group.upd)]
[%app-name s+app-name.resource.upd]
[%resource s+(enjs-path:resource resource.resource.upd)]
[%metadata (metadatum metadatum.upd)]
==
::
%remove
:- %remove
%- pairs
:~ [%group s+(enjs-path:resource group.upd)]
[%app-name s+app-name.resource.upd]
[%resource s+(enjs-path:resource resource.resource.upd)]
==
::
%associations
[%associations (associations associations.upd)]
::
== ==
::
++ hook-update
|= upd=^hook-update
%+ frond %metadata-hook-update
%+ frond -.upd
%- pairs
?- -.upd
%preview
:~ [%group s+(enjs-path:resource group.upd)]
[%channels (associations channels.upd)]
[%members (numb members.upd)]
[%channel-count (numb channel-count.upd)]
[%metadata (metadatum metadatum.upd)]
==
%req-preview
~[group+s+(enjs-path:resource group.upd)]
==
--
::
++ dejs
=, dejs:format
|%
++ action
%- of
:~ [%add add]
[%remove remove]
[%initial-group initial-group]
==
::
++ initial-group
|= json
[%initial-group *resource *associations]
::
++ add
%- ot
:~ [%group dejs-path:resource]
[%resource md-resource]
[%metadata metadatum]
==
++ remove
%- ot
:~ [%group dejs-path:resource]
[%resource md-resource]
==
::
++ nu
|= jon=json
?> ?=([%s *] jon)
(rash p.jon hex)
::
++ vip
%- su
;~ pose
(tag %$)
(tag %reader-comments)
(tag %member-metadata)
==
::
++ metadatum
^- $-(json ^metadatum)
%- ot
:~ [%title so]
[%description so]
[%color nu]
[%date-created (se %da)]
[%creator (su ;~(pfix sig fed:ag))]
[%module so]
[%picture so]
[%preview bo]
[%vip vip]
==
::
++ tag |*(a=@tas (cold a (jest a)))
::
++ md-resource
^- $-(json ^md-resource)
%- ot
:~ [%app-name so]
[%resource dejs-path:resource]
==
--
--

View File

@ -1,61 +1,69 @@
:: metadata: helpers for getting data from the metadata-store
::
/- *metadata-store
/+ res=resource
/- store=metadata-store
/+ resource
::
|_ =bowl:gall
++ app-paths-from-group
|= [=app-name =group-path]
^- (list app-path)
|= [=app-name:store group=resource]
^- (list resource)
%+ murn
%~ tap in
=- (~(gut by -) group-path ~)
.^ (jug ^group-path md-resource)
=- (~(gut by -) group ~)
.^ (jug resource md-resource:store)
%gy
(scot %p our.bowl)
%metadata-store
(scot %da now.bowl)
/group-indices
==
|= =md-resource
^- (unit app-path)
|= =md-resource:store
^- (unit resource)
?. =(app-name.md-resource app-name) ~
`app-path.md-resource
`resource.md-resource
::
++ peek-metadata
|= [app-name=term =group=resource:res =app=resource:res]
^- (unit metadata)
=/ group-cord=cord (scot %t (spat (en-path:res group-resource)))
=/ app-cord=cord (scot %t (spat (en-path:res app-resource)))
=/ our=cord (scot %p our.bowl)
=/ now=cord (scot %da now.bowl)
.^ (unit metadata)
++ app-metadata-for-group
|= [group=resource =app-name:store]
=/ =associations:store
(metadata-for-group group)
%- ~(gas by *associations:store)
%+ skim ~(tap by associations)
|= [=md-resource:store association:store]
=(app-name app-name.md-resource)
::
++ metadata-for-group
|= group=resource
.^ associations:store
%gx (scot %p our.bowl) %metadata-store (scot %da now.bowl)
%metadata group-cord app-name app-cord /noun
%group (snoc (en-path:resource group) %noun)
==
::
++ group-from-app-resource
|= [app=term =app=resource:res]
^- (unit resource:res)
=/ app-path (en-path:res app-resource)
=/ group-paths (groups-from-resource app app-path)
?~ group-paths
~
`(de-path:res i.group-paths)
::
++ groups-from-resource
|= =md-resource
^- (list group-path)
=; resources
%~ tap in
%+ ~(gut by resources)
md-resource
*(set group-path)
.^ (jug ^md-resource group-path)
++ md-resources-from-group
|= group=resource
=- (~(get ju -) group)
.^ (jug resource md-resource:store)
%gy
(scot %p our.bowl)
%metadata-store
(scot %da now.bowl)
/resource-indices
/group-indices
==
::
++ peek-association
|= [app-name=term rid=resource]
.^ (unit association:store)
%gx (scot %p our.bowl) %metadata-store (scot %da now.bowl)
%metadata app-name (snoc (en-path:resource rid) %noun)
==
::
++ peek-metadatum
|= =md-resource:store
%+ bind (peek-association md-resource)
|=(association:store metadatum)
::
++ peek-group
|= =md-resource:store
^- (unit resource)
%+ bind (peek-association md-resource)
|=(association:store group)
--

View File

@ -232,14 +232,23 @@
++ on-poke
|= [=mark =vase]
^- [(list card:agent:gall) agent:gall]
?> (team:title our.bowl src.bowl)
?. =(mark %pull-hook-action)
?+ mark
=^ cards pull-hook
(on-poke:og mark vase)
[cards this]
=^ cards state
(poke-hook-action:hc !<(action vase))
[cards this]
::
%sane
?> (team:title [our src]:bowl)
=^ cards state
poke-sane:hc
[cards this]
::
%pull-hook-action
?> (team:title [our src]:bowl)
=^ cards state
(poke-hook-action:hc !<(action vase))
[cards this]
==
::
++ on-watch
|= =path
@ -285,7 +294,7 @@
(on-agent:og wire sign)
[cards this]
:_ this
~[(update-store:hc q.cage.sign)]
~[(update-store:hc rid q.cage.sign)]
==
++ on-leave
|= =path
@ -309,10 +318,47 @@
++ on-peek
|= =path
^- (unit (unit cage))
(on-peek:og path)
?. =(/x/tracking path)
(on-peek:og path)
``noun+!>(~(key by tracking))
--
|_ =bowl:gall
+* og ~(. pull-hook bowl)
++ poke-sane
^- (quip card:agent:gall _state)
=/ cards
restart-subscriptions
~? > ?=(^ cards)
"Fixed subscriptions in {<dap.bowl>}"
:_ state
restart-subscriptions
::
++ check-subscription
|= [rid=resource =ship]
^- ?
%+ lien
~(tap in ~(key by wex.bowl))
|= [=wire her=^ship app=term]
^- ?
?& =(app push-hook-name.config)
=(ship her)
=((scag 4 wire) /helper/pull-hook/pull/resource)
=(`rid (de-path-soft:resource (slag 4 wire)))
==
::
++ restart-subscriptions
^- (list card:agent:gall)
%- zing
%+ turn
~(tap by tracking)
|= [rid=resource =ship]
^- (list card:agent:gall)
?: (check-subscription rid ship) ~
~& >> "restarting: {<rid>}"
=/ pax=(unit path)
(on-pull-kick:og rid)
?~ pax ~
(watch-resource rid u.pax)
::
++ mule-scry
|= [ref=* raw=*]
@ -424,15 +470,24 @@
/helper/pull-hook
wire
::
++ get-conversion
.^ tube:clay
%cc (scot %p our.bowl) %home (scot %da now.bowl)
/[update-mark.config]/resource
==
::
++ give-update
^- card
[%give %fact ~[/tracking] %pull-hook-update !>(tracking)]
::
++ update-store
|= =vase
|= [wire-rid=resource =vase]
^- card
=/ =wire
(make-wire /store)
=+ !<(rid=resource (get-conversion vase))
?> =(src.bowl (~(got by tracking) rid))
?> =(wire-rid rid)
[%pass wire %agent [our.bowl store-name.config] %poke update-mark.config vase]
--
--

View File

@ -67,16 +67,6 @@
|* =config
$_ ^|
|_ bowl:gall
::
:: +resource-for-update: get affected resource from an update
::
:: Given a vase of the update, the mark of which is
:: update-mark.config, produce the affected resource, if any.
::
++ resource-for-update
|~ vase
*(unit resource)
::
:: +take-update: handle update from store
::
:: Given an update from the store, do other things after proxying
@ -175,9 +165,11 @@
|^
?- -.old
%1
=. cards
:_(cards (build-mark:hc %sing))
=^ og-cards push-hook
(on-load:og inner-state.old)
[(weld cards og-cards) this(state old)]
[(weld (flop cards) og-cards) this(state old)]
::
%0
%_ $
@ -274,17 +266,29 @@
=^ cards push-hook
(on-leave:og path)
[cards this]
::
++ on-arvo
|= [=wire =sign-arvo]
=^ cards push-hook
(on-arvo:og wire sign-arvo)
[cards this]
?. ?=([%helper %push-hook @ *] wire)
=^ cards push-hook
(on-arvo:og wire sign-arvo)
[cards this]
?. ?=(%resource-conversion i.t.t.wire)
(on-arvo:def wire sign-arvo)
:_ this
~[(build-mark:hc %next)]
::
++ on-fail
|= [=term =tang]
=^ cards push-hook
(on-fail:og term tang)
[cards this]
++ on-peek on-peek:og
++ on-peek
|= =path
^- (unit (unit cage))
?. =(/x/sharing path)
(on-peek:og path)
``noun+!>(sharing)
--
|_ =bowl:gall
+* og ~(. push-hook bowl)
@ -368,7 +372,7 @@
|= =vase
^- (list card:agent:gall)
=/ rid=(unit resource)
(resource-for-update:og vase)
(resource-for-update vase)
?~ rid ~
=/ prefix=path
resource+(en-path:resource u.rid)
@ -385,7 +389,7 @@
|= =vase
^- (list card:agent:gall)
=/ rid=(unit resource)
(resource-for-update:og vase)
(resource-for-update vase)
?~ rid ~
=/ =path
resource+(en-path:resource u.rid)
@ -394,5 +398,30 @@
=/ dap=term
?:(=(our.bowl entity.u.rid) store-name.config dap.bowl)
[%pass wire %agent [entity.u.rid dap] %poke update-mark.config vase]~
::
++ get-conversion
.^ tube:clay
%cc (scot %p our.bowl) %home (scot %da now.bowl)
/[update-mark.config]/resource
==
::
++ resource-for-update
|= update=vase
=/ =tube:clay
get-conversion
%+ bind
(mole |.((tube update)))
|=(=vase !<(resource vase))
::
++ build-mark
|= rav=?(%sing %next)
^- card
=/ =wire
(make-wire /resource-conversion)
=/ =mood:clay
[%c da+now.bowl /[update-mark.config]/resource]
=/ =rave:clay
?:(?=(%next rav) [rav mood] [rav mood])
[%pass wire %arvo %c %warp our.bowl [%home `rave]]
--
--

132
pkg/arvo/lib/settings.hoon Normal file
View File

@ -0,0 +1,132 @@
/- *settings
|%
++ enjs
=, enjs:format
|%
++ data
|= dat=^data
^- json
%+ frond -.dat
?- -.dat
%all (settings +.dat)
%bucket (bucket +.dat)
%entry (value +.dat)
==
::
++ settings
|= s=^settings
^- json
[%o (~(run by s) bucket)]
::
++ event
|= evt=^event
^- json
%+ frond -.evt
?- -.evt
%put-bucket (put-bucket +.evt)
%del-bucket (del-bucket +.evt)
%put-entry (put-entry +.evt)
%del-entry (del-entry +.evt)
==
::
++ put-bucket
|= [k=key b=^bucket]
^- json
%- pairs
:~ bucket-key+s+k
bucket+(bucket b)
==
::
++ del-bucket
|= k=key
^- json
%- pairs
:~ bucket-key+s+k
==
::
++ put-entry
|= [b=key k=key v=val]
^- json
%- pairs
:~ bucket-key+s+b
entry-key+s+k
value+(val v)
==
::
++ del-entry
|= [buc=key =key]
^- json
%- pairs
:~ bucket-key+s+buc
entry-key+s+key
==
::
++ value
|= =val
^- json
?- -.val
%s val
%b val
%n (numb p.val)
==
::
++ bucket
|= b=^bucket
^- json
[%o (~(run by b) value)]
--
::
++ dejs
=, dejs:format
|%
++ event
|= jon=json
^- ^event
%. jon
%- of
:~ put-bucket+put-bucket
del-bucket+del-bucket
put-entry+put-entry
del-entry+del-entry
==
::
++ put-bucket
%- ot
:~ bucket-key+so
bucket+bucket
==
::
++ del-bucket
%- ot
:~ bucket-key+so
==
::
++ put-entry
%- ot
:~ bucket-key+so
entry-key+so
value+val
==
::
++ del-entry
%- ot
:~ bucket-key+so
entry-key+so
==
::
++ value
|= jon=json
^- val
?+ -.jon !!
%s jon
%b jon
%n [%n (rash p.jon dem)]
==
::
++ bucket
|= jon=json
^- ^bucket
?> ?=([%o *] jon)
(~(run by p.jon) value)
--
--

View File

@ -238,6 +238,13 @@
`[%done ~]
==
::
++ raw-poke-our
|= [app=term =cage]
=/ m (strand ,~)
^- form:m
;< =bowl:spider bind:m get-bowl
(raw-poke [our.bowl app] cage)
::
++ poke-our
|= [=term =cage]
=/ m (strand ,~)

View File

@ -0,0 +1,10 @@
/- *contact-hook
|_ act=contact-hook-action
++ grab |%
++ noun contact-hook-action
--
++ grow |%
++ noun act
--
++ grad %noun
--

View File

@ -7,6 +7,13 @@
|%
++ noun upd
++ json (update:enjs upd)
++ resource
?+ -.q.upd !!
?(%run-updates %add-nodes %remove-nodes %add-graph) resource.q.upd
?(%remove-graph %archive-graph %unarchive-graph) resource.q.upd
?(%add-tag %remove-tag) resource.q.upd
?(%add-signatures %remove-signatures) resource.uid.q.upd
==
++ mime [/application/x-urb-graph-update (as-octs (jam upd))]
--
::

View File

@ -4,9 +4,13 @@
++ grow
|%
++ noun upd
++ resource
?< ?=(%initial -.upd)
resource.upd
::
++ json
%+ frond:enjs:format 'groupUpdate'
(update:enjs upd)
%+ frond:enjs:format 'groupUpdate'
(update:enjs upd)
--
++ grab
|%

View File

@ -0,0 +1,10 @@
/- *metadata-hook
|_ act=metadata-hook-action
++ grab |%
++ noun metadata-hook-action
--
++ grow |%
++ noun act
--
++ grad %noun
--

View File

@ -1,16 +1,14 @@
/+ *metadata-json
=, dejs:format
|_ act=metadata-action
/+ store=metadata-store
|_ =action:store
++ grad %noun
++ grow
|%
++ noun act
++ noun action
++ json update:enjs:store
--
++ grab
|%
++ noun metadata-action
++ json
|= jon=^json
(json-to-action jon)
++ noun action:store
++ json action:dejs:store
--
--

View File

@ -0,0 +1,15 @@
/+ store=metadata-store
|_ =hook-update:store
++ grad %noun
++ grow
|%
++ noun hook-update
++ json (hook-update:enjs:store hook-update)
--
::
++ grab
|%
++ noun hook-update:store
--
--

View File

@ -1,15 +1,19 @@
/+ *metadata-json
|_ upd=metadata-update
/+ store=metadata-store
|_ =update:store
++ grad %noun
++ grow
|%
++ noun upd
++ json (update-to-json upd)
++ noun update
++ resource
?> ?=(?(%add %remove %initial-group) -.update)
group.update
++ json (update:enjs:store update)
--
::
++ grab
|%
++ noun metadata-update
++ noun update:store
++ json action:dejs:store
--
::
--

View File

@ -0,0 +1,14 @@
/+ resource
|_ rid=resource
++ grad %noun
++ grow
|%
++ noun rid
++ json (enjs:resource rid)
--
++ grab
|%
++ noun resource
++ json dejs:resource
--
--

View File

@ -0,0 +1,13 @@
/+ *settings
|_ dat=data
++ grad %noun
++ grow
|%
++ noun dat
++ json (data:enjs dat)
--
++ grab
|%
++ noun data
--
--

View File

@ -0,0 +1,16 @@
/+ *settings
|_ evt=event
++ grad %noun
++ grow
|%
++ noun evt
++ json
%+ frond:enjs:format %settings-event
(event:enjs evt)
--
++ grab
|%
++ noun event
++ json event:dejs
--
--

View File

@ -34,7 +34,8 @@
==
::
+$ logged-update-0
$% [%add-nodes =resource nodes=(map index node)]
$% [%add-graph =resource =graph mark=(unit mark) overwrite=?]
[%add-nodes =resource nodes=(map index node)]
[%remove-nodes =resource indices=(set index)]
[%add-signatures =uid =signatures]
[%remove-signatures =uid =signatures]
@ -42,7 +43,6 @@
::
+$ update-0
$% logged-update-0
[%add-graph =resource =graph mark=(unit mark) overwrite=?]
[%remove-graph =resource]
::
[%add-tag =term =resource]

View File

@ -62,9 +62,7 @@
::
+$ group-contents
$~ [%add-members *resource ~]
$% $>(?(%add-members %remove-members) update:group-store)
metadata-action:metadata-store
==
$>(?(%add-members %remove-members) update:group-store)
::
+$ notification
[date=@da read=? =contents]

View File

@ -1,28 +1,64 @@
/- *resource
^?
|%
+$ group-path path
::
+$ app-name term
+$ app-path path
+$ md-resource [=app-name =app-path]
+$ associations (map [group-path md-resource] metadata)
+$ md-resource [=app-name =resource]
+$ association [group=resource =metadatum]
+$ associations (map md-resource association)
+$ group-preview
$: group=resource
channels=associations
members=@ud
channel-count=@ud
=metadatum
==
::
+$ color @ux
+$ metadata
+$ url @t
::
:: $vip-metadata: variation in permissions
::
:: This will be passed to the graph-permissions mark
:: conversion to allow for custom permissions.
::
:: %reader-comments: Allow readers to comment, regardless
:: of whether they can write. (notebook, collections)
:: %member-metadata: Allow members to add channels (groups)
:: %$: No variation
::
+$ vip-metadata ?(%reader-comments %member-metadata %$)
+$ metadatum
$: title=cord
description=cord
=color
date-created=time
creator=ship
module=term
picture=url
preview=?
vip=vip-metadata
==
::
+$ metadata-action
$% [%add =group-path resource=md-resource =metadata]
[%remove =group-path resource=md-resource]
+$ action
$% [%add group=resource resource=md-resource =metadatum]
[%remove group=resource resource=md-resource]
[%initial-group group=resource =associations]
==
::
+$ metadata-update
$% metadata-action
+$ hook-update
$% [%req-preview group=resource]
[%preview group-preview]
==
::
+$ update
$% action
[%associations =associations]
[%update-metadata =group-path resource=md-resource =metadata]
$: %updated-metadata
group=resource
resource=md-resource
before=metadatum
=metadatum
==
==
--

View File

@ -0,0 +1,21 @@
|%
+$ settings (map key bucket)
+$ bucket (map key val)
+$ key term
+$ val
$% [%s p=@t]
[%b p=?]
[%n p=@]
==
+$ event
$% [%put-bucket =key =bucket]
[%del-bucket =key]
[%put-entry buc=key =key =val]
[%del-entry buc=key =key]
==
+$ data
$% [%all =settings]
[%bucket =bucket]
[%entry =val]
==
--

View File

@ -180,6 +180,9 @@
$(index +(index), sorted [(~(got by fragments) index) sorted])
::
(cue (rep 13 (flop sorted)))
:: +jim: caching +jam
::
++ jim |=(n=* ~+((jam n)))
:: +bind-duct: find or make new $bone for .duct in .ossuary
::
++ bind-duct
@ -1931,7 +1934,7 @@
==
now
::
=/ =message-blob (dedup-message (jam payload))
=/ =message-blob (dedup-message (jim payload))
=. peer-core (run-message-pump bone %memo message-blob)
::
?: &(=(%boon valence) ?=(?(%dead %unborn) -.qos.peer-state))

View File

@ -66,11 +66,11 @@
module module.action
==
=/ =metadata-action
[%add group-path graph+(en-path:resource rid.action) metadata]
[%add group graph+rid.action metadata]
;< ~ bind:m
(poke-our %metadata-hook %metadata-action !>(metadata-action))
(poke-our %metadata-store %metadata-action !>(metadata-action))
;< ~ bind:m
(poke-our %metadata-hook %metadata-hook-action !>([%add-owned group-path]))
(poke-our %metadata-push-hook %push-hook-action !>([%add group]))
::
:: Send invites
::

View File

@ -33,7 +33,7 @@
(pure:m (need ugroup))
::
++ delete-graph
|= rid=resource
|= [group-rid=resource rid=resource]
=/ m (strand ,~)
^- form:m
;< =bowl:spider bind:m get-bowl:strandio
@ -43,12 +43,9 @@
(poke-our %graph-push-hook %push-hook-action !>([%remove rid]))
;< ~ bind:m
%+ poke-our %metadata-hook
metadata-hook-action+!>([%remove (en-path:resource rid)])
;< ~ bind:m
%+ poke-our %metadata-store
:- %metadata-action
!> :+ %remove
(en-path:resource rid)
(en-path:resource group-rid)
[%graph (en-path:resource rid)]
(pure:m ~)
--
@ -69,11 +66,14 @@
(scry-group u.ugroup-rid)
?. hidden.group
;< ~ bind:m
(delete-graph rid.action)
(delete-graph u.ugroup-rid rid.action)
(pure:m !>(~))
;< ~ bind:m
(poke-our %group-store %group-action !>([%remove-group rid.action ~]))
;< ~ bind:m
(poke-our %group-push-hook %push-hook-action !>([%remove rid.action]))
;< ~ bind:m (delete-graph rid.action)
;< ~ bind:m (delete-graph u.ugroup-rid rid.action)
;< ~ bind:m
%+ poke-our %metadata-hook
metadata-hook-action+!>([%remove (en-path:resource u.ugroup-rid)])
(pure:m !>(~))

View File

@ -11,31 +11,31 @@
|= rid=resource
=/ m (strand ,(unit resource))
^- form:m
;< pax=(unit (set path)) bind:m
%+ scry:strandio ,(unit (set path))
;< res=(unit resource) bind:m
%+ scry:strandio ,(unit resource)
;: weld
/gx/metadata-store/resource/graph
(en-path:resource rid)
/noun
==
%- pure:m
?~ pax ~
?~ u.pax ~
`(de-path:resource n.u.pax)
::
(pure:m res)
::
++ wait-for-group-join
|= rid=resource
=/ m (strand ,~)
^- form:m
=/ pax
(en-path:resource rid)
=/ hold=@dr ~s0..8000
|- ^- form:m
?> (lte hold ~m5)
=* loop $
;< u-group=(unit group) bind:m
(scry:strandio ,(unit group) (weld /gx/group-store/groups (snoc pax %noun)))
?^ u-group
(pure:m ~)
;< ~ bind:m (sleep:strandio `@dr`(div ~s1 2))
;< ~ bind:m (sleep:strandio hold)
=. hold (mul hold 2)
loop
::
++ wait-for-md
@ -44,13 +44,16 @@
^- form:m
=/ pax
(en-path:resource rid)
=/ hold=@dr ~s0..8000
|- ^- form:m
?> (lte hold ~m5)
=* loop $
;< groups=(set path) bind:m
(scry:strandio ,(set path) /gy/metadata-store/group-indices)
?: (~(has in groups) pax)
;< groups=(jug path md-resource) bind:m
(scry:strandio ,(jug path md-resource) /gy/metadata-store/group-indices)
?: (~(has by groups) pax)
(pure:m ~)
;< ~ bind:m (sleep:strandio `@dr`(div ~s1 2))
;< ~ bind:m (sleep:strandio hold)
=. hold (mul hold 2)
loop
--
::
@ -83,9 +86,8 @@
;< ~ bind:m (wait-for-group-join rid.action)
::
;< ~ bind:m
%+ poke-our %metadata-hook
metadata-hook-action+!>([%add-synced ship.action (en-path:resource rid.action)])
::
%+ poke-our %metadata-pull-hook
pull-hook-action+!>([%add ship.action rid.action])::
;< ~ bind:m (wait-for-md rid.action)
::
;< ~ bind:m

View File

@ -3,6 +3,7 @@
::
=* strand strand:spider
=* raw-poke raw-poke:strandio
=* raw-poke-our raw-poke-our:strandio
=* scry scry:strandio
::
^- thread:spider
@ -51,12 +52,12 @@
:: stop serving or syncing metadata associated with group
::
;< ~ bind:m
%+ raw-poke
[our.bowl %metadata-hook]
:- %metadata-hook-action
!>([%remove (en-path:res resource.update)])
:: get metadata associated with group
::
%- raw-poke-our
?: =(our.bowl entity.resource.update)
:- %metadata-push-hook
push-hook-action+!>([%remove resource.update])
:- %metadata-pull-hook
pull-hook-action+!>([%remove resource.update])
;< =associations:met bind:m
%+ scry associations:met
;: weld
@ -64,8 +65,8 @@
(en-path:res resource.update)
/noun
==
=/ entries=(list [g=group-path:met m=md-resource:met])
~(tap in ~(key by associations))
=/ entries=(list [m=md-resource:met g=resource:res =metadata:met])
~(tap by associations)
|- ^- form:m
=* loop $
?~ entries
@ -80,18 +81,16 @@
[%remove g.i.entries m.i.entries]
:: archive graph associated with group
::
=/ app-resource (de-path-soft:res app-path.m.i.entries)
?~ app-resource
loop(entries t.entries)
=* app-resource resource.m.i.entries
;< ~ bind:m
%+ raw-poke
[our.bowl %graph-store]
:- %graph-update
!> ^- update:gra
[%0 now.bowl [%archive-graph u.app-resource]]
[%0 now.bowl [%archive-graph app-resource]]
;< ~ bind:m
%+ raw-poke
[our.bowl %graph-pull-hook]
:- %pull-hook-action
!>([%remove u.app-resource])
!>([%remove app-resource])
loop(entries t.entries)

31
pkg/arvo/ted/sane.hoon Normal file
View File

@ -0,0 +1,31 @@
/- spider
/+ *strandio
=>
|%
++ strand strand:spider
::
++ supported-apps
^- (list term)
:~ %graph-pull-hook
%group-pull-hook
%group-push-hook
==
::
++ poke-all-sane
=/ m (strand ,~)
^- form:m
=/ apps supported-apps
|- =* loop $
?~ apps
(pure:m ~)
=* app i.apps
;< ~ bind:m (poke-our app sane+!>(%sane))
loop(apps t.apps)
--
::
^- thread:spider
|= vase
=/ m (strand ,vase)
;< ~ bind:m poke-all-sane
;< ~ bind:m (poke-our %sane noun+!>(%fix))
(pure:m !>("Done"))

View File

@ -0,0 +1,37 @@
# Official Urbit Docker Image
This is the official Docker image for [Urbit](https://urbit.org).
Urbit is a clean-slate OS and network for the 21st century.
## Using
To use this image, you should mount a volume with a keyfile, comet file, or existing pier at `/urbit`, and map ports
as described below.
### Volume Mount
This image expects a volume mounted at `/urbit`. This volume should initially contain one of
- A keyfile `<shipname>.key` for a galaxy, star, planet, or moon. See the setup instructions for Urbit for information on [obtaining a keyfile](https://urbit.org/using/install/).
* e.g. `sampel-palnet.key` for the planet `sampel-palnet`.
- An empty file with the extension `.comet`. This will cause Urbit to boot a [comet](https://urbit.org/docs/glossary/comet/) in a pier named for the `.comet` file (less the extension).
* e.g. starting with an empty file `my-urbit-bot.comet` will result in Urbit booting a comet into the pier
`my-urbit-bot` under your volume.
- An existing pier as a directory `<shipname>`. You can migrate an existing ship to a new docker container in this way by placing its pier under the volume.
* e.g. if your ship is `sampel-palnet` then you likely have a directory `sampel-palnet` whose path you pass to `./urbit` when starting. [Move your pier](https://urbit.org/using/operations/using-your-ship/#moving-your-pier) directory to the volume and then start the container.
The first two options result in Urbit attempting to boot either the ship named by the name of the keyfile, or a comet. In both cases, after that boot is successful, the `.key` or `.comet` file will be removed from the volume and the pier will take its place.
In consequence, it is safe to remove the container and start a new container which mounts the same volume, e.g. to upgrade the version of the urbit binary by running a later container version. It is also possible to stop the container and then move the pier away e.g. to a location where you will run it directly with the Urbit binary.
### Ports
The image includes `EXPOSE` directives for TCP port 80 and UDP port 34343. Port `80` is used for Urbit's HTTP interface for both [Landscape](https://urbit.org/docs/glossary/landscape/) and for [API calls](https://urbit.org/using/integrating-api/) to the ship. Port `34343` is used by [Ames](https://urbit.org/docs/glossary/ames/) for ship-to-ship communication.
You can either pass the `-P` flag to docker to map ports directly to the corresponding ports on the host, or map them individually with `-p` flags. For local testing the latter is often convenient, for instance to remap port 80 to an unprivileged port.
## Extending
You likely do not want to extend this image. External applications which interact with Urbit do so primarily via an HTTP API, which should be exposed as described above. For containerized applications using Urbit, it is more appropriate to use a container orchestration service such as Docker Compose or Kubernetes to run Urbit alongside other containers which will interface with its API.
## Development
The docker image is built by a Nix derivation in the [`nix/pkgs/docker-image/default.nix`](https://github.com/urbit/urbit/tree/master/nix/pkgs/docker-image/default.nix) file under the Urbit git repository.

View File

@ -96,7 +96,7 @@ module.exports = {
]
}
},
exclude: /node_modules/
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light)\/).*/
},
{
test: /\.css$/i,

View File

@ -26,7 +26,7 @@ module.exports = {
]
}
},
exclude: /node_modules/
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light)\/).*/
},
{
test: /\.css$/i,

File diff suppressed because it is too large Load Diff

View File

@ -4,83 +4,85 @@
"description": "",
"main": "index.js",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@babel/runtime": "^7.12.5",
"@reach/disclosure": "^0.10.5",
"@reach/menu-button": "^0.10.5",
"@reach/tabs": "^0.10.5",
"@tlon/indigo-light": "^1.0.3",
"@tlon/indigo-react": "1.2.15",
"@tlon/sigil-js": "^1.4.2",
"aws-sdk": "^2.726.0",
"@tlon/indigo-dark": "^1.0.6",
"@tlon/indigo-light": "^1.0.6",
"@tlon/indigo-react": "1.2.17",
"@tlon/sigil-js": "^1.4.3",
"aws-sdk": "^2.830.0",
"big-integer": "^1.6.48",
"classnames": "^2.2.6",
"codemirror": "^5.55.0",
"css-loader": "^3.5.3",
"file-saver": "^2.0.2",
"formik": "^2.1.4",
"immer": "^8.0.0",
"lodash": "^4.17.15",
"codemirror": "^5.59.2",
"css-loader": "^3.6.0",
"file-saver": "^2.0.5",
"formik": "^2.2.6",
"immer": "^8.0.1",
"lodash": "^4.17.20",
"markdown-to-jsx": "^6.11.4",
"moment": "^2.20.1",
"moment": "^2.29.1",
"mousetrap": "^1.6.5",
"mousetrap-global-bind": "^1.1.0",
"normalize-wheel": "1.0.1",
"oembed-parser": "^1.4.1",
"oembed-parser": "^1.4.5",
"prop-types": "^15.7.2",
"react": "^16.5.2",
"react": "^16.14.0",
"react-codemirror2": "^6.0.1",
"react-dom": "^16.8.6",
"react-dom": "^16.14.0",
"react-helmet": "^6.1.0",
"react-markdown": "^4.3.1",
"react-oembed-container": "^1.0.0",
"react-router-dom": "^5.0.0",
"react-virtuoso": "^0.20.0",
"react-router-dom": "^5.2.0",
"react-virtuoso": "^0.20.3",
"react-visibility-sensor": "^5.1.1",
"remark-breaks": "^2.0.1",
"remark-disable-tokenizers": "^1.0.24",
"style-loader": "^1.2.1",
"styled-components": "^5.1.0",
"style-loader": "^1.3.0",
"styled-components": "^5.1.1",
"styled-system": "^5.1.5",
"suncalc": "^1.8.0",
"urbit-ob": "^5.0.0",
"urbit-ob": "^5.0.1",
"yup": "^0.29.3",
"zustand": "^3.2.0"
"zustand": "^3.3.1"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.9.5",
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.10.5",
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.10.1",
"@types/lodash": "^4.14.155",
"@types/react": "^16.9.38",
"@types/react-dom": "^16.9.8",
"@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.2",
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@types/lodash": "^4.14.168",
"@types/react": "^16.14.2",
"@types/react-dom": "^16.9.10",
"@types/react-router-dom": "^5.1.7",
"@types/styled-components": "^5.1.7",
"@types/styled-system": "^5.1.10",
"@types/yup": "^0.29.7",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
"@types/yup": "^0.29.11",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-loader": "^8.2.2",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-root-import": "^6.5.0",
"babel-plugin-root-import": "^6.6.0",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.2",
"cross-env": "^7.0.3",
"eslint": "^6.8.0",
"eslint-plugin-react": "^7.19.0",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.2.0",
"eslint-plugin-react": "^7.22.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.1",
"moment-locales-webpack-plugin": "^1.2.0",
"react-hot-loader": "^4.12.21",
"sass": "^1.26.5",
"react-hot-loader": "^4.13.0",
"sass": "^1.32.5",
"sass-loader": "^8.0.2",
"typescript": "^3.9.7",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
},
"scripts": {
"lint": "eslint ./src/**/*.{js,ts,tsx}",

View File

@ -10,7 +10,7 @@
<!-- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> -->
<title>OS1</title>
<title>Landscape</title>
</head>
<body>

View File

@ -11,6 +11,7 @@ import LaunchApi from './launch';
import GraphApi from './graph';
import S3Api from './s3';
import {HarkApi} from './hark';
import SettingsApi from './settings';
export default class GlobalApi extends BaseApi<StoreState> {
local = new LocalApi(this.ship, this.channel, this.store);
@ -22,6 +23,7 @@ export default class GlobalApi extends BaseApi<StoreState> {
s3 = new S3Api(this.ship, this.channel, this.store);
graph = new GraphApi(this.ship, this.channel, this.store);
hark = new HarkApi(this.ship, this.channel, this.store);
settings = new SettingsApi(this.ship, this.channel, this.store);
constructor(
public ship: Patp,

View File

@ -3,8 +3,8 @@ import { StoreState } from '../store/type';
import { Patp, Path, PatpNoSig } from '~/types/noun';
import _ from 'lodash';
import {makeResource, resourceFromPath} from '../lib/group';
import {GroupPolicy, Enc, Post, NodeMap, Content} from '~/types';
import { numToUd, unixToDa, decToUd, deSig } from '~/logic/lib/util';
import {GroupPolicy, Enc, Post, NodeMap, Content, Resource} from '~/types';
import { numToUd, unixToDa, decToUd, deSig, resourceAsPath } from '~/logic/lib/util';
export const createBlankNodeWithChildPost = (
parentIndex: string = '',
@ -81,6 +81,8 @@ function moduleToMark(mod: string): string | undefined {
export default class GraphApi extends BaseApi<StoreState> {
joiningGraphs = new Set<string>();
private storeAction(action: any): Promise<any> {
return this.action('graph-store', 'graph-update', action)
}
@ -138,11 +140,19 @@ export default class GraphApi extends BaseApi<StoreState> {
joinGraph(ship: Patp, name: string) {
const resource = makeResource(ship, name);
const rid = resourceAsPath(resource);
if(this.joiningGraphs.has(rid)) {
return Promise.resolve();
}
this.joiningGraphs.add(rid);
return this.viewAction('graph-join', {
join: {
resource,
ship,
}
}).then(res => {
this.joiningGraphs.delete(rid);
return res;
});
}

View File

@ -75,8 +75,8 @@ export class HarkApi extends BaseApi<StoreState> {
return this.harkAction(
{ 'read-count': {
graph: {
graph: association['app-path'],
group: association['group-path'],
graph: association.resource,
group: association.group,
module: association.metadata.module,
description,
index: parent
@ -91,8 +91,8 @@ export class HarkApi extends BaseApi<StoreState> {
'read-each': {
index:
{ graph:
{ graph: association['app-path'],
group: association['group-path'],
{ graph: association.resource,
group: association.group,
description,
module: mod,
index: parent

View File

@ -1,18 +1,19 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { Path, Patp, Association, Metadata } from '~/types';
import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '~/types';
import {uxToHex} from '../lib/util';
export default class MetadataApi extends BaseApi<StoreState> {
metadataAdd(appName: string, appPath: Path, groupPath: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {
metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {
const creator = `~${this.ship}`;
return this.metadataAction({
add: {
'group-path': groupPath,
group,
resource: {
'app-path': appPath,
resource,
'app-name': appName
},
metadata: {
@ -21,7 +22,22 @@ export default class MetadataApi extends BaseApi<StoreState> {
color,
'date-created': dateCreated,
creator,
'module': moduleName
'module': moduleName,
preview: false,
picture: '',
permissions: ''
}
}
});
}
remove(appName: string, resource: string, group: string) {
return this.metadataAction({
remove: {
group,
resource: {
resource,
'app-name': appName
}
}
});
@ -29,19 +45,67 @@ export default class MetadataApi extends BaseApi<StoreState> {
update(association: Association, newMetadata: Partial<Metadata>) {
const metadata = {...association.metadata, ...newMetadata };
metadata.color = uxToHex(metadata.color);
return this.metadataAction({
add: {
'group-path': association['group-path'],
group: association.group,
resource: {
'app-path': association['app-path'],
'app-name': association['app-name'],
resource: association.resource,
'app-name': association['app-name']
},
metadata
}
});
}
preview(group: string) {
return new Promise<MetadataUpdatePreview>((resolve, reject) => {
const tempChannel: any = new (window as any).channel();
let done = false;
setTimeout(() => {
if(done) {
return;
}
done = true;
tempChannel.delete();
reject(new Error("offline"))
}, 30000);
tempChannel.subscribe(window.ship, "metadata-pull-hook", `/preview${group}`,
(err) => {
reject(err);
tempChannel.delete();
},
(ev: any) => {
console.log(ev);
if ('metadata-hook-update' in ev) {
done = true;
tempChannel.delete();
const upd = ev['metadata-hook-update'].preview as MetadataUpdatePreview;
resolve(upd);
} else {
done = true;
tempChannel.delete();
reject(new Error("no-permissions"));
}
},
(quit) => {
tempChannel.delete();
if(!done) {
reject(new Error("offline"))
}
},
(a) => {
console.log(a);
}
);
})
}
private metadataAction(data) {
return this.action('metadata-hook', 'metadata-action', data);
return this.action('metadata-push-hook', 'metadata-update', data);
}
}

View File

@ -0,0 +1,74 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import {
SettingsUpdate,
SettingsData,
Key,
Value,
Bucket,
} from '~/types/settings';
export default class SettingsApi extends BaseApi<StoreState> {
private storeAction(action: SettingsEvent): Promise<any> {
return this.action('settings-store', 'settings-event', action);
}
putBucket(key: Key, bucket: Bucket) {
this.storeAction({
"put-bucket": {
"bucket-key": key,
"bucket": bucket,
}
});
}
delBucket(key: Key) {
this.storeAction({
"del-bucket": {
"bucket-key": key,
}
});
}
putEntry(buc: Key, key: Key, val: Value) {
this.storeAction({
"put-entry": {
"bucket-key": buc,
"entry-key": key,
"value": val,
}
});
}
delEntry(buc: Key, key: Key) {
this.storeAction({
"put-entry": {
"bucket-key": buc,
"entry-key": key,
}
});
}
async getAll() {
const data = await this.scry("settings-store", "/all");
this.store.handleEvent({data: {"settings-data": data.all}});
}
async getBucket(bucket: Key) {
const data = await this.scry('settings-store', `/bucket/${bucket}`);
this.store.handleEvent({data: {"settings-data": {
"bucket-key": bucket,
"bucket": data.bucket,
}}});
}
async getEntry(bucket: Key, entry: Key) {
const data = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
this.store.handleEvent({data: {"settings-data": {
"bucket-key": bucket,
"entry-key": entry,
"entry": data.entry,
}}});
}
}

View File

@ -74,7 +74,7 @@ export default function index(associations, apps, currentGroup, groups) {
// iterate through each app's metadata object
Object.keys(associations[e]).map((association) => {
const each = associations[e][association];
let title = each['app-path'];
let title = each.resource;
if (each.metadata.title !== '') {
title = each.metadata.title;
}
@ -88,25 +88,25 @@ export default function index(associations, apps, currentGroup, groups) {
app = each.metadata.module;
}
const shipStart = each['app-path'].substr(each['app-path'].indexOf('~'));
const shipStart = each.resource.substr(each.resource.indexOf('~'));
if (app === 'groups') {
const obj = result(
title,
`/~landscape${each['app-path']}`,
`/~landscape${each.resource}`,
app.charAt(0).toUpperCase() + app.slice(1),
cite(shipStart.slice(0, shipStart.indexOf('/')))
);
landscape.push(obj);
} else {
const app = each.metadata.module || each['app-name'];
const group = (groups[each['group-path']]?.hidden)
? '/home' : each['group-path'];
const group = (groups[each.group]?.hidden)
? '/home' : each.group;
const obj = result(
title,
`/~landscape${group}/join/${app}${each['app-path']}`,
`/~landscape${group}/join/${app}${each.resource}`,
app.charAt(0).toUpperCase() + app.slice(1),
(associations?.contacts?.[each['group-path']]?.metadata?.title || null)
(associations?.contacts?.[each.group]?.metadata?.title || null)
);
subscriptions.push(obj);
}

View File

@ -52,13 +52,16 @@ const tokenizeMessage = (text) => {
}
messages.push({ url: str });
message = [];
} else if(urbitOb.isValidPatp(str) && !isInCodeBlock) {
} else if (urbitOb.isValidPatp(str.replace(/[^a-z\-\~]/g, '')) && !isInCodeBlock) {
if (message.length > 0) {
// If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join(' ') });
message = [];
}
messages.push({ mention: str });
messages.push({ mention: str.replace(/[^a-z\-\~]/g, '') });
if (str.replace(/[a-z\-\~]/g, '').length > 0) {
messages.push({ text: str.replace(/[a-z\-\~]/g, '') });
}
message = [];
} else {

View File

@ -0,0 +1,17 @@
import { useEffect } from 'react';
import {useLocation} from "react-router-dom";
export function useHashLink() {
const location = useLocation();
useEffect(() => {
if(!location.hash) {
return;
}
document.querySelector(location.hash)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, [location.hash]);
}

View File

@ -0,0 +1,103 @@
import React, {
useState,
ReactNode,
useCallback,
SyntheticEvent,
useMemo,
useEffect,
} from "react";
import { Box } from "@tlon/indigo-react";
type ModalFunc = (dismiss: () => void) => JSX.Element;
interface UseModalProps {
modal: JSX.Element | ModalFunc;
}
interface UseModalResult {
modal: ReactNode;
showModal: () => void;
}
const stopPropagation = (e: SyntheticEvent) => {
e.stopPropagation();
};
export function useModal(props: UseModalProps): UseModalResult {
const [modalShown, setModalShown] = useState(false);
const dismiss = useCallback(() => {
setModalShown(false);
}, [setModalShown]);
const showModal = useCallback(() => {
setModalShown(true);
}, [setModalShown]);
const inner = useMemo(
() =>
!modalShown
? null
: typeof props.modal === "function"
? props.modal(dismiss)
: props.modal,
[modalShown, props.modal, dismiss]
);
const handleKeyDown = useCallback(
(event) => {
if (event.key === "Escape") {
dismiss();
}
},
[dismiss]
);
useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [modalShown]);
const modal = useMemo(
() =>
!inner ? null : (
<Box
backgroundColor="scales.black30"
left="0px"
top="0px"
width="100%"
height="100%"
zIndex={10}
position="fixed"
display="flex"
justifyContent="center"
alignItems="center"
onClick={dismiss}
>
<Box
maxWidth="500px"
width="100%"
bg="white"
borderRadius={2}
border={[0, 1]}
borderColor={["washedGray", "washedGray"]}
onClick={stopPropagation}
display="flex"
alignItems="stretch"
flexDirection="column"
>
{inner}
</Box>
</Box>
),
[inner, dismiss]
);
return {
showModal,
modal,
};
}

View File

@ -50,11 +50,11 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
ACL: "public-read",
ContentType: file.type,
};
setUploading(true);
const { Location } = await client.current.upload(params).promise();
setUploading(false);
return Location;
@ -75,6 +75,7 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
const fileSelector = document.createElement('input');
fileSelector.setAttribute('type', 'file');
fileSelector.setAttribute('accept', accept);
fileSelector.style.visibility = 'hidden';
fileSelector.addEventListener('change', () => {
const files = fileSelector.files;
if (!files || files.length <= 0) {
@ -82,10 +83,12 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
return;
}
uploadDefault(files[0]).then(resolve);
document.body.removeChild(fileSelector);
})
document.body.appendChild(fileSelector);
fileSelector.click();
})
},
[uploadDefault]
);

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import _ from "lodash";
import f, { memoize } from "lodash/fp";
import bigInt, { BigInteger } from "big-integer";
@ -13,9 +13,16 @@ export const MOMENT_CALENDAR_DATE = {
nextWeek: "dddd",
lastDay: "[Yesterday]",
lastWeek: "[Last] dddd",
sameElse: "DD/MM/YYYY",
sameElse: "~YYYY.M.D",
};
export const getModuleIcon = (mod: string) => {
if (mod === "link") {
return "Collection";
}
return _.capitalize(mod);
}
export function appIsGraph(app: string) {
return app === 'publish' || app == 'link';
}
@ -357,7 +364,17 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
return isPlural ? `${text}s`: `${vowel ? 'an' : 'a'} ${text}`;
}
export function useShowNickname(contact: Contact | null): boolean {
const hideNicknames = useLocalState(state => state.hideNicknames);
// Hide is an optional second parameter for when this function is used in class components
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames);
return !!(contact && contact.nickname && !hideNicknames);
}
}
export function useHovering() {
const [hovering, setHovering] = useState(false);
const bind = {
onMouseEnter: () => setHovering(true),
onMouseLeave: () => setHovering(false)
};
return { hovering, bind };
}

View File

@ -386,5 +386,7 @@ function archive(json: any, state: HarkState) {
notifIdxEqual(index, idxNotif.index)
);
state.notifications.set(time, unarchived);
const newlyRead = archived.filter(x => !x.notification.read).length;
updateNotificationStats(state, index, 'notifications', (x) => x - newlyRead);
}
}

View File

@ -11,6 +11,7 @@ export default class MetadataReducer<S extends MetadataState> {
reduce(json: Cage, state: S) {
let data = json['metadata-update']
if (data) {
console.log(data);
this.associations(data, state);
this.add(data, state);
this.update(data, state);
@ -25,14 +26,14 @@ export default class MetadataReducer<S extends MetadataState> {
Object.keys(data).forEach((key) => {
let val = data[key];
let appName = val['app-name'];
let appPath = val['app-path'];
let rid = val.resource;
if (!(appName in metadata)) {
metadata[appName] = {};
}
if (!(appPath in metadata[appName])) {
metadata[appName][appPath] = {};
if (!(rid in metadata[appName])) {
metadata[appName][rid] = {};
}
metadata[appName][appPath] = val;
metadata[appName][rid] = val;
});
state.associations = metadata;
@ -44,7 +45,7 @@ export default class MetadataReducer<S extends MetadataState> {
if (data) {
let metadata = state.associations;
let appName = data['app-name'];
let appPath = data['app-path'];
let appPath = data.resource;
if (!(appName in metadata)) {
metadata[appName] = {};
@ -63,15 +64,15 @@ export default class MetadataReducer<S extends MetadataState> {
if (data) {
let metadata = state.associations;
let appName = data['app-name'];
let appPath = data['app-path'];
let rid = data.resource;
if (!(appName in metadata)) {
metadata[appName] = {};
}
if (!(appPath in metadata[appName])) {
metadata[appName][appPath] = {};
if (!(rid in metadata[appName])) {
metadata[appName][rid] = {};
}
metadata[appName][appPath] = data;
metadata[appName][rid] = data;
state.associations = metadata;
}
@ -82,10 +83,10 @@ export default class MetadataReducer<S extends MetadataState> {
if (data) {
let metadata = state.associations;
let appName = data['app-name'];
let appPath = data['app-path'];
let rid = data.resource;
if (appName in metadata && appPath in metadata[appName]) {
delete metadata[appName][appPath];
if (appName in metadata && rid in metadata[appName]) {
delete metadata[appName][rid];
}
state.associations = metadata;
}

View File

@ -0,0 +1,77 @@
import _ from 'lodash';
import { StoreState } from '../../store/type';
import {
SettingsUpdate,
} from '~/types/settings';
type SettingsState = Pick<StoreState, 'settings'>;
export default class SettingsReducer<S extends SettingsState>{
reduce(json: Cage, state: S) {
let data = json["settings-event"];
if (data) {
this.putBucket(data, state);
this.delBucket(data, state);
this.putEntry(data, state);
this.delEntry(data, state);
}
data = json["settings-data"];
if (data) {
this.getAll(data, state);
this.getBucket(data, state);
this.getEntry(data, state);
}
}
putBucket(json: SettingsUpdate, state: S) {
const data = _.get(json, 'put-bucket', false);
if (data) {
state.settings[data["bucket-key"]] = data.bucket;
}
}
delBucket(json: SettingsUpdate, state: S) {
const data = _.get(json, 'del-bucket', false);
if (data) {
delete state.settings[data["bucket-key"]];
}
}
putEntry(json: SettingsUpdate, state: S) {
const data = _.get(json, 'put-entry', false);
if (data) {
if (!state.settings[data["bucket-key"]]) {
state.settings[data["bucket-key"]] = {};
}
state.settings[data["bucket-key"]][data["entry-key"]] = data.value;
}
}
delEntry(json: SettingsUpdate, state: S) {
const data = _.get(json, 'del-entry', false);
if (data) {
delete state.settings[data["bucket-key"]][data["entry-key"]];
}
}
getAll(json: any, state: S) {
state.settings = json;
}
getBucket(json: any, state: S) {
const key = _.get(json, 'bucket-key', false);
const bucket = _.get(json, 'bucket', false);
if (key && bucket) {
state.settings[key] = bucket;
}
}
getEntry(json: any, state: S) {
const bucketKey = _.get(json, 'bucket-key', false);
const entryKey = _.get(json, 'entry-key', false);
const entry = _.get(json, 'entry', false);
if (bucketKey && entryKey && entry) {
state.settings[bucketKey][entryKey] = entry;
}
}
}

View File

@ -31,7 +31,7 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
suspendedFocus: undefined,
toggleOmnibox: () => set(produce(state => {
state.omniboxShown = !state.omniboxShown;
if (state.suspendedFocus) {
if (typeof state.suspendedFocus?.focus === 'function') {
state.suspendedFocus.focus();
state.suspendedFocus = undefined;
} else {

View File

@ -13,22 +13,10 @@ import { HarkReducer } from '../reducers/hark-update';
import GroupReducer from '../reducers/group-update';
import LaunchReducer from '../reducers/launch-update';
import ConnectionReducer from '../reducers/connection';
import SettingsReducer from '../reducers/settings-update';
import {OrderedMap} from '../lib/OrderedMap';
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
export const homeAssociation = {
"app-path": "/home",
"app-name": "contact",
"group-path": "/home",
metadata: {
color: "0x0",
title: "DMs + Drafts",
description: "",
"date-created": "",
module: "",
},
};
export default class GlobalStore extends BaseStore<StoreState> {
inviteReducer = new InviteReducer();
@ -39,6 +27,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
groupReducer = new GroupReducer();
launchReducer = new LaunchReducer();
connReducer = new ConnectionReducer();
settingsReducer = new SettingsReducer();
rehydrate() {
this.localReducer.rehydrate(this.state);
@ -89,7 +78,8 @@ export default class GlobalStore extends BaseStore<StoreState> {
graph: {},
group: {}
},
notificationsCount: 0
notificationsCount: 0,
settings: {}
};
}
@ -104,5 +94,6 @@ export default class GlobalStore extends BaseStore<StoreState> {
this.connReducer.reduce(data, this.state);
GraphReducer(data, this.state);
HarkReducer(data, this.state);
this.settingsReducer.reduce(data, this.state);
}
}

View File

@ -44,6 +44,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
this.subscribe('/updates', 'hark-store');
this.subscribe('/updates', 'hark-graph-hook');
this.subscribe('/updates', 'hark-group-hook');
this.subscribe('/all', 'settings-store');
}
restart() {

View File

@ -5,6 +5,7 @@ import { MetadataUpdate } from "./metadata-update";
import { GroupUpdate } from "./group-update";
import { LaunchUpdate, WeatherState } from "./launch-update";
import { ConnectionStatus } from "./connection";
import { SettingsUpdate } from "./settings";
interface MarksToTypes {
readonly json: any;
@ -14,6 +15,7 @@ interface MarksToTypes {
readonly groupUpdate: GroupUpdate;
readonly "launch-update": LaunchUpdate;
readonly "link-listen-update": LinkListenUpdate;
readonly "settings-event": SettingsUpdate;
// not really marks but w/e
readonly 'local': LocalUpdate;
readonly 'weather': WeatherState | {};

View File

@ -25,10 +25,18 @@ type MetadataUpdateUpdate = {
type MetadataUpdateRemove = {
remove: Resource & {
'group-path': Path;
group: Path;
}
}
export interface MetadataUpdatePreview {
group: string;
channels: Associations;
"channel-count": number;
members: number;
metadata: Metadata;
}
export type Associations = Record<AppName, AppAssociations>;
export type AppAssociations = {
@ -36,12 +44,12 @@ export type AppAssociations = {
}
interface Resource {
'app-path': Path;
resource: Path;
'app-name': AppName;
}
export type Association = Resource & {
'group-path': Path;
group: Path;
metadata: Metadata;
};
@ -52,4 +60,9 @@ export interface Metadata {
description: string;
title: string;
module: string;
picture: string;
preview: boolean;
permissions: Permissions;
}
export type Permissions = '' | 'reader-comments';

View File

@ -0,0 +1,55 @@
export type Key = string;
export type Value = string | boolean | number;
export type Bucket = Map<string, Value>;
export type Settings = Map<string, Bucket>;
interface PutBucket {
"put-bucket": {
"bucket-key": Key;
"bucket": Bucket;
};
}
interface DelBucket {
"del-bucket": {
"bucket-key": Key;
};
}
interface PutEntry {
"put-entry": {
"bucket-key": Key;
"entry-key": Key;
"value": Value;
};
}
interface DelEntry {
"del-entry": {
"bucket-key": Key;
"entry-key": Key;
};
}
interface AllData {
"all": Settings;
}
interface BucketData {
"bucket": Bucket;
}
interface EntryData {
"entry": Value;
}
export type SettingsUpdate =
| PutBucket
| DelBucket
| PutEntry
| DelEntry;
export type SettingsData =
| AllData
| BucketData
| EntryData;

View File

@ -11,8 +11,8 @@ import 'mousetrap-global-bind';
import './css/indigo-static.css';
import './css/fonts.css';
import light from './themes/light';
import dark from './themes/old-dark';
import light from '@tlon/indigo-light';
import dark from '@tlon/indigo-dark';
import { Text, Anchor, Row } from '@tlon/indigo-react';
@ -40,7 +40,7 @@ const Root = styled.div`
background-size: cover;
` : p.background?.type === 'color' ? `
background-color: ${p.background.color};
` : ''
` : `background-color: ${p.theme.colors.white};`
}
display: flex;
flex-flow: column nowrap;
@ -90,10 +90,11 @@ class App extends React.Component {
this.themeWatcher.onchange = this.updateTheme;
setTimeout(() => {
// Something about how the store works doesn't like changing it
// before the app has actually rendered, hence the timeout
// before the app has actually rendered, hence the timeout.
this.updateTheme(this.themeWatcher);
}, 500);
this.api.local.getBaseHash();
this.api.settings.getAll();
this.store.rehydrate();
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
e.preventDefault();

View File

@ -21,8 +21,8 @@ type ChatResourceProps = StoreState & {
} & RouteComponentProps;
export function ChatResource(props: ChatResourceProps) {
const station = props.association['app-path'];
const groupPath = props.association['group-path'];
const station = props.association.resource;
const groupPath = props.association.group;
const group = props.groups[groupPath];
const contacts = props.contacts[groupPath] || {};

View File

@ -231,8 +231,8 @@ export const MessageWithSigil = (props) => {
}}
title={`~${msg.author}`}
>{name}</Text>
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
<Text flexShrink='0' fontSize='0' gray mono className="v-mid">{timestamp}</Text>
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
</Box>
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
{msg.contents.map(c =>
@ -259,7 +259,7 @@ const ContentBox = styled(Box)`
export const MessageWithoutSigil = ({ timestamp, contacts, msg, measure, group }) => (
<>
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child" fontSize='0'>{timestamp}</Text>
<ContentBox flexShrink={0} fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
{msg.contents.map((c, i) => (
<MessageContent
@ -282,7 +282,7 @@ export const MessageContent = ({ content, contacts, measure, fontSize, group })
url={content.url}
onLoad={measure}
imageProps={{style: {
maxWidth: '18rem',
maxWidth: 'min(100%,18rem)',
display: 'block'
}}}
videoProps={{style: {

View File

@ -258,7 +258,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
const messageProps = { association, group, contacts, unreadMarkerRef, history, api };
const keys = graph.keys().reverse();
const unreadIndex = keys[this.props.unreadCount];
const unreadIndex = graph.keys()[this.props.unreadCount];
const unreadMsg = unreadIndex && graph.get(unreadIndex);
return (

View File

@ -2,8 +2,9 @@ import React, { Component } from 'react';
import { UnControlled as CodeEditor } from 'react-codemirror2';
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
import CodeMirror from 'codemirror';
import styled from "styled-components";
import { Row, BaseTextArea } from '@tlon/indigo-react';
import { Row, BaseTextArea, Box } from '@tlon/indigo-react';
import 'codemirror/mode/markdown/markdown';
import 'codemirror/addon/display/placeholder';
@ -52,9 +53,40 @@ const inputProxy = (input) => new Proxy(input, {
if (property === 'setValue') {
return (val) => target.value = val;
}
if (property === 'element') {
return input;
}
}
});
const MobileBox = styled(Box)`
display: inline-grid;
vertical-align: center;
align-items: stretch;
position: relative;
justify-content: flex-start;
width: 100%;
&:after,
textarea {
grid-area: 2 / 1;
width: auto;
min-width: 1em;
font: inherit;
padding: 0.25em;
margin: 0;
resize: none;
background: none;
appearance: none;
border: none;
}
&::after {
content: attr(data-value) ' ';
visibility: hidden;
white-space: pre-wrap;
}
`;
export default class ChatEditor extends Component {
constructor(props) {
super(props);
@ -161,31 +193,49 @@ export default class ChatEditor extends Component {
alignItems='center'
flexGrow='1'
height='100%'
paddingTop={MOBILE_BROWSER_REGEX.test(navigator.userAgent) ? '16px' : '0'}
paddingBottom={MOBILE_BROWSER_REGEX.test(navigator.userAgent) ? '16px' : '0'}
maxHeight='224px'
width='calc(100% - 88px)'
className={inCodeMode ? 'chat code' : 'chat'}
color="black"
>
{MOBILE_BROWSER_REGEX.test(navigator.userAgent)
? <BaseTextArea
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
fontSize="14px"
lineHeight="tall"
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
placeholder={inCodeMode ? "Code..." : "Message..."}
onKeyUp={event => {
if (event.key === 'Enter') {
this.submit();
} else {
? <MobileBox
data-value={this.state.message}
fontSize="1"
lineHeight="tall"
onClick={event => {
if (this.editor) {
this.editor.element.focus();
}
}}
>
<BaseTextArea
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
fontSize="1"
lineHeight="tall"
rows="1"
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
placeholder={inCodeMode ? "Code..." : "Message..."}
onChange={event => {
this.messageChange(null, null, event.target.value);
}
}}
ref={input => {
if (!input) return;
this.editor = inputProxy(input);
}}
{...props}
/>
}}
onKeyDown={event => {
if (event.key === 'Enter') {
event.preventDefault();
this.submit();
} else {
this.messageChange(null, null, event.target.value);
}
}}
ref={input => {
if (!input) return;
this.editor = inputProxy(input);
}}
{...props}
/>
</MobileBox>
: <CodeEditor
className="lh-copy"
value={message}

View File

@ -12,6 +12,7 @@ export default class CodeContent extends Component {
(
<Text
display='block'
fontSize='0'
mono
p='1'
my='0'
@ -37,6 +38,7 @@ export default class CodeContent extends Component {
overflow='auto'
maxHeight='10em'
maxWidth='100%'
fontSize='0'
style={{ whiteSpace: 'pre' }}
>
{content.code.expression}

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import RemarkBreaks from 'remark-breaks';
import urbitOb from 'urbit-ob';
import { Text } from '@tlon/indigo-react';
@ -26,10 +27,10 @@ const DISABLED_INLINE_TOKENS = [
const renderers = {
inlineCode: ({language, value}) => {
return <Text mono p='1' backgroundColor='washedGray' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
return <Text mono p='1' backgroundColor='washedGray' fontSize='0' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
},
paragraph: ({ children }) => {
return (<Text fontSize="14px">{children}</Text>);
return (<Text fontSize="1">{children}</Text>);
},
code: ({language, value}) => {
return <Text
@ -38,6 +39,7 @@ const renderers = {
display='block'
borderRadius='1'
mono
fontSize='0'
backgroundColor='washedGray'
overflowX='auto'
style={{ whiteSpace: 'pre'}}>
@ -66,6 +68,7 @@ const MessageMarkdown = React.memo(props => (
return true;
}}
plugins={[[
RemarkBreaks,
RemarkDisableTokenizers,
{ block: DISABLED_BLOCK_TOKENS, inline: DISABLED_INLINE_TOKENS }
]]} />

View File

@ -277,9 +277,6 @@ pre.CodeMirror-placeholder.CodeMirror-line-like { color: var(--gray); }
/* dark */
@media (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-black-d {
background-color: black;
}

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import Helmet from 'react-helmet';
import styled from 'styled-components';
import { Box, Row, Icon, Text } from '@tlon/indigo-react';
@ -14,6 +13,7 @@ import ModalButton from './components/ModalButton';
import { writeText } from '~/logic/lib/util';
import { NewGroup } from "~/views/landscape/components/NewGroup";
import { JoinGroup } from "~/views/landscape/components/JoinGroup";
import { Helmet } from 'react-helmet';
const ScrollbarLessBox = styled(Box)`
scrollbar-width: none !important;
@ -25,13 +25,38 @@ const ScrollbarLessBox = styled(Box)`
export default function LaunchApp(props) {
const [hashText, setHashText] = useState(props.baseHash);
const hashBox = (
<Box
position={["relative", "absolute"]}
fontFamily="mono"
left="0"
bottom="0"
color="scales.black20"
bg="white"
ml={3}
mb={3}
borderRadius={2}
fontSize={0}
p={2}
boxShadow="0 0 0px 1px inset"
cursor="pointer"
onClick={() => {
writeText(props.baseHash);
setHashText('copied');
setTimeout(() => {
setHashText(props.baseHash);
}, 2000);
}}
>
<Text color="gray">{hashText || props.baseHash}</Text>
</Box>
);
return (
<>
<Helmet>
<title>OS1 - Home</title>
<Helmet defer={false}>
<title>{ props.notificationsCount ? `(${String(props.notificationsCount) }) `: '' }Landscape</title>
</Helmet>
<ScrollbarLessBox height='100%' overflowY='scroll'>
<ScrollbarLessBox height='100%' overflowY='scroll' display="flex" flexDirection="column">
<Welcome firstTime={props.launch.firstTime} api={props.api} />
<Box
mx='2'
@ -53,7 +78,7 @@ export default function LaunchApp(props) {
color="black"
icon="Mail"
/>
<Text ml="1" mt='1px' color="black">DMs + Drafts</Text>
<Text ml="2" mt='1px' color="black">DMs + Drafts</Text>
</Row>
</Box>
</Tile>
@ -77,36 +102,15 @@ export default function LaunchApp(props) {
icon="CreateGroup"
bg="green"
color="#fff"
text="Create a Group"
text="Create Group"
>
<NewGroup {...props} />
</ModalButton>
<Groups unreads={props.unreads} groups={props.groups} associations={props.associations} />
</Box>
<Box alignSelf="flex-start" display={["block", "none"]}>{hashBox}</Box>
</ScrollbarLessBox>
<Box
position="absolute"
fontFamily="mono"
left="0"
bottom="0"
color="gray"
bg="white"
ml={3}
mb={3}
borderRadius={2}
fontSize={0}
p={2}
cursor="pointer"
onClick={() => {
writeText(props.baseHash);
setHashText('copied');
setTimeout(() => {
setHashText(props.baseHash);
}, 2000);
}}
>
{hashText || props.baseHash}
</Box>
<Box display={["none", "block"]}>{hashBox}</Box>
</>
);
}

Some files were not shown because too many files have changed in this diff Show More