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  }