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

     1  package fs
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/cyverse/go-irodsclient/irods/connection"
     8  	irods_fs "github.com/cyverse/go-irodsclient/irods/fs"
     9  	"github.com/cyverse/go-irodsclient/irods/types"
    10  	"golang.org/x/xerrors"
    11  )
    12  
    13  // FileHandle is a handle for a file opened
    14  type FileHandle struct {
    15  	id                  string
    16  	filesystem          *FileSystem
    17  	connection          *connection.IRODSConnection
    18  	irodsFileHandle     *types.IRODSFileHandle
    19  	irodsFileLockHandle *types.IRODSFileLockHandle
    20  	entry               *Entry
    21  	offset              int64
    22  	openMode            types.FileOpenMode
    23  	mutex               sync.Mutex
    24  }
    25  
    26  // GetID returns ID
    27  func (handle *FileHandle) GetID() string {
    28  	return handle.id
    29  }
    30  
    31  // Lock locks the handle
    32  func (handle *FileHandle) Lock() {
    33  	handle.mutex.Lock()
    34  }
    35  
    36  // Unlock unlocks the handle
    37  func (handle *FileHandle) Unlock() {
    38  	handle.mutex.Unlock()
    39  }
    40  
    41  // GetOffset returns current offset
    42  func (handle *FileHandle) GetOffset() int64 {
    43  	handle.mutex.Lock()
    44  	defer handle.mutex.Unlock()
    45  
    46  	return handle.offset
    47  }
    48  
    49  // GetOpenMode returns file open mode
    50  func (handle *FileHandle) GetOpenMode() types.FileOpenMode {
    51  	return handle.openMode
    52  }
    53  
    54  // IsReadMode returns true if file is opened with read mode
    55  func (handle *FileHandle) IsReadMode() bool {
    56  	return handle.openMode.IsRead()
    57  }
    58  
    59  // IsReadOnlyMode returns true if file is opened with read only mode
    60  func (handle *FileHandle) IsReadOnlyMode() bool {
    61  	return handle.openMode.IsReadOnly()
    62  }
    63  
    64  // IsWriteMode returns true if file is opened with write mode
    65  func (handle *FileHandle) IsWriteMode() bool {
    66  	return handle.openMode.IsWrite()
    67  }
    68  
    69  // IsWriteOnlyMode returns true if file is opened with write only mode
    70  func (handle *FileHandle) IsWriteOnlyMode() bool {
    71  	return handle.openMode.IsWriteOnly()
    72  }
    73  
    74  // GetIRODSFileHandle returns iRODS File Handle
    75  func (handle *FileHandle) GetIRODSFileHandle() *types.IRODSFileHandle {
    76  	return handle.irodsFileHandle
    77  }
    78  
    79  // GetEntry returns Entry info
    80  func (handle *FileHandle) GetEntry() *Entry {
    81  	return handle.entry
    82  }
    83  
    84  // Close closes the file
    85  func (handle *FileHandle) Close() error {
    86  	handle.mutex.Lock()
    87  	defer handle.mutex.Unlock()
    88  
    89  	if handle.irodsFileLockHandle != nil {
    90  		// unlock if locked
    91  		err := irods_fs.UnlockDataObject(handle.connection, handle.irodsFileLockHandle)
    92  		if err != nil {
    93  			return err
    94  		}
    95  
    96  		handle.irodsFileLockHandle = nil
    97  	}
    98  
    99  	defer handle.filesystem.ioSession.ReturnConnection(handle.connection)
   100  
   101  	err := irods_fs.CloseDataObject(handle.connection, handle.irodsFileHandle)
   102  	handle.filesystem.fileHandleMap.Remove(handle.id)
   103  
   104  	if handle.IsWriteMode() {
   105  		handle.filesystem.invalidateCacheForFileUpdate(handle.entry.Path)
   106  		handle.filesystem.cachePropagation.PropagateFileUpdate(handle.entry.Path)
   107  	}
   108  
   109  	return err
   110  }
   111  
   112  // Seek moves file pointer
   113  func (handle *FileHandle) Seek(offset int64, whence int) (int64, error) {
   114  	handle.mutex.Lock()
   115  	defer handle.mutex.Unlock()
   116  
   117  	newOffset, err := irods_fs.SeekDataObject(handle.connection, handle.irodsFileHandle, offset, types.Whence(whence))
   118  	if err != nil {
   119  		return newOffset, err
   120  	}
   121  
   122  	handle.offset = newOffset
   123  	return newOffset, nil
   124  }
   125  
   126  // Truncate truncates the file
   127  func (handle *FileHandle) Truncate(size int64) error {
   128  	handle.mutex.Lock()
   129  	defer handle.mutex.Unlock()
   130  
   131  	err := irods_fs.TruncateDataObjectHandle(handle.connection, handle.irodsFileHandle, size)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	return nil
   137  }
   138  
   139  // Read reads the file, implements io.Reader.Read
   140  func (handle *FileHandle) Read(buffer []byte) (int, error) {
   141  	handle.mutex.Lock()
   142  	defer handle.mutex.Unlock()
   143  
   144  	if !handle.IsReadMode() {
   145  		return 0, xerrors.Errorf("file is opened with %s mode", handle.openMode)
   146  	}
   147  
   148  	readLen, err := irods_fs.ReadDataObject(handle.connection, handle.irodsFileHandle, buffer)
   149  	if readLen > 0 {
   150  		handle.offset += int64(readLen)
   151  	}
   152  
   153  	// it is possible to return readLen + EOF
   154  	return readLen, err
   155  }
   156  
   157  // ReadAt reads data from given offset
   158  func (handle *FileHandle) ReadAt(buffer []byte, offset int64) (int, error) {
   159  	handle.mutex.Lock()
   160  	defer handle.mutex.Unlock()
   161  
   162  	if !handle.IsReadMode() {
   163  		return 0, xerrors.Errorf("file is opened with %s mode", handle.openMode)
   164  	}
   165  
   166  	if handle.offset != offset {
   167  		newOffset, err := irods_fs.SeekDataObject(handle.connection, handle.irodsFileHandle, offset, types.SeekSet)
   168  		if err != nil {
   169  			return 0, err
   170  		}
   171  
   172  		handle.offset = newOffset
   173  
   174  		if newOffset != offset {
   175  			return 0, xerrors.Errorf("failed to seek to %d", offset)
   176  		}
   177  	}
   178  
   179  	readLen, err := irods_fs.ReadDataObject(handle.connection, handle.irodsFileHandle, buffer)
   180  	if readLen > 0 {
   181  		handle.offset += int64(readLen)
   182  	}
   183  
   184  	// it is possible to return readLen + EOF
   185  	return readLen, err
   186  }
   187  
   188  // Write writes the file
   189  func (handle *FileHandle) Write(data []byte) (int, error) {
   190  	handle.mutex.Lock()
   191  	defer handle.mutex.Unlock()
   192  
   193  	if !handle.IsWriteMode() {
   194  		return 0, xerrors.Errorf("file is opened with %s mode", handle.openMode)
   195  	}
   196  
   197  	err := irods_fs.WriteDataObject(handle.connection, handle.irodsFileHandle, data)
   198  	if err != nil {
   199  		return 0, err
   200  	}
   201  
   202  	handle.offset += int64(len(data))
   203  
   204  	// update
   205  	if handle.entry.Size < handle.offset+int64(len(data)) {
   206  		handle.entry.Size = handle.offset + int64(len(data))
   207  	}
   208  
   209  	return len(data), nil
   210  }
   211  
   212  // WriteAt writes the file to given offset
   213  func (handle *FileHandle) WriteAt(data []byte, offset int64) (int, error) {
   214  	handle.mutex.Lock()
   215  	defer handle.mutex.Unlock()
   216  
   217  	if !handle.IsWriteMode() {
   218  		return 0, xerrors.Errorf("file is opened with %s mode", handle.openMode)
   219  	}
   220  
   221  	if handle.offset != offset {
   222  		newOffset, err := irods_fs.SeekDataObject(handle.connection, handle.irodsFileHandle, offset, types.SeekSet)
   223  		if err != nil {
   224  			return 0, err
   225  		}
   226  
   227  		handle.offset = newOffset
   228  
   229  		if newOffset != offset {
   230  			return 0, xerrors.Errorf("failed to seek to %d", offset)
   231  		}
   232  	}
   233  
   234  	err := irods_fs.WriteDataObject(handle.connection, handle.irodsFileHandle, data)
   235  	if err != nil {
   236  		return 0, err
   237  	}
   238  
   239  	handle.offset += int64(len(data))
   240  
   241  	// update
   242  	if handle.entry.Size < handle.offset+int64(len(data)) {
   243  		handle.entry.Size = handle.offset + int64(len(data))
   244  	}
   245  
   246  	return len(data), nil
   247  }
   248  
   249  // LockDataObject locks data object with write lock (exclusive)
   250  func (handle *FileHandle) LockDataObject(wait bool) error {
   251  	handle.mutex.Lock()
   252  	defer handle.mutex.Unlock()
   253  
   254  	lockType := types.DataObjectLockTypeWrite
   255  	lockCommand := types.DataObjectLockCommandSetLock
   256  	if wait {
   257  		lockCommand = types.DataObjectLockCommandSetLockWait
   258  	}
   259  
   260  	fileLockHandle, err := irods_fs.LockDataObject(handle.connection, handle.irodsFileHandle.Path, lockType, lockCommand)
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	handle.irodsFileLockHandle = fileLockHandle
   266  
   267  	return nil
   268  }
   269  
   270  // RLockDataObject locks data object with read lock
   271  func (handle *FileHandle) RLockDataObject(wait bool) error {
   272  	handle.mutex.Lock()
   273  	defer handle.mutex.Unlock()
   274  
   275  	lockType := types.DataObjectLockTypeRead
   276  	lockCommand := types.DataObjectLockCommandSetLock
   277  	if wait {
   278  		lockCommand = types.DataObjectLockCommandSetLockWait
   279  	}
   280  
   281  	fileLockHandle, err := irods_fs.LockDataObject(handle.connection, handle.irodsFileHandle.Path, lockType, lockCommand)
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	handle.irodsFileLockHandle = fileLockHandle
   287  
   288  	return nil
   289  }
   290  
   291  // UnlockDataObject unlocks data object
   292  func (handle *FileHandle) UnlockDataObject() error {
   293  	handle.mutex.Lock()
   294  	defer handle.mutex.Unlock()
   295  
   296  	if handle.irodsFileLockHandle != nil {
   297  		err := irods_fs.UnlockDataObject(handle.connection, handle.irodsFileLockHandle)
   298  		if err != nil {
   299  			return err
   300  		}
   301  
   302  		handle.irodsFileLockHandle = nil
   303  	}
   304  
   305  	return nil
   306  }
   307  
   308  // preprocessRename should be called before the file is renamed
   309  func (handle *FileHandle) preprocessRename() error {
   310  	// first, we need to close the file
   311  	err := irods_fs.CloseDataObject(handle.connection, handle.irodsFileHandle)
   312  
   313  	if handle.IsWriteMode() {
   314  		handle.filesystem.invalidateCacheForFileUpdate(handle.entry.Path)
   315  		handle.filesystem.cachePropagation.PropagateFileUpdate(handle.entry.Path)
   316  	}
   317  
   318  	return err
   319  }
   320  
   321  // postprocessRename should be called after the file is renamed
   322  func (handle *FileHandle) postprocessRename(newPath string, newEntry *Entry) error {
   323  	// apply path change
   324  	newOpenMode := types.FileOpenModeReadWrite
   325  	switch handle.openMode {
   326  	case types.FileOpenModeReadOnly:
   327  		newOpenMode = handle.openMode
   328  	case types.FileOpenModeReadWrite:
   329  		newOpenMode = handle.openMode
   330  	case types.FileOpenModeWriteOnly:
   331  		newOpenMode = handle.openMode
   332  	case types.FileOpenModeWriteTruncate:
   333  		newOpenMode = types.FileOpenModeWriteOnly
   334  	case types.FileOpenModeAppend:
   335  		newOpenMode = handle.openMode
   336  	case types.FileOpenModeReadAppend:
   337  		newOpenMode = handle.openMode
   338  	}
   339  
   340  	// reopen
   341  	newHandle, offset, err := irods_fs.OpenDataObject(handle.connection, newPath, handle.irodsFileHandle.Resource, string(newOpenMode))
   342  	if err != nil {
   343  		return err
   344  	}
   345  
   346  	// seek
   347  	if offset != handle.offset {
   348  		newOffset, err := irods_fs.SeekDataObject(handle.connection, newHandle, handle.offset, types.SeekSet)
   349  		if err != nil {
   350  			return err
   351  		}
   352  
   353  		if handle.offset != newOffset {
   354  			return xerrors.Errorf("failed to seek to %d", handle.offset)
   355  		}
   356  	}
   357  
   358  	handle.irodsFileHandle = newHandle
   359  	handle.entry = newEntry
   360  	handle.openMode = newOpenMode
   361  	return nil
   362  }
   363  
   364  // ToString stringifies the object
   365  func (handle *FileHandle) ToString() string {
   366  	return fmt.Sprintf("<FileHandle %d %s %s %s>", handle.entry.ID, handle.entry.Type, handle.entry.Name, handle.openMode)
   367  }