github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/persist.go (about) 1 package renter 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "encoding/base64" 7 "errors" 8 "io" 9 "os" 10 "path/filepath" 11 "strconv" 12 13 "github.com/NebulousLabs/Sia/build" 14 "github.com/NebulousLabs/Sia/encoding" 15 "github.com/NebulousLabs/Sia/modules" 16 "github.com/NebulousLabs/Sia/persist" 17 "github.com/NebulousLabs/Sia/types" 18 ) 19 20 const ( 21 PersistFilename = "renter.json" 22 ShareExtension = ".sia" 23 logFile = modules.RenterDir + ".log" 24 ) 25 26 var ( 27 ErrNoNicknames = errors.New("at least one nickname must be supplied") 28 ErrNonShareSuffix = errors.New("suffix of file must be " + ShareExtension) 29 ErrBadFile = errors.New("not a .sia file") 30 ErrIncompatible = errors.New("file is not compatible with current version") 31 32 shareHeader = [15]byte{'S', 'i', 'a', ' ', 'S', 'h', 'a', 'r', 'e', 'd', ' ', 'F', 'i', 'l', 'e'} 33 shareVersion = "0.4" 34 35 saveMetadata = persist.Metadata{ 36 Header: "Renter Persistence", 37 Version: "0.4", 38 } 39 ) 40 41 // MarshalSia implements the encoding.SiaMarshaller interface, writing the 42 // file data to w. 43 func (f *file) MarshalSia(w io.Writer) error { 44 enc := encoding.NewEncoder(w) 45 46 // encode easy fields 47 err := enc.EncodeAll( 48 f.name, 49 f.size, 50 f.masterKey, 51 f.pieceSize, 52 f.mode, 53 ) 54 if err != nil { 55 return err 56 } 57 // COMPATv0.4.3 - encode the bytesUploaded and chunksUploaded fields 58 // TODO: the resulting .sia file may confuse old clients. 59 err = enc.EncodeAll(f.pieceSize*f.numChunks()*uint64(f.erasureCode.NumPieces()), f.numChunks()) 60 if err != nil { 61 return err 62 } 63 64 // encode erasureCode 65 switch code := f.erasureCode.(type) { 66 case *rsCode: 67 err = enc.EncodeAll( 68 "Reed-Solomon", 69 uint64(code.dataPieces), 70 uint64(code.numPieces-code.dataPieces), 71 ) 72 if err != nil { 73 return err 74 } 75 default: 76 if build.DEBUG { 77 panic("unknown erasure code") 78 } 79 return errors.New("unknown erasure code") 80 } 81 // encode contracts 82 if err := enc.Encode(uint64(len(f.contracts))); err != nil { 83 return err 84 } 85 for _, c := range f.contracts { 86 if err := enc.Encode(c); err != nil { 87 return err 88 } 89 } 90 return nil 91 } 92 93 // UnmarshalSia implements the encoding.SiaUnmarshaller interface, 94 // reconstructing a file from the encoded bytes read from r. 95 func (f *file) UnmarshalSia(r io.Reader) error { 96 dec := encoding.NewDecoder(r) 97 98 // COMPATv0.4.3 - decode bytesUploaded and chunksUploaded into dummy vars. 99 var bytesUploaded, chunksUploaded uint64 100 101 // decode easy fields 102 err := dec.DecodeAll( 103 &f.name, 104 &f.size, 105 &f.masterKey, 106 &f.pieceSize, 107 &f.mode, 108 &bytesUploaded, 109 &chunksUploaded, 110 ) 111 if err != nil { 112 return err 113 } 114 115 // decode erasure coder 116 var codeType string 117 if err := dec.Decode(&codeType); err != nil { 118 return err 119 } 120 switch codeType { 121 case "Reed-Solomon": 122 var nData, nParity uint64 123 err = dec.DecodeAll( 124 &nData, 125 &nParity, 126 ) 127 if err != nil { 128 return err 129 } 130 rsc, err := NewRSCode(int(nData), int(nParity)) 131 if err != nil { 132 return err 133 } 134 f.erasureCode = rsc 135 default: 136 return errors.New("unrecognized erasure code type: " + codeType) 137 } 138 139 // decode contracts 140 var nContracts uint64 141 if err := dec.Decode(&nContracts); err != nil { 142 return err 143 } 144 f.contracts = make(map[types.FileContractID]fileContract) 145 var contract fileContract 146 for i := uint64(0); i < nContracts; i++ { 147 if err := dec.Decode(&contract); err != nil { 148 return err 149 } 150 f.contracts[contract.ID] = contract 151 } 152 return nil 153 } 154 155 // saveFile saves a file to the renter directory. 156 func (r *Renter) saveFile(f *file) error { 157 // Create directory structure specified in nickname. 158 fullPath := filepath.Join(r.persistDir, f.name+ShareExtension) 159 err := os.MkdirAll(filepath.Dir(fullPath), 0700) 160 if err != nil { 161 return err 162 } 163 164 // Open SafeFile handle. 165 handle, err := persist.NewSafeFile(filepath.Join(r.persistDir, f.name+ShareExtension)) 166 if err != nil { 167 return err 168 } 169 defer handle.Close() 170 171 // Write file data. 172 err = shareFiles([]*file{f}, handle) 173 if err != nil { 174 return err 175 } 176 177 // Commit the SafeFile. 178 return handle.Commit() 179 } 180 181 // save stores the current renter data to disk. 182 func (r *Renter) save() error { 183 data := struct { 184 Tracking map[string]trackedFile 185 }{r.tracking} 186 return persist.SaveFile(saveMetadata, data, filepath.Join(r.persistDir, PersistFilename)) 187 } 188 189 // saveSync stores the current renter data to disk and then syncs to disk. 190 func (r *Renter) saveSync() error { 191 data := struct { 192 Tracking map[string]trackedFile 193 }{r.tracking} 194 return persist.SaveFileSync(saveMetadata, data, filepath.Join(r.persistDir, PersistFilename)) 195 } 196 197 // load fetches the saved renter data from disk. 198 func (r *Renter) load() error { 199 // Recursively load all files found in renter directory. Errors 200 // encountered during loading are logged, but are not considered fatal. 201 err := filepath.Walk(r.persistDir, func(path string, info os.FileInfo, err error) error { 202 // This error is non-nil if filepath.Walk couldn't stat a file or 203 // folder. This generally indicates a serious error. 204 if err != nil { 205 return err 206 } 207 208 // Skip folders and non-sia files. 209 if info.IsDir() || filepath.Ext(path) != ShareExtension { 210 return nil 211 } 212 213 // Open the file. 214 file, err := os.Open(path) 215 if err != nil { 216 r.log.Println("ERROR: could not open .sia file:", err) 217 return nil 218 } 219 defer file.Close() 220 221 // Load the file contents into the renter. 222 _, err = r.loadSharedFiles(file) 223 if err != nil { 224 r.log.Println("ERROR: could not load .sia file:", err) 225 return nil 226 } 227 return nil 228 }) 229 if err != nil { 230 return err 231 } 232 233 // Load contracts, repair set, and entropy. 234 data := struct { 235 Tracking map[string]trackedFile 236 Repairing map[string]string // COMPATv0.4.8 237 }{} 238 err = persist.LoadFile(saveMetadata, &data, filepath.Join(r.persistDir, PersistFilename)) 239 if err != nil { 240 return err 241 } 242 if data.Tracking != nil { 243 r.tracking = data.Tracking 244 } 245 246 return nil 247 } 248 249 // shareFiles writes the specified files to w. First a header is written, 250 // followed by the gzipped concatenation of each file. 251 func shareFiles(files []*file, w io.Writer) error { 252 // Write header. 253 err := encoding.NewEncoder(w).EncodeAll( 254 shareHeader, 255 shareVersion, 256 uint64(len(files)), 257 ) 258 if err != nil { 259 return err 260 } 261 262 // Create compressor. 263 zip, _ := gzip.NewWriterLevel(w, gzip.BestCompression) 264 enc := encoding.NewEncoder(zip) 265 266 // Encode each file. 267 for _, f := range files { 268 err = enc.Encode(f) 269 if err != nil { 270 return err 271 } 272 } 273 274 return zip.Close() 275 } 276 277 // ShareFile saves the specified files to shareDest. 278 func (r *Renter) ShareFiles(nicknames []string, shareDest string) error { 279 lockID := r.mu.RLock() 280 defer r.mu.RUnlock(lockID) 281 282 // TODO: consider just appending the proper extension. 283 if filepath.Ext(shareDest) != ShareExtension { 284 return ErrNonShareSuffix 285 } 286 287 handle, err := os.Create(shareDest) 288 if err != nil { 289 return err 290 } 291 defer handle.Close() 292 293 // Load files from renter. 294 files := make([]*file, len(nicknames)) 295 for i, name := range nicknames { 296 f, exists := r.files[name] 297 if !exists { 298 return ErrUnknownPath 299 } 300 files[i] = f 301 } 302 303 err = shareFiles(files, handle) 304 if err != nil { 305 os.Remove(shareDest) 306 return err 307 } 308 309 return nil 310 } 311 312 // ShareFilesAscii returns the specified files in ASCII format. 313 func (r *Renter) ShareFilesAscii(nicknames []string) (string, error) { 314 lockID := r.mu.RLock() 315 defer r.mu.RUnlock(lockID) 316 317 // Load files from renter. 318 files := make([]*file, len(nicknames)) 319 for i, name := range nicknames { 320 f, exists := r.files[name] 321 if !exists { 322 return "", ErrUnknownPath 323 } 324 files[i] = f 325 } 326 327 buf := new(bytes.Buffer) 328 err := shareFiles(files, base64.NewEncoder(base64.URLEncoding, buf)) 329 if err != nil { 330 return "", err 331 } 332 333 return buf.String(), nil 334 } 335 336 // loadSharedFiles reads .sia data from reader and registers the contained 337 // files in the renter. It returns the nicknames of the loaded files. 338 func (r *Renter) loadSharedFiles(reader io.Reader) ([]string, error) { 339 // read header 340 var header [15]byte 341 var version string 342 var numFiles uint64 343 err := encoding.NewDecoder(reader).DecodeAll( 344 &header, 345 &version, 346 &numFiles, 347 ) 348 if err != nil { 349 return nil, err 350 } else if header != shareHeader { 351 return nil, ErrBadFile 352 } else if version != shareVersion { 353 return nil, ErrIncompatible 354 } 355 356 // Create decompressor. 357 unzip, err := gzip.NewReader(reader) 358 if err != nil { 359 return nil, err 360 } 361 dec := encoding.NewDecoder(unzip) 362 363 // Read each file. 364 files := make([]*file, numFiles) 365 for i := range files { 366 files[i] = new(file) 367 err := dec.Decode(files[i]) 368 if err != nil { 369 return nil, err 370 } 371 372 // Make sure the file's name does not conflict with existing files. 373 dupCount := 0 374 origName := files[i].name 375 for { 376 _, exists := r.files[files[i].name] 377 if !exists { 378 break 379 } 380 dupCount++ 381 files[i].name = origName + "_" + strconv.Itoa(dupCount) 382 } 383 } 384 385 // Add files to renter. 386 names := make([]string, numFiles) 387 for i, f := range files { 388 r.files[f.name] = f 389 names[i] = f.name 390 } 391 // Save the files. 392 for _, f := range files { 393 r.saveFile(f) 394 } 395 396 return names, nil 397 } 398 399 // initPersist handles all of the persistence initialization, such as creating 400 // the persistence directory and starting the logger. 401 func (r *Renter) initPersist() error { 402 // Create the perist directory if it does not yet exist. 403 err := os.MkdirAll(r.persistDir, 0700) 404 if err != nil { 405 return err 406 } 407 408 // Initialize the logger. 409 r.log, err = persist.NewFileLogger(filepath.Join(r.persistDir, logFile)) 410 if err != nil { 411 return err 412 } 413 414 // Load the prior persistence structures. 415 err = r.load() 416 if err != nil && !os.IsNotExist(err) { 417 return err 418 } 419 return nil 420 } 421 422 // LoadSharedFiles loads a .sia file into the renter. It returns the nicknames 423 // of the loaded files. 424 func (r *Renter) LoadSharedFiles(filename string) ([]string, error) { 425 lockID := r.mu.Lock() 426 defer r.mu.Unlock(lockID) 427 428 file, err := os.Open(filename) 429 if err != nil { 430 return nil, err 431 } 432 defer file.Close() 433 return r.loadSharedFiles(file) 434 } 435 436 // LoadSharedFilesAscii loads an ASCII-encoded .sia file into the renter. It 437 // returns the nicknames of the loaded files. 438 func (r *Renter) LoadSharedFilesAscii(asciiSia string) ([]string, error) { 439 lockID := r.mu.Lock() 440 defer r.mu.Unlock(lockID) 441 442 dec := base64.NewDecoder(base64.URLEncoding, bytes.NewBufferString(asciiSia)) 443 return r.loadSharedFiles(dec) 444 }