veyron/services/mgmt/binary/impl: code refactoring.

Add stats.go for stats object, and fs_utils.go for common functions
that manipulate the filesystem for storing/retrieving binary parts.

Change-Id: I394d62af1deb0d23656fab3270d0b7f19a3b47d4
diff --git a/services/mgmt/binary/impl/dispatcher.go b/services/mgmt/binary/impl/dispatcher.go
index 465b0a6..1b80de9 100644
--- a/services/mgmt/binary/impl/dispatcher.go
+++ b/services/mgmt/binary/impl/dispatcher.go
@@ -1,13 +1,6 @@
 package impl
 
 import (
-	"crypto/md5"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-
 	"veyron.io/veyron/veyron2/ipc"
 	"veyron.io/veyron/veyron2/security"
 	"veyron.io/veyron/veyron2/services/mgmt/repository"
@@ -24,31 +17,6 @@
 	state *state
 }
 
-// TODO(caprita): Move this together with state into a new file, state.go.
-
-// NewState creates a new state object for the binary service.  This
-// should be passed into both NewDispatcher and NewHTTPRoot.
-func NewState(root string, depth int) (*state, error) {
-	if min, max := 0, md5.Size-1; min > depth || depth > max {
-		return nil, fmt.Errorf("Unexpected depth, expected a value between %v and %v, got %v", min, max, depth)
-	}
-	if _, err := os.Stat(root); err != nil {
-		return nil, fmt.Errorf("Stat(%v) failed: %v", root, err)
-	}
-	path := filepath.Join(root, VersionFile)
-	output, err := ioutil.ReadFile(path)
-	if err != nil {
-		return nil, fmt.Errorf("ReadFile(%v) failed: %v", path, err)
-	}
-	if expected, got := Version, strings.TrimSpace(string(output)); expected != got {
-		return nil, fmt.Errorf("Unexpected version: expected %v, got %v", expected, got)
-	}
-	return &state{
-		depth: depth,
-		root:  root,
-	}, nil
-}
-
 // NewDispatcher is the dispatcher factory.
 func NewDispatcher(state *state, authorizer security.Authorizer) ipc.Dispatcher {
 	return &dispatcher{
diff --git a/services/mgmt/binary/impl/fs_utils.go b/services/mgmt/binary/impl/fs_utils.go
new file mode 100644
index 0000000..bc4c818
--- /dev/null
+++ b/services/mgmt/binary/impl/fs_utils.go
@@ -0,0 +1,80 @@
+package impl
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+
+	"veyron.io/veyron/veyron2/vlog"
+)
+
+const (
+	checksum = "checksum"
+	data     = "data"
+	lock     = "lock"
+)
+
+// checksumExists checks whether the given part path is valid and
+// contains a checksum. The implementation uses the existence of
+// the path dir to determine whether the part is valid, and the
+// existence of checksum to determine whether the binary part
+// exists.
+func checksumExists(path string) error {
+	switch _, err := os.Stat(path); {
+	case os.IsNotExist(err):
+		return errInvalidPart
+	case err != nil:
+		vlog.Errorf("Stat(%v) failed: %v", path, err)
+		return errOperationFailed
+	}
+	checksumFile := filepath.Join(path, checksum)
+	_, err := os.Stat(checksumFile)
+	switch {
+	case os.IsNotExist(err):
+		return errNotFound
+	case err != nil:
+		vlog.Errorf("Stat(%v) failed: %v", checksumFile, err)
+		return errOperationFailed
+	default:
+		return nil
+	}
+}
+
+// generatePartPath generates a path for the given binary part.
+func (i *invoker) generatePartPath(part int) string {
+	return generatePartPath(i.path, part)
+}
+
+func generatePartPath(dir string, part int) string {
+	return filepath.Join(dir, fmt.Sprintf("%d", part))
+}
+
+// getParts returns a collection of paths to the parts of the binary.
+func getParts(path string) ([]string, error) {
+	infos, err := ioutil.ReadDir(path)
+	if err != nil {
+		vlog.Errorf("ReadDir(%v) failed: %v", path, err)
+		return []string{}, errOperationFailed
+	}
+	result := make([]string, len(infos))
+	for _, info := range infos {
+		if info.IsDir() {
+			partName := info.Name()
+			idx, err := strconv.Atoi(partName)
+			if err != nil {
+				vlog.Errorf("Atoi(%v) failed: %v", partName, err)
+				return []string{}, errOperationFailed
+			}
+			if idx < 0 || idx >= len(infos) || result[idx] != "" {
+				return []string{}, errOperationFailed
+			}
+			result[idx] = filepath.Join(path, partName)
+		} else {
+			// The only entries should correspond to the part dirs.
+			return []string{}, errOperationFailed
+		}
+	}
+	return result, nil
+}
diff --git a/services/mgmt/binary/impl/http.go b/services/mgmt/binary/impl/http.go
index 2c189b3..df6ec69 100644
--- a/services/mgmt/binary/impl/http.go
+++ b/services/mgmt/binary/impl/http.go
@@ -29,7 +29,7 @@
 func (r httpRoot) Open(name string) (http.File, error) {
 	name = strings.TrimPrefix(name, "/")
 	vlog.Infof("HTTP handler opening %s", name)
-	parts, err := getParts(dir(name, r.state))
+	parts, err := getParts(r.state.dir(name))
 	if err != nil {
 		return nil, err
 	}
diff --git a/services/mgmt/binary/impl/invoker.go b/services/mgmt/binary/impl/invoker.go
index 893abcf..182b128 100644
--- a/services/mgmt/binary/impl/invoker.go
+++ b/services/mgmt/binary/impl/invoker.go
@@ -21,12 +21,10 @@
 import (
 	"crypto/md5"
 	"encoding/hex"
-	"fmt"
 	"io"
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"strconv"
 	"syscall"
 
 	"veyron.io/veyron/veyron2/ipc"
@@ -36,30 +34,6 @@
 	"veyron.io/veyron/veyron2/vlog"
 )
 
-// state holds the state shared across different binary repository
-// invocations.
-type state struct {
-	// depth determines the depth of the directory hierarchy that the
-	// binary repository uses to organize binaries in the local file
-	// system. There is a trade-off here: smaller values lead to faster
-	// access, while higher values allow the performance to scale to
-	// larger collections of binaries. The number should be a value
-	// between 0 and (md5.Size - 1).
-	//
-	// Note that the cardinality of each level (except the leaf level)
-	// is at most 256. If you expect to have X total binary items, and
-	// you want to limit directories to at most Y entries (because of
-	// filesystem limitations), then you should set depth to at least
-	// log_256(X/Y). For example, using hierarchyDepth = 3 with a local
-	// filesystem that can handle up to 1,000 entries per directory
-	// before its performance degrades allows the binary repository to
-	// store 16B objects.
-	depth int
-	// root identifies the local filesystem directory in which the
-	// binary repository stores its objects.
-	root string
-}
-
 // invoker holds the state of a binary repository invocation.
 type invoker struct {
 	// path is the local filesystem path to the object identified by the
@@ -73,12 +47,6 @@
 	suffix string
 }
 
-const (
-	checksum = "checksum"
-	data     = "data"
-	lock     = "lock"
-)
-
 var (
 	errExists          = verror.Existsf("binary already exists")
 	errNotFound        = verror.NoExistf("binary not found")
@@ -95,22 +63,10 @@
 	Size:     binary.MissingSize,
 }
 
-// dir generates the local filesystem path for the binary identified by suffix.
-func dir(suffix string, state *state) string {
-	h := md5.New()
-	h.Write([]byte(suffix))
-	hash := hex.EncodeToString(h.Sum(nil))
-	dir := ""
-	for j := 0; j < state.depth; j++ {
-		dir = filepath.Join(dir, hash[j*2:(j+1)*2])
-	}
-	return filepath.Join(state.root, dir, hash)
-}
-
 // newInvoker is the invoker factory.
 func newInvoker(state *state, suffix string) *invoker {
 	return &invoker{
-		path:   dir(suffix, state),
+		path:   state.dir(suffix),
 		state:  state,
 		suffix: suffix,
 	}
@@ -120,69 +76,6 @@
 
 const bufferLength = 4096
 
-// checksumExists checks whether the given part path is valid and
-// contains a checksum. The implementation uses the existence of
-// the path dir to determine whether the part is valid, and the
-// existence of checksum to determine whether the binary part
-// exists.
-func checksumExists(path string) error {
-	switch _, err := os.Stat(path); {
-	case os.IsNotExist(err):
-		return errInvalidPart
-	case err != nil:
-		vlog.Errorf("Stat(%v) failed: %v", path, err)
-		return errOperationFailed
-	}
-	checksumFile := filepath.Join(path, checksum)
-	_, err := os.Stat(checksumFile)
-	switch {
-	case os.IsNotExist(err):
-		return errNotFound
-	case err != nil:
-		vlog.Errorf("Stat(%v) failed: %v", checksumFile, err)
-		return errOperationFailed
-	default:
-		return nil
-	}
-}
-
-// generatePartPath generates a path for the given binary part.
-func (i *invoker) generatePartPath(part int) string {
-	return generatePartPath(i.path, part)
-}
-
-func generatePartPath(dir string, part int) string {
-	return filepath.Join(dir, fmt.Sprintf("%d", part))
-}
-
-// getParts returns a collection of paths to the parts of the binary.
-func getParts(path string) ([]string, error) {
-	infos, err := ioutil.ReadDir(path)
-	if err != nil {
-		vlog.Errorf("ReadDir(%v) failed: %v", path, err)
-		return []string{}, errOperationFailed
-	}
-	result := make([]string, len(infos))
-	for _, info := range infos {
-		if info.IsDir() {
-			partName := info.Name()
-			idx, err := strconv.Atoi(partName)
-			if err != nil {
-				vlog.Errorf("Atoi(%v) failed: %v", partName, err)
-				return []string{}, errOperationFailed
-			}
-			if idx < 0 || idx >= len(infos) || result[idx] != "" {
-				return []string{}, errOperationFailed
-			}
-			result[idx] = filepath.Join(path, partName)
-		} else {
-			// The only entries should correspond to the part dirs.
-			return []string{}, errOperationFailed
-		}
-	}
-	return result, nil
-}
-
 func (i *invoker) Create(_ ipc.ServerContext, nparts int32) error {
 	vlog.Infof("%v.Create(%v)", i.suffix, nparts)
 	if nparts < 1 {
diff --git a/services/mgmt/binary/impl/state.go b/services/mgmt/binary/impl/state.go
new file mode 100644
index 0000000..cf6c3b1
--- /dev/null
+++ b/services/mgmt/binary/impl/state.go
@@ -0,0 +1,70 @@
+package impl
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+// state holds the state shared across different binary repository
+// invocations.
+type state struct {
+	// depth determines the depth of the directory hierarchy that the
+	// binary repository uses to organize binaries in the local file
+	// system. There is a trade-off here: smaller values lead to faster
+	// access, while higher values allow the performance to scale to
+	// larger collections of binaries. The number should be a value
+	// between 0 and (md5.Size - 1).
+	//
+	// Note that the cardinality of each level (except the leaf level)
+	// is at most 256. If you expect to have X total binary items, and
+	// you want to limit directories to at most Y entries (because of
+	// filesystem limitations), then you should set depth to at least
+	// log_256(X/Y). For example, using hierarchyDepth = 3 with a local
+	// filesystem that can handle up to 1,000 entries per directory
+	// before its performance degrades allows the binary repository to
+	// store 16B objects.
+	depth int
+	// root identifies the local filesystem directory in which the
+	// binary repository stores its objects.
+	root string
+}
+
+// NewState creates a new state object for the binary service.  This
+// should be passed into both NewDispatcher and NewHTTPRoot.
+func NewState(root string, depth int) (*state, error) {
+	if min, max := 0, md5.Size-1; min > depth || depth > max {
+		return nil, fmt.Errorf("Unexpected depth, expected a value between %v and %v, got %v", min, max, depth)
+	}
+	if _, err := os.Stat(root); err != nil {
+		return nil, fmt.Errorf("Stat(%v) failed: %v", root, err)
+	}
+	path := filepath.Join(root, VersionFile)
+	output, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, fmt.Errorf("ReadFile(%v) failed: %v", path, err)
+	}
+	if expected, got := Version, strings.TrimSpace(string(output)); expected != got {
+		return nil, fmt.Errorf("Unexpected version: expected %v, got %v", expected, got)
+	}
+	return &state{
+		depth: depth,
+		root:  root,
+	}, nil
+}
+
+// dir generates the local filesystem path for the binary identified by suffix.
+func (s *state) dir(suffix string) string {
+	h := md5.New()
+	h.Write([]byte(suffix))
+	hash := hex.EncodeToString(h.Sum(nil))
+	dir := ""
+	for j := 0; j < s.depth; j++ {
+		dir = filepath.Join(dir, hash[j*2:(j+1)*2])
+	}
+	return filepath.Join(s.root, dir, hash)
+}