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  }