Camlistore branch, master, updated. 71f1646fdfbcfbfdc4cb6081e2ed46224a08ce82

0 views
Skip to first unread message

Camlistore-git

unread,
May 5, 2011, 9:27:59 PM5/5/11
to camlistor...@googlegroups.com
This is an automated email from the git hooks/post-receive script.

The branch, master has been updated
via 71f1646fdfbcfbfdc4cb6081e2ed46224a08ce82 (commit)
via 1a14a121d04f6f5d58d62fa4ea72ff6c619d588b (commit)
via 962d65877d16d21da352e8850c4f179f55b015dc (commit)
via 73a7329401227d0fde8cd6b088c41b4961a7b2a4 (commit)
via 916c2cdaad3e6da77b38331c5a793e5b8d4ca309 (commit)
via b078f4f88ce1488e02a12e92a45e2ada913c9c39 (commit)
via 9f8aae9d0aea8a95fc02a030a6f758886fd22365 (commit)
from 127b23ad947b89359bdeffe0029e2257710dc203 (commit)

- Log -----------------------------------------------------------------
commit 71f1646fdfbcfbfdc4cb6081e2ed46224a08ce82
Author: Brad Fitzpatrick <br...@danga.com>
Date: Thu May 5 18:27:19 2011 -0700

more UI js/html work, for signatures.

commit 1a14a121d04f6f5d58d62fa4ea72ff6c619d588b
Author: Brad Fitzpatrick <br...@danga.com>
Date: Thu May 5 17:15:32 2011 -0700

improve jsonsign discovery handler

commit 962d65877d16d21da352e8850c4f179f55b015dc
Author: Brad Fitzpatrick <br...@danga.com>
Date: Thu May 5 16:41:00 2011 -0700

some sig discovery work

commit 73a7329401227d0fde8cd6b088c41b4961a7b2a4
Author: Brad Fitzpatrick <br...@danga.com>
Date: Thu May 5 16:31:19 2011 -0700

blobref memory store

commit 916c2cdaad3e6da77b38331c5a793e5b8d4ca309
Author: Brad Fitzpatrick <br...@danga.com>
Date: Thu May 5 16:31:09 2011 -0700

disable fonts to speed up slide development

commit b078f4f88ce1488e02a12e92a45e2ada913c9c39
Author: Brad Fitzpatrick <br...@danga.com>
Date: Thu May 5 15:50:16 2011 -0700

more slides

commit 9f8aae9d0aea8a95fc02a030a6f758886fd22365
Author: Brad Fitzpatrick <br...@danga.com>
Date: Thu May 5 15:06:49 2011 -0700

more camlistore slide work

-----------------------------------------------------------------------

Summary of changes:
lib/go/camli/blobref/fetcher.go | 31 +-
lib/go/camli/blobserver/handlers/get.go | 26 +-
lib/go/camli/httputil/httputil.go | 16 +
server/go/camlistored/camlistored.go | 2 +-
server/go/camlistored/sig.go | 63 +++-
server/go/camlistored/ui/index.html | 25 +
server/go/camlistored/ui/ui.js | 56 ++-
.../2011-05-07-Camlistore-Sao-Paolo/blobjects.png | Bin 0 -> 136514 bytes
talks/2011-05-07-Camlistore-Sao-Paolo/diagrams.odp | Bin 0 -> 15790 bytes
talks/2011-05-07-Camlistore-Sao-Paolo/fsbackup.png | Bin 0 -> 26577 bytes
talks/2011-05-07-Camlistore-Sao-Paolo/index.html | 534 ++++++++++++--------
talks/2011-05-07-Camlistore-Sao-Paolo/repl.png | Bin 0 -> 36057 bytes
talks/2011-05-07-Camlistore-Sao-Paolo/slides.js | 4 +-
13 files changed, 512 insertions(+), 245 deletions(-)
create mode 100644 talks/2011-05-07-Camlistore-Sao-Paolo/blobjects.png
create mode 100644 talks/2011-05-07-Camlistore-Sao-Paolo/diagrams.odp
create mode 100644 talks/2011-05-07-Camlistore-Sao-Paolo/fsbackup.png
create mode 100644 talks/2011-05-07-Camlistore-Sao-Paolo/repl.png

Diff:
diff --git a/lib/go/camli/blobref/fetcher.go b/lib/go/camli/blobref/fetcher.go
index f5dfcfe..fb15978 100644
--- a/lib/go/camli/blobref/fetcher.go
+++ b/lib/go/camli/blobref/fetcher.go
@@ -20,9 +20,12 @@ import (
"crypto"
"fmt"
"io"
+ "io/ioutil"
"log"
"os"
"path/filepath"
+ "strings"
+ "sync"
)

