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 }