gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/filesystem/filenode.go (about) 1 package filesystem 2 3 import ( 4 "math" 5 "os" 6 "path/filepath" 7 8 "gitlab.com/NebulousLabs/errors" 9 "gitlab.com/SkynetLabs/skyd/build" 10 "gitlab.com/SkynetLabs/skyd/skymodules" 11 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siafile" 12 "go.sia.tech/siad/crypto" 13 "go.sia.tech/siad/types" 14 ) 15 16 type ( 17 // FileNode is a node which references a SiaFile. 18 FileNode struct { 19 node 20 closed bool 21 22 *siafile.SiaFile 23 } 24 ) 25 26 // AddPiece wraps siafile.AddPiece to guarantee that it's not called when the 27 // fileNode was already closed. 28 func (n *FileNode) AddPiece(pk types.SiaPublicKey, chunkIndex, pieceIndex uint64, merkleRoot crypto.Hash) (err error) { 29 n.mu.Lock() 30 defer n.mu.Unlock() 31 if n.closed { 32 err := errors.New("AddPiece called on close FileNode") 33 build.Critical(err) 34 return err 35 } 36 return n.SiaFile.AddPiece(pk, chunkIndex, pieceIndex, merkleRoot) 37 } 38 39 // close closes the file and removes it from the parent if it was the last open 40 // instance. 41 // NOTE: If the file has a parent, it needs to be already locked when this is 42 // called. 43 func (n *FileNode) close() { 44 // Mark node as closed and sanity check that it hasn't been closed before. 45 if n.closed { 46 build.Critical("close called multiple times on same FileNode") 47 } 48 n.closed = true 49 50 // Call common close method. 51 n.node.closeNode() 52 53 // Remove node from parent if the current thread was the last one. 54 parent := n.parent 55 if parent != nil && len(n.threads) == 0 { 56 parent.removeFile(n) 57 } 58 } 59 60 // close closes the file and removes it from the parent if it was the last open 61 // instance. 62 // NOTE: If the file has a parent, it needs to be already locked when this is 63 // called. 64 func (n *FileNode) managedClose() { 65 n.node.mu.Lock() 66 defer n.node.mu.Unlock() 67 n.close() 68 } 69 70 // Close calls close on the FileNode and also removes the FileNode from its 71 // parent if it's no longer being used and if it doesn't have any children which 72 // are currently in use. This happens iteratively for all parent as long as 73 // removing a child resulted in them not having any children left. 74 func (n *FileNode) Close() error { 75 // If a parent exists, we need to lock it while closing a child. 76 parent := n.node.managedLockWithParent() 77 78 // close the node. 79 n.close() 80 81 // Unlock child and parent. 82 n.node.mu.Unlock() 83 if parent != nil { 84 parent.node.mu.Unlock() 85 // Check if the parent needs to be removed from its parent too. 86 parent.managedTryRemoveFromParentsIteratively() 87 } 88 return nil 89 } 90 91 // Copy copies a file node and returns the copy. 92 func (n *FileNode) Copy() *FileNode { 93 return n.managedCopy() 94 } 95 96 // managedCopy copies a file node and returns the copy. 97 func (n *FileNode) managedCopy() *FileNode { 98 n.node.mu.Lock() 99 defer n.node.mu.Unlock() 100 newNode := *n 101 newNode.closed = false 102 newNode.threadUID = newThreadUID() 103 newNode.threads[newNode.threadUID] = struct{}{} 104 return &newNode 105 } 106 107 // Delete deletes the fNode's underlying file from disk. 108 func (n *FileNode) managedDelete() error { 109 n.node.mu.Lock() 110 defer n.node.mu.Unlock() 111 return n.SiaFile.Delete() 112 } 113 114 // managedMode returns the underlying file's os.FileMode. 115 func (n *FileNode) managedMode() os.FileMode { 116 n.node.mu.Lock() 117 defer n.node.mu.Unlock() 118 return n.SiaFile.Mode() 119 } 120 121 // managedFileInfo returns the FileInfo of the file node. 122 func (n *FileNode) managedFileInfo(siaPath skymodules.SiaPath, offline map[string]bool, goodForRenew map[string]bool, contracts map[string]skymodules.RenterContract) (skymodules.FileInfo, error) { 123 // Build the FileInfo 124 md := n.Metadata() 125 var onDisk bool 126 localPath := md.LocalPath 127 if localPath != "" { 128 _, err := os.Stat(localPath) 129 onDisk = err == nil 130 } 131 _, _, health, stuckHealth, numStuckChunks, repairBytes, stuckBytes := n.Health(offline, goodForRenew) 132 _, redundancy, err := n.Redundancy(offline, goodForRenew) 133 if err != nil { 134 return skymodules.FileInfo{}, errors.AddContext(err, "failed to get n redundancy") 135 } 136 uploadProgress, uploadedBytes, err := n.UploadProgressAndBytes() 137 if err != nil { 138 return skymodules.FileInfo{}, errors.AddContext(err, "failed to get upload progress and bytes") 139 } 140 maxHealth := math.Max(health, stuckHealth) 141 fileInfo := skymodules.FileInfo{ 142 AccessTime: md.AccessTime, 143 Available: redundancy >= 1, 144 ChangeTime: md.ChangeTime, 145 CipherType: n.MasterKey().Type().String(), 146 CreateTime: md.CreateTime, 147 Expiration: n.Expiration(contracts), 148 Filesize: uint64(md.FileSize), 149 Finished: md.Finished, 150 Health: health, 151 LocalPath: localPath, 152 Lost: md.Lost, 153 MaxHealth: maxHealth, 154 MaxHealthPercent: skymodules.HealthPercentage(maxHealth), 155 ModificationTime: md.ModTime, 156 NumStuckChunks: numStuckChunks, 157 OnDisk: onDisk, 158 Recoverable: onDisk || redundancy >= 1, 159 Redundancy: redundancy, 160 Renewing: true, 161 RepairBytes: repairBytes, 162 Skylinks: md.Skylinks, 163 SiaPath: siaPath, 164 Stuck: numStuckChunks > 0, 165 StuckHealth: stuckHealth, 166 StuckBytes: stuckBytes, 167 UID: n.staticUID, 168 UploadedBytes: uploadedBytes, 169 UploadProgress: uploadProgress, 170 } 171 return fileInfo, nil 172 } 173 174 // managedRename renames the fNode's underlying file. 175 func (n *FileNode) managedRename(newName string, oldParent, newParent *DirNode) error { 176 // Lock the parents. If they are the same, only lock one. 177 if oldParent.staticUID == newParent.staticUID { 178 oldParent.node.mu.Lock() 179 defer oldParent.node.mu.Unlock() 180 } else { 181 oldParent.node.mu.Lock() 182 defer oldParent.node.mu.Unlock() 183 newParent.node.mu.Lock() 184 defer newParent.node.mu.Unlock() 185 } 186 n.node.mu.Lock() 187 defer n.node.mu.Unlock() 188 // Check that newParent doesn't have a file or folder with that name 189 // already. 190 if exists := newParent.childExists(newName); exists { 191 return ErrExists 192 } 193 newPath := filepath.Join(newParent.absPath(), newName) + skymodules.SiaFileExtension 194 // Rename the file. 195 err := n.SiaFile.Rename(newPath) 196 if errors.Contains(err, siafile.ErrPathOverload) { 197 return ErrExists 198 } 199 if err != nil { 200 return err 201 } 202 // Remove file from old parent and add it to new parent. 203 // TODO: iteratively remove parents like in Close 204 oldParent.removeFile(n) 205 // Update parent and name. 206 n.parent = newParent 207 *n.name = newName 208 *n.path = newPath 209 // Add file to new parent. 210 n.parent.files[*n.name] = n 211 return err 212 } 213 214 // cachedFileInfo returns information on a siafile. As a performance 215 // optimization, the fileInfo takes the maps returned by 216 // renter.callRenterContractsAndUtilitiess for many files at once. 217 func (n *FileNode) staticCachedInfo(siaPath skymodules.SiaPath) (skymodules.FileInfo, error) { 218 md := n.Metadata() 219 220 // Build the FileInfo 221 var onDisk bool 222 localPath := md.LocalPath 223 if localPath != "" { 224 _, err := os.Stat(localPath) 225 onDisk = err == nil 226 } 227 maxHealth := math.Max(md.CachedHealth, md.CachedStuckHealth) 228 fileInfo := skymodules.FileInfo{ 229 AccessTime: md.AccessTime, 230 Available: md.CachedUserRedundancy >= 1, 231 ChangeTime: md.ChangeTime, 232 CipherType: md.StaticMasterKeyType.String(), 233 CreateTime: md.CreateTime, 234 Expiration: md.CachedExpiration, 235 Filesize: uint64(md.FileSize), 236 Finished: md.Finished, 237 Health: md.CachedHealth, 238 LocalPath: localPath, 239 MaxHealth: maxHealth, 240 MaxHealthPercent: skymodules.HealthPercentage(maxHealth), 241 ModificationTime: md.ModTime, 242 NumStuckChunks: md.NumStuckChunks, 243 OnDisk: onDisk, 244 Recoverable: onDisk || md.CachedUserRedundancy >= 1, 245 Redundancy: md.CachedUserRedundancy, 246 Renewing: true, 247 RepairBytes: md.CachedRepairBytes, 248 Skylinks: md.Skylinks, 249 SiaPath: siaPath, 250 Stuck: md.NumStuckChunks > 0, 251 StuckBytes: md.CachedStuckBytes, 252 StuckHealth: md.CachedStuckHealth, 253 UID: n.staticUID, 254 UploadedBytes: md.CachedUploadedBytes, 255 UploadProgress: md.CachedUploadProgress, 256 } 257 return fileInfo, nil 258 }