github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/allocation.go (about) 1 package sdk 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/base64" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "math" 12 "mime/multipart" 13 "net/http" 14 "net/url" 15 "os" 16 "path" 17 "path/filepath" 18 "strconv" 19 "strings" 20 "sync" 21 "sync/atomic" 22 "time" 23 24 "github.com/0chain/common/core/currency" 25 "github.com/0chain/errors" 26 thrown "github.com/0chain/errors" 27 "github.com/0chain/gosdk/constants" 28 "github.com/0chain/gosdk/core/common" 29 "github.com/0chain/gosdk/core/pathutil" 30 "github.com/0chain/gosdk/core/sys" 31 "github.com/0chain/gosdk/zboxcore/blockchain" 32 "github.com/0chain/gosdk/zboxcore/fileref" 33 "github.com/0chain/gosdk/zboxcore/logger" 34 l "github.com/0chain/gosdk/zboxcore/logger" 35 "github.com/0chain/gosdk/zboxcore/marker" 36 "github.com/0chain/gosdk/zboxcore/zboxutil" 37 "github.com/mitchellh/go-homedir" 38 "go.uber.org/zap" 39 ) 40 41 var ( 42 noBLOBBERS = errors.New("", "No Blobbers set in this allocation") 43 notInitialized = errors.New("sdk_not_initialized", "Please call InitStorageSDK Init and use GetAllocation to get the allocation object") 44 IsWasm = false 45 MultiOpBatchSize = 50 46 RepairBatchSize = 50 47 Workdir string 48 ) 49 50 const ( 51 KB = 1024 52 MB = 1024 * KB 53 GB = 1024 * MB 54 ) 55 56 const ( 57 CanUploadMask = uint16(1) // 0000 0001 58 CanDeleteMask = uint16(2) // 0000 0010 59 CanUpdateMask = uint16(4) // 0000 0100 60 CanMoveMask = uint16(8) // 0000 1000 61 CanCopyMask = uint16(16) // 0001 0000 62 CanRenameMask = uint16(32) // 0010 0000 63 ) 64 65 const ( 66 emptyFileDataHash = "d41d8cd98f00b204e9800998ecf8427e" 67 getRefPageLimit = 100 68 ) 69 70 // Expected success rate is calculated (NumDataShards)*100/(NumDataShards+NumParityShards) 71 72 var GetFileInfo = func(localpath string) (os.FileInfo, error) { 73 return sys.Files.Stat(localpath) 74 } 75 76 // BlobberAllocationStats represents the blobber allocation statistics. 77 type BlobberAllocationStats struct { 78 BlobberID string 79 BlobberURL string 80 ID string `json:"ID"` 81 Tx string `json:"Tx"` 82 TotalSize int64 `json:"TotalSize"` 83 UsedSize int `json:"UsedSize"` 84 OwnerID string `json:"OwnerID"` 85 OwnerPublicKey string `json:"OwnerPublicKey"` 86 Expiration int `json:"Expiration"` 87 AllocationRoot string `json:"AllocationRoot"` 88 BlobberSize int `json:"BlobberSize"` 89 BlobberSizeUsed int `json:"BlobberSizeUsed"` 90 LatestRedeemedWM string `json:"LatestRedeemedWM"` 91 IsRedeemRequired bool `json:"IsRedeemRequired"` 92 CleanedUp bool `json:"CleanedUp"` 93 Finalized bool `json:"Finalized"` 94 Terms []struct { 95 ID int `json:"ID"` 96 BlobberID string `json:"BlobberID"` 97 AllocationID string `json:"AllocationID"` 98 ReadPrice int `json:"ReadPrice"` 99 WritePrice int `json:"WritePrice"` 100 } `json:"Terms"` 101 } 102 103 // ConsolidatedFileMeta represents the file meta data. 104 type ConsolidatedFileMeta struct { 105 Name string 106 Type string 107 Path string 108 LookupHash string 109 Hash string 110 MimeType string 111 Size int64 112 NumBlocks int64 113 ActualFileSize int64 114 ActualNumBlocks int64 115 EncryptedKey string 116 117 ActualThumbnailSize int64 118 ActualThumbnailHash string 119 120 Collaborators []fileref.Collaborator 121 } 122 123 type ConsolidatedFileMetaByName struct { 124 Name string 125 Type string 126 Path string 127 LookupHash string 128 Hash string 129 MimeType string 130 Size int64 131 NumBlocks int64 132 ActualFileSize int64 133 ActualNumBlocks int64 134 EncryptedKey string 135 FileMetaHash string 136 ThumbnailHash string 137 ActualThumbnailSize int64 138 ActualThumbnailHash string 139 Collaborators []fileref.Collaborator 140 CreatedAt common.Timestamp 141 UpdatedAt common.Timestamp 142 } 143 144 type AllocationStats struct { 145 UsedSize int64 `json:"used_size"` 146 NumWrites int64 `json:"num_of_writes"` 147 NumReads int64 `json:"num_of_reads"` 148 TotalChallenges int64 `json:"total_challenges"` 149 OpenChallenges int64 `json:"num_open_challenges"` 150 SuccessChallenges int64 `json:"num_success_challenges"` 151 FailedChallenges int64 `json:"num_failed_challenges"` 152 LastestClosedChallengeTxn string `json:"latest_closed_challenge"` 153 } 154 155 // PriceRange represents a price range allowed by user to filter blobbers. 156 type PriceRange struct { 157 Min uint64 `json:"min"` 158 Max uint64 `json:"max"` 159 } 160 161 // IsValid price range. 162 func (pr *PriceRange) IsValid() bool { 163 return pr.Min <= pr.Max 164 } 165 166 // Terms represents Blobber terms. A Blobber can update its terms, 167 // but any existing offer will use terms of offer signing time. 168 type Terms struct { 169 ReadPrice common.Balance `json:"read_price"` // tokens / GB 170 WritePrice common.Balance `json:"write_price"` // tokens / GB 171 MaxOfferDuration time.Duration `json:"max_offer_duration"` 172 } 173 174 // UpdateTerms represents Blobber terms during update blobber calls. 175 // A Blobber can update its terms, but any existing offer will use terms of offer signing time. 176 type UpdateTerms struct { 177 ReadPrice *common.Balance `json:"read_price,omitempty"` // tokens / GB 178 WritePrice *common.Balance `json:"write_price,omitempty"` // tokens / GB 179 MaxOfferDuration *time.Duration `json:"max_offer_duration,omitempty"` 180 } 181 182 // BlobberAllocation represents the blobber in the context of an allocation 183 type BlobberAllocation struct { 184 BlobberID string `json:"blobber_id"` 185 Size int64 `json:"size"` 186 Terms Terms `json:"terms"` 187 MinLockDemand common.Balance `json:"min_lock_demand"` 188 Spent common.Balance `json:"spent"` 189 Penalty common.Balance `json:"penalty"` 190 ReadReward common.Balance `json:"read_reward"` 191 Returned common.Balance `json:"returned"` 192 ChallengeReward common.Balance `json:"challenge_reward"` 193 FinalReward common.Balance `json:"final_reward"` 194 } 195 196 // Allocation represents a storage allocation. 197 type Allocation struct { 198 // ID is the unique identifier of the allocation. 199 ID string `json:"id"` 200 // Tx is the transaction hash of the latest transaction related to the allocation. 201 Tx string `json:"tx"` 202 203 // DataShards is the number of data shards. 204 DataShards int `json:"data_shards"` 205 206 // ParityShards is the number of parity shards. 207 ParityShards int `json:"parity_shards"` 208 209 // Size is the size of the allocation. 210 Size int64 `json:"size"` 211 212 // Expiration is the expiration date of the allocation. 213 Expiration int64 `json:"expiration_date"` 214 215 // Owner is the id of the owner of the allocation. 216 Owner string `json:"owner_id"` 217 218 // OwnerPublicKey is the public key of the owner of the allocation. 219 OwnerPublicKey string `json:"owner_public_key"` 220 221 // Payer is the id of the payer of the allocation. 222 Payer string `json:"payer_id"` 223 224 // Blobbers is the list of blobbers that store the data of the allocation. 225 Blobbers []*blockchain.StorageNode `json:"blobbers"` 226 227 // Stats contains the statistics of the allocation. 228 Stats *AllocationStats `json:"stats"` 229 230 // TimeUnit is the time unit of the allocation. 231 TimeUnit time.Duration `json:"time_unit"` 232 233 // WritePool is the write pool of the allocation. 234 WritePool common.Balance `json:"write_pool"` 235 236 // BlobberDetails contains real terms used for the allocation. 237 // If the allocation has updated, then terms calculated using 238 // weighted average values. 239 BlobberDetails []*BlobberAllocation `json:"blobber_details"` 240 241 // ReadPriceRange is requested reading prices range. 242 ReadPriceRange PriceRange `json:"read_price_range"` 243 244 // WritePriceRange is requested writing prices range. 245 WritePriceRange PriceRange `json:"write_price_range"` 246 247 // MinLockDemand is the minimum lock demand of the allocation. 248 MinLockDemand float64 `json:"min_lock_demand"` 249 250 // ChallengeCompletionTime is the time taken to complete a challenge. 251 ChallengeCompletionTime time.Duration `json:"challenge_completion_time"` 252 253 // StartTime is the start time of the allocation. 254 StartTime common.Timestamp `json:"start_time"` 255 256 // Finalized is the flag to indicate if the allocation is finalized. 257 Finalized bool `json:"finalized,omitempty"` 258 259 // Cancelled is the flag to indicate if the allocation is cancelled. 260 Canceled bool `json:"canceled,omitempty"` 261 262 // MovedToChallenge is the amount moved to challenge pool related to the allocation. 263 MovedToChallenge common.Balance `json:"moved_to_challenge,omitempty"` 264 265 // MovedBack is the amount moved back from the challenge pool related to the allocation. 266 MovedBack common.Balance `json:"moved_back,omitempty"` 267 268 // MovedToValidators is the amount moved to validators related to the allocation. 269 MovedToValidators common.Balance `json:"moved_to_validators,omitempty"` 270 271 // FileOptions is a bitmask of file options, which are the permissions of the allocation. 272 FileOptions uint16 `json:"file_options"` 273 274 IsEnterprise bool `json:"is_enterprise"` 275 276 // FileOptions to define file restrictions on an allocation for third-parties 277 // default 00000000 for all crud operations suggesting only owner has the below listed abilities. 278 // enabling option/s allows any third party to perform certain ops 279 // 00000001 - 1 - upload 280 // 00000010 - 2 - delete 281 // 00000100 - 4 - update 282 // 00001000 - 8 - move 283 // 00010000 - 16 - copy 284 // 00100000 - 32 - rename 285 ThirdPartyExtendable bool `json:"third_party_extendable"` 286 287 numBlockDownloads int 288 downloadChan chan *DownloadRequest 289 repairChan chan *RepairRequest 290 ctx context.Context 291 ctxCancelF context.CancelFunc 292 mutex *sync.Mutex 293 commitMutex *sync.Mutex 294 downloadProgressMap map[string]*DownloadRequest 295 downloadRequests []*DownloadRequest 296 repairRequestInProgress *RepairRequest 297 initialized bool 298 checkStatus bool 299 readFree bool 300 // conseususes 301 consensusThreshold int 302 fullconsensus int 303 sig string `json:"-"` 304 } 305 306 // OperationRequest represents an operation request with its related options. 307 type OperationRequest struct { 308 OperationType string 309 LocalPath string 310 RemotePath string 311 DestName string // Required only for rename operation 312 DestPath string // Required for copy and move operation 313 IsUpdate bool 314 IsRepair bool // Required for repair operation 315 IsWebstreaming bool 316 EncryptedKey string 317 318 // Required for uploads 319 Workdir string 320 FileMeta FileMeta 321 FileReader io.Reader 322 Mask *zboxutil.Uint128 // Required for delete repair operation 323 DownloadFile bool // Required for upload repair operation 324 StreamUpload bool // Required for streaming file when actualSize is not available 325 CancelCauseFunc context.CancelCauseFunc 326 Opts []ChunkedUploadOption 327 } 328 329 // GetReadPriceRange returns the read price range from the global configuration. 330 func GetReadPriceRange() (PriceRange, error) { 331 return getPriceRange("max_read_price") 332 } 333 334 // GetWritePriceRange returns the write price range from the global configuration. 335 func GetWritePriceRange() (PriceRange, error) { 336 return getPriceRange("max_write_price") 337 } 338 339 func SetMultiOpBatchSize(size int) { 340 MultiOpBatchSize = size 341 } 342 343 func SetWasm() { 344 IsWasm = true 345 BatchSize = 4 346 extraCount = 0 347 RepairBatchSize = 20 348 RepairBlocks = 50 349 } 350 351 // SetCheckStatus sets the check status of the allocation. 352 // - checkStatus: the check status to set. 353 func (a *Allocation) SetCheckStatus(checkStatus bool) { 354 a.checkStatus = checkStatus 355 } 356 357 func getPriceRange(name string) (PriceRange, error) { 358 conf, err := GetStorageSCConfig() 359 if err != nil { 360 return PriceRange{}, err 361 } 362 f := conf.Fields[name] 363 fStr, ok := f.(string) 364 if !ok { 365 return PriceRange{}, fmt.Errorf("type is wrong") 366 } 367 mrp, err := strconv.ParseFloat(fStr, 64) 368 if err != nil { 369 return PriceRange{}, err 370 } 371 coin, err := currency.ParseZCN(mrp) 372 if err != nil { 373 return PriceRange{}, err 374 } 375 max, err := coin.Int64() 376 if err != nil { 377 return PriceRange{}, err 378 } 379 return PriceRange{0, uint64(max)}, err 380 381 } 382 383 // GetStats returns the statistics of the allocation. 384 func (a *Allocation) GetStats() *AllocationStats { 385 return a.Stats 386 } 387 388 // GetBlobberStats returns the statistics of the blobbers in the allocation. 389 func (a *Allocation) GetBlobberStats() map[string]*BlobberAllocationStats { 390 numList := len(a.Blobbers) 391 wg := &sync.WaitGroup{} 392 wg.Add(numList) 393 rspCh := make(chan *BlobberAllocationStats, numList) 394 for _, blobber := range a.Blobbers { 395 go getAllocationDataFromBlobber(blobber, a.ID, a.Tx, rspCh, wg) 396 } 397 wg.Wait() 398 result := make(map[string]*BlobberAllocationStats, len(a.Blobbers)) 399 for i := 0; i < numList; i++ { 400 resp := <-rspCh 401 result[resp.BlobberURL] = resp 402 } 403 return result 404 } 405 406 var downloadWorkerCount = 6 407 408 func SetDownloadWorkerCount(count int) { 409 downloadWorkerCount = count 410 } 411 412 // InitAllocation initializes the allocation. 413 func (a *Allocation) InitAllocation() { 414 a.downloadChan = make(chan *DownloadRequest, 100) 415 a.repairChan = make(chan *RepairRequest, 1) 416 a.ctx, a.ctxCancelF = context.WithCancel(context.Background()) 417 a.downloadProgressMap = make(map[string]*DownloadRequest) 418 a.downloadRequests = make([]*DownloadRequest, 0, 100) 419 a.mutex = &sync.Mutex{} 420 a.commitMutex = &sync.Mutex{} 421 a.fullconsensus, a.consensusThreshold = a.getConsensuses() 422 a.readFree = true 423 if a.ReadPriceRange.Max > 0 { 424 for _, blobberDetail := range a.BlobberDetails { 425 if blobberDetail.Terms.ReadPrice > 0 { 426 a.readFree = false 427 break 428 } 429 } 430 } 431 a.startWorker(a.ctx) 432 InitCommitWorker(a.Blobbers) 433 InitBlockDownloader(a.Blobbers, downloadWorkerCount) 434 a.initialized = true 435 } 436 437 func (a *Allocation) isInitialized() bool { 438 return a.initialized && sdkInitialized 439 } 440 441 func (a *Allocation) startWorker(ctx context.Context) { 442 go a.dispatchWork(ctx) 443 } 444 445 func (a *Allocation) dispatchWork(ctx context.Context) { 446 for { 447 select { 448 case <-ctx.Done(): 449 l.Logger.Info("Upload cancelled by the parent") 450 return 451 case downloadReq := <-a.downloadChan: 452 l.Logger.Info(fmt.Sprintf("received a download request for %v\n", downloadReq.remotefilepath)) 453 go func() { 454 downloadReq.processDownload() 455 }() 456 case repairReq := <-a.repairChan: 457 458 l.Logger.Info(fmt.Sprintf("received a repair request for %v\n", repairReq.listDir.Path)) 459 go repairReq.processRepair(ctx, a) 460 } 461 } 462 } 463 464 // CanUpload returns true if the allocation grants upload operation 465 func (a *Allocation) CanUpload() bool { 466 return (a.FileOptions & CanUploadMask) > 0 467 } 468 469 // CanDelete returns true if the allocation grants delete operation 470 func (a *Allocation) CanDelete() bool { 471 return (a.FileOptions & CanDeleteMask) > 0 472 } 473 474 // CanUpdate returns true if the allocation grants update operation 475 func (a *Allocation) CanUpdate() bool { 476 return (a.FileOptions & CanUpdateMask) > 0 477 } 478 479 // CanMove returns true if the allocation grants move operation 480 func (a *Allocation) CanMove() bool { 481 return (a.FileOptions & CanMoveMask) > 0 482 } 483 484 // CanCopy returns true if the allocation grants copy operation 485 func (a *Allocation) CanCopy() bool { 486 return (a.FileOptions & CanCopyMask) > 0 487 } 488 489 // CanRename returns true if the allocation grants rename operation 490 func (a *Allocation) CanRename() bool { 491 return (a.FileOptions & CanRenameMask) > 0 492 } 493 494 // UpdateFile [Deprecated]please use CreateChunkedUpload 495 func (a *Allocation) UpdateFile(workdir, localpath string, remotepath string, 496 status StatusCallback) error { 497 498 return a.StartChunkedUpload(workdir, localpath, remotepath, status, true, false, "", false, false) 499 } 500 501 // UploadFile [Deprecated]please use CreateChunkedUpload 502 func (a *Allocation) UploadFile(workdir, localpath string, remotepath string, 503 status StatusCallback) error { 504 505 return a.StartChunkedUpload(workdir, localpath, remotepath, status, false, false, "", false, false) 506 } 507 508 // RepairFile repair a file in the allocation. 509 // - file: the file to repair. 510 // - remotepath: the remote path of the file. 511 // - statusCallback: a callback function to get the status of the repair. 512 // - mask: the mask of the repair descriping the blobbers to repair. 513 // - ref: the file reference, a representation of the file in the database. 514 func (a *Allocation) RepairFile(file sys.File, remotepath string, statusCallback StatusCallback, mask zboxutil.Uint128, ref *fileref.FileRef) *OperationRequest { 515 idr, _ := homedir.Dir() 516 if Workdir != "" { 517 idr = Workdir 518 } 519 mask = mask.Not().And(zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1)) 520 fileMeta := FileMeta{ 521 ActualSize: ref.ActualFileSize, 522 MimeType: ref.MimeType, 523 RemoteName: ref.Name, 524 RemotePath: remotepath, 525 } 526 var opts []ChunkedUploadOption 527 if ref.EncryptedKey != "" { 528 opts = []ChunkedUploadOption{ 529 WithMask(mask), 530 WithEncrypt(true), 531 WithStatusCallback(statusCallback), 532 WithEncryptedPoint(ref.EncryptedKeyPoint), 533 WithChunkNumber(RepairBlocks), 534 } 535 } else { 536 opts = []ChunkedUploadOption{ 537 WithMask(mask), 538 WithStatusCallback(statusCallback), 539 WithChunkNumber(RepairBlocks), 540 } 541 } 542 op := &OperationRequest{ 543 OperationType: constants.FileOperationInsert, 544 IsRepair: true, 545 RemotePath: remotepath, 546 Workdir: idr, 547 FileMeta: fileMeta, 548 Opts: opts, 549 FileReader: file, 550 Mask: &mask, 551 EncryptedKey: ref.EncryptedKey, 552 } 553 if ref.ActualFileHash == emptyFileDataHash { 554 op.FileMeta.ActualSize = 0 555 } 556 return op 557 } 558 559 // UpdateFileWithThumbnail [Deprecated]please use CreateChunkedUpload 560 func (a *Allocation) UpdateFileWithThumbnail(workdir, localpath string, remotepath string, 561 thumbnailpath string, status StatusCallback) error { 562 563 return a.StartChunkedUpload(workdir, localpath, remotepath, status, true, false, 564 thumbnailpath, false, false) 565 } 566 567 // UploadFileWithThumbnail [Deprecated]please use CreateChunkedUpload 568 func (a *Allocation) UploadFileWithThumbnail(workdir string, localpath string, 569 remotepath string, thumbnailpath string, 570 status StatusCallback) error { 571 572 return a.StartChunkedUpload(workdir, localpath, remotepath, status, false, false, 573 thumbnailpath, false, false) 574 } 575 576 // EncryptAndUpdateFile [Deprecated]please use CreateChunkedUpload 577 func (a *Allocation) EncryptAndUpdateFile(workdir string, localpath string, remotepath string, 578 status StatusCallback) error { 579 580 return a.StartChunkedUpload(workdir, localpath, remotepath, status, true, false, "", true, false) 581 } 582 583 // EncryptAndUploadFile [Deprecated]please use CreateChunkedUpload 584 func (a *Allocation) EncryptAndUploadFile(workdir string, localpath string, remotepath string, 585 status StatusCallback) error { 586 587 return a.StartChunkedUpload(workdir, localpath, remotepath, status, false, false, "", true, false) 588 } 589 590 // EncryptAndUpdateFileWithThumbnail [Deprecated]please use CreateChunkedUpload 591 func (a *Allocation) EncryptAndUpdateFileWithThumbnail(workdir string, localpath string, 592 remotepath string, thumbnailpath string, status StatusCallback) error { 593 594 return a.StartChunkedUpload(workdir, localpath, remotepath, status, true, false, 595 thumbnailpath, true, false) 596 } 597 598 // EncryptAndUploadFileWithThumbnail [Deprecated]please use CreateChunkedUpload 599 func (a *Allocation) EncryptAndUploadFileWithThumbnail( 600 workdir string, 601 localpath string, 602 remotepath string, 603 thumbnailpath string, 604 605 status StatusCallback, 606 ) error { 607 608 return a.StartChunkedUpload(workdir, 609 localpath, 610 remotepath, 611 status, 612 false, 613 false, 614 thumbnailpath, 615 true, 616 false, 617 ) 618 } 619 620 // StartMultiUpload starts a multi upload operation. 621 // A multi upload operation uploads multiple files to the allocation, given ordered arrays of upload parameters. 622 // The paramteres are ordered in a way that the ith element of each array corresponds to the ith file to upload. 623 // The upload operation is done in parallel. 624 // - workdir: the working directory, where the files are stored. 625 // - localPaths: the local paths of the files to upload. 626 // - fileNames: the names of the files to upload. 627 // - thumbnailPaths: the paths of the thumbnails of the files to upload. 628 // - encrypts: the encryption flags of the files to upload. 629 // - chunkNumbers: the chunk numbers of the files to upload. Chunk number is used to upload the file in chunks. 630 // - remotePaths: the remote paths of the files to upload. 631 // - isUpdate: the update flags of the files to upload. If true, the file is to overwrite an existing file. 632 // - isWebstreaming: the webstreaming flags of the files to upload. 633 // - status: the status callback function. Will be used to gather the status of the upload operations. 634 // 635 // Returns any error encountered during any of the upload operations, or during preparation of the upload operations. 636 func (a *Allocation) StartMultiUpload(workdir string, localPaths []string, fileNames []string, thumbnailPaths []string, encrypts []bool, chunkNumbers []int, remotePaths []string, isUpdate []bool, isWebstreaming []bool, status StatusCallback) error { 637 if len(localPaths) != len(thumbnailPaths) { 638 return errors.New("invalid_value", "length of localpaths and thumbnailpaths must be equal") 639 } 640 if len(localPaths) != len(encrypts) { 641 return errors.New("invalid_value", "length of encrypt not equal to number of files") 642 } 643 if !a.isInitialized() { 644 return notInitialized 645 } 646 647 if !a.CanUpload() { 648 return constants.ErrFileOptionNotPermitted 649 } 650 651 totalOperations := len(localPaths) 652 if totalOperations == 0 { 653 return nil 654 } 655 operationRequests := make([]OperationRequest, totalOperations) 656 for idx, localPath := range localPaths { 657 remotePath := zboxutil.RemoteClean(remotePaths[idx]) 658 isabs := zboxutil.IsRemoteAbs(remotePath) 659 if !isabs { 660 err := thrown.New("invalid_path", "Path should be valid and absolute") 661 return err 662 } 663 fileReader, err := os.Open(localPath) 664 if err != nil { 665 return err 666 } 667 defer fileReader.Close() 668 thumbnailPath := thumbnailPaths[idx] 669 fileName := fileNames[idx] 670 chunkNumber := chunkNumbers[idx] 671 if fileName == "" { 672 return thrown.New("invalid_param", "filename can't be empty") 673 } 674 encrypt := encrypts[idx] 675 676 fileInfo, err := fileReader.Stat() 677 if err != nil { 678 return err 679 } 680 681 mimeType, err := zboxutil.GetFileContentType(path.Ext(fileName), fileReader) 682 if err != nil { 683 return err 684 } 685 686 if !strings.HasSuffix(remotePath, "/") { 687 remotePath = remotePath + "/" 688 } 689 fullRemotePath := zboxutil.GetFullRemotePath(localPath, remotePath) 690 fullRemotePathWithoutName, _ := pathutil.Split(fullRemotePath) 691 fullRemotePath = fullRemotePathWithoutName + "/" + fileName 692 693 fileMeta := FileMeta{ 694 Path: localPath, 695 ActualSize: fileInfo.Size(), 696 MimeType: mimeType, 697 RemoteName: fileName, 698 RemotePath: fullRemotePath, 699 } 700 options := []ChunkedUploadOption{ 701 WithStatusCallback(status), 702 WithEncrypt(encrypt), 703 } 704 if chunkNumber != 0 { 705 options = append(options, WithChunkNumber(chunkNumber)) 706 } 707 if thumbnailPath != "" { 708 buf, err := sys.Files.ReadFile(thumbnailPath) 709 if err != nil { 710 return err 711 } 712 713 options = append(options, WithThumbnail(buf)) 714 } 715 operationRequests[idx] = OperationRequest{ 716 FileMeta: fileMeta, 717 FileReader: fileReader, 718 OperationType: constants.FileOperationInsert, 719 Opts: options, 720 Workdir: workdir, 721 RemotePath: fileMeta.RemotePath, 722 } 723 724 if isUpdate[idx] { 725 operationRequests[idx].OperationType = constants.FileOperationUpdate 726 } 727 if isWebstreaming[idx] { 728 operationRequests[idx].IsWebstreaming = true 729 } 730 731 } 732 err := a.DoMultiOperation(operationRequests) 733 if err != nil { 734 logger.Logger.Error("Error in multi upload ", err.Error()) 735 return err 736 } 737 return nil 738 } 739 740 // StartChunkedUpload starts a chunked upload operation. 741 // A chunked upload operation uploads a file to the allocation in chunks. 742 // - workdir: the working directory, where the file is stored. 743 // - localPath: the local path of the file to upload. 744 // - remotePath: the remote path of the file to upload. 745 // - status: the status callback function. Will be used to gather the status of the upload operation. 746 // - isUpdate: the update flag of the file to upload. If true, the file is to overwrite an existing file. 747 // - isRepair: the repair flag of the file to upload. If true, the file is to repair an existing file. 748 // - thumbnailPath: the path of the thumbnail of the file to upload. 749 // - encryption: the encryption flag of the file to upload. 750 // - webStreaming: the webstreaming flag of the file to upload. 751 // - uploadOpts: the options of the upload operation as operation functions that customize the upload operation. 752 func (a *Allocation) StartChunkedUpload(workdir, localPath string, 753 remotePath string, 754 status StatusCallback, 755 isUpdate bool, 756 isRepair bool, 757 thumbnailPath string, 758 encryption bool, 759 webStreaming bool, 760 uploadOpts ...ChunkedUploadOption, 761 ) error { 762 763 if !a.isInitialized() { 764 return notInitialized 765 } 766 767 if (!isUpdate && !a.CanUpload()) || (isUpdate && !a.CanUpdate()) { 768 return constants.ErrFileOptionNotPermitted 769 } 770 771 fileReader, err := os.Open(localPath) 772 if err != nil { 773 return err 774 } 775 defer fileReader.Close() 776 777 fileInfo, err := fileReader.Stat() 778 if err != nil { 779 return err 780 } 781 782 remotePath = zboxutil.RemoteClean(remotePath) 783 isabs := zboxutil.IsRemoteAbs(remotePath) 784 if !isabs { 785 err = thrown.New("invalid_path", "Path should be valid and absolute") 786 return err 787 } 788 remotePath = zboxutil.GetFullRemotePath(localPath, remotePath) 789 790 _, fileName := pathutil.Split(remotePath) 791 792 mimeType, err := zboxutil.GetFileContentType(path.Ext(fileName), fileReader) 793 if err != nil { 794 return err 795 } 796 797 fileMeta := FileMeta{ 798 Path: localPath, 799 ActualSize: fileInfo.Size(), 800 MimeType: mimeType, 801 RemoteName: fileName, 802 RemotePath: remotePath, 803 } 804 805 options := []ChunkedUploadOption{ 806 WithEncrypt(encryption), 807 WithStatusCallback(status), 808 } 809 options = append(options, uploadOpts...) 810 811 if thumbnailPath != "" { 812 buf, err := sys.Files.ReadFile(thumbnailPath) 813 if err != nil { 814 return err 815 } 816 817 options = append(options, WithThumbnail(buf)) 818 } 819 820 connectionId := zboxutil.NewConnectionId() 821 now := time.Now() 822 ChunkedUpload, err := CreateChunkedUpload(a.ctx, workdir, 823 a, fileMeta, fileReader, 824 isUpdate, isRepair, webStreaming, connectionId, 825 options...) 826 if err != nil { 827 return err 828 } 829 elapsedCreateChunkedUpload := time.Since(now) 830 logger.Logger.Info("[StartChunkedUpload]", zap.String("allocation_id", a.ID), 831 zap.Duration("CreateChunkedUpload", elapsedCreateChunkedUpload)) 832 833 return ChunkedUpload.Start() 834 } 835 836 // GetCurrentVersion retrieves the current version of the allocation. 837 // The version of the allocation is the version of the latest write marker. 838 // The versions are gathered from the blobbers of the allocation. 839 // If the versions are not consistent, the allocation is repaired. 840 // Returns a boolean indicating if the allocation is repaired, and an error if any. 841 // In case of more than 2 versions found, an error is returned. 842 func (a *Allocation) GetCurrentVersion() (bool, error) { 843 //get versions from blobbers 844 845 wg := &sync.WaitGroup{} 846 markerChan := make(chan *RollbackBlobber, len(a.Blobbers)) 847 var errCnt int32 848 for _, blobber := range a.Blobbers { 849 850 wg.Add(1) 851 go func(blobber *blockchain.StorageNode) { 852 853 defer wg.Done() 854 wr, err := GetWritemarker(a.ID, a.Tx, a.sig, blobber.ID, blobber.Baseurl) 855 if err != nil { 856 atomic.AddInt32(&errCnt, 1) 857 logger.Logger.Error("error during getWritemarke", zap.Error(err)) 858 } 859 if wr == nil { 860 markerChan <- nil 861 } else { 862 markerChan <- &RollbackBlobber{ 863 blobber: blobber, 864 lpm: wr, 865 commitResult: &CommitResult{}, 866 } 867 } 868 }(blobber) 869 870 } 871 872 wg.Wait() 873 close(markerChan) 874 875 versionMap := make(map[int64][]*RollbackBlobber) 876 877 for rb := range markerChan { 878 879 if rb == nil || rb.lpm.LatestWM == nil { 880 continue 881 } 882 883 if _, ok := versionMap[rb.lpm.LatestWM.Timestamp]; !ok { 884 versionMap[rb.lpm.LatestWM.Timestamp] = make([]*RollbackBlobber, 0) 885 } 886 887 versionMap[rb.lpm.LatestWM.Timestamp] = append(versionMap[rb.lpm.LatestWM.Timestamp], rb) 888 889 if len(versionMap) > 2 { 890 return false, fmt.Errorf("more than 2 versions found") 891 } 892 893 } 894 // TODO: check how many blobbers can be down 895 if errCnt > 0 { 896 return false, fmt.Errorf("error in getting writemarker from %v blobbers", errCnt) 897 } 898 899 if len(versionMap) == 0 { 900 return true, nil 901 } 902 903 // TODO:return if len(versionMap) == 1 904 905 var prevVersion int64 906 var latestVersion int64 907 908 for version := range versionMap { 909 if prevVersion == 0 { 910 prevVersion = version 911 } else { 912 latestVersion = version 913 } 914 } 915 916 if prevVersion > latestVersion { 917 prevVersion, latestVersion = latestVersion, prevVersion 918 } 919 920 // TODO: Check if allocation can be repaired 921 922 success := true 923 924 // rollback to prev version 925 for _, rb := range versionMap[latestVersion] { 926 927 wg.Add(1) 928 go func(rb *RollbackBlobber) { 929 defer wg.Done() 930 err := rb.processRollback(context.TODO(), a.ID) 931 if err != nil { 932 success = false 933 } 934 }(rb) 935 } 936 937 wg.Wait() 938 939 if !success { 940 return false, fmt.Errorf("error in rollback") 941 } 942 943 return success, nil 944 } 945 946 // RepairRequired checks if a repair is required for the given remotepath in the allocation. 947 // The repair is required if the file is not found in all the blobbers. 948 // Returns the found mask, delete mask, a boolean indicating if the repair is required, and an error if any. 949 // The found mask is a 128-bitmask of the blobbers where the file is found. 950 // The delete mask is a 128-bitmask of the blobbers where the file is not found. 951 // - remotepath: the remote path of the file to check. 952 func (a *Allocation) RepairRequired(remotepath string) (zboxutil.Uint128, zboxutil.Uint128, bool, *fileref.FileRef, error) { 953 if !a.isInitialized() { 954 return zboxutil.Uint128{}, zboxutil.Uint128{}, false, nil, notInitialized 955 } 956 957 listReq := &ListRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 958 listReq.allocationID = a.ID 959 listReq.allocationTx = a.Tx 960 listReq.sig = a.sig 961 listReq.blobbers = a.Blobbers 962 listReq.fullconsensus = a.fullconsensus 963 listReq.consensusThresh = a.DataShards 964 listReq.ctx = a.ctx 965 listReq.remotefilepath = remotepath 966 found, deleteMask, fileRef, _ := listReq.getFileConsensusFromBlobbers() 967 if fileRef == nil { 968 var repairErr error 969 if deleteMask.Equals(zboxutil.NewUint128(0)) { 970 repairErr = errors.New("", "File not found for the given remotepath") 971 } 972 return found, deleteMask, false, fileRef, repairErr 973 } 974 975 uploadMask := zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1) 976 977 return found, deleteMask, !found.Equals(uploadMask), fileRef, nil 978 } 979 980 // DoMultiOperation performs multiple operations on the allocation. 981 // The operations are performed in parallel. 982 // - operations: the operations to perform. 983 // - opts: the options of the multi operation as operation functions that customize the multi operation. 984 func (a *Allocation) DoMultiOperation(operations []OperationRequest, opts ...MultiOperationOption) error { 985 if len(operations) == 0 { 986 return nil 987 } 988 if !a.isInitialized() { 989 return notInitialized 990 } 991 connectionID := zboxutil.NewConnectionId() 992 var mo MultiOperation 993 mo.allocationObj = a 994 995 for i := 0; i < len(operations); { 996 // resetting multi operation and previous paths for every batch 997 mo.operationMask = zboxutil.NewUint128(0) 998 mo.maskMU = &sync.Mutex{} 999 mo.connectionID = connectionID 1000 mo.ctx, mo.ctxCncl = context.WithCancelCause(a.ctx) 1001 mo.Consensus = Consensus{ 1002 RWMutex: &sync.RWMutex{}, 1003 consensusThresh: a.consensusThreshold, 1004 fullconsensus: a.fullconsensus, 1005 } 1006 for _, opt := range opts { 1007 opt(&mo) 1008 } 1009 previousPaths := make(map[string]bool) 1010 connectionErrors := make([]error, len(mo.allocationObj.Blobbers)) 1011 1012 var wg sync.WaitGroup 1013 for blobberIdx := range mo.allocationObj.Blobbers { 1014 wg.Add(1) 1015 go func(pos int) { 1016 defer wg.Done() 1017 err := mo.createConnectionObj(pos) 1018 if err != nil { 1019 l.Logger.Error(err.Error()) 1020 connectionErrors[pos] = err 1021 } 1022 }(blobberIdx) 1023 } 1024 wg.Wait() 1025 // Check consensus 1026 if mo.operationMask.CountOnes() < mo.consensusThresh { 1027 l.Logger.Error("Multioperation: create connection failed. Required consensus not met", 1028 zap.Int("consensusThresh", mo.consensusThresh), 1029 zap.Int("operationMask", mo.operationMask.CountOnes()), 1030 zap.Any("connectionErrors", connectionErrors)) 1031 1032 majorErr := zboxutil.MajorError(connectionErrors) 1033 if majorErr != nil { 1034 return errors.New("consensus_not_met", 1035 fmt.Sprintf("Multioperation: create connection failed. Required consensus %d got %d. Major error: %s", 1036 mo.consensusThresh, mo.operationMask.CountOnes(), majorErr.Error())) 1037 } 1038 return errors.New("consensus_not_met", 1039 fmt.Sprintf("Multioperation: create connection failed. Required consensus %d got %d", 1040 mo.consensusThresh, mo.operationMask.CountOnes())) 1041 } 1042 1043 for ; i < len(operations); i++ { 1044 if len(mo.operations) >= MultiOpBatchSize { 1045 // max batch size reached, commit 1046 connectionID = zboxutil.NewConnectionId() 1047 break 1048 } 1049 op := operations[i] 1050 op.RemotePath = strings.TrimSpace(op.RemotePath) 1051 if op.FileMeta.RemotePath != "" { 1052 op.FileMeta.RemotePath = strings.TrimSpace(op.FileMeta.RemotePath) 1053 op.FileMeta.RemoteName = strings.TrimSpace(op.FileMeta.RemoteName) 1054 } 1055 remotePath := op.RemotePath 1056 parentPaths := GenerateParentPaths(remotePath) 1057 1058 if _, ok := previousPaths[remotePath]; ok { 1059 // conflict found, commit 1060 connectionID = zboxutil.NewConnectionId() 1061 break 1062 } 1063 1064 var ( 1065 operation Operationer 1066 err error 1067 newConnectionID string 1068 ) 1069 1070 switch op.OperationType { 1071 case constants.FileOperationRename: 1072 operation = NewRenameOperation(op.RemotePath, op.DestName, mo.operationMask, mo.maskMU, mo.consensusThresh, mo.fullconsensus, mo.ctx) 1073 1074 case constants.FileOperationCopy: 1075 operation = NewCopyOperation(op.RemotePath, op.DestPath, mo.operationMask, mo.maskMU, mo.consensusThresh, mo.fullconsensus, mo.ctx) 1076 1077 case constants.FileOperationMove: 1078 operation = NewMoveOperation(op.RemotePath, op.DestPath, mo.operationMask, mo.maskMU, mo.consensusThresh, mo.fullconsensus, mo.ctx) 1079 1080 case constants.FileOperationInsert: 1081 cancelLock.Lock() 1082 CancelOpCtx[op.FileMeta.RemotePath] = mo.ctxCncl 1083 cancelLock.Unlock() 1084 operation, newConnectionID, err = NewUploadOperation(mo.ctx, op.Workdir, mo.allocationObj, mo.connectionID, op.FileMeta, op.FileReader, false, op.IsWebstreaming, op.IsRepair, op.DownloadFile, op.StreamUpload, op.Opts...) 1085 1086 case constants.FileOperationDelete: 1087 if op.Mask != nil { 1088 operation = NewDeleteOperation(op.RemotePath, *op.Mask, mo.maskMU, mo.consensusThresh, mo.fullconsensus, mo.ctx) 1089 } else { 1090 operation = NewDeleteOperation(op.RemotePath, mo.operationMask, mo.maskMU, mo.consensusThresh, mo.fullconsensus, mo.ctx) 1091 } 1092 1093 case constants.FileOperationUpdate: 1094 cancelLock.Lock() 1095 CancelOpCtx[op.FileMeta.RemotePath] = mo.ctxCncl 1096 cancelLock.Unlock() 1097 operation, newConnectionID, err = NewUploadOperation(mo.ctx, op.Workdir, mo.allocationObj, mo.connectionID, op.FileMeta, op.FileReader, true, op.IsWebstreaming, op.IsRepair, op.DownloadFile, op.StreamUpload, op.Opts...) 1098 1099 case constants.FileOperationCreateDir: 1100 operation = NewDirOperation(op.RemotePath, op.FileMeta.CustomMeta, mo.operationMask, mo.maskMU, mo.consensusThresh, mo.fullconsensus, mo.ctx) 1101 1102 default: 1103 return errors.New("invalid_operation", "Operation is not valid") 1104 } 1105 if err != nil { 1106 return err 1107 } 1108 1109 if newConnectionID != "" && newConnectionID != connectionID { 1110 connectionID = newConnectionID 1111 break 1112 } 1113 err = operation.Verify(a) 1114 if err != nil { 1115 return err 1116 } 1117 1118 for path := range parentPaths { 1119 previousPaths[path] = true 1120 } 1121 1122 mo.operations = append(mo.operations, operation) 1123 } 1124 1125 if len(mo.operations) > 0 { 1126 err := mo.Process() 1127 if err != nil { 1128 return err 1129 } 1130 1131 mo.operations = nil 1132 } 1133 } 1134 return nil 1135 } 1136 1137 // GenerateParentPath generates the parent path of the given path. 1138 // - path: the path to generate the parent path from. 1139 func GenerateParentPaths(path string) map[string]bool { 1140 path = strings.Trim(path, "/") 1141 parts := strings.Split(path, "/") 1142 parentPaths := make(map[string]bool) 1143 1144 for i := range parts { 1145 parentPaths["/"+strings.Join(parts[:i+1], "/")] = true 1146 } 1147 return parentPaths 1148 } 1149 1150 // DownloadFileToFileHandler adds a download operation a file to a file handler. 1151 // Triggers the download operations if the added download operation is final. 1152 // The file is downloaded from the allocation to the file handler. 1153 // - fileHandler: the file handler to download the file to. 1154 // - remotePath: the remote path of the file to download. 1155 // - verifyDownload: a flag to verify the download. If true, the download should be verified against the client keys. 1156 // - status: the status callback function. Will be used to gather the status of the download operation. 1157 // - isFinal: a flag to indicate if the download is the final download, meaning no more downloads are expected. It triggers the finalization of the download operation. 1158 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 1159 func (a *Allocation) DownloadFileToFileHandler( 1160 fileHandler sys.File, 1161 remotePath string, 1162 verifyDownload bool, 1163 status StatusCallback, 1164 isFinal bool, 1165 downloadReqOpts ...DownloadRequestOption, 1166 ) error { 1167 return a.addAndGenerateDownloadRequest(fileHandler, remotePath, DOWNLOAD_CONTENT_FULL, 1, 0, 1168 numBlockDownloads, verifyDownload, status, isFinal, "", downloadReqOpts...) 1169 } 1170 1171 // DownloadFileByBlockToFileHandler adds a download operation of a file by block to a file handler. 1172 // Triggers the download operations if the added download operation is final. 1173 // The file is downloaded from the allocation to the file handler in blocks. 1174 // - fileHandler: the file handler to download the file to. 1175 // - remotePath: the remote path of the file to download. 1176 // - startBlock: the start block of the file to download. 1177 // - endBlock: the end block of the file to download. 1178 // - numBlocks: the number of blocks to download. 1179 // - verifyDownload: a flag to verify the download. If true, the download should be verified against the client keys. 1180 // - status: the status callback function. Will be used to gather the status of the download operation. 1181 // - isFinal: a flag to indicate if the download is the final download, meaning no more downloads are expected. It triggers the finalization of the download operation. 1182 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 1183 func (a *Allocation) DownloadByBlocksToFileHandler( 1184 fileHandler sys.File, 1185 remotePath string, 1186 startBlock, endBlock int64, 1187 numBlocks int, 1188 verifyDownload bool, 1189 status StatusCallback, 1190 isFinal bool, 1191 downloadReqOpts ...DownloadRequestOption, 1192 ) error { 1193 return a.addAndGenerateDownloadRequest(fileHandler, remotePath, DOWNLOAD_CONTENT_FULL, startBlock, endBlock, 1194 numBlocks, verifyDownload, status, isFinal, "", downloadReqOpts...) 1195 } 1196 1197 // DownloadThumbnailToFileHandler adds a download operation of a thumbnail to a file handler. 1198 // Triggers the download operations if the added download operation is final. 1199 // The thumbnail is downloaded from the allocation to the file handler. 1200 // - fileHandler: the file handler to download the thumbnail to. 1201 // - remotePath: the remote path of the thumbnail to download. 1202 // - verifyDownload: a flag to verify the download. If true, the download should be verified against the client keys. 1203 // - status: the status callback function. Will be used to gather the status of the download operation. 1204 // - isFinal: a flag to indicate if the download is the final download, meaning no more downloads are expected. It triggers the finalization of the download operation. 1205 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 1206 func (a *Allocation) DownloadThumbnailToFileHandler( 1207 fileHandler sys.File, 1208 remotePath string, 1209 verifyDownload bool, 1210 status StatusCallback, 1211 isFinal bool, 1212 downloadReqOpts ...DownloadRequestOption, 1213 ) error { 1214 return a.addAndGenerateDownloadRequest(fileHandler, remotePath, DOWNLOAD_CONTENT_THUMB, 1, 0, 1215 numBlockDownloads, verifyDownload, status, isFinal, "", downloadReqOpts...) 1216 } 1217 1218 // DownloadFile adds a download operation of a file from the allocation. 1219 // Triggers the download operations if the added download operation is final. 1220 // The file is downloaded from the allocation to the local path. 1221 // - localPath: the local path to download the file to. 1222 // - remotePath: the remote path of the file to download. 1223 // - verifyDownload: a flag to verify the download. If true, the download should be verified against the client keys. 1224 // - status: the status callback function. Will be used to gather the status of the download operation. 1225 // - isFinal: a flag to indicate if the download is the final download, meaning no more downloads are expected. It triggers the finalization of the download operation. 1226 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 1227 1228 func (a *Allocation) DownloadFile(localPath string, remotePath string, verifyDownload bool, status StatusCallback, isFinal bool, downloadReqOpts ...DownloadRequestOption) error { 1229 f, localFilePath, toKeep, err := a.prepareAndOpenLocalFile(localPath, remotePath) 1230 if err != nil { 1231 return err 1232 } 1233 downloadReqOpts = append(downloadReqOpts, WithFileCallback(func() { 1234 f.Close() //nolint: errcheck 1235 })) 1236 err = a.addAndGenerateDownloadRequest(f, remotePath, DOWNLOAD_CONTENT_FULL, 1, 0, 1237 numBlockDownloads, verifyDownload, status, isFinal, localFilePath, downloadReqOpts...) 1238 if err != nil { 1239 if !toKeep { 1240 os.Remove(localFilePath) //nolint: errcheck 1241 } 1242 f.Close() //nolint: errcheck 1243 return err 1244 } 1245 return nil 1246 } 1247 1248 // DownloadFileByBlock adds a download operation of a file by block from the allocation. 1249 // Triggers the download operations if the added download operation is final. 1250 // The file is downloaded from the allocation to the local path in blocks. 1251 // - localPath: the local path to download the file to. 1252 // - remotePath: the remote path of the file to download. 1253 // - startBlock: the start block of the file to download. 1254 // - endBlock: the end block of the file to download. 1255 // - numBlocks: the number of blocks to download. 1256 // - verifyDownload: a flag to verify the download. If true, the download should be verified against the client keys. 1257 // - status: the status callback function. Will be used to gather the status of the download operation. 1258 // - isFinal: a flag to indicate if the download is the final download, meaning no more downloads are expected. It triggers the finalization of the download operation. 1259 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 1260 1261 // TODO: Use a map to store the download request and use flag isFinal to start the download, calculate readCount in parallel if possible 1262 func (a *Allocation) DownloadFileByBlock( 1263 localPath string, remotePath string, startBlock int64, endBlock int64, 1264 numBlocks int, verifyDownload bool, status StatusCallback, isFinal bool, downloadReqOpts ...DownloadRequestOption) error { 1265 f, localFilePath, toKeep, err := a.prepareAndOpenLocalFile(localPath, remotePath) 1266 if err != nil { 1267 return err 1268 } 1269 downloadReqOpts = append(downloadReqOpts, WithFileCallback(func() { 1270 f.Close() //nolint: errcheck 1271 })) 1272 err = a.addAndGenerateDownloadRequest(f, remotePath, DOWNLOAD_CONTENT_FULL, startBlock, endBlock, 1273 numBlockDownloads, verifyDownload, status, isFinal, localFilePath, downloadReqOpts...) 1274 if err != nil { 1275 if !toKeep { 1276 os.Remove(localFilePath) //nolint: errcheck 1277 } 1278 f.Close() //nolint: errcheck 1279 return err 1280 } 1281 return nil 1282 } 1283 1284 // DownloadThumbnail adds a download operation of a thumbnail from the allocation. 1285 // Triggers the download operations if the added download operation is final. 1286 // The thumbnail is downloaded from the allocation to the local path. 1287 // - localPath: the local path to download the thumbnail to. 1288 // - remotePath: the remote path of the thumbnail to download. 1289 // - verifyDownload: a flag to verify the download. If true, the download should be verified against the client keys. 1290 // - status: the status callback function. Will be used to gather the status of the download operation. 1291 // - isFinal: a flag to indicate if the download is the final download, meaning no more downloads are expected. It triggers the finalization of the download operation. 1292 func (a *Allocation) DownloadThumbnail(localPath string, remotePath string, verifyDownload bool, status StatusCallback, isFinal bool) error { 1293 f, localFilePath, toKeep, err := a.prepareAndOpenLocalFile(localPath, remotePath) 1294 if err != nil { 1295 return err 1296 } 1297 1298 err = a.addAndGenerateDownloadRequest(f, remotePath, DOWNLOAD_CONTENT_THUMB, 1, 0, 1299 numBlockDownloads, verifyDownload, status, isFinal, localFilePath, WithFileCallback(func() { 1300 f.Close() //nolint: errcheck 1301 })) 1302 if err != nil { 1303 if !toKeep { 1304 os.Remove(localFilePath) //nolint: errcheck 1305 } 1306 f.Close() //nolint: errcheck 1307 return err 1308 } 1309 return nil 1310 } 1311 1312 func (a *Allocation) generateDownloadRequest( 1313 fileHandler sys.File, 1314 remotePath string, 1315 contentMode string, 1316 startBlock, endBlock int64, 1317 numBlocks int, 1318 verifyDownload bool, 1319 status StatusCallback, 1320 connectionID string, 1321 localFilePath string, 1322 ) (*DownloadRequest, error) { 1323 if len(a.Blobbers) == 0 { 1324 return nil, noBLOBBERS 1325 } 1326 1327 downloadReq := &DownloadRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 1328 downloadReq.maskMu = &sync.Mutex{} 1329 downloadReq.allocationID = a.ID 1330 downloadReq.allocationTx = a.Tx 1331 downloadReq.allocOwnerID = a.Owner 1332 downloadReq.sig = a.sig 1333 downloadReq.allocOwnerPubKey = a.OwnerPublicKey 1334 downloadReq.ctx, downloadReq.ctxCncl = context.WithCancel(a.ctx) 1335 downloadReq.fileHandler = fileHandler 1336 downloadReq.localFilePath = localFilePath 1337 downloadReq.remotefilepath = remotePath 1338 downloadReq.statusCallback = status 1339 downloadReq.downloadMask = zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1) 1340 downloadReq.blobbers = a.Blobbers 1341 downloadReq.datashards = a.DataShards 1342 downloadReq.parityshards = a.ParityShards 1343 downloadReq.startBlock = startBlock - 1 1344 downloadReq.endBlock = endBlock 1345 downloadReq.numBlocks = int64(numBlocks) 1346 downloadReq.shouldVerify = verifyDownload 1347 downloadReq.fullconsensus = a.fullconsensus 1348 downloadReq.consensusThresh = a.DataShards 1349 downloadReq.completedCallback = func(remotepath string, remotepathhash string) { 1350 a.mutex.Lock() 1351 defer a.mutex.Unlock() 1352 delete(a.downloadProgressMap, remotepath) 1353 } 1354 // downloadReq.fileCallback = func() { 1355 // if downloadReq.fileHandler != nil { 1356 // downloadReq.fileHandler.Close() //nolint: errcheck 1357 // } 1358 // } 1359 downloadReq.contentMode = contentMode 1360 downloadReq.connectionID = connectionID 1361 downloadReq.downloadQueue = make(downloadQueue, len(a.Blobbers)) 1362 for i := 0; i < len(a.Blobbers); i++ { 1363 downloadReq.downloadQueue[i].timeTaken = 1000000 1364 } 1365 downloadReq.isEnterprise = a.IsEnterprise 1366 1367 return downloadReq, nil 1368 } 1369 1370 func (a *Allocation) addAndGenerateDownloadRequest( 1371 fileHandler sys.File, 1372 remotePath, contentMode string, 1373 startBlock, endBlock int64, 1374 numBlocks int, 1375 verifyDownload bool, 1376 status StatusCallback, 1377 isFinal bool, 1378 localFilePath string, 1379 downloadReqOpts ...DownloadRequestOption, 1380 ) error { 1381 downloadReq, err := a.generateDownloadRequest( 1382 fileHandler, remotePath, contentMode, startBlock, endBlock, 1383 numBlocks, verifyDownload, status, "", localFilePath) 1384 if err != nil { 1385 return err 1386 } 1387 a.mutex.Lock() 1388 defer a.mutex.Unlock() 1389 if len(a.downloadRequests) > 0 { 1390 downloadReq.connectionID = a.downloadRequests[0].connectionID 1391 } else { 1392 downloadReq.connectionID = zboxutil.NewConnectionId() 1393 } 1394 for _, opt := range downloadReqOpts { 1395 opt(downloadReq) 1396 } 1397 downloadReq.workdir = filepath.Join(downloadReq.workdir, ".zcn") 1398 a.downloadProgressMap[remotePath] = downloadReq 1399 a.downloadRequests = append(a.downloadRequests, downloadReq) 1400 if isFinal { 1401 downloadOps := a.downloadRequests 1402 a.downloadRequests = nil 1403 go func() { 1404 a.processReadMarker(downloadOps) 1405 }() 1406 } 1407 return nil 1408 } 1409 1410 func (a *Allocation) processReadMarker(drs []*DownloadRequest) { 1411 blobberMap := make(map[uint64]int64) 1412 mpLock := sync.Mutex{} 1413 wg := sync.WaitGroup{} 1414 now := time.Now() 1415 1416 for _, dr := range drs { 1417 wg.Add(1) 1418 go func(dr *DownloadRequest) { 1419 defer wg.Done() 1420 if a.readFree { 1421 dr.freeRead = true 1422 } 1423 dr.processDownloadRequest() 1424 var pos uint64 1425 if !dr.skip { 1426 for i := dr.downloadMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 1427 pos = uint64(i.TrailingZeros()) 1428 mpLock.Lock() 1429 blobberMap[pos] += dr.blocksPerShard 1430 mpLock.Unlock() 1431 } 1432 } 1433 }(dr) 1434 } 1435 wg.Wait() 1436 elapsedProcessDownloadRequest := time.Since(now) 1437 1438 // Do not send readmarkers for free reads 1439 if a.readFree { 1440 for _, dr := range drs { 1441 if dr.skip { 1442 continue 1443 } 1444 go func(dr *DownloadRequest) { 1445 a.downloadChan <- dr 1446 }(dr) 1447 } 1448 l.Logger.Debug("[processReadMarker]", zap.String("allocation_id", a.ID), 1449 zap.Int("num of download requests", len(drs)), 1450 zap.Duration("processDownloadRequest", elapsedProcessDownloadRequest)) 1451 return 1452 } 1453 1454 successMask := zboxutil.NewUint128(0) 1455 var redeemError error 1456 1457 for pos, totalBlocks := range blobberMap { 1458 if totalBlocks == 0 { 1459 continue 1460 } 1461 wg.Add(1) 1462 go func(pos uint64, totalBlocks int64) { 1463 blobber := drs[0].blobbers[pos] 1464 err := drs[0].submitReadMarker(blobber, totalBlocks) 1465 if err == nil { 1466 successMask = successMask.Or(zboxutil.NewUint128(1).Lsh(pos)) 1467 } else { 1468 redeemError = err 1469 } 1470 wg.Done() 1471 }(pos, totalBlocks) 1472 } 1473 wg.Wait() 1474 elapsedSubmitReadmarker := time.Since(now) - elapsedProcessDownloadRequest 1475 1476 l.Logger.Info("[processReadMarker]", zap.String("allocation_id", a.ID), 1477 zap.Int("num of download requests", len(drs)), 1478 zap.Duration("processDownloadRequest", elapsedProcessDownloadRequest), 1479 zap.Duration("submitReadmarker", elapsedSubmitReadmarker)) 1480 for _, dr := range drs { 1481 if dr.skip { 1482 continue 1483 } 1484 dr.downloadMask = successMask.And(dr.downloadMask) 1485 if dr.consensusThresh > dr.downloadMask.CountOnes() { 1486 if redeemError == nil { 1487 redeemError = errors.New("read_marker_failed", "Failed to submit read marker to the blobbers") 1488 } 1489 dr.errorCB(redeemError, dr.remotefilepath) 1490 continue 1491 } 1492 go func(dr *DownloadRequest) { 1493 a.downloadChan <- dr 1494 }(dr) 1495 } 1496 } 1497 1498 func (a *Allocation) prepareAndOpenLocalFile(localPath string, remotePath string) (*os.File, string, bool, error) { 1499 var toKeep bool 1500 1501 if !a.isInitialized() { 1502 return nil, "", toKeep, notInitialized 1503 } 1504 1505 var localFilePath string 1506 1507 // If the localPath has a file extension, treat it as a file. Otherwise, treat it as a directory. 1508 if filepath.Ext(localPath) != "" { 1509 localFilePath = localPath 1510 } else { 1511 localFileName := filepath.Base(remotePath) 1512 localFilePath = filepath.Join(localPath, localFileName) 1513 } 1514 1515 // Create necessary directories if they do not exist 1516 dir := filepath.Dir(localFilePath) 1517 if _, err := os.Stat(dir); os.IsNotExist(err) { 1518 if err := os.MkdirAll(dir, 0744); err != nil { 1519 return nil, "", toKeep, err 1520 } 1521 } 1522 1523 var f *os.File 1524 info, err := os.Stat(localFilePath) 1525 if errors.Is(err, os.ErrNotExist) { 1526 f, err = os.OpenFile(localFilePath, os.O_WRONLY|os.O_CREATE, 0644) 1527 if err != nil { 1528 return nil, "", toKeep, errors.Wrap(err, "Can't create local file") 1529 } 1530 } else { 1531 f, err = os.OpenFile(localFilePath, os.O_WRONLY, 0644) 1532 if err != nil { 1533 return nil, "", toKeep, errors.Wrap(err, "Can't open local file in append mode") 1534 } 1535 if info.Size() > 0 { 1536 toKeep = true 1537 } 1538 } 1539 1540 return f, localFilePath, toKeep, nil 1541 } 1542 1543 // ListDirFromAuthTicket lists the allocation directory encoded in the given auth ticket. 1544 // Usually used for directory sharing, the owner sets the directory as shared and generates an auth ticket which they should share with other non-owner users. 1545 // The non-owner users can list the shared directory using the auth ticket. 1546 // - authTicket: the auth ticket to list the directory. 1547 // - lookupHash: the lookup hash of the directory to list. It's an augmentation of the allocation ID and the path hash. 1548 // - opts: the options of the list request as operation functions that customize the list request. 1549 func (a *Allocation) ListDirFromAuthTicket(authTicket string, lookupHash string, opts ...ListRequestOptions) (*ListResult, error) { 1550 if !a.isInitialized() { 1551 return nil, notInitialized 1552 } 1553 sEnc, err := base64.StdEncoding.DecodeString(authTicket) 1554 if err != nil { 1555 return nil, errors.New("auth_ticket_decode_error", "Error decoding the auth ticket."+err.Error()) 1556 } 1557 at := &marker.AuthTicket{} 1558 err = json.Unmarshal(sEnc, at) 1559 if err != nil { 1560 return nil, errors.New("auth_ticket_decode_error", "Error unmarshaling the auth ticket."+err.Error()) 1561 } 1562 if len(at.FilePathHash) == 0 || len(lookupHash) == 0 { 1563 return nil, errors.New("invalid_path", "Invalid path for the list") 1564 } 1565 1566 listReq := &ListRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 1567 listReq.allocationID = a.ID 1568 listReq.allocationTx = a.Tx 1569 listReq.sig = a.sig 1570 listReq.blobbers = a.Blobbers 1571 listReq.fullconsensus = a.fullconsensus 1572 listReq.consensusThresh = a.consensusThreshold 1573 listReq.ctx = a.ctx 1574 listReq.remotefilepathhash = lookupHash 1575 listReq.authToken = at 1576 for _, opt := range opts { 1577 opt(listReq) 1578 } 1579 ref, err := listReq.GetListFromBlobbers() 1580 1581 if err != nil { 1582 return nil, err 1583 } 1584 1585 if ref != nil { 1586 return ref, nil 1587 } 1588 return nil, errors.New("list_request_failed", "Failed to get list response from the blobbers") 1589 } 1590 1591 // ListDir lists the allocation directory. 1592 // - path: the path of the directory to list. 1593 // - opts: the options of the list request as operation functions that customize the list request. 1594 func (a *Allocation) ListDir(path string, opts ...ListRequestOptions) (*ListResult, error) { 1595 if !a.isInitialized() { 1596 return nil, notInitialized 1597 } 1598 if len(path) == 0 { 1599 return nil, errors.New("invalid_path", "Invalid path for the list") 1600 } 1601 path = zboxutil.RemoteClean(path) 1602 isabs := zboxutil.IsRemoteAbs(path) 1603 if !isabs { 1604 return nil, errors.New("invalid_path", "Path should be valid and absolute") 1605 } 1606 listReq := &ListRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 1607 listReq.allocationID = a.ID 1608 listReq.allocationTx = a.Tx 1609 listReq.sig = a.sig 1610 listReq.blobbers = a.Blobbers 1611 listReq.fullconsensus = a.fullconsensus 1612 listReq.consensusThresh = a.DataShards 1613 listReq.ctx = a.ctx 1614 listReq.remotefilepath = path 1615 for _, opt := range opts { 1616 opt(listReq) 1617 } 1618 ref, err := listReq.GetListFromBlobbers() 1619 if err != nil { 1620 return nil, err 1621 } 1622 1623 if ref != nil { 1624 return ref, nil 1625 } 1626 return nil, errors.New("list_request_failed", "Failed to get list response from the blobbers") 1627 } 1628 1629 func (a *Allocation) getRefs(path, pathHash, authToken, offsetPath, updatedDate, offsetDate, fileType, refType string, level, pageLimit int) (*ObjectTreeResult, error) { 1630 if !a.isInitialized() { 1631 return nil, notInitialized 1632 } 1633 1634 oTreeReq := &ObjectTreeRequest{ 1635 allocationID: a.ID, 1636 allocationTx: a.Tx, 1637 sig: a.sig, 1638 blobbers: a.Blobbers, 1639 authToken: authToken, 1640 pathHash: pathHash, 1641 remotefilepath: path, 1642 pageLimit: pageLimit, 1643 level: level, 1644 offsetPath: offsetPath, 1645 updatedDate: updatedDate, 1646 offsetDate: offsetDate, 1647 fileType: fileType, 1648 refType: refType, 1649 ctx: a.ctx, 1650 } 1651 oTreeReq.fullconsensus = a.fullconsensus 1652 oTreeReq.consensusThresh = a.DataShards 1653 return oTreeReq.GetRefs() 1654 } 1655 1656 func (a *Allocation) getDownloadMaskForBlobber(blobberID string) (zboxutil.Uint128, []*blockchain.StorageNode, error) { 1657 1658 x := zboxutil.NewUint128(1) 1659 blobberIdx := 0 1660 found := false 1661 for idx, b := range a.Blobbers { 1662 if b.ID == blobberID { 1663 found = true 1664 blobberIdx = idx 1665 } 1666 } 1667 1668 if !found { 1669 return x, nil, fmt.Errorf("no blobber found with the given ID") 1670 } 1671 1672 return x, a.Blobbers[blobberIdx : blobberIdx+1], nil 1673 } 1674 1675 // DownloadFromBlobber downloads a file from a specific blobber. 1676 // - blobberID: the ID of the blobber to download the file from. 1677 // - localPath: the local path to download the file to. 1678 // - remotePath: the remote path of the file to download. 1679 // - status: the status callback function. Will be used to gather the status of the download operation. 1680 // - opts: the options of the download request as operation functions that customize the download request. 1681 func (a *Allocation) DownloadFromBlobber(blobberID, localPath, remotePath string, status StatusCallback, opts ...DownloadRequestOption) error { 1682 1683 mask, blobbers, err := a.getDownloadMaskForBlobber(blobberID) 1684 if err != nil { 1685 l.Logger.Error(err) 1686 return err 1687 } 1688 1689 verifyDownload := false // should be set to false 1690 1691 f, localFilePath, toKeep, err := a.prepareAndOpenLocalFile(localPath, remotePath) 1692 if err != nil { 1693 return err 1694 } 1695 downloadReq, err := a.generateDownloadRequest(f, remotePath, DOWNLOAD_CONTENT_FULL, 1, 0, numBlockDownloads, verifyDownload, 1696 status, zboxutil.NewConnectionId(), localFilePath) 1697 if err != nil { 1698 if !toKeep { 1699 os.Remove(localFilePath) //nolint: errcheck 1700 } 1701 f.Close() //nolint: errcheck 1702 return err 1703 } 1704 1705 downloadReq.downloadMask = mask 1706 downloadReq.blobbers = blobbers 1707 downloadReq.fullconsensus = 1 1708 downloadReq.consensusThresh = 1 1709 opts = append(opts, WithFileCallback(func() { 1710 f.Close() //nolint: errcheck 1711 })) 1712 for _, opt := range opts { 1713 opt(downloadReq) 1714 } 1715 1716 fRef, err := downloadReq.getFileRef() 1717 if err != nil { 1718 l.Logger.Error(err.Error()) 1719 downloadReq.errorCB(fmt.Errorf("Error while getting file ref. Error: %v", err), remotePath) 1720 return err 1721 } 1722 downloadReq.numBlocks = fRef.NumBlocks 1723 1724 a.processReadMarker([]*DownloadRequest{downloadReq}) 1725 if downloadReq.skip { 1726 return errors.New("download_request_failed", "Failed to get download response from the blobbers") 1727 } 1728 return nil 1729 } 1730 1731 // GetRefsWithAuthTicket retrieve file refs that are children of a shared remote path. 1732 // Refs are the representations of files and directories in the blobber database. 1733 // An auth ticket is provided in case the path is shared, and usually by a non-owner user. 1734 // This function will retrieve paginated objectTree and will handle concensus; Required tree should be made in application side. 1735 // - authToken: the auth ticket to get the refs. 1736 // - offsetPath: the offset path to get the refs. 1737 // - updatedDate: the updated date to get the refs. 1738 // - offsetDate: the offset date to get the refs. 1739 // - fileType: the file type to get the refs. 1740 // - refType: the ref type to get the refs, e.g., file or directory. 1741 // - level: the level of the refs to get relative to the path root (strating from 0 as the root path). 1742 // - pageLimit: the limit of the refs to get per page. 1743 func (a *Allocation) GetRefsWithAuthTicket(authToken, offsetPath, updatedDate, offsetDate, fileType, refType string, level, pageLimit int) (*ObjectTreeResult, error) { 1744 if authToken == "" { 1745 return nil, errors.New("empty_auth_token", "auth token cannot be empty") 1746 } 1747 sEnc, err := base64.StdEncoding.DecodeString(authToken) 1748 if err != nil { 1749 return nil, errors.New("auth_ticket_decode_error", "Error decoding the auth ticket."+err.Error()) 1750 } 1751 1752 authTicket := new(marker.AuthTicket) 1753 if err := json.Unmarshal(sEnc, authTicket); err != nil { 1754 return nil, errors.New("json_unmarshall_error", err.Error()) 1755 } 1756 1757 at, _ := json.Marshal(authTicket) 1758 return a.getRefs("", authTicket.FilePathHash, string(at), offsetPath, updatedDate, offsetDate, fileType, refType, level, pageLimit) 1759 } 1760 1761 // GetRefs retrieve file refs that are children of a remote path. 1762 // Used by the owner to get the refs of the files and directories in the allocation. 1763 // This function will retrieve paginated objectTree and will handle concensus; Required tree should be made in application side. 1764 // - path: the path to get the refs. 1765 // - offsetPath: the offset path to get the refs. 1766 // - updatedDate: the updated date to get the refs. 1767 // - offsetDate: the offset date to get the refs. 1768 // - fileType: the file type to get the refs. 1769 // - refType: the ref type to get the refs, e.g., file or directory. 1770 // - level: the level of the refs to get relative to the path root (strating from 0 as the root path). 1771 // - pageLimit: the limit of the refs to get per page. 1772 func (a *Allocation) GetRefs(path, offsetPath, updatedDate, offsetDate, fileType, refType string, level, pageLimit int) (*ObjectTreeResult, error) { 1773 if len(path) == 0 || !zboxutil.IsRemoteAbs(path) { 1774 return nil, errors.New("invalid_path", fmt.Sprintf("Absolute path required. Path provided: %v", path)) 1775 } 1776 1777 return a.getRefs(path, "", "", offsetPath, updatedDate, offsetDate, fileType, refType, level, pageLimit) 1778 } 1779 1780 func (a *Allocation) ListObjects(ctx context.Context, path, offsetPath, updatedDate, offsetDate, fileType, refType string, level, pageLimit int) <-chan ORef { 1781 oRefChan := make(chan ORef, 1) 1782 sendObjectRef := func(ref ORef) { 1783 select { 1784 case oRefChan <- ref: 1785 case <-ctx.Done(): 1786 } 1787 } 1788 go func(oRefChan chan<- ORef) { 1789 defer func() { 1790 if contextCanceled(ctx) { 1791 oRefChan <- ORef{ 1792 Err: ctx.Err(), 1793 } 1794 } 1795 close(oRefChan) 1796 }() 1797 continuationPath := offsetPath 1798 for { 1799 oRefs, err := a.GetRefs(path, continuationPath, updatedDate, offsetDate, fileType, refType, level, pageLimit) 1800 if err != nil { 1801 sendObjectRef(ORef{ 1802 Err: err, 1803 }) 1804 return 1805 } 1806 for _, ref := range oRefs.Refs { 1807 select { 1808 // Send object content. 1809 case oRefChan <- ref: 1810 // If receives done from the caller, return here. 1811 case <-ctx.Done(): 1812 return 1813 } 1814 } 1815 if len(oRefs.Refs) < pageLimit { 1816 return 1817 } 1818 if oRefs.OffsetPath == "" || oRefs.OffsetPath == continuationPath { 1819 return 1820 } 1821 continuationPath = oRefs.OffsetPath 1822 } 1823 1824 }(oRefChan) 1825 return oRefChan 1826 } 1827 1828 func (a *Allocation) GetRefsFromLookupHash(pathHash, offsetPath, updatedDate, offsetDate, fileType, refType string, level, pageLimit int) (*ObjectTreeResult, error) { 1829 if pathHash == "" { 1830 return nil, errors.New("invalid_lookup_hash", "lookup hash cannot be empty") 1831 } 1832 1833 return a.getRefs("", pathHash, "", offsetPath, updatedDate, offsetDate, fileType, refType, level, pageLimit) 1834 1835 } 1836 1837 // GetRecentlyAddedRefs retrieves the recently added refs in the allocation. 1838 // The refs are the representations of files and directories in the blobber database. 1839 // This function will retrieve paginated objectTree and will handle concensus; Required tree should be made in application side. 1840 // - page: the page number of the refs to get. 1841 // - fromDate: the date to get the refs from. 1842 // - pageLimit: the limit of the refs to get per page. 1843 func (a *Allocation) GetRecentlyAddedRefs(page int, fromDate int64, pageLimit int) (*RecentlyAddedRefResult, error) { 1844 if !a.isInitialized() { 1845 return nil, notInitialized 1846 } 1847 1848 if page < 1 { 1849 return nil, errors.New("invalid_params", 1850 fmt.Sprintf("page value should be greater than or equal to 1."+ 1851 "Got page: %d", page)) 1852 } 1853 1854 offset := int64(page-1) * int64(pageLimit) 1855 req := &RecentlyAddedRefRequest{ 1856 allocationID: a.ID, 1857 allocationTx: a.Tx, 1858 sig: a.sig, 1859 blobbers: a.Blobbers, 1860 offset: offset, 1861 fromDate: fromDate, 1862 ctx: a.ctx, 1863 wg: &sync.WaitGroup{}, 1864 pageLimit: pageLimit, 1865 Consensus: Consensus{ 1866 RWMutex: &sync.RWMutex{}, 1867 fullconsensus: a.fullconsensus, 1868 consensusThresh: a.consensusThreshold, 1869 }, 1870 } 1871 return req.GetRecentlyAddedRefs() 1872 } 1873 1874 // GetFileMeta retrieves the file meta data of a file in the allocation. 1875 // The file meta data includes the file type, name, hash, lookup hash, mime type, path, size, number of blocks, encrypted key, collaborators, actual file size, actual thumbnail hash, and actual thumbnail size. 1876 // - path: the path of the file to get the meta data. 1877 func (a *Allocation) GetFileMeta(path string) (*ConsolidatedFileMeta, error) { 1878 if !a.isInitialized() { 1879 return nil, notInitialized 1880 } 1881 1882 result := &ConsolidatedFileMeta{} 1883 listReq := &ListRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 1884 listReq.allocationID = a.ID 1885 listReq.allocationTx = a.Tx 1886 listReq.sig = a.sig 1887 listReq.blobbers = a.Blobbers 1888 listReq.fullconsensus = a.fullconsensus 1889 listReq.consensusThresh = a.consensusThreshold 1890 listReq.ctx = a.ctx 1891 listReq.remotefilepath = path 1892 _, _, ref, _ := listReq.getFileConsensusFromBlobbers() 1893 if ref != nil { 1894 result.Type = ref.Type 1895 result.Name = ref.Name 1896 result.Hash = ref.ActualFileHash 1897 result.LookupHash = ref.LookupHash 1898 result.MimeType = ref.MimeType 1899 result.Path = ref.Path 1900 result.Size = ref.Size 1901 result.NumBlocks = ref.NumBlocks 1902 result.EncryptedKey = ref.EncryptedKey 1903 result.Collaborators = ref.Collaborators 1904 result.ActualFileSize = ref.ActualFileSize 1905 result.ActualThumbnailHash = ref.ActualThumbnailHash 1906 result.ActualThumbnailSize = ref.ActualThumbnailSize 1907 if result.ActualFileSize > 0 { 1908 result.ActualNumBlocks = (ref.ActualFileSize + CHUNK_SIZE - 1) / CHUNK_SIZE 1909 } 1910 return result, nil 1911 } 1912 return nil, errors.New("file_meta_error", "Error getting the file meta data from blobbers") 1913 } 1914 1915 // GetFileMetaByName retrieve consolidated file metadata given its name (its full path starting from root "/"). 1916 // - fileName: full file path starting from the allocation root. 1917 func (a *Allocation) GetFileMetaByName(fileName string) ([]*ConsolidatedFileMetaByName, error) { 1918 if !a.isInitialized() { 1919 return nil, notInitialized 1920 } 1921 1922 resultArr := []*ConsolidatedFileMetaByName{} 1923 listReq := &ListRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 1924 listReq.allocationID = a.ID 1925 listReq.allocationTx = a.Tx 1926 listReq.blobbers = a.Blobbers 1927 listReq.fullconsensus = a.fullconsensus 1928 listReq.consensusThresh = a.consensusThreshold 1929 listReq.ctx = a.ctx 1930 listReq.filename = fileName 1931 _, _, refs, _ := listReq.getMultipleFileConsensusFromBlobbers() 1932 if len(refs) != 0 { 1933 for _, ref := range refs { 1934 result := &ConsolidatedFileMetaByName{} 1935 if ref != nil { 1936 result.Type = ref.Type 1937 result.Name = ref.Name 1938 result.Hash = ref.ActualFileHash 1939 result.LookupHash = ref.LookupHash 1940 result.MimeType = ref.MimeType 1941 result.Path = ref.Path 1942 result.Size = ref.Size 1943 result.NumBlocks = ref.NumBlocks 1944 result.EncryptedKey = ref.EncryptedKey 1945 result.Collaborators = ref.Collaborators 1946 result.ActualFileSize = ref.ActualFileSize 1947 result.ActualThumbnailHash = ref.ActualThumbnailHash 1948 result.ActualThumbnailSize = ref.ActualThumbnailSize 1949 result.FileMetaHash = ref.FileMetaHash 1950 result.ThumbnailHash = ref.ThumbnailHash 1951 result.CreatedAt = ref.CreatedAt 1952 result.UpdatedAt = ref.UpdatedAt 1953 if result.ActualFileSize > 0 { 1954 result.ActualNumBlocks = (ref.ActualFileSize + CHUNK_SIZE - 1) / CHUNK_SIZE 1955 } 1956 } 1957 resultArr = append(resultArr, result) 1958 } 1959 return resultArr, nil 1960 } 1961 return nil, errors.New("file_meta_error", "Error getting the file meta data from blobbers") 1962 } 1963 1964 // GetChunkReadSize returns the size of the chunk to read. 1965 // The size of the chunk to read is calculated based on the data shards and the encryption flag. 1966 // If the encryption flag is true, the size of the chunk to read is the chunk size minus the encrypted data padding size and the encryption header size. 1967 // Otherwise, the size of the chunk to read is the chunk size multiplied by the data shards. 1968 // - encrypt: the flag to indicate if the chunk is encrypted. 1969 func (a *Allocation) GetChunkReadSize(encrypt bool) int64 { 1970 chunkDataSize := int64(DefaultChunkSize) 1971 if encrypt { 1972 chunkDataSize -= (EncryptedDataPaddingSize + EncryptionHeaderSize) 1973 } 1974 return chunkDataSize * int64(a.DataShards) 1975 } 1976 1977 // GetFileMetaFromAuthTicket retrieves the file meta data of a file in the allocation using the auth ticket. 1978 // The file meta data includes the file type, name, hash, lookup hash, mime type, path, size, number of blocks, actual file size, actual thumbnail hash, and actual thumbnail size. 1979 // The auth ticket is used to access the file meta data of a shared file. 1980 // Usually used for file sharing, the owner sets the file as shared and generates an auth ticket which they should share with other non-owner users. 1981 // - authTicket: the auth ticket to get the file meta data. 1982 // - lookupHash: the lookup hash of the file to get the meta data. It's an augmentation of the allocation ID and the path hash. 1983 func (a *Allocation) GetFileMetaFromAuthTicket(authTicket string, lookupHash string) (*ConsolidatedFileMeta, error) { 1984 if !a.isInitialized() { 1985 return nil, notInitialized 1986 } 1987 1988 result := &ConsolidatedFileMeta{} 1989 sEnc, err := base64.StdEncoding.DecodeString(authTicket) 1990 if err != nil { 1991 return nil, errors.New("auth_ticket_decode_error", "Error decoding the auth ticket."+err.Error()) 1992 } 1993 at := &marker.AuthTicket{} 1994 err = json.Unmarshal(sEnc, at) 1995 if err != nil { 1996 return nil, errors.New("auth_ticket_decode_error", "Error unmarshaling the auth ticket."+err.Error()) 1997 } 1998 if len(at.FilePathHash) == 0 || len(lookupHash) == 0 { 1999 return nil, errors.New("invalid_path", "Invalid path for the list") 2000 } 2001 2002 listReq := &ListRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 2003 listReq.allocationID = a.ID 2004 listReq.allocationTx = a.Tx 2005 listReq.sig = a.sig 2006 listReq.blobbers = a.Blobbers 2007 listReq.fullconsensus = a.fullconsensus 2008 listReq.consensusThresh = a.consensusThreshold 2009 listReq.ctx = a.ctx 2010 listReq.remotefilepathhash = lookupHash 2011 listReq.authToken = at 2012 _, _, ref, _ := listReq.getFileConsensusFromBlobbers() 2013 if ref != nil { 2014 result.Type = ref.Type 2015 result.Name = ref.Name 2016 result.Hash = ref.ActualFileHash 2017 result.LookupHash = ref.LookupHash 2018 result.MimeType = ref.MimeType 2019 result.Path = ref.Path 2020 result.Size = ref.Size 2021 result.NumBlocks = ref.NumBlocks 2022 result.ActualFileSize = ref.ActualFileSize 2023 result.ActualThumbnailHash = ref.ActualThumbnailHash 2024 result.ActualThumbnailSize = ref.ActualThumbnailSize 2025 if result.ActualFileSize > 0 { 2026 result.ActualNumBlocks = (result.ActualFileSize + CHUNK_SIZE - 1) / CHUNK_SIZE 2027 } 2028 return result, nil 2029 } 2030 return nil, errors.New("file_meta_error", "Error getting the file meta data from blobbers") 2031 } 2032 2033 // GetFileStats retrieves the file stats of a file in the allocation. 2034 // The file stats include the number of blocks, size, and actual file size. 2035 // - path: the path of the file to get the stats. 2036 func (a *Allocation) GetFileStats(path string) (map[string]*FileStats, error) { 2037 if !a.isInitialized() { 2038 return nil, notInitialized 2039 } 2040 if len(path) == 0 { 2041 return nil, errors.New("invalid_path", "Invalid path for the list") 2042 } 2043 path = zboxutil.RemoteClean(path) 2044 isabs := zboxutil.IsRemoteAbs(path) 2045 if !isabs { 2046 return nil, errors.New("invalid_path", "Path should be valid and absolute") 2047 } 2048 listReq := &ListRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 2049 listReq.allocationID = a.ID 2050 listReq.allocationTx = a.Tx 2051 listReq.sig = a.sig 2052 listReq.blobbers = a.Blobbers 2053 listReq.fullconsensus = a.fullconsensus 2054 listReq.consensusThresh = a.consensusThreshold 2055 listReq.ctx = a.ctx 2056 listReq.remotefilepath = path 2057 ref := listReq.getFileStatsFromBlobbers() 2058 if ref != nil { 2059 return ref, nil 2060 } 2061 return nil, errors.New("file_stats_request_failed", "Failed to get file stats response from the blobbers") 2062 } 2063 2064 // DeleteFile deletes a file from the allocation. 2065 // The file is deleted from the allocation and the blobbers. 2066 // - path: the path of the file to delete. 2067 func (a *Allocation) DeleteFile(path string) error { 2068 return a.deleteFile(path, a.consensusThreshold, a.fullconsensus, zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1)) 2069 } 2070 2071 func (a *Allocation) deleteFile(path string, threshConsensus, fullConsensus int, mask zboxutil.Uint128) error { 2072 if !a.isInitialized() { 2073 return notInitialized 2074 } 2075 2076 if !a.CanDelete() { 2077 return constants.ErrFileOptionNotPermitted 2078 } 2079 2080 if len(path) == 0 { 2081 return errors.New("invalid_path", "Invalid path for the list") 2082 } 2083 path = zboxutil.RemoteClean(path) 2084 isabs := zboxutil.IsRemoteAbs(path) 2085 if !isabs { 2086 return errors.New("invalid_path", "Path should be valid and absolute") 2087 } 2088 2089 req := &DeleteRequest{consensus: Consensus{RWMutex: &sync.RWMutex{}}} 2090 req.allocationObj = a 2091 req.blobbers = a.Blobbers 2092 req.allocationID = a.ID 2093 req.allocationTx = a.Tx 2094 req.sig = a.sig 2095 req.consensus.Init(threshConsensus, fullConsensus) 2096 req.ctx, req.ctxCncl = context.WithCancel(a.ctx) 2097 req.remotefilepath = path 2098 req.connectionID = zboxutil.NewConnectionId() 2099 req.deleteMask = mask 2100 req.maskMu = &sync.Mutex{} 2101 req.timestamp = int64(common.Now()) 2102 err := req.ProcessDelete() 2103 return err 2104 } 2105 2106 func (a *Allocation) createDir(remotePath string, threshConsensus, fullConsensus int, mask zboxutil.Uint128) error { 2107 if !a.isInitialized() { 2108 return notInitialized 2109 } 2110 2111 if remotePath == "" { 2112 return errors.New("invalid_name", "Invalid name for dir") 2113 } 2114 2115 if !path.IsAbs(remotePath) { 2116 return errors.New("invalid_path", "Path is not absolute") 2117 } 2118 2119 remotePath = zboxutil.RemoteClean(remotePath) 2120 timestamp := int64(common.Now()) 2121 req := DirRequest{ 2122 allocationObj: a, 2123 allocationID: a.ID, 2124 allocationTx: a.Tx, 2125 sig: a.sig, 2126 blobbers: a.Blobbers, 2127 mu: &sync.Mutex{}, 2128 dirMask: mask, 2129 connectionID: zboxutil.NewConnectionId(), 2130 remotePath: remotePath, 2131 wg: &sync.WaitGroup{}, 2132 timestamp: timestamp, 2133 Consensus: Consensus{ 2134 RWMutex: &sync.RWMutex{}, 2135 consensusThresh: threshConsensus, 2136 fullconsensus: fullConsensus, 2137 }, 2138 alreadyExists: make(map[uint64]bool), 2139 } 2140 req.ctx, req.ctxCncl = context.WithCancel(a.ctx) 2141 2142 err := req.ProcessDir(a) 2143 return err 2144 } 2145 2146 // GetAuthTicketForShare returns the authentication ticket for sharing a file or directory within the allocation. 2147 // It generates an authentication ticket using the provided parameters and the current time. 2148 // The authentication ticket can be used by the recipient to access the shared file or directory. 2149 // 2150 // Parameters: 2151 // - path: The path of the file or directory to be shared. 2152 // - filename: The name of the file to be shared. 2153 // - referenceType: The type of reference for the shared file or directory. 2154 // - refereeClientID: The client ID of the recipient who will be granted access to the shared file or directory. 2155 // 2156 // Returns: 2157 // - string: The authentication ticket for sharing the file or directory. 2158 // - error: An error if the authentication ticket generation fails. 2159 func (a *Allocation) GetAuthTicketForShare( 2160 path, filename, referenceType, refereeClientID string) (string, error) { 2161 2162 now := time.Now() 2163 return a.GetAuthTicket(path, filename, referenceType, refereeClientID, "", 0, &now) 2164 } 2165 2166 // RevokeShare revokes the shared access to a file or directory within the allocation. 2167 // It revokes the shared access to the file or directory for the specified recipient. 2168 // 2169 // Parameters: 2170 // - path: The path of the file or directory to revoke the shared access. 2171 // - refereeClientID: The client ID of the recipient whose shared access is to be revoked. 2172 // 2173 // Returns: 2174 // - error: An error if the shared access revocation fails. 2175 func (a *Allocation) RevokeShare(path string, refereeClientID string) error { 2176 success := make(chan int, len(a.Blobbers)) 2177 notFound := make(chan int, len(a.Blobbers)) 2178 wg := &sync.WaitGroup{} 2179 for idx := range a.Blobbers { 2180 baseUrl := a.Blobbers[idx].Baseurl 2181 query := &url.Values{} 2182 query.Add("path", path) 2183 query.Add("refereeClientID", refereeClientID) 2184 2185 httpreq, err := zboxutil.NewRevokeShareRequest(baseUrl, a.ID, a.Tx, a.sig, query) 2186 if err != nil { 2187 return err 2188 } 2189 2190 wg.Add(1) 2191 go func() { 2192 defer wg.Done() 2193 err := zboxutil.HttpDo(a.ctx, a.ctxCancelF, httpreq, func(resp *http.Response, err error) error { 2194 if err != nil { 2195 l.Logger.Error("Revoke share : ", err) 2196 return err 2197 } 2198 defer resp.Body.Close() 2199 2200 respbody, err := ioutil.ReadAll(resp.Body) 2201 if err != nil { 2202 l.Logger.Error("Error: Resp ", err) 2203 return err 2204 } 2205 if resp.StatusCode != http.StatusOK { 2206 l.Logger.Error(baseUrl, " Revoke share error response: ", resp.StatusCode, string(respbody)) 2207 return fmt.Errorf(string(respbody)) 2208 } 2209 data := map[string]interface{}{} 2210 err = json.Unmarshal(respbody, &data) 2211 if err != nil { 2212 return err 2213 } 2214 if data["status"].(float64) == http.StatusNotFound { 2215 notFound <- 1 2216 } 2217 return nil 2218 }) 2219 if err == nil { 2220 success <- 1 2221 } 2222 }() 2223 } 2224 wg.Wait() 2225 if len(success) == len(a.Blobbers) { 2226 if len(notFound) == len(a.Blobbers) { 2227 return errors.New("", "share not found") 2228 } 2229 return nil 2230 } 2231 return errors.New("", "consensus not reached") 2232 } 2233 2234 var ErrInvalidPrivateShare = errors.New("invalid_private_share", "private sharing is only available for encrypted file") 2235 2236 // GetAuthTicket generates an authentication ticket for the specified file or directory in the allocation. 2237 // The authentication ticket is used to grant access to the file or directory to another client. 2238 // The function takes the following parameters: 2239 // - path: The path of the file or directory (should be absolute). 2240 // - filename: The name of the file. 2241 // - referenceType: The type of reference (file or directory). 2242 // - refereeClientID: The client ID of the referee. 2243 // - refereeEncryptionPublicKey: The encryption public key of the referee. 2244 // - expiration: The expiration time of the authentication ticket in Unix timestamp format. 2245 // - availableAfter: The time after which the authentication ticket becomes available in Unix timestamp format. 2246 // 2247 // Returns the authentication ticket as a base64-encoded string and an error if any. 2248 func (a *Allocation) GetAuthTicket(path, filename string, 2249 referenceType, refereeClientID, refereeEncryptionPublicKey string, expiration int64, availableAfter *time.Time) (string, error) { 2250 2251 if !a.isInitialized() { 2252 return "", notInitialized 2253 } 2254 2255 if path == "" { 2256 return "", errors.New("invalid_path", "Invalid path for the list") 2257 } 2258 2259 path = zboxutil.RemoteClean(path) 2260 isabs := zboxutil.IsRemoteAbs(path) 2261 if !isabs { 2262 return "", errors.New("invalid_path", "Path should be valid and absolute") 2263 } 2264 2265 if referenceType == fileref.FILE && refereeClientID != "" { 2266 fileMeta, err := a.GetFileMeta(path) 2267 if err != nil { 2268 return "", err 2269 } 2270 2271 // private sharing is only available for encrypted file 2272 if fileMeta.EncryptedKey == "" { 2273 return "", ErrInvalidPrivateShare 2274 } 2275 } 2276 2277 shareReq := &ShareRequest{ 2278 expirationSeconds: expiration, 2279 allocationID: a.ID, 2280 allocationTx: a.Tx, 2281 sig: a.sig, 2282 blobbers: a.Blobbers, 2283 ctx: a.ctx, 2284 remotefilepath: path, 2285 remotefilename: filename, 2286 } 2287 2288 if referenceType == fileref.DIRECTORY { 2289 shareReq.refType = fileref.DIRECTORY 2290 } else { 2291 shareReq.refType = fileref.FILE 2292 } 2293 2294 aTicket, err := shareReq.getAuthTicket(refereeClientID, refereeEncryptionPublicKey) 2295 if err != nil { 2296 return "", err 2297 } 2298 2299 atBytes, err := json.Marshal(aTicket) 2300 if err != nil { 2301 return "", err 2302 } 2303 2304 if err := a.UploadAuthTicketToBlobber(string(atBytes), refereeEncryptionPublicKey, availableAfter); err != nil { 2305 return "", err 2306 } 2307 2308 aTicket.ReEncryptionKey = "" 2309 if err := aTicket.Sign(); err != nil { 2310 return "", err 2311 } 2312 2313 atBytes, err = json.Marshal(aTicket) 2314 if err != nil { 2315 return "", err 2316 } 2317 2318 return base64.StdEncoding.EncodeToString(atBytes), nil 2319 } 2320 2321 // UploadAuthTicketToBlobber uploads the authentication ticket to the blobbers after creating it at the client side. 2322 // The authentication ticket is uploaded to the blobbers to grant access to the file or directory to a client other than the owner. 2323 // - authTicket: The authentication ticket to upload. 2324 // - clientEncPubKey: The encryption public key of the client, used in case of private sharing. 2325 // - availableAfter: The time after which the authentication ticket becomes available in Unix timestamp format. 2326 func (a *Allocation) UploadAuthTicketToBlobber(authTicket string, clientEncPubKey string, availableAfter *time.Time) error { 2327 success := make(chan int, len(a.Blobbers)) 2328 wg := &sync.WaitGroup{} 2329 for idx := range a.Blobbers { 2330 url := a.Blobbers[idx].Baseurl 2331 body := new(bytes.Buffer) 2332 formWriter := multipart.NewWriter(body) 2333 if err := formWriter.WriteField("encryption_public_key", clientEncPubKey); err != nil { 2334 return err 2335 } 2336 if err := formWriter.WriteField("auth_ticket", authTicket); err != nil { 2337 return err 2338 } 2339 if availableAfter != nil { 2340 if err := formWriter.WriteField("available_after", strconv.FormatInt(availableAfter.Unix(), 10)); err != nil { 2341 return err 2342 } 2343 } 2344 2345 if err := formWriter.Close(); err != nil { 2346 return err 2347 } 2348 httpreq, err := zboxutil.NewShareRequest(url, a.ID, a.Tx, a.sig, body) 2349 if err != nil { 2350 return err 2351 } 2352 httpreq.Header.Set("Content-Type", formWriter.FormDataContentType()) 2353 2354 wg.Add(1) 2355 go func() { 2356 defer wg.Done() 2357 err := zboxutil.HttpDo(a.ctx, a.ctxCancelF, httpreq, func(resp *http.Response, err error) error { 2358 if err != nil { 2359 l.Logger.Error("Insert share info : ", err) 2360 return err 2361 } 2362 defer resp.Body.Close() 2363 2364 respbody, err := ioutil.ReadAll(resp.Body) 2365 if err != nil { 2366 l.Logger.Error("Error: Resp ", err) 2367 return err 2368 } 2369 if resp.StatusCode != http.StatusOK { 2370 l.Logger.Error(url, " Insert share info error response: ", resp.StatusCode, string(respbody)) 2371 return fmt.Errorf(string(respbody)) 2372 } 2373 return nil 2374 }) 2375 if err == nil { 2376 success <- 1 2377 } 2378 }() 2379 } 2380 wg.Wait() 2381 consensus := Consensus{ 2382 RWMutex: &sync.RWMutex{}, 2383 consensus: len(success), 2384 consensusThresh: a.DataShards, 2385 fullconsensus: a.fullconsensus, 2386 } 2387 if !consensus.isConsensusOk() { 2388 return errors.New("", "consensus not reached") 2389 } 2390 return nil 2391 } 2392 2393 // CancelDownload cancels the download operation for the specified remote path. 2394 // It cancels the download operation and removes the download request from the download progress map. 2395 // - remotepath: The remote path of the file to cancel the download operation. 2396 func (a *Allocation) CancelDownload(remotepath string) error { 2397 if downloadReq, ok := a.downloadProgressMap[remotepath]; ok { 2398 downloadReq.isDownloadCanceled = true 2399 downloadReq.ctxCncl() 2400 return nil 2401 } 2402 return errors.New("remote_path_not_found", "Invalid path. No download in progress for the path "+remotepath) 2403 } 2404 2405 // DownloadFromReader downloads a file from the allocation to the specified local path using the provided reader. 2406 // [DEPRECATED] Use DownloadFile or DownloadFromAuthTicket instead. 2407 func (a *Allocation) DownloadFromReader( 2408 remotePath, localPath, lookupHash, authTicket, contentMode string, 2409 verifyDownload bool, 2410 blocksPerMarker uint, 2411 ) error { 2412 2413 finfo, err := os.Stat(localPath) 2414 if err != nil { 2415 return err 2416 } 2417 if !finfo.IsDir() { 2418 return errors.New("invalid_path", "local path must be directory") 2419 } 2420 2421 r, err := a.GetAllocationFileReader(remotePath, lookupHash, authTicket, contentMode, verifyDownload, blocksPerMarker) 2422 if err != nil { 2423 return err 2424 } 2425 2426 sd := r.(*StreamDownload) 2427 2428 fileName := filepath.Base(sd.remotefilepath) 2429 var localFPath string 2430 if contentMode == DOWNLOAD_CONTENT_THUMB { 2431 localFPath = filepath.Join(localPath, fileName, ".thumb") 2432 } else { 2433 localFPath = filepath.Join(localPath, fileName) 2434 } 2435 2436 finfo, err = os.Stat(localFPath) 2437 2438 var f *os.File 2439 if errors.Is(err, os.ErrNotExist) { 2440 f, err = os.Create(localFPath) 2441 } else { 2442 _, err = r.Seek(finfo.Size(), io.SeekStart) 2443 if err != nil { 2444 return err 2445 } 2446 f, err = os.OpenFile(localFPath, os.O_WRONLY|os.O_APPEND, 0644) 2447 } 2448 2449 if err != nil { 2450 return err 2451 } 2452 defer f.Close() 2453 2454 buf := make([]byte, 1024*KB) 2455 for { 2456 n, err := r.Read(buf) 2457 if err != nil && errors.Is(err, io.EOF) { 2458 _, err = f.Write(buf[:n]) 2459 if err != nil { 2460 return err 2461 } 2462 break 2463 } 2464 _, err = f.Write(buf[:n]) 2465 if err != nil { 2466 return err 2467 } 2468 } 2469 2470 return nil 2471 } 2472 2473 // GetAllocationFileReader will check file ref existence and returns an instance that provides 2474 // io.ReadSeekerCloser interface. 2475 // [DEPRECATED] Use DownloadFile or DownloadFromAuthTicket instead. 2476 func (a *Allocation) GetAllocationFileReader( 2477 remotePath, lookupHash, authTicket, contentMode string, 2478 verifyDownload bool, 2479 blocksPerMarker uint, 2480 ) (io.ReadSeekCloser, error) { 2481 2482 if !a.isInitialized() { 2483 return nil, notInitialized 2484 } 2485 //Remove content mode option 2486 remotePath = filepath.Clean(remotePath) 2487 var res *ObjectTreeResult 2488 var err error 2489 switch { 2490 case authTicket != "": 2491 res, err = a.GetRefsWithAuthTicket(authTicket, "", "", "", "", "regular", 0, 1) 2492 case remotePath != "": 2493 res, err = a.GetRefs(remotePath, "", "", "", "", "regular", 0, 1) 2494 case lookupHash != "": 2495 res, err = a.GetRefsFromLookupHash(lookupHash, "", "", "", "", "regular", 0, 1) // 2496 default: 2497 return nil, errors.New("invalid_path", "remote path or authticket is required") 2498 } 2499 2500 if err != nil { 2501 return nil, err 2502 } 2503 2504 if len(res.Refs) == 0 { 2505 return nil, errors.New("file_does_not_exist", "") 2506 } 2507 ref := &res.Refs[0] 2508 if ref.Type != fileref.FILE { 2509 return nil, errors.New("operation_not_supported", "downloading other than file is not supported") 2510 } 2511 2512 if blocksPerMarker == 0 { 2513 blocksPerMarker = uint(numBlockDownloads) 2514 } 2515 2516 sdo := &StreamDownloadOption{ 2517 ContentMode: contentMode, 2518 AuthTicket: authTicket, 2519 VerifyDownload: verifyDownload, 2520 BlocksPerMarker: blocksPerMarker, 2521 } 2522 2523 return GetDStorageFileReader(a, ref, sdo) 2524 } 2525 2526 // DownloadFileToFileHandlerFromAuthTicket adds a download operation of a file from the allocation to the specified file handler 2527 // using the provided authentication ticket. 2528 // Triggers the downaload operations if this download request is the final one. 2529 // 2530 // Parameters: 2531 // - fileHandler: The file handler to write the downloaded file to. 2532 // - authTicket: The authentication ticket for accessing the allocation. 2533 // - remoteLookupHash: The lookup hash of the remote file. 2534 // - remoteFilename: The name of the remote file. 2535 // - verifyDownload: A boolean indicating whether to verify the downloaded file. 2536 // - status: A callback function to receive status updates during the download. 2537 // - isFinal: A boolean indicating whether this is the final download request. 2538 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 2539 // 2540 // Returns: 2541 // - An error if the download fails, nil otherwise. 2542 func (a *Allocation) DownloadFileToFileHandlerFromAuthTicket( 2543 fileHandler sys.File, 2544 authTicket string, 2545 remoteLookupHash string, 2546 remoteFilename string, 2547 verifyDownload bool, 2548 status StatusCallback, 2549 isFinal bool, 2550 downloadReqOpts ...DownloadRequestOption, 2551 ) error { 2552 return a.downloadFromAuthTicket(fileHandler, authTicket, remoteLookupHash, 1, 0, numBlockDownloads, 2553 remoteFilename, DOWNLOAD_CONTENT_FULL, verifyDownload, status, isFinal, "", downloadReqOpts...) 2554 } 2555 2556 // DownloadByBlocksToFileHandlerFromAuthTicket adds a download operation of a file from the allocation to the specified file handler 2557 // using the provided authentication ticket. 2558 // Triggers the downaload operations if this download request is the final one. 2559 // 2560 // Parameters: 2561 // - fileHandler: The file handler to write the downloaded file to. 2562 // - authTicket: The authentication ticket for accessing the allocation. 2563 // - remoteLookupHash: The lookup hash of the remote file. 2564 // - startBlock: The starting block number to download. 2565 // - endBlock: The ending block number to download. 2566 // - numBlocks: The number of blocks to download. 2567 // - remoteFilename: The name of the remote file. 2568 // - verifyDownload: A boolean indicating whether to verify the downloaded file. 2569 // - status: A callback function to receive status updates during the download. 2570 // - isFinal: A boolean indicating whether this is the final download request. 2571 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 2572 func (a *Allocation) DownloadByBlocksToFileHandlerFromAuthTicket( 2573 fileHandler sys.File, 2574 authTicket string, 2575 remoteLookupHash string, 2576 startBlock, endBlock int64, 2577 numBlocks int, 2578 remoteFilename string, 2579 verifyDownload bool, 2580 status StatusCallback, 2581 isFinal bool, 2582 downloadReqOpts ...DownloadRequestOption, 2583 ) error { 2584 return a.downloadFromAuthTicket(fileHandler, authTicket, remoteLookupHash, startBlock, endBlock, numBlocks, 2585 remoteFilename, DOWNLOAD_CONTENT_FULL, verifyDownload, status, isFinal, "", downloadReqOpts...) 2586 } 2587 2588 // DownloadThumbnailToFileHandlerFromAuthTicket adds a download operation of a thumbnail from the allocation to the specified file handler 2589 // using the provided authentication ticket. 2590 // Triggers the downaload operations if this download request is the final one. 2591 // 2592 // Parameters: 2593 // - fileHandler: The file handler to write the downloaded thumbnail to. 2594 // - authTicket: The authentication ticket for accessing the allocation. 2595 // - remoteLookupHash: The lookup hash of the remote file. 2596 // - remoteFilename: The name of the remote file. 2597 // - verifyDownload: A boolean indicating whether to verify the downloaded thumbnail. 2598 // - status: A callback function to receive status updates during the download. 2599 // - isFinal: A boolean indicating whether this is the final download request. 2600 func (a *Allocation) DownloadThumbnailToFileHandlerFromAuthTicket( 2601 fileHandler sys.File, 2602 authTicket string, 2603 remoteLookupHash string, 2604 remoteFilename string, 2605 verifyDownload bool, 2606 status StatusCallback, 2607 isFinal bool, 2608 ) error { 2609 return a.downloadFromAuthTicket(fileHandler, authTicket, remoteLookupHash, 1, 0, numBlockDownloads, 2610 remoteFilename, DOWNLOAD_CONTENT_THUMB, verifyDownload, status, isFinal, "") 2611 } 2612 2613 // DownloadThumbnailFromAuthTicket downloads a thumbnail from the allocation to the specified local path using the provided authentication ticket. 2614 // Triggers the downaload operations if this download request is the final one. 2615 // 2616 // Parameters: 2617 // - localPath: The local path to save the downloaded thumbnail. 2618 // - authTicket: The authentication ticket for accessing the allocation. 2619 // - remoteLookupHash: The lookup hash of the remote file. 2620 // - remoteFilename: The name of the remote file. 2621 // - verifyDownload: A boolean indicating whether to verify the downloaded thumbnail. 2622 // - status: A callback function to receive status updates during the download. 2623 // - isFinal: A boolean indicating whether this is the final download request. 2624 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 2625 func (a *Allocation) DownloadThumbnailFromAuthTicket( 2626 localPath string, 2627 authTicket string, 2628 remoteLookupHash string, 2629 remoteFilename string, 2630 verifyDownload bool, 2631 status StatusCallback, 2632 isFinal bool, 2633 downloadReqOpts ...DownloadRequestOption, 2634 ) error { 2635 f, localFilePath, toKeep, err := a.prepareAndOpenLocalFile(localPath, remoteFilename) 2636 if err != nil { 2637 return err 2638 } 2639 downloadReqOpts = append(downloadReqOpts, WithFileCallback(func() { 2640 f.Close() //nolint: errcheck 2641 })) 2642 err = a.downloadFromAuthTicket(f, authTicket, remoteLookupHash, 1, 0, numBlockDownloads, remoteFilename, 2643 DOWNLOAD_CONTENT_THUMB, verifyDownload, status, isFinal, localFilePath, downloadReqOpts...) 2644 if err != nil { 2645 if !toKeep { 2646 os.Remove(localFilePath) //nolint: errcheck 2647 } 2648 f.Close() //nolint: errcheck 2649 return err 2650 } 2651 return nil 2652 } 2653 2654 // DownloadFromAuthTicket downloads a file from the allocation to the specified local path using the provided authentication ticket. 2655 // Triggers the downaload operations if this download request is the final one. 2656 // 2657 // Parameters: 2658 // - localPath: The local path to save the downloaded file. 2659 // - authTicket: The authentication ticket for accessing the allocation. 2660 // - remoteLookupHash: The lookup hash of the remote file. 2661 // - remoteFilename: The name of the remote file. 2662 // - verifyDownload: A boolean indicating whether to verify the downloaded file. 2663 // - status: A callback function to receive status updates during the download. 2664 // - isFinal: A boolean indicating whether this is the final download request. 2665 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 2666 func (a *Allocation) DownloadFromAuthTicket(localPath string, authTicket string, 2667 remoteLookupHash string, remoteFilename string, verifyDownload bool, status StatusCallback, isFinal bool, downloadReqOpts ...DownloadRequestOption) error { 2668 f, localFilePath, toKeep, err := a.prepareAndOpenLocalFile(localPath, remoteFilename) 2669 if err != nil { 2670 return err 2671 } 2672 downloadReqOpts = append(downloadReqOpts, WithFileCallback(func() { 2673 f.Close() //nolint: errcheck 2674 })) 2675 err = a.downloadFromAuthTicket(f, authTicket, remoteLookupHash, 1, 0, numBlockDownloads, remoteFilename, 2676 DOWNLOAD_CONTENT_FULL, verifyDownload, status, isFinal, localFilePath, downloadReqOpts...) 2677 if err != nil { 2678 if !toKeep { 2679 os.Remove(localFilePath) //nolint: errcheck 2680 } 2681 f.Close() //nolint: errcheck 2682 return err 2683 } 2684 return nil 2685 } 2686 2687 // DownloadFromAuthTicketByBlocks downloads a file from the allocation to the specified local path using the provided authentication ticket. 2688 // The file is downloaded by blocks from the specified start block to the end block. 2689 // Triggers the downaload operations if this download request is the final one. 2690 // 2691 // Parameters: 2692 // - localPath: The local path to save the downloaded file. 2693 // - authTicket: The authentication ticket for accessing the allocation. 2694 // - startBlock: The starting block number to download. 2695 // - endBlock: The ending block number to download. 2696 // - numBlocks: The number of blocks to download. 2697 // - remoteLookupHash: The lookup hash of the remote file. 2698 // - remoteFilename: The name of the remote file. 2699 // - verifyDownload: A boolean indicating whether to verify the downloaded file. 2700 // - status: A callback function to receive status updates during the download. 2701 // - isFinal: A boolean indicating whether this is the final download request. 2702 // - downloadReqOpts: the options of the download operation as operation functions that customize the download operation. 2703 func (a *Allocation) DownloadFromAuthTicketByBlocks(localPath string, 2704 authTicket string, startBlock int64, endBlock int64, numBlocks int, 2705 remoteLookupHash string, remoteFilename string, verifyDownload bool, 2706 status StatusCallback, isFinal bool, downloadReqOpts ...DownloadRequestOption) error { 2707 2708 f, localFilePath, toKeep, err := a.prepareAndOpenLocalFile(localPath, remoteFilename) 2709 if err != nil { 2710 return err 2711 } 2712 downloadReqOpts = append(downloadReqOpts, WithFileCallback(func() { 2713 f.Close() //nolint: errcheck 2714 })) 2715 err = a.downloadFromAuthTicket(f, authTicket, remoteLookupHash, startBlock, endBlock, numBlockDownloads, remoteFilename, 2716 DOWNLOAD_CONTENT_FULL, verifyDownload, status, isFinal, localFilePath, downloadReqOpts...) 2717 if err != nil { 2718 if !toKeep { 2719 os.Remove(localFilePath) //nolint: errcheck 2720 } 2721 f.Close() //nolint: errcheck 2722 return err 2723 } 2724 return nil 2725 } 2726 2727 func (a *Allocation) downloadFromAuthTicket(fileHandler sys.File, authTicket string, 2728 remoteLookupHash string, startBlock int64, endBlock int64, numBlocks int, 2729 remoteFilename string, contentMode string, verifyDownload bool, 2730 status StatusCallback, isFinal bool, localFilePath string, downlaodReqOpts ...DownloadRequestOption) error { 2731 2732 sEnc, err := base64.StdEncoding.DecodeString(authTicket) 2733 if err != nil { 2734 return errors.New("auth_ticket_decode_error", "Error decoding the auth ticket."+err.Error()) 2735 } 2736 at := &marker.AuthTicket{} 2737 err = json.Unmarshal(sEnc, at) 2738 if err != nil { 2739 return errors.New("auth_ticket_decode_error", "Error unmarshaling the auth ticket."+err.Error()) 2740 } 2741 2742 if len(a.Blobbers) == 0 { 2743 return noBLOBBERS 2744 } 2745 2746 downloadReq := &DownloadRequest{Consensus: Consensus{RWMutex: &sync.RWMutex{}}} 2747 downloadReq.maskMu = &sync.Mutex{} 2748 downloadReq.allocationID = a.ID 2749 downloadReq.allocationTx = a.Tx 2750 downloadReq.sig = a.sig 2751 downloadReq.allocOwnerID = a.Owner 2752 downloadReq.allocOwnerPubKey = a.OwnerPublicKey 2753 downloadReq.ctx, downloadReq.ctxCncl = context.WithCancel(a.ctx) 2754 downloadReq.fileHandler = fileHandler 2755 downloadReq.localFilePath = localFilePath 2756 downloadReq.remotefilepathhash = remoteLookupHash 2757 downloadReq.authTicket = at 2758 downloadReq.statusCallback = status 2759 downloadReq.downloadMask = zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1) 2760 downloadReq.blobbers = a.Blobbers 2761 downloadReq.datashards = a.DataShards 2762 downloadReq.parityshards = a.ParityShards 2763 downloadReq.contentMode = contentMode 2764 downloadReq.startBlock = startBlock - 1 2765 downloadReq.endBlock = endBlock 2766 downloadReq.numBlocks = int64(numBlocks) 2767 downloadReq.shouldVerify = verifyDownload 2768 downloadReq.fullconsensus = a.fullconsensus 2769 downloadReq.consensusThresh = a.consensusThreshold 2770 downloadReq.isEnterprise = a.IsEnterprise 2771 downloadReq.downloadQueue = make(downloadQueue, len(a.Blobbers)) 2772 for i := 0; i < len(a.Blobbers); i++ { 2773 downloadReq.downloadQueue[i].timeTaken = 1000000 2774 } 2775 downloadReq.connectionID = zboxutil.NewConnectionId() 2776 downloadReq.completedCallback = func(remotepath string, remotepathHash string) { 2777 a.mutex.Lock() 2778 defer a.mutex.Unlock() 2779 delete(a.downloadProgressMap, remotepathHash) 2780 } 2781 downloadReq.fileCallback = func() { 2782 if downloadReq.fileHandler != nil { 2783 downloadReq.fileHandler.Close() //nolint: errcheck 2784 } 2785 } 2786 for _, opt := range downlaodReqOpts { 2787 opt(downloadReq) 2788 } 2789 a.mutex.Lock() 2790 a.downloadProgressMap[remoteLookupHash] = downloadReq 2791 if len(a.downloadRequests) > 0 { 2792 downloadReq.connectionID = a.downloadRequests[0].connectionID 2793 } 2794 a.downloadRequests = append(a.downloadRequests, downloadReq) 2795 if isFinal { 2796 downloadOps := a.downloadRequests 2797 a.downloadRequests = nil 2798 go func() { 2799 a.processReadMarker(downloadOps) 2800 }() 2801 } 2802 a.mutex.Unlock() 2803 return nil 2804 } 2805 2806 // StartRepair starts the repair operation for the specified path in the allocation. 2807 // It starts the repair operation and returns an error if the path is not found. 2808 // Repair operation is used to repair the files in the allocation, which are corrupted or missing in some blobbers. 2809 // - localRootPath: The local root path to repair the files. 2810 // - pathToRepair: The path to repair in the allocation. 2811 // - statusCB: A callback function to receive status updates during the repair operation. 2812 func (a *Allocation) StartRepair(localRootPath, pathToRepair string, statusCB StatusCallback) error { 2813 if !a.isInitialized() { 2814 return notInitialized 2815 } 2816 2817 listDir, err := a.ListDir(pathToRepair, 2818 WithListRequestForRepair(true), 2819 WithListRequestPageLimit(-1), 2820 ) 2821 if err != nil { 2822 return err 2823 } 2824 2825 repairReq := &RepairRequest{ 2826 listDir: listDir, 2827 localRootPath: localRootPath, 2828 statusCB: statusCB, 2829 } 2830 2831 repairReq.completedCallback = func() { 2832 a.mutex.Lock() 2833 defer a.mutex.Unlock() 2834 a.repairRequestInProgress = nil 2835 } 2836 2837 go func() { 2838 a.repairChan <- repairReq 2839 a.mutex.Lock() 2840 defer a.mutex.Unlock() 2841 a.repairRequestInProgress = repairReq 2842 }() 2843 return nil 2844 } 2845 2846 // RepairAlloc repairs all the files in allocation 2847 func (a *Allocation) RepairAlloc(statusCB StatusCallback) (err error) { 2848 var dir string 2849 if IsWasm { 2850 dir = "/tmp" 2851 } else { 2852 dir, err = os.Getwd() 2853 if err != nil { 2854 return err 2855 } 2856 } 2857 return a.StartRepair(dir, "/", statusCB) 2858 } 2859 2860 // RepairSize Gets the size in bytes to repair allocation 2861 // - remotePath: the path to repair in the allocation. 2862 func (a *Allocation) RepairSize(remotePath string) (RepairSize, error) { 2863 if !a.isInitialized() { 2864 return RepairSize{}, notInitialized 2865 } 2866 2867 dir, err := a.ListDir(remotePath, 2868 WithListRequestForRepair(true), 2869 WithListRequestPageLimit(-1), 2870 ) 2871 if err != nil { 2872 return RepairSize{}, err 2873 } 2874 2875 repairReq := RepairRequest{ 2876 allocation: a, 2877 } 2878 return repairReq.Size(context.Background(), dir) 2879 } 2880 2881 // CancelUpload cancels the upload operation for the specified remote path. 2882 // It cancels the upload operation and returns an error if the remote path is not found. 2883 // - remotePath: The remote path to cancel the upload operation. 2884 func (a *Allocation) CancelUpload(remotePath string) error { 2885 cancelLock.Lock() 2886 cancelFunc, ok := CancelOpCtx[remotePath] 2887 cancelLock.Unlock() 2888 if !ok { 2889 return errors.New("remote_path_not_found", "Invalid path. No upload in progress for the path "+remotePath) 2890 } else { 2891 cancelFunc(fmt.Errorf("upload canceled by user")) 2892 } 2893 return nil 2894 } 2895 2896 // PauseUpload pauses the upload operation for the specified remote path. 2897 // It pauses the upload operation and returns an error if the remote path is not found. 2898 // - remotePath: The remote path to pause the upload operation. 2899 func (a *Allocation) PauseUpload(remotePath string) error { 2900 cancelLock.Lock() 2901 cancelFunc, ok := CancelOpCtx[remotePath] 2902 cancelLock.Unlock() 2903 if !ok { 2904 logger.Logger.Error("PauseUpload: remote path not found", remotePath) 2905 return errors.New("remote_path_not_found", "Invalid path. No upload in progress for the path "+remotePath) 2906 } else { 2907 logger.Logger.Info("PauseUpload: remote path found", remotePath) 2908 cancelFunc(ErrPauseUpload) 2909 } 2910 return nil 2911 } 2912 2913 // CancelRepair cancels the repair operation for the allocation. 2914 // It cancels the repair operation and returns an error if no repair is in progress for the allocation. 2915 func (a *Allocation) CancelRepair() error { 2916 if a.repairRequestInProgress != nil { 2917 a.repairRequestInProgress.isRepairCanceled = true 2918 return nil 2919 } 2920 return errors.New("invalid_cancel_repair_request", "No repair in progress for the allocation") 2921 } 2922 2923 func (a *Allocation) GetMaxWriteReadFromBlobbers(blobbers []*BlobberAllocation) (maxW float64, maxR float64, err error) { 2924 if !a.isInitialized() { 2925 return 0, 0, notInitialized 2926 } 2927 2928 if len(blobbers) == 0 { 2929 return 0, 0, noBLOBBERS 2930 } 2931 2932 maxWritePrice, maxReadPrice := 0.0, 0.0 2933 for _, v := range blobbers { 2934 writePrice, err := v.Terms.WritePrice.ToToken() 2935 if err != nil { 2936 return 0, 0, err 2937 } 2938 if writePrice > maxWritePrice { 2939 maxWritePrice = writePrice 2940 } 2941 readPrice, err := v.Terms.ReadPrice.ToToken() 2942 if err != nil { 2943 return 0, 0, err 2944 } 2945 if readPrice > maxReadPrice { 2946 maxReadPrice = readPrice 2947 } 2948 } 2949 2950 return maxWritePrice, maxReadPrice, nil 2951 } 2952 2953 // GetMaxWriteRead returns the maximum write and read prices from the blobbers in the allocation. 2954 func (a *Allocation) GetMaxWriteRead() (maxW float64, maxR float64, err error) { 2955 return a.GetMaxWriteReadFromBlobbers(a.BlobberDetails) 2956 } 2957 2958 // GetMinWriteRead returns the minimum write and read prices from the blobbers in the allocation. 2959 func (a *Allocation) GetMinWriteRead() (minW float64, minR float64, err error) { 2960 if !a.isInitialized() { 2961 return 0, 0, notInitialized 2962 } 2963 2964 blobbersCopy := a.BlobberDetails 2965 if len(blobbersCopy) == 0 { 2966 return 0, 0, noBLOBBERS 2967 } 2968 2969 minWritePrice, minReadPrice := -1.0, -1.0 2970 for _, v := range blobbersCopy { 2971 writePrice, err := v.Terms.WritePrice.ToToken() 2972 if err != nil { 2973 return 0, 0, err 2974 } 2975 if writePrice < minWritePrice || minWritePrice < 0 { 2976 minWritePrice = writePrice 2977 } 2978 readPrice, err := v.Terms.ReadPrice.ToToken() 2979 if err != nil { 2980 return 0, 0, err 2981 } 2982 if readPrice < minReadPrice || minReadPrice < 0 { 2983 minReadPrice = readPrice 2984 } 2985 } 2986 2987 return minWritePrice, minReadPrice, nil 2988 } 2989 2990 // GetMaxStorageCostFromBlobbers returns the maximum storage cost from a given list of allocation blobbers. 2991 // - size: The size of the file to calculate the storage cost. 2992 // - blobbers: The list of blobbers to calculate the storage cost. 2993 func (a *Allocation) GetMaxStorageCostFromBlobbers(size int64, blobbers []*BlobberAllocation) (float64, error) { 2994 var cost common.Balance // total price for size / duration 2995 2996 for _, d := range blobbers { 2997 var err error 2998 cost, err = common.AddBalance(cost, a.uploadCostForBlobber(float64(d.Terms.WritePrice), size, 2999 a.DataShards, a.ParityShards)) 3000 if err != nil { 3001 return 0.0, err 3002 } 3003 } 3004 3005 return cost.ToToken() 3006 } 3007 3008 // GetMaxStorageCost returns the maximum storage cost from the blobbers in the allocation. 3009 // - size: The size of the file to calculate the storage cost. 3010 func (a *Allocation) GetMaxStorageCost(size int64) (float64, error) { 3011 var cost common.Balance // total price for size / duration 3012 3013 for _, d := range a.BlobberDetails { 3014 fmt.Printf("write price for blobber %f datashards %d parity %d\n", 3015 float64(d.Terms.WritePrice), a.DataShards, a.ParityShards) 3016 3017 var err error 3018 cost, err = common.AddBalance(cost, a.uploadCostForBlobber(float64(d.Terms.WritePrice), size, 3019 a.DataShards, a.ParityShards)) 3020 if err != nil { 3021 return 0.0, err 3022 } 3023 } 3024 fmt.Printf("Total cost %d\n", cost) 3025 return cost.ToToken() 3026 } 3027 3028 // GetMinStorageCost returns the minimum storage cost from the blobbers in the allocation. 3029 // - size: The size of the file to calculate the storage cost. 3030 func (a *Allocation) GetMinStorageCost(size int64) (common.Balance, error) { 3031 minW, _, err := a.GetMinWriteRead() 3032 if err != nil { 3033 return 0, err 3034 } 3035 3036 return a.uploadCostForBlobber(minW, size, a.DataShards, a.ParityShards), nil 3037 } 3038 3039 func (a *Allocation) uploadCostForBlobber(price float64, size int64, data, parity int) ( 3040 cost common.Balance) { 3041 3042 if data == 0 || parity == 0 { 3043 return 0.0 3044 } 3045 3046 var ps = (size + int64(data) - 1) / int64(data) 3047 ps = ps * int64(data+parity) 3048 3049 return common.Balance(price * a.sizeInGB(ps)) 3050 } 3051 3052 func (a *Allocation) sizeInGB(size int64) float64 { 3053 return float64(size) / GB 3054 } 3055 3056 func (a *Allocation) getConsensuses() (fullConsensus, consensusThreshold int) { 3057 if a.DataShards == 0 { 3058 return 0, 0 3059 } 3060 3061 if a.ParityShards == 0 { 3062 return a.DataShards, a.DataShards 3063 } 3064 3065 return a.DataShards + a.ParityShards, a.DataShards + 1 3066 } 3067 3068 func (a *Allocation) SetConsensusThreshold() { 3069 a.consensusThreshold = a.DataShards 3070 } 3071 3072 // UpdateWithRepair updates the allocation with the specified parameters and starts the repair operation if required. 3073 // It updates the allocation with the specified parameters and starts the repair operation if required. 3074 // - size: The updated size of the allocation to update. 3075 // - extend: A boolean indicating whether to extend the expiration of the allocation. 3076 // - lock: The lock value to update the allocation. 3077 // - addBlobberId: The blobber ID to add to the allocation. 3078 // - addBlobberAuthTicket: The authentication ticket for the blobber to add to the allocation. 3079 // - removeBlobberId: The blobber ID to remove from the allocation. 3080 // - setThirdPartyExtendable: A boolean indicating whether to set the allocation as third-party extendable. If set to true, the allocation can be extended in terms of size by a non-owner. 3081 // - fileOptionsParams: The file options parameters which control permissions of the files of the allocations. 3082 // - statusCB: A callback function to receive status updates during the update operation. 3083 func (a *Allocation) UpdateWithRepair( 3084 size int64, 3085 extend bool, 3086 lock uint64, 3087 addBlobberId, addBlobberAuthTicket, removeBlobberId string, 3088 setThirdPartyExtendable bool, fileOptionsParams *FileOptionsParameters, 3089 statusCB StatusCallback, 3090 ) (string, error) { 3091 updatedAlloc, hash, isRepairRequired, err := a.UpdateWithStatus(size, extend, lock, addBlobberId, addBlobberAuthTicket, removeBlobberId, setThirdPartyExtendable, fileOptionsParams, statusCB) 3092 if err != nil { 3093 return hash, err 3094 } 3095 3096 if isRepairRequired { 3097 if err := updatedAlloc.RepairAlloc(statusCB); err != nil { 3098 return hash, err 3099 } 3100 } 3101 3102 return hash, nil 3103 } 3104 3105 // UpdateWithStatus updates the allocation with the specified parameters. 3106 // It updates the allocation with the specified parameters and returns the updated allocation, hash, and a boolean indicating whether repair is required. 3107 // - size: The updated size of the allocation to update. 3108 // - extend: A boolean indicating whether to extend the expiration of the allocation. 3109 // - lock: The lock value to update the allocation. 3110 // - addBlobberId: The blobber ID to add to the allocation. 3111 // - addBlobberAuthTicket: The authentication ticket for the blobber to add to the allocation. Used in case of adding a restricted blobber. 3112 // - removeBlobberId: The blobber ID to remove from the allocation. 3113 // - setThirdPartyExtendable: A boolean indicating whether to set the allocation as third-party extendable. If set to true, the allocation can be extended in terms of size by a non-owner. 3114 // - fileOptionsParams: The file options parameters which control permissions of the files of the allocations. 3115 // - statusCB: A callback function to receive status updates during the update operation. 3116 // 3117 // Returns the updated allocation, hash, and a boolean indicating whether repair is required. 3118 func (a *Allocation) UpdateWithStatus( 3119 size int64, 3120 extend bool, 3121 lock uint64, 3122 addBlobberId, addBlobberAuthTicket, removeBlobberId string, 3123 setThirdPartyExtendable bool, fileOptionsParams *FileOptionsParameters, 3124 statusCB StatusCallback, 3125 ) (*Allocation, string, bool, error) { 3126 var ( 3127 alloc *Allocation 3128 isRepairRequired bool 3129 ) 3130 if lock > math.MaxInt64 { 3131 return alloc, "", isRepairRequired, errors.New("invalid_lock", "int64 overflow on lock value") 3132 } 3133 3134 l.Logger.Info("Updating allocation") 3135 hash, _, err := UpdateAllocation(size, extend, a.ID, lock, addBlobberId, addBlobberAuthTicket, removeBlobberId, setThirdPartyExtendable, fileOptionsParams) 3136 if err != nil { 3137 return alloc, "", isRepairRequired, err 3138 } 3139 l.Logger.Info(fmt.Sprintf("allocation updated with hash: %s", hash)) 3140 3141 if addBlobberId != "" { 3142 l.Logger.Info("waiting for a minute for the blobber to be added to network") 3143 3144 deadline := time.Now().Add(1 * time.Minute) 3145 for time.Now().Before(deadline) { 3146 alloc, err = GetAllocation(a.ID) 3147 if err != nil { 3148 l.Logger.Error("failed to get allocation") 3149 return alloc, hash, isRepairRequired, err 3150 } 3151 3152 for _, blobber := range alloc.Blobbers { 3153 if addBlobberId == blobber.ID { 3154 l.Logger.Info("allocation updated successfully") 3155 a = alloc 3156 goto repair 3157 } 3158 } 3159 time.Sleep(1 * time.Second) 3160 } 3161 return alloc, "", isRepairRequired, errors.New("", "new blobber not found in the updated allocation") 3162 } 3163 3164 repair: 3165 l.Logger.Info("starting repair") 3166 3167 shouldRepair := false 3168 if addBlobberId != "" { 3169 shouldRepair = true 3170 } 3171 3172 if shouldRepair { 3173 isRepairRequired = true 3174 } 3175 3176 return alloc, hash, isRepairRequired, nil 3177 } 3178 3179 func (a *Allocation) DownloadDirectory(ctx context.Context, remotePath, localPath, authTicket string, sb StatusCallback) error { 3180 if len(a.Blobbers) == 0 { 3181 return noBLOBBERS 3182 } 3183 localPath = filepath.Clean(localPath) 3184 dirID := zboxutil.NewConnectionId() 3185 err := sys.Files.CreateDirectory(dirID) 3186 if err != nil { 3187 if sb != nil { 3188 sb.Error(a.ID, remotePath, OpDownload, err) 3189 } 3190 return err 3191 } 3192 defer sys.Files.RemoveAllDirectories() 3193 3194 oRefChan := a.ListObjects(ctx, remotePath, "", "", "", fileref.FILE, fileref.REGULAR, 0, getRefPageLimit) 3195 refSlice := make([]ORef, BatchSize) 3196 refIndex := 0 3197 wg := &sync.WaitGroup{} 3198 dirPath := path.Dir(remotePath) 3199 var totalSize int 3200 for oRef := range oRefChan { 3201 if contextCanceled(ctx) { 3202 if sb != nil { 3203 sb.Error(a.ID, remotePath, OpDownload, ctx.Err()) 3204 } 3205 return ctx.Err() 3206 } 3207 if oRef.Err != nil { 3208 if sb != nil { 3209 sb.Error(a.ID, remotePath, OpDownload, oRef.Err) 3210 } 3211 return oRef.Err 3212 } 3213 refSlice[refIndex] = oRef 3214 refIndex++ 3215 if refIndex == BatchSize { 3216 wg.Add(refIndex) 3217 downloadStatusBar := &StatusBar{ 3218 wg: wg, 3219 sb: sb, 3220 } 3221 for ind, ref := range refSlice { 3222 fPath := ref.Path 3223 if dirPath != "/" { 3224 fPath = strings.TrimPrefix(ref.Path, dirPath) 3225 } 3226 if localPath != "" { 3227 fPath = filepath.Join(localPath, fPath) 3228 } 3229 fh, err := sys.Files.GetFileHandler(dirID, fPath) 3230 if err != nil { 3231 if sb != nil { 3232 sb.Error(a.ID, remotePath, OpDownload, err) 3233 } 3234 return err 3235 } 3236 if authTicket == "" { 3237 _ = a.DownloadFileToFileHandler(fh, ref.Path, false, downloadStatusBar, ind == BatchSize-1, WithFileCallback(func() { 3238 fh.Close() //nolint: errcheck 3239 })) //nolint: errcheck 3240 } else { 3241 _ = a.DownloadFileToFileHandlerFromAuthTicket(fh, authTicket, ref.LookupHash, ref.Path, false, downloadStatusBar, ind == BatchSize-1, WithFileCallback(func() { 3242 fh.Close() //nolint: errcheck 3243 })) //nolint: errcheck 3244 } 3245 totalSize += int(ref.ActualFileSize) 3246 } 3247 wg.Wait() 3248 if downloadStatusBar.err != nil { 3249 return downloadStatusBar.err 3250 } 3251 refIndex = 0 3252 } 3253 } 3254 if refIndex > 0 { 3255 wg.Add(refIndex) 3256 downloadStatusBar := &StatusBar{ 3257 wg: wg, 3258 sb: sb, 3259 } 3260 for ind, ref := range refSlice[:refIndex] { 3261 fPath := ref.Path 3262 if dirPath != "/" { 3263 fPath = strings.TrimPrefix(ref.Path, dirPath) 3264 } 3265 if localPath != "" { 3266 fPath = filepath.Join(localPath, fPath) 3267 } 3268 fh, err := sys.Files.GetFileHandler(dirID, fPath) 3269 if err != nil { 3270 if sb != nil { 3271 sb.Error(a.ID, remotePath, OpDownload, err) 3272 } 3273 return err 3274 } 3275 if authTicket == "" { 3276 _ = a.DownloadFileToFileHandler(fh, ref.Path, false, downloadStatusBar, ind == refIndex-1, WithFileCallback(func() { 3277 fh.Close() //nolint: errcheck 3278 })) //nolint: errcheck 3279 } else { 3280 _ = a.DownloadFileToFileHandlerFromAuthTicket(fh, authTicket, ref.LookupHash, ref.Path, false, downloadStatusBar, ind == refIndex-1, WithFileCallback(func() { 3281 fh.Close() //nolint: errcheck 3282 })) //nolint: errcheck 3283 } 3284 totalSize += int(ref.ActualFileSize) 3285 } 3286 wg.Wait() 3287 if downloadStatusBar.err != nil { 3288 if sb != nil { 3289 sb.Error(a.ID, remotePath, OpDownload, downloadStatusBar.err) 3290 } 3291 return downloadStatusBar.err 3292 } 3293 } 3294 refSlice = nil 3295 if sb != nil { 3296 sb.Completed(a.ID, remotePath, filepath.Base(remotePath), "", totalSize, OpDownload) 3297 } 3298 return nil 3299 } 3300 3301 // contextCanceled returns whether a context is canceled. 3302 func contextCanceled(ctx context.Context) bool { 3303 select { 3304 case <-ctx.Done(): 3305 return true 3306 default: 3307 return false 3308 } 3309 }