github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/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  			if err := ds.readBlobs(ropts); err != nil {
   139  				return err
   140  			}
   141  			continue
   142  		}
   143  
   144  		stat()
   145  		if err != nil {
   146  			return err
   147  		}
   148  
   149  		if !fi.IsDir() && strings.HasSuffix(name, ".dat") {
   150  			blobName := strings.TrimSuffix(name, ".dat")
   151  			if blobName <= opts.after {
   152  				continue
   153  			}
   154  			if blobRef, ok := blob.Parse(blobName); ok {
   155  				select {
   156  				case opts.ch <- blob.SizedRef{Ref: blobRef, Size: uint32(fi.Size())}:
   157  				case <-opts.done:
   158  					return context.ErrCanceled
   159  				}
   160  				(*opts.remain)--
   161  			}
   162  			continue
   163  		}
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func (ds *DiskStorage) EnumerateBlobs(ctx *context.Context, dest chan<- blob.SizedRef, after string, limit int) error {
   170  	defer close(dest)
   171  	if limit == 0 {
   172  		log.Printf("Warning: localdisk.EnumerateBlobs called with a limit of 0")
   173  	}
   174  
   175  	limitMutable := limit
   176  	return ds.readBlobs(readBlobRequest{
   177  		done:    ctx.Done(),
   178  		ch:      dest,
   179  		dirRoot: ds.root,
   180  		after:   after,
   181  		remain:  &limitMutable,
   182  	})
   183  }
   184  
   185  func skipDir(name string) bool {
   186  	// The partition directory is old. (removed from codebase, but
   187  	// likely still on disk for some people)
   188  	// the "cache" directory is just a hack: it's used
   189  	// by the serverconfig/genconfig code, as a default
   190  	// location for most users to put their thumbnail
   191  	// cache.  For now we just also skip it here.
   192  	return name == "partition" || name == "cache"
   193  }
   194  
   195  func isShardDir(name string) bool {
   196  	return len(name) == 2 && isHex(name[0]) && isHex(name[1])
   197  }
   198  
   199  func isHex(b byte) bool {
   200  	return ('0' <= b && b <= '9') || ('a' <= b && b <= 'f')
   201  }