github.com/balzaczyy/golucene@v0.0.0-20151210033525-d0be9ee89713/core/store/fs.go (about) 1 package store 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/balzaczyy/golucene/core/util" 7 "io" 8 "math" 9 "os" 10 "path/filepath" 11 "strconv" 12 "sync" 13 // "time" 14 ) 15 16 type NoSuchDirectoryError struct { 17 msg string 18 } 19 20 func newNoSuchDirectoryError(msg string) *NoSuchDirectoryError { 21 return &NoSuchDirectoryError{msg} 22 } 23 24 func (err *NoSuchDirectoryError) Error() string { 25 return err.msg 26 } 27 28 type FSDirectorySPI interface { 29 OpenInput(string, IOContext) (IndexInput, error) 30 } 31 32 type FSDirectory struct { 33 *DirectoryImpl 34 *BaseDirectory 35 FSDirectorySPI 36 sync.Locker 37 path string 38 staleFiles map[string]bool // synchronized, files written, but not yet sync'ed 39 staleFilesLock *sync.RWMutex 40 chunkSize int 41 } 42 43 // TODO support lock factory 44 func newFSDirectory(spi FSDirectorySPI, path string) (d *FSDirectory, err error) { 45 d = &FSDirectory{ 46 Locker: &sync.Mutex{}, 47 path: path, 48 staleFiles: make(map[string]bool), 49 staleFilesLock: &sync.RWMutex{}, 50 chunkSize: math.MaxInt32, 51 } 52 d.DirectoryImpl = NewDirectoryImpl(d) 53 d.BaseDirectory = NewBaseDirectory(d) 54 d.FSDirectorySPI = spi 55 56 if fi, err := os.Stat(path); err == nil && !fi.IsDir() { 57 return d, newNoSuchDirectoryError(fmt.Sprintf("file '%v' exists but is not a directory", path)) 58 } 59 60 // TODO default to native lock factory 61 d.SetLockFactory(NewSimpleFSLockFactory(path)) 62 return d, nil 63 } 64 65 func OpenFSDirectory(path string) (d Directory, err error) { 66 // TODO support native implementations 67 super, err := NewSimpleFSDirectory(path) 68 if err != nil { 69 return nil, err 70 } 71 return super, nil 72 } 73 74 func (d *FSDirectory) SetLockFactory(lockFactory LockFactory) { 75 d.BaseDirectory.SetLockFactory(lockFactory) 76 77 // for filesystem based LockFactory, delete the lockPrefix, if the locks are placed 78 // in index dir. If no index dir is given, set ourselves 79 // TODO change FSDirectory to interface 80 if lf, ok := lockFactory.(*SimpleFSLockFactory); ok { 81 if lf.lockDir == "" { 82 lf.lockDir = d.path 83 lf.lockPrefix = "" 84 } else if lf.lockDir == d.path { 85 lf.lockPrefix = "" 86 } 87 } 88 } 89 90 func FSDirectoryListAll(path string) (paths []string, err error) { 91 f, err := os.Open(path) 92 if os.IsNotExist(err) { 93 return nil, newNoSuchDirectoryError(fmt.Sprintf("directory '%v' does not exist", path)) 94 } else if err != nil { 95 return nil, err 96 } 97 defer f.Close() 98 fi, err := f.Stat() 99 if !fi.IsDir() { 100 return nil, newNoSuchDirectoryError(fmt.Sprintf("file '%v' exists but is not a directory", path)) 101 } 102 103 // Exclude subdirs 104 return f.Readdirnames(0) 105 } 106 107 func (d *FSDirectory) ListAll() (paths []string, err error) { 108 d.EnsureOpen() 109 return FSDirectoryListAll(d.path) 110 } 111 112 func (d *FSDirectory) FileExists(name string) bool { 113 d.EnsureOpen() 114 _, err := os.Stat(filepath.Join(d.path, name)) 115 return err == nil || os.IsExist(err) 116 } 117 118 // Returns the length in bytes of a file in the directory. 119 func (d *FSDirectory) FileLength(name string) (n int64, err error) { 120 d.EnsureOpen() 121 fi, err := os.Stat(filepath.Join(d.path, name)) 122 if err != nil { 123 return 0, err 124 } 125 return fi.Size(), nil 126 } 127 128 // Removes an existing file in the directory. 129 func (d *FSDirectory) DeleteFile(name string) (err error) { 130 d.EnsureOpen() 131 if err = os.Remove(filepath.Join(d.path, name)); err == nil { 132 d.staleFilesLock.Lock() 133 defer d.staleFilesLock.Unlock() 134 delete(d.staleFiles, name) 135 } 136 return 137 } 138 139 /* 140 Creates an IndexOutput for the file with the given name. 141 */ 142 func (d *FSDirectory) CreateOutput(name string, ctx IOContext) (out IndexOutput, err error) { 143 d.EnsureOpen() 144 err = d.ensureCanWrite(name) 145 if err != nil { 146 return nil, err 147 } 148 return newFSIndexOutput(d, name) 149 } 150 151 func (d *FSDirectory) ensureCanWrite(name string) error { 152 err := os.MkdirAll(d.path, os.ModeDir|0660) 153 if err != nil { 154 return errors.New(fmt.Sprintf("Cannot create directory %v: %v", d.path, err)) 155 } 156 157 filename := filepath.Join(d.path, name) 158 _, err = os.Stat(filename) 159 if err == nil || os.IsExist(err) { 160 err = os.Remove(filename) 161 if err != nil { 162 return errors.New(fmt.Sprintf("Cannot overwrite %v/%v: %v", d.path, name, err)) 163 } 164 } 165 return nil 166 } 167 168 /* 169 Sub classes should call this method on closing an open IndexOuput, 170 reporting the name of the file that was closed. FSDirectory needs 171 this information to take care of syncing stale files. 172 */ 173 func (d *FSDirectory) onIndexOutputClosed(name string) { 174 d.staleFilesLock.Lock() 175 defer d.staleFilesLock.Unlock() 176 d.staleFiles[name] = true 177 } 178 179 func (d *FSDirectory) Sync(names []string) (err error) { 180 d.EnsureOpen() 181 182 toSync := make(map[string]bool) 183 d.staleFilesLock.RLock() 184 for _, name := range names { 185 if _, ok := d.staleFiles[name]; ok { 186 continue 187 } 188 toSync[name] = true 189 } 190 d.staleFilesLock.RUnlock() 191 192 for name, _ := range toSync { 193 err = d.fsync(name) 194 if err != nil { 195 return err 196 } 197 } 198 199 // fsync the directory itself, but only if there was any file 200 // fsynced before (otherwise it can happen that the directory does 201 // not yet exist)! 202 if len(toSync) > 0 { 203 err = util.Fsync(d.path, true) 204 if err != nil { 205 return err 206 } 207 } 208 209 for name, _ := range toSync { 210 delete(d.staleFiles, name) 211 } 212 return 213 } 214 215 func (d *FSDirectory) LockID() string { 216 d.EnsureOpen() 217 var digest int 218 for _, ch := range d.path { 219 digest = 31*digest + int(ch) 220 } 221 return fmt.Sprintf("lucene-%v", strconv.FormatUint(uint64(digest), 10)) 222 } 223 224 func (d *FSDirectory) Close() error { 225 d.Lock() // synchronized 226 defer d.Unlock() 227 d.IsOpen = false 228 return nil 229 } 230 231 func (d *FSDirectory) fsync(name string) error { 232 // var err, err2 error 233 // var success = false 234 // var retryCount = 0 235 // for !success && retryCount < 5 { 236 // retryCount++ 237 // if err2, success = func() (error, bool) { 238 // file, err := os.Open(filepath.Join(d.path, name)) 239 // if err != nil { 240 // return err, false 241 // } 242 // defer file.Close() 243 // return file.Sync(), true 244 // }(); err2 != nil { 245 // if err == nil { 246 // err = err2 247 // } 248 // time.Sleep(5 * time.Millisecond) 249 // } 250 // } 251 // return err 252 253 // Go's fsync doesn't work the same way as Java 254 // disabled since I got "fsync: Access is denied" all the time 255 return nil 256 } 257 258 func (d *FSDirectory) String() string { 259 return fmt.Sprintf("FSDirectory@%v", d.DirectoryImpl.String()) 260 } 261 262 // type FSIndexInput struct { 263 // *BufferedIndexInput 264 // file *os.File 265 // isClone bool 266 // chunkSize int 267 // off int64 268 // end int64 269 // } 270 271 // func newFSIndexInput(desc, path string, context IOContext, chunkSize int) (*FSIndexInput, error) { 272 // f, err := os.Open(path) 273 // if err != nil { 274 // return nil, err 275 // } 276 // fi, err := f.Stat() 277 // if err != nil { 278 // return nil, err 279 // } 280 // ans := &FSIndexInput{nil, f, false, chunkSize, 0, fi.Size()} 281 // ans.BufferedIndexInput = newBufferedIndexInput(ans, desc, context) 282 // return ans, nil 283 // } 284 285 // func newFSIndexInputFromFileSlice(desc string, f *os.File, off, length int64, bufferSize, chunkSize int) *FSIndexInput { 286 // ans := &FSIndexInput{nil, f, true, chunkSize, off, off + length} 287 // ans.BufferedIndexInput = newBufferedIndexInputBySize(ans, desc, bufferSize) 288 // return ans 289 // } 290 291 // func (in *FSIndexInput) Close() error { 292 // // only close the file if this is not a clone 293 // if !in.isClone { 294 // in.file.Close() 295 // } 296 // return nil 297 // } 298 299 // func (in *FSIndexInput) Clone() IndexInput { 300 // return &FSIndexInput{ 301 // in.BufferedIndexInput.Clone(), 302 // in.file, 303 // true, 304 // in.chunkSize, 305 // in.off, 306 // in.end} 307 // } 308 309 // func (in *FSIndexInput) Length() int64 { 310 // return in.end - in.off 311 // } 312 313 // func (in *FSIndexInput) String() string { 314 // return fmt.Sprintf("%v, off=%v, end=%v", in.BufferedIndexInput.String(), in.off, in.end) 315 // } 316 317 type FilteredWriteCloser struct { 318 io.WriteCloser 319 f func(p []byte) (int, error) 320 } 321 322 func filter(w io.WriteCloser, f func(p []byte) (int, error)) *FilteredWriteCloser { 323 return &FilteredWriteCloser{w, f} 324 } 325 326 func (w *FilteredWriteCloser) Write(p []byte) (int, error) { 327 return w.f(p) 328 } 329 330 /* 331 Writes output with File.Write([]byte) (int, error) 332 */ 333 type FSIndexOutput struct { 334 *OutputStreamIndexOutput 335 *FSDirectory 336 name string 337 } 338 339 func newFSIndexOutput(parent *FSDirectory, name string) (*FSIndexOutput, error) { 340 file, err := os.OpenFile(filepath.Join(parent.path, name), os.O_CREATE|os.O_EXCL|os.O_RDWR, 0660) 341 if err != nil { 342 return nil, err 343 } 344 return &FSIndexOutput{ 345 newOutputStreamIndexOutput(filter(file, func(p []byte) (int, error) { 346 // This implementation ensures, that we never write more than CHUNK_SIZE bytes: 347 chunk := CHUNK_SIZE 348 offset, length := 0, len(p) 349 for length > 0 { 350 if length < chunk { 351 chunk = length 352 } 353 n, err := file.Write(p[offset : offset+chunk]) 354 if err != nil { 355 return offset, err 356 } 357 length -= n 358 offset += n 359 } 360 return offset, nil 361 }), CHUNK_SIZE), 362 parent, 363 name, 364 }, nil 365 } 366 367 func (out *FSIndexOutput) Close() (err error) { 368 defer func() { 369 err = out.OutputStreamIndexOutput.Close() 370 }() 371 out.onIndexOutputClosed(out.name) 372 return nil 373 }