github.com/cyverse/go-irodsclient@v0.13.2/fs/fs.go (about)

     1  package fs
     2  
     3  import (
     4  	"path"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/cyverse/go-irodsclient/irods/connection"
     9  	irods_fs "github.com/cyverse/go-irodsclient/irods/fs"
    10  	"github.com/cyverse/go-irodsclient/irods/metrics"
    11  	"github.com/cyverse/go-irodsclient/irods/session"
    12  	"github.com/cyverse/go-irodsclient/irods/types"
    13  	"github.com/cyverse/go-irodsclient/irods/util"
    14  	"github.com/rs/xid"
    15  	"golang.org/x/xerrors"
    16  )
    17  
    18  // FileSystem provides a file-system like interface
    19  type FileSystem struct {
    20  	id                   string
    21  	account              *types.IRODSAccount
    22  	config               *FileSystemConfig
    23  	ioSession            *session.IRODSSession
    24  	metaSession          *session.IRODSSession
    25  	cache                *FileSystemCache
    26  	cachePropagation     *FileSystemCachePropagation
    27  	cacheEventHandlerMap *FilesystemCacheEventHandlerMap
    28  	fileHandleMap        *FileHandleMap
    29  }
    30  
    31  // NewFileSystem creates a new FileSystem
    32  func NewFileSystem(account *types.IRODSAccount, config *FileSystemConfig) (*FileSystem, error) {
    33  	ioSessionConfig := session.NewIRODSSessionConfig(config.ApplicationName, config.ConnectionErrorTimeout, config.ConnectionInitNumber, config.ConnectionLifespan, config.OperationTimeout, config.ConnectionIdleTimeout, config.ConnectionMax, config.TCPBufferSize, config.StartNewTransaction)
    34  	ioSession, err := session.NewIRODSSession(account, ioSessionConfig)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	metaSessionConfig := session.NewIRODSSessionConfig(config.ApplicationName, config.ConnectionErrorTimeout, config.ConnectionInitNumber, config.ConnectionLifespan, config.OperationTimeout, config.ConnectionIdleTimeout, FileSystemConnectionMetaDefault, config.TCPBufferSize, config.StartNewTransaction)
    40  	metaSession, err := session.NewIRODSSession(account, metaSessionConfig)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	ioTransactionFailureHandler := func(commitFail bool, poormansRollbackFail bool) {
    46  		metaSession.SetCommitFail(commitFail)
    47  		metaSession.SetPoormansRollbackFail(poormansRollbackFail)
    48  	}
    49  
    50  	metaTransactionFailureHandler := func(commitFail bool, poormansRollbackFail bool) {
    51  		ioSession.SetCommitFail(commitFail)
    52  		ioSession.SetPoormansRollbackFail(poormansRollbackFail)
    53  	}
    54  
    55  	ioSession.SetTransactionFailureHandler(ioTransactionFailureHandler)
    56  	metaSession.SetTransactionFailureHandler(metaTransactionFailureHandler)
    57  
    58  	cache := NewFileSystemCache(config.CacheTimeout, config.CacheCleanupTime, config.CacheTimeoutSettings, config.InvalidateParentEntryCacheImmediately)
    59  
    60  	fs := &FileSystem{
    61  		id:                   xid.New().String(), // generate a new ID
    62  		account:              account,
    63  		config:               config,
    64  		ioSession:            ioSession,
    65  		metaSession:          metaSession,
    66  		cache:                cache,
    67  		cacheEventHandlerMap: NewFilesystemCacheEventHandlerMap(),
    68  		fileHandleMap:        NewFileHandleMap(),
    69  	}
    70  
    71  	cachePropagation := NewFileSystemCachePropagation(fs)
    72  	fs.cachePropagation = cachePropagation
    73  
    74  	return fs, nil
    75  }
    76  
    77  // NewFileSystemWithDefault creates a new FileSystem with default configurations
    78  func NewFileSystemWithDefault(account *types.IRODSAccount, applicationName string) (*FileSystem, error) {
    79  	config := NewFileSystemConfigWithDefault(applicationName)
    80  	ioSessionConfig := session.NewIRODSSessionConfig(config.ApplicationName, config.ConnectionErrorTimeout, config.ConnectionInitNumber, config.ConnectionLifespan, config.OperationTimeout, config.ConnectionIdleTimeout, config.ConnectionMax, config.TCPBufferSize, config.StartNewTransaction)
    81  	ioSession, err := session.NewIRODSSession(account, ioSessionConfig)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	metaSessionConfig := session.NewIRODSSessionConfig(config.ApplicationName, config.ConnectionErrorTimeout, config.ConnectionInitNumber, config.ConnectionLifespan, config.OperationTimeout, config.ConnectionIdleTimeout, FileSystemConnectionMetaDefault, config.TCPBufferSize, config.StartNewTransaction)
    87  	metaSession, err := session.NewIRODSSession(account, metaSessionConfig)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	cache := NewFileSystemCache(config.CacheTimeout, config.CacheCleanupTime, config.CacheTimeoutSettings, config.InvalidateParentEntryCacheImmediately)
    93  
    94  	fs := &FileSystem{
    95  		id:                   xid.New().String(), // generate a new ID
    96  		account:              account,
    97  		config:               config,
    98  		ioSession:            ioSession,
    99  		metaSession:          metaSession,
   100  		cache:                cache,
   101  		cacheEventHandlerMap: NewFilesystemCacheEventHandlerMap(),
   102  		fileHandleMap:        NewFileHandleMap(),
   103  	}
   104  
   105  	cachePropagation := NewFileSystemCachePropagation(fs)
   106  	fs.cachePropagation = cachePropagation
   107  
   108  	return fs, nil
   109  }
   110  
   111  // NewFileSystemWithSessionConfig creates a new FileSystem with custom session configurations
   112  func NewFileSystemWithSessionConfig(account *types.IRODSAccount, sessConfig *session.IRODSSessionConfig) (*FileSystem, error) {
   113  	config := NewFileSystemConfigWithDefault(sessConfig.ApplicationName)
   114  	ioSession, err := session.NewIRODSSession(account, sessConfig)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	metaSessionConfig := session.NewIRODSSessionConfig(config.ApplicationName, config.ConnectionErrorTimeout, config.ConnectionInitNumber, config.ConnectionLifespan, config.OperationTimeout, config.ConnectionIdleTimeout, FileSystemConnectionMetaDefault, config.TCPBufferSize, config.StartNewTransaction)
   120  	metaSession, err := session.NewIRODSSession(account, metaSessionConfig)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	cache := NewFileSystemCache(config.CacheTimeout, config.CacheCleanupTime, config.CacheTimeoutSettings, config.InvalidateParentEntryCacheImmediately)
   126  
   127  	fs := &FileSystem{
   128  		id:                   xid.New().String(), // generate a new ID
   129  		account:              account,
   130  		config:               config,
   131  		ioSession:            ioSession,
   132  		metaSession:          metaSession,
   133  		cache:                cache,
   134  		cacheEventHandlerMap: NewFilesystemCacheEventHandlerMap(),
   135  		fileHandleMap:        NewFileHandleMap(),
   136  	}
   137  
   138  	cachePropagation := NewFileSystemCachePropagation(fs)
   139  	fs.cachePropagation = cachePropagation
   140  
   141  	return fs, nil
   142  }
   143  
   144  // Release releases all resources
   145  func (fs *FileSystem) Release() {
   146  	handles := fs.fileHandleMap.PopAll()
   147  	for _, handle := range handles {
   148  		handle.Close()
   149  	}
   150  
   151  	fs.cacheEventHandlerMap.Release()
   152  	fs.cachePropagation.Release()
   153  
   154  	fs.ioSession.Release()
   155  	fs.metaSession.Release()
   156  }
   157  
   158  // GetID returns file system instance ID
   159  func (fs *FileSystem) GetID() string {
   160  	return fs.id
   161  }
   162  
   163  // GetIOConnection returns irods connection for IO
   164  func (fs *FileSystem) GetIOConnection() (*connection.IRODSConnection, error) {
   165  	return fs.ioSession.AcquireConnection()
   166  }
   167  
   168  // ReturnIOConnection returns irods connection for IO back to session
   169  func (fs *FileSystem) ReturnIOConnection(conn *connection.IRODSConnection) {
   170  	fs.ioSession.ReturnConnection(conn)
   171  }
   172  
   173  // GetMetadataConnection returns irods connection for metadata operations
   174  func (fs *FileSystem) GetMetadataConnection() (*connection.IRODSConnection, error) {
   175  	return fs.metaSession.AcquireConnection()
   176  }
   177  
   178  // ReturnMetadataConnection returns irods connection for metadata operations back to session
   179  func (fs *FileSystem) ReturnMetadataConnection(conn *connection.IRODSConnection) {
   180  	fs.metaSession.ReturnConnection(conn)
   181  }
   182  
   183  // ConnectionTotal counts current established connections
   184  func (fs *FileSystem) ConnectionTotal() int {
   185  	return fs.ioSession.ConnectionTotal() + fs.metaSession.ConnectionTotal()
   186  }
   187  
   188  // GetServerVersion returns server version info
   189  func (fs *FileSystem) GetServerVersion() (*types.IRODSVersion, error) {
   190  	conn, err := fs.metaSession.AcquireConnection()
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	defer fs.metaSession.ReturnConnection(conn)
   195  
   196  	return conn.GetVersion(), nil
   197  }
   198  
   199  // SupportParallelUpload returns if the server supports parallel upload
   200  func (fs *FileSystem) SupportParallelUpload() bool {
   201  	return fs.metaSession.SupportParallelUpload()
   202  }
   203  
   204  // GetMetrics returns metrics
   205  func (fs *FileSystem) GetMetrics() *metrics.IRODSMetrics {
   206  	ioMetrics := fs.ioSession.GetMetrics()
   207  	metaMetrics := fs.metaSession.GetMetrics()
   208  
   209  	newMetrics := &metrics.IRODSMetrics{}
   210  	newMetrics.Sum(ioMetrics)
   211  	newMetrics.Sum(metaMetrics)
   212  	return newMetrics
   213  }
   214  
   215  // Stat returns file status
   216  func (fs *FileSystem) Stat(p string) (*Entry, error) {
   217  	irodsPath := util.GetCorrectIRODSPath(p)
   218  
   219  	// check if a negative cache for the given path exists
   220  	if fs.cache.HasNegativeEntryCache(irodsPath) {
   221  		// has a negative cache - fail fast
   222  		return nil, xerrors.Errorf("failed to find the data object or the collection for path %s: %w", irodsPath, types.NewFileNotFoundError(irodsPath))
   223  	}
   224  
   225  	// check if a cached Entry for the given path exists
   226  	cachedEntry := fs.cache.GetEntryCache(irodsPath)
   227  	if cachedEntry != nil {
   228  		return cachedEntry, nil
   229  	}
   230  
   231  	// check if a cached dir Entry for the given path exists
   232  	parentPath := path.Dir(irodsPath)
   233  	cachedDirEntryPaths := fs.cache.GetDirCache(parentPath)
   234  	dirEntryExist := false
   235  	if cachedDirEntryPaths != nil {
   236  		for _, cachedDirEntryPath := range cachedDirEntryPaths {
   237  			if cachedDirEntryPath == irodsPath {
   238  				dirEntryExist = true
   239  				break
   240  			}
   241  		}
   242  
   243  		if !dirEntryExist {
   244  			// dir entry not exist - fail fast
   245  			fs.cache.AddNegativeEntryCache(irodsPath)
   246  			return nil, xerrors.Errorf("failed to find the data object or the collection for path %s: %w", irodsPath, types.NewFileNotFoundError(irodsPath))
   247  		}
   248  	}
   249  
   250  	// if cache does not exist,
   251  	// check dir first
   252  	dirStat, err := fs.getCollectionNoCache(irodsPath)
   253  	if err != nil {
   254  		if !types.IsFileNotFoundError(err) {
   255  			return nil, err
   256  		}
   257  	} else {
   258  		return dirStat, nil
   259  	}
   260  
   261  	// if it's not dir, check file
   262  	fileStat, err := fs.getDataObjectNoCache(irodsPath)
   263  	if err != nil {
   264  		if !types.IsFileNotFoundError(err) {
   265  			return nil, err
   266  		}
   267  	} else {
   268  		return fileStat, nil
   269  	}
   270  
   271  	// not a collection, not a data object
   272  	fs.cache.AddNegativeEntryCache(irodsPath)
   273  	return nil, xerrors.Errorf("failed to find the data object or the collection for path %s: %w", irodsPath, types.NewFileNotFoundError(irodsPath))
   274  }
   275  
   276  // StatDir returns status of a directory
   277  func (fs *FileSystem) StatDir(path string) (*Entry, error) {
   278  	irodsPath := util.GetCorrectIRODSPath(path)
   279  
   280  	return fs.getCollection(irodsPath)
   281  }
   282  
   283  // StatFile returns status of a file
   284  func (fs *FileSystem) StatFile(path string) (*Entry, error) {
   285  	irodsPath := util.GetCorrectIRODSPath(path)
   286  
   287  	return fs.getDataObject(irodsPath)
   288  }
   289  
   290  // Exists checks file/directory existence
   291  func (fs *FileSystem) Exists(path string) bool {
   292  	entry, err := fs.Stat(path)
   293  	if err != nil {
   294  		return false
   295  	}
   296  	return entry.ID > 0
   297  }
   298  
   299  // ExistsDir checks directory existence
   300  func (fs *FileSystem) ExistsDir(path string) bool {
   301  	entry, err := fs.StatDir(path)
   302  	if err != nil {
   303  		return false
   304  	}
   305  	return entry.ID > 0
   306  }
   307  
   308  // ExistsFile checks file existence
   309  func (fs *FileSystem) ExistsFile(path string) bool {
   310  	entry, err := fs.StatFile(path)
   311  	if err != nil {
   312  		return false
   313  	}
   314  	return entry.ID > 0
   315  }
   316  
   317  // List lists all file system entries under the given path
   318  func (fs *FileSystem) List(path string) ([]*Entry, error) {
   319  	irodsPath := util.GetCorrectIRODSPath(path)
   320  
   321  	collectionEntry, err := fs.getCollection(irodsPath)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  
   326  	collection := fs.getCollectionFromEntry(collectionEntry)
   327  
   328  	return fs.listEntries(collection)
   329  }
   330  
   331  // RemoveDir deletes a directory
   332  func (fs *FileSystem) RemoveDir(path string, recurse bool, force bool) error {
   333  	irodsPath := util.GetCorrectIRODSPath(path)
   334  
   335  	conn, err := fs.metaSession.AcquireConnection()
   336  	if err != nil {
   337  		return err
   338  	}
   339  	defer fs.metaSession.ReturnConnection(conn)
   340  
   341  	err = irods_fs.DeleteCollection(conn, irodsPath, recurse, force)
   342  	if err != nil {
   343  		return err
   344  	}
   345  
   346  	fs.invalidateCacheForDirRemove(irodsPath, recurse)
   347  	fs.cachePropagation.PropagateDirRemove(irodsPath)
   348  	return nil
   349  }
   350  
   351  // RemoveFile deletes a file
   352  func (fs *FileSystem) RemoveFile(path string, force bool) error {
   353  	irodsPath := util.GetCorrectIRODSPath(path)
   354  
   355  	conn, err := fs.metaSession.AcquireConnection()
   356  	if err != nil {
   357  		return err
   358  	}
   359  	defer fs.metaSession.ReturnConnection(conn)
   360  
   361  	// if file handle is opened, wg
   362  	wg := sync.WaitGroup{}
   363  	wg.Add(1)
   364  
   365  	eventHandlerID := fs.fileHandleMap.AddCloseEventHandler(irodsPath, func(path, id string, empty bool) {
   366  		if empty {
   367  			wg.Done()
   368  		}
   369  	})
   370  
   371  	defer fs.fileHandleMap.RemoveCloseEventHandler(eventHandlerID)
   372  
   373  	if util.WaitTimeout(&wg, fs.config.OperationTimeout) {
   374  		// timed out
   375  		return xerrors.Errorf("failed to remove file, there are files still opened")
   376  	}
   377  
   378  	// wait done
   379  	err = irods_fs.DeleteDataObject(conn, irodsPath, force)
   380  	if err != nil {
   381  		return err
   382  	}
   383  
   384  	fs.invalidateCacheForFileRemove(irodsPath)
   385  	fs.cachePropagation.PropagateFileRemove(irodsPath)
   386  	return nil
   387  }
   388  
   389  // RenameDir renames a dir
   390  func (fs *FileSystem) RenameDir(srcPath string, destPath string) error {
   391  	irodsSrcPath := util.GetCorrectIRODSPath(srcPath)
   392  	irodsDestPath := util.GetCorrectIRODSPath(destPath)
   393  
   394  	destDirPath := irodsDestPath
   395  	if fs.ExistsDir(irodsDestPath) {
   396  		// make full file name for dest
   397  		srcFileName := util.GetIRODSPathFileName(irodsSrcPath)
   398  		destDirPath = util.MakeIRODSPath(irodsDestPath, srcFileName)
   399  	}
   400  
   401  	return fs.RenameDirToDir(irodsSrcPath, destDirPath)
   402  }
   403  
   404  // RenameDirToDir renames a dir
   405  func (fs *FileSystem) RenameDirToDir(srcPath string, destPath string) error {
   406  	irodsSrcPath := util.GetCorrectIRODSPath(srcPath)
   407  	irodsDestPath := util.GetCorrectIRODSPath(destPath)
   408  
   409  	conn, err := fs.metaSession.AcquireConnection()
   410  	if err != nil {
   411  		return err
   412  	}
   413  	defer fs.metaSession.ReturnConnection(conn)
   414  
   415  	// preprocess
   416  	handles, err := fs.preprocessRenameFileHandleForDir(irodsSrcPath)
   417  	if err != nil {
   418  		return err
   419  	}
   420  
   421  	err = irods_fs.MoveCollection(conn, irodsSrcPath, irodsDestPath)
   422  	if err != nil {
   423  		return err
   424  	}
   425  
   426  	fs.invalidateCacheForDirRemove(irodsSrcPath, true)
   427  	fs.cachePropagation.PropagateDirRemove(irodsSrcPath)
   428  	fs.invalidateCacheForDirCreate(irodsDestPath)
   429  	fs.cachePropagation.PropagateDirCreate(irodsDestPath)
   430  
   431  	// postprocess
   432  	err = fs.postprocessRenameFileHandleForDir(handles, conn, irodsSrcPath, irodsDestPath)
   433  	if err != nil {
   434  		return err
   435  	}
   436  	return nil
   437  }
   438  
   439  // RenameFile renames a file
   440  func (fs *FileSystem) RenameFile(srcPath string, destPath string) error {
   441  	irodsSrcPath := util.GetCorrectIRODSPath(srcPath)
   442  	irodsDestPath := util.GetCorrectIRODSPath(destPath)
   443  
   444  	destFilePath := irodsDestPath
   445  	if fs.ExistsDir(irodsDestPath) {
   446  		// make full file name for dest
   447  		srcFileName := util.GetIRODSPathFileName(irodsSrcPath)
   448  		destFilePath = util.MakeIRODSPath(irodsDestPath, srcFileName)
   449  	}
   450  
   451  	return fs.RenameFileToFile(irodsSrcPath, destFilePath)
   452  }
   453  
   454  // RenameFileToFile renames a file
   455  func (fs *FileSystem) RenameFileToFile(srcPath string, destPath string) error {
   456  	irodsSrcPath := util.GetCorrectIRODSPath(srcPath)
   457  	irodsDestPath := util.GetCorrectIRODSPath(destPath)
   458  
   459  	conn, err := fs.metaSession.AcquireConnection()
   460  	if err != nil {
   461  		return err
   462  	}
   463  	defer fs.metaSession.ReturnConnection(conn)
   464  
   465  	// preprocess
   466  	handles, err := fs.preprocessRenameFileHandle(irodsSrcPath)
   467  	if err != nil {
   468  		return err
   469  	}
   470  
   471  	// rename
   472  	err = irods_fs.MoveDataObject(conn, irodsSrcPath, irodsDestPath)
   473  	if err != nil {
   474  		return err
   475  	}
   476  
   477  	fs.invalidateCacheForFileRemove(irodsSrcPath)
   478  	fs.cachePropagation.PropagateFileRemove(irodsSrcPath)
   479  	fs.invalidateCacheForFileCreate(irodsDestPath)
   480  	fs.cachePropagation.PropagateFileCreate(irodsDestPath)
   481  
   482  	// postprocess
   483  	err = fs.postprocessRenameFileHandle(handles, conn, irodsDestPath)
   484  	if err != nil {
   485  		return err
   486  	}
   487  
   488  	return nil
   489  }
   490  
   491  func (fs *FileSystem) preprocessRenameFileHandle(srcPath string) ([]*FileHandle, error) {
   492  	handles := fs.fileHandleMap.PopByPath(srcPath)
   493  	handlesLocked := []*FileHandle{}
   494  
   495  	errs := []error{}
   496  	for _, handle := range handles {
   497  		// lock handles
   498  		handle.Lock()
   499  
   500  		err := handle.preprocessRename()
   501  		if err != nil {
   502  			errs = append(errs, err)
   503  			// unlock handle
   504  			handle.Unlock()
   505  		} else {
   506  			handlesLocked = append(handlesLocked, handle)
   507  		}
   508  	}
   509  
   510  	if len(errs) > 0 {
   511  		return handlesLocked, errs[0]
   512  	}
   513  	return handlesLocked, nil
   514  }
   515  
   516  func (fs *FileSystem) preprocessRenameFileHandleForDir(srcPath string) ([]*FileHandle, error) {
   517  	paths := fs.fileHandleMap.ListPathsInDir(srcPath)
   518  
   519  	errs := []error{}
   520  	handles := []*FileHandle{}
   521  	for _, path := range paths {
   522  		handlesForPath, err := fs.preprocessRenameFileHandle(path)
   523  		if err != nil {
   524  			errs = append(errs, err)
   525  		} else {
   526  			handles = append(handles, handlesForPath...)
   527  		}
   528  	}
   529  
   530  	if len(errs) > 0 {
   531  		return handles, errs[0]
   532  	}
   533  	return handles, nil
   534  }
   535  
   536  func (fs *FileSystem) postprocessRenameFileHandle(handles []*FileHandle, conn *connection.IRODSConnection, destPath string) error {
   537  	newEntry, err := fs.getDataObjectWithConnection(conn, destPath)
   538  	if err != nil {
   539  		return err
   540  	}
   541  
   542  	errs := []error{}
   543  	for _, handle := range handles {
   544  		err := handle.postprocessRename(destPath, newEntry)
   545  		if err != nil {
   546  			errs = append(errs, err)
   547  		}
   548  
   549  		handle.Unlock()
   550  		fs.fileHandleMap.Add(handle)
   551  	}
   552  
   553  	if len(errs) > 0 {
   554  		return errs[0]
   555  	}
   556  	return nil
   557  }
   558  
   559  func (fs *FileSystem) postprocessRenameFileHandleForDir(handles []*FileHandle, conn *connection.IRODSConnection, srcPath string, destPath string) error {
   560  	errs := []error{}
   561  
   562  	// map (original path => new Entry)
   563  	entryMap := map[string]*Entry{}
   564  	for _, handle := range handles {
   565  		if _, ok := entryMap[handle.entry.Path]; !ok {
   566  			// mapping not exist
   567  			// make full destPath
   568  			relPath, err := util.GetRelativeIRODSPath(srcPath, handle.entry.Path)
   569  			if err != nil {
   570  				errs = append(errs, err)
   571  			} else {
   572  				destFullPath := path.Join(destPath, relPath)
   573  				newEntry, err := fs.getDataObjectWithConnection(conn, destFullPath)
   574  				if err != nil {
   575  					errs = append(errs, err)
   576  				} else {
   577  					entryMap[handle.entry.Path] = newEntry
   578  				}
   579  			}
   580  		}
   581  	}
   582  
   583  	if len(errs) > 0 {
   584  		for _, handle := range handles {
   585  			handle.Unlock()
   586  		}
   587  		return errs[0]
   588  	}
   589  
   590  	for _, handle := range handles {
   591  		newEntry := entryMap[handle.entry.Path]
   592  		err := handle.postprocessRename(newEntry.Path, newEntry)
   593  		if err != nil {
   594  			errs = append(errs, err)
   595  		}
   596  
   597  		handle.Unlock()
   598  		fs.fileHandleMap.Add(handle)
   599  	}
   600  
   601  	if len(errs) > 0 {
   602  		return errs[0]
   603  	}
   604  	return nil
   605  }
   606  
   607  // MakeDir creates a directory
   608  func (fs *FileSystem) MakeDir(path string, recurse bool) error {
   609  	irodsPath := util.GetCorrectIRODSPath(path)
   610  
   611  	conn, err := fs.metaSession.AcquireConnection()
   612  	if err != nil {
   613  		return err
   614  	}
   615  	defer fs.metaSession.ReturnConnection(conn)
   616  
   617  	dirEntry, err := fs.StatDir(path)
   618  	if err == nil {
   619  		if dirEntry.ID > 0 {
   620  			// already exists
   621  			if recurse {
   622  				return nil
   623  			}
   624  			return types.NewFileAlreadyExistError(path)
   625  		}
   626  	}
   627  
   628  	err = irods_fs.CreateCollection(conn, irodsPath, recurse)
   629  	if err != nil {
   630  		return err
   631  	}
   632  
   633  	fs.invalidateCacheForDirCreate(irodsPath)
   634  	fs.cachePropagation.PropagateDirCreate(irodsPath)
   635  	fs.cache.AddDirCache(irodsPath, []string{})
   636  	return nil
   637  }
   638  
   639  // CopyFile copies a file
   640  func (fs *FileSystem) CopyFile(srcPath string, destPath string, force bool) error {
   641  	irodsSrcPath := util.GetCorrectIRODSPath(srcPath)
   642  	irodsDestPath := util.GetCorrectIRODSPath(destPath)
   643  
   644  	destFilePath := irodsDestPath
   645  	if fs.ExistsDir(irodsDestPath) {
   646  		// make full file name for dest
   647  		srcFileName := util.GetIRODSPathFileName(irodsSrcPath)
   648  		destFilePath = util.MakeIRODSPath(irodsDestPath, srcFileName)
   649  	}
   650  
   651  	return fs.CopyFileToFile(irodsSrcPath, destFilePath, force)
   652  }
   653  
   654  // CopyFileToFile copies a file
   655  func (fs *FileSystem) CopyFileToFile(srcPath string, destPath string, force bool) error {
   656  	irodsSrcPath := util.GetCorrectIRODSPath(srcPath)
   657  	irodsDestPath := util.GetCorrectIRODSPath(destPath)
   658  
   659  	conn, err := fs.metaSession.AcquireConnection()
   660  	if err != nil {
   661  		return err
   662  	}
   663  	defer fs.metaSession.ReturnConnection(conn)
   664  
   665  	err = irods_fs.CopyDataObject(conn, irodsSrcPath, irodsDestPath, force)
   666  	if err != nil {
   667  		return err
   668  	}
   669  
   670  	fs.invalidateCacheForFileCreate(irodsDestPath)
   671  	fs.cachePropagation.PropagateFileCreate(irodsDestPath)
   672  	return nil
   673  }
   674  
   675  // TruncateFile truncates a file
   676  func (fs *FileSystem) TruncateFile(path string, size int64) error {
   677  	irodsPath := util.GetCorrectIRODSPath(path)
   678  
   679  	if size < 0 {
   680  		size = 0
   681  	}
   682  
   683  	conn, err := fs.metaSession.AcquireConnection()
   684  	if err != nil {
   685  		return err
   686  	}
   687  	defer fs.metaSession.ReturnConnection(conn)
   688  
   689  	err = irods_fs.TruncateDataObject(conn, irodsPath, size)
   690  	if err != nil {
   691  		return err
   692  	}
   693  
   694  	fs.invalidateCacheForFileUpdate(irodsPath)
   695  	fs.cachePropagation.PropagateFileUpdate(irodsPath)
   696  	return nil
   697  }
   698  
   699  // ReplicateFile replicates a file
   700  func (fs *FileSystem) ReplicateFile(path string, resource string, update bool) error {
   701  	irodsPath := util.GetCorrectIRODSPath(path)
   702  
   703  	conn, err := fs.metaSession.AcquireConnection()
   704  	if err != nil {
   705  		return err
   706  	}
   707  	defer fs.metaSession.ReturnConnection(conn)
   708  
   709  	err = irods_fs.ReplicateDataObject(conn, irodsPath, resource, update, false)
   710  	if err != nil {
   711  		return err
   712  	}
   713  
   714  	fs.invalidateCacheForFileUpdate(irodsPath)
   715  	fs.cachePropagation.PropagateFileUpdate(irodsPath)
   716  	return nil
   717  }
   718  
   719  // OpenFile opens an existing file for read/write
   720  func (fs *FileSystem) OpenFile(path string, resource string, mode string) (*FileHandle, error) {
   721  	irodsPath := util.GetCorrectIRODSPath(path)
   722  
   723  	conn, err := fs.ioSession.AcquireConnection()
   724  	if err != nil {
   725  		return nil, err
   726  	}
   727  
   728  	handle, offset, err := irods_fs.OpenDataObject(conn, irodsPath, resource, mode)
   729  	if err != nil {
   730  		fs.ioSession.ReturnConnection(conn)
   731  		return nil, err
   732  	}
   733  
   734  	var entry *Entry = nil
   735  	openMode := types.FileOpenMode(mode)
   736  	if openMode.IsOpeningExisting() {
   737  		// file may exists
   738  		entryExisting, err := fs.getDataObjectWithConnection(conn, irodsPath)
   739  		if err == nil {
   740  			entry = entryExisting
   741  		}
   742  	}
   743  
   744  	if entry == nil {
   745  		// create a new
   746  		entry = &Entry{
   747  			ID:                0,
   748  			Type:              FileEntry,
   749  			Name:              util.GetIRODSPathFileName(irodsPath),
   750  			Path:              irodsPath,
   751  			Owner:             fs.account.ClientUser,
   752  			Size:              0,
   753  			CreateTime:        time.Now(),
   754  			ModifyTime:        time.Now(),
   755  			CheckSumAlgorithm: "",
   756  			CheckSum:          "",
   757  		}
   758  	}
   759  
   760  	// do not return connection here
   761  	fileHandle := &FileHandle{
   762  		id:              xid.New().String(),
   763  		filesystem:      fs,
   764  		connection:      conn,
   765  		irodsFileHandle: handle,
   766  		entry:           entry,
   767  		offset:          offset,
   768  		openMode:        types.FileOpenMode(mode),
   769  	}
   770  
   771  	fs.fileHandleMap.Add(fileHandle)
   772  	return fileHandle, nil
   773  }
   774  
   775  // CreateFile opens a new file for write
   776  func (fs *FileSystem) CreateFile(path string, resource string, mode string) (*FileHandle, error) {
   777  	irodsPath := util.GetCorrectIRODSPath(path)
   778  
   779  	conn, err := fs.ioSession.AcquireConnection()
   780  	if err != nil {
   781  		return nil, err
   782  	}
   783  
   784  	// create
   785  	handle, err := irods_fs.CreateDataObject(conn, irodsPath, resource, mode, true)
   786  	if err != nil {
   787  		fs.ioSession.ReturnConnection(conn)
   788  		return nil, err
   789  	}
   790  
   791  	// close - this is required to let other processes see the file existence
   792  	err = irods_fs.CloseDataObject(conn, handle)
   793  	if err != nil {
   794  		fs.ioSession.ReturnConnection(conn)
   795  		return nil, err
   796  	}
   797  
   798  	entry, err := fs.getDataObjectWithConnectionNoCache(conn, irodsPath)
   799  	if err != nil {
   800  		fs.ioSession.ReturnConnection(conn)
   801  		return nil, err
   802  	}
   803  
   804  	// re-open
   805  	handle, offset, err := irods_fs.OpenDataObject(conn, irodsPath, resource, mode)
   806  	if err != nil {
   807  		fs.ioSession.ReturnConnection(conn)
   808  		return nil, err
   809  	}
   810  
   811  	// do not return connection here
   812  	fileHandle := &FileHandle{
   813  		id:              xid.New().String(),
   814  		filesystem:      fs,
   815  		connection:      conn,
   816  		irodsFileHandle: handle,
   817  		entry:           entry,
   818  		offset:          offset,
   819  		openMode:        types.FileOpenMode(mode),
   820  	}
   821  
   822  	fs.fileHandleMap.Add(fileHandle)
   823  	fs.invalidateCacheForFileCreate(irodsPath)
   824  	fs.cachePropagation.PropagateFileCreate(irodsPath)
   825  
   826  	return fileHandle, nil
   827  }
   828  
   829  // getCollectionNoCache returns collection entry
   830  func (fs *FileSystem) getCollectionNoCache(path string) (*Entry, error) {
   831  	// retrieve it and add it to cache
   832  	conn, err := fs.metaSession.AcquireConnection()
   833  	if err != nil {
   834  		return nil, err
   835  	}
   836  	defer fs.metaSession.ReturnConnection(conn)
   837  
   838  	collection, err := irods_fs.GetCollection(conn, path)
   839  	if err != nil {
   840  		return nil, err
   841  	}
   842  
   843  	if collection.ID > 0 {
   844  		entry := fs.getEntryFromCollection(collection)
   845  
   846  		// cache it
   847  		fs.cache.RemoveNegativeEntryCache(path)
   848  		fs.cache.AddEntryCache(entry)
   849  		return entry, nil
   850  	}
   851  
   852  	return nil, xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
   853  }
   854  
   855  // getCollection returns collection entry
   856  func (fs *FileSystem) getCollection(path string) (*Entry, error) {
   857  	if fs.cache.HasNegativeEntryCache(path) {
   858  		return nil, xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
   859  	}
   860  
   861  	// check cache first
   862  	cachedEntry := fs.cache.GetEntryCache(path)
   863  	if cachedEntry != nil && cachedEntry.Type == DirectoryEntry {
   864  		return cachedEntry, nil
   865  	}
   866  
   867  	// otherwise, retrieve it and add it to cache
   868  	return fs.getCollectionNoCache(path)
   869  }
   870  
   871  // getCollectionFromEntry returns collection from entry
   872  func (fs *FileSystem) getCollectionFromEntry(entry *Entry) *types.IRODSCollection {
   873  	return &types.IRODSCollection{
   874  		ID:         entry.ID,
   875  		Path:       entry.Path,
   876  		Name:       entry.Name,
   877  		Owner:      entry.Owner,
   878  		CreateTime: entry.CreateTime,
   879  		ModifyTime: entry.ModifyTime,
   880  	}
   881  }
   882  
   883  func (fs *FileSystem) getEntryFromCollection(collection *types.IRODSCollection) *Entry {
   884  	return &Entry{
   885  		ID:                collection.ID,
   886  		Type:              DirectoryEntry,
   887  		Name:              collection.Name,
   888  		Path:              collection.Path,
   889  		Owner:             collection.Owner,
   890  		Size:              0,
   891  		DataType:          "",
   892  		CreateTime:        collection.CreateTime,
   893  		ModifyTime:        collection.ModifyTime,
   894  		CheckSumAlgorithm: "",
   895  		CheckSum:          "",
   896  	}
   897  }
   898  
   899  func (fs *FileSystem) getEntryFromDataObject(dataobject *types.IRODSDataObject) *Entry {
   900  	checksum := dataobject.Replicas[0].Checksum
   901  	checksumAlgorithm := ""
   902  	checksumString := ""
   903  
   904  	if checksum != nil {
   905  		checksumAlgorithm = checksum.GetChecksumAlgorithm()
   906  		checksumString = checksum.GetChecksumString()
   907  	}
   908  
   909  	return &Entry{
   910  		ID:                dataobject.ID,
   911  		Type:              FileEntry,
   912  		Name:              dataobject.Name,
   913  		Path:              dataobject.Path,
   914  		Owner:             dataobject.Replicas[0].Owner,
   915  		Size:              dataobject.Size,
   916  		DataType:          dataobject.DataType,
   917  		CreateTime:        dataobject.Replicas[0].CreateTime,
   918  		ModifyTime:        dataobject.Replicas[0].ModifyTime,
   919  		CheckSumAlgorithm: checksumAlgorithm,
   920  		CheckSum:          checksumString,
   921  	}
   922  }
   923  
   924  // listEntries lists entries in a collection
   925  func (fs *FileSystem) listEntries(collection *types.IRODSCollection) ([]*Entry, error) {
   926  	// check cache first
   927  	cachedEntries := []*Entry{}
   928  	useCached := false
   929  
   930  	cachedDirEntryPaths := fs.cache.GetDirCache(collection.Path)
   931  	if cachedDirEntryPaths != nil {
   932  		useCached = true
   933  		for _, cachedDirEntryPath := range cachedDirEntryPaths {
   934  			cachedEntry := fs.cache.GetEntryCache(cachedDirEntryPath)
   935  			if cachedEntry != nil {
   936  				cachedEntries = append(cachedEntries, cachedEntry)
   937  			} else {
   938  				useCached = false
   939  			}
   940  		}
   941  	}
   942  
   943  	if useCached {
   944  		// remove from nagative entry cache
   945  		for _, cachedEntry := range cachedEntries {
   946  			fs.cache.RemoveNegativeEntryCache(cachedEntry.Path)
   947  		}
   948  		return cachedEntries, nil
   949  	}
   950  
   951  	// otherwise, retrieve it and add it to cache
   952  	conn, err := fs.metaSession.AcquireConnection()
   953  	if err != nil {
   954  		return nil, err
   955  	}
   956  	defer fs.metaSession.ReturnConnection(conn)
   957  
   958  	collections, err := irods_fs.ListSubCollections(conn, collection.Path)
   959  	if err != nil {
   960  		return nil, err
   961  	}
   962  
   963  	entries := []*Entry{}
   964  
   965  	for _, coll := range collections {
   966  		entry := fs.getEntryFromCollection(coll)
   967  		entries = append(entries, entry)
   968  
   969  		// cache it
   970  		fs.cache.RemoveNegativeEntryCache(entry.Path)
   971  		fs.cache.AddEntryCache(entry)
   972  	}
   973  
   974  	dataobjects, err := irods_fs.ListDataObjectsMasterReplica(conn, collection)
   975  	if err != nil {
   976  		return nil, err
   977  	}
   978  
   979  	for _, dataobject := range dataobjects {
   980  		if len(dataobject.Replicas) == 0 {
   981  			continue
   982  		}
   983  
   984  		entry := fs.getEntryFromDataObject(dataobject)
   985  		entries = append(entries, entry)
   986  
   987  		// cache it
   988  		fs.cache.RemoveNegativeEntryCache(entry.Path)
   989  		fs.cache.AddEntryCache(entry)
   990  	}
   991  
   992  	// cache dir entries
   993  	dirEntryPaths := []string{}
   994  	for _, entry := range entries {
   995  		dirEntryPaths = append(dirEntryPaths, entry.Path)
   996  	}
   997  	fs.cache.AddDirCache(collection.Path, dirEntryPaths)
   998  
   999  	return entries, nil
  1000  }
  1001  
  1002  // getDataObjectWithConnectionNoCache returns an entry for data object
  1003  func (fs *FileSystem) getDataObjectWithConnectionNoCache(conn *connection.IRODSConnection, path string) (*Entry, error) {
  1004  	// retrieve it and add it to cache
  1005  	collectionEntry, err := fs.getCollection(util.GetIRODSPathDirname(path))
  1006  	if err != nil {
  1007  		return nil, err
  1008  	}
  1009  
  1010  	collection := fs.getCollectionFromEntry(collectionEntry)
  1011  
  1012  	dataobject, err := irods_fs.GetDataObjectMasterReplica(conn, collection, util.GetIRODSPathFileName(path))
  1013  	if err != nil {
  1014  		return nil, err
  1015  	}
  1016  
  1017  	if dataobject.ID > 0 {
  1018  		entry := fs.getEntryFromDataObject(dataobject)
  1019  
  1020  		// cache it
  1021  		fs.cache.RemoveNegativeEntryCache(path)
  1022  		fs.cache.AddEntryCache(entry)
  1023  		return entry, nil
  1024  	}
  1025  
  1026  	return nil, xerrors.Errorf("failed to find the data object for path %s: %w", path, types.NewFileNotFoundError(path))
  1027  }
  1028  
  1029  // getDataObjectWithConnection returns an entry for data object
  1030  func (fs *FileSystem) getDataObjectWithConnection(conn *connection.IRODSConnection, path string) (*Entry, error) {
  1031  	if fs.cache.HasNegativeEntryCache(path) {
  1032  		return nil, xerrors.Errorf("failed to find the data object for path %s: %w", path, types.NewFileNotFoundError(path))
  1033  	}
  1034  
  1035  	// check cache first
  1036  	cachedEntry := fs.cache.GetEntryCache(path)
  1037  	if cachedEntry != nil && cachedEntry.Type == FileEntry {
  1038  		return cachedEntry, nil
  1039  	}
  1040  
  1041  	// otherwise, retrieve it and add it to cache
  1042  	return fs.getDataObjectWithConnectionNoCache(conn, path)
  1043  }
  1044  
  1045  // getDataObjectNoCache returns an entry for data object
  1046  func (fs *FileSystem) getDataObjectNoCache(path string) (*Entry, error) {
  1047  	// retrieve it and add it to cache
  1048  	collectionEntry, err := fs.getCollection(util.GetIRODSPathDirname(path))
  1049  	if err != nil {
  1050  		return nil, err
  1051  	}
  1052  
  1053  	collection := fs.getCollectionFromEntry(collectionEntry)
  1054  
  1055  	conn, err := fs.metaSession.AcquireConnection()
  1056  	if err != nil {
  1057  		return nil, err
  1058  	}
  1059  	defer fs.metaSession.ReturnConnection(conn)
  1060  
  1061  	dataobject, err := irods_fs.GetDataObjectMasterReplica(conn, collection, util.GetIRODSPathFileName(path))
  1062  	if err != nil {
  1063  		return nil, err
  1064  	}
  1065  
  1066  	if dataobject.ID > 0 {
  1067  		entry := fs.getEntryFromDataObject(dataobject)
  1068  
  1069  		// cache it
  1070  		fs.cache.RemoveNegativeEntryCache(path)
  1071  		fs.cache.AddEntryCache(entry)
  1072  		return entry, nil
  1073  	}
  1074  
  1075  	return nil, xerrors.Errorf("failed to find the data object for path %s: %w", path, types.NewFileNotFoundError(path))
  1076  }
  1077  
  1078  // getDataObject returns an entry for data object
  1079  func (fs *FileSystem) getDataObject(path string) (*Entry, error) {
  1080  	if fs.cache.HasNegativeEntryCache(path) {
  1081  		return nil, xerrors.Errorf("failed to find the data object for path %s: %w", path, types.NewFileNotFoundError(path))
  1082  	}
  1083  
  1084  	// check cache first
  1085  	cachedEntry := fs.cache.GetEntryCache(path)
  1086  	if cachedEntry != nil && cachedEntry.Type == FileEntry {
  1087  		return cachedEntry, nil
  1088  	}
  1089  
  1090  	// otherwise, retrieve it and add it to cache
  1091  	return fs.getDataObjectNoCache(path)
  1092  }