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 }