storage: support local disk for uploads
This adds a -data flag to localperfdata to specify a local data
directory. In combination with -dsn to specify a local database,
localperfdata is now useful for non-testing applications.
Change-Id: I32ddb7ba5a96483c1f93cb7b52bbc2643b5b3798
Reviewed-on: https://go-review.googlesource.com/37861
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/storage/fs/local/local.go b/storage/fs/local/local.go
new file mode 100644
index 0000000..650e6e3
--- /dev/null
+++ b/storage/fs/local/local.go
@@ -0,0 +1,48 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package local implements the fs.FS interface using local files.
+// Metadata is not stored separately; the header of each file should
+// contain metadata as written by storage/app.
+package local
+
+import (
+ "os"
+ "path/filepath"
+
+ "golang.org/x/net/context"
+ "golang.org/x/perf/storage/fs"
+)
+
+// impl is an fs.FS backed by local disk.
+type impl struct {
+ root string
+}
+
+// NewFS constructs an FS that writes to the provided directory.
+func NewFS(root string) fs.FS {
+ return &impl{root}
+}
+
+// NewWriter creates a file and assigns metadata as extended filesystem attributes.
+func (fs *impl) NewWriter(ctx context.Context, name string, metadata map[string]string) (fs.Writer, error) {
+ if err := os.MkdirAll(filepath.Join(fs.root, filepath.Dir(name)), 0777); err != nil {
+ return nil, err
+ }
+ f, err := os.Create(filepath.Join(fs.root, name))
+ if err != nil {
+ return nil, err
+ }
+ return &wrapper{f}, nil
+}
+
+type wrapper struct {
+ *os.File
+}
+
+// CloseWithError closes the file and attempts to unlink it.
+func (w *wrapper) CloseWithError(error) error {
+ w.Close()
+ return os.Remove(w.Name())
+}
diff --git a/storage/fs/local/local_test.go b/storage/fs/local/local_test.go
new file mode 100644
index 0000000..b34ab23
--- /dev/null
+++ b/storage/fs/local/local_test.go
@@ -0,0 +1,50 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package local
+
+import (
+ "context"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "golang.org/x/perf/internal/diff"
+)
+
+func TestNewWriter(t *testing.T) {
+ ctx := context.Background()
+
+ dir, err := ioutil.TempDir("", "local_test")
+ if err != nil {
+ t.Fatalf("TempDir = %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ fs := NewFS(dir)
+
+ w, err := fs.NewWriter(ctx, "dir/file", map[string]string{"key": "value", "key2": "value2"})
+ if err != nil {
+ t.Fatalf("NewWriter = %v", err)
+ }
+
+ want := "hello world"
+
+ if _, err := w.Write([]byte(want)); err != nil {
+ t.Fatalf("Write = %v", err)
+ }
+
+ if err := w.Close(); err != nil {
+ t.Fatalf("Close = %v", err)
+ }
+
+ have, err := ioutil.ReadFile(filepath.Join(dir, "dir/file"))
+ if err != nil {
+ t.Fatalf("ReadFile = %v", err)
+ }
+ if d := diff.Diff(string(have), want); d != "" {
+ t.Errorf("file contents differ. have (-)/want (+)\n%s", d)
+ }
+}
diff --git a/storage/localperfdata/app.go b/storage/localperfdata/app.go
index 9a8d560..8e90214 100644
--- a/storage/localperfdata/app.go
+++ b/storage/localperfdata/app.go
@@ -19,12 +19,14 @@
"golang.org/x/perf/storage/db"
_ "golang.org/x/perf/storage/db/sqlite3"
"golang.org/x/perf/storage/fs"
+ "golang.org/x/perf/storage/fs/local"
)
var (
addr = flag.String("addr", ":8080", "serve HTTP on `address`")
viewURLBase = flag.String("view_url_base", "", "/upload response with `URL` for viewing")
dsn = flag.String("dsn", ":memory:", "sqlite `dsn`")
+ data = flag.String("data", "", "data `directory` (in-memory if empty)")
baseDir = flag.String("base_dir", basedir.Find("golang.org/x/perf/storage/appengine"), "base `directory` for static files")
)
@@ -40,7 +42,11 @@
if err != nil {
log.Fatalf("open database: %v", err)
}
- fs := fs.NewMemFS()
+ var fs fs.FS = fs.NewMemFS()
+
+ if *data != "" {
+ fs = local.NewFS(*data)
+ }
app := &app.App{
DB: db,