diff --git a/pkg/index/index.go b/pkg/index/index.go index 7f7001778..58590a5d3 100644 --- a/pkg/index/index.go +++ b/pkg/index/index.go @@ -586,10 +586,10 @@ func (x *Index) EdgesTo(ref *blobref.BlobRef, opts *search.EdgesToOpts) (edges [ } for _, parentRef := range permanodeParents { edges = append(edges, &search.Edge{ - From: parentRef, - FromType: "permanode", - To: ref, - }) + From: parentRef, + FromType: "permanode", + To: ref, + }) } return edges, nil } diff --git a/pkg/index/indextest/testdata/dude.jpg b/pkg/index/indextest/testdata/dude.jpg new file mode 100644 index 000000000..447710ecc Binary files /dev/null and b/pkg/index/indextest/testdata/dude.jpg differ diff --git a/pkg/index/indextest/tests.go b/pkg/index/indextest/tests.go index 929e1be5e..6f3fd0158 100644 --- a/pkg/index/indextest/tests.go +++ b/pkg/index/indextest/tests.go @@ -18,6 +18,7 @@ package indextest import ( "fmt" + "io/ioutil" "log" "os" "path/filepath" @@ -253,6 +254,30 @@ func Index(t *testing.T, initIdx func() *index.Index) { t.Logf("add-attribute claim %q points to member permanode %q", memberRef, pnChild) memberRefTime := id.lastTime() + // TODO(bradfitz): add EXIF tests here, once that stuff is ready. + if false { + for i := 1; i <= 8; i++ { + fileBase := fmt.Sprintf("f%d-exif.jpg", i) + fileName := filepath.Join(findGoPathPackage("camlistore.org"), "pkg", "images", "testdata", fileBase) + contents, err := ioutil.ReadFile(fileName) + if err != nil { + t.Fatal(err) + } + id.UploadFile(fileBase, string(contents)) + } + } + + // Upload a basic image. + var jpegFileRef *blobref.BlobRef + { + fileName := filepath.Join(findGoPathPackage("camlistore.org"), "pkg", "index", "indextest", "testdata", "dude.jpg") + contents, err := ioutil.ReadFile(fileName) + if err != nil { + t.Fatal(err) + } + jpegFileRef, _ = id.UploadFile("dude.jpg", string(contents)) + } + lastPermanodeMutation := id.lastTime() id.dumpIndex(t) @@ -261,6 +286,11 @@ func Index(t *testing.T, initIdx func() *index.Index) { t.Fatalf("%q = %q, want %q", key, g, e) } + key = "imagesize|" + jpegFileRef.String() + if g, e := id.Get(key), "50|100"; g != e { + t.Errorf("JPEG dude.jpg key %q = %q; want %q", key, g, e) + } + key = "have:" + pn.String() pnSizeStr := id.Get(key) if pnSizeStr == "" { @@ -519,8 +549,8 @@ func EdgesTo(t *testing.T, initIdx func() *index.Index) { t.Fatalf("num edges = %d; want 1", len(edges)) } wantEdge := &search.Edge{ - From: pn1, - To: pn2, + From: pn1, + To: pn2, FromType: "permanode", } if got, want := edges[0].String(), wantEdge.String(); got != want { diff --git a/pkg/index/keys.go b/pkg/index/keys.go index 658ea3d65..280443c8a 100644 --- a/pkg/index/keys.go +++ b/pkg/index/keys.go @@ -216,4 +216,16 @@ var ( {"name", typeStr}, // the name, if static. }, } + + // Width and height after any EXIF rotation. + keyImageSize = &keyType{ + "imagesize", + []part{ + {"fileref", typeBlobRef}, // blobref of "file" schema blob + }, + []part{ + {"width", typeStr}, + {"height", typeStr}, + }, + } ) diff --git a/pkg/index/receive.go b/pkg/index/receive.go index 070b5134b..0a69f6f8c 100644 --- a/pkg/index/receive.go +++ b/pkg/index/receive.go @@ -21,7 +21,12 @@ import ( "crypto/sha1" "errors" "fmt" + "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" "io" + "io/ioutil" "log" "strings" @@ -121,7 +126,34 @@ func (ix *Index) populateFile(blobRef *blobref.BlobRef, ss *schema.Superset, bm return nil } mime, reader := magic.MimeTypeFromReader(fr) - size, err := io.Copy(sha1, reader) + + var copyDest io.Writer = sha1 + var withCopyErr func(error) // or nil + if strings.HasPrefix(mime, "image/") { + pr, pw := io.Pipe() + copyDest = io.MultiWriter(copyDest, pw) + confc := make(chan *image.Config, 1) + go func() { + conf, _, err := image.DecodeConfig(pr) + defer io.Copy(ioutil.Discard, pr) + if err == nil { + confc <- &conf + } else { + confc <- nil + } + }() + withCopyErr = func(err error) { + pw.CloseWithError(err) + if conf := <-confc; conf != nil { + bm.Set(keyImageSize.Key(blobRef), keyImageSize.Val(fmt.Sprint(conf.Width), fmt.Sprint(conf.Height))) + } + } + } + + size, err := io.Copy(copyDest, reader) + if f := withCopyErr; f != nil { + f(err) + } if err != nil { // TODO: job scheduling system to retry this spaced // out max n times. Right now our options are