github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/downloadworker.go (about) 1 package sdk 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/md5" 7 "encoding/hex" 8 "encoding/json" 9 "fmt" 10 "hash" 11 "io" 12 "io/ioutil" 13 "net/http" 14 "os" 15 "path/filepath" 16 "sort" 17 "strings" 18 "sync" 19 "sync/atomic" 20 "time" 21 22 "github.com/0chain/errors" 23 "github.com/0chain/gosdk/core/common" 24 "github.com/0chain/gosdk/core/sys" 25 "github.com/0chain/gosdk/zboxcore/blockchain" 26 "github.com/0chain/gosdk/zboxcore/client" 27 "github.com/0chain/gosdk/zboxcore/encryption" 28 "github.com/0chain/gosdk/zboxcore/fileref" 29 "github.com/0chain/gosdk/zboxcore/logger" 30 l "github.com/0chain/gosdk/zboxcore/logger" 31 "github.com/0chain/gosdk/zboxcore/marker" 32 "github.com/0chain/gosdk/zboxcore/zboxutil" 33 "github.com/klauspost/reedsolomon" 34 "go.dedis.ch/kyber/v3/group/edwards25519" 35 "golang.org/x/sync/errgroup" 36 ) 37 38 const ( 39 DOWNLOAD_CONTENT_FULL = "full" 40 DOWNLOAD_CONTENT_THUMB = "thumbnail" 41 ) 42 43 var ( 44 extraCount = 2 45 ) 46 47 type DownloadRequestOption func(dr *DownloadRequest) 48 49 // WithDownloadProgressStorer set download progress storer of download request options. 50 // - storer: download progress storer instance, used to store download progress. 51 func WithDownloadProgressStorer(storer DownloadProgressStorer) DownloadRequestOption { 52 return func(dr *DownloadRequest) { 53 dr.downloadStorer = storer 54 } 55 } 56 57 func WithWorkDir(workdir string) DownloadRequestOption { 58 return func(dr *DownloadRequest) { 59 dr.workdir = workdir 60 } 61 } 62 63 func WithFileCallback(cb func()) DownloadRequestOption { 64 return func(dr *DownloadRequest) { 65 dr.fileCallback = cb 66 } 67 } 68 69 type DownloadRequest struct { 70 allocationID string 71 allocationTx string 72 sig string 73 allocOwnerID string 74 allocOwnerPubKey string 75 blobbers []*blockchain.StorageNode 76 datashards int 77 parityshards int 78 remotefilepath string 79 remotefilepathhash string 80 fileHandler sys.File 81 localFilePath string 82 startBlock int64 83 endBlock int64 84 chunkSize int 85 numBlocks int64 86 validationRootMap map[string]*blobberFile 87 statusCallback StatusCallback 88 ctx context.Context 89 ctxCncl context.CancelFunc 90 authTicket *marker.AuthTicket 91 downloadMask zboxutil.Uint128 92 encryptedKey string 93 isDownloadCanceled bool 94 completedCallback func(remotepath string, remotepathhash string) 95 fileCallback func() 96 contentMode string 97 Consensus 98 effectiveBlockSize int // blocksize - encryptionOverHead 99 ecEncoder reedsolomon.Encoder 100 maskMu *sync.Mutex 101 encScheme encryption.EncryptionScheme 102 shouldVerify bool 103 blocksPerShard int64 104 connectionID string 105 skip bool 106 freeRead bool 107 fRef *fileref.FileRef 108 chunksPerShard int64 109 size int64 110 offset int64 111 bufferMap map[int]zboxutil.DownloadBuffer 112 downloadStorer DownloadProgressStorer 113 workdir string 114 downloadQueue downloadQueue // Always initialize this queue with max time taken 115 isResume bool 116 isEnterprise bool 117 } 118 119 type downloadPriority struct { 120 timeTaken int64 121 blobberIdx int 122 } 123 124 type downloadQueue []downloadPriority 125 126 func (pq downloadQueue) Len() int { return len(pq) } 127 128 func (pq downloadQueue) Less(i, j int) bool { 129 return pq[i].timeTaken < pq[j].timeTaken 130 } 131 132 type DownloadProgress struct { 133 ID string `json:"id"` 134 LastWrittenBlock int `json:"last_block"` 135 numBlocks int `json:"-"` 136 } 137 type blockData struct { 138 blockNum int 139 data [][][]byte 140 } 141 142 func (req *DownloadRequest) removeFromMask(pos uint64) { 143 req.maskMu.Lock() 144 req.downloadMask = req.downloadMask.And(zboxutil.NewUint128(1).Lsh(pos).Not()) 145 req.maskMu.Unlock() 146 } 147 148 func (req *DownloadRequest) getBlocksDataFromBlobbers(startBlock, totalBlock int64, timeRequest bool) ([][][]byte, error) { 149 shards := make([][][]byte, totalBlock) 150 for i := range shards { 151 shards[i] = make([][]byte, len(req.blobbers)) 152 } 153 154 mask := req.downloadMask 155 requiredDownloads := req.consensusThresh 156 var ( 157 remainingMask zboxutil.Uint128 158 failed int 159 err error 160 downloadErrors []string 161 ) 162 163 curReqDownloads := requiredDownloads 164 for { 165 remainingMask, failed, downloadErrors, err = req.downloadBlock( 166 startBlock, totalBlock, mask, curReqDownloads, shards, timeRequest) 167 if err != nil { 168 return nil, err 169 } 170 if failed == 0 || (timeRequest && mask.CountOnes()-failed >= requiredDownloads) { 171 break 172 } 173 174 if failed > remainingMask.CountOnes() { 175 return nil, errors.New("download_failed", 176 fmt.Sprintf("%d failed blobbers exceeded %d remaining blobbers."+ 177 " Download errors: %s", 178 failed, remainingMask.CountOnes(), strings.Join(downloadErrors, " "))) 179 } 180 181 curReqDownloads = failed 182 mask = remainingMask 183 } 184 return shards, err 185 } 186 187 // getBlocksData will get data blocks for some interval from minimal blobers and aggregate them and 188 // return to the caller 189 func (req *DownloadRequest) getBlocksData(startBlock, totalBlock int64, timeRequest bool) ([][][]byte, error) { 190 191 shards, err := req.getBlocksDataFromBlobbers(startBlock, totalBlock, timeRequest) 192 if err != nil { 193 return nil, err 194 } 195 196 // erasure decoding 197 // Can we benefit from goroutine for erasure decoding?? 198 // c := req.datashards * req.effectiveBlockSize 199 // data := make([]byte, req.datashards*req.effectiveBlockSize*int(totalBlock)) 200 for i := range shards { 201 err = req.decodeEC(shards[i]) 202 if err != nil { 203 return nil, err 204 } 205 206 } 207 return shards, nil 208 } 209 210 // downloadBlock This function will add download requests to the download channel which picks up 211 // download requests and processes it. 212 // This function will fill up `shards` in respective position and also return failed number of 213 // blobbers along with remainingMask that are the blobbers that are not yet requested. 214 func (req *DownloadRequest) downloadBlock( 215 startBlock, totalBlock int64, 216 mask zboxutil.Uint128, requiredDownloads int, 217 shards [][][]byte, timeRequest bool) (zboxutil.Uint128, int, []string, error) { 218 219 var remainingMask zboxutil.Uint128 220 activeBlobbers := mask.CountOnes() 221 if activeBlobbers < requiredDownloads { 222 return zboxutil.NewUint128(0), 0, nil, errors.New("insufficient_blobbers", 223 fmt.Sprintf("Required downloads %d, remaining active blobber %d", 224 req.consensusThresh, activeBlobbers)) 225 } 226 if timeRequest { 227 requiredDownloads = activeBlobbers 228 } 229 rspCh := make(chan *downloadBlock, requiredDownloads) 230 231 var ( 232 pos uint64 233 c int 234 skipDownload bool 235 ) 236 237 for i := mask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 238 if c == requiredDownloads { 239 remainingMask = i 240 break 241 } 242 243 pos = uint64(i.TrailingZeros()) 244 blobberIdx := req.downloadQueue[pos].blobberIdx 245 blockDownloadReq := &BlockDownloadRequest{ 246 allocationID: req.allocationID, 247 allocationTx: req.allocationTx, 248 allocOwnerID: req.allocOwnerID, 249 authTicket: req.authTicket, 250 blobber: req.blobbers[blobberIdx], 251 blobberIdx: blobberIdx, 252 maskIdx: int(pos), 253 chunkSize: req.chunkSize, 254 blockNum: startBlock, 255 contentMode: req.contentMode, 256 result: rspCh, 257 ctx: req.ctx, 258 remotefilepath: req.remotefilepath, 259 remotefilepathhash: req.remotefilepathhash, 260 numBlocks: totalBlock, 261 encryptedKey: req.encryptedKey, 262 shouldVerify: req.shouldVerify, 263 connectionID: req.connectionID, 264 } 265 266 if blockDownloadReq.blobber.IsSkip() { 267 rspCh <- &downloadBlock{ 268 Success: false, 269 idx: blockDownloadReq.blobberIdx, 270 err: errors.New("", "skip blobber by previous errors")} 271 skipDownload = true 272 } 273 274 if !skipDownload { 275 bf := req.validationRootMap[blockDownloadReq.blobber.ID] 276 blockDownloadReq.blobberFile = bf 277 if req.shouldVerify { 278 go AddBlockDownloadReq(req.ctx, blockDownloadReq, nil, req.effectiveBlockSize) 279 } else { 280 go AddBlockDownloadReq(req.ctx, blockDownloadReq, req.bufferMap[blobberIdx], req.effectiveBlockSize) 281 } 282 } 283 284 c++ 285 } 286 287 var failed int32 288 downloadErrors := make([]string, requiredDownloads) 289 wg := &sync.WaitGroup{} 290 for i := 0; i < requiredDownloads; i++ { 291 result := <-rspCh 292 wg.Add(1) 293 go func(i int) { 294 var err error 295 defer func() { 296 if err != nil { 297 totalFail := atomic.AddInt32(&failed, 1) 298 // if first request remove from end as we will convert the slice into heap 299 if timeRequest { 300 req.removeFromMask(uint64(activeBlobbers - int(totalFail))) 301 } else { 302 req.removeFromMask(uint64(result.maskIdx)) 303 } 304 downloadErrors[i] = fmt.Sprintf("Error %s from %s", 305 err.Error(), req.blobbers[result.idx].Baseurl) 306 logger.Logger.Error(err) 307 if req.bufferMap != nil && req.bufferMap[result.idx] != nil { 308 req.bufferMap[result.idx].ReleaseChunk(int(req.startBlock)) 309 } 310 } else if timeRequest { 311 req.downloadQueue[result.maskIdx].timeTaken = result.timeTaken 312 } 313 wg.Done() 314 }() 315 if !result.Success { 316 err = fmt.Errorf("Unsuccessful download. Error: %v", result.err) 317 return 318 } 319 err = req.fillShards(shards, result) 320 }(i) 321 } 322 323 wg.Wait() 324 return remainingMask, int(failed), downloadErrors, nil 325 } 326 327 // decodeEC will reconstruct shards and verify it 328 func (req *DownloadRequest) decodeEC(shards [][]byte) (err error) { 329 err = req.ecEncoder.ReconstructData(shards) 330 if err != nil { 331 return 332 } 333 // c := len(shards[0]) 334 // data = make([]byte, req.datashards*c) 335 // for i := 0; i < req.datashards; i++ { 336 // index := i * c 337 // copy(data[index:index+c], shards[i]) 338 // } 339 return nil 340 } 341 342 //shards -> shards[i][data] 343 344 // fillShards will fill `shards` with data from blobbers that belongs to specific 345 // blockNumber and blobber's position index in an allocation 346 func (req *DownloadRequest) fillShards(shards [][][]byte, result *downloadBlock) (err error) { 347 348 for i := 0; i < len(result.BlockChunks); i++ { 349 var data []byte 350 if req.encryptedKey != "" { 351 data, err = req.getDecryptedData(result, i) 352 if err != nil { 353 return err 354 } 355 } else { 356 data = result.BlockChunks[i] 357 } 358 if i >= len(shards) || len(shards[i]) <= result.idx { 359 l.Logger.Error("Invalid shard index", result.idx, len(shards)) 360 return errors.New("invalid_shard_index", fmt.Sprintf("Invalid shard index %d shard len: %d shard block len: %d", result.idx, len(shards), i)) 361 } 362 shards[i][result.idx] = data 363 } 364 return 365 } 366 367 // getDecryptedData will decrypt encrypted data and return it. 368 func (req *DownloadRequest) getDecryptedData(result *downloadBlock, blockNum int) (data []byte, err error) { 369 if req.authTicket != nil { 370 return req.getDecryptedDataForAuthTicket(result, blockNum) 371 } 372 373 headerBytes := result.BlockChunks[blockNum][:EncryptionHeaderSize] 374 headerBytes = bytes.Trim(headerBytes, "\x00") 375 376 if len(headerBytes) != EncryptionHeaderSize { 377 logger.Logger.Error("Block has invalid header", req.blobbers[result.idx].Baseurl) 378 return nil, errors.New( 379 "invalid_header", 380 fmt.Sprintf("Block from %s has invalid header. Required header size: %d, got %d", 381 req.blobbers[result.idx].Baseurl, EncryptionHeaderSize, len(headerBytes))) 382 } 383 384 encMsg := &encryption.EncryptedMessage{} 385 encMsg.EncryptedData = result.BlockChunks[blockNum][EncryptionHeaderSize:] 386 encMsg.MessageChecksum, encMsg.OverallChecksum = string(headerBytes[:128]), string(headerBytes[128:]) 387 encMsg.EncryptedKey = req.encScheme.GetEncryptedKey() 388 decryptedBytes, err := req.encScheme.Decrypt(encMsg) 389 if err != nil { 390 logger.Logger.Error("Block decryption failed", req.blobbers[result.idx].Baseurl, err) 391 return nil, errors.New( 392 "decryption_error", 393 fmt.Sprintf("Decryption error %s while decrypting data from %s blobber", 394 err.Error(), req.blobbers[result.idx].Baseurl)) 395 } 396 return decryptedBytes, nil 397 } 398 399 // getDecryptedDataForAuthTicket will return decrypt shared encrypted data using re-encryption/re-decryption 400 // mechanism 401 func (req *DownloadRequest) getDecryptedDataForAuthTicket(result *downloadBlock, blockNum int) (data []byte, err error) { 402 suite := edwards25519.NewBlakeSHA256Ed25519() 403 reEncMessage := &encryption.ReEncryptedMessage{ 404 D1: suite.Point(), 405 D4: suite.Point(), 406 D5: suite.Point(), 407 } 408 err = reEncMessage.Unmarshal(result.BlockChunks[blockNum]) 409 if err != nil { 410 logger.Logger.Error("ReEncrypted Block unmarshall failed", req.blobbers[result.idx].Baseurl, err) 411 return nil, err 412 } 413 decrypted, err := req.encScheme.ReDecrypt(reEncMessage) 414 if err != nil { 415 logger.Logger.Error("Block redecryption failed", req.blobbers[result.idx].Baseurl, err) 416 return nil, err 417 } 418 return decrypted, nil 419 } 420 421 // processDownload will setup download parameters and downloads data with given 422 // start block, end block and number of blocks to download in single request. 423 // This will also write data to the file handler and will verify content by calculating content hash. 424 func (req *DownloadRequest) processDownload() { 425 ctx := req.ctx 426 if req.completedCallback != nil { 427 defer req.completedCallback(req.remotefilepath, req.remotefilepathhash) 428 } 429 if req.fileCallback != nil { 430 defer func() { 431 if !req.skip { 432 req.fileCallback() 433 } 434 }() 435 } 436 defer req.ctxCncl() 437 remotePathCB := req.remotefilepath 438 if remotePathCB == "" { 439 remotePathCB = req.remotefilepathhash 440 } 441 442 var op = OpDownload 443 if req.contentMode == DOWNLOAD_CONTENT_THUMB { 444 op = opThumbnailDownload 445 } 446 fRef := req.fRef 447 if fRef != nil && fRef.ActualFileHash == emptyFileDataHash { 448 logger.Logger.Info("File is empty") 449 _, err := req.fileHandler.Write([]byte(emptyFileDataHash)) 450 if err != nil { 451 req.errorCB(errors.Wrap(err, "Write file failed"), remotePathCB) 452 return 453 } 454 req.fileHandler.Sync() //nolint 455 if req.statusCallback != nil && !req.skip { 456 req.statusCallback.Completed( 457 req.allocationID, remotePathCB, fRef.Name, fRef.MimeType, 32, op) 458 } 459 return 460 } 461 size, chunksPerShard, blocksPerShard := req.size, req.chunksPerShard, req.blocksPerShard 462 463 now := time.Now() 464 err := req.initEC() 465 if err != nil { 466 logger.Logger.Error(err) 467 req.errorCB( 468 fmt.Errorf("Error while initializing file ref. Error: %v", 469 err), remotePathCB) 470 return 471 } 472 elapsedInitEC := time.Since(now) 473 if req.encryptedKey != "" { 474 err = req.initEncryption() 475 if err != nil { 476 req.errorCB( 477 fmt.Errorf("Error while initializing encryption"), remotePathCB, 478 ) 479 return 480 } 481 } 482 elapsedInitEncryption := time.Since(now) - elapsedInitEC 483 484 var downloaded int 485 startBlock, endBlock, numBlocks := req.startBlock, req.endBlock, req.numBlocks 486 // remainingSize should be calculated based on startBlock number 487 // otherwise end data will have null bytes. 488 remainingSize := size - startBlock*int64(req.effectiveBlockSize)*int64(req.datashards) 489 490 if endBlock*int64(req.effectiveBlockSize)*int64(req.datashards) < req.size { 491 remainingSize = blocksPerShard * int64(req.effectiveBlockSize) * int64(req.datashards) 492 } else if req.isResume { 493 remainingSize = size 494 } 495 496 if memFile, ok := req.fileHandler.(*sys.MemFile); ok { 497 memFile.InitBuffer(int(remainingSize)) 498 } 499 500 if req.statusCallback != nil { 501 // Started will also initialize progress bar. So without calling this function 502 // other callback's call will panic 503 req.statusCallback.Started(req.allocationID, remotePathCB, op, int(remainingSize)) 504 } 505 506 if req.shouldVerify { 507 if req.isEnterprise || (req.authTicket != nil && req.encryptedKey != "") { 508 req.shouldVerify = false 509 } 510 } 511 n := int((endBlock - startBlock + numBlocks - 1) / numBlocks) 512 513 // Buffered channel to hold the blocks as they are downloaded 514 blocks := make(chan blockData, n) 515 516 var ( 517 actualFileHasher hash.Hash 518 isPREAndWholeFile bool 519 ) 520 521 if !req.shouldVerify && (startBlock == 0 && endBlock == chunksPerShard) && shouldVerifyHash { 522 actualFileHasher = md5.New() 523 isPREAndWholeFile = true 524 } 525 526 toSync := false 527 if _, ok := req.fileHandler.(*sys.MemChanFile); ok { 528 toSync = true 529 } 530 var writerAt bool 531 writeAtHandler, ok := req.fileHandler.(io.WriterAt) 532 if ok { 533 writerAt = true 534 } 535 bufBlocks := int(numBlocks) 536 if n == 1 && endBlock-startBlock < numBlocks { 537 bufBlocks = int(endBlock - startBlock) 538 } 539 if !req.shouldVerify { 540 var pos uint64 541 req.bufferMap = make(map[int]zboxutil.DownloadBuffer) 542 defer func() { 543 l.Logger.Debug("Clearing download buffers: ", len(req.bufferMap)) 544 for ind, rb := range req.bufferMap { 545 rb.ClearBuffer() 546 delete(req.bufferMap, ind) 547 } 548 req.bufferMap = nil 549 }() 550 sz := downloadWorkerCount + extraCount 551 if sz > n { 552 sz = n 553 } 554 555 for i := req.downloadMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 556 pos = uint64(i.TrailingZeros()) 557 blobberIdx := int(pos) 558 if writerAt { 559 req.bufferMap[blobberIdx] = zboxutil.NewDownloadBufferWithChan(sz, bufBlocks, req.effectiveBlockSize) 560 } else { 561 bufMask := zboxutil.NewDownloadBufferWithMask(sz, bufBlocks, req.effectiveBlockSize) 562 bufMask.SetNumBlocks(int(numBlocks)) 563 req.bufferMap[blobberIdx] = bufMask 564 } 565 } 566 } 567 // reset mask to number of active blobbers, not it denotes index of download queue and not blobber index 568 activeBlobbers := req.downloadMask.CountOnes() 569 req.downloadMask = zboxutil.NewUint128(1).Lsh(uint64(activeBlobbers)).Sub64(1) 570 571 logger.Logger.Info( 572 fmt.Sprintf("Downloading file with size: %d from start block: %d and end block: %d. "+ 573 "Blocks per blobber: %d remainingSize: %d and total requests: %d", size, req.startBlock, req.endBlock, blocksPerShard, remainingSize, n), 574 ) 575 576 writeCtx, writeCancel := context.WithCancel(ctx) 577 defer writeCancel() 578 var wg sync.WaitGroup 579 580 if !writerAt { 581 wg.Add(1) 582 // Handle writing the blocks in order as soon as they are downloaded 583 go func() { 584 defer wg.Done() 585 buffer := make(map[int][][][]byte) 586 for i := 0; i < n; i++ { 587 select { 588 case <-writeCtx.Done(): 589 goto breakLoop 590 default: 591 } 592 if data, ok := buffer[i]; ok { 593 // If the block we need to write next is already in the buffer, write it 594 hashWg := &sync.WaitGroup{} 595 if isPREAndWholeFile { 596 if i == n-1 { 597 writeData(actualFileHasher, data, req.datashards, int(remainingSize)) //nolint 598 if calculatedFileHash, ok := checkHash(actualFileHasher, fRef, req.contentMode); !ok { 599 req.errorCB(fmt.Errorf("Expected actual file hash %s, calculated file hash %s", 600 fRef.ActualFileHash, calculatedFileHash), remotePathCB) 601 return 602 } 603 } else { 604 hashWg.Add(1) 605 go func() { 606 writeData(actualFileHasher, data, req.datashards, int(remainingSize)) //nolint 607 hashWg.Done() 608 }() 609 } 610 } 611 612 totalWritten, err := writeData(req.fileHandler, data, req.datashards, int(remainingSize)) 613 if err != nil { 614 req.errorCB(errors.Wrap(err, "Write file failed"), remotePathCB) 615 return 616 } 617 if toSync { 618 req.fileHandler.Sync() //nolint 619 } 620 621 if isPREAndWholeFile { 622 hashWg.Wait() 623 } 624 for _, rb := range req.bufferMap { 625 rb.ReleaseChunk(int(startBlock + int64(i)*numBlocks)) 626 } 627 downloaded = downloaded + totalWritten 628 remainingSize -= int64(totalWritten) 629 630 if req.statusCallback != nil { 631 req.statusCallback.InProgress(req.allocationID, remotePathCB, op, downloaded, nil) 632 } 633 634 // Remove the block from the buffer 635 delete(buffer, i) 636 } else { 637 // If the block we need to write next is not in the buffer, wait for it 638 for block := range blocks { 639 if block.blockNum == i { 640 // Write the data 641 hashWg := &sync.WaitGroup{} 642 if isPREAndWholeFile { 643 if i == n-1 { 644 writeData(actualFileHasher, block.data, req.datashards, int(remainingSize)) //nolint 645 if calculatedFileHash, ok := checkHash(actualFileHasher, fRef, req.contentMode); !ok { 646 req.errorCB(fmt.Errorf("Expected actual file hash %s, calculated file hash %s", 647 fRef.ActualFileHash, calculatedFileHash), remotePathCB) 648 return 649 } 650 } else { 651 hashWg.Add(1) 652 go func() { 653 writeData(actualFileHasher, block.data, req.datashards, int(remainingSize)) //nolint 654 hashWg.Done() 655 }() 656 } 657 } 658 659 totalWritten, err := writeData(req.fileHandler, block.data, req.datashards, int(remainingSize)) 660 if err != nil { 661 req.errorCB(errors.Wrap(err, "Write file failed"), remotePathCB) 662 return 663 } 664 665 if toSync { 666 req.fileHandler.Sync() //nolint 667 } 668 669 if isPREAndWholeFile { 670 hashWg.Wait() 671 } 672 for _, rb := range req.bufferMap { 673 rb.ReleaseChunk(int(startBlock + int64(i)*numBlocks)) 674 } 675 676 downloaded = downloaded + totalWritten 677 remainingSize -= int64(totalWritten) 678 679 if req.statusCallback != nil { 680 req.statusCallback.InProgress(req.allocationID, remotePathCB, op, downloaded, nil) 681 } 682 683 break 684 } else { 685 // If this block is not the one we're waiting for, store it in the buffer 686 buffer[block.blockNum] = block.data 687 } 688 } 689 } 690 } 691 breakLoop: 692 }() 693 } 694 if req.downloadStorer != nil { 695 storerCtx, storerCancel := context.WithCancel(ctx) 696 defer storerCancel() 697 req.downloadStorer.Start(storerCtx) 698 } 699 700 var progressLock sync.Mutex 701 firstReqWG := sync.WaitGroup{} 702 firstReqWG.Add(1) 703 eg, egCtx := errgroup.WithContext(ctx) 704 eg.SetLimit(downloadWorkerCount + extraCount) 705 for i := 0; i < n; i++ { 706 j := i 707 if i == 1 { 708 firstReqWG.Wait() 709 sort.Slice(req.downloadQueue, req.downloadQueue.Less) 710 } 711 select { 712 case <-egCtx.Done(): 713 goto breakDownloadLoop 714 default: 715 } 716 eg.Go(func() error { 717 718 if j == 0 { 719 defer firstReqWG.Done() 720 } 721 blocksToDownload := numBlocks 722 if startBlock+int64(j)*numBlocks+numBlocks > endBlock { 723 blocksToDownload = endBlock - (startBlock + int64(j)*numBlocks) 724 } 725 data, err := req.getBlocksData(startBlock+int64(j)*numBlocks, blocksToDownload, j == 0) 726 if req.isDownloadCanceled { 727 return errors.New("download_abort", "Download aborted by user") 728 } 729 if err != nil { 730 return errors.Wrap(err, fmt.Sprintf("Download failed for block %d. ", startBlock+int64(j)*numBlocks)) 731 } 732 if !writerAt { 733 blocks <- blockData{blockNum: j, data: data} 734 } else { 735 var offset int64 736 if req.downloadStorer != nil { 737 offset = (startBlock + int64(j)*numBlocks) * int64(req.effectiveBlockSize) * int64(req.datashards) 738 } else { 739 offset = int64(j) * numBlocks * int64(req.effectiveBlockSize) * int64(req.datashards) 740 } 741 var total int 742 if j == n-1 { 743 total, err = writeAtData(writeAtHandler, data, req.datashards, offset, int(remainingSize-offset)) 744 } else { 745 total, err = writeAtData(writeAtHandler, data, req.datashards, offset, -1) 746 } 747 if err != nil { 748 logger.Logger.Error("downloadFailed: ", startBlock+int64(j)*numBlocks, " remainingSize: ", remainingSize, " offset: ", offset) 749 return errors.Wrap(err, fmt.Sprintf("WriteAt failed for block %d. ", startBlock+int64(j)*numBlocks)) 750 } 751 for _, rb := range req.bufferMap { 752 rb.ReleaseChunk(int(startBlock + int64(j)*numBlocks)) 753 } 754 if req.downloadStorer != nil { 755 go req.downloadStorer.Update(int(startBlock + int64(j)*numBlocks + blocksToDownload)) 756 } 757 if req.statusCallback != nil { 758 progressLock.Lock() 759 downloaded += total 760 req.statusCallback.InProgress(req.allocationID, remotePathCB, op, int(downloaded), nil) 761 progressLock.Unlock() 762 } 763 } 764 return nil 765 }) 766 breakDownloadLoop: 767 } 768 if err := eg.Wait(); err != nil { 769 writeCancel() 770 close(blocks) 771 wg.Wait() 772 req.errorCB(err, remotePathCB) 773 return 774 } 775 776 close(blocks) 777 wg.Wait() 778 // req.fileHandler.Sync() //nolint 779 elapsedGetBlocksAndWrite := time.Since(now) - elapsedInitEC - elapsedInitEncryption 780 l.Logger.Debug(fmt.Sprintf("[processDownload] Timings:\n allocation_id: %s,\n remotefilepath: %s,\n initEC: %d ms,\n initEncryption: %d ms,\n getBlocks and writes: %d ms", 781 req.allocationID, 782 req.remotefilepath, 783 elapsedInitEC.Milliseconds(), 784 elapsedInitEncryption.Milliseconds(), 785 elapsedGetBlocksAndWrite.Milliseconds(), 786 )) 787 788 if req.statusCallback != nil && !req.skip { 789 req.statusCallback.Completed( 790 req.allocationID, remotePathCB, fRef.Name, fRef.MimeType, int(size), op) 791 } 792 if req.downloadStorer != nil { 793 req.downloadStorer.Remove() //nolint:errcheck 794 } 795 } 796 797 func checkHash(actualFileHasher hash.Hash, fref *fileref.FileRef, contentMode string) (string, bool) { 798 calculatedFileHash := hex.EncodeToString(actualFileHasher.Sum(nil)) 799 if contentMode == DOWNLOAD_CONTENT_THUMB { 800 return calculatedFileHash, calculatedFileHash == fref.ActualThumbnailHash 801 } else { 802 return calculatedFileHash, calculatedFileHash == fref.ActualFileHash 803 } 804 } 805 806 func (req *DownloadRequest) submitReadMarker(blobber *blockchain.StorageNode, readCount int64) (err error) { 807 var retryCount = 3 808 for retryCount > 0 { 809 if err = req.attemptSubmitReadMarker(blobber, readCount); err != nil { 810 logger.Logger.Error(fmt.Sprintf("Error while attempting to submit readmarker %v, retry: %d", err, retryCount)) 811 if IsErrCode(err, NotEnoughTokens) || IsErrCode(err, InvalidAuthTicket) || IsErrCode(err, InvalidShare) { 812 return err 813 } 814 if IsErrCode(err, LockExists) || IsErrCode(err, RateLimitError) { 815 continue 816 } 817 retryCount-- 818 } else { 819 return nil 820 } 821 } 822 blobber.SetSkip(true) 823 return fmt.Errorf("submit read marker failed after retries: %w", err) 824 } 825 826 func (req *DownloadRequest) attemptSubmitReadMarker(blobber *blockchain.StorageNode, readCount int64) error { 827 lockBlobberReadCtr(req.allocationID, blobber.ID) 828 defer unlockBlobberReadCtr(req.allocationID, blobber.ID) 829 rm := &marker.ReadMarker{ 830 ClientID: client.GetClientID(), 831 ClientPublicKey: client.GetClientPublicKey(), 832 BlobberID: blobber.ID, 833 AllocationID: req.allocationID, 834 OwnerID: req.allocOwnerID, 835 Timestamp: common.Now(), 836 ReadCounter: getBlobberReadCtr(req.allocationID, blobber.ID) + readCount, 837 SessionRC: readCount, 838 } 839 err := rm.Sign() 840 if err != nil { 841 return fmt.Errorf("error signing read marker: %w", err) 842 } 843 logger.Logger.Debug(fmt.Sprintf("Attempting to submit RM: ReadCounter: %d, SessionRC: %d, BlobberID: %v", rm.ReadCounter, rm.SessionRC, rm.BlobberID)) 844 rmData, err := json.Marshal(rm) 845 if err != nil { 846 return fmt.Errorf("error marshaling read marker: %w", err) 847 } 848 httpreq, err := zboxutil.NewRedeemRequest(blobber.Baseurl, req.allocationID, req.allocationTx) 849 if err != nil { 850 return fmt.Errorf("error creating download request: %w", err) 851 } 852 853 header := &DownloadRequestHeader{ 854 PathHash: req.remotefilepathhash, 855 ReadMarker: rmData, 856 ConnectionID: req.connectionID, 857 } 858 header.ToHeader(httpreq) 859 860 ctx, cancel := context.WithTimeout(req.ctx, 30*time.Second) 861 defer cancel() 862 863 err = zboxutil.HttpDo(ctx, cancel, httpreq, func(resp *http.Response, err error) error { 864 if err != nil { 865 return err 866 } 867 if resp.Body != nil { 868 defer resp.Body.Close() 869 } 870 871 if resp.StatusCode == http.StatusTooManyRequests { 872 logger.Logger.Info(blobber.Baseurl, 873 " got too many request error. Retrying") 874 var r int 875 r, err = zboxutil.GetRateLimitValue(resp) 876 if err != nil { 877 logger.Logger.Error(err) 878 return errors.New("rate_limit_error", "Error while getting rate limit value") 879 } 880 time.Sleep(time.Duration(r) * time.Second) 881 return errors.New("rate_limit_error", "Too many requests") 882 } 883 884 if resp.StatusCode != http.StatusOK { 885 return req.handleReadMarkerError(resp, blobber, rm) 886 } 887 incBlobberReadCtr(req.allocationID, blobber.ID, readCount) 888 889 logger.Logger.Debug("Submit readmarker 200 OK") 890 891 return nil 892 }) 893 return err 894 } 895 896 func (req *DownloadRequest) handleReadMarkerError(resp *http.Response, blobber *blockchain.StorageNode, rm *marker.ReadMarker) error { 897 respBody, err := ioutil.ReadAll(resp.Body) 898 if err != nil { 899 return err 900 } 901 902 appErrorCode := resp.Header.Get("X-App-Error-Code") 903 if appErrorCode != "" { 904 if appErrorCode == NotEnoughTokens { 905 logger.Logger.Debug(fmt.Sprintf("NotEnoughTokens - blobberID: %v", blobber.ID)) 906 blobber.SetSkip(true) 907 return errors.New(NotEnoughTokens, string(respBody)) 908 } 909 if appErrorCode == InvalidAuthTicket { 910 logger.Logger.Debug(fmt.Sprintf("InvalidAuthTicket - blobberID: %v", blobber.ID)) 911 blobber.SetSkip(true) 912 return errors.New(InvalidAuthTicket, string(respBody)) 913 } 914 if appErrorCode == InvalidShare { 915 logger.Logger.Debug(fmt.Sprintf("InvalidShare - blobberID: %v", blobber.ID)) 916 blobber.SetSkip(true) 917 return errors.New(InvalidShare, string(respBody)) 918 } 919 if appErrorCode == LockExists { 920 logger.Logger.Debug(fmt.Sprintf("LockExists - blobberID: %v", blobber.ID)) 921 time.Sleep(time.Second * 1) 922 return errors.New(LockExists, string(respBody)) 923 } 924 } 925 926 var rspData downloadBlock 927 if err = json.Unmarshal(respBody, &rspData); err == nil && rspData.LatestRM != nil { 928 if err := rm.ValidateWithOtherRM(rspData.LatestRM); err != nil { 929 return err 930 } 931 932 lastBlobberReadCounter := getBlobberReadCtr(req.allocationID, blobber.ID) 933 if rspData.LatestRM.ReadCounter != lastBlobberReadCounter { 934 setBlobberReadCtr(req.allocationID, blobber.ID, rspData.LatestRM.ReadCounter) 935 return fmt.Errorf("stale_read_marker: readmarker counter is not in sync with latest counter. Last blobber read counter: %d, but readmarker's counter was: %d", rspData.LatestRM.ReadCounter, lastBlobberReadCounter) 936 } 937 return fmt.Errorf("download_error: response status: %d, error: %v", resp.StatusCode, rspData.err) 938 } 939 940 return fmt.Errorf("response_error: %s", string(respBody)) 941 } 942 943 func IsErrCode(err error, code string) bool { 944 if err == nil { 945 return false 946 } 947 if e, ok := err.(*errors.Error); ok && e.Code == code { 948 return true 949 } 950 return strings.Contains(err.Error(), code) 951 } 952 953 // initEC will initialize erasure encoder/decoder 954 func (req *DownloadRequest) initEC() error { 955 var err error 956 req.ecEncoder, err = reedsolomon.New( 957 req.datashards, req.parityshards, 958 reedsolomon.WithAutoGoroutines(int(req.effectiveBlockSize))) 959 960 if err != nil { 961 return errors.New("init_ec", 962 fmt.Sprintf("Got error %s, while initializing erasure encoder", err.Error())) 963 } 964 return nil 965 } 966 967 // initEncryption will initialize encScheme with client's keys 968 func (req *DownloadRequest) initEncryption() (err error) { 969 req.encScheme = encryption.NewEncryptionScheme() 970 mnemonic := client.GetClient().Mnemonic 971 if mnemonic != "" { 972 _, err = req.encScheme.Initialize(client.GetClient().Mnemonic) 973 if err != nil { 974 return err 975 } 976 } else { 977 return errors.New("invalid_mnemonic", "Invalid mnemonic") 978 } 979 980 err = req.encScheme.InitForDecryption("filetype:audio", req.encryptedKey) 981 if err != nil { 982 return err 983 } 984 return nil 985 } 986 987 func (req *DownloadRequest) errorCB(err error, remotePathCB string) { 988 var op = OpDownload 989 if req.contentMode == DOWNLOAD_CONTENT_THUMB { 990 op = opThumbnailDownload 991 } 992 if req.downloadStorer != nil && !strings.Contains(err.Error(), "context canceled") { 993 req.downloadStorer.Remove() //nolint: errcheck 994 } 995 if req.skip { 996 return 997 } 998 req.skip = true 999 if req.localFilePath != "" { 1000 if info, err := req.fileHandler.Stat(); err == nil && info.Size() == 0 { 1001 os.Remove(req.localFilePath) //nolint: errcheck 1002 } 1003 } 1004 if req.fileHandler != nil { 1005 req.fileHandler.Close() //nolint: errcheck 1006 } 1007 if req.statusCallback != nil { 1008 req.statusCallback.Error( 1009 req.allocationID, remotePathCB, op, err) 1010 } 1011 } 1012 1013 func (req *DownloadRequest) calculateShardsParams( 1014 fRef *fileref.FileRef) (chunksPerShard int64, err error) { 1015 1016 size := fRef.ActualFileSize 1017 if req.contentMode == DOWNLOAD_CONTENT_THUMB { 1018 if fRef.ActualThumbnailSize == 0 { 1019 return 0, errors.New("invalid_request", "Thumbnail does not exist") 1020 } 1021 size = fRef.ActualThumbnailSize 1022 } 1023 req.size = size 1024 req.encryptedKey = fRef.EncryptedKey 1025 req.chunkSize = int(fRef.ChunkSize) 1026 1027 effectivePerShardSize := (size + int64(req.datashards) - 1) / int64(req.datashards) 1028 effectiveBlockSize := fRef.ChunkSize 1029 if fRef.EncryptedKey != "" { 1030 effectiveBlockSize -= EncryptionHeaderSize + EncryptedDataPaddingSize 1031 } 1032 1033 req.effectiveBlockSize = int(effectiveBlockSize) 1034 1035 chunksPerShard = (effectivePerShardSize + effectiveBlockSize - 1) / effectiveBlockSize 1036 1037 info, err := req.fileHandler.Stat() 1038 if err != nil { 1039 return 0, err 1040 } 1041 // Can be nil when using file writer in wasm 1042 if info != nil { 1043 if req.downloadStorer != nil { 1044 err = sys.Files.MkdirAll(filepath.Join(req.workdir, "download"), 0766) 1045 if err != nil { 1046 return 0, err 1047 } 1048 progressID := req.progressID() 1049 var dp *DownloadProgress 1050 if info.Size() > 0 { 1051 dp = req.downloadStorer.Load(progressID, int(req.numBlocks)) 1052 } 1053 if dp != nil { 1054 req.startBlock = int64(dp.LastWrittenBlock) 1055 if req.startBlock > 0 { 1056 req.isResume = true 1057 } 1058 } else { 1059 dp = &DownloadProgress{ 1060 ID: progressID, 1061 numBlocks: int(req.numBlocks), 1062 } 1063 req.downloadStorer.Save(dp) 1064 } 1065 } 1066 } 1067 1068 if req.endBlock == 0 || req.endBlock > chunksPerShard { 1069 req.endBlock = chunksPerShard 1070 } 1071 1072 if req.startBlock >= req.endBlock { 1073 err = errors.New("invalid_block_num", "start block should be less than end block") 1074 return 0, err 1075 } 1076 1077 return 1078 } 1079 1080 type blobberFile struct { 1081 validationRoot []byte 1082 size int64 1083 } 1084 1085 func GetFileRefFromBlobber(allocationID, blobberId, remotePath string) (fRef *fileref.FileRef, err error) { 1086 wg := &sync.WaitGroup{} 1087 wg.Add(1) 1088 blobber, err := GetBlobber(blobberId) 1089 if err != nil { 1090 return nil, err 1091 } 1092 1093 a, err := GetAllocation(allocationID) 1094 if err != nil { 1095 return nil, err 1096 } 1097 1098 ctx := context.Background() 1099 listReq := &ListRequest{} 1100 1101 listReq.allocationID = a.ID 1102 listReq.allocationTx = a.Tx 1103 listReq.sig = a.sig 1104 listReq.blobbers = []*blockchain.StorageNode{ 1105 {ID: string(blobber.ID), Baseurl: blobber.BaseURL}, 1106 } 1107 listReq.fullconsensus = 1 1108 listReq.consensusThresh = 1 1109 listReq.ctx = ctx 1110 listReq.remotefilepath = remotePath 1111 1112 rspCh := make(chan *fileMetaResponse, 1) 1113 go listReq.getFileMetaInfoFromBlobber(listReq.blobbers[0], 0, rspCh) 1114 resp := <-rspCh 1115 return resp.fileref, resp.err 1116 } 1117 1118 func (req *DownloadRequest) getFileRef() (fRef *fileref.FileRef, err error) { 1119 listReq := &ListRequest{ 1120 remotefilepath: req.remotefilepath, 1121 remotefilepathhash: req.remotefilepathhash, 1122 allocationID: req.allocationID, 1123 allocationTx: req.allocationTx, 1124 sig: req.sig, 1125 blobbers: req.blobbers, 1126 authToken: req.authTicket, 1127 Consensus: Consensus{ 1128 RWMutex: &sync.RWMutex{}, 1129 fullconsensus: req.fullconsensus, 1130 consensusThresh: req.consensusThresh, 1131 }, 1132 ctx: req.ctx, 1133 } 1134 1135 fMetaResp := listReq.getFileMetaFromBlobbers() 1136 1137 fRef, err = req.getFileMetaConsensus(fMetaResp) 1138 if err != nil { 1139 return 1140 } 1141 1142 if fRef.Type == fileref.DIRECTORY { 1143 err = errors.New("invalid_operation", "cannot download directory") 1144 return nil, err 1145 } 1146 return 1147 } 1148 1149 // getFileMetaConsensus will verify actual file hash signature and take consensus in it. 1150 // Then it will use the signature to calculation validation root signature and verify signature 1151 // of validation root send by the blobber. 1152 func (req *DownloadRequest) getFileMetaConsensus(fMetaResp []*fileMetaResponse) (*fileref.FileRef, error) { 1153 var selected *fileMetaResponse 1154 foundMask := zboxutil.NewUint128(0) 1155 req.consensus = 0 1156 retMap := make(map[string]int) 1157 for _, fmr := range fMetaResp { 1158 if fmr.err != nil || fmr.fileref == nil { 1159 continue 1160 } 1161 actualHash := fmr.fileref.ActualFileHash 1162 actualFileHashSignature := fmr.fileref.ActualFileHashSignature 1163 1164 isValid, err := sys.VerifyWith( 1165 req.allocOwnerPubKey, 1166 actualFileHashSignature, 1167 actualHash, 1168 ) 1169 if err != nil { 1170 l.Logger.Error(err) 1171 continue 1172 } 1173 if !isValid { 1174 l.Logger.Error("invalid signature") 1175 continue 1176 } 1177 1178 retMap[actualFileHashSignature]++ 1179 if retMap[actualFileHashSignature] > req.consensus { 1180 req.consensus = retMap[actualFileHashSignature] 1181 } 1182 if req.isConsensusOk() { 1183 selected = fmr 1184 break 1185 } 1186 } 1187 1188 if selected == nil { 1189 l.Logger.Error("File consensus not found for ", req.remotefilepath) 1190 return nil, errors.New("consensus_not_met", "") 1191 } 1192 1193 req.validationRootMap = make(map[string]*blobberFile) 1194 blobberCount := 0 1195 countThreshold := req.consensusThresh + 1 1196 if countThreshold > req.fullconsensus { 1197 countThreshold = req.consensusThresh 1198 } 1199 if req.freeRead { 1200 countThreshold = req.fullconsensus 1201 } 1202 for i := 0; i < len(fMetaResp); i++ { 1203 fmr := fMetaResp[i] 1204 if fmr.err != nil || fmr.fileref == nil { 1205 continue 1206 } 1207 fRef := fmr.fileref 1208 1209 if selected.fileref.ActualFileHashSignature != fRef.ActualFileHashSignature { 1210 continue 1211 } 1212 if !req.isEnterprise { 1213 isValid, err := sys.VerifyWith( 1214 req.allocOwnerPubKey, 1215 fRef.ValidationRootSignature, 1216 fRef.ActualFileHashSignature+fRef.ValidationRoot, 1217 ) 1218 if err != nil { 1219 l.Logger.Error(err, "allocOwnerPubKey: ", req.allocOwnerPubKey, " validationRootSignature: ", fRef.ValidationRootSignature, " actualFileHashSignature: ", fRef.ActualFileHashSignature, " validationRoot: ", fRef.ValidationRoot) 1220 continue 1221 } 1222 if !isValid { 1223 l.Logger.Error("invalid validation root signature") 1224 continue 1225 } 1226 1227 blobber := req.blobbers[fmr.blobberIdx] 1228 vr, _ := hex.DecodeString(fmr.fileref.ValidationRoot) 1229 req.validationRootMap[blobber.ID] = &blobberFile{ 1230 size: fmr.fileref.Size, 1231 validationRoot: vr, 1232 } 1233 } 1234 shift := zboxutil.NewUint128(1).Lsh(uint64(fmr.blobberIdx)) 1235 foundMask = foundMask.Or(shift) 1236 req.downloadQueue[fmr.blobberIdx] = downloadPriority{ 1237 blobberIdx: fmr.blobberIdx, 1238 timeTaken: 60000, 1239 } 1240 blobberCount++ 1241 if blobberCount == countThreshold { 1242 break 1243 } 1244 } 1245 req.consensus = foundMask.CountOnes() 1246 if !req.isConsensusOk() { 1247 return nil, fmt.Errorf("consensus_not_met") 1248 } 1249 req.downloadMask = foundMask 1250 sort.Slice(req.downloadQueue, req.downloadQueue.Less) 1251 return selected.fileref, nil 1252 } 1253 1254 func (req *DownloadRequest) processDownloadRequest() { 1255 remotePathCB := req.remotefilepath 1256 if remotePathCB == "" { 1257 remotePathCB = req.remotefilepathhash 1258 } 1259 if req.startBlock < 0 || req.endBlock < 0 { 1260 req.errorCB( 1261 fmt.Errorf("start block or end block or both cannot be negative."), remotePathCB, 1262 ) 1263 return 1264 } 1265 fRef, err := req.getFileRef() 1266 if err != nil { 1267 logger.Logger.Error(err.Error()) 1268 req.errorCB( 1269 fmt.Errorf("Error while getting file ref. Error: %v", 1270 err), remotePathCB) 1271 1272 return 1273 } 1274 req.fRef = fRef 1275 chunksPerShard, err := req.calculateShardsParams(fRef) 1276 if err != nil { 1277 logger.Logger.Error(err.Error()) 1278 req.errorCB( 1279 fmt.Errorf("Error while calculating shard params. Error: %v", 1280 err), remotePathCB) 1281 return 1282 } 1283 req.chunksPerShard = chunksPerShard 1284 startBlock, endBlock := req.startBlock, req.endBlock 1285 // remainingSize should be calculated based on startBlock number 1286 // otherwise end data will have null bytes. 1287 remainingSize := req.size - startBlock*int64(req.effectiveBlockSize) 1288 1289 var wantSize int64 1290 if endBlock*int64(req.effectiveBlockSize) < req.size { 1291 wantSize = endBlock*int64(req.effectiveBlockSize) - startBlock*int64(req.effectiveBlockSize) 1292 } else { 1293 wantSize = remainingSize 1294 } 1295 1296 if remainingSize <= 0 { 1297 logger.Logger.Error("Nothing to download") 1298 req.errorCB( 1299 fmt.Errorf("Size to download is %d. Nothing to download", remainingSize), remotePathCB, 1300 ) 1301 return 1302 } 1303 1304 blocksPerShard := (wantSize + int64(req.effectiveBlockSize) - 1) / int64(req.effectiveBlockSize) 1305 req.blocksPerShard = blocksPerShard 1306 } 1307 1308 func (req *DownloadRequest) Seek(offset int64, whence int) (int64, error) { 1309 switch whence { 1310 case io.SeekStart: 1311 if offset >= req.size { 1312 return 0, errors.New(ExceededMaxOffsetValue, "file is already downloaded") 1313 } 1314 req.offset = offset 1315 case io.SeekCurrent: 1316 if req.offset+offset >= req.size { 1317 return 0, errors.New(ExceededMaxOffsetValue, "") 1318 } 1319 req.offset += offset 1320 case io.SeekEnd: 1321 newOffset := req.size - offset 1322 if newOffset < 0 { 1323 return 0, errors.New(NegativeOffsetResultantValue, "") 1324 } 1325 req.offset = offset 1326 default: 1327 return 0, errors.New(InvalidWhenceValue, 1328 fmt.Sprintf("expected 0, 1 or 2, provided %d", whence)) 1329 } 1330 return req.offset, nil 1331 } 1332 1333 func writeData(dest io.Writer, data [][][]byte, dataShards, remaining int) (int, error) { 1334 total := 0 1335 if len(data) == 0 { 1336 return 0, errors.New(InvalidWhenceValue, "data cannot be empty") 1337 } 1338 if dest == nil { 1339 return 0, errors.New(InvalidWhenceValue, "destination writer cannot be nil") 1340 } 1341 for i := 0; i < len(data); i++ { 1342 for j := 0; j < dataShards; j++ { 1343 if len(data[i][j]) <= remaining { 1344 n, err := dest.Write(data[i][j]) 1345 total += n 1346 if err != nil { 1347 return total, err 1348 } 1349 } else { 1350 n, err := dest.Write(data[i][j][:remaining]) 1351 total += n 1352 if err != nil { 1353 return total, err 1354 } 1355 } 1356 remaining -= len(data[i][j]) 1357 if remaining <= 0 { 1358 return total, nil 1359 } 1360 } 1361 } 1362 return total, nil 1363 } 1364 1365 func writeAtData(dest io.WriterAt, data [][][]byte, dataShards int, offset int64, lastBlock int) (int, error) { 1366 var total int 1367 for i := 0; i < len(data); i++ { 1368 for j := 0; j < dataShards; j++ { 1369 if lastBlock != -1 { 1370 if len(data[i][j]) <= lastBlock { 1371 n, err := dest.WriteAt(data[i][j], offset+int64(total)) 1372 total += n 1373 if err != nil { 1374 logger.Logger.Error("writeAt failed: ", err, " offset: ", offset, " total: ", total, "toWriteData: ", len(data[i][j]), " lastBlock: ", lastBlock) 1375 return total, err 1376 } 1377 } else { 1378 n, err := dest.WriteAt(data[i][j][:lastBlock], offset+int64(total)) 1379 total += n 1380 if err != nil { 1381 logger.Logger.Error("writeAt failed: ", err, " offset: ", offset, " total: ", total, "toWriteData: ", len(data[i][j]), " lastBlock: ", lastBlock) 1382 return total, err 1383 } 1384 } 1385 lastBlock -= len(data[i][j]) 1386 if lastBlock <= 0 { 1387 return total, nil 1388 } 1389 } else { 1390 n, err := dest.WriteAt(data[i][j], offset+int64(total)) 1391 total += n 1392 if err != nil { 1393 logger.Logger.Error("writeAt failed: ", err, " offset: ", offset, " total: ", total) 1394 return total, err 1395 } 1396 } 1397 } 1398 } 1399 return total, nil 1400 } 1401 1402 func (dr *DownloadRequest) progressID() string { 1403 1404 if len(dr.allocationID) > 8 { 1405 return filepath.Join(dr.workdir, "download", "d"+dr.allocationID[:8]+"_"+dr.fRef.MetaID()) 1406 } 1407 1408 return filepath.Join(dr.workdir, "download", dr.allocationID+"_"+dr.fRef.MetaID()) 1409 }