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 }