├── .gitignore ├── README.md ├── app └── library-proxy.hoon ├── ignore_files.txt ├── install.sh ├── lib └── library.hoon ├── mar ├── graph │ └── validator │ │ └── library.hoon └── library │ ├── action.hoon │ ├── command.hoon │ └── response.hoon ├── misc └── library cmdline snippets.txt └── sur └── library.hoon /.gitignore: -------------------------------------------------------------------------------- 1 | PRIVATE.scratch.md 2 | *~undo-tree~ 3 | *.swp 4 | *.save 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Library 2 | 3 | Simple library application. 4 | 5 | ![Made with %graph-store](https://img.shields.io/badge/Made%20with-%25graph--store-darkblue) 6 | 7 | 8 | 12 | 13 | ## Features 14 | 15 | Using this application, you will be able to: 16 | 17 | - Create libraries, where a library is a collection of books 18 | - Add and remove books from a library, if you are the owner 19 | - Allow other's to view your library based on various permissioning schemes (policies) 20 | - Add and remove comments from a library, if you are the owner or were granted access 21 | 22 | 23 | ## Installation 24 | 25 | First, clone this repository in the directory that your piers are stored in. 26 | 27 | ``` 28 | $ git clone https://github.com/ynx0/library 29 | ``` 30 | 31 | Then, `|mount` the clay filesystem on each ship. 32 | 33 | ``` 34 | :dojo> |mount % 35 | ``` 36 | 37 | Then, run the install script. This copies the source code of the library app into the `%home` desk. 38 | 39 | ``` 40 | $ ./library/install.sh 41 | ``` 42 | 43 | Finally, `|commit` the `%home` desk in both ships 44 | 45 | ``` 46 | :dojo> |commit %home 47 | ``` 48 | 49 | You should see output similar to the following: 50 | 51 | ``` 52 | + /~zod/kids/2/sur/library/hoon 53 | + /~zod/kids/2/mar/library/action/hoon 54 | + /~zod/kids/2/mar/library/command/hoon 55 | + /~zod/kids/2/app/library-proxy/hoon 56 | + /~zod/kids/2/lib/library/hoon 57 | + /~zod/kids/2/mar/library/response/hoon 58 | + /~zod/kids/2/mar/graph/validator/library/hoon 59 | ``` 60 | 61 | ## Usage 62 | 63 | *Note: The following section assumes you have multiple (fake) ships running. We'll user **~zod** and **~nus**, but feel free to use any ones you like.* 64 | 65 | 66 | 67 | ### Setup 68 | 69 | First, follow the installation instructions for two ships, **~zod** and **~nus**. 70 | 71 | Then, go ahead and start the `%library-proxy` app on both ships. 72 | 73 | ``` 74 | :dojo> |start %library-proxy 75 | ``` 76 | 77 | **Code references** 78 | - [`app/library-proxy.hoon`](https://github.com/ynx0/library/blob/db6ff0354e9e639a5488e67921468477f5df9b98/app/library-proxy.hoon#L30-L33) - the `+on-init` arm 79 | 80 | 81 | 82 | To get baseline, run the following two command. 83 | 84 | This first one prints out the state of the graphs in `%graph-store`. 85 | ``` 86 | ~zod:dojo> :graph-store +dbug [%state 'graphs'] 87 | > {[p=[entity=~zod name=%dm-inbox] q=[p={} q=[~ %graph-validator-dm]]]} 88 | ``` 89 | 90 | This second one prints out the state of the `%library-proxy` app. 91 | ``` 92 | ~zod:dojo> :library-proxy +dbug 93 | > [%0 readers={} policies={}] 94 | ``` 95 | 96 | The output shows us that there is currently only one empty graph created by default for DMs, 97 | and that `%library-proxy` is currently not tracking any readers or policies. 98 | 99 | 100 | 101 | ### Creating a library 102 | 103 | Now, let's create a library on **~zod**. 104 | 105 | We'll create it with the `%open` policy, which means that anyone can request for access to the library. 106 | ``` 107 | ~zod:dojo> :library-proxy &library-command [%create-library %library1 [%open ~]] 108 | ``` 109 | 110 | Let's verify that the library exists. 111 | ``` 112 | ~zod:dojo> :graph-store +dbug [%state 'graphs'] 113 | > { [p=[entity=~zod name=%library1] q=[p={} q=[~ %graph-validator-library]]] 114 | [p=[entity=~zod name=%dm-inbox] q=[p={} q=[~ %graph-validator-dm]]] 115 | } 116 | ``` 117 | We can see that there is now a new empty graph with the name we specified, 118 | and that it is indeed using the validator for our library application. 119 | 120 | Let's also verify that we've successfully recorded the policy for our library. 121 | ``` 122 | > ~zod:dojo> :library-proxy +dbug 123 | > [%0 readers={} policies={[p=[entity=~zod name=%library1] q=[%open ~]]}] 124 | ``` 125 | 126 | From this point on, the output will only contain important bits of information, 127 | skipping over what's not necessary. 128 | 129 | 130 | **Code references** 131 | - [`app/library-proxy.hoon#L47-50`](https://github.com/ynx0/library/blob/db6ff0354e9e639a5488e67921468477f5df9b98/app/library-proxy.hoon#L47-L50) - the relevant section in the `+on-poke` arm 132 | - [`app/library-proxy.hoon#`](https://github.com/ynx0/library/blob/db6ff0354e9e639a5488e67921468477f5df9b98/app/library-proxy.hoon#L175-L180) - the wing that handles `%create-library` 133 | - [`sur/library.hoon`](https://github.com/ynx0/library/blob/master/sur/library.hoon#L18) - the definition of a `library-command` 134 | 135 | 136 | ### Adding a book 137 | 138 | Now, let's add a book to our library. Note that the isbn must be either length 10 or 13. 139 | ``` 140 | ~zod:dojo> :library-proxy &library-command [%add-book %library1 ['Dune123' '0441172717']] 141 | ``` 142 | 143 | Verify that the book was created successfully. 144 |
145 | 146 | ~zod:dojo> :graph-store +dbug [%state 'graphs']` (Large output. Click to expand) 147 | 148 | 149 | ``` 150 | ~zod:dojo> :graph-store +dbug [%state 'graphs'] 151 | 152 | { [ p=[entity=~zod name=%library1] 153 | q 154 | [ p 155 | { [ key=170.141.184.505.110.303.839.596.375.394.968.666.112 156 | val 157 | [ post 158 | [ %.y 159 | p 160 | [ author=~zod 161 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112] 162 | time-sent=~2021.6.18..16.30.00..595d 163 | contents=~ 164 | hash=~ 165 | signatures={} 166 | ] 167 | ] 168 | children 169 | [ %graph 170 | p 171 | { [ key=8.319.395.793.566.789.475 172 | val 173 | [ post 174 | [ %.y 175 | p 176 | [ author=~zod 177 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 8.319.395.793.566.789.475] 178 | time-sent=~2021.6.18..16.30.00..595d 179 | contents=~ 180 | hash=~ 181 | signatures={} 182 | ] 183 | ] 184 | children=[%empty ~] 185 | ] 186 | ] 187 | [ key=1.635.018.093 188 | val 189 | [ post 190 | [ %.y 191 | p 192 | [ author=~zod 193 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093] 194 | time-sent=~2021.6.18..16.30.00..595d 195 | contents=~ 196 | hash=~ 197 | signatures={} 198 | ] 199 | ] 200 | children 201 | [ %graph 202 | p 203 | { [ key=1 204 | val 205 | [ post 206 | [ %.y 207 | p 208 | [ author=~zod 209 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093 1] 210 | time-sent=~2021.6.18..16.30.00..595d 211 | contents=~[[%text text='Dune123'] [%text text='0441172717']] 212 | hash=~ 213 | signatures={} 214 | ] 215 | ] 216 | children=[%empty ~] 217 | ] 218 | ] 219 | } 220 | ] 221 | ] 222 | ] 223 | } 224 | ] 225 | ] 226 | ] 227 | } 228 | q=[~ %graph-validator-library] 229 | ] 230 | ] 231 | } 232 | ``` 233 | 234 |
235 | 236 | Although all the data is present and valid, it is not in a human readable format. 237 | To understand the underlying structure, take a look at 238 | [this section](urbit.org/docs/userspace/graph-store/sample-application-overview#organizing-data-within-graph-store) of the documentation 239 | for an explanation of the schema. 240 | 241 | 242 | **Code references** 243 | - [`app/library-proxy.hoon`](https://github.com/ynx0/library/blob/db6ff0354e9e639a5488e67921468477f5df9b98/app/library-proxy.hoon#L188-L193) - the wing that handles `%add-book` 244 | - [`sur/library.hoon`](https://github.com/ynx0/library/blob/db6ff0354e9e639a5488e67921468477f5df9b98/sur/library.hoon#L20) - the definition of the `%add-book` command. 245 | - [`mar/graph/validator/library.hoon`](https://github.com/ynx0/library/blob/72224e27efa5d02cdc4261b32602be6ef4faf89d/mar/graph/validator/library.hoon#L42-L49) - wing that enforces isbn validity 246 | 247 | ### Editing 248 | 249 | Now, let's edit title of the book to remove the unnecessary numbers. 250 | 251 | To make things easy, we'll first give the index of the book a face. Use the index from the last `%graph-store` debug output, like so: 252 | 253 | ``` 254 | ~zod:dojo> =top-of-dune 170.141.184.505.110.303.839.596.375.394.968.666.112 255 | ``` 256 | 257 | Now, we issue the `%revise-book` command, supplying the new book metadata. 258 | ``` 259 | ~zod:dojo> :library-proxy &library-command [%revise-book %library1 top-of-dune ['Dune: The Book' 'thirteenchars']] 260 | ``` 261 | 262 | Verify that a revision node was created successfully. 263 | ``` 264 | :graph-store +dbug [%state 'graphs'] 265 | ``` 266 | 267 | 268 | 269 | ### Requesting access to someone else's library 270 | 271 | Let's bring another ship, **~nus**, into the picture. 272 | 273 | First, get the list of available libraries from **~zod** 274 | ``` 275 | ~nus:dojo> :~zod/library-proxy &library-action [%get-libraries ~] 276 | {%library1} 277 | ``` 278 | We see one library, with the name *library1*. 279 | 280 | Let's request this one from **~zod**. 281 | 282 | ``` 283 | ~nus:dojo> :library-proxy &library-command [%request-library [~zod %library1]] 284 | ``` 285 | 286 | If we inspect **~nus**'s `%graph-store`, we will now see that it now has a new graph from **~zod** corresponding to *library1*. 287 | ``` 288 | ~nus:dojo> :graph-store +dbug [%state 'graphs'] 289 | [p=[entity=~zod name=%library1] q=[p={} q=[~ %graph-validator-library]]] 290 | ``` 291 | However, there are no books yet that have been populated. 292 | In Library, we only store data about and keep track of books that we are interested. 293 | 294 | 295 | 296 | **Code references** 297 | 298 | 1. [`app/library-proxy.hoon`](#L999) - the relevant section in the `+on-poke` arm 299 | 2. [`app/library-proxy.hoon`](#L999) - the wing that handles a `%library-action` 300 | 2. [`app/library-proxy.hoon`](#L999) - the wing that handles action `%get-libraries` 301 | 2. [`app/library-proxy.hoon`](#L999) - the wing that handles command `%request-library` 302 | 2. [...] - the part that handles the %get-library (call is under the hood) 303 | 2. [`sur/library.hoon`](#L999) - the definition of a `library-command` 304 | 2. [`sur/library.hoon`](#L999) - the definition of a `library-action` 305 | 306 | 307 | ### Requesting a book from a library 308 | 309 | So, let's figure out what books are available on *library1*. 310 | ``` 311 | ~nus:dojo> :~zod/library-proxy &library-action [%get-books %library1] 312 | 313 | %library1 314 | {170.141.184.505.110.703.100.063.230.385.815.814.144} 315 | ``` 316 | 317 | Right now, we see the index of one book. We'll give it a face on this ship as well for easy access. 318 | 319 | ``` 320 | ~nus:dojo> =top-of-dune 170.141.184.505.110.303.839.596.375.394.968.666.112 321 | ``` 322 | 323 | Now let's request this book. 324 | ``` 325 | ~nus:dojo> :library-proxy &library-command [%request-book [~zod %library1] top-of-dune] 326 | ``` 327 | 328 | And verify that we got the update. 329 | 330 |
331 | 332 | ~nus:dojo> :graph-store +dbug [%state 'graphs'] (Large output. Click to expand) 333 | 334 | 335 | ``` 336 | ~nus:dojo> :graph-store +dbug [%state 'graphs'] 337 | { [ p=[entity=~zod name=%library1] 338 | q 339 | [ p 340 | { [ key=170.141.184.505.110.303.839.596.375.394.968.666.112 341 | val 342 | [ post 343 | [ %.y 344 | p 345 | [ author=~zod 346 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112] 347 | time-sent=~2021.6.18..16.30.00..595d 348 | contents=~ 349 | hash=~ 350 | signatures={} 351 | ] 352 | ] 353 | children 354 | [ %graph 355 | p 356 | { [ key=8.319.395.793.566.789.475 357 | val 358 | [ post 359 | [ %.y 360 | p 361 | [ author=~zod 362 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 8.319.395.793.566.789.475] 363 | time-sent=~2021.6.18..16.30.00..595d 364 | contents=~ 365 | hash=~ 366 | signatures={} 367 | ] 368 | ] 369 | children=[%empty ~] 370 | ] 371 | ] 372 | [ key=1.635.018.093 373 | val 374 | [ post 375 | [ %.y 376 | p 377 | [ author=~zod 378 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093] 379 | time-sent=~2021.6.18..16.30.00..595d 380 | contents=~ 381 | hash=~ 382 | signatures={} 383 | ] 384 | ] 385 | children 386 | [ %graph 387 | p 388 | { [ key=1 389 | val 390 | [ post 391 | [ %.y 392 | p 393 | [ author=~zod 394 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093 1] 395 | time-sent=~2021.6.18..16.30.00..595d 396 | contents=~[[%text text='Dune123'] [%text text='0441172717']] 397 | hash=~ 398 | signatures={} 399 | ] 400 | ] 401 | children=[%empty ~] 402 | ] 403 | ] 404 | } 405 | ] 406 | ] 407 | ] 408 | } 409 | ] 410 | ] 411 | ] 412 | } 413 | q=[~ %graph-validator-library] 414 | ] 415 | ] 416 | } 417 | 418 | ``` 419 | 420 |
421 | 422 | 423 | Take a look at **~zod**'s `%library-proxy` and notice how its state has updated information regarding who's tracking what resource: 424 | ``` 425 | ~zod:dojo> :library-proxy +dbug 426 | [ %0 427 | readers={[p=~nus q={[p=[entity=~zod name=%library1] q={170.141.184.505.110.303.839.596.375.394.968.666.112}]}]} 428 | policies={[p=[entity=~zod name=%library1] q=[%open ~]]} 429 | ] 430 | 431 | ``` 432 | 433 | As a result of the last two actions on **~nus**'s part, **~zod**'s `%library-proxy` now knows that: 434 | - (a) **~nus** has requested and succesfully been granted access to %library1, and 435 | - (b) **~nus** is interested in tracking updates to the book with index `170.141.184.505.110.303.839.596.375.394.968.666.112`, (which corresponds to Dune) 436 | 437 | 438 | 439 | **Code references** 440 | 441 | 1. [`app/library-proxy.hoon`](#L999) - the relevant section in the `+on-poke` arm 442 | 2. [`app/library-proxy.hoon`](#L999) - the wing that handles a `%library-action` 443 | 2. [`app/library-proxy.hoon`](#L999) - the wing that handles action `%get-books` 444 | 2. [...] - the part that handles the %get-book (call is under the hood) 445 | 2. [`sur/library.hoon`](#L999) - the definition of a `library-command` 446 | 2. [`sur/library.hoon`](#L999) - the definition of a `library-action` 447 | 2. [`mar/library/action.hoon`](#L999) - mark file defining a `library-action` 448 | 449 | 450 | 451 | 452 | ### Commenting on a book 453 | 454 | After having read the book, **~nus** would like to comment about it. 455 | ``` 456 | ~nus:dojo> :~zod/library-proxy &library-action [%add-comment %library1 top-of-dune 'dune is ok'] 457 | ``` 458 | 459 | After thinking for a second, **~nus** realizes she didn't complete her thought, so she writes another comment. 460 | ``` 461 | ~nus:dojo> :~zod/library-proxy &library-action [%add-comment %library1 top-of-dune 'in my opinion'] 462 | ``` 463 | 464 | Let's make sure **~nus**'s comment was properly sent to **~zod**. 465 | 466 |
467 | 468 | ~zod:dojo> :graph-store +dbug [%state 'graphs'] (Large output. Click to expand) 469 | 470 | 471 | ``` 472 | ~zod:dojo> :graph-store +dbug [%state 'graphs'] 473 | { [ p=[entity=~zod name=%library1] 474 | q 475 | [ p 476 | { [ key=170.141.184.505.110.303.839.596.375.394.968.666.112 477 | val 478 | [ post 479 | [ %.y 480 | p 481 | [ author=~zod 482 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112] 483 | time-sent=~2021.6.18..16.30.00..595d 484 | contents=~ 485 | hash=~ 486 | signatures={} 487 | ] 488 | ] 489 | children 490 | [ %graph 491 | p 492 | { [ key=8.319.395.793.566.789.475 493 | val 494 | [ post 495 | [ %.y 496 | p 497 | [ author=~zod 498 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 8.319.395.793.566.789.475] 499 | time-sent=~2021.6.18..16.30.00..595d 500 | contents=~ 501 | hash=~ 502 | signatures={} 503 | ] 504 | ] 505 | children 506 | [ %graph 507 | p 508 | { [ key=170.141.184.505.110.617.158.107.698.780.100.886.528 509 | val 510 | [ post 511 | [ %.y 512 | p 513 | [ author=~nus 514 | index 515 | ~[ 516 | 170.141.184.505.110.303.839.596.375.394.968.666.112 517 | 8.319.395.793.566.789.475 518 | 170.141.184.505.110.617.158.107.698.780.100.886.528 519 | ] 520 | time-sent=~2021.6.18..21.13.05..612e 521 | contents=~[[%text text='in my opinion']] 522 | hash=~ 523 | signatures={} 524 | ] 525 | ] 526 | children=[%empty ~] 527 | ] 528 | ] 529 | [ key=170.141.184.505.110.615.575.362.645.737.013.772.288 530 | val 531 | [ post 532 | [ %.y 533 | p 534 | [ author=~nus 535 | index 536 | ~[ 537 | 170.141.184.505.110.303.839.596.375.394.968.666.112 538 | 8.319.395.793.566.789.475 539 | 170.141.184.505.110.615.575.362.645.737.013.772.288 540 | ] 541 | time-sent=~2021.6.18..21.11.39..942e 542 | contents=~[[%text text='dune is ok']] 543 | hash=~ 544 | signatures={} 545 | ] 546 | ] 547 | children=[%empty ~] 548 | ] 549 | ] 550 | } 551 | ] 552 | ] 553 | ] 554 | [ key=1.635.018.093 555 | val 556 | [ post 557 | [ %.y 558 | p 559 | [ author=~zod 560 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093] 561 | time-sent=~2021.6.18..16.30.00..595d 562 | contents=~ 563 | hash=~ 564 | signatures={} 565 | ] 566 | ] 567 | children 568 | [ %graph 569 | p 570 | { [ key=1 571 | val 572 | [ post 573 | [ %.y 574 | p 575 | [ author=~zod 576 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093 1] 577 | time-sent=~2021.6.18..16.30.00..595d 578 | contents=~[[%text text='Dune123'] [%text text='0441172717']] 579 | hash=~ 580 | signatures={} 581 | ] 582 | ] 583 | children=[%empty ~] 584 | ] 585 | ] 586 | } 587 | ] 588 | ] 589 | ] 590 | } 591 | ] 592 | ] 593 | ] 594 | } 595 | q=[~ %graph-validator-library] 596 | ] 597 | ] 598 | } 599 | 600 | ``` 601 | 602 |
603 | 604 | 605 | **Code references** 606 | 607 | 1. [`app/library-proxy.hoon`](#L999) - the wing that handles a `%library-action` 608 | 2. [`app/library-proxy.hoon`](#L999) - the wing that handles action `%add-comment` 609 | 2. [`sur/library.hoon`](#L999) - the definition of a `library-action` 610 | 611 | 612 | ### Deleting a comment 613 | 614 | Now, **~nus** feels like deleting her first comment, and does so: 615 | ``` 616 | ~nus:dojo> :~zod/library-proxy &library-action [%remove-comment %library1 ~[top-of-dune %comments 170.141.184.505.110.615.575.362.645.737.013.772.288]] 617 | ``` 618 | 619 | **~zod** wants to clean up **~nus**'s second comment, because it doesn't really make sense without the first one. 620 | So he ends up deleting it, since he's the owner of the library. 621 | 622 | ``` 623 | ~zod:dojo> :library-proxy &library-action [%remove-comment %library1 ~[top-of-dune %comments 170.141.184.505.110.617.158.107.698.780.100.886.528]] 624 | ``` 625 | 626 | Now, if we look at the `%graph-store` states for both **~zod** and **~nus**, we'll see the above changes reflected on both graphs. 627 | The book nodes on both graphs are identical. 628 | 629 |
630 | 631 | :dojo> :graph-store +dbug (Large output. Click to expand) 632 | 633 | 634 | ``` 635 | ~zod:dojo> :graph-store +dbug 636 | ~nus:dojo> :graph-store +dbug 637 | { [ p=[entity=~zod name=%library1] 638 | q 639 | [ p 640 | { [ key=170.141.184.505.110.303.839.596.375.394.968.666.112 641 | val 642 | [ post 643 | [ %.y 644 | p 645 | [ author=~zod 646 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112] 647 | time-sent=~2021.6.18..16.30.00..595d 648 | contents=~ 649 | hash=~ 650 | signatures={} 651 | ] 652 | ] 653 | children 654 | [ %graph 655 | p 656 | { [ key=8.319.395.793.566.789.475 657 | val 658 | [ post 659 | [ %.y 660 | p 661 | [ author=~zod 662 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 8.319.395.793.566.789.475] 663 | time-sent=~2021.6.18..16.30.00..595d 664 | contents=~ 665 | hash=~ 666 | signatures={} 667 | ] 668 | ] 669 | children 670 | [ %graph 671 | p 672 | { [ key=170.141.184.505.110.617.158.107.698.780.100.886.528 673 | val=[post=[%.n p=0x101c.1386.2893.6180.f690.0761.5141.544c] children=[%empty ~]] 674 | ] 675 | [ key=170.141.184.505.110.615.575.362.645.737.013.772.288 676 | val=[post=[%.n p=0x253e.9b5d.cde6.fa0b.e8d8.5fce.ce41.458b] children=[%empty ~]] 677 | ] 678 | } 679 | ] 680 | ] 681 | ] 682 | [ key=1.635.018.093 683 | val 684 | [ post 685 | [ %.y 686 | p 687 | [ author=~zod 688 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093] 689 | time-sent=~2021.6.18..16.30.00..595d 690 | contents=~ 691 | hash=~ 692 | signatures={} 693 | ] 694 | ] 695 | children 696 | [ %graph 697 | p 698 | { [ key=1 699 | val 700 | [ post 701 | [ %.y 702 | p 703 | [ author=~zod 704 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093 1] 705 | time-sent=~2021.6.18..16.30.00..595d 706 | contents=~[[%text text='Dune123'] [%text text='0441172717']] 707 | hash=~ 708 | signatures={} 709 | ] 710 | ] 711 | children=[%empty ~] 712 | ] 713 | ] 714 | } 715 | ] 716 | ] 717 | ] 718 | } 719 | ] 720 | ] 721 | ] 722 | } 723 | q=[~ %graph-validator-library] 724 | ] 725 | ] 726 | } 727 | ``` 728 | 729 |
730 | 731 | 732 | **Code references** 733 | 734 | 1. [`app/library-proxy.hoon`](#L999) - the wing that handles action `%remove-comment` 735 | 2. [`sur/library.hoon`](#L999) - the definition of a `library-action` 736 | 737 | 738 | ### Deleting a book 739 | 740 | 741 | Now, **~zod** decides to delete the book *Dune* 742 | ``` 743 | :library-proxy &library-command [%remove-book %library1 top-of-dune] 744 | ``` 745 | 746 | Looking at the state, 747 | 748 |
749 | 750 | :dojo> :graph-store +dbug (Large output. Click to expand) 751 | 752 | 753 | ``` 754 | ~zod:dojo> :graph-store +dbug [%state 'graphs'] 755 | ~nus:dojo> :graph-store +dbug [%state 'graphs'] 756 | { [ p=[entity=~zod name=%library1] 757 | q 758 | [ p 759 | { [ key=170.141.184.505.110.303.839.596.375.394.968.666.112 760 | val 761 | [ post=[%.n p=0xb8e5.ae1a.f49d.a8fa.30cd.d37d.8776.e9ba] 762 | children 763 | [ %graph 764 | p 765 | { [ key=8.319.395.793.566.789.475 766 | val 767 | [ post 768 | [ %.y 769 | p 770 | [ author=~zod 771 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 8.319.395.793.566.789.475] 772 | time-sent=~2021.6.18..16.30.00..595d 773 | contents=~ 774 | hash=~ 775 | signatures={} 776 | ] 777 | ] 778 | children 779 | [ %graph 780 | p 781 | { [ key=170.141.184.505.110.617.158.107.698.780.100.886.528 782 | val=[post=[%.n p=0x101c.1386.2893.6180.f690.0761.5141.544c] children=[%empty ~]] 783 | ] 784 | [ key=170.141.184.505.110.615.575.362.645.737.013.772.288 785 | val=[post=[%.n p=0x253e.9b5d.cde6.fa0b.e8d8.5fce.ce41.458b] children=[%empty ~]] 786 | ] 787 | } 788 | ] 789 | ] 790 | ] 791 | [ key=1.635.018.093 792 | val 793 | [ post 794 | [ %.y 795 | p 796 | [ author=~zod 797 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093] 798 | time-sent=~2021.6.18..16.30.00..595d 799 | contents=~ 800 | hash=~ 801 | signatures={} 802 | ] 803 | ] 804 | children 805 | [ %graph 806 | p 807 | { [ key=1 808 | val 809 | [ post 810 | [ %.y 811 | p 812 | [ author=~zod 813 | index=~[170.141.184.505.110.303.839.596.375.394.968.666.112 1.635.018.093 1] 814 | time-sent=~2021.6.18..16.30.00..595d 815 | contents=~[[%text text='Dune123'] [%text text='0441172717']] 816 | hash=~ 817 | signatures={} 818 | ] 819 | ] 820 | children=[%empty ~] 821 | ] 822 | ] 823 | } 824 | ] 825 | ] 826 | ] 827 | } 828 | ] 829 | ] 830 | ] 831 | } 832 | q=[~ %graph-validator-library] 833 | ] 834 | ] 835 | [p=[entity=~zod name=%dm-inbox] q=[p={} q=[~ %graph-validator-dm]]] 836 | } 837 | 838 | ``` 839 | 840 |
841 | 842 | 843 | We can see that the book has been flagged as deleted from both users' `%graph-store`. 844 | 845 | We can also see that on ~zod's library proxy, 846 | ``` 847 | ~zod:dojo> :library-proxy +dbug 848 | [%0 readers={[p=~nus q={}]} policies={[p=[entity=~zod name=%library1] q=[%open ~]]}] 849 | ``` 850 | 851 | the index for the book is now removed from **~nus**'s tracked books on *library1*. 852 | 853 | Compare to the earlier state: 854 | ``` 855 | [ %0 856 | readers={[p=~nus q={[p=[entity=~zod name=%library1] q={170.141.184.505.110.303.839.596.375.394.968.666.112}]}]} 857 | policies={[p=[entity=~zod name=%library1] q=[%open ~]]} 858 | ] 859 | ``` 860 | 861 | **Code references** 862 | 863 | 1. [`app/library-proxy.hoon`](#L999) - the wing that handles action `%remove-comment` 864 | 2. [`sur/library.hoon`](#L999) - the definition of a `library-action` 865 | 866 | 867 | ### Deleting a library 868 | 869 | 870 | To wrap up, **~zod** deletes the library. 871 | ``` 872 | ~zod:dojo> :library-proxy &library-command [%remove-library %library1] 873 | ``` 874 | 875 | Looking at the state, 876 | 877 | ``` 878 | ~zod:dojo> :graph-store +dbug [%state 'graphs'] 879 | ~nus:dojo> :graph-store +dbug [%state 'graphs'] 880 | > {[p=[entity=~zod name=%dm-inbox] q=[p={} q=[~ %graph-validator-dm]]]} 881 | ``` 882 | 883 | We can see that the library has been removed from both `%graph-store`s, 884 | and that the only the default DM graph remains. 885 | 886 | Also note that on **~zod**'s library proxy, *library1* is no longer tracked by **~nus**, 887 | nor is there any policy associated with it. 888 | ``` 889 | ~zod:dojo> :library-proxy +dbug 890 | [%0 readers={[p=~nus q={}]} policies={}] 891 | ``` 892 | 893 | **Code references** 894 | 895 | 1. [`app/library-proxy.hoon`](#L999) - the wing that handles command `%remove-library` 896 | 2. [`sur/library.hoon`](#L999) - the definition of a `library-command` 897 | 898 | 899 | 900 | ### Policies 901 | 902 | There are three different access policies supported by Library: 903 | 904 | - `%open` - anyone can request access to the library 905 | - `%children` - only children can request access to the library 906 | - `%whitelist` - only select ships specified can request the access to library 907 | 908 | They can be set *once* at the time of creation of the library. 909 | 910 | **Code references** 911 | 912 | 1. [`sur/library.hoon`](#L999) - the definition of `policy` 913 | 1. [`lib/library.hoon`](#L999) - the arm that implements policy checking 914 | 915 | 920 | -------------------------------------------------------------------------------- /app/library-proxy.hoon: -------------------------------------------------------------------------------- 1 | /- *resource, library 2 | /+ store=graph-store, graph, default-agent, 3 | dbug, verb, agentio, libr=library 4 | |% 5 | +$ versioned-state 6 | $% state-0 7 | == 8 | :: 9 | +$ state-0 [%0 base-state-0] 10 | +$ base-state-0 11 | $: 12 | =readers:library 13 | =policies:library 14 | == 15 | :: 16 | +$ card card:agent:gall 17 | -- 18 | %- agent:dbug 19 | =| state-0 20 | =* state - 21 | =< 22 | ^- agent:gall 23 | |_ =bowl:gall 24 | +* this . 25 | def ~(. (default-agent this %|) bowl) 26 | hc ~(. +> bowl) 27 | io ~(. agentio bowl) 28 | gra ~(. graph bowl) 29 | :: 30 | ++ on-init 31 | ^- (quip card _this) 32 | ~& > '%library-proxy initialized successfully' 33 | [(~(watch-our pass:io /local-store) %graph-store /updates)^~ this] 34 | ++ on-save 35 | ^- vase 36 | !>(state) 37 | ++ on-load 38 | |= old-state=vase 39 | ^- (quip card _this) 40 | ~& > '%library-proxy recompiled successfully' 41 | `this(state !<(versioned-state old-state)) 42 | ++ on-poke 43 | |= [=mark =vase] 44 | ^- (quip card _this) 45 | =^ cards state 46 | ?+ mark (on-poke:def mark vase) 47 | %library-command 48 | ?> (is-owner:hc src.bowl) :: only allow ourselves to use this poke 49 | =+ !<(=command:library vase) 50 | (handle-command:hc command) 51 | :: 52 | %library-action 53 | =+ !<(=action:library vase) 54 | (handle-action:hc action) 55 | :: 56 | %library-response 57 | =+ !<(=response:library vase) 58 | (handle-response:hc response) 59 | == 60 | [cards this] 61 | ++ on-agent 62 | |= [=wire =sign:agent:gall] 63 | ^- (quip card _this) 64 | :: (in this model. each ship is responsible solely for sending out updates of resources it owns, and no one else. 65 | :: as a result, we simply trust updates (after imposter check) from a given ship; that is, it is the sole source of truth). 66 | :: type reference https://github.com/urbit/urbit/blob/85fdd6b190479030d2e763b326060ce8020fa9ae/pkg/arvo/sys/lull.hoon#L1689 67 | ?+ -.sign (on-agent:def wire sign) 68 | %kick 69 | :: a %kick doesn't always mean the publisher has voluntarily terminated the subscription. 70 | :: it can also mean that there is network traffic cloggage 71 | :: thus, we must try to resubscribe here, and then once that goes through if we then get a failing watch ack only then do we give up 72 | ~& >> "kicked from subscription {}" 73 | ~& >> "attempting to resubscribe" 74 | ?~ wire ~|("empty wire, can't resubscribe. this shouldn't happen" `this) 75 | :: 76 | ?> ?=([%request-library @ @ ~] wire) 77 | =/ host (slav %p i.t.wire) 78 | =/ name `@tas`i.t.t.wire 79 | [(sub-to-library:hc [host name])^~ this] 80 | :: 81 | %watch-ack 82 | ?~ p.sign 83 | ~& > "subscribed on wire {} successfully" 84 | `this :: no error, subscription was successful 85 | ~& >>> "subscribe on wire {} failed" 86 | `this :: we have truly been kicked. a sad day 87 | :: 88 | %fact 89 | =^ cards state 90 | ?+ p.cage.sign `state 91 | %graph-update-2 92 | =+ !<(=update:store q.cage.sign) 93 | ?: (is-owner:hc src.bowl) 94 | (handle-graph-update-outgoing:hc update) 95 | (handle-graph-update-incoming:hc update) 96 | == 97 | [cards this] 98 | == 99 | :: 100 | ++ on-watch 101 | |= =path 102 | ^- (quip card _this) 103 | =^ cards state 104 | ?+ path (on-watch:def path) 105 | [%updates @ @ ~] 106 | :: path format: /updates/[src.bowl]/[name.rid] 107 | :: human format: /updates/[subscriber-ship]/[library-name] 108 | =/ subscriber `@p`(slav %p i.t.path) 109 | =/ library-name `@tas`i.t.t.path 110 | ?< (is-owner:hc src.bowl) :: do not allow ourselves to subscribe, invalid 111 | ?> =(subscriber src.bowl) :: check for imposter (sus) 112 | =/ policy (~(got by policies) library-name) 113 | ?> (is-allowed:libr subscriber our.bowl policy) 114 | :: we scry the original graph just to get its original creation time 115 | :: otherwise, it is discarded. what is actually sent is an empty graph 116 | :: todo refactor for readability 117 | =/ original-time-sent p:(scry-for:gra update:store /graph/(scot %p our.bowl)/[library-name]) 118 | =/ initial-library-update (create-library:libr [our.bowl library-name] original-time-sent) 119 | :: readers are who's actually interested, and wants to hear updates 120 | :: implicitly, having a successful subscription means you have permission, 121 | :: not necessarily that you are interested in hearing about anything yet. 122 | :: todo refactor following state modif 123 | =. readers 124 | %+ ~(put by readers) 125 | subscriber 126 | (~(gas by *prim:library) [[library-name *(set atom)] ~]) 127 | :_ state 128 | :~ (fact:io graph-update-2+!>(initial-library-update) (incoming-sub-path subscriber library-name)^~) 129 | == 130 | == 131 | [cards this] 132 | ++ on-peek 133 | |= pax=path 134 | ^- (unit (unit cage)) 135 | ?+ pax (on-peek:def pax) 136 | [%x %libraries ~] 137 | :: 1. scry for all graph keys in our local graph store 138 | =/ keys 139 | =/ key-update (scry-for:gra update:store /keys) 140 | ?> ?=(%keys -.q.key-update) 141 | resources.q.key-update 142 | :: this also has the redundancy where the entity.rid is always our.bowl 143 | =/ library-keys=(set resource) 144 | %- silt 145 | %+ skim ~(tap in keys) 146 | |= [key=resource] 147 | ^- ? 148 | =/ mark :: invariant: entity.key == our.bowl 149 | (scry-for:gra (unit @tas) /graph/(scot %p entity.key)/[name.key]/mark) 150 | =(`%graph-validator-library mark) 151 | =/ library-names (silt (turn ~(tap in library-keys) tail)) 152 | ``noun+!>(`(set @tas)`library-names) 153 | :: 154 | [%x %books @ ~] 155 | =/ name=@tas i.t.t.pax 156 | =/ update (scry-for:gra update:store /graph/(scot %p our.bowl)/[name]) 157 | ?> ?=(%add-graph -.q.update) 158 | =/ the-graph graph.q.update 159 | =/ book-tops (silt (turn (tap:orm:store the-graph) head)) 160 | ``noun+!>(`(set atom)`book-tops) 161 | == 162 | ++ on-leave on-leave:def 163 | ++ on-arvo on-arvo:def 164 | ++ on-fail on-fail:def 165 | -- 166 | |_ bowl=bowl:gall 167 | +* gra ~(. graph bowl) 168 | io ~(. agentio bowl) 169 | :: 170 | ++ handle-command 171 | |= [=command:library] 172 | |^ ^- (quip card _state) 173 | =^ cards state 174 | ?- -.command 175 | %create-library 176 | =/ library-name library-name.command 177 | =/ time-sent now.bowl 178 | =/ policy policy.command 179 | =. policies (~(put by policies) library-name policy) :: set the policy for the given rid into the actual state 180 | (poke-local (create-library:libr [our.bowl library-name] time-sent)) 181 | :: 182 | %remove-library 183 | =/ library-name library-name.command 184 | =/ time-sent now.bowl 185 | =. policies (~(del by policies) library-name) :: remove the policy for the given rid from 186 | (poke-local (remove-library:libr [our.bowl library-name] time-sent)) 187 | :: 188 | %add-book 189 | =/ library-name library-name.command 190 | =/ author our.bowl 191 | =/ time-sent now.bowl 192 | =/ book book.command 193 | (poke-local (add-book:libr [our.bowl library-name] author time-sent book)) 194 | :: 195 | %revise-book 196 | =/ library-name library-name.command 197 | =/ top top.command 198 | =/ new-book new-book.command 199 | =/ author our.bowl 200 | =/ time-sent now.bowl 201 | =/ update (scry-for:gra update:store /graph/(scot %p our.bowl)/[library-name]) 202 | ?> ?=(%add-graph -.q.update) 203 | =/ meta-node (got-deep:gra graph.q.update ~[top %meta]) 204 | =/ meta-child-graph 205 | ?> ?=(%graph -.children.meta-node) :: invariant: book will always have at least one revision, i.e. the first ever revisoni, i.e. the original book metadata. this could be made more lax, to automatically create one if missing, but i don't think it makes sense to be lax here... 206 | p.children.meta-node 207 | =/ latest-revision-node (need (pry:orm:store meta-child-graph)) 208 | =/ last-revision-index ~[top %meta key.latest-revision-node] :: we use this construction instead of doing the index.p.post.node dance because it works even if the post has been deletedd (which it should never but i guess its easier to code) 209 | =/ new-index (incr-index:libr last-revision-index) 210 | (poke-local (revise-book-meta:libr [our.bowl library-name] new-index author time-sent new-book)) 211 | :: 212 | %remove-book 213 | =/ library-name library-name.command 214 | =/ top top.command 215 | =/ time-sent now.bowl 216 | :: removing the book from `readers` is handled during the handling of %remove-graph 217 | :: because it needs the metadata to know who to send the update to 218 | (poke-local (remove-book:libr [our.bowl library-name] top time-sent)) 219 | :: 220 | %request-library 221 | =/ rid rid.command 222 | ~| "tried to request access to library that we own" 223 | ?< (is-owner entity.rid) :: invalid. we should never request our own library, this may cause a loop 224 | [(sub-to-library rid)^~ state] 225 | :: 226 | %request-book 227 | =/ rid rid.command 228 | =/ top top.command 229 | ?: (is-owner entity.rid) 230 | ~|("tried to request access to library that we own" !!) 231 | =/ =action:library [%get-book name.rid top] 232 | :: this should crash if we haven't %request-library'd first. 233 | :_ state 234 | (~(poke pass:io /book-request) [entity.rid %library-proxy] library-action+!>(action))^~ 235 | == 236 | [cards state] 237 | :: 238 | ++ poke-local 239 | |= [=update:store] 240 | ^- (quip card _state) 241 | [(poke-local-store update)^~ state] 242 | -- 243 | ++ handle-action 244 | |= [=action:library] 245 | |^ ^- (quip card _state) 246 | =^ cards state 247 | ?- -.action 248 | %add-comment 249 | =/ library-name library-name.action 250 | =/ top top.action 251 | =/ author src.bowl 252 | =/ time-sent now.bowl 253 | =/ comment comment.action 254 | =/ prm (~(get by readers) author) 255 | :: :: commenter must be either: 256 | ?> ?| (is-owner author) :: us 257 | (~(has ju (need prm)) library-name top) :: someone with permissions 258 | == 259 | =/ update (add-comment:libr [our.bowl library-name] top author time-sent comment) 260 | [(poke-local-store update)^~ state] 261 | :: 262 | %remove-comment 263 | =/ library-name library-name.action 264 | =/ comment-index index.action 265 | ?> ?=([@ %comments @ ~] comment-index) 266 | =/ prev-comment-update 267 | (scry-for:gra update:store (weld /graph/(scot %p our.bowl)/[library-name]/node/index/kith (index-to-path:libr comment-index))) 268 | ?. (can-remove-comment src.bowl comment-index prev-comment-update) 269 | `state :: if requesting ship cannot remove comment, silently ignore 270 | =/ remove-update (remove-comment:libr [our.bowl library-name] comment-index now.bowl) 271 | [(poke-local-store remove-update)^~ state] 272 | :: 273 | %get-book 274 | =/ library-name library-name.action 275 | =/ top book-index.action 276 | =/ policy (~(get by policies) library-name) 277 | ?~ policy `state :: if there is no policy set for the given rid, it is an invalid request. ignore 278 | ?. (is-allowed:libr src.bowl our.bowl u.policy) `state :: only give them the books if they are allowed 279 | ?< =(our.bowl src.bowl) :: invalid, disallow ourselves from requesting from our own library 280 | =/ libraries (scry-for (set @tas) /libraries) 281 | ?> (~(has in libraries) library-name) 282 | :: 1. add the person to readers 283 | =/ prm (fall (~(get by readers) src.bowl) *prim:library) 284 | =. prm (~(put ju prm) library-name top) 285 | =. readers (~(put by readers) src.bowl prm) 286 | :: 2. send them the graph update 287 | =/ update (scry-for:gra update:store /graph/(scot %p our.bowl)/[library-name]/node/index/kith/(scot %ud top)) 288 | :_ state 289 | :~ (fact:io graph-update-2+!>(update) ~[(incoming-sub-path src.bowl library-name)]) 290 | == 291 | :: 292 | %get-libraries 293 | =/ libraries (scry-for (set @tas) /libraries) 294 | =? libraries !(is-owner src.bowl) :: filter out allowed libraries if requester isn't the owner 295 | %- silt 296 | %+ skim ~(tap in libraries) 297 | |= [library-name=@tas] 298 | =/ policy (~(got by policies) library-name) 299 | (is-allowed:libr src.bowl our.bowl policy) 300 | :_ state 301 | :~ (~(poke pass:io /) [src.bowl %library-proxy] library-response+!>(available-libraries+libraries)) 302 | == 303 | :: 304 | %get-books 305 | :: todo if/when full-text/extra info is enabled, the resulting data could be a set of book-indexes along with just title and isbn without(!) 306 | :: fulltext, so that you only download the fulltext of books that you care about, and you have more metadata to judge by 307 | =/ library-name library-name.action 308 | =/ policy (~(get by policies) library-name) 309 | ?~ policy `state :: if there is no policy set for the given rid, it is an invalid request. ignore 310 | ?. (is-allowed:libr src.bowl our.bowl u.policy) `state :: only give them list of books if they are allowed 311 | =/ book-indexes (scry-for (set atom) /books/[library-name]) 312 | :_ state 313 | :~ (~(poke pass:io /) [src.bowl %library-proxy] library-response+!>(available-books+[library-name book-indexes])) 314 | == 315 | == 316 | [cards state] 317 | :: 318 | ++ can-remove-comment 319 | |= [=ship comment-index=index:store comment-update=update:store] 320 | ^- ? 321 | ?> ?=(%add-nodes -.q.comment-update) 322 | =/ comment-node (~(got by nodes.q.comment-update) comment-index) 323 | ?. ?=(%.y -.post.comment-node) 324 | %.n :: cannot remove already deleted comment 325 | =/ prev-post p.post.comment-node 326 | =/ prev-author author.prev-post 327 | :: you may remove a comment if you are: 328 | ?| (is-owner src.bowl) :: the owner of the proxy 329 | =(prev-author src.bowl) :: the author of comment 330 | == 331 | -- 332 | ++ handle-response 333 | |= [=response:library] 334 | ^- (quip card _state) 335 | ?- -.response 336 | %available-libraries 337 | ~& libraries.response 338 | `state 339 | :: 340 | %available-books 341 | ~& library-name.response 342 | ~& book-indexes.response 343 | `state 344 | == 345 | ++ handle-graph-update-outgoing 346 | |= [=update:store] 347 | |^ ^- (quip card _state) 348 | :: this is where we forward any graph store updates to any subscriber of ours 349 | =^ cards state 350 | :: resource-for-update always returns a list of one (1) resource 351 | =/ wrapped-rid (resource-for-update:gra !>(update)) 352 | ?~ wrapped-rid 353 | `state :: if theres no resource, we don't forward cause we can't tell if its something based on our own resource 354 | =/ update-rid i.wrapped-rid 355 | ?. =(our.bowl entity.update-rid) 356 | `state :: we only broadcast updates for resources we own 357 | =/ library-name name.update-rid 358 | ?+ -.q.update ~&("ignoring update {<-.q.update>}" `state) 359 | %add-graph `state :: do not forward add graph to anyone. this gets manually forwarded in on-watch 360 | :: 361 | :: the following pokes should never be forwarded 362 | %archive-graph `state 363 | %unarchive-graph `state 364 | %run-updates `state 365 | :: 366 | %add-signatures [(send-if-tracking-uid update library-name index.uid.q.update) state] 367 | %remove-signatures [(send-if-tracking-uid update library-name index.uid.q.update) state] 368 | %add-tag [(send-if-tracking-uid update library-name index.uid.q.update) state] 369 | %remove-tag [(send-if-tracking-uid update library-name index.uid.q.update) state] 370 | :: 371 | %remove-graph 372 | :_ =/ new-state (remove-library state library-name) 373 | new-state 374 | %- zing 375 | ~& readers 376 | %+ murn ~(tap by readers) 377 | |= [her=ship prm=prim:library] 378 | =/ tracked-libraries ~(key by prm) 379 | ?. (~(has in tracked-libraries) library-name) ~ :: only send the update if her is tracking this resource 380 | %- some 381 | =/ paths (incoming-sub-path her library-name)^~ 382 | :~ (fact:io graph-update-2+!>(update) paths) 383 | (kick-only:io her paths) 384 | == 385 | :: 386 | %add-nodes 387 | :_ state 388 | %- zing 389 | %+ murn ~(tap by readers) 390 | |= [her=ship prm=prim:library] 391 | =/ tracked-books=(unit (set @)) (~(get by prm) library-name) 392 | ?~ tracked-books ~ :: if they aren't tracking any books yet, don't bother making cards 393 | %- some 394 | %+ murn ~(tap by nodes.q.update) 395 | |= [idx=index:store *] 396 | ?. (~(has in u.tracked-books) (head idx)) ~ :: only forward this update if they are tracking this book 397 | `(fact:io graph-update-2+!>(update) (incoming-sub-path her library-name)^~) 398 | :: 399 | %remove-posts 400 | :: need to clear reader state *after* creating cards, cause we can't create card without state 401 | :_ =/ new-state (remove-any-books state library-name indices.q.update) 402 | new-state 403 | %+ murn ~(tap by readers) 404 | |= [her=ship prm=prim:library] 405 | =/ tracked-books=(unit (set @)) 406 | (~(get by prm) library-name) 407 | :: if no tracked books for this resource, don't bother making any cards 408 | ?~ tracked-books ~ 409 | :: ensure that users who receive a remove-posts 410 | :: only receive it for indices that they would have 411 | =. indices.q.update (filter-indices indices.q.update u.tracked-books) 412 | `(fact:io graph-update-2+!>(update) (incoming-sub-path her library-name)^~) 413 | == 414 | [cards state] 415 | :: 416 | ++ filter-indices 417 | |= [indices=(set index:store) tracked-books=(set @)] 418 | :: return indices that only pertain to the books that are being tracked 419 | ^- (set index:store) 420 | %- silt 421 | %+ skim ~(tap in indices) 422 | |= [idx=index:store] 423 | (~(has in tracked-books) (head idx)) 424 | ++ send-if-tracking-uid 425 | |= [=update:store library-name=@tas idx=index:store] 426 | ^- (list card) 427 | %+ murn ~(tap by readers) 428 | |= [her=ship prm=prim:library] 429 | =/ tracked-books=(unit (set @)) 430 | (~(get by prm) library-name) 431 | :: if no tracked books for this resource, don't bother making any cards 432 | ?~ tracked-books ~ 433 | ?. (~(has in u.tracked-books) (head idx)) ~ :: only forward this update if they are tracking this book 434 | `(fact:io graph-update-2+!>(update) (incoming-sub-path her library-name)^~) 435 | ++ remove-library 436 | |= [old=_state name=@tas] 437 | ^- _state 438 | =/ new-readers 439 | %- ~(run by readers.old) 440 | |= prm=prim:library 441 | (~(del by prm) name) 442 | old(readers new-readers) 443 | ++ remove-any-books 444 | :: if any indexes are pointing to books 445 | :: clear them from the state 446 | |= [old=_state library-name=@tas indices=(set index:store)] 447 | ^- _state 448 | =/ index-list ~(tap by indices) 449 | =/ new-readers 450 | %- ~(run by readers.old) 451 | |= prm=prim:library 452 | ^- prim:library 453 | |- 454 | ?~ index-list prm :: we've processed all indexes, return the modified prim 455 | =/ idx=index:store i.index-list 456 | ?. ?=([@ ~] idx) $(index-list t.index-list) :: the index doesn't match a book to be deleted, so we skip handling it here 457 | $(index-list t.index-list, prm (~(del ju prm) library-name (head idx))) :: stop tracking any readers for this book 458 | old(readers new-readers) 459 | -- 460 | :: 461 | ++ handle-graph-update-incoming 462 | |= [=update:store] 463 | ^- (quip card _state) 464 | ~& "got foreign graph update {<-.q.update>} from {}" 465 | ::~& update 466 | =^ cards state 467 | =/ wrapped-rid (resource-for-update:gra !>(update)) 468 | ?~ wrapped-rid `state 469 | =/ rid i.wrapped-rid 470 | ?> =(src.bowl entity.rid) :: only owners may send graph updates for resources they own 471 | [(poke-local-store update)^~ state] 472 | [cards state] 473 | :: 474 | ++ poke-local-store 475 | |= [=update:store] 476 | ^- card 477 | (~(poke-our pass:io /) %graph-store graph-update-2+!>(update)) 478 | ++ sub-to-library 479 | |= [rid=resource] 480 | ^- card 481 | =/ wir /request-library/(scot %p entity.rid)/[name.rid] 482 | :: note: agentio prepends "agentio-watch" to the provided wire 483 | (~(watch pass:io wir) [entity.rid %library-proxy] (outgoing-sub-path name.rid)) 484 | :: TODO probably rename to is-host 485 | ++ is-owner 486 | :: is the ship the owner of this proxys 487 | |= [=ship] 488 | =(our.bowl ship) 489 | ++ incoming-sub-path 490 | :: someone is subscribed to us 491 | |= [reader=ship library-name=@tas] 492 | ^- path 493 | /updates/(scot %p reader)/[library-name] 494 | ++ outgoing-sub-path 495 | :: we're subscribed to someone else 496 | |= [library-name=@tas] 497 | ^- path 498 | /updates/(scot %p our.bowl)/[library-name] 499 | ++ scry-for 500 | |* [=mold =path] 501 | .^ mold 502 | %gx 503 | (scot %p our.bowl) 504 | %library-proxy 505 | (scot %da now.bowl) 506 | (snoc `^path`path %noun) 507 | == 508 | -- 509 | -------------------------------------------------------------------------------- /ignore_files.txt: -------------------------------------------------------------------------------- 1 | .* 2 | *.sh 3 | *.md 4 | *.txt 5 | *.js 6 | *.swp 7 | *.save 8 | ./misc/* -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | usage() { printf "Usage: $0 [-w] URBIT_PIER_DIRECTORY \n(-w: flag to watch and live copy code)\n" 1>&2; exit 1; } 3 | 4 | if [ $# -eq 0 ]; then 5 | usage 6 | exit 2 7 | fi 8 | PIER=$1 9 | DIR=$(dirname $0) 10 | EXCLUDE_FILE=$DIR/ignore_files.txt 11 | 12 | while getopts "w" opt; do 13 | case ${opt} in 14 | w) WATCH_MODE="true" 15 | PIER=$2 16 | ;; 17 | *) usage 18 | ;; 19 | esac 20 | done 21 | 22 | # todo use watch -n or smtn 23 | if [ -z "$WATCH_MODE" ]; then 24 | echo "Installed $DIR" 25 | rsync -r --exclude-from=$EXCLUDE_FILE * $PIER/home/ 26 | else 27 | echo "Watching for changes to copy to ${PIER}..." 28 | while [ 0 ] 29 | do 30 | sleep 0.8 31 | rsync -r --exclude-from=$EXCLUDE_FILE * $PIER/home/ 32 | done 33 | fi 34 | -------------------------------------------------------------------------------- /lib/library.hoon: -------------------------------------------------------------------------------- 1 | /- *post, resource, library, spider 2 | /+ *graph-store, strandio 3 | |% 4 | ++ incr-index 5 | :: increments the last value in the index by 1 6 | |= [idx=index:post] 7 | ^- index:post 8 | =/ old-revision-count=@ (rear idx) :: get the last item 9 | =/ new-revision-count=@ +(old-revision-count) :: increment revision count 10 | (snoc (snip idx) new-revision-count) :: remove last item, combine with new revision count 11 | :: 12 | ++ make-meta-contents 13 | |= [=book:library] 14 | ^- (list content) 15 | ~[[%text title.book] [%text isbn.book]] 16 | ++ make-comment-contents 17 | |= [comment-text=comment:library] 18 | ^- (list content) 19 | ~[[%text comment-text]] 20 | :: 21 | ++ is-allowed 22 | |= [requester=ship host=ship =policy:library] 23 | ^- ? 24 | ?: =(requester host) :: host is always allowed 25 | %.y 26 | ?- -.policy 27 | %open %.y 28 | %children (team:title host requester) 29 | %whitelist (~(has in ships.policy) requester) 30 | == 31 | ++ index-to-path 32 | |= [idx=index:post] 33 | ^- path 34 | (turn idx (cury scot %ud)) 35 | :: 36 | :: 37 | ++ create-library 38 | |= [[owner=ship name=@tas] time-sent=time] 39 | ^- update 40 | :- time-sent 41 | :* %add-graph 42 | [owner name] 43 | *graph 44 | `%graph-validator-library 45 | %.n :: don't overwrite an existing library 46 | == 47 | ++ remove-library 48 | |= [[owner=ship name=@tas] time-sent=time] 49 | ^- update 50 | :- time-sent 51 | [%remove-graph [owner name]] 52 | ++ add-book 53 | |= [rid=resource author=ship time-sent=time =book:library] 54 | ^- update 55 | =| blank=post 56 | =: author.blank author 57 | time-sent.blank time-sent 58 | contents.blank ~ 59 | == 60 | =/ meta-contents (make-meta-contents book) 61 | =/ top time-sent 62 | :- time-sent 63 | :+ %add-nodes rid 64 | %- ~(gas by *(map index node)) 65 | :~ [~[top] [[%.y blank(index ~[top])] [%empty ~]]] 66 | [~[top %meta] [[%.y blank(index ~[top %meta])] [%empty ~]]] 67 | [~[top %meta 1] [[%.y blank(index ~[top %meta 1], contents meta-contents)] [%empty ~]]] 68 | [~[top %comments] [[%.y blank(index ~[top %comments])] [%empty ~]]] 69 | == 70 | :: 71 | ++ remove-book 72 | |= [rid=resource top=@ time-sent=time] 73 | :: this could be improved. 74 | :: right now it clears the post content of the top level structural node 75 | :: what would be better is to clear all of the indexes 76 | :: so we need to have a list of the indexes of all [top %meta @] revisions and all [top %comments @]. 77 | :: right now, landscape also just deletes the top level node, using it as a "this post has been marked deleted" 78 | :: and just hides the whole. so it also works the same way and ends up being fine 79 | ^- update 80 | :- time-sent 81 | :+ %remove-posts rid 82 | (silt ~[[top ~]]) 83 | :: 84 | ++ revise-book-meta 85 | |= [rid=resource new-revision-index=index:post author=ship time-sent=time =book:library] 86 | ^- update 87 | =/ meta-contents=(list content) (make-meta-contents book) 88 | =| meta-post=post 89 | =: author.meta-post author 90 | index.meta-post new-revision-index 91 | time-sent.meta-post time-sent 92 | contents.meta-post meta-contents 93 | == 94 | :- time-sent 95 | :+ %add-nodes rid 96 | %- ~(gas by *(map index node)) 97 | ~[[new-revision-index [[%.y meta-post] [%empty ~]]]] 98 | :: 99 | ++ add-comment 100 | |= [rid=resource top=@ author=ship time-sent=time =comment:library] 101 | ^- update 102 | =/ comment-index=index ~[top %comments time-sent] 103 | =/ comment-contents (make-comment-contents comment) 104 | =| comment-post=post 105 | =: author.comment-post author 106 | index.comment-post comment-index 107 | time-sent.comment-post time-sent 108 | contents.comment-post comment-contents 109 | == 110 | :- time-sent 111 | :+ %add-nodes rid 112 | %- ~(gas by *(map index node)) 113 | ~[[comment-index [[%.y comment-post] [%empty ~]]]] 114 | :: 115 | ++ remove-comment 116 | |= [rid=resource comment-index=index time-sent=time] 117 | ^- update 118 | ~| "invalid index {} provided" 119 | ?> ?=([@ %comments @ ~] comment-index) 120 | :- time-sent 121 | :+ %remove-posts rid 122 | (silt ~[comment-index]) 123 | -- 124 | -------------------------------------------------------------------------------- /mar/graph/validator/library.hoon: -------------------------------------------------------------------------------- 1 | /- *post, graph=graph-store 2 | => 3 | |% 4 | ++ is-title-valid 5 | |= [title=cord] 6 | ^- ? 7 | %.y 8 | ++ is-isbn-valid :: we could do https://github.com/xlcnd/isbnlib/blob/41f59c74a69a2675c3f1/isbnlib/_core.py#L52 9 | |= [isbn=cord] 10 | ^- ? 11 | =/ len (met 3 isbn) 12 | ?|(=(len 10) =(len 13)) 13 | -- 14 | |_ i=indexed-post 15 | ++ grow 16 | |% 17 | ++ noun i 18 | :: 19 | ++ notification-kind `[%message [0 1] %count %none] 20 | :: 21 | ++ graph-indexed-post 22 | ^- indexed-post 23 | ?+ index.p.i !! 24 | :: top level node: book 25 | :: structural node with no content 26 | :: 27 | [@ ~] 28 | ~| "top level book node should be empty!" 29 | ?> ?=(~ contents.p.i) 30 | i 31 | :: metadata revision container 32 | :: structural node with no content 33 | :: 34 | [@ %meta ~] 35 | ~| "metadata revision container should be empty!" 36 | ?> ?=(~ contents.p.i) 37 | i 38 | :: single metadata revision 39 | :: content node. first %text element is treated as title, 40 | :: second is treated as isbn 41 | :: 42 | [@ %meta @ ~] 43 | =/ contents contents.p.i 44 | ?> ?=([[%text *] [%text *] ~] contents.p.i) 45 | =/ title +.i.contents.p.i 46 | ?> (is-title-valid title) 47 | =/ isbn +.i.t.contents.p.i 48 | ?> (is-isbn-valid isbn) 49 | i 50 | :: comments section container 51 | :: structural node with no content 52 | :: 53 | [@ %comments ~] 54 | ~| "comments section container should be empty!" 55 | ?> ?=(~ contents.p.i) 56 | i 57 | :: comment 58 | :: 59 | [@ %comments @ ~] 60 | :: we could allow any content 61 | ::?> ?=(* contents.p.i) :: any content is allowed. 62 | ?> ?=([[%text *] ~] contents.p.i) :: only a single %text content is allowed 63 | =/ contents contents.p.i 64 | i 65 | == 66 | -- 67 | ++ grab 68 | |% 69 | ++ noun indexed-post 70 | -- 71 | ++ grad %noun 72 | -- 73 | -------------------------------------------------------------------------------- /mar/library/action.hoon: -------------------------------------------------------------------------------- 1 | /- library 2 | |_ act=action:library 3 | ++ grab 4 | |% 5 | ++ noun action:library 6 | -- 7 | ++ grow 8 | |% 9 | ++ noun act 10 | -- 11 | ++ grad %noun 12 | -- 13 | -------------------------------------------------------------------------------- /mar/library/command.hoon: -------------------------------------------------------------------------------- 1 | /- library 2 | |_ cmd=command:library 3 | ++ grab 4 | |% 5 | ++ noun command:library 6 | -- 7 | ++ grow 8 | |% 9 | ++ noun cmd 10 | -- 11 | ++ grad %noun 12 | -- 13 | -------------------------------------------------------------------------------- /mar/library/response.hoon: -------------------------------------------------------------------------------- 1 | /- library 2 | |_ rsp=response:library 3 | ++ grab 4 | |% 5 | ++ noun response:library 6 | -- 7 | ++ grow 8 | |% 9 | ++ noun rsp 10 | -- 11 | ++ grad %noun 12 | -- 13 | -------------------------------------------------------------------------------- /misc/library cmdline snippets.txt: -------------------------------------------------------------------------------- 1 | |mount % 2 | |commit %home 3 | 4 | =library -build-file %/sur/library/hoon 5 | =store -build-file %/sur/graph-store/hoon 6 | =libstore -build-file %/lib/graph-store/hoon 7 | =libgraph -build-file %/lib/graph/hoon 8 | =liblibrary -build-file %/lib/library/hoon 9 | 10 | 11 | |start %library-proxy 12 | 13 | :: ~zod and moon ota setup 14 | |merge %kids our %home 15 | |ota ~zod %kids 16 | 17 | 18 | :: handy state inspections 19 | :: 20 | 21 | :graph-store +dbug [%state 'graphs'] 22 | :library-proxy +dbug 23 | 24 | =libraries .^((set @tas) %gx /(scot %p our)/library-proxy/(scot %da now)/libraries/noun) 25 | =available-books .^((set atom) %gx /(scot %p our)/library-proxy/(scot %da now)/books/library1/noun) 26 | 27 | 28 | 29 | ========================================== 30 | 31 | 32 | :: ~zod creates a library called %library1 33 | :library-proxy &library-command [%create-library %library1 [%open ~]] 34 | 35 | :: ~zod tries to add a book with invalid isbn length, fails 36 | :library-proxy &library-command [%add-book %library1 ['Dune' 'not10or13chars']] 37 | 38 | :: ~zod adds two books adding a book 39 | :library-proxy &library-command [%add-book %library1 ['Dune' '10charlong']] 40 | :library-proxy &library-command [%add-book %library1 ['ABC' 'thirteenchars']] 41 | 42 | ::: replace with the top level index fragment of the book titled 'Dune' 43 | =top-of-dune 170.141.184.505.000.000.000.000.000.000.000.000.000 44 | 45 | :: ~zod revises book 'Dune' 46 | :library-proxy &library-command [%revise-book %library1 top-of-dune ['Dune: The Book' 'thirteenchars']] 47 | 48 | :: ~zod adds comment on book 'Dune' 49 | :library-proxy &library-action [%add-comment %library1 top-of-dune 'I love dune'] 50 | 51 | :: ~nus issues poke %get-libraries for libraries available on ~zod 52 | :~zod/library-proxy &library-action [%get-libraries ~] 53 | 54 | :: ~nus issues poke %get-books for books available on ~zod/library1 55 | :~zod/library-proxy &library-action [%get-books %library1] 56 | 57 | :: ~nus tries to add a book, fails because isn't in readers yet 58 | :~zod/library-proxy &library-command [%add-book %library1 ['fake book' 'isbnisbnis']] 59 | 60 | :: ~nus tries to add comment on book, fails cause not part of readers 61 | :~zod/library-proxy &library-action [%add-comment %library1 top-of-dune 'failing comment'] 62 | 63 | :: ~nus issues %request-library poke to start recieving updates to %library1 64 | :library-proxy &library-command [%request-library [~zod %library1]] 65 | 66 | :: ~nus issues %request-library poke to start recieving updates %library1 67 | :library-proxy &library-command [%request-book [~zod %library1] top-of-dune] 68 | 69 | :: ~nus tries to add comment, is successful 70 | :~zod/library-proxy &library-action [%add-comment %library1 top-of-dune 'dune is ok'] 71 | 72 | :: ~nus adds a second comment again, is successful 73 | :~zod/library-proxy &library-action [%add-comment %library1 top-of-dune 'in my opinion'] 74 | 75 | :: ~nus deletes own comment, succeeds 76 | :~zod/library-proxy &library-action [%remove-comment %library1 ~[top-of-dune %comments ]] 77 | 78 | :: ~zod deletes nus's comment, succeeds 79 | :library-proxy &library-action [%remove-comment %library1 ~[top-of-dune %comments ]] 80 | 81 | :: ~nus tries to delete ~zod's comment, fails 82 | :~zod/library-proxy &library-action [%remove-comment %library1 ~[top-of-dune %comments ]] 83 | 84 | :: ~nus tries to delete ~zod's book, fails 85 | :~zod/library-proxy &library-command [%remove-book %library1 top-of-dune] 86 | 87 | :: ~nus tries to delete ~zod's library, fails 88 | :~zod/library-proxy &library-command [%remove-library %library1] 89 | 90 | :: ~zod deletes book with title 'Dune', succeeds. appropriate state in proxy is cleared 91 | :library-proxy &library-command [%remove-book %library1 top-of-dune] 92 | 93 | :: ~zod deletes the library, succeeds 94 | :library-proxy &library-command [%remove-library %library1] 95 | 96 | 97 | :: testing out policies 98 | :: 99 | 100 | :library-proxy &library-command [%create-library %children-only [%children ~]] 101 | :library-proxy &library-command [%create-library %frens-only [%whitelist (silt ~[~nus ~sun])]] 102 | 103 | 104 | :: ~nus tries to join %children-only, fails (tested) 105 | :library-proxy &library-command [%request-library %children-only] 106 | 107 | :: ~sampel-palnet-dozzod-dozzod, moon of ~zod, tries to join %children-only, succeeds 108 | :library-proxy &library-command [%request-library %children-only] 109 | 110 | ::: 111 | 112 | :: ~sampel-palnet-dozzod-dozzod, moon of ~zod, tries to join %frens-only, fails 113 | :library-proxy &library-command [%request-library %frens-only] 114 | 115 | :: ~nus and sun try to join %frens-only, succeed (tested) 116 | :library-proxy &library-command [%request-library %frens-only] 117 | 118 | 119 | :: misc failure cases 120 | :: 121 | 122 | :: ~zod tries to request his own book, fails 123 | :library-proxy &library-command [%request-book %library1 top-of-dune] 124 | 125 | :: ~zod tries to request his own library, fails 126 | :library-proxy &library-command [%request-library %library1] 127 | 128 | 129 | ## Working Scries 130 | 131 | .^((unit @tas) %gx /=graph-store=/graph-mark/(scot %p our)/library1/noun) 132 | 133 | .^(update:store %gx /=graph-store=/keys/noun) 134 | 135 | .^(update:store %gx /=graph-store=/graph/(scot %p our)/library1/noun) 136 | 137 | =idx ~[170.141.184.505.067.084.173.349.631.314.681.135.104 8.319.395.793.566.789.475 170.141.184.505.067.091.826.073.283.725.486.981.120] 138 | .^(update:store %gx (weld /=graph-store=/node/(scot %p our)/library1 (snoc `path`(turn idx (cury scot %ud)) %noun))) 139 | 140 | > ?+ -.q.upd !! %keys resources.q.upd == 141 | {[entity=~zod name=%library1]} 142 | 143 | =libraries .^((set resource:store) %gx /=library-proxy=/libraries/noun) 144 | 145 | =available-books .^((set atom) %gx /=library-proxy=/books/library1/noun) 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /sur/library.hoon: -------------------------------------------------------------------------------- 1 | /- *resource, store=graph-store 2 | |% 3 | +$ comment cord 4 | +$ book 5 | $: 6 | title=cord 7 | isbn=cord 8 | :: TODO fulltext=cord ? 9 | == 10 | :: 11 | :: networking datastructures. 12 | :: the following are for tracking who to send updates to 13 | :: not for access control. (for that look at `policies`) 14 | +$ prim (jug @tas atom) :: for a given library name, what indexes does the reader want to hear about? 15 | +$ readers (map ship prim) :: given a ship, what are the libraries and books per library that it cares about? 16 | :: only we can poke ourselves with a command 17 | +$ command 18 | $% [%create-library library-name=@tas =policy] 19 | [%remove-library library-name=@tas] 20 | [%add-book library-name=@tas =book] 21 | [%revise-book library-name=@tas top=@ new-book=book] :: XX if we had to, we could use a separate "book-diff" type instead of =book but it's not necessary 22 | [%remove-book library-name=@tas top=@] 23 | :: the following commands originate from your own library proxy to request a foreign library on your behalf 24 | [%request-library rid=resource] 25 | [%request-book rid=resource top=@] 26 | == 27 | :: 28 | :: anyone can poke us with an action, incl. ourselves 29 | +$ action 30 | $% [%add-comment library-name=@tas top=@ =comment] :: anyone can add if they are allowed 31 | [%remove-comment library-name=@tas =index:store] :: anyone can remove their own comment 32 | [%get-book library-name=@tas book-index=atom] :: someone else wants to now get the data for a book and hear about its updates. we should only ever hear this from a foreign library-proxy and never us ourself 33 | :: scry wrappers 34 | [%get-libraries ~] 35 | [%get-books library-name=@tas] 36 | == 37 | :: 38 | :: poke-backs to scry wrappers 39 | +$ response 40 | $% [%available-libraries libraries=(set @tas)] 41 | [%available-books library-name=@tas book-indexes=(set @)] 42 | == 43 | :: 44 | +$ policies (map @tas policy) 45 | +$ policy 46 | $% [%open ~] :: any ship is allowed 47 | [%children ~] :: any children (i.e. moons) are allowed 48 | [%whitelist ships=(set ship)] :: any ships in the provided set are allowed 49 | == 50 | -- 51 | --------------------------------------------------------------------------------