var _ = log.Printf
@@ -113,18 +116,36 @@ func (df *DirFetcher) Fetch(b *BlobRef) (file ReadSeekCloser, size int64, err os
// MemoryStore stores blobs in memory and is a Fetcher and
// StreamingFetcher. Its zero value is usable.
type MemoryStore struct {
- m map[string][]byte
+ lk sync.Mutex
+ m map[string]string
}

-func (s *MemoryStore) AddBlob(hashtype crypto.Hash, data []byte) os.Error {
+func (s *MemoryStore) AddBlob(hashtype crypto.Hash, data string) (*BlobRef, os.Error) {
if hashtype != crypto.SHA1 {
- return os.NewError("blobref: unsupported hash type")
+ return nil, os.NewError("blobref: unsupported hash type")
}
hash := hashtype.New()
- hash.Write(data)
+ hash.Write([]byte(data))
bstr := fmt.Sprintf("sha1-%x", hash.Sum())
+ s.lk.Lock()
+ defer s.lk.Unlock()
+ if s.m == nil {
+ s.m = make(map[string]string)
+ }
s.m[bstr] = data
log.Printf("added %s", bstr)
- return nil
+ return Parse(bstr), nil
}

+func (s *MemoryStore) FetchStreaming(b *BlobRef) (file io.ReadCloser, size int64, err os.Error) {
+ s.lk.Lock()
+ defer s.lk.Unlock()
+ if s.m == nil {
+ return nil, 0, os.ENOENT
+ }
+ str, ok := s.m[b.String()]
+ if !ok {
+ return nil, 0, os.ENOENT
+ }
+ return ioutil.NopCloser(strings.NewReader(str)), int64(len(str)), nil
+}
diff --git a/lib/go/camli/blobserver/handlers/get.go b/lib/go/camli/blobserver/handlers/get.go
index e139a4f..b5b6b93 100644
--- a/lib/go/camli/blobserver/handlers/get.go
+++ b/lib/go/camli/blobserver/handlers/get.go
@@ -38,14 +38,20 @@ import (

var kGetPattern *regexp.Regexp = regexp.MustCompile(`/camli/([a-z0-9]+)-([a-f0-9]+)$`)

+type GetHandler struct {
+ Fetcher blobref.StreamingFetcher
+ AllowGlobalAccess bool
+}
+
func CreateGetHandler(fetcher blobref.StreamingFetcher) func(http.ResponseWriter, *http.Request) {
+ gh := &GetHandler{Fetcher: fetcher}
return func(conn http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/camli/sha1-deadbeef00000000000000000000000000000000" {
// Test handler.
simulatePrematurelyClosedConnection(conn, req)
return
}
- handleGet(conn, req, fetcher)
+ gh.ServeHTTP(conn, req)
}
}

@@ -57,27 +63,27 @@ func sendUnauthorized(conn http.ResponseWriter) {
fmt.Fprintf(conn, "<h1>Unauthorized</h1>")
}

-func handleGet(conn http.ResponseWriter, req *http.Request, fetcher blobref.StreamingFetcher) {
+func (h *GetHandler) ServeHTTP(conn http.ResponseWriter, req *http.Request) {
blobRef := blobFromUrlPath(req.URL.Path)
if blobRef == nil {
- httputil.BadRequestError(conn, "Malformed GET URL.")
+ http.Error(conn, "Malformed GET URL.", 400)
return
}

switch {
- case auth.IsAuthorized(req):
- serveBlobRef(conn, req, blobRef, fetcher)
+ case h.AllowGlobalAccess || auth.IsAuthorized(req):
+ serveBlobRef(conn, req, blobRef, h.Fetcher)
case auth.TriedAuthorization(req):
log.Printf("Attempted authorization failed on %s", req.URL)
sendUnauthorized(conn)
default:
- handleGetViaSharing(conn, req, blobRef, fetcher)
+ handleGetViaSharing(conn, req, blobRef, h.Fetcher)
}
}

// serveBlobRef sends 'blobref' to 'conn' as directed by the Range header in 'req'
func serveBlobRef(conn http.ResponseWriter, req *http.Request,
- blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) {
+blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) {

file, size, err := fetcher.FetchStreaming(blobRef)
switch err {
@@ -180,7 +186,7 @@ func serveBlobRef(conn http.ResponseWriter, req *http.Request,

// Unauthenticated user. Be paranoid.
func handleGetViaSharing(conn http.ResponseWriter, req *http.Request,
- blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) {
+blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) {

viaPathOkay := false
startTime := time.Nanoseconds()
@@ -307,5 +313,5 @@ func simulatePrematurelyClosedConnection(conn http.ResponseWriter, req *http.Req
flusher.Flush()
}
wrc, _, _ := hj.Hijack()
- wrc.Close() // without sending final chunk; should be an error for the client
-}
\ No newline at end of file
+ wrc.Close() // without sending final chunk; should be an error for the client
+}
diff --git a/lib/go/camli/httputil/httputil.go b/lib/go/camli/httputil/httputil.go
index 6c63edb..b913e6c 100644
--- a/lib/go/camli/httputil/httputil.go
+++ b/lib/go/camli/httputil/httputil.go
@@ -22,6 +22,7 @@ import (
"json"
"os"
"log"
+ "strings"
)

func BadRequestError(conn http.ResponseWriter, errorMessage string) {
@@ -46,3 +47,18 @@ func ReturnJson(conn http.ResponseWriter, data interface{}) {
conn.Write(bytes)
conn.Write([]byte("\n"))
}
+
+type PrefixHandler struct {
+ Prefix string
+ Handler http.Handler
+}
+
+func (p *PrefixHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+ if !strings.HasPrefix(req.URL.Path, p.Prefix) {
+ http.Error(rw, "Inconfigured PrefixHandler", 500)
+ return
+ }
+ req.Header.Set("X-PrefixHandler-PathBase", p.Prefix)
+ req.Header.Set("X-PrefixHandler-PathSuffix", req.URL.Path[len(p.Prefix):])
+ p.Handler.ServeHTTP(rw, req)
+}
diff --git a/server/go/camlistored/camlistored.go b/server/go/camlistored/camlistored.go
index 02beb5a..0b6d280 100644
--- a/server/go/camlistored/camlistored.go
+++ b/server/go/camlistored/camlistored.go
@@ -273,7 +273,7 @@ func configFileMain() {
prefix, err)
}
createdHandlers[prefix] = h
- ws.Handle(prefix, h)
+ ws.Handle(prefix, &httputil.PrefixHandler{prefix, h})
}
switch {
case handlerType == "search":
diff --git a/server/go/camlistored/sig.go b/server/go/camlistored/sig.go
index 707dc19..4e682f6 100644
--- a/server/go/camlistored/sig.go
+++ b/server/go/camlistored/sig.go
@@ -18,6 +18,7 @@ package main

import (
"bytes"
+ "crypto"
"crypto/openpgp"
"crypto/openpgp/armor"
"fmt"
@@ -28,6 +29,8 @@ import (
"strings"

"camli/blobref"
+ "camli/blobserver/handlers"
+ "camli/httputil"
"camli/jsonconfig"
"camli/jsonsign"
)
@@ -44,8 +47,12 @@ type JSONSignHandler struct {
// of the longer forms.
keyId string

+ pubKeyBlobRef *blobref.BlobRef
pubKeyFetcher blobref.StreamingFetcher

+ pubKeyBlobRefServeSuffix string // "camli/sha1-xxxx"
+ pubKeyHandler http.Handler
+
entity *openpgp.Entity
}

@@ -98,19 +105,58 @@ func createJSONSignHandler(conf jsonconfig.Obj) (http.Handler, os.Error) {
wc.Close()
log.Printf("got key: %s", buf.String())

- h.pubKeyFetcher = nil // TODO
+ ms := new(blobref.MemoryStore)
+ h.pubKeyBlobRef, err = ms.AddBlob(crypto.SHA1, buf.String())
+ if err != nil {
+ return nil, err
+ }
+ h.pubKeyFetcher = ms
+
+ h.pubKeyBlobRefServeSuffix = "camli/" + h.pubKeyBlobRef.String()
+ h.pubKeyHandler = &handlers.GetHandler{
+ Fetcher: ms,
+ AllowGlobalAccess: true, // just public keys
+ }

return h, nil
}

func (h *JSONSignHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+ base := req.Header.Get("X-PrefixHandler-PathBase")
+ subPath := req.Header.Get("X-PrefixHandler-PathSuffix")
switch req.Method {
+ case "GET":
+ switch subPath {
+ case "":
+ http.Redirect(rw, req, base + "camli/sig/discovery", http.StatusFound)
+ return
+ case h.pubKeyBlobRefServeSuffix:
+ h.pubKeyHandler.ServeHTTP(rw, req)
+ return
+ case "camli/sig/sign":
+ fallthrough
+ case "camli/sig/verify":
+ http.Error(rw, "POST required", 400)
+ return
+ case "camli/sig/discovery":
+ m := map[string]interface{}{
+ "publicKeyId": h.keyId,
+ "signHandler": base + "camli/sig/sign",
+ "verifyHandler": base + "camli/sig/verify",
+ }
+ if h.pubKeyBlobRef != nil {
+ m["publicKeyBlobRef"] = h.pubKeyBlobRef.String()
+ m["publicKey"] = base + h.pubKeyBlobRefServeSuffix
+ }
+ httputil.ReturnJson(rw, m)
+ return
+ }
case "POST":
- switch {
- case strings.HasSuffix(req.URL.Path, "/camli/sig/sign"):
+ switch subPath {
+ case "camli/sig/sign":
h.handleSign(rw, req)
return
- case strings.HasSuffix(req.URL.Path, "/camli/sig/verify"):
+ case "camli/sig/verify":
h.handleVerify(rw, req)
return
}
@@ -125,9 +171,16 @@ func (h *JSONSignHandler) handleVerify(rw http.ResponseWriter, req *http.Request
func (h *JSONSignHandler) handleSign(rw http.ResponseWriter, req *http.Request) {
req.ParseForm()

+ badReq := func(s string) {
+ http.Error(rw, s, http.StatusBadRequest)
+ log.Printf("bad request: %s", s)
+ return
+ }
+ // TODO: SECURITY: auth
+
jsonStr := req.FormValue("json")
if jsonStr == "" {
- http.Error(rw, "Missing json parameter", http.StatusBadRequest)
+ badReq(rw, "Missing json parameter", http.StatusBadRequest)
return
}
if len(jsonStr) > kMaxJsonLength {
diff --git a/server/go/camlistored/ui/index.html b/server/go/camlistored/ui/index.html
index e896a37..8f98503 100644
--- a/server/go/camlistored/ui/index.html
+++ b/server/go/camlistored/ui/index.html
@@ -8,10 +8,35 @@
<h1>Camlistored UI</h1>

<form>
+ <h2>Root Discovery</h2>
<p><input type="button" id="discobtn" onclick="discover()" value="Do Discovery" /></p>
<div id="discores" style="border: 2px solid gray">(discovery results)</div>
+
+
+ <h2>Signing</h2>
+ <p><input type="button" id="sigdiscobtn" onclick="discoverJsonSign()" value="Do jsonSign discovery" /></p>
+ <div id="sigdiscores" style="border: 2px solid gray">(jsonsign discovery results)</div>
+
+ <table>
+ <tr align='left'>
+ <th>JSON blob to sign: <input type='button' id='addkeyref' onclick='addKeyRef()' value="Add keyref"/></th>
+ <th></th>
+ <th>Signed blob:</th>
+ </tr>
+ <tr>
+ <td><textarea id='clearjson' rows=10 cols=40>{"camliVersion": 1,
+ "camliType": "whatever",
+ "foo": "bar"
+}</textarea></td>
+ <td valign='middle'><input type='button' id='sign' onclick='doSign()' value="Sign &gt;&gt;" /></td>
+ <td><textarea id="signedjson" rows=10 cols=40 readonly></textarea></td>
+ </tr>
+ </table>
+
+ <h2>Search</h2>
<p><input type="button" id="searchbtn" onclick="search()" value="Do Search" /></p>
<div id="searchres" style="border: 2px solid gray">(search results)</div>
+
</form>

</body>
diff --git a/server/go/camlistored/ui/ui.js b/server/go/camlistored/ui/ui.js
index 4401045..91c6ece 100644
--- a/server/go/camlistored/ui/ui.js
+++ b/server/go/camlistored/ui/ui.js
@@ -38,8 +38,62 @@ function discover() {
xhr.send();
}

+var sigdisco = null;
+
+function discoverJsonSign() {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState != 4) { return; }
+ if (xhr.status != 200) {
+ console.log("no status 200; got " + xhr.status);
+ return;
+ }
+ sigdisco = JSON.parse(xhr.responseText);
+ document.getElementById("sigdiscores").innerHTML = JSON.stringify(sigdisco);
+ };
+ xhr.open("GET", disco.jsonSignRoot + "/camli/sig/discovery", true);
+ xhr.send();
+}
+
+function addKeyRef() {
+ if (!sigdisco) {
+ alert("must do jsonsign discovery first");
+ return;
+ }
+ clearta = document.getElementById("clearjson");
+ var j;
+ try {
+ j = JSON.parse(clearta.value);
+ } catch (x) {
+ alert(x);
+ return
+ }
+ j.camliSigner = sigdisco.publicKeyBlobRef;
+ clearta.value = JSON.stringify(j);
+}
+
+function doSign() {
+ if (!sigdisco) {
+ alert("must do jsonsign discovery first");
+ return;
+ }
+ clearta = document.getElementById("clearjson");
+
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState != 4) { return; }
+ if (xhr.status != 200) {
+ alert("got status " + xhr.status)
+ return;
+ }
+ document.getElementById("signedjson").value = xhr.responseText;
+ };
+ xhr.open("POST", sigdisco.signHandler, true)
+ xhr.send("json=" + encodeURIComponent(clearta.vale));
+}
+
function search() {
- var xhr = new XMLHttpRequest();
+ var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) { return; }
if (xhr.status != 200) {
diff --git a/talks/2011-05-07-Camlistore-Sao-Paolo/blobjects.png b/talks/2011-05-07-Camlistore-Sao-Paolo/blobjects.png
new file mode 100644
index 0000000..7ede091
Binary files /dev/null and b/talks/2011-05-07-Camlistore-Sao-Paolo/blobjects.png differ
diff --git a/talks/2011-05-07-Camlistore-Sao-Paolo/diagrams.odp b/talks/2011-05-07-Camlistore-Sao-Paolo/diagrams.odp
new file mode 100644
index 0000000..629da12
Binary files /dev/null and b/talks/2011-05-07-Camlistore-Sao-Paolo/diagrams.odp differ
diff --git a/talks/2011-05-07-Camlistore-Sao-Paolo/fsbackup.png b/talks/2011-05-07-Camlistore-Sao-Paolo/fsbackup.png
new file mode 100644
index 0000000..8c6d240
Binary files /dev/null and b/talks/2011-05-07-Camlistore-Sao-Paolo/fsbackup.png differ
diff --git a/talks/2011-05-07-Camlistore-Sao-Paolo/index.html b/talks/2011-05-07-Camlistore-Sao-Paolo/index.html
index c290736..0e7b039 100755
--- a/talks/2011-05-07-Camlistore-Sao-Paolo/index.html
+++ b/talks/2011-05-07-Camlistore-Sao-Paolo/index.html
@@ -164,7 +164,7 @@
Motivation (cont)
</h3>
<ul>
- <li>I still want to solve the Decentralized Social Network Problem
+ <li>I still want to help solve the Decentralized Social Network Problem
<ul>
<li>protocols, not companies</li>
<li>gmail, hotmail: hosted versions of SMTP, IMAP</li>
@@ -194,17 +194,27 @@
</h3>
<ul>
<li>one private dumping ground to store anything</li>
- <li><i><b>your</b></i> private repo, for life</li>
- <li>backups, filesystems, objects, my website, ...</li>
+ <li>backups, filesystems, objects, photos, likes, bookmarks, shares, my website, ...</li>
<li>live backup my phone</li>
<li>live replicate / sync my dumping group between my house & laptop & Amazon & Google</li>
- <li>give out access to / sub-trees to friends or the world</li>
<li>web UI (ala gmail, docs.google.com, etc) or FUSE filesystem</li>
<li>Easy for end-users; powerful for dorks</li>
</ul>
</article>

<article>
+ <h3>
+ Security Model
+ </h3>
+ <ul>
+ <li><i><b>your</b></i> private repo, for life</li>
+ <li>everything private by default</li>
+ <li>grant access to specific objects/trees with friends or the world</li>
+ <li>web UI or CLI tools let you share</li>
+ </ul>
+ </article>
+
+ <article>
<h1 class='center'>
So what's with the silly name?
</h1>
@@ -275,210 +285,358 @@
</ul>
</article>

+ <article>
+ <h2>
+ From the bottom up...
+ </h2>
+ </article>
+
+ <article>
+ <h2>
+ Blob Server
+ </h2>
+ </article>
+
+ <article>
+ <h3>Blob Server: how dumb it is</h3>
+ <ul>
+ <li>"Blob" == zero or more bytes. <i class='red'>no</i> meta-data</li>
+ <li>private operations, to owner of data only:</li>
+ <ul>
+ <li class='green'>get(blobref) → blob</li>
+ <li class='green'>stat(blobref+) → [(blobref, size), ...]</li>
+ <li class='green'>put(blobref, blob)</li>
+ <li class='green'>enumerate(..) → [(blobref, size)...] (sorted by blobref)</li>
+ </ul>
+ <li>no public (non-owner) access</li>
+ <li>HTTP interface: <tt>GET /camli/sha1-xxxxxxx HTTP/1.1</tt></li>
+ <li><span class='green'>delete(blobref)</span> is disabled by default, privileged op for GC or replication queues only</li>
+ </ul>
+ </article>
+
+ <article>
+ <h3>Blob Server: seriously, no metadata</h3>
+ <ul>
+ <li>no filenames</li>
+ <li>no "mime types"</li>
+ <li>no "{create,mod,access} time"</li>
+ <li>size is implicit</li>
+ <li>blob: just some bytes</li>
+ <li>metadata? layers above.</li>
+ </ul>
+ </article>
+
+ <article>
+ <h1 class='center'>
+ Uh, what can you do with that?
+ </h1>
+ </article>
+
+ <article>
+ <h3>Uh, what can you do with that?</h3>
+ <ul>
+ <li>with just a blob server?</li>
+ <li>not much</li>
+ <li>but let's start with an easy example...</li>
+ </ul>
+ </article>
+
+ <article>
+ <h1 class='center'>
+ Filesystem Backups
+ </h1>
+ </article>
+
+ <article>
+ <h3>Filesystem Backups</h3>
+ <ul>
+ <li>previous project: brackup
+ <ul>
+ <li>good: Perl, slide/dice/encrypt S3 backup, content-addressed, good iterative backups</li>
+ <li>bad: large (several MB) "backup manifest" text files
+ </ul>
+ </li>
+
+ <li>fossil/venti, git, etc: directories content-addressed by content of their children, hash trees, etc</li>
+ <li>git: "tree objects", "commmit objects", etc</li>
+ <li>Camlistore: "schema blobs"</li>
+ </ul>
+ </article>
+
+ <article>
+ <h3>Schema: how to model your content</h3>
+ <ul>
+ <li>Camlistore defines <i>one possible</i> schema</li>
+ <li>but blobserver doesn't know about it all</li>
+ <li>tools generate schema,</li>
+ <li>indexer + search understand the schema.</li>
+ </ul>
+ </article>
+
+ <article>
+ <h3>Schema Blobs</h3>
+ <ul>
+ <li>so if all blobs are just dumb blobs of bytes with no metadata,</li>
+ <li>how do you store metadata?</li>
+ <li>as blobs themselves!</li>
+ </ul>
+ </article>
+
+ <article>
+ <h3>Minimal Schema Blob</h3>
+
+ <section>
+ <pre>{
+ "camliVersion": 1,
+ "camliType": "whatever"
+}</pre>
+ </section>
+ <p>Whitespace doesn't matter. Just must be valid JSON in its
+entirety. Use whatever JSON libraries you've got.</p>
+
+ <p>That one is named<br/><tt class='smaller'>sha1-19e851fe3eb3d1f3d9d1cefe9f92c6f3c7d754f6</tt></p>
+ <p>or perhaps: <tt class='smaller'>sha512-2c6746aba012337aaf113fd63c24d994a0703d33eb5d6ed58859e45dc4e02dcf<br/>dae5c4d46c5c757fb85d5aff342245fe4edb780c028a6f3c994c1295236c931e</tt></p>
+
+ </article>
+
<!-- END -->

<article>
- <p>
- This is a slide with just text. This is a slide with just text.
- This is a slide with just text. This is a slide with just text.
- This is a slide with just text. This is a slide with just text.
- </p>
- <p>
- There is more text just underneath.
- </p>
+ <h3>Schema blob; type "file"</h3>
+ <section><pre>{"camliVersion": 1,
+ <span style='background: #fff'>"camliType": "file",</span>
+ "fileName": "foo.dat",
+ "unixPermission": "0644",
+ ...,
+ "size": 6000133,
+ "contentParts": [
+ {"blobRef": "sha1-...dead", "size": 111},
+ {"blobRef": "sha1-...beef", "size": 5000000, "offset": 492 },
+ {"size": 1000000},
+ {"blobRef": "digalg-blobref", "size": 22},
+ ]
+}</pre></section>
+ </article>
+
+ <article>
+ <h3>Schema blob; type "directory"</h3>
+ <section><pre>{"camliVersion": 1,
+ <span style='background: #fff'>"camliType": "directory",</span>
+ "fileName": "foodir",
+ "unixPermission": "0755",
+ ...,
+ "entries": <span style='background: #fff'>"sha1-c3764bc2138338d5e2936def18ff8cc9cda38455"</span>
+}</pre></section>
+ </article>
+
+ <article>
+ <h3>Schema blob; type "static-set"</h3>
+ <section><pre>{"camliVersion": 1,
+ <span style='background: #fff'>"camliType": "static-set",</span>
+"members": [
+ "sha1-xxxxxxxxxxxx",
+ "sha1-xxxxxxxxxxxx",
+ "sha1-xxxxxxxxxxxx",
+ "sha1-xxxxxxxxxxxx",
+ "sha1-xxxxxxxxxxxx",
+ "sha1-xxxxxxxxxxxx",
+ ]
+}</pre></section>
</article>

<article>
<h3>
- Simple slide with header and text
+ Backup a directory...
</h3>
- <p>
- This is a slide with just text. This is a slide with just text.
- This is a slide with just text. This is a slide with just text.
- This is a slide with just text. This is a slide with just text.
- </p>
- <p>
- There is more text just underneath with a <code>code sample: 5px</code>.
- </p>
+ <section><pre>$ camput --file $HOME
+sha1-8659a52f726588dc44d38dfb22d84a4da2902fed</pre></section>
+
+<p>(like git/hg/fossil, that identifier represents everything down.)</p>
+
+<p>Iterative backups are cheap, easy identifier to share, etc</p>
+
+<p>But how will you remember that identifier? (later)</p>
</article>

- <article class='smaller'>
+ <article>
<h3>
- Simple slide with header and text (small font)
+ But what about mutable data?
</h3>
- <p>
- This is a slide with just text. This is a slide with just text.
- This is a slide with just text. This is a slide with just text.
- This is a slide with just text. This is a slide with just text.
- </p>
- <p>
- There is more text just underneath with a <code>code sample: 5px</code>.
- </p>
+ <ul>
+ <li>immutable data is easy to represent & reference</li>
+ <ul>
+ <li><tt class='smaller'>sha1-8659a52f726588dc44d38dfb22d84a4da2902fed</tt> is an immutable snapshot</li>
+ </ul>
+ <li>how to represent mutable data in an immutable, content-addressed world?</li>
+ <li>how to share a reference to a mutable object when changing an object mutates its name?</li>
+ </ul>
</article>

<article>
+ <h1 class='center'>
+ Objects & "Permanodes"
+ </h1>
+ </article>
+
+ <article>
<h3>
- Slide with bullet points and a longer title, just because we
- can make it longer
+ Terminology
</h3>
<ul>
- <li>
- Use this template to create your presentation
- </li>
- <li>
- Use the provided color palette, box and arrow graphics, and
- chart styles
- </li>
- <li>
- Instructions are provided to assist you in using this
- presentation template effectively
- </li>
- <li>
- At all times strive to maintain Google's corporate look and feel
- </li>
+ <li><span class='red'>blob</span>: just dumb, immutable series of bytes</li>
+ <li><span class='red'>schema blob</span>: a blob that's a valid JSON object w/ camliVersion & camliType</li>
+ <li><span class='red'>signed schema blob</span> aka "<span class='red'>claim</span>": a schema blob with an embedded OpenPGP signature</li>
+ <li><span class='red'>object</span>: something mutable. represented as an anchor "<span class='blue'>permanode</span>" + a set of mutations (<span class='blue'>claims</span>)</li>
+ <li><span class='red'>permanode</span>: a stable reference. an anchor. just a <span class='blue'>signed schema blob</span>, but of almost no content...</li>
</ul>
</article>

<article>
<h3>
- Slide with bullet points that builds
+ Permanode
</h3>
- <ul class="nobuild">
- <li>
- This is an example of a list
- </li>
- <li>
- The list items fade in
- </li>
- <li>
- Last one!
- </li>
- </ul>
+ <section><pre><span style='font-weight: bold' class='blue'>$ camput --permanode</span>
+sha1-ea799271abfbf85d8e22e4577f15f704c8349026
+
+<span style='font-weight: bold' class="blue">$ camget sha1-ea799271abfbf85d8e22e4577f15f704c8349026</span>
+<span style="background: #ff7">{"camliVersion": 1,
+ "camliSigner": "sha1-c4da9d771661563a27704b91b67989e7ea1e50b8",
+ <span style='font-weight: bold'>"camliType": "permanode"</span>,
+ "random": "oj)r}$Wa/[J|XQThNdhE"</span>
+,"camliSig":"iQEcBAABAgAGBQJNRxceAAoJEGjzeDN/6vt8ihIH/Aov7FRIq4dODAPWGDwqL
+1X9Ko2ZtSSO1lwHxCQVdCMquDtAdI3387fDlEG/ALoT/LhmtXQgYTt8QqDxVdu
+EK1or6/jqo3RMQ8tTgZ+rW2cj9f3Q/dg7el0Ngoq03hyYXdo3whxCH2x0jajSt4RCc
+gdXN6XmLlOgD/LVQEJ303Du1OhCvKX1A40BIdwe1zxBc5zkLmoa8rClAlHdqwo
+gxYFY4cwFm+jJM5YhSPemNrDe8W7KT6r0oA7SVfOan1NbIQUel65xwIZBD0ah
+CXBx6WXvfId6AdiahnbZiBup1fWSzxeeW7Y2/RQwv5IZ8UgfBqRHvnxcbNmScrzl
+p3V3ZoY"}</pre></section>

- <div class="nobuild">
- <p>Any element with child nodes can build.</p>
- <p>It doesn't have to be a list.</p>
- </div>
</article>

- <article class='smaller'>
+ <article>
<h3>
- Slide with bullet points (small font)
+ Backup a directory...
</h3>
+ <section><pre><span style='font-weight: bold'>$ camput --file $HOME</span>
+sha1-8659a52f726588dc44d38dfb22d84a4da2902fed
+<span style='font-weight: bold'>$ camput --permanode --file $HOME</span>
+sha1-ea799271abfbf85d8e22e4577f15f704c8349026
+<span style='font-weight: bold'>$ camput --permanode --name="Brad's home directory" --file $HOME</span>
+sha1-ea799271abfbf85d8e22e4577f15f704c8349026</pre></section>
<ul>
- <li>
- Use this template to create your presentation
- <li>
- Use the provided color palette, box and arrow graphics, and
- chart styles
- <li>
- Instructions are provided to assist you in using this
- presentation template effectively
- <li>
- At all times strive to maintain Google's corporate look and feel
+ <li>all the file data blobs, file/dir schema blobs,</li>
+ <li>a new permanode, owned by you</li>
+ <li>a mutation: permanode's content attribute == directory root</li>
+ <li>a mutation: permanode's name attribute == "Brad's home directory"</li>
</ul>
</article>

+ <article class='fill'>
+ <p><img src="fsbackup.png" height="100%"/></p>
+ </article>
+
<article>
- <h3>
- Slide with a table
- </h3>
-
- <table>
- <tr>
- <th>
- Name
- <th>
- Occupation
- <tr>
- <td>
- Luke Mahé
- <td>
- V.P. of Keepin’ It Real
- <tr>
- <td>
- Marcin Wichary
- <td>
- The Michael Bay of Doodles
- </table>
+ <h1 class='center'>
+ Modeling non-filesystem objects
+ </h1>
</article>
-
- <article class='smaller'>
+
+ <article>
+ <h3>Example: a photo gallery</h3>
+ <ul>
+ <li>Photos are objects</li>
+ <li>Galleries (sets) are objects</li>
+ <li>Photos are members of galleries</li>
+ <li>Photos & galleries have attributes (single-valued: "title", multi-valued: "tag")</li>
+ <li>Photos might be updated over time:
+ <ul>
+ <li>EXIF GPS updated, cropping, white balance</li>
+ <li>don't want to break links!</li>
+ </ul>
+ </li>
+ </ul>
+ </article>
+
+ <article class='fill'>
+ <p><img src="blobjects.png" width="100%"/></p>
+ </article>
+
+ <article>
+ <h1 class='center'>
+ How to make sense of that?
+ </h1>
+ </article>
+
+ <article>
+ <h1 class='center'>
+ Indexing & Search
+ </h1>
+ </article>
+
+ <article>
<h3>
- Slide with a table (smaller text)
+ Indexing: summary
</h3>
-
- <table>
- <tr>
- <th>
- Name
- <th>
- Occupation
- <tr>
- <td>
- Luke Mahé
- <td>
- V.P. of Keepin’ It Real
- <tr>
- <td>
- Marcin Wichary
- <td>
- The Michael Bay of Doodles
- </table>
+ <p style='margin-top: 2em'>For each blob, build an index of:
+ <ul>
+ <li>directed graph of inter-blob references</li>
+ <li>(permanode, time) => resolved attributes</li>
+ <li>(permanode, time) => set memberships</li>
+ <li>etc...</li>
+ </ul>
</article>
-
+
<article>
<h3>
- Styles
+ Indexing & Replication
</h3>
<ul>
- <li>
- <span class='red'>class="red"</span>
- <li>
- <span class='blue'>class="blue"</span>
- <li>
- <span class='green'>class="green"</span>
- <li>
- <span class='yellow'>class="yellow"</span>
- <li>
- <span class='black'>class="black"</span>
- <li>
- <span class='white'>class="white"</span>
- <li>
- <b>bold</b> and <i>italic</i>
+ <li>indexing is real-time, no polling</li>
+ <li>MySQL index speaks the blob server protocol</li>
+ <li>just replicated to MySQL (etc) just like Amazon S3 (etc)</li>
</ul>
+ <center><img src='repl.png' /></center>
</article>
-
+
<article>
- <h2>
- Segue slide
- </h2>
+ <h3>
+ Search
+ </h3>
+ <ul>
+ <li>Permanodes created by $who, sorted by date desc, type "photo", tagged "funny"</li>
+ <li>My recent backups with attribute "hostname" == "camlistore.org",</l>
+ <li>All friends' galleries in which this photo appears,</li>
+ <li>etc...</li>
+ </ul>
+ <p>...similar to your email, or docs.google.com. "My stuff" or "My bookmarks".</p>
</article>

<article>
<h3>
- Slide with an image
+ Privacy Model
</h3>
- <p>
- <img style='height: 500px' src='images/example-graph.png'>
- </p>
- <div class='source'>
- Source: Sergey Brin
- </div>
+ <ul>
+ <li>all your blobs & objects & searches are private</li>
+ <li>nothing is public by default</li>
+ </ul>
</article>

- <article>
+ <article>
+ <h1>
+ What if you want to share with friends, or globally publish something?
+ </h1>
+ </article>
+
+ <article>
<h3>
- Slide with an image (centered)
+ Sharing & Share Blobs
</h3>
- <p>
- <img class='centered' style='height: 500px' src='images/example-graph.png'>
- </p>
- <div class='source'>
- Source: Larry Page
- </div>
+ <ul>
+ <li>the act of sharing involves creating a new <span class='red'>share claim</span>, just another blob, signed.</li>
+ </ul>
</article>

+
<article class='fill'>
<h3>
Image filling the slide (with optional header)
@@ -487,76 +645,10 @@
<img src='images/example-cat.jpg'>
</p>
<div class='source white'>
- Source: Eric Schmidt
+ Cat
</div>
</article>

- <article>
- <h3>
- This slide has some code
- </h3>
- <section>
- <pre>
-&lt;script type='text/javascript'&gt;
- // Say hello world until the user starts questioning
- // the meaningfulness of their existence.
- function helloWorld(world) {
- for (var i = 42; --i &gt;= 0;) {
- alert('Hello ' + String(world));
- }
- }
-&lt;/script&gt;
-&lt;style&gt;
- p { color: pink }
- b { color: blue }
- u { color: 'umber' }
-&lt;/style&gt;
-</pre>
- </section>
- </article>
-
- <article class='smaller'>
- <h3>
- This slide has some code (small font)
- </h3>
- <section>
- <pre>
-&lt;script type='text/javascript'&gt;
- // Say hello world until the user starts questioning
- // the meaningfulness of their existence.
- function helloWorld(world) {
- for (var i = 42; --i &gt;= 0;) {
- alert('Hello ' + String(world));
- }
- }
-&lt;/script&gt;
-&lt;style&gt;
- p { color: pink }
- b { color: blue }
- u { color: 'umber' }
-&lt;/style&gt;
-</pre>
- </section>
- </article>
-
- <article>
- <q>
- The best way to predict the future is to invent it.
- </q>
- <div class='author'>
- Alan Kay
- </div>
- </article>
-
- <article class='smaller'>
- <q>
- A distributed system is one in which the failure of a computer
- you didn’t even know existed can render your own computer unusable.
- </q>
- <div class='author'>
- Leslie Lamport
- </div>
- </article>

<article class='nobackground'>
<h3>
@@ -583,8 +675,8 @@
</h3>

<ul>
- <li>
- <a href='http://www.google.com'>google.com</a>
+ <li>Brad Fitzpatrick, br...@danga.com</li>
+ <li>camlistore.org</li>
</ul>
</article>

diff --git a/talks/2011-05-07-Camlistore-Sao-Paolo/repl.png b/talks/2011-05-07-Camlistore-Sao-Paolo/repl.png
new file mode 100644
index 0000000..81ed94e
Binary files /dev/null and b/talks/2011-05-07-Camlistore-Sao-Paolo/repl.png differ
diff --git a/talks/2011-05-07-Camlistore-Sao-Paolo/slides.js b/talks/2011-05-07-Camlistore-Sao-Paolo/slides.js
index eed1cdb..7f8ce9b 100644
--- a/talks/2011-05-07-Camlistore-Sao-Paolo/slides.js
+++ b/talks/2011-05-07-Camlistore-Sao-Paolo/slides.js
@@ -546,8 +546,8 @@ function addFontStyle() {
var el = document.createElement('link');
el.rel = 'stylesheet';
el.type = 'text/css';
- el.href = 'http://fonts.googleapis.com/css?family=' +
- 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono';
+// el.href = 'http://fonts.googleapis.com/css?family=' +
+// 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono';

document.body.appendChild(el);
};


hooks/post-receive
--
Camlistore

Reply all
Reply to author
Forward
0 new messages