github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/schema/dirreader.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package schema
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  
    25  	"camlistore.org/pkg/blob"
    26  )
    27  
    28  // A DirReader reads the entries of a "directory" schema blob's
    29  // referenced "static-set" blob.
    30  type DirReader struct {
    31  	fetcher blob.SeekFetcher
    32  	ss      *superset
    33  
    34  	staticSet []blob.Ref
    35  	current   int
    36  }
    37  
    38  // NewDirReader creates a new directory reader and prepares to
    39  // fetch the static-set entries
    40  func NewDirReader(fetcher blob.SeekFetcher, dirBlobRef blob.Ref) (*DirReader, error) {
    41  	ss := new(superset)
    42  	err := ss.setFromBlobRef(fetcher, dirBlobRef)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	if ss.Type != "directory" {
    47  		return nil, fmt.Errorf("schema/filereader: expected \"directory\" schema blob for %s, got %q", dirBlobRef, ss.Type)
    48  	}
    49  	dr, err := ss.NewDirReader(fetcher)
    50  	if err != nil {
    51  		return nil, fmt.Errorf("schema/filereader: creating DirReader for %s: %v", dirBlobRef, err)
    52  	}
    53  	dr.current = 0
    54  	return dr, nil
    55  }
    56  
    57  func (b *Blob) NewDirReader(fetcher blob.SeekFetcher) (*DirReader, error) {
    58  	return b.ss.NewDirReader(fetcher)
    59  }
    60  
    61  func (ss *superset) NewDirReader(fetcher blob.SeekFetcher) (*DirReader, error) {
    62  	if ss.Type != "directory" {
    63  		return nil, fmt.Errorf("Superset not of type \"directory\"")
    64  	}
    65  	return &DirReader{fetcher: fetcher, ss: ss}, nil
    66  }
    67  
    68  func (ss *superset) setFromBlobRef(fetcher blob.SeekFetcher, blobRef blob.Ref) error {
    69  	if !blobRef.Valid() {
    70  		return errors.New("schema/filereader: blobref invalid")
    71  	}
    72  	ss.BlobRef = blobRef
    73  	rsc, _, err := fetcher.Fetch(blobRef)
    74  	if err != nil {
    75  		return fmt.Errorf("schema/filereader: fetching schema blob %s: %v", blobRef, err)
    76  	}
    77  	defer rsc.Close()
    78  	if err = json.NewDecoder(rsc).Decode(ss); err != nil {
    79  		return fmt.Errorf("schema/filereader: decoding schema blob %s: %v", blobRef, err)
    80  	}
    81  	return nil
    82  }
    83  
    84  // StaticSet returns the whole of the static set members of that directory
    85  func (dr *DirReader) StaticSet() ([]blob.Ref, error) {
    86  	if dr.staticSet != nil {
    87  		return dr.staticSet, nil
    88  	}
    89  	staticSetBlobref := dr.ss.Entries
    90  	if !staticSetBlobref.Valid() {
    91  		return nil, fmt.Errorf("schema/filereader: Invalid blobref\n")
    92  	}
    93  	rsc, _, err := dr.fetcher.Fetch(staticSetBlobref)
    94  	if err != nil {
    95  		return nil, fmt.Errorf("schema/filereader: fetching schema blob %s: %v", staticSetBlobref, err)
    96  	}
    97  	defer rsc.Close()
    98  	ss, err := parseSuperset(rsc)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("schema/filereader: decoding schema blob %s: %v", staticSetBlobref, err)
   101  	}
   102  	if ss.Type != "static-set" {
   103  		return nil, fmt.Errorf("schema/filereader: expected \"static-set\" schema blob for %s, got %q", staticSetBlobref, ss.Type)
   104  	}
   105  	for _, member := range ss.Members {
   106  		if !member.Valid() {
   107  			return nil, fmt.Errorf("schema/filereader: invalid (static-set member) blobref\n")
   108  		}
   109  		dr.staticSet = append(dr.staticSet, member)
   110  	}
   111  	return dr.staticSet, nil
   112  }
   113  
   114  // Readdir implements the Directory interface.
   115  func (dr *DirReader) Readdir(n int) (entries []DirectoryEntry, err error) {
   116  	sts, err := dr.StaticSet()
   117  	if err != nil {
   118  		return nil, fmt.Errorf("schema/filereader: can't get StaticSet: %v\n", err)
   119  	}
   120  	up := dr.current + n
   121  	if n <= 0 {
   122  		dr.current = 0
   123  		up = len(sts)
   124  	} else {
   125  		if n > (len(sts) - dr.current) {
   126  			err = io.EOF
   127  			up = len(sts)
   128  		}
   129  	}
   130  
   131  	// TODO(bradfitz): push down information to the fetcher
   132  	// (e.g. cachingfetcher -> remote client http) that we're
   133  	// going to load a bunch, so the HTTP client (if not using
   134  	// SPDY) can do discovery and see if the server supports a
   135  	// batch handler, then get them all in one round-trip, rather
   136  	// than attacking the server with hundreds of parallel TLS
   137  	// setups.
   138  
   139  	type res struct {
   140  		ent DirectoryEntry
   141  		err error
   142  	}
   143  	var cs []chan res
   144  
   145  	// Kick off all directory entry loads.
   146  	// TODO: bound this?
   147  	for _, entRef := range sts[dr.current:up] {
   148  		c := make(chan res, 1)
   149  		cs = append(cs, c)
   150  		go func(entRef blob.Ref) {
   151  			entry, err := NewDirectoryEntryFromBlobRef(dr.fetcher, entRef)
   152  			c <- res{entry, err}
   153  		}(entRef)
   154  	}
   155  
   156  	for _, c := range cs {
   157  		res := <-c
   158  		if res.err != nil {
   159  			return nil, fmt.Errorf("schema/filereader: can't create dirEntry: %v\n", err)
   160  		}
   161  		entries = append(entries, res.ent)
   162  	}
   163  	return entries, nil
   164  }