diff --git a/README.md b/README.md index 86dc572..fdcce22 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ Email `contact` at `vaporsoft.xyz` to ask questions or get involved. Your feedba # Caveats - * Current windows support is unknown (and unlikely to work in current state). Please report bugs. - * Not published to RubyGems yet (see installation instructions below) + * Implementation is assumed to be stable, but does not have much production use. + * Windows support is unknown (and unlikely to work in current state). Please report bugs. * Single threaded use is assumed. Built for a single user per OS process. Many design tradeoffs were made around that use case. * Bundling operations need performance tuning. Optimizations are planned and help is welcome. -# Installation +# Installation - Bundler (Easy) Add this to you Gemfile: @@ -46,23 +46,16 @@ See `kitchen_sink.sh` examples. # Current Status - - [ ] CLI is wrapping `FILE.` and `FEED.` multihahshes in "string quotes". Why? - - [ ] Update Dev docs in protocol spec to reflect changes to `lipmaa` header. - [ ] Update spec document CLI usage examples to reflect API changes in 2020. + - [ ] Ability to unblock someone. + - [ ] Ability to delete a key from a draft. + - [ ] Update Dev docs in protocol spec to reflect changes to `lipmaa` header. - [ ] 100% class / module documentation - [ ] Run a [terminology extraction tool](https://www.visualthesaurus.com/vocabgrabber/#) on the documentation and write a glossary of terms. - - [ ] Publish to RubyGems + - [ ] Ability to list all blocked users. + - [ ] Ability to unblock a user. -# Optimizations - - - [ ] add parsers and validators for all CLI inputs - - [ ] Make the switch to LevelDB, RocksDB, [UNQLite](https://unqlite.org/features.html) or similar (currently using Ruby PStore). - - [ ] Reduce whole darn repo into single module to aide portability. `::Helpers` module is OK. - - [ ] Update the bundles.md document once `bundle consume` works. - - [ ] Performance benchmarks (Do this second to last!) - - [ ] Performance tuning (Do this last!) - -# New Features / Road Map +# "Nice to Have" - [ ] Support partial verification via `lipmaa` property. - [ ] Add `--since=`/`--until=` args to `bundle create` for sending partial / "slice" bundles. diff --git a/bin/pigeon-cli b/bin/pigeon-cli index 8a37e57..efedd4a 100755 --- a/bin/pigeon-cli +++ b/bin/pigeon-cli @@ -101,7 +101,7 @@ module Pigeon desc "append", "Add a key/value pair to the current DRAFT" def append(key, raw_value = "") - v = raw_value != "" ? raw_value : STDIN.read + v = raw_value != "" ? raw_value : STDIN.read.chomp if db.get_draft db.update_draft(key, v) puts db.get_draft.render_as_draft @@ -191,10 +191,10 @@ module Pigeon desc "identity SUBCOMMAND ...ARGS", "Manage `.pgn` identity" subcommand "identity", Identity - desc "message SUBCOMMAND ...ARGS", "Manage blob storage" + desc "message SUBCOMMAND ...ARGS", "Manage text-based messages" subcommand "message", PigeonMessage - desc "peer SUBCOMMAND ...ARGS", "Manage blob storage" + desc "peer SUBCOMMAND ...ARGS", "Manage blob (file) storage" subcommand "peer", Peer end end diff --git a/lib/pigeon.rb b/lib/pigeon.rb index 57a6d5f..88f107d 100644 --- a/lib/pigeon.rb +++ b/lib/pigeon.rb @@ -55,7 +55,7 @@ module Pigeon STRING_KEYS_ONLY = "String keys only" MISSING_BODY = "BODY CANT BE EMPTY" STILL_HAVE_DRAFT = "RESET DRAFT (%s) FIRST (db.delete_current_draft)" - MISSING_DRAFT = "NO DRAFT. CREATE ONE FIRST. Call db.new_draft(kind, body)" + MISSING_DRAFT = "NO DRAFT. CREATE ONE FIRST. Call db.new_draft(kind:, body:)" RUNAWAY_LOOP = "RUNAWAY LOOP DETECTED" # Constants for internal use only: diff --git a/ruby_tutorial.md b/ruby_tutorial.md index 43329df..cbc0fc6 100644 --- a/ruby_tutorial.md +++ b/ruby_tutorial.md @@ -12,13 +12,12 @@ This document will teach you how to: * Build messages using drafts. * Manage and query existing messages. * Replicate a database among peers. - * Go beyond simple text messages and attach files to messages. + * Attach binary files to messages. * Communicate with remote databases using "bundles". This guide assumes you are familiar with Ruby and the Pigeon Protocol. For an introduction to the protocol, see our protocol specification [here](https://tildegit.org/PigeonProtocolConsortium/protocol_spec). -Pigeon strive to have a "natural" API rather than a simple one. We will cover the API methods listed below. These are the only methods you will need to know to build a pigeon-based application: - +Below is a list of all methods needed to run a Pigeon node. Pigeon strives to have a _natural_ API rather than a simple one, which means you may not need to know every single method to operate a node successfully. **BLOB METHODS:** `#add_blob`,`#get_blob` @@ -32,7 +31,7 @@ Pigeon strive to have a "natural" API rather than a simple one. We will cover th **PEER METHODS:** `#all_peers`, `#add_peer`, `#remove_peer`, `#all_blocks`, `#block_peer`, `#peer_blocked` -Once you understand the methods listed above, you will have everything you need to start writing Pigeon-based applications. Please let us know what you build! Send an email to `contact` at `vaporsoft.xyz` with your progress. +**Note to application developers:** Please let us know what you build! Send an email to `contact` at `vaporsoft.xyz` with your progress. ## Installation @@ -77,57 +76,56 @@ Let's see if we have a draft to work with: ```ruby db.get_draft -# => #"\"bar\"", "b"=>"&CH...QG.sha256"}, @depth=-1, @kind="unit_test", @lipmaa="NONE", @prev="NONE", @signature="NONE"> +RuntimeError: NO DRAFT. CREATE ONE FIRST. Call db.new_draft(kind:, body:) +from lib/pigeon/database.rb:104:in `get_draft' ``` -It appears that my database has a draft. I don't actually remember what this draft was, so I will just delete it before proceeding. +We do not have a draft yet. We need to create one: ```ruby -db.delete_current_draft -# => nil +db.new_draft(kind: "garden_diary", body: {"message_text"=>"Tomato plant looking healthy."}) +=> #"\"Hello, world!\""}, @depth=-1, @kind="example123", +# @lipmaa="NONE", @prev="NONE", @signature="NONE"> ``` -Now I can create a new draft. I am going to create a new `garden_diary` for a fictitious gardening app. In my gardening app, I expect every `garden_diary` message to have a `message_text` entry in its body. We can add that now. +The command above creates a new draft entry of kind `garden_entry` with on key/value pair in the body. We can view the draft at any time via `#get_draft`: ```ruby -db.new_draft(kind: "garden_diary", body: {"message_text" => "Tomato plant looking healthy."}) -# => #"\"Tomato plant looking healthy.\""}, @depth=-1, @kind="garden_diary", @lipmaa="NONE", @prev="NONE", @signature="NONE"> +db.get_draft +# => #"\"Hello, world!\""}, @depth=-1, +# @kind="example123", @lipmaa="NONE", @prev="NONE", +# @signature="NONE"> ``` -A few notes about this draft message: - - * `"garden_diary` is the message `kind`. This is definable by application developers and helps determine the type of message we are dealing with. A fictitious diary app might have other entries such as `"status_update"` or `"photo_entry"`. It depends on the application you are building. - * Notice that my hash used string keys for the `"message_text"` body entry. You can only use strings for key / value pairs (no `:symbols` or numbers). Later on we will learn how to attach files to messages. - * The `body:` part is optional. I could have called `db.new_draft(kind: "garden_diary")` and added key / value pairs to the body later. - -Oops! Speaking of adding entries to a draft's body, it looks like I forgot something. In my fictitious gardening app, a `garden_diary` entry doesn't just have a `"message_text"`, it also has a `"current_mood"` entry. Luckily, it is easy to add keys to unpublished drafts. Let's add the key now: +Since the draft has not been published to the feed, its contents are mutable. We can add a new key/value pair to the message body with the following command: ```ruby db.update_draft("current_mood", "Feeling great") # => "\"Feeling great\"" ``` -OK, I think our draft message is looking better. Let's take a look: +A few notes about this draft message: -```ruby -db.get_draft -# => => #"\"Tomato plant looking healthy.\"", "current_mood"=>"\"Feeling great\""}, @depth=-1, @kind="garden_diary", @lipmaa="NONE", @prev="NONE", @signature="NONE"> -``` + * `"garden_diary` is the message `kind`. This is definable by application developers and helps determine the type of message we are dealing with. A fictitious diary app might have other entries such as `"status_update"` or `"photo_entry"`. It depends on the application you are building. + * I used string keys for the `"message_text"` body entry rather than symbols or numbers. This is because you can only use strings for key / value pairs (no `:symbols` or numbers). Later on we will learn how to attach files to messages. + * The `body:` part is optional. I could have called `db.new_draft(kind: "garden_diary")` and added key / value pairs to the body later. -I can see the status of my current draft message using `db.get_draft`. It returns a `Pigeon::Draft` object, whish is not very human readable. To get a more human readable version, I can use the `render_as_draft` method on a `Draft` object: +Let's take a final look at our draft message. To get a more human readable version, I can use the `render_as_draft` method on a `Draft` object: ```ruby human_readable_string = db.get_draft.render_as_draft # => "author DRAFT\nkind garden_dia...." puts human_readable_string -# => author DRAFT -# kind garden_diary -# prev DRAFT -# depth DRAFT -# lipmaa DRAFT +# => author DRAFT +# depth DRAFT +# kind example123 +# lipmaa DRAFT +# prev DRAFT # -# message_text:"Tomato plant looking healthy." -# current_mood:"Feeling great" +# greeting:"Hello, world!" +# current_mood:"Feeling great" ``` Some interesting things about the draft we just rendered: @@ -135,6 +133,12 @@ Some interesting things about the draft we just rendered: * Unlike a message, a draft has no signature (yet). * The `author`, `kind`, `prev`, `depth`, `lipmaa` properties are all set to `"DRAFT"`. Real values will be populated when we finally publish the draft. +If we want to start over, we can delete a draft via `delete_current_draft`: +```ruby +db.delete_current_draft +# => nil +``` + ## Turning Drafts Into Messages Now that I am happy with my draft, I can publish it. Once published, my message cannot be modified, so it is very important to visually inspect a draft with `db.get_draft` before proceeding. @@ -142,33 +146,33 @@ Since we did that in the last step, I will go ahead and publish the message: ``` my_message = db.publish_draft -# => #, -# @body={"message_text"=>"\"Tomato plant looking healthy.\"", "current_mood"=>"\"Feeling great\""}, -# @depth=0, -# @kind="garden_diary", -# @lipmaa=0, -# @prev="NONE", -# @signature="2ZHC8TX3P2SQVQTMFYXTAT4S02RN43JNZNECRJDA7QMSJNE5G7NV7GTRK3PGFHFY9MBE1Q95BCKBSJH4V0PTX6945A34Z1CARTGH230.sig.ed25519"> +=> #, + @body={"greeting"=>"\"Hello, world!\"", "current_mood"=>"\"Feeling great\""}, + @depth=0, + @kind="garden_diary", + @lipmaa="NONE", + @prev="NONE", + @signature="QNY...208"> ``` Let's look at our new message in a human-readable way: ```ruby puts my_message.render -# => author @753...T6G.ed25519 -# kind garden_diary -# prev NONE -# depth 0 -# lipmaa 0 +# author USER.6DQ4RRNBKJ2T4EY5E1GZYYX6X6SZXV1W0GNH1HA4KGKA5KZ2Y2DG +# depth 0 +# kind example123 +# lipmaa NONE +# prev NONE # -# message_text:"Tomato plant looking healthy." -# current_mood:"Feeling great" +# greeting:"Hello, world!" +# current_mood:"Feeling great" # -# signature 2ZH...230.sig.ed25519 +# signature QNY...208 ``` -We see that unlike our draft, the message has a signature. The header fields are also populated. +Unlike our draft, the message has a signature. The header fields are also populated. In the next section, we will learn more about messages.