github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/blobserver/localdisk/enumerate.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 localdisk
    18  
    19  import (
    20  	"fmt"
    21  	"log"
    22  	"os"
    23  	"path/filepath"
    24  	"sort"
    25  	"strings"
    26  
    27  	"camlistore.org/pkg/blob"
    28  	"camlistore.org/pkg/context"
    29  )
    30  
    31  type readBlobRequest struct {
    32  	done    <-chan struct{}
    33  	ch      chan<- blob.SizedRef
    34  	after   string
    35  	remain  *int // limit countdown
    36  	dirRoot string
    37  
    38  	// Not used on initial request, only on recursion
    39  	blobPrefix, pathInto string
    40  }
    41  
    42  type enumerateError struct {
    43  	msg string
    44  	err error
    45  }
    46  
    47  func (ee *enumerateError) Error() string {
    48  	return fmt.Sprintf("Enumerate error: %s: %v", ee.msg, ee.err)
    49  }
    50  
    51  func (ds *DiskStorage) readBlobs(opts readBlobRequest) error {
    52  	dirFullPath := filepath.Join(opts.dirRoot, opts.pathInto)
    53  	dir, err := os.Open(dirFullPath)
    54  	if err != nil {
    55  		return &enumerateError{"localdisk: opening directory " + dirFullPath, err}
    56  	}
    57  	names, err := dir.Readdirnames(-1)
    58  	dir.Close()
    59  	if err == nil && len(names) == 0 {
    60  		// remove empty blob dir if we are in a queue but not the queue root itself
    61  		if strings.Contains(dirFullPath, "queue-") &&
    62  			!strings.Contains(filepath.Base(dirFullPath), "queue-") {
    63  			go ds.tryRemoveDir(dirFullPath)
    64  		}
    65  		return nil
    66  	}
    67  	if err != nil {
    68  		return &enumerateError{"localdisk: readdirnames of " + dirFullPath, err}
    69  	}
    70  	sort.Strings(names)
    71  	stat := make(map[string]chan interface{}) // gets sent error or os.FileInfo
    72  	for _, name := range names {
    73  		if skipDir(name) || isShardDir(name) {
    74  			continue
    75  		}
    76  		ch := make(chan interface{}, 1) // 1 in case it's not read
    77  		name := name
    78  		stat[name] = ch
    79  		go func() {
    80  			fi, err := os.Stat(filepath.Join(dirFullPath, name))
    81  			if err != nil {
    82  				ch <- err
    83  			} else {
    84  				ch <- fi
    85  			}
    86  		}()
    87  	}
    88  
    89  	for _, name := range names {
    90  		if *opts.remain == 0 {
    91  			return nil
    92  		}
    93  		if skipDir(name) {
    94  			continue
    95  		}
    96  		var (
    97  			fi      os.FileInfo
    98  			err     error
    99  			didStat bool
   100  		)
   101  		stat := func() {
   102  			if didStat {
   103  				return
   104  			}
   105  			didStat = true
   106  			fiv := <-stat[name]
   107  			var ok bool
   108  			if err, ok = fiv.(error); ok {
   109  				err = &enumerateError{"localdisk: stat of file " + filepath.Join(dirFullPath, name), err}
   110  			} else {
   111  				fi = fiv.(os.FileInfo)
   112  			}
   113  		}
   114  		isDir := func() bool {
   115  			stat()
   116  			return fi != nil && fi.IsDir()
   117  		}
   118  
   119  		if isShardDir(name) || isDir() {
   120  			var newBlobPrefix string
   121  			if opts.blobPrefix == "" {
   122  				newBlobPrefix = name + "-"
   123  			} else {
   124  				newBlobPrefix = opts.blobPrefix + name
   125  			}
   126  			if len(opts.after) > 0 {
   127  				compareLen := len(newBlobPrefix)
   128  				if len(opts.after) < compareLen {
   129  					compareLen = len(opts.after)
   130  				}
   131  				if newBlobPrefix[:compareLen] < opts.after[:compareLen] {
   132  					continue
   133  				}
   134  			}
   135  			ropts := opts
   136  			ropts.blobPrefix = newBlobPrefix
   137  			ropts.pathInto = opts.pathInto + "/" + name
   138  			ds.readBlobs(ropts)
   139  			continue
   140  		}
   141  
   142  		stat()
   143  		if err != nil {
   144  			return err
   145  		}
   146  
   147  		if !fi.IsDir() && strings.HasSuffix(name, ".dat") {
   148  			blobName := strings.TrimSuffix(name, ".dat")
   149  			if blobName <= opts.after {
   150  				continue
   151  			}
   152  			if blobRef, ok := blob.Parse(blobName); ok {
   153  				select {
   154  				case opts.ch <- blob.SizedRef{Ref: blobRef, Size: fi.Size()}:
   155  				case <-opts.done:
   156  					return context.ErrCanceled
   157  				}
   158  				(*opts.remain)--
   159  			}
   160  			continue
   161  		}
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  func (ds *DiskStorage) EnumerateBlobs(ctx *context.Context, dest chan<- blob.SizedRef, after string, limit int) error {
   168  	defer close(dest)
   169  	if limit == 0 {
   170  		log.Printf("Warning: localdisk.EnumerateBlobs called with a limit of 0")
   171  	}
   172  
   173  	limitMutable := limit
   174  	return ds.readBlobs(readBlobRequest{
   175  		done:    ctx.Done(),
   176  		ch:      dest,
   177  		dirRoot: ds.root,
   178  		after:   after,
   179  		remain:  &limitMutable,
   180  	})
   181  }
   182  
   183  func skipDir(name string) bool {
   184  	// The partition directory is old. (removed from codebase, but
   185  	// likely still on disk for some people)
   186  	// the "cache" directory is just a hack: it's used
   187  	// by the serverconfig/genconfig code, as a default
   188  	// location for most users to put their thumbnail
   189  	// cache.  For now we just also skip it here.
   190  	return name == "partition" || name == "cache"
   191  }
   192  
   193  func isShardDir(name string) bool {
   194  	return len(name) == 2 && isHex(name[0]) && isHex(name[1])
   195  }
   196  
   197  func isHex(b byte) bool {
   198  	return ('0' <= b && b <= '9') || ('a' <= b && b <= 'f')
   199  }