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  }