Switch intro example to blobstore client
diff --git a/demo/BUCK b/demo/BUCK
index 846c149..43b83aa 100644
--- a/demo/BUCK
+++ b/demo/BUCK
@@ -4,8 +4,8 @@
     name = "demo",
     srcs = glob(["src/**/*.rs"]),
     deps = [
+        ":blobstore-sys",
         ":bridge",
-        ":demo-sys",
         "//:cxx",
     ],
 )
@@ -13,21 +13,21 @@
 rust_cxx_bridge(
     name = "bridge",
     src = "src/main.rs",
-    deps = [":demo-include"],
+    deps = [":blobstore-include"],
 )
 
 cxx_library(
-    name = "demo-sys",
-    srcs = ["src/demo.cc"],
+    name = "blobstore-sys",
+    srcs = ["src/blobstore.cc"],
     compiler_flags = ["-std=c++14"],
     deps = [
+        ":blobstore-include",
         ":bridge/include",
-        ":demo-include",
     ],
 )
 
 cxx_library(
-    name = "demo-include",
-    exported_headers = ["include/demo.h"],
+    name = "blobstore-include",
+    exported_headers = ["include/blobstore.h"],
     deps = ["//:core"],
 )
diff --git a/demo/BUILD b/demo/BUILD
index 5bc8c77..cce8119 100644
--- a/demo/BUILD
+++ b/demo/BUILD
@@ -6,8 +6,8 @@
     name = "demo",
     srcs = glob(["src/**/*.rs"]),
     deps = [
+        ":blobstore-sys",
         ":bridge",
-        ":demo-sys",
         "//:cxx",
     ],
 )
@@ -15,21 +15,21 @@
 rust_cxx_bridge(
     name = "bridge",
     src = "src/main.rs",
-    deps = [":demo-include"],
+    deps = [":blobstore-include"],
 )
 
 cc_library(
-    name = "demo-sys",
-    srcs = ["src/demo.cc"],
+    name = "blobstore-sys",
+    srcs = ["src/blobstore.cc"],
     copts = ["-std=c++14"],
     deps = [
+        ":blobstore-include",
         ":bridge/include",
-        ":demo-include",
     ],
 )
 
 cc_library(
-    name = "demo-include",
-    hdrs = ["include/demo.h"],
+    name = "blobstore-include",
+    hdrs = ["include/blobstore.h"],
     deps = ["//:core"],
 )
diff --git a/demo/build.rs b/demo/build.rs
index a3c31b3..c1b55cc 100644
--- a/demo/build.rs
+++ b/demo/build.rs
@@ -1,10 +1,10 @@
 fn main() {
     cxx_build::bridge("src/main.rs")
-        .file("src/demo.cc")
+        .file("src/blobstore.cc")
         .flag_if_supported("-std=c++14")
         .compile("cxxbridge-demo");
 
     println!("cargo:rerun-if-changed=src/main.rs");
-    println!("cargo:rerun-if-changed=src/demo.cc");
-    println!("cargo:rerun-if-changed=include/demo.h");
+    println!("cargo:rerun-if-changed=src/blobstore.cc");
+    println!("cargo:rerun-if-changed=include/blobstore.h");
 }
