gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/filesystem/siafile/metadata.go (about)

     1  package siafile
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"gitlab.com/NebulousLabs/errors"
    12  	"gitlab.com/NebulousLabs/writeaheadlog"
    13  
    14  	"gitlab.com/SkynetLabs/skyd/build"
    15  	"gitlab.com/SkynetLabs/skyd/skymodules"
    16  	"go.sia.tech/siad/crypto"
    17  	"go.sia.tech/siad/persist"
    18  	"go.sia.tech/siad/types"
    19  )
    20  
    21  type (
    22  	// SiafileUID is a unique identifier for siafile which is used to track
    23  	// siafiles even after renaming them.
    24  	SiafileUID string
    25  
    26  	// Metadata is the metadata of a SiaFile and is JSON encoded.
    27  	// Note: Methods which update the metadata and can potentially fail after
    28  	// doing so and before persisting the change should use backup() and
    29  	// restore() to restore the metadata before returning the error. Also
    30  	// changes to Metadata require backup() and restore() to be updated as well.
    31  	Metadata struct {
    32  		UniqueID SiafileUID `json:"uniqueid"` // unique identifier for file
    33  
    34  		StaticPagesPerChunk uint8    `json:"pagesperchunk"` // number of pages reserved for storing a chunk.
    35  		StaticVersion       [16]byte `json:"version"`       // version of the sia file format used
    36  		FileSize            int64    `json:"filesize"`      // total size of the file
    37  		StaticPieceSize     uint64   `json:"piecesize"`     // size of a single piece of the file
    38  		LocalPath           string   `json:"localpath"`     // file to the local copy of the file used for repairing
    39  
    40  		// Fields for encryption
    41  		StaticMasterKey      []byte            `json:"masterkey"` // masterkey used to encrypt pieces
    42  		StaticMasterKeyType  crypto.CipherType `json:"masterkeytype"`
    43  		StaticSharingKey     []byte            `json:"sharingkey"` // key used to encrypt shared pieces
    44  		StaticSharingKeyType crypto.CipherType `json:"sharingkeytype"`
    45  
    46  		// The following fields are the usual unix timestamps of files.
    47  		ModTime    time.Time `json:"modtime"`    // time of last content modification
    48  		ChangeTime time.Time `json:"changetime"` // time of last metadata modification
    49  		AccessTime time.Time `json:"accesstime"` // time of last access
    50  		CreateTime time.Time `json:"createtime"` // time of file creation
    51  
    52  		// Cached fields. These fields are cached fields and are only meant to be used
    53  		// to create FileInfos for file related API endpoints. There is no guarantee
    54  		// that these fields are up-to-date. Neither in memory nor on disk. Updates to
    55  		// these fields aren't persisted immediately. Instead they will only be
    56  		// persisted whenever another method persists the metadata or when the SiaFile
    57  		// is closed.
    58  		//
    59  		// CachedRedundancy is the redundancy of the file on the network and is
    60  		// updated within the 'Redundancy' method which is periodically called by the
    61  		// repair code.
    62  		//
    63  		// CachedUserRedundancy is the redundancy of the file on the network as
    64  		// visible to the user and is updated within the 'Redundancy' method which is
    65  		// periodically called by the repair code.
    66  		//
    67  		// CachedHealth is the health of the file on the network and is also
    68  		// periodically updated by the health check loop whenever 'Health' is called.
    69  		//
    70  		// CachedStuckHealth is the health of the stuck chunks of the file. It is
    71  		// updated by the health check loop. CachedExpiration is the lowest height at
    72  		// which any of the file's contracts will expire. Also updated periodically by
    73  		// the health check loop whenever 'Health' is called.
    74  		//
    75  		// CachedUploadedBytes is the number of bytes of the file that have been
    76  		// uploaded to the network so far. Is updated every time a piece is added to
    77  		// the siafile.
    78  		//
    79  		// CachedUploadProgress is the upload progress of the file and is updated
    80  		// every time a piece is added to the siafile.
    81  		CachedRedundancy     float64           `json:"cachedredundancy"`
    82  		CachedRepairBytes    uint64            `json:"cachedrepairbytes"`
    83  		CachedUserRedundancy float64           `json:"cacheduserredundancy"`
    84  		CachedHealth         float64           `json:"cachedhealth"`
    85  		CachedStuckBytes     uint64            `json:"cachedstuckbytes"`
    86  		CachedStuckHealth    float64           `json:"cachedstuckhealth"`
    87  		CachedExpiration     types.BlockHeight `json:"cachedexpiration"`
    88  		CachedUploadedBytes  uint64            `json:"cacheduploadedbytes"`
    89  		CachedUploadProgress float64           `json:"cacheduploadprogress"`
    90  
    91  		// Repair loop fields
    92  		//
    93  		// Finished indicates if the file ever finished uploading. A file is
    94  		// considered to have finished uploading if the health was ever < 1.
    95  		//
    96  		// LastHealthCheckTime is the timestamp of the last time the SiaFile's
    97  		// health was checked by Health()
    98  		//
    99  		// LazyUpload indicates if the file was uploaded with LazyPinning
   100  		//
   101  		// Lost indicates if the node views the file as lost
   102  		//
   103  		// NumStuckChunks is the number of all the SiaFile's chunks that have
   104  		// been marked as stuck by the repair loop. This doesn't include a potential
   105  		// partial chunk at the end of the file though. Use 'numStuckChunks()' for
   106  		// that instead.
   107  		Finished            bool      `json:"finished"`
   108  		LastHealthCheckTime time.Time `json:"lasthealthchecktime"`
   109  		LazyUpload          bool      `json:"lazyupload"`
   110  		Lost                bool      `json:"lost"`
   111  		NumStuckChunks      uint64    `json:"numstuckchunks"`
   112  
   113  		// File ownership/permission fields.
   114  		Mode    os.FileMode `json:"mode"`    // unix filemode of the sia file - uint32
   115  		UserID  int32       `json:"userid"`  // id of the user who owns the file
   116  		GroupID int32       `json:"groupid"` // id of the group that owns the file
   117  
   118  		// The following fields are the offsets for data that is written to disk
   119  		// after the pubKeyTable. We reserve a generous amount of space for the
   120  		// table and extra fields, but we need to remember those offsets in case we
   121  		// need to resize later on.
   122  		//
   123  		// chunkOffset is the offset of the first chunk, forced to be a factor of
   124  		// 4096, default 4kib
   125  		//
   126  		// pubKeyTableOffset is the offset of the publicKeyTable within the
   127  		// file.
   128  		//
   129  		ChunkOffset       int64 `json:"chunkoffset"`
   130  		PubKeyTableOffset int64 `json:"pubkeytableoffset"`
   131  
   132  		// erasure code settings.
   133  		//
   134  		// StaticErasureCodeType specifies the algorithm used for erasure coding
   135  		// chunks. Available types are:
   136  		//   0 - Invalid / Missing Code
   137  		//   1 - Reed Solomon Code
   138  		//
   139  		// erasureCodeParams specifies possible parameters for a certain
   140  		// StaticErasureCodeType. Currently params will be parsed as follows:
   141  		//   Reed Solomon Code - 4 bytes dataPieces / 4 bytes parityPieces
   142  		//
   143  		StaticErasureCodeType   [4]byte                 `json:"erasurecodetype"`
   144  		StaticErasureCodeParams [8]byte                 `json:"erasurecodeparams"`
   145  		staticErasureCode       skymodules.ErasureCoder // not persisted, exists for convenience
   146  
   147  		// Skylink tracking. If this siafile is known to have sectors of any
   148  		// skyfiles, those skyfiles will be listed here. It should be noted that
   149  		// a single siafile can be responsible for tracking many skyfiles.
   150  		Skylinks []string `json:"skylinks"`
   151  	}
   152  
   153  	// BubbledMetadata is the metadata of a siafile that gets bubbled
   154  	BubbledMetadata struct {
   155  		CreateTime          time.Time
   156  		Finished            bool
   157  		Health              float64
   158  		LastHealthCheckTime time.Time
   159  		LazyUpload          bool
   160  		Lost                bool
   161  		ModTime             time.Time
   162  		NumSkylinks         uint64
   163  		NumStuckChunks      uint64
   164  		OnDisk              bool
   165  		Redundancy          float64
   166  		RepairBytes         uint64
   167  		Size                uint64
   168  		StuckBytes          uint64
   169  		StuckHealth         float64
   170  		UID                 SiafileUID
   171  	}
   172  )
   173  
   174  // AccessTime returns the AccessTime timestamp of the file.
   175  func (sf *SiaFile) AccessTime() time.Time {
   176  	sf.mu.RLock()
   177  	defer sf.mu.RUnlock()
   178  	return sf.staticMetadata.AccessTime
   179  }
   180  
   181  // AddSkylink will add a skylink to the SiaFile.
   182  func (sf *SiaFile) AddSkylink(s skymodules.Skylink) (err error) {
   183  	sf.mu.Lock()
   184  	defer sf.mu.Unlock()
   185  	// backup the changed metadata before changing it. Revert the change on
   186  	// error.
   187  	defer func(backup Metadata) {
   188  		if err != nil {
   189  			sf.staticMetadata.restore(backup)
   190  		}
   191  	}(sf.staticMetadata.backup())
   192  	sf.staticMetadata.Skylinks = append(sf.staticMetadata.Skylinks, s.String())
   193  
   194  	// Save changes to metadata to disk.
   195  	return sf.saveMetadata()
   196  }
   197  
   198  // ChangeTime returns the ChangeTime timestamp of the file.
   199  func (sf *SiaFile) ChangeTime() time.Time {
   200  	sf.mu.RLock()
   201  	defer sf.mu.RUnlock()
   202  	return sf.staticMetadata.ChangeTime
   203  }
   204  
   205  // CreateTime returns the CreateTime timestamp of the file.
   206  func (sf *SiaFile) CreateTime() time.Time {
   207  	sf.mu.RLock()
   208  	defer sf.mu.RUnlock()
   209  	return sf.staticMetadata.CreateTime
   210  }
   211  
   212  // ChunkSize returns the size of a single chunk of the file.
   213  func (sf *SiaFile) ChunkSize() uint64 {
   214  	return sf.staticChunkSize()
   215  }
   216  
   217  // Finished returns whether or not the file finished uploading
   218  func (sf *SiaFile) Finished() bool {
   219  	sf.mu.RLock()
   220  	defer sf.mu.RUnlock()
   221  	return sf.finished()
   222  }
   223  
   224  // finished returns whether or not the file finished uploading
   225  func (sf *SiaFile) finished() bool {
   226  	return sf.staticMetadata.Finished
   227  }
   228  
   229  // LastHealthCheckTime returns the LastHealthCheckTime timestamp of the file
   230  func (sf *SiaFile) LastHealthCheckTime() time.Time {
   231  	sf.mu.RLock()
   232  	defer sf.mu.RUnlock()
   233  	return sf.staticMetadata.LastHealthCheckTime
   234  }
   235  
   236  // LazyUpload indicates whether a siafile is intended to be uploaded lazily.
   237  // Meaning that it can be uploaded from the network only during repairs.
   238  func (sf *SiaFile) LazyUpload() bool {
   239  	sf.mu.RLock()
   240  	defer sf.mu.RUnlock()
   241  	return sf.staticMetadata.LazyUpload
   242  }
   243  
   244  // LocalPath returns the path of the local data of the file.
   245  func (sf *SiaFile) LocalPath() string {
   246  	sf.mu.RLock()
   247  	defer sf.mu.RUnlock()
   248  	return sf.staticMetadata.LocalPath
   249  }
   250  
   251  // MasterKey returns the masterkey used to encrypt the file.
   252  func (sf *SiaFile) MasterKey() crypto.CipherKey {
   253  	return sf.staticMasterKey()
   254  }
   255  
   256  // Metadata returns the SiaFile's metadata, resolving any fields related to
   257  // partial chunks.
   258  func (sf *SiaFile) Metadata() Metadata {
   259  	sf.mu.RLock()
   260  	defer sf.mu.RUnlock()
   261  	md := sf.staticMetadata
   262  	md.NumStuckChunks = sf.numStuckChunks()
   263  	md.Finished = sf.finished()
   264  	return md
   265  }
   266  
   267  // Mode returns the FileMode of the SiaFile.
   268  func (sf *SiaFile) Mode() os.FileMode {
   269  	sf.mu.RLock()
   270  	defer sf.mu.RUnlock()
   271  	return sf.staticMetadata.Mode
   272  }
   273  
   274  // ModTime returns the ModTime timestamp of the file.
   275  func (sf *SiaFile) ModTime() time.Time {
   276  	sf.mu.RLock()
   277  	defer sf.mu.RUnlock()
   278  	return sf.staticMetadata.ModTime
   279  }
   280  
   281  // NumStuckChunks returns the Number of Stuck Chunks recorded in the file's
   282  // metadata
   283  func (sf *SiaFile) NumStuckChunks() uint64 {
   284  	sf.mu.RLock()
   285  	defer sf.mu.RUnlock()
   286  	return sf.numStuckChunks()
   287  }
   288  
   289  // PieceSize returns the size of a single piece of the file.
   290  func (sf *SiaFile) PieceSize() uint64 {
   291  	return sf.staticMetadata.StaticPieceSize
   292  }
   293  
   294  // Rename changes the name of the file to a new one. To guarantee that renaming
   295  // the file is atomic across all operating systems, we create a wal transaction
   296  // that moves over all the chunks one-by-one and deletes the src file.
   297  func (sf *SiaFile) Rename(newSiaFilePath string) error {
   298  	sf.mu.Lock()
   299  	defer sf.mu.Unlock()
   300  	return sf.rename(newSiaFilePath)
   301  }
   302  
   303  // backup creates a deep-copy of a Metadata.
   304  func (md Metadata) backup() (b Metadata) {
   305  	// Copy the static fields first. They are shallow copies since they are not
   306  	// allowed to change.
   307  	b.StaticPagesPerChunk = md.StaticPagesPerChunk
   308  	b.StaticVersion = md.StaticVersion
   309  	b.StaticPieceSize = md.StaticPieceSize
   310  	b.StaticMasterKey = md.StaticMasterKey
   311  	b.StaticMasterKeyType = md.StaticMasterKeyType
   312  	b.StaticSharingKey = md.StaticSharingKey
   313  	b.StaticSharingKeyType = md.StaticSharingKeyType
   314  	b.StaticErasureCodeType = md.StaticErasureCodeType
   315  	b.StaticErasureCodeParams = md.StaticErasureCodeParams
   316  	b.staticErasureCode = md.staticErasureCode
   317  
   318  	// Deep copy the remaining fields. For the sake of completion and safety we
   319  	// also copy the native types one-by-one even though they could be cloned
   320  	// together with the static types by a simple assignment like b = md
   321  	b.UniqueID = md.UniqueID
   322  	b.FileSize = md.FileSize
   323  	b.LocalPath = md.LocalPath
   324  	b.ModTime = md.ModTime
   325  	b.ChangeTime = md.ChangeTime
   326  	b.AccessTime = md.AccessTime
   327  	b.CreateTime = md.CreateTime
   328  	b.CachedRepairBytes = md.CachedRepairBytes
   329  	b.CachedStuckBytes = md.CachedStuckBytes
   330  	b.CachedRedundancy = md.CachedRedundancy
   331  	b.CachedUserRedundancy = md.CachedUserRedundancy
   332  	b.CachedHealth = md.CachedHealth
   333  	b.CachedStuckHealth = md.CachedStuckHealth
   334  	b.CachedExpiration = md.CachedExpiration
   335  	b.CachedUploadedBytes = md.CachedUploadedBytes
   336  	b.CachedUploadProgress = md.CachedUploadProgress
   337  	b.Finished = md.Finished
   338  	b.LastHealthCheckTime = md.LastHealthCheckTime
   339  	b.LazyUpload = md.LazyUpload
   340  	b.Lost = md.Lost
   341  	b.NumStuckChunks = md.NumStuckChunks
   342  	b.Mode = md.Mode
   343  	b.UserID = md.UserID
   344  	b.GroupID = md.GroupID
   345  	b.ChunkOffset = md.ChunkOffset
   346  	b.PubKeyTableOffset = md.PubKeyTableOffset
   347  	// Special handling for slice since reflect.DeepEqual is false when
   348  	// comparing empty slice to nil.
   349  	if md.Skylinks == nil {
   350  		b.Skylinks = nil
   351  	} else {
   352  		// Being extra explicit about capacity and length here.
   353  		b.Skylinks = make([]string, len(md.Skylinks), cap(md.Skylinks))
   354  		copy(b.Skylinks, md.Skylinks)
   355  	}
   356  	// If the backup was successful it should match the original.
   357  	if build.Release == "testing" && !md.equals(b) {
   358  		fmt.Println("md:\n", md)
   359  		fmt.Println("b:\n", b)
   360  		build.Critical("backup: copy doesn't match original")
   361  	}
   362  	return
   363  }
   364  
   365  // restore restores the metadata from a backup created with the backup() method.
   366  func (md *Metadata) restore(b Metadata) {
   367  	md.UniqueID = b.UniqueID
   368  	md.FileSize = b.FileSize
   369  	md.LocalPath = b.LocalPath
   370  	md.ModTime = b.ModTime
   371  	md.ChangeTime = b.ChangeTime
   372  	md.AccessTime = b.AccessTime
   373  	md.CreateTime = b.CreateTime
   374  	md.CachedRepairBytes = b.CachedRepairBytes
   375  	md.CachedStuckBytes = b.CachedStuckBytes
   376  	md.CachedRedundancy = b.CachedRedundancy
   377  	md.CachedUserRedundancy = b.CachedUserRedundancy
   378  	md.CachedHealth = b.CachedHealth
   379  	md.CachedStuckHealth = b.CachedStuckHealth
   380  	md.CachedExpiration = b.CachedExpiration
   381  	md.CachedUploadedBytes = b.CachedUploadedBytes
   382  	md.CachedUploadProgress = b.CachedUploadProgress
   383  	md.Finished = b.Finished
   384  	md.LastHealthCheckTime = b.LastHealthCheckTime
   385  	b.LazyUpload = md.LazyUpload
   386  	b.Lost = md.Lost
   387  	md.NumStuckChunks = b.NumStuckChunks
   388  	md.Mode = b.Mode
   389  	md.UserID = b.UserID
   390  	md.GroupID = b.GroupID
   391  	md.ChunkOffset = b.ChunkOffset
   392  	md.PubKeyTableOffset = b.PubKeyTableOffset
   393  	md.Skylinks = b.Skylinks
   394  	// If the backup was successful it should match the backup.
   395  	if build.Release == "testing" && !md.equals(b) {
   396  		fmt.Println("md:\n", md)
   397  		fmt.Println("b:\n", b)
   398  		build.Critical("restore: copy doesn't match original")
   399  	}
   400  }
   401  
   402  // equal compares the two structs for equality by serializing them and comparing
   403  // the serialized representations.
   404  //
   405  // WARNING: Do not use in production!
   406  func (md *Metadata) equals(b Metadata) bool {
   407  	if build.Release != "testing" {
   408  		build.Critical("Metadata.equals used in non-testing code!")
   409  	}
   410  	mdBytes, err := json.Marshal(md)
   411  	if err != nil {
   412  		build.Critical(fmt.Sprintf("failed to marshal: %s. Problematic entity: %+v\n", err.Error(), md))
   413  	}
   414  	bBytes, err := json.Marshal(b)
   415  	if err != nil {
   416  		build.Critical(fmt.Sprintf("failed to marshal: %s. Problematic entity: %+v\n", err.Error(), b))
   417  	}
   418  	return bytes.Equal(mdBytes, bBytes)
   419  }
   420  
   421  // rename changes the name of the file to a new one. To guarantee that renaming
   422  // the file is atomic across all operating systems, we create a wal transaction
   423  // that moves over all the chunks one-by-one and deletes the src file.
   424  func (sf *SiaFile) rename(newSiaFilePath string) (err error) {
   425  	if sf.deleted {
   426  		return errors.New("can't rename deleted siafile")
   427  	}
   428  	// backup the changed metadata before changing it. Revert the change on
   429  	// error.
   430  	oldPath := sf.siaFilePath
   431  	defer func(backup Metadata) {
   432  		if err != nil {
   433  			sf.staticMetadata.restore(backup)
   434  			sf.siaFilePath = oldPath
   435  		}
   436  	}(sf.staticMetadata.backup())
   437  	// Check if file exists at new location.
   438  	if _, err := os.Stat(newSiaFilePath); err == nil {
   439  		return ErrPathOverload
   440  	}
   441  	// Create path to renamed location.
   442  	dir, _ := filepath.Split(newSiaFilePath)
   443  	err = os.MkdirAll(dir, 0700)
   444  	if err != nil {
   445  		return err
   446  	}
   447  	// Create the delete update before changing the path to the new one.
   448  	updates := []writeaheadlog.Update{sf.createDeleteUpdate()}
   449  	// Load all the chunks.
   450  	chunks := make([]chunk, 0, sf.numChunks)
   451  	err = sf.iterateChunksReadonly(func(chunk chunk) error {
   452  		chunks = append(chunks, chunk)
   453  		return nil
   454  	})
   455  	if err != nil {
   456  		return err
   457  	}
   458  	// Rename file in memory.
   459  	sf.siaFilePath = newSiaFilePath
   460  	// Update the ChangeTime because the metadata changed.
   461  	sf.staticMetadata.ChangeTime = time.Now()
   462  	// Write the header to the new location.
   463  	headerUpdate, err := sf.saveHeaderUpdates()
   464  	if err != nil {
   465  		return err
   466  	}
   467  	updates = append(updates, headerUpdate...)
   468  	// Write the chunks to the new location.
   469  	for _, chunk := range chunks {
   470  		updates = append(updates, sf.saveChunkUpdate(chunk))
   471  	}
   472  	// Apply updates.
   473  	return createAndApplyTransaction(sf.wal, updates...)
   474  }
   475  
   476  // SetMode sets the filemode of the sia file.
   477  func (sf *SiaFile) SetMode(mode os.FileMode) (err error) {
   478  	sf.mu.Lock()
   479  	defer sf.mu.Unlock()
   480  	// backup the changed metadata before changing it. Revert the change on
   481  	// error.
   482  	defer func(backup Metadata) {
   483  		if err != nil {
   484  			sf.staticMetadata.restore(backup)
   485  		}
   486  	}(sf.staticMetadata.backup())
   487  	sf.staticMetadata.Mode = mode
   488  	sf.staticMetadata.ChangeTime = time.Now()
   489  
   490  	// Save changes to metadata to disk.
   491  	return sf.saveMetadata()
   492  }
   493  
   494  // SetLastHealthCheckTime sets the LastHealthCheckTime in memory to the current
   495  // time but does not update and write to disk.
   496  //
   497  // NOTE: This call should be used in conjunction with a method that saves the
   498  // SiaFile metadata
   499  func (sf *SiaFile) SetLastHealthCheckTime() {
   500  	sf.mu.Lock()
   501  	defer sf.mu.Unlock()
   502  	sf.staticMetadata.LastHealthCheckTime = time.Now()
   503  }
   504  
   505  // SetLocalPath changes the local path of the file which is used to repair
   506  // the file from disk.
   507  func (sf *SiaFile) SetLocalPath(path string) (err error) {
   508  	sf.mu.Lock()
   509  	defer sf.mu.Unlock()
   510  	// backup the changed metadata before changing it. Revert the change on
   511  	// error.
   512  	defer func(backup Metadata) {
   513  		if err != nil {
   514  			sf.staticMetadata.restore(backup)
   515  		}
   516  	}(sf.staticMetadata.backup())
   517  
   518  	sf.staticMetadata.LocalPath = path
   519  
   520  	// Save changes to metadata to disk.
   521  	return sf.saveMetadata()
   522  }
   523  
   524  // SetLost sets the Lost flag for the siafile.
   525  func (sf *SiaFile) SetLost(lost bool) (err error) {
   526  	sf.mu.Lock()
   527  	defer sf.mu.Unlock()
   528  	// backup the changed metadata before changing it. Revert the change on
   529  	// error.
   530  	defer func(backup Metadata) {
   531  		if err != nil {
   532  			sf.staticMetadata.restore(backup)
   533  		}
   534  	}(sf.staticMetadata.backup())
   535  
   536  	sf.staticMetadata.Lost = lost
   537  
   538  	// Save changes to metadata to disk.
   539  	return sf.saveMetadata()
   540  }
   541  
   542  // Size returns the file's size.
   543  func (sf *SiaFile) Size() uint64 {
   544  	sf.mu.RLock()
   545  	defer sf.mu.RUnlock()
   546  	return uint64(sf.staticMetadata.FileSize)
   547  }
   548  
   549  // UpdateUniqueID creates a new random uid for the SiaFile.
   550  func (sf *SiaFile) UpdateUniqueID() {
   551  	sf.staticMetadata.UniqueID = uniqueID()
   552  }
   553  
   554  // UpdateAccessTime updates the AccessTime timestamp to the current time.
   555  func (sf *SiaFile) UpdateAccessTime() (err error) {
   556  	sf.mu.Lock()
   557  	defer sf.mu.Unlock()
   558  	// backup the changed metadata before changing it. Revert the change on
   559  	// error.
   560  	defer func(backup Metadata) {
   561  		if err != nil {
   562  			sf.staticMetadata.restore(backup)
   563  		}
   564  	}(sf.staticMetadata.backup())
   565  	sf.staticMetadata.AccessTime = time.Now()
   566  
   567  	// Save changes to metadata to disk.
   568  	return sf.saveMetadata()
   569  }
   570  
   571  // numStuckChunks returns the number of stuck chunks recorded in the file's
   572  // metadata.
   573  func (sf *SiaFile) numStuckChunks() uint64 {
   574  	return sf.staticMetadata.NumStuckChunks
   575  }
   576  
   577  // staticChunkSize returns the size of a single chunk of the file.
   578  func (sf *SiaFile) staticChunkSize() uint64 {
   579  	return sf.staticMetadata.StaticPieceSize * uint64(sf.staticMetadata.staticErasureCode.MinPieces())
   580  }
   581  
   582  // staticMasterKey returns the masterkey used to encrypt the file.
   583  func (sf *SiaFile) staticMasterKey() crypto.CipherKey {
   584  	sk, err := crypto.NewSiaKey(sf.staticMetadata.StaticMasterKeyType, sf.staticMetadata.StaticMasterKey)
   585  	if err != nil {
   586  		// This should never happen since the constructor of the SiaFile takes
   587  		// a CipherKey as an argument which guarantees that it is already a
   588  		// valid key.
   589  		panic(errors.AddContext(err, "failed to create masterkey of siafile"))
   590  	}
   591  	return sk
   592  }
   593  
   594  // onDisk is a helper for indicated if a siafile is ondisk, which means it has a
   595  // localPath.
   596  func (sf *SiaFile) onDisk() bool {
   597  	return sf.staticMetadata.LocalPath != ""
   598  }
   599  
   600  // uniqueID creates a random unique SiafileUID.
   601  func uniqueID() SiafileUID {
   602  	return SiafileUID(persist.UID())
   603  }