github.com/janelia-flyem/dvid@v1.0.0/datatype/tarsupervoxels/tarsupervoxels.go (about)

     1  /*
     2     Package tarsupervoxels implements DVID support for data blobs associated with supervoxels.
     3  */
     4  package tarsupervoxels
     5  
     6  import (
     7  	"archive/tar"
     8  	"bytes"
     9  	"crypto/md5"
    10  	"encoding/gob"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"io/ioutil"
    15  	"net/http"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/janelia-flyem/dvid/datastore"
    21  	"github.com/janelia-flyem/dvid/datatype/common/labels"
    22  	"github.com/janelia-flyem/dvid/datatype/labelmap"
    23  	"github.com/janelia-flyem/dvid/dvid"
    24  	"github.com/janelia-flyem/dvid/server"
    25  	"github.com/janelia-flyem/dvid/storage"
    26  )
    27  
    28  const (
    29  	Version  = "0.1"
    30  	RepoURL  = "github.com/janelia-flyem/dvid/datatype/tarsupervoxels"
    31  	TypeName = "tarsupervoxels"
    32  )
    33  
    34  const helpMessage = `
    35  API for 'keyvalue' datatype (github.com/janelia-flyem/dvid/datatype/tarsupervoxels)
    36  =============================================================================
    37  
    38  Note: UUIDs referenced below are strings that may either be a unique prefix of a
    39  hexadecimal UUID string (e.g., 3FA22) or a branch leaf specification that adds
    40  a colon (":") followed by the case-dependent branch name.  In the case of a
    41  branch leaf specification, the unique UUID prefix just identifies the repo of
    42  the branch, and the UUID referenced is really the leaf of the branch name.
    43  For example, if we have a DAG with root A -> B -> C where C is the current
    44  HEAD or leaf of the "master" (default) branch, then asking for "B:master" is
    45  the same as asking for "C".  If we add another version so A -> B -> C -> D, then
    46  references to "B:master" now return the data from "D".
    47  
    48  Command-line:
    49  
    50  $ dvid repo <UUID> new tarsupervoxels <data name> <settings...>
    51  
    52      Adds newly named supervoxels tar support to repo with specified UUID.
    53  
    54      Example:
    55  
    56      $ dvid repo 3f8c new tarsupervoxels stuff
    57  
    58      Arguments:
    59  
    60      UUID           Hexadecimal string with enough characters to uniquely identify a version node.
    61      data name      Name of data to create, e.g., "supervoxel-meshes"
    62      settings       Configuration settings in "key=value" format separated by spaces.
    63                     Example key/value: "Extension" is the expected extension for blobs uploaded.
    64                         If no extension is given, it is "dat" by default.
    65                         It could be set to "drc" for Google Draco file formats, for example.
    66  
    67      ------------------
    68  
    69  HTTP API (Level 2 REST):
    70  
    71  Note that browsers support HTTP PUT and DELETE via javascript but only GET/POST are
    72  included in HTML specs.  For ease of use in constructing clients, HTTP POST is used
    73  to create or modify resources in an idempotent fashion.
    74  
    75  GET  <api URL>/node/<UUID>/<data name>/help
    76  
    77      Returns data-specific help message.
    78  
    79  
    80  GET  <api URL>/node/<UUID>/<data name>/info
    81  POST <api URL>/node/<UUID>/<data name>/info
    82  
    83      Retrieves or puts data properties.
    84  
    85      Example: 
    86  
    87      GET <api URL>/node/3f8c/supervoxel-meshes/info
    88  
    89      Returns or posts JSON of configuration settings with the following optional tarsupervoxel-specific field:
    90  
    91       "Extension"        Expected extension for blobs uploaded (default: "dat").
    92                             Other common uses include "drc" for Google Draco file formats..
    93  
    94      Arguments:
    95  
    96      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
    97      data name     Name of tarsupervoxels data instance.
    98  
    99  
   100  POST <api URL>/node/<UUID>/<data name>/sync?<options>
   101  
   102      Establishes labelmap for which supervoxel mapping is used.  Expects JSON to be POSTed
   103      with the following format:
   104  
   105      { "sync": "segmentation" }
   106  
   107      To delete syncs, pass an empty string of names with query string "replace=true":
   108  
   109      { "sync": "" }
   110  
   111      The tarsupervoxels data type only accepts syncs to label instances that provide supervoxel info.
   112  
   113      GET Query-string Options:
   114  
   115      replace    Set to "true" if you want passed syncs to replace and not be appended to current syncs.
   116                 Default operation is false.
   117  
   118  GET  <api URL>/node/<UUID>/<data name>/supervoxel/<id>
   119  POST <api URL>/node/<UUID>/<data name>/supervoxel/<id>
   120  DEL  <api URL>/node/<UUID>/<data name>/supervoxel/<id> 
   121  
   122      Performs get, put or delete of data on a supervoxel depending on the HTTP verb.  
   123  
   124      Example: 
   125  
   126      GET <api URL>/node/3f8c/supervoxel-meshes/supervoxel/18473948
   127  
   128          Returns the data associated with the supervoxel 18473948 of instance "supervoxel-meshes".
   129  
   130      POST <api URL>/node/3f8c/supervoxel-meshes/supervoxel/18473948
   131  
   132          Stores data associated with supervoxel 18473948 of instance 
   133          "supervoxel-meshes".
   134  
   135      The "Content-type" of the HTTP GET response and POST payload are "application/octet-stream" for arbitrary binary data.
   136  
   137      Arguments:
   138  
   139      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   140      data name     Name of tarsupervoxels data instance.
   141      label         The supervoxel id.
   142  
   143  GET  <api URL>/node/<UUID>/<data name>/tarfile/<label> 
   144  HEAD <api URL>/node/<UUID>/<data name>/tarfile/<label> 
   145  
   146      GET returns a tarfile of all supervoxel data that has been mapped to the given label.
   147      File names within the tarfile will be the supervoxel id and an extension.  HTTP status
   148      code 400 (Bad Request) is returned if no such label exists.  If a supervoxel's data does 
   149      not exist, a file will be returned named "X.missing" where X is the supervoxel id.
   150      Note that HTTP status code 200 (OK) is usually returned if the streaming response
   151      has been initiated, and if an error occurs during the return, there will be an ill-formed
   152      tar file.  This is a tradeoff to allow streaming response.
   153  
   154      HEAD returns 200 if the body exists and all supervoxels have stored data, even if it is
   155      a zero length value.  HTTP status code 400 (Bad Request) is returned if no such label 
   156      exists, or one of the label's supervoxels has no associated data, or there was an error.
   157      NOTE that a HEAD bad request response does not mean the corresponding GET will also 
   158      fail since the corresponding GET will include placeholders for missing supervoxel files.
   159  
   160      Example: 
   161  
   162      GET <api URL>/node/3f8c/supervoxel-meshes/tarfile/18473948
   163  
   164      The "Content-type" of the HTTP response is "application/tar".
   165  
   166      Arguments:
   167  
   168      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   169      data name     Name of tarsupervoxels data instance.
   170      label         The label (body) id.
   171  
   172  GET  <api URL>/node/<UUID>/<data name>/missing/<label> 
   173  
   174      Returns a JSON array of all of the label's supervoxels with missing data:
   175  
   176      [181739,3819485677]
   177  
   178      If none of the label's supervoxels are missing, it returns an empty array "[]".
   179  
   180      Example: 
   181  
   182      GET <api URL>/node/3f8c/supervoxel-meshes/missing/18473948
   183  
   184      The "Content-type" of the HTTP response is "application/json".
   185  
   186      Arguments:
   187  
   188      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   189      data name     Name of tarsupervoxels data instance.
   190      label         The label (body) id.
   191  
   192  
   193  
   194  GET  <api URL>/node/<UUID>/<data name>/exists 
   195  
   196      Returns the existence of data associated with supervoxels.  Expects JSON
   197      for the list of supervoxels in the body of the request:
   198  
   199      [ 1, 2, 3, ... ]
   200  
   201      Returns JSON for the existence of data stored for each of the above supervoxels:
   202  
   203      [ true, false, true, ... ]
   204  
   205      Arguments:
   206      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   207      data name     Name of tarsupervoxels instance.
   208  
   209      Query-string Options:
   210  
   211      hash          MD5 hash of request body content in hexidecimal string format.
   212      
   213  POST <api URL>/node/<UUID>/<data name>/load
   214  
   215      Allows bulk-loading of tarfile with supervoxels data.  Each tarred file should
   216      have the supervoxel id as the filename *minus* the extension, e.g., 18491823.dat
   217      would be stored under supervoxel 18491823.
   218  
   219      Arguments:
   220  
   221      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   222      data name     Name of tarsupervoxels data instance.
   223  
   224  `
   225  
   226  func init() {
   227  	datastore.Register(NewType())
   228  
   229  	// Need to register types that will be used to fulfill interfaces.
   230  	gob.Register(&Type{})
   231  	gob.Register(&Data{})
   232  }
   233  
   234  // Type embeds the datastore's Type to create a unique type for keyvalue functions.
   235  type Type struct {
   236  	datastore.Type
   237  }
   238  
   239  // NewType returns a pointer to a new keyvalue Type with default values set.
   240  func NewType() *Type {
   241  	dtype := new(Type)
   242  	dtype.Type = datastore.Type{
   243  		Name:    TypeName,
   244  		URL:     RepoURL,
   245  		Version: Version,
   246  		Requirements: &storage.Requirements{
   247  			Batcher: true,
   248  		},
   249  	}
   250  	return dtype
   251  }
   252  
   253  // --- TypeService interface ---
   254  
   255  // NewDataService returns a pointer to new keyvalue data with default values.
   256  func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) {
   257  	basedata, err := datastore.NewDataService(dtype, uuid, id, name, c)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	extension, found, err := c.GetString("Extension")
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	if !found {
   266  		return nil, fmt.Errorf("tarsupervoxels instances must have Extension set in the configuration")
   267  	}
   268  	return &Data{Data: basedata, Extension: extension}, nil
   269  }
   270  
   271  func (dtype *Type) Help() string {
   272  	return fmt.Sprintf(helpMessage)
   273  }
   274  
   275  // GetByUUIDName returns a pointer to tarsupervoxels data given a UUID and data name.
   276  func GetByUUIDName(uuid dvid.UUID, name dvid.InstanceName) (*Data, error) {
   277  	source, err := datastore.GetDataByUUIDName(uuid, name)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	data, ok := source.(*Data)
   282  	if !ok {
   283  		return nil, fmt.Errorf("Instance '%s' is not a tarsupervoxels datatype!", name)
   284  	}
   285  	return data, nil
   286  }
   287  
   288  type mappedLabelType interface {
   289  	GetSupervoxels(dvid.VersionID, uint64) (labels.Set, error)
   290  	GetMappedLabels(dvid.VersionID, []uint64) (mapped []uint64, found []bool, err error)
   291  	DataName() dvid.InstanceName
   292  }
   293  
   294  // Data embeds the datastore's Data and extends it with keyvalue properties (none for now).
   295  type Data struct {
   296  	*datastore.Data
   297  
   298  	// Extension is the expected extension for blobs uploaded.
   299  	// If no extension is given, it is "dat" by default.
   300  	Extension string
   301  }
   302  
   303  // --- Override of DataService interface ---
   304  
   305  func (d *Data) modifyConfig(config dvid.Config) error {
   306  	if err := d.Data.ModifyConfig(config); err != nil {
   307  		return err
   308  	}
   309  	s, found, err := config.GetString("Extension")
   310  	if err != nil {
   311  		return err
   312  	}
   313  	if found {
   314  		d.Extension = s
   315  	}
   316  	return nil
   317  }
   318  
   319  func (d *Data) getSyncedLabels() mappedLabelType {
   320  	for dataUUID := range d.SyncedData() {
   321  		ldata, err := labelmap.GetByDataUUID(dataUUID)
   322  		if err == nil {
   323  			return ldata
   324  		}
   325  	}
   326  	return nil
   327  }
   328  
   329  func (d *Data) Equals(d2 *Data) bool {
   330  	if !d.Data.Equals(d2.Data) {
   331  		return false
   332  	}
   333  	return true
   334  }
   335  
   336  type propsJSON struct {
   337  	Extension string
   338  }
   339  
   340  func (d *Data) MarshalJSON() ([]byte, error) {
   341  	return json.Marshal(struct {
   342  		Base     *datastore.Data
   343  		Extended propsJSON
   344  	}{
   345  		d.Data,
   346  		propsJSON{
   347  			Extension: d.Extension,
   348  		},
   349  	})
   350  }
   351  
   352  func (d *Data) GobDecode(b []byte) error {
   353  	buf := bytes.NewBuffer(b)
   354  	dec := gob.NewDecoder(buf)
   355  	if err := dec.Decode(&(d.Data)); err != nil {
   356  		return err
   357  	}
   358  	if err := dec.Decode(&(d.Extension)); err != nil {
   359  		return fmt.Errorf("decoding tarsupervoxels %q: no Extension", d.DataName())
   360  	}
   361  	return nil
   362  }
   363  
   364  func (d *Data) GobEncode() ([]byte, error) {
   365  	var buf bytes.Buffer
   366  	enc := gob.NewEncoder(&buf)
   367  	if err := enc.Encode(d.Data); err != nil {
   368  		return nil, err
   369  	}
   370  	if err := enc.Encode(d.Extension); err != nil {
   371  		return nil, err
   372  	}
   373  	return buf.Bytes(), nil
   374  }
   375  
   376  func (d *Data) getRootContext(uuid dvid.UUID) (*datastore.VersionedCtx, error) {
   377  	root, err := datastore.GetRepoRoot(uuid)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  	v, err := datastore.VersionFromUUID(root)
   382  	if err != nil {
   383  		return nil, err
   384  	}
   385  	return datastore.NewVersionedCtx(d, v), nil
   386  }
   387  
   388  // GetData gets data for a supervoxel where the returned bool is true if data is found
   389  func (d *Data) GetData(uuid dvid.UUID, supervoxel uint64) ([]byte, bool, error) {
   390  	db, err := datastore.GetKeyValueDB(d)
   391  	if err != nil {
   392  		return nil, false, err
   393  	}
   394  	tk, err := NewTKey(supervoxel, d.Extension)
   395  	if err != nil {
   396  		return nil, false, err
   397  	}
   398  	ctx, err := d.getRootContext(uuid)
   399  	if err != nil {
   400  		return nil, false, err
   401  	}
   402  	data, err := db.Get(ctx, tk)
   403  	if err != nil {
   404  		return nil, false, fmt.Errorf("Error in retrieving supervoxel %d: %v", supervoxel, err)
   405  	}
   406  	if data == nil {
   407  		return nil, false, nil
   408  	}
   409  	return data, true, nil
   410  }
   411  
   412  // PutData puts supervoxel data
   413  func (d *Data) PutData(uuid dvid.UUID, supervoxel uint64, data []byte) error {
   414  	db, err := datastore.GetKeyValueDB(d)
   415  	if err != nil {
   416  		return err
   417  	}
   418  	tk, err := NewTKey(supervoxel, d.Extension)
   419  	if err != nil {
   420  		return err
   421  	}
   422  	ctx, err := d.getRootContext(uuid)
   423  	if err != nil {
   424  		return err
   425  	}
   426  	return db.Put(ctx, tk, data)
   427  }
   428  
   429  // DeleteData deletes upervoxel data
   430  func (d *Data) DeleteData(uuid dvid.UUID, supervoxel uint64) error {
   431  	db, err := datastore.GetKeyValueDB(d)
   432  	if err != nil {
   433  		return err
   434  	}
   435  	tk, err := NewTKey(supervoxel, d.Extension)
   436  	if err != nil {
   437  		return err
   438  	}
   439  	ctx, err := d.getRootContext(uuid)
   440  	if err != nil {
   441  		return err
   442  	}
   443  	return db.Delete(ctx, tk)
   444  }
   445  
   446  // JSONString returns the JSON for this Data's configuration
   447  func (d *Data) JSONString() (jsonStr string, err error) {
   448  	m, err := json.Marshal(d)
   449  	if err != nil {
   450  		return "", err
   451  	}
   452  	return string(m), nil
   453  }
   454  
   455  type fileData struct {
   456  	header *tar.Header
   457  	data   []byte
   458  	err    error
   459  }
   460  
   461  func (d *Data) getSupervoxelGoroutine(db storage.KeyValueDB, ctx *datastore.VersionedCtx, supervoxels []uint64, outCh chan fileData, done <-chan struct{}) {
   462  	dbt, canGetTimestamp := db.(storage.KeyValueTimestampGetter)
   463  	for _, supervoxel := range supervoxels {
   464  		tk, err := NewTKey(supervoxel, d.Extension)
   465  		if err != nil {
   466  			outCh <- fileData{err: err}
   467  			continue
   468  		}
   469  		var modTime time.Time
   470  		var data []byte
   471  		if canGetTimestamp {
   472  			data, modTime, err = dbt.GetWithTimestamp(ctx, tk)
   473  		} else {
   474  			data, err = db.Get(ctx, tk)
   475  		}
   476  
   477  		// the store should return data = nil if not written, and data = []byte{} (len 0) if empty.
   478  		if err != nil {
   479  			outCh <- fileData{err: err}
   480  			continue
   481  		}
   482  		var ext string
   483  		if data == nil {
   484  			ext = "missing"
   485  		} else {
   486  			ext = d.Extension
   487  		}
   488  		hdr := &tar.Header{
   489  			Name:    fmt.Sprintf("%d.%s", supervoxel, ext),
   490  			Size:    int64(len(data)),
   491  			Mode:    0755,
   492  			ModTime: modTime,
   493  		}
   494  		select {
   495  		case outCh <- fileData{header: hdr, data: data}:
   496  		case <-done:
   497  		}
   498  	}
   499  }
   500  
   501  // if hash is not empty, make sure it is hash of data.
   502  func checkContentHash(hash string, data []byte) error {
   503  	if hash == "" {
   504  		return nil
   505  	}
   506  	hexHash := fmt.Sprintf("%x", md5.Sum(data))
   507  	if hexHash != hash {
   508  		return fmt.Errorf("content hash incorrect.  expected %s, got %s", hash, hexHash)
   509  	}
   510  	return nil
   511  }
   512  
   513  func (d *Data) handleExistence(uuid dvid.UUID, w http.ResponseWriter, r *http.Request) {
   514  	// GET <api URL>/node/<UUID>/<data name>/exists
   515  	if strings.ToLower(r.Method) != "get" {
   516  		server.BadRequest(w, r, "exists query must be a GET request")
   517  		return
   518  	}
   519  	data, err := ioutil.ReadAll(r.Body)
   520  	if err != nil {
   521  		server.BadRequest(w, r, "Bad GET request body for exists query: %v", err)
   522  		return
   523  	}
   524  	queryStrings := r.URL.Query()
   525  	hash := queryStrings.Get("hash")
   526  	if err := checkContentHash(hash, data); err != nil {
   527  		server.BadRequest(w, r, err)
   528  		return
   529  	}
   530  	var supervoxels []uint64
   531  	if err := json.Unmarshal(data, &supervoxels); err != nil {
   532  		server.BadRequest(w, r, fmt.Sprintf("Bad exists request JSON: %v", err))
   533  		return
   534  	}
   535  
   536  	db, err := datastore.GetKeyValueDB(d)
   537  	if err != nil {
   538  		server.BadRequest(w, r, err)
   539  		return
   540  	}
   541  	ctx, err := d.getRootContext(uuid)
   542  	if err != nil {
   543  		server.BadRequest(w, r, err)
   544  		return
   545  	}
   546  
   547  	buf := new(bytes.Buffer)
   548  	fmt.Fprintf(buf, "[")
   549  	sep := false
   550  	for _, supervoxel := range supervoxels {
   551  		if sep {
   552  			fmt.Fprintf(buf, ",")
   553  		}
   554  		tk, err := NewTKey(supervoxel, d.Extension)
   555  		if err != nil {
   556  			server.BadRequest(w, r, err)
   557  			return
   558  		}
   559  		dataPresent, err := db.Exists(ctx, tk)
   560  		if err != nil || !dataPresent {
   561  			fmt.Fprintf(buf, "false")
   562  		} else {
   563  			fmt.Fprintf(buf, "true")
   564  		}
   565  		sep = true
   566  	}
   567  	fmt.Fprintf(buf, "]")
   568  
   569  	w.Header().Set("Content-type", "application/json")
   570  	if _, err := w.Write(buf.Bytes()); err != nil {
   571  		server.BadRequest(w, r, err)
   572  	}
   573  }
   574  
   575  func (d *Data) handleMissing(uuid dvid.UUID, w http.ResponseWriter, label uint64) error {
   576  	// GET <api URL>/node/<UUID>/<data name>/missing/<label>
   577  	db, err := datastore.GetKeyValueDB(d)
   578  	if err != nil {
   579  		return err
   580  	}
   581  	ldata := d.getSyncedLabels()
   582  	if ldata == nil {
   583  		return fmt.Errorf("data %q is not synced with any labelmap instance", d.DataName())
   584  	}
   585  	ctx, err := d.getRootContext(uuid)
   586  	if err != nil {
   587  		return err
   588  	}
   589  	v, err := datastore.VersionFromUUID(uuid)
   590  	if err != nil {
   591  		return err
   592  	}
   593  	supervoxels, err := ldata.GetSupervoxels(v, label)
   594  	if err != nil {
   595  		return err
   596  	}
   597  	if len(supervoxels) == 0 {
   598  		return fmt.Errorf("label %d has no supervoxels", label)
   599  	}
   600  
   601  	var missing []string
   602  	for supervoxel := range supervoxels {
   603  		tk, err := NewTKey(supervoxel, d.Extension)
   604  		if err != nil {
   605  			return err
   606  		}
   607  		dataPresent, err := db.Exists(ctx, tk)
   608  		if err != nil {
   609  			return err
   610  		}
   611  		if !dataPresent {
   612  			missing = append(missing, fmt.Sprintf("%d", supervoxel))
   613  		}
   614  	}
   615  	out := "[" + strings.Join(missing, ",") + "]"
   616  	w.Header().Set("Content-type", "application/json")
   617  	if _, err := w.Write([]byte(out)); err != nil {
   618  		return err
   619  	}
   620  	return nil
   621  }
   622  
   623  func (d *Data) checkTarfile(w http.ResponseWriter, uuid dvid.UUID, label uint64) error {
   624  	db, err := datastore.GetKeyValueDB(d)
   625  	if err != nil {
   626  		return err
   627  	}
   628  	ldata := d.getSyncedLabels()
   629  	if ldata == nil {
   630  		return fmt.Errorf("data %q is not synced with any labelmap instance", d.DataName())
   631  	}
   632  	ctx, err := d.getRootContext(uuid)
   633  	if err != nil {
   634  		return err
   635  	}
   636  	v, err := datastore.VersionFromUUID(uuid)
   637  	if err != nil {
   638  		return err
   639  	}
   640  	supervoxels, err := ldata.GetSupervoxels(v, label)
   641  	if err != nil {
   642  		return err
   643  	}
   644  	if len(supervoxels) == 0 {
   645  		return fmt.Errorf("label %d has no supervoxels", label)
   646  	}
   647  	allPresent := true
   648  	for supervoxel := range supervoxels {
   649  		tk, err := NewTKey(supervoxel, d.Extension)
   650  		if err != nil {
   651  			return err
   652  		}
   653  		allPresent, err = db.Exists(ctx, tk)
   654  		if err != nil {
   655  			return err
   656  		}
   657  		if !allPresent {
   658  			break
   659  		}
   660  	}
   661  	if !allPresent {
   662  		return fmt.Errorf("not all supervoxel data available for label %d", label)
   663  	}
   664  	return nil
   665  }
   666  
   667  func (d *Data) sendTarfile(w http.ResponseWriter, uuid dvid.UUID, label uint64) error {
   668  	db, err := datastore.GetKeyValueDB(d)
   669  	if err != nil {
   670  		return err
   671  	}
   672  	ldata := d.getSyncedLabels()
   673  	if ldata == nil {
   674  		return fmt.Errorf("data %q is not synced with any labelmap instance", d.DataName())
   675  	}
   676  	ctx, err := d.getRootContext(uuid)
   677  	if err != nil {
   678  		return err
   679  	}
   680  	v, err := datastore.VersionFromUUID(uuid)
   681  	if err != nil {
   682  		return err
   683  	}
   684  	supervoxels, err := ldata.GetSupervoxels(v, label)
   685  	if err != nil {
   686  		return err
   687  	}
   688  	if len(supervoxels) == 0 {
   689  		return fmt.Errorf("label %d has no supervoxels", label)
   690  	}
   691  	numHandlers := 256 // Must be less than max open files, probably equal to multiple of disk queue
   692  	svlist := make(map[int][]uint64, len(supervoxels))
   693  	i := 0
   694  	for supervoxel := range supervoxels {
   695  		handler := i % numHandlers
   696  		svs := svlist[handler]
   697  		svs = append(svs, supervoxel)
   698  		svlist[handler] = svs
   699  		i++
   700  	}
   701  
   702  	done := make(chan struct{})
   703  	defer close(done)
   704  	outCh := make(chan fileData, len(supervoxels))
   705  	for i := 0; i < numHandlers; i++ {
   706  		go d.getSupervoxelGoroutine(db, ctx, svlist[i], outCh, done)
   707  	}
   708  
   709  	w.Header().Set("Content-type", "application/tar")
   710  	tw := tar.NewWriter(w)
   711  	defer tw.Close()
   712  	for i := 0; i < len(supervoxels); i++ {
   713  		fd := <-outCh
   714  		if fd.err != nil {
   715  			return fd.err
   716  		}
   717  		if fd.header != nil {
   718  			if err := tw.WriteHeader(fd.header); err != nil {
   719  				return err
   720  			}
   721  			if _, err := tw.Write(fd.data); err != nil {
   722  				return err
   723  			}
   724  		}
   725  	}
   726  	return nil
   727  }
   728  
   729  func (d *Data) ingestTarfile(r *http.Request, uuid dvid.UUID) error {
   730  	db, err := datastore.GetKeyValueDB(d)
   731  	if err != nil {
   732  		return err
   733  	}
   734  	ctx, err := d.getRootContext(uuid)
   735  	if err != nil {
   736  		return err
   737  	}
   738  	filenum := 1
   739  	tr := tar.NewReader(r.Body)
   740  	for {
   741  		hdr, err := tr.Next()
   742  		if err == io.EOF {
   743  			break
   744  		}
   745  		if err != nil {
   746  			return err
   747  		}
   748  		var supervoxel uint64
   749  		var ext string
   750  		n, err := fmt.Sscanf(hdr.Name, "%d.%s", &supervoxel, &ext)
   751  		if err != nil || n != 2 {
   752  			return fmt.Errorf("file %d name is invalid, expect supervoxel+ext: %s", filenum, hdr.Name)
   753  		}
   754  		if ext != d.Extension {
   755  			return fmt.Errorf("file %d name has bad extension (expect %q): %s", filenum, d.Extension, hdr.Name)
   756  		}
   757  		if supervoxel == 0 {
   758  			return fmt.Errorf("supervoxel 0 is reserved and cannot have data saved under 0 id")
   759  		}
   760  		var buf bytes.Buffer
   761  		if _, err := io.Copy(&buf, tr); err != nil {
   762  			return err
   763  		}
   764  		tk, err := NewTKey(supervoxel, ext)
   765  		if err := db.Put(ctx, tk, buf.Bytes()); err != nil {
   766  			return err
   767  		}
   768  		filenum++
   769  	}
   770  	return nil
   771  }
   772  
   773  // --- DataService interface ---
   774  
   775  func (d *Data) Help() string {
   776  	return fmt.Sprintf(helpMessage)
   777  }
   778  
   779  // DoRPC acts as a switchboard for RPC commands.
   780  func (d *Data) DoRPC(request datastore.Request, reply *datastore.Response) error {
   781  	switch request.TypeCommand() {
   782  	default:
   783  		return fmt.Errorf("unknown command.  Data '%s' [%s] does not support '%s' command",
   784  			d.DataName(), d.TypeName(), request.TypeCommand())
   785  	}
   786  }
   787  
   788  // ServeHTTP handles all incoming HTTP requests for this data.
   789  func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) {
   790  	timedLog := dvid.NewTimeLog()
   791  
   792  	// Break URL request into arguments
   793  	url := r.URL.Path[len(server.WebAPIPath):]
   794  	parts := strings.Split(url, "/")
   795  	if len(parts[len(parts)-1]) == 0 {
   796  		parts = parts[:len(parts)-1]
   797  	}
   798  
   799  	if len(parts) < 4 {
   800  		server.BadRequest(w, r, "incomplete API specification")
   801  		return
   802  	}
   803  
   804  	var comment string
   805  	action := strings.ToLower(r.Method)
   806  
   807  	switch parts[3] {
   808  	case "help":
   809  		w.Header().Set("Content-Type", "text/plain")
   810  		fmt.Fprintln(w, d.Help())
   811  		return
   812  
   813  	case "info":
   814  		if action == "post" {
   815  			config, err := server.DecodeJSON(r)
   816  			if err != nil {
   817  				server.BadRequest(w, r, err)
   818  				return
   819  			}
   820  			if err := d.modifyConfig(config); err != nil {
   821  				server.BadRequest(w, r, err)
   822  				return
   823  			}
   824  			if err := datastore.SaveDataByUUID(uuid, d); err != nil {
   825  				server.BadRequest(w, r, err)
   826  				return
   827  			}
   828  			fmt.Fprintf(w, "Changed '%s' based on received configuration:\n%s\n", d.DataName(), config)
   829  			return
   830  		} else {
   831  			jsonBytes, err := d.JSONString()
   832  			if err != nil {
   833  				server.BadRequest(w, r, err)
   834  				return
   835  			}
   836  			w.Header().Set("Content-Type", "application/json")
   837  			fmt.Fprintf(w, string(jsonBytes))
   838  		}
   839  
   840  	case "sync":
   841  		if action != "post" {
   842  			server.BadRequest(w, r, "Only POST allowed to sync endpoint")
   843  			return
   844  		}
   845  		replace := r.URL.Query().Get("replace") == "true"
   846  		if err := datastore.SetSyncByJSON(d, uuid, replace, r.Body); err != nil {
   847  			server.BadRequest(w, r, err)
   848  			return
   849  		}
   850  
   851  	case "load":
   852  		if action != "post" {
   853  			server.BadRequest(w, r, "only POST action is supported for the 'load' endpoint")
   854  			return
   855  		}
   856  		if err := d.ingestTarfile(r, uuid); err != nil {
   857  			server.BadRequest(w, r, err)
   858  			return
   859  		}
   860  		comment = fmt.Sprintf("HTTP POST load on data %q", d.DataName())
   861  
   862  	case "exists":
   863  		d.handleExistence(uuid, w, r)
   864  		comment = fmt.Sprintf("HTTP GET exists of data %q", d.DataName())
   865  
   866  	case "missing":
   867  		if len(parts) < 5 {
   868  			server.BadRequest(w, r, "expect uint64 to follow /missing endpoint")
   869  			return
   870  		}
   871  		label, err := strconv.ParseUint(parts[4], 10, 64)
   872  		if err != nil {
   873  			server.BadRequest(w, r, err)
   874  			return
   875  		}
   876  		if label == 0 {
   877  			server.BadRequest(w, r, "Label 0 is protected background value and cannot be used")
   878  			return
   879  		}
   880  		if err := d.handleMissing(uuid, w, label); err != nil {
   881  			server.BadRequest(w, r, "can't get missing supervoxels: %v", err)
   882  			return
   883  		}
   884  		comment = fmt.Sprintf("HTTP GET missing supervoxels of label %d, data %q", label, d.DataName())
   885  
   886  	case "tarfile":
   887  		if len(parts) < 5 {
   888  			server.BadRequest(w, r, "expect uint64 to follow /tarfile endpoint")
   889  			return
   890  		}
   891  		label, err := strconv.ParseUint(parts[4], 10, 64)
   892  		if err != nil {
   893  			server.BadRequest(w, r, err)
   894  			return
   895  		}
   896  		if label == 0 {
   897  			server.BadRequest(w, r, "Label 0 is protected background value and cannot be used")
   898  			return
   899  		}
   900  		switch action {
   901  		case "get":
   902  			if err := d.sendTarfile(w, uuid, label); err != nil {
   903  				server.BadRequest(w, r, "can't send tarfile for label %d: %v", label, err)
   904  				return
   905  			}
   906  			comment = fmt.Sprintf("HTTP GET tarfile on data %q, label %d", d.DataName(), label)
   907  		case "head":
   908  			if err := d.checkTarfile(w, uuid, label); err != nil {
   909  				server.BadRequest(w, r, "can't check existence of tarfile for label %d: %v", label, err)
   910  				return
   911  			}
   912  			comment = fmt.Sprintf("HTTP HEAD tarfile on data %q, label %d", d.DataName(), label)
   913  		default:
   914  			server.BadRequest(w, r, "only GET and HEAD actions are support for the 'tarfile' endpoint")
   915  			return
   916  		}
   917  
   918  	case "supervoxel":
   919  		if len(parts) < 5 {
   920  			server.BadRequest(w, r, "expect uint64 to follow 'supervoxel' endpoint")
   921  			return
   922  		}
   923  		supervoxel, err := strconv.ParseUint(parts[4], 10, 64)
   924  		if err != nil {
   925  			server.BadRequest(w, r, err)
   926  			return
   927  		}
   928  		if supervoxel == 0 {
   929  			server.BadRequest(w, r, "Supervoxel 0 is protected background value and cannot be used\n")
   930  			return
   931  		}
   932  
   933  		switch action {
   934  		case "get":
   935  			data, found, err := d.GetData(uuid, supervoxel)
   936  			if err != nil {
   937  				server.BadRequest(w, r, err)
   938  				return
   939  			}
   940  			if !found {
   941  				http.Error(w, fmt.Sprintf("Supervoxel %d not found", supervoxel), http.StatusNotFound)
   942  				return
   943  			}
   944  			if data != nil || len(data) > 0 {
   945  				_, err = w.Write(data)
   946  				if err != nil {
   947  					server.BadRequest(w, r, err)
   948  					return
   949  				}
   950  				w.Header().Set("Content-Type", "application/octet-stream")
   951  			}
   952  			comment = fmt.Sprintf("HTTP GET supervoxel %d of tarsupervoxels %q: %d bytes (%s)\n", supervoxel, d.DataName(), len(data), url)
   953  
   954  		case "delete":
   955  			if err := d.DeleteData(uuid, supervoxel); err != nil {
   956  				server.BadRequest(w, r, err)
   957  				return
   958  			}
   959  			comment = fmt.Sprintf("HTTP DELETE supervoxel %d data of tarsupervoxels %q (%s)\n", supervoxel, d.DataName(), url)
   960  
   961  		case "post":
   962  			data, err := ioutil.ReadAll(r.Body)
   963  			if err != nil {
   964  				server.BadRequest(w, r, err)
   965  				return
   966  			}
   967  			if err := d.PutData(uuid, supervoxel, data); err != nil {
   968  				server.BadRequest(w, r, err)
   969  				return
   970  			}
   971  			comment = fmt.Sprintf("HTTP POST tarsupervoxels %q: %d bytes (%s)\n", d.DataName(), len(data), url)
   972  		default:
   973  			server.BadRequest(w, r, "supervoxel endpoint does not support %q HTTP verb", action)
   974  			return
   975  		}
   976  
   977  	default:
   978  		server.BadAPIRequest(w, r, d)
   979  		return
   980  	}
   981  
   982  	timedLog.Infof(comment)
   983  	return
   984  }