diff --git a/demo/include/blobstore.h b/demo/include/blobstore.h
new file mode 100644
index 0000000..d89583a
--- /dev/null
+++ b/demo/include/blobstore.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "rust/cxx.h"
+#include <memory>
+
+namespace org {
+namespace blobstore {
+
+struct MultiBuf;
+struct BlobMetadata;
+
+class BlobstoreClient {
+public:
+  BlobstoreClient();
+  uint64_t put(MultiBuf &buf) const;
+  void tag(uint64_t blobid, rust::Str tag) const;
+  BlobMetadata metadata(uint64_t blobid) const;
+
+private:
+  class impl;
+  std::shared_ptr<impl> impl;
+};
+
+std::unique_ptr<BlobstoreClient> new_blobstore_client();
+
+} // namespace blobstore
+} // namespace org
diff --git a/demo/include/demo.h b/demo/include/demo.h
deleted file mode 100644
index fafc474..0000000
--- a/demo/include/demo.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-#include "rust/cxx.h"
-#include <memory>
-#include <string>
-
-namespace org {
-namespace example {
-
-class ThingC {
-public:
-  ThingC(std::string appname);
-  ~ThingC();
-
-  std::string appname;
-};
-
-struct SharedThing;
-
-std::unique_ptr<ThingC> make_demo(rust::Str appname);
-const std::string &get_name(const ThingC &thing);
-void do_thing(SharedThing state);
-
-} // namespace example
-} // namespace org
diff --git a/demo/src/blobstore.cc b/demo/src/blobstore.cc
new file mode 100644
index 0000000..a75affc
--- /dev/null
+++ b/demo/src/blobstore.cc
@@ -0,0 +1,71 @@
+#include "demo/include/blobstore.h"
+#include "demo/src/main.rs.h"
+#include <algorithm>
+#include <functional>
+#include <set>
+#include <string>
+#include <unordered_map>
+
+namespace org {
+namespace blobstore {
+
+// Toy implementation of an in-memory blobstore.
+//
+// In reality the implementation of BlobstoreClient could be a large complex C++
+// library.
+class BlobstoreClient::impl {
+  friend BlobstoreClient;
+  using Blob = struct {
+    std::string data;
+    std::set<std::string> tags;
+  };
+  std::unordered_map<uint64_t, Blob> blobs;
+};
+
+BlobstoreClient::BlobstoreClient() : impl(new typename BlobstoreClient::impl) {}
+
+// Upload a new blob and return a blobid that serves as a handle to the blob.
+uint64_t BlobstoreClient::put(MultiBuf &buf) const {
+  std::string contents;
+
+  // Traverse the caller's chunk iterator.
+  //
+  // In reality there might be sophisticated batching of chunks and/or parallel
+  // upload implemented by the blobstore's C++ client.
+  while (true) {
+    auto chunk = next_chunk(buf);
+    if (chunk.size() == 0) {
+      break;
+    }
+    contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size());
+  }
+
+  // Insert into map and provide caller the handle.
+  auto blobid = std::hash<std::string>{}(contents);
+  impl->blobs[blobid] = {std::move(contents), {}};
+  return blobid;
+}
+
+// Add tag to an existing blob.
+void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const {
+  impl->blobs[blobid].tags.emplace(tag);
+}
+
+// Retrieve metadata about a blob.
+BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const {
+  BlobMetadata metadata{};
+  auto blob = impl->blobs.find(blobid);
+  if (blob != impl->blobs.end()) {
+    metadata.size = blob->second.data.size();
+    std::for_each(blob->second.tags.begin(), blob->second.tags.end(),
+                  [&](auto &t) { metadata.tags.emplace_back(t); });
+  }
+  return metadata;
+}
+
+std::unique_ptr<BlobstoreClient> new_blobstore_client() {
+  return std::make_unique<BlobstoreClient>();
+}
+
+} // namespace blobstore
+} // namespace org
diff --git a/demo/src/demo.cc b/demo/src/demo.cc
deleted file mode 100644
index 79c693f..0000000
--- a/demo/src/demo.cc
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "demo/include/demo.h"
-#include "demo/src/main.rs.h"
-#include <iostream>
-
-namespace org {
-namespace example {
-
-ThingC::ThingC(std::string appname) : appname(std::move(appname)) {}
-
-ThingC::~ThingC() { std::cout << "done with ThingC" << std::endl; }
-
-std::unique_ptr<ThingC> make_demo(rust::Str appname) {
-  return std::make_unique<ThingC>(std::string(appname));
-}
-
-const std::string &get_name(const ThingC &thing) { return thing.appname; }
-
-void do_thing(SharedThing state) { print_r(*state.y); }
-
-} // namespace example
-} // namespace org
diff --git a/demo/src/main.rs b/demo/src/main.rs
index 8f62084..10f57e5 100644
--- a/demo/src/main.rs
+++ b/demo/src/main.rs
@@ -1,39 +1,59 @@
-#[cxx::bridge(namespace = "org::example")]
+#[cxx::bridge(namespace = "org::blobstore")]
 mod ffi {
-    struct SharedThing {
-        z: i32,
-        y: Box<ThingR>,
-        x: UniquePtr<ThingC>,
+    // Shared structs with fields visible to both languages.
+    struct BlobMetadata {
+        size: usize,
+        tags: Vec<String>,
     }
 
-    extern "C" {
-        include!("demo/include/demo.h");
-
-        type ThingC;
-        fn make_demo(appname: &str) -> UniquePtr<ThingC>;
-        fn get_name(thing: &ThingC) -> &CxxString;
-        fn do_thing(state: SharedThing);
-    }
-
+    // Rust types and signatures exposed to C++.
     extern "Rust" {
-        type ThingR;
-        fn print_r(r: &ThingR);
+        type MultiBuf;
+
+        fn next_chunk(buf: &mut MultiBuf) -> &[u8];
+    }
+
+    // C++ types and signatures exposed to Rust.
+    extern "C++" {
+        include!("demo/include/blobstore.h");
+
+        type BlobstoreClient;
+
+        fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
+        fn put(&self, parts: &mut MultiBuf) -> u64;
+        fn tag(&self, blobid: u64, tag: &str);
+        fn metadata(&self, blobid: u64) -> BlobMetadata;
     }
 }
 
-pub struct ThingR(usize);
-
-fn print_r(r: &ThingR) {
-    println!("called back with r={}", r.0);
+// An iterator over contiguous chunks of a discontiguous file object.
+//
+// Toy implementation uses a Vec<Vec<u8>> but in reality this might be iterating
+// over some more complex Rust data structure like a rope, or maybe loading
+// chunks lazily from somewhere.
+pub struct MultiBuf {
+    chunks: Vec<Vec<u8>>,
+    pos: usize,
+}
+pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] {
+    let next = buf.chunks.get(buf.pos);
+    buf.pos += 1;
+    next.map(Vec::as_slice).unwrap_or(&[])
 }
 
 fn main() {
-    let x = ffi::make_demo("demo of cxx::bridge");
-    println!("this is a {}", ffi::get_name(x.as_ref().unwrap()));
+    let client = ffi::new_blobstore_client();
 
-    ffi::do_thing(ffi::SharedThing {
-        z: 222,
-        y: Box::new(ThingR(333)),
-        x,
-    });
+    // Upload a blob.
+    let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
+    let mut buf = MultiBuf { chunks, pos: 0 };
+    let blobid = client.put(&mut buf);
+    println!("blobid = {}", blobid);
+
+    // Add a tag.
+    client.tag(blobid, "rust");
+
+    // Read back the tags.
+    let metadata = client.metadata(blobid);
+    println!("tags = {:?}", metadata.tags);
 }