gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/siadir/siadir.go (about)

     1  package siadir
     2  
     3  import (
     4  	"encoding/json"
     5  	"io/ioutil"
     6  	"os"
     7  	"sync"
     8  	"time"
     9  
    10  	"gitlab.com/NebulousLabs/errors"
    11  
    12  	"gitlab.com/SiaPrime/SiaPrime/modules"
    13  	"gitlab.com/SiaPrime/writeaheadlog"
    14  )
    15  
    16  const (
    17  	// SiaDirExtension is the name of the metadata file for the sia directory
    18  	SiaDirExtension = ".siadir"
    19  
    20  	// DefaultDirHealth is the default health for the directory and the fall
    21  	// back value when there is an error. This is to protect against falsely
    22  	// trying to repair directories that had a read error
    23  	DefaultDirHealth = float64(0)
    24  )
    25  
    26  var (
    27  	// ErrPathOverload is an error when a siadir already exists at that location
    28  	ErrPathOverload = errors.New("a siadir already exists at that location")
    29  	// ErrUnknownPath is an error when a siadir cannot be found with the given path
    30  	ErrUnknownPath = errors.New("no siadir known with that path")
    31  	// ErrUnknownThread is an error when a siadir is trying to be closed by a
    32  	// thread that is not in the threadMap
    33  	ErrUnknownThread = errors.New("thread should not be calling Close(), does not have control of the siadir")
    34  )
    35  
    36  type (
    37  	// SiaDir contains the metadata information about a renter directory
    38  	SiaDir struct {
    39  		metadata Metadata
    40  
    41  		// siaPath is the path to the siadir on the sia network
    42  		siaPath modules.SiaPath
    43  
    44  		// rootDir is the path to the root directory on disk
    45  		rootDir string
    46  
    47  		// Utility fields
    48  		deleted bool
    49  		deps    modules.Dependencies
    50  		mu      sync.Mutex
    51  		wal     *writeaheadlog.WAL
    52  	}
    53  
    54  	// Metadata is the metadata that is saved to disk as a .siadir file
    55  	Metadata struct {
    56  		// For each field in the metadata there is an aggregate value and a
    57  		// siadir specific value. If a field has the aggregate prefix it means
    58  		// that the value takes into account all the siafiles and siadirs in the
    59  		// sub tree. The definition of aggregate and siadir specific values is
    60  		// otherwise the same.
    61  		//
    62  		// Health is the health of the most in need siafile that is not stuck
    63  		//
    64  		// LastHealthCheckTime is the oldest LastHealthCheckTime of any of the
    65  		// siafiles in the siadir and is the last time the health was calculated
    66  		// by the health loop
    67  		//
    68  		// MinRedundancy is the minimum redundancy of any of the siafiles in the
    69  		// siadir
    70  		//
    71  		// ModTime is the last time any of the siafiles in the siadir was
    72  		// updated
    73  		//
    74  		// NumFiles is the total number of siafiles in a siadir
    75  		//
    76  		// NumStuckChunks is the sum of all the Stuck Chunks of any of the
    77  		// siafiles in the siadir
    78  		//
    79  		// NumSubDirs is the number of sub-siadirs in a siadir
    80  		//
    81  		// Size is the total amount of data stored in the siafiles of the siadir
    82  		//
    83  		// StuckHealth is the health of the most in need siafile in the siadir,
    84  		// stuck or not stuck
    85  
    86  		// The following fields are aggregate values of the siadir. These values are
    87  		// the totals of the siadir and any sub siadirs, or are calculated based on
    88  		// all the values in the subtree
    89  		AggregateHealth              float64   `json:"aggregatehealth"`
    90  		AggregateLastHealthCheckTime time.Time `json:"aggregatelasthealthchecktime"`
    91  		AggregateMinRedundancy       float64   `json:"aggregateminredundancy"`
    92  		AggregateModTime             time.Time `json:"aggregatemodtime"`
    93  		AggregateNumFiles            uint64    `json:"aggregatenumfiles"`
    94  		AggregateNumStuckChunks      uint64    `json:"aggregatenumstuckchunks"`
    95  		AggregateNumSubDirs          uint64    `json:"aggregatenumsubdirs"`
    96  		AggregateSize                uint64    `json:"aggregatesize"`
    97  		AggregateStuckHealth         float64   `json:"aggregatestuckhealth"`
    98  
    99  		// The following fields are information specific to the siadir that is not
   100  		// an aggregate of the entire sub directory tree
   101  		Health              float64   `json:"health"`
   102  		LastHealthCheckTime time.Time `json:"lasthealthchecktime"`
   103  		MinRedundancy       float64   `json:"minredundancy"`
   104  		ModTime             time.Time `json:"modtime"`
   105  		NumFiles            uint64    `json:"numfiles"`
   106  		NumStuckChunks      uint64    `json:"numstuckchunks"`
   107  		NumSubDirs          uint64    `json:"numsubdirs"`
   108  		Size                uint64    `json:"size"`
   109  		StuckHealth         float64   `json:"stuckhealth"`
   110  	}
   111  )
   112  
   113  // DirReader is a helper type that allows reading a raw .siadir from disk while
   114  // keeping the file in memory locked.
   115  type DirReader struct {
   116  	f  *os.File
   117  	sd *SiaDir
   118  }
   119  
   120  // Close closes the underlying file.
   121  func (sdr *DirReader) Close() error {
   122  	sdr.sd.mu.Unlock()
   123  	return sdr.f.Close()
   124  }
   125  
   126  // Read calls Read on the underlying file.
   127  func (sdr *DirReader) Read(b []byte) (int, error) {
   128  	return sdr.f.Read(b)
   129  }
   130  
   131  // Stat returns the FileInfo of the underlying file.
   132  func (sdr *DirReader) Stat() (os.FileInfo, error) {
   133  	return sdr.f.Stat()
   134  }
   135  
   136  // New creates a new directory in the renter directory and makes sure there is a
   137  // metadata file in the directory and creates one as needed. This method will
   138  // also make sure that all the parent directories are created and have metadata
   139  // files as well and will return the SiaDir containing the information for the
   140  // directory that matches the siaPath provided
   141  func New(siaPath modules.SiaPath, rootDir string, wal *writeaheadlog.WAL) (*SiaDir, error) {
   142  	// Create path to directory and ensure path contains all metadata
   143  	updates, err := createDirMetadataAll(siaPath, rootDir)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	// Create metadata for directory
   149  	md, update, err := createDirMetadata(siaPath, rootDir)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	// Create SiaDir
   155  	sd := &SiaDir{
   156  		metadata: md,
   157  		deps:     modules.ProdDependencies,
   158  		siaPath:  siaPath,
   159  		rootDir:  rootDir,
   160  		wal:      wal,
   161  	}
   162  
   163  	return sd, managedCreateAndApplyTransaction(wal, append(updates, update)...)
   164  }
   165  
   166  // createDirMetadata makes sure there is a metadata file in the directory and
   167  // creates one as needed
   168  func createDirMetadata(siaPath modules.SiaPath, rootDir string) (Metadata, writeaheadlog.Update, error) {
   169  	// Check if metadata file exists
   170  	_, err := os.Stat(siaPath.SiaDirMetadataSysPath(rootDir))
   171  	if err == nil || !os.IsNotExist(err) {
   172  		return Metadata{}, writeaheadlog.Update{}, err
   173  	}
   174  
   175  	// Initialize metadata, set Health and StuckHealth to DefaultDirHealth so
   176  	// empty directories won't be viewed as being the most in need. Initialize
   177  	// ModTimes.
   178  	md := Metadata{
   179  		AggregateHealth:      DefaultDirHealth,
   180  		AggregateModTime:     time.Now(),
   181  		AggregateStuckHealth: DefaultDirHealth,
   182  
   183  		Health:      DefaultDirHealth,
   184  		ModTime:     time.Now(),
   185  		StuckHealth: DefaultDirHealth,
   186  	}
   187  	path := siaPath.SiaDirMetadataSysPath(rootDir)
   188  	update, err := createMetadataUpdate(path, md)
   189  	return md, update, err
   190  }
   191  
   192  // loadSiaDirMetadata loads the directory metadata from disk.
   193  func loadSiaDirMetadata(path string, deps modules.Dependencies) (md Metadata, err error) {
   194  	// Open the file.
   195  	file, err := deps.Open(path)
   196  	if err != nil {
   197  		return Metadata{}, err
   198  	}
   199  	defer file.Close()
   200  
   201  	// Read the file
   202  	bytes, err := ioutil.ReadAll(file)
   203  	if err != nil {
   204  		return Metadata{}, err
   205  	}
   206  
   207  	// Parse the json object.
   208  	err = json.Unmarshal(bytes, &md)
   209  	return
   210  }
   211  
   212  // LoadSiaDir loads the directory metadata from disk
   213  func LoadSiaDir(rootDir string, siaPath modules.SiaPath, deps modules.Dependencies, wal *writeaheadlog.WAL) (sd *SiaDir, err error) {
   214  	sd = &SiaDir{
   215  		deps:    deps,
   216  		siaPath: siaPath,
   217  		rootDir: rootDir,
   218  		wal:     wal,
   219  	}
   220  	sd.metadata, err = loadSiaDirMetadata(siaPath.SiaDirMetadataSysPath(rootDir), modules.ProdDependencies)
   221  	return sd, err
   222  }
   223  
   224  // delete removes the directory from disk and marks it as deleted. Once the directory is
   225  // deleted, attempting to access the directory will return an error.
   226  func (sd *SiaDir) delete() error {
   227  	update := sd.createDeleteUpdate()
   228  	err := sd.createAndApplyTransaction(update)
   229  	sd.deleted = true
   230  	return err
   231  }
   232  
   233  // Delete removes the directory from disk and marks it as deleted. Once the directory is
   234  // deleted, attempting to access the directory will return an error.
   235  func (sd *SiaDir) Delete() error {
   236  	sd.mu.Lock()
   237  	defer sd.mu.Unlock()
   238  	return sd.delete()
   239  }
   240  
   241  // Deleted returns the deleted field of the siaDir
   242  func (sd *SiaDir) Deleted() bool {
   243  	sd.mu.Lock()
   244  	defer sd.mu.Unlock()
   245  	return sd.deleted
   246  }
   247  
   248  // DirReader creates a io.ReadCloser that can be used to read the raw SiaDir
   249  // from disk.
   250  func (sd *SiaDir) DirReader() (*DirReader, error) {
   251  	sd.mu.Lock()
   252  	if sd.deleted {
   253  		sd.mu.Unlock()
   254  		return nil, errors.New("can't copy deleted SiaDir")
   255  	}
   256  	// Open file.
   257  	path := sd.siaPath.SiaDirMetadataSysPath(sd.rootDir)
   258  	f, err := os.Open(path)
   259  	if err != nil {
   260  		sd.mu.Unlock()
   261  		return nil, err
   262  	}
   263  	return &DirReader{
   264  		sd: sd,
   265  		f:  f,
   266  	}, nil
   267  }
   268  
   269  // Metadata returns the metadata of the SiaDir
   270  func (sd *SiaDir) Metadata() Metadata {
   271  	sd.mu.Lock()
   272  	defer sd.mu.Unlock()
   273  	return sd.metadata
   274  }
   275  
   276  // SiaPath returns the SiaPath of the SiaDir
   277  func (sd *SiaDir) SiaPath() modules.SiaPath {
   278  	sd.mu.Lock()
   279  	defer sd.mu.Unlock()
   280  	return sd.siaPath
   281  }
   282  
   283  // UpdateMetadata updates the SiaDir metadata on disk
   284  func (sd *SiaDir) UpdateMetadata(metadata Metadata) error {
   285  	sd.mu.Lock()
   286  	defer sd.mu.Unlock()
   287  	sd.metadata.AggregateHealth = metadata.AggregateHealth
   288  	sd.metadata.AggregateLastHealthCheckTime = metadata.AggregateLastHealthCheckTime
   289  	sd.metadata.AggregateMinRedundancy = metadata.AggregateMinRedundancy
   290  	sd.metadata.AggregateModTime = metadata.AggregateModTime
   291  	sd.metadata.AggregateNumFiles = metadata.AggregateNumFiles
   292  	sd.metadata.AggregateNumStuckChunks = metadata.AggregateNumStuckChunks
   293  	sd.metadata.AggregateNumSubDirs = metadata.AggregateNumSubDirs
   294  	sd.metadata.AggregateSize = metadata.AggregateSize
   295  	sd.metadata.AggregateStuckHealth = metadata.AggregateStuckHealth
   296  
   297  	sd.metadata.Health = metadata.Health
   298  	sd.metadata.LastHealthCheckTime = metadata.LastHealthCheckTime
   299  	sd.metadata.MinRedundancy = metadata.MinRedundancy
   300  	sd.metadata.ModTime = metadata.ModTime
   301  	sd.metadata.NumFiles = metadata.NumFiles
   302  	sd.metadata.NumStuckChunks = metadata.NumStuckChunks
   303  	sd.metadata.NumSubDirs = metadata.NumSubDirs
   304  	sd.metadata.Size = metadata.Size
   305  	sd.metadata.StuckHealth = metadata.StuckHealth
   306  	return sd.saveDir()
   307  }