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 }