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 }