github.com/0chain/gosdk@v1.17.11/wasmsdk/blobber.go (about)

     1  //go:build js && wasm
     2  // +build js,wasm
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"path"
    13  	"strings"
    14  	"sync"
    15  	"syscall/js"
    16  	"time"
    17  
    18  	"github.com/0chain/gosdk/constants"
    19  	"github.com/0chain/gosdk/core/common"
    20  	"github.com/0chain/gosdk/core/encryption"
    21  	"github.com/0chain/gosdk/core/pathutil"
    22  	"github.com/0chain/gosdk/core/sys"
    23  	"github.com/hack-pad/safejs"
    24  
    25  	"github.com/0chain/gosdk/core/transaction"
    26  	"github.com/0chain/gosdk/wasmsdk/jsbridge"
    27  	"github.com/0chain/gosdk/zboxcore/fileref"
    28  	"github.com/0chain/gosdk/zboxcore/sdk"
    29  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    30  
    31  	"github.com/hack-pad/go-webworkers/worker"
    32  )
    33  
    34  const FileOperationInsert = "insert"
    35  
    36  var (
    37  	downloadDirContextMap = make(map[string]context.CancelCauseFunc)
    38  	downloadDirLock       = sync.Mutex{}
    39  )
    40  
    41  // listObjects list allocation objects from its blobbers
    42  //   - allocationID is the allocation id
    43  //   - remotePath is the remote path of the file
    44  //   - offset is the offset of the list
    45  //   - pageLimit is the limit of the page
    46  func listObjects(allocationID string, remotePath string, offset, pageLimit int) (*sdk.ListResult, error) {
    47  	defer func() {
    48  		if r := recover(); r != nil {
    49  			PrintError("Recovered in listObjects Error", r)
    50  		}
    51  	}()
    52  	alloc, err := getAllocation(allocationID)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	return alloc.ListDir(remotePath, sdk.WithListRequestOffset(offset), sdk.WithListRequestPageLimit(pageLimit))
    58  }
    59  
    60  // listObjectsFromAuthTicket list allocation objects from its blobbers using auth ticket
    61  //   - allocationID is the allocation id
    62  //   - authTicket is the auth ticket, provided usually by a non-owner to be able to access a shared source
    63  //   - lookupHash is the lookup hash
    64  //   - offset is the offset of the list
    65  //   - pageLimit is the limit of the page
    66  func listObjectsFromAuthTicket(allocationID, authTicket, lookupHash string, offset, pageLimit int) (*sdk.ListResult, error) {
    67  	alloc, err := getAllocation(allocationID)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	return alloc.ListDirFromAuthTicket(authTicket, lookupHash, sdk.WithListRequestOffset(offset), sdk.WithListRequestPageLimit(pageLimit))
    72  }
    73  
    74  // cancelUpload cancel the upload operation of the file
    75  //   - allocationID is the allocation id
    76  //   - remotePath is the remote path of the file
    77  func cancelUpload(allocationID, remotePath string) error {
    78  	allocationObj, err := getAllocation(allocationID)
    79  	if err != nil {
    80  		PrintError("Error fetching the allocation", err)
    81  		return err
    82  	}
    83  	return allocationObj.CancelUpload(remotePath)
    84  }
    85  
    86  // pauseUpload pause the upload operation of the file
    87  //   - allocationID is the allocation id
    88  //   - remotePath is the remote path of the file
    89  func pauseUpload(allocationID, remotePath string) error {
    90  	allocationObj, err := getAllocation(allocationID)
    91  	if err != nil {
    92  		PrintError("Error fetching the allocation", err)
    93  		return err
    94  	}
    95  	return allocationObj.PauseUpload(remotePath)
    96  }
    97  
    98  // createDir create a directory on blobbers
    99  //   - allocationID is the allocation id
   100  //   - remotePath is the remote path of the file
   101  func createDir(allocationID, remotePath string) error {
   102  	if len(allocationID) == 0 {
   103  		return RequiredArg("allocationID")
   104  	}
   105  
   106  	if len(remotePath) == 0 {
   107  		return RequiredArg("remotePath")
   108  	}
   109  
   110  	allocationObj, err := getAllocation(allocationID)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	return allocationObj.DoMultiOperation([]sdk.OperationRequest{
   116  		{
   117  			OperationType: constants.FileOperationCreateDir,
   118  			RemotePath:    remotePath,
   119  		},
   120  	})
   121  }
   122  
   123  // getFileStats get file stats from blobbers
   124  //   - allocationID is the allocation id
   125  //   - remotePath is the remote path of the file
   126  func getFileStats(allocationID, remotePath string) ([]*sdk.FileStats, error) {
   127  	if len(allocationID) == 0 {
   128  		return nil, RequiredArg("allocationID")
   129  	}
   130  
   131  	if len(remotePath) == 0 {
   132  		return nil, RequiredArg("remotePath")
   133  	}
   134  
   135  	allocationObj, err := getAllocation(allocationID)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	fileStats, err := allocationObj.GetFileStats(remotePath)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	var stats []*sdk.FileStats
   146  
   147  	for _, it := range fileStats {
   148  		stats = append(stats, it)
   149  	}
   150  
   151  	return stats, nil
   152  }
   153  
   154  // updateBlobberSettings expects settings JSON of type sdk.Blobber
   155  // and updates the blobber settings. Can only be called by the owner of the blobber.
   156  //   - blobberSettingsJson is the blobber settings in JSON format
   157  func updateBlobberSettings(blobberSettingsJson string) (*transaction.Transaction, error) {
   158  	var blobberSettings sdk.Blobber
   159  	err := json.Unmarshal([]byte(blobberSettingsJson), &blobberSettings)
   160  	if err != nil {
   161  		sdkLogger.Error(err)
   162  		return nil, err
   163  	}
   164  
   165  	var sn = transaction.SmartContractTxnData{
   166  		Name:      transaction.STORAGESC_UPDATE_BLOBBER_SETTINGS,
   167  		InputArgs: blobberSettings,
   168  	}
   169  
   170  	_, _, _, txn, err := sdk.StorageSmartContractTxn(sn)
   171  	return txn, err
   172  }
   173  
   174  // Delete delete file from an allocation, only the owner of the allocation can delete a file.
   175  //   - allocationID is the allocation id
   176  //   - remotePath is the remote path of the file
   177  func Delete(allocationID, remotePath string) (*FileCommandResponse, error) {
   178  
   179  	if len(allocationID) == 0 {
   180  		return nil, RequiredArg("allocationID")
   181  	}
   182  
   183  	if len(remotePath) == 0 {
   184  		return nil, RequiredArg("remotePath")
   185  	}
   186  
   187  	allocationObj, err := getAllocation(allocationID)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	err = allocationObj.DoMultiOperation([]sdk.OperationRequest{
   193  		{
   194  			OperationType: constants.FileOperationDelete,
   195  			RemotePath:    remotePath,
   196  		},
   197  	})
   198  	sdkLogger.Info(remotePath + " deleted")
   199  
   200  	resp := &FileCommandResponse{
   201  		CommandSuccess: true,
   202  	}
   203  
   204  	return resp, nil
   205  }
   206  
   207  // Rename rename file on an allocation, only the owner of the allocation can rename a file.
   208  //   - allocationID is the allocation id
   209  //   - remotePath is the remote path of the file
   210  //   - destName is the new name of the file
   211  func Rename(allocationID, remotePath, destName string) (*FileCommandResponse, error) {
   212  	if len(allocationID) == 0 {
   213  		return nil, RequiredArg("allocationID")
   214  	}
   215  
   216  	if len(remotePath) == 0 {
   217  		return nil, RequiredArg("remotePath")
   218  	}
   219  
   220  	if len(destName) == 0 {
   221  		return nil, RequiredArg("destName")
   222  	}
   223  
   224  	allocationObj, err := getAllocation(allocationID)
   225  	if err != nil {
   226  		PrintError("Error fetching the allocation", err)
   227  		return nil, err
   228  	}
   229  
   230  	err = allocationObj.DoMultiOperation([]sdk.OperationRequest{
   231  		{
   232  			OperationType: constants.FileOperationRename,
   233  			RemotePath:    remotePath,
   234  			DestName:      destName,
   235  		},
   236  	})
   237  
   238  	if err != nil {
   239  		PrintError(err.Error())
   240  		return nil, err
   241  	}
   242  	sdkLogger.Info(remotePath + " renamed")
   243  
   244  	resp := &FileCommandResponse{
   245  		CommandSuccess: true,
   246  	}
   247  
   248  	return resp, nil
   249  }
   250  
   251  // Copy copy file to another folder path on an allocation, only the owner of the allocation can copy an object.
   252  //   - allocationID is the allocation id
   253  //   - remotePath is the remote path of the file (source path)
   254  //   - destPath is the destination path of the file
   255  func Copy(allocationID, remotePath, destPath string) (*FileCommandResponse, error) {
   256  
   257  	if len(allocationID) == 0 {
   258  		return nil, RequiredArg("allocationID")
   259  	}
   260  
   261  	if len(remotePath) == 0 {
   262  		return nil, RequiredArg("remotePath")
   263  	}
   264  
   265  	if len(destPath) == 0 {
   266  		return nil, RequiredArg("destPath")
   267  	}
   268  
   269  	allocationObj, err := getAllocation(allocationID)
   270  	if err != nil {
   271  		PrintError("Error fetching the allocation", err)
   272  		return nil, err
   273  	}
   274  
   275  	err = allocationObj.DoMultiOperation([]sdk.OperationRequest{
   276  		{
   277  			OperationType: constants.FileOperationCopy,
   278  			RemotePath:    remotePath,
   279  			DestPath:      destPath,
   280  		},
   281  	})
   282  
   283  	if err != nil {
   284  		PrintError(err.Error())
   285  		return nil, err
   286  	}
   287  
   288  	sdkLogger.Info(remotePath + " copied")
   289  
   290  	resp := &FileCommandResponse{
   291  		CommandSuccess: true,
   292  	}
   293  
   294  	return resp, nil
   295  }
   296  
   297  // Move move file to another remote folder path on dStorage. Only the owner of the allocation can copy an object.
   298  //   - allocationID is the allocation id
   299  //   - remotePath is the remote path of the file (source path)
   300  //   - destPath is the destination path of the file
   301  func Move(allocationID, remotePath, destPath string) (*FileCommandResponse, error) {
   302  	if len(allocationID) == 0 {
   303  		return nil, RequiredArg("allocationID")
   304  	}
   305  
   306  	if len(remotePath) == 0 {
   307  		return nil, RequiredArg("remotePath")
   308  	}
   309  
   310  	if len(destPath) == 0 {
   311  		return nil, RequiredArg("destPath")
   312  	}
   313  
   314  	allocationObj, err := getAllocation(allocationID)
   315  	if err != nil {
   316  		PrintError("Error fetching the allocation", err)
   317  		return nil, err
   318  	}
   319  
   320  	err = allocationObj.DoMultiOperation([]sdk.OperationRequest{
   321  		{
   322  			OperationType: constants.FileOperationMove,
   323  			RemotePath:    remotePath,
   324  			DestPath:      destPath,
   325  		},
   326  	})
   327  
   328  	if err != nil {
   329  		PrintError(err.Error())
   330  		return nil, err
   331  	}
   332  
   333  	sdkLogger.Info(remotePath + " moved")
   334  
   335  	resp := &FileCommandResponse{
   336  		CommandSuccess: true,
   337  	}
   338  
   339  	return resp, nil
   340  }
   341  
   342  // Share  generate an authtoken that provides authorization to the holder to the specified file on the remotepath.
   343  //   - allocationID is the allocation id
   344  //   - remotePath is the remote path of the file
   345  //   - clientID is the client id
   346  //   - encryptionPublicKey is the encryption public key of the client to share with, in case of private sharing
   347  //   - expiration is the expiration time of the auth ticket
   348  //   - revoke is the flag to revoke the share
   349  //   - availableAfter is the time after which the share is available
   350  func Share(allocationID, remotePath, clientID, encryptionPublicKey string, expiration int, revoke bool, availableAfter string) (string, error) {
   351  
   352  	if len(allocationID) == 0 {
   353  		return "", RequiredArg("allocationID")
   354  	}
   355  
   356  	if len(remotePath) == 0 {
   357  		return "", RequiredArg("remotePath")
   358  	}
   359  
   360  	allocationObj, err := getAllocation(allocationID)
   361  	if err != nil {
   362  		PrintError("Error fetching the allocation", err)
   363  		return "", err
   364  	}
   365  
   366  	refType := fileref.DIRECTORY
   367  
   368  	sdkLogger.Info("getting filestats")
   369  	statsMap, err := allocationObj.GetFileStats(remotePath)
   370  	if err != nil {
   371  		PrintError("Error in getting information about the object." + err.Error())
   372  		return "", err
   373  	}
   374  
   375  	for _, v := range statsMap {
   376  		if v != nil {
   377  			refType = fileref.FILE
   378  			break
   379  		}
   380  	}
   381  
   382  	var fileName string
   383  	_, fileName = pathutil.Split(remotePath)
   384  
   385  	if revoke {
   386  		err := allocationObj.RevokeShare(remotePath, clientID)
   387  		if err != nil {
   388  			PrintError(err.Error())
   389  			return "", err
   390  		}
   391  		sdkLogger.Info("Share revoked for client " + clientID)
   392  		return "", nil
   393  	}
   394  
   395  	availableAt := time.Now()
   396  
   397  	if len(availableAfter) > 0 {
   398  		aa, err := common.ParseTime(availableAt, availableAfter)
   399  		if err != nil {
   400  			PrintError(err.Error())
   401  			return "", err
   402  		}
   403  		availableAt = *aa
   404  	}
   405  
   406  	ref, err := allocationObj.GetAuthTicket(remotePath, fileName, refType, clientID, encryptionPublicKey, int64(expiration), &availableAt)
   407  	if err != nil {
   408  		PrintError(err.Error())
   409  		return "", err
   410  	}
   411  	sdkLogger.Info("Auth token :" + ref)
   412  
   413  	return ref, nil
   414  
   415  }
   416  
   417  func getFileMetaByName(allocationID, fileNameQuery string) ([]*sdk.ConsolidatedFileMetaByName, error) {
   418  	allocationObj, err := getAllocation(allocationID)
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  	fileMetas, err := allocationObj.GetFileMetaByName(fileNameQuery)
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  	return fileMetas, nil
   427  }
   428  
   429  // multiDownload - start multi-download operation.
   430  // ## Inputs
   431  //   - allocationID
   432  //   - jsonMultiDownloadOptions: Json Array of MultiDownloadOption.
   433  //   - authTicket
   434  //   - callbackFuncName: callback function name Invoke with totalBytes, completedBytes, objURL, err
   435  //
   436  // ## Outputs
   437  //   - json string of array of DownloadCommandResponse
   438  //   - error
   439  func multiDownload(allocationID, jsonMultiDownloadOptions, authTicket, callbackFuncName string) (string, error) {
   440  	defer func() {
   441  		if r := recover(); r != nil {
   442  			PrintError("Recovered in multiDownload Error", r)
   443  		}
   444  	}()
   445  	sdkLogger.Info("starting multidownload")
   446  	wg := &sync.WaitGroup{}
   447  	useCallback := false
   448  	if callbackFuncName != "" {
   449  		useCallback = true
   450  	}
   451  	var options []*MultiDownloadOption
   452  	err := json.Unmarshal([]byte(jsonMultiDownloadOptions), &options)
   453  	if err != nil {
   454  		return "", err
   455  	}
   456  	var alloc *sdk.Allocation
   457  	if authTicket == "" {
   458  		alloc, err = getAllocation(allocationID)
   459  	} else {
   460  		alloc, err = sdk.GetAllocationFromAuthTicket(authTicket)
   461  	}
   462  	if err != nil {
   463  		return "", err
   464  	}
   465  	allStatusBar := make([]*StatusBar, len(options))
   466  	wg.Add(len(options))
   467  	for ind, option := range options {
   468  		fileName := strings.Replace(path.Base(option.RemotePath), "/", "-", -1)
   469  		localPath := allocationID + "_" + fileName
   470  		option.LocalPath = localPath
   471  		statusBar := &StatusBar{wg: wg, totalBytesMap: make(map[string]int)}
   472  		allStatusBar[ind] = statusBar
   473  		if useCallback {
   474  			callback := js.Global().Get(callbackFuncName)
   475  			statusBar.callback = func(totalBytes, completedBytes int, filename, objURL, err string) {
   476  				callback.Invoke(totalBytes, completedBytes, filename, objURL, err)
   477  			}
   478  		}
   479  		var mf sys.File
   480  		if option.DownloadToDisk {
   481  			terminateWorkersWithAllocation(alloc)
   482  			mf, err = jsbridge.NewFileWriter(fileName)
   483  			if err != nil {
   484  				PrintError(err.Error())
   485  				return "", err
   486  			}
   487  		} else {
   488  			statusBar.localPath = localPath
   489  			fs, _ := sys.Files.Open(localPath)
   490  			mf, _ = fs.(*sys.MemFile)
   491  		}
   492  
   493  		var downloader sdk.Downloader
   494  		if option.DownloadOp == 1 {
   495  			downloader, err = sdk.CreateDownloader(allocationID, localPath, option.RemotePath,
   496  				sdk.WithAllocation(alloc),
   497  				sdk.WithAuthticket(authTicket, option.RemoteLookupHash),
   498  				sdk.WithOnlyThumbnail(false),
   499  				sdk.WithBlocks(0, 0, option.NumBlocks),
   500  				sdk.WithFileHandler(mf),
   501  			)
   502  		} else {
   503  			downloader, err = sdk.CreateDownloader(allocationID, localPath, option.RemotePath,
   504  				sdk.WithAllocation(alloc),
   505  				sdk.WithAuthticket(authTicket, option.RemoteLookupHash),
   506  				sdk.WithOnlyThumbnail(true),
   507  				sdk.WithBlocks(0, 0, option.NumBlocks),
   508  				sdk.WithFileHandler(mf),
   509  			)
   510  		}
   511  		if err != nil {
   512  			PrintError(err.Error())
   513  			return "", err
   514  		}
   515  		defer sys.Files.Remove(option.LocalPath) //nolint
   516  		downloader.Start(statusBar, ind == len(options)-1)
   517  	}
   518  	wg.Wait()
   519  	resp := make([]DownloadCommandResponse, len(options))
   520  
   521  	for ind, statusBar := range allStatusBar {
   522  		statusResponse := DownloadCommandResponse{}
   523  		if !statusBar.success {
   524  			statusResponse.CommandSuccess = false
   525  			statusResponse.Error = "Download failed: " + statusBar.err.Error()
   526  		} else {
   527  			statusResponse.CommandSuccess = true
   528  			statusResponse.FileName = options[ind].RemoteFileName
   529  			statusResponse.Url = statusBar.objURL
   530  		}
   531  		resp[ind] = statusResponse
   532  	}
   533  
   534  	respBytes, err := json.Marshal(resp)
   535  	if err != nil {
   536  		return "", err
   537  	}
   538  	return string(respBytes), nil
   539  }
   540  
   541  // BulkUploadOption options for the a single file upload, usually as part of bulk operations request
   542  type BulkUploadOption struct {
   543  	AllocationID string `json:"allocationId,omitempty"`
   544  	RemotePath   string `json:"remotePath,omitempty"`
   545  
   546  	ThumbnailBytes jsbridge.Bytes `json:"thumbnailBytes,omitempty"`
   547  	Encrypt        bool           `json:"encrypt,omitempty"`
   548  	IsWebstreaming bool           `json:"webstreaming,omitempty"`
   549  	IsUpdate       bool           `json:"isUpdate,omitempty"`
   550  	IsRepair       bool           `json:"isRepair,omitempty"`
   551  
   552  	NumBlocks         int    `json:"numBlocks,omitempty"`
   553  	FileSize          int64  `json:"fileSize,omitempty"`
   554  	ReadChunkFuncName string `json:"readChunkFuncName,omitempty"`
   555  	CallbackFuncName  string `json:"callbackFuncName,omitempty"`
   556  	Md5HashFuncName   string `json:"md5HashFuncName,omitempty"`
   557  	MimeType          string `json:"mimeType,omitempty"`
   558  	MemoryStorer      bool   `json:"memoryStorer,omitempty"`
   559  	CustomMeta        string `json:"customMeta,omitempty"`
   560  }
   561  
   562  // BulkUploadResult result of a single file upload, usually as part of bulk operations request
   563  type BulkUploadResult struct {
   564  	RemotePath string `json:"remotePath,omitempty"`
   565  	Success    bool   `json:"success,omitempty"`
   566  	Error      string `json:"error,omitempty"`
   567  }
   568  
   569  // MultiUploadResult result of a multi file upload
   570  type MultiUploadResult struct {
   571  	Success bool   `json:"success,omitempty"`
   572  	Error   string `json:"error,omitempty"`
   573  }
   574  
   575  // MultiOperationOption options for the a single file operation, usually as part of multi operations request
   576  type MultiOperationOption struct {
   577  	OperationType string `json:"operationType,omitempty"`
   578  	RemotePath    string `json:"remotePath,omitempty"`
   579  	DestName      string `json:"destName,omitempty"` // Required only for rename operation
   580  	DestPath      string `json:"destPath,omitempty"` // Required for copy and move operation`
   581  }
   582  
   583  // MultiDownloadOption options for the a single file download, usually as part of multi download request
   584  type MultiDownloadOption struct {
   585  	RemotePath       string `json:"remotePath"`
   586  	LocalPath        string `json:"localPath,omitempty"`
   587  	DownloadOp       int    `json:"downloadOp"`
   588  	NumBlocks        int    `json:"numBlocks"`
   589  	RemoteFileName   string `json:"remoteFileName"`             //Required only for file download with auth ticket
   590  	RemoteLookupHash string `json:"remoteLookupHash,omitempty"` //Required only for file download with auth ticket
   591  	DownloadToDisk   bool   `json:"downloadToDisk"`
   592  }
   593  
   594  // MultiOperation do copy, move, delete and createdir operation together
   595  //
   596  // ## Inputs
   597  //   - allocationID
   598  //   - jsonMultiUploadOptions: Json Array of MultiOperationOption. eg: "[{"operationType":"move","remotePath":"/README.md","destPath":"/folder1/"},{"operationType":"delete","remotePath":"/t3.txt"}]"
   599  //
   600  // ## Outputs
   601  //   - error
   602  func MultiOperation(allocationID string, jsonMultiUploadOptions string) error {
   603  	if allocationID == "" {
   604  		return errors.New("AllocationID is required")
   605  	}
   606  
   607  	if jsonMultiUploadOptions == "" {
   608  		return errors.New("operations are empty")
   609  	}
   610  
   611  	var options []MultiOperationOption
   612  	err := json.Unmarshal([]byte(jsonMultiUploadOptions), &options)
   613  	if err != nil {
   614  		sdkLogger.Info("error unmarshalling")
   615  		return err
   616  	}
   617  	totalOp := len(options)
   618  	operations := make([]sdk.OperationRequest, totalOp)
   619  	for idx, op := range options {
   620  		operations[idx] = sdk.OperationRequest{
   621  			OperationType: op.OperationType,
   622  			RemotePath:    op.RemotePath,
   623  			DestName:      op.DestName,
   624  			DestPath:      op.DestPath,
   625  		}
   626  	}
   627  	allocationObj, err := getAllocation(allocationID)
   628  	if err != nil {
   629  		return err
   630  	}
   631  	return allocationObj.DoMultiOperation(operations)
   632  }
   633  
   634  // bulkUpload upload multiple files in parallel
   635  //   - jsonBulkUploadOptions is the json array of BulkUploadOption
   636  func bulkUpload(jsonBulkUploadOptions string) ([]BulkUploadResult, error) {
   637  	var options []BulkUploadOption
   638  	err := json.Unmarshal([]byte(jsonBulkUploadOptions), &options)
   639  	if err != nil {
   640  		return nil, err
   641  	}
   642  	n := len(options)
   643  	wait := make(chan BulkUploadResult, 1)
   644  
   645  	for _, option := range options {
   646  		go func(o BulkUploadOption) {
   647  			result := BulkUploadResult{
   648  				RemotePath: o.RemotePath,
   649  			}
   650  			defer func() { wait <- result }()
   651  
   652  			ok, err := uploadWithJsFuncs(o.AllocationID, o.RemotePath,
   653  				o.ReadChunkFuncName,
   654  				o.FileSize,
   655  				o.ThumbnailBytes.Buffer,
   656  				o.IsWebstreaming,
   657  				o.Encrypt,
   658  				o.IsUpdate,
   659  				o.IsRepair,
   660  				o.NumBlocks,
   661  				o.CallbackFuncName)
   662  			result.Success = ok
   663  			if err != nil {
   664  				result.Error = err.Error()
   665  				result.Success = false
   666  			}
   667  
   668  		}(option)
   669  
   670  	}
   671  
   672  	results := make([]BulkUploadResult, 0, n)
   673  	for i := 0; i < n; i++ {
   674  		result := <-wait
   675  		results = append(results, result)
   676  	}
   677  
   678  	return results, nil
   679  }
   680  
   681  // setUploadMode set upload mode, default is medium, for low set 0, for high set 2
   682  //   - mode is the upload mode
   683  func setUploadMode(mode int) {
   684  	switch mode {
   685  	case 0:
   686  		sdk.SetUploadMode(sdk.UploadModeLow)
   687  	case 1:
   688  		sdk.SetUploadMode(sdk.UploadModeMedium)
   689  	case 2:
   690  		sdk.SetUploadMode(sdk.UploadModeHigh)
   691  	}
   692  }
   693  
   694  // multiUpload upload multiple files in parallel
   695  //   - jsonBulkUploadOptions is the json array of BulkUploadOption. Follows the BulkUploadOption struct
   696  func multiUpload(jsonBulkUploadOptions string) (MultiUploadResult, error) {
   697  	defer func() {
   698  		if r := recover(); r != nil {
   699  			PrintError("Recovered in multiupload Error", r)
   700  		}
   701  	}()
   702  	var options []BulkUploadOption
   703  	result := MultiUploadResult{}
   704  	err := json.Unmarshal([]byte(jsonBulkUploadOptions), &options)
   705  	if err != nil {
   706  		result.Error = "Error in unmarshaling json"
   707  		result.Success = false
   708  		return result, err
   709  	}
   710  	n := len(options)
   711  	if n == 0 {
   712  		result.Error = "No files to upload"
   713  		result.Success = false
   714  		return result, errors.New("There are nothing to upload")
   715  	}
   716  	allocationID := options[0].AllocationID
   717  	allocationObj, err := getAllocation(allocationID)
   718  	if err != nil {
   719  		result.Error = "Error fetching the allocation"
   720  		result.Success = false
   721  		return result, errors.New("Error fetching the allocation")
   722  	}
   723  	err = addWebWorkers(allocationObj)
   724  	if err != nil {
   725  		result.Error = err.Error()
   726  		result.Success = false
   727  		return result, err
   728  	}
   729  
   730  	operationRequests := make([]sdk.OperationRequest, n)
   731  	for idx, option := range options {
   732  		wg := &sync.WaitGroup{}
   733  		statusBar := &StatusBar{wg: wg, totalBytesMap: make(map[string]int)}
   734  		callbackFuncName := option.CallbackFuncName
   735  		if callbackFuncName != "" {
   736  			callback := js.Global().Get(callbackFuncName)
   737  			statusBar.callback = func(totalBytes, completedBytes int, filename, objURL, err string) {
   738  				callback.Invoke(totalBytes, completedBytes, filename, objURL, err)
   739  			}
   740  		}
   741  		wg.Add(1)
   742  		encrypt := option.Encrypt
   743  		remotePath := option.RemotePath
   744  		fileReader, err := jsbridge.NewFileReader(option.ReadChunkFuncName, option.FileSize, allocationObj.GetChunkReadSize(encrypt))
   745  		if err != nil {
   746  			result.Error = "Error in file operation"
   747  			result.Success = false
   748  			return result, err
   749  		}
   750  		mimeType := option.MimeType
   751  		localPath := remotePath
   752  		remotePath = zboxutil.RemoteClean(remotePath)
   753  		isabs := zboxutil.IsRemoteAbs(remotePath)
   754  		if !isabs {
   755  			err = errors.New("invalid_path: Path should be valid and absolute")
   756  			result.Error = err.Error()
   757  			result.Success = false
   758  			return result, err
   759  		}
   760  		fullRemotePath := zboxutil.GetFullRemotePath(localPath, remotePath)
   761  
   762  		_, fileName := pathutil.Split(fullRemotePath)
   763  
   764  		if mimeType == "" {
   765  			mimeType, err = zboxutil.GetFileContentType(path.Ext(fileName), fileReader)
   766  			if err != nil {
   767  				result.Error = "Error in file operation"
   768  				result.Success = false
   769  				return result, err
   770  			}
   771  		}
   772  
   773  		fileMeta := sdk.FileMeta{
   774  			Path:       localPath,
   775  			ActualSize: option.FileSize,
   776  			MimeType:   mimeType,
   777  			RemoteName: fileName,
   778  			RemotePath: fullRemotePath,
   779  			CustomMeta: option.CustomMeta,
   780  		}
   781  		numBlocks := option.NumBlocks
   782  		if numBlocks <= 1 {
   783  			numBlocks = 100
   784  		}
   785  
   786  		options := []sdk.ChunkedUploadOption{
   787  			sdk.WithThumbnail(option.ThumbnailBytes.Buffer),
   788  			sdk.WithEncrypt(encrypt),
   789  			sdk.WithStatusCallback(statusBar),
   790  			sdk.WithChunkNumber(numBlocks),
   791  		}
   792  		if option.MemoryStorer {
   793  			options = append(options, sdk.WithProgressStorer(&chunkedUploadProgressStorer{
   794  				list: make(map[string]*sdk.UploadProgress),
   795  			}))
   796  		}
   797  		if option.Md5HashFuncName != "" {
   798  			fileHasher := newFileHasher(option.Md5HashFuncName)
   799  			options = append(options, sdk.WithFileHasher(fileHasher))
   800  		}
   801  		operationRequests[idx] = sdk.OperationRequest{
   802  			FileMeta:       fileMeta,
   803  			FileReader:     fileReader,
   804  			OperationType:  FileOperationInsert,
   805  			Opts:           options,
   806  			Workdir:        "/",
   807  			IsWebstreaming: option.IsWebstreaming,
   808  		}
   809  
   810  	}
   811  	err = allocationObj.DoMultiOperation(operationRequests)
   812  	if err != nil {
   813  		result.Error = err.Error()
   814  		result.Success = false
   815  		return result, err
   816  	}
   817  	result.Success = true
   818  	return result, nil
   819  }
   820  
   821  func uploadWithJsFuncs(allocationID, remotePath string, readChunkFuncName string, fileSize int64, thumbnailBytes []byte, webStreaming, encrypt, isUpdate, isRepair bool, numBlocks int, callbackFuncName string) (bool, error) {
   822  
   823  	if len(allocationID) == 0 {
   824  		return false, RequiredArg("allocationID")
   825  	}
   826  
   827  	if len(remotePath) == 0 {
   828  		return false, RequiredArg("remotePath")
   829  	}
   830  
   831  	allocationObj, err := getAllocation(allocationID)
   832  	if err != nil {
   833  		PrintError("Error fetching the allocation", err)
   834  		return false, err
   835  	}
   836  
   837  	wg := &sync.WaitGroup{}
   838  	statusBar := &StatusBar{wg: wg, totalBytesMap: make(map[string]int)}
   839  	if callbackFuncName != "" {
   840  		callback := js.Global().Get(callbackFuncName)
   841  		statusBar.callback = func(totalBytes, completedBytes int, filename, objURL, err string) {
   842  			callback.Invoke(totalBytes, completedBytes, filename, objURL, err)
   843  		}
   844  	}
   845  	wg.Add(1)
   846  
   847  	fileReader, err := jsbridge.NewFileReader(readChunkFuncName, fileSize, allocationObj.GetChunkReadSize(encrypt))
   848  	if err != nil {
   849  		return false, err
   850  	}
   851  
   852  	localPath := remotePath
   853  
   854  	remotePath = zboxutil.RemoteClean(remotePath)
   855  	isabs := zboxutil.IsRemoteAbs(remotePath)
   856  	if !isabs {
   857  		err = errors.New("invalid_path: Path should be valid and absolute")
   858  		return false, err
   859  	}
   860  	remotePath = zboxutil.GetFullRemotePath(localPath, remotePath)
   861  
   862  	_, fileName := pathutil.Split(remotePath)
   863  
   864  	mimeType, err := zboxutil.GetFileContentType(path.Ext(fileName), fileReader)
   865  	if err != nil {
   866  		return false, err
   867  	}
   868  
   869  	fileMeta := sdk.FileMeta{
   870  		Path:       localPath,
   871  		ActualSize: fileSize,
   872  		MimeType:   mimeType,
   873  		RemoteName: fileName,
   874  		RemotePath: remotePath,
   875  	}
   876  
   877  	if numBlocks < 1 {
   878  		numBlocks = 100
   879  	}
   880  	if allocationObj.DataShards > 7 {
   881  		numBlocks = 50
   882  	}
   883  
   884  	ChunkedUpload, err := sdk.CreateChunkedUpload(context.TODO(), "/", allocationObj, fileMeta, fileReader, isUpdate, isRepair, webStreaming, zboxutil.NewConnectionId(),
   885  		sdk.WithThumbnail(thumbnailBytes),
   886  		sdk.WithEncrypt(encrypt),
   887  		sdk.WithStatusCallback(statusBar),
   888  		sdk.WithChunkNumber(numBlocks))
   889  	if err != nil {
   890  		return false, err
   891  	}
   892  
   893  	err = ChunkedUpload.Start()
   894  
   895  	if err != nil {
   896  		PrintError("Upload failed.", err)
   897  		return false, err
   898  	}
   899  
   900  	wg.Wait()
   901  	if !statusBar.success {
   902  		return false, errors.New("upload failed: unknown")
   903  	}
   904  
   905  	return true, nil
   906  }
   907  
   908  // upload upload file
   909  //   - allocationID is the allocation id
   910  //   - remotePath is the remote path of the file
   911  //   - fileBytes is the file in bytes
   912  //   - thumbnailBytes is the thumbnail in bytes
   913  //   - webStreaming is the flag to enable web streaming
   914  //   - encrypt is the flag to enable encryption of the uploaded file
   915  //   - isUpdate is the flag to update the file
   916  //   - isRepair is the flag to repair the file
   917  //   - numBlocks is the number of blocks to upload
   918  func upload(allocationID, remotePath string, fileBytes, thumbnailBytes []byte, webStreaming, encrypt, isUpdate, isRepair bool, numBlocks int) (*FileCommandResponse, error) {
   919  	if len(allocationID) == 0 {
   920  		return nil, RequiredArg("allocationID")
   921  	}
   922  
   923  	if len(remotePath) == 0 {
   924  		return nil, RequiredArg("remotePath")
   925  	}
   926  
   927  	allocationObj, err := getAllocation(allocationID)
   928  	if err != nil {
   929  		PrintError("Error fetching the allocation", err)
   930  		return nil, err
   931  	}
   932  
   933  	wg := &sync.WaitGroup{}
   934  	statusBar := &StatusBar{wg: wg, totalBytesMap: make(map[string]int)}
   935  	wg.Add(1)
   936  
   937  	fileReader := bytes.NewReader(fileBytes)
   938  
   939  	localPath := remotePath
   940  
   941  	remotePath = zboxutil.RemoteClean(remotePath)
   942  	isabs := zboxutil.IsRemoteAbs(remotePath)
   943  	if !isabs {
   944  		err = errors.New("invalid_path: Path should be valid and absolute")
   945  		return nil, err
   946  	}
   947  	remotePath = zboxutil.GetFullRemotePath(localPath, remotePath)
   948  
   949  	_, fileName := pathutil.Split(remotePath)
   950  
   951  	mimeType, err := zboxutil.GetFileContentType(path.Ext(fileName), fileReader)
   952  	if err != nil {
   953  		return nil, err
   954  	}
   955  
   956  	fileMeta := sdk.FileMeta{
   957  		Path:       localPath,
   958  		ActualSize: int64(len(fileBytes)),
   959  		MimeType:   mimeType,
   960  		RemoteName: fileName,
   961  		RemotePath: remotePath,
   962  	}
   963  
   964  	if numBlocks < 1 {
   965  		numBlocks = 100
   966  	}
   967  
   968  	ChunkedUpload, err := sdk.CreateChunkedUpload(context.TODO(), "/", allocationObj, fileMeta, fileReader, isUpdate, isRepair, webStreaming,
   969  		zboxutil.NewConnectionId(),
   970  		sdk.WithThumbnail(thumbnailBytes),
   971  		sdk.WithEncrypt(encrypt),
   972  		sdk.WithStatusCallback(statusBar),
   973  		sdk.WithChunkNumber(numBlocks))
   974  	if err != nil {
   975  		return nil, err
   976  	}
   977  
   978  	err = ChunkedUpload.Start()
   979  
   980  	if err != nil {
   981  		PrintError("Upload failed.", err)
   982  		return nil, err
   983  	}
   984  	wg.Wait()
   985  	if !statusBar.success {
   986  		return nil, errors.New("upload failed: unknown")
   987  	}
   988  
   989  	resp := &FileCommandResponse{
   990  		CommandSuccess: true,
   991  	}
   992  
   993  	return resp, nil
   994  }
   995  
   996  // downloadBlocks download file blocks
   997  // 		- allocId : allocation ID of the file
   998  // 		- remotePath : remote path of the file
   999  // 		- authTicket : auth ticket of the file, if the file is shared
  1000  // 		- lookupHash : lookup hash of the file, which is used to locate the file if remotepath and allocation id are not provided
  1001  
  1002  func downloadBlocks(allocId string, remotePath, authTicket, lookupHash string, startBlock, endBlock int64) ([]byte, error) {
  1003  
  1004  	if len(remotePath) == 0 && len(authTicket) == 0 {
  1005  		return nil, RequiredArg("remotePath/authTicket")
  1006  	}
  1007  
  1008  	alloc, err := getAllocation(allocId)
  1009  
  1010  	if err != nil {
  1011  		PrintError("Error fetching the allocation", err)
  1012  		return nil, err
  1013  	}
  1014  
  1015  	var (
  1016  		wg        = &sync.WaitGroup{}
  1017  		statusBar = &StatusBar{wg: wg, totalBytesMap: make(map[string]int)}
  1018  	)
  1019  
  1020  	pathHash := encryption.FastHash(remotePath)
  1021  	fs, err := sys.Files.Open(pathHash)
  1022  	if err != nil {
  1023  		return nil, fmt.Errorf("could not open local file: %v", err)
  1024  	}
  1025  
  1026  	mf, _ := fs.(*sys.MemFile)
  1027  	if mf == nil {
  1028  		return nil, fmt.Errorf("invalid memfile")
  1029  	}
  1030  
  1031  	defer sys.Files.Remove(pathHash) //nolint
  1032  
  1033  	wg.Add(1)
  1034  	if authTicket != "" {
  1035  		err = alloc.DownloadByBlocksToFileHandlerFromAuthTicket(mf, authTicket, lookupHash, startBlock, endBlock, 100, remotePath, false, statusBar, true)
  1036  	} else {
  1037  		err = alloc.DownloadByBlocksToFileHandler(
  1038  			mf,
  1039  			remotePath,
  1040  			startBlock,
  1041  			endBlock,
  1042  			100,
  1043  			false,
  1044  			statusBar, true)
  1045  	}
  1046  	if err != nil {
  1047  		return nil, err
  1048  	}
  1049  	wg.Wait()
  1050  	return mf.Buffer, nil
  1051  }
  1052  
  1053  // getBlobbers get list of active blobbers, and format them as array json string
  1054  //   - stakable : flag to get only stakable blobbers
  1055  func getBlobbers(stakable bool) ([]*sdk.Blobber, error) {
  1056  	blobbs, err := sdk.GetBlobbers(true, stakable)
  1057  	if err != nil {
  1058  		return nil, err
  1059  	}
  1060  	return blobbs, err
  1061  }
  1062  
  1063  // repairAllocation repair the allocation
  1064  // Allocation repair is a process to repair the allocation files on its blobbers by re-uploading the missing blocks.
  1065  //   - allocationID : allocation ID of the file
  1066  func repairAllocation(allocationID, callbackFuncName string) error {
  1067  	alloc, err := getAllocation(allocationID)
  1068  	if err != nil {
  1069  		return err
  1070  	}
  1071  	err = addWebWorkers(alloc)
  1072  	if err != nil {
  1073  		return err
  1074  	}
  1075  	wg := &sync.WaitGroup{}
  1076  	statusBar := &StatusBar{wg: wg, isRepair: true, totalBytesMap: make(map[string]int)}
  1077  	wg.Add(1)
  1078  	if callbackFuncName != "" {
  1079  		callback := js.Global().Get(callbackFuncName)
  1080  		statusBar.callback = func(totalBytes, completedBytes int, filename, objURL, err string) {
  1081  			callback.Invoke(totalBytes, completedBytes, filename, objURL, err)
  1082  		}
  1083  	}
  1084  	err = alloc.RepairAlloc(statusBar)
  1085  	if err != nil {
  1086  		return err
  1087  	}
  1088  	wg.Wait()
  1089  	return statusBar.err
  1090  }
  1091  
  1092  // checkAllocStatus check the status of the allocation, either it is ok, needs repair or broken
  1093  //   - allocationID : allocation ID of the file
  1094  func checkAllocStatus(allocationID string) (string, error) {
  1095  	alloc, err := getAllocation(allocationID)
  1096  	if err != nil {
  1097  		return "", err
  1098  	}
  1099  	status, blobberStatus, err := alloc.CheckAllocStatus()
  1100  	var statusStr string
  1101  	switch status {
  1102  	case sdk.Repair:
  1103  		statusStr = "repair"
  1104  	case sdk.Broken:
  1105  		statusStr = "broken"
  1106  	default:
  1107  		statusStr = "ok"
  1108  	}
  1109  	statusResult := CheckStatusResult{
  1110  		Status:        statusStr,
  1111  		Err:           err,
  1112  		BlobberStatus: blobberStatus,
  1113  	}
  1114  	statusBytes, err := json.Marshal(statusResult)
  1115  	if err != nil {
  1116  		return "", err
  1117  	}
  1118  
  1119  	return string(statusBytes), err
  1120  }
  1121  
  1122  // skipStatusCheck skip the status check of the allocation
  1123  //   - allocationID : allocation ID of the file
  1124  func skipStatusCheck(allocationID string, checkStatus bool) error {
  1125  	alloc, err := getAllocation(allocationID)
  1126  	if err != nil {
  1127  		return err
  1128  	}
  1129  	alloc.SetCheckStatus(checkStatus)
  1130  	return nil
  1131  }
  1132  
  1133  // terminateWorkers remove local workers that sync with the allocation
  1134  //   - allocationID : allocation ID of the file
  1135  func terminateWorkers(allocationID string) {
  1136  	alloc, err := getAllocation(allocationID)
  1137  	if err != nil {
  1138  		return
  1139  	}
  1140  	for _, blobber := range alloc.Blobbers {
  1141  		jsbridge.RemoveWorker(blobber.ID)
  1142  	}
  1143  }
  1144  
  1145  func terminateWorkersWithAllocation(alloc *sdk.Allocation) {
  1146  	for _, blobber := range alloc.Blobbers {
  1147  		jsbridge.RemoveWorker(blobber.ID)
  1148  	}
  1149  }
  1150  
  1151  // createWorkers create local workers that sync with the allocation
  1152  //   - allocationID : allocation ID of the file
  1153  func createWorkers(allocationID string) error {
  1154  	alloc, err := getAllocation(allocationID)
  1155  	if err != nil {
  1156  		return err
  1157  	}
  1158  	return addWebWorkers(alloc)
  1159  }
  1160  
  1161  // downloadDirectory download directory to local file system using fs api, will only work in browsers where fs api is available
  1162  //   - allocationID : allocation ID of the file
  1163  //   - remotePath : remote path of the directory
  1164  //   - authticket : auth ticket of the file, if the file is shared
  1165  //   - callbackFuncName : callback function name to get the progress of the download
  1166  func downloadDirectory(allocationID, remotePath, authticket, callbackFuncName string) error {
  1167  	alloc, err := getAllocation(allocationID)
  1168  	if err != nil {
  1169  		return err
  1170  	}
  1171  	wg := &sync.WaitGroup{}
  1172  	wg.Add(1)
  1173  	statusBar := &StatusBar{wg: wg, totalBytesMap: make(map[string]int)}
  1174  	if callbackFuncName != "" {
  1175  		callback := js.Global().Get(callbackFuncName)
  1176  		statusBar.callback = func(totalBytes, completedBytes int, filename, objURL, err string) {
  1177  			callback.Invoke(totalBytes, completedBytes, filename, objURL, err)
  1178  		}
  1179  	}
  1180  	ctx, cancel := context.WithCancelCause(context.Background())
  1181  	defer cancel(nil)
  1182  	errChan := make(chan error, 1)
  1183  	go func() {
  1184  		errChan <- alloc.DownloadDirectory(ctx, remotePath, "", authticket, statusBar)
  1185  	}()
  1186  	downloadDirLock.Lock()
  1187  	downloadDirContextMap[remotePath] = cancel
  1188  	downloadDirLock.Unlock()
  1189  	select {
  1190  	case err = <-errChan:
  1191  		if err != nil {
  1192  			PrintError("Error in download directory: ", err)
  1193  		}
  1194  		return err
  1195  	case <-ctx.Done():
  1196  		return context.Cause(ctx)
  1197  	}
  1198  }
  1199  
  1200  // cancelDownloadDirectory cancel the download directory operation
  1201  //   - remotePath : remote path of the directory
  1202  func cancelDownloadDirectory(remotePath string) {
  1203  	downloadDirLock.Lock()
  1204  	cancel, ok := downloadDirContextMap[remotePath]
  1205  	if ok {
  1206  		cancel(errors.New("download directory canceled by user"))
  1207  	}
  1208  	downloadDirLock.Unlock()
  1209  }
  1210  
  1211  func startListener(respChan chan string) error {
  1212  	ctx, cancel := context.WithCancel(context.Background())
  1213  	defer cancel()
  1214  
  1215  	selfWorker, err := jsbridge.NewSelfWorker()
  1216  	if err != nil {
  1217  		return err
  1218  	}
  1219  	defer fmt.Println("[web worker] exiting")
  1220  	safeVal, _ := safejs.ValueOf("startListener")
  1221  	selfWorker.PostMessage(safeVal, nil) //nolint:errcheck
  1222  
  1223  	listener, err := selfWorker.Listen(ctx)
  1224  	if err != nil {
  1225  		return err
  1226  	}
  1227  	sdk.InitHasherMap()
  1228  	for event := range listener {
  1229  		func(event worker.MessageEvent) {
  1230  			msgType, data, err := jsbridge.GetMsgType(event)
  1231  			if err != nil {
  1232  				PrintError("Error in getting data from event", err)
  1233  				return
  1234  			}
  1235  
  1236  			switch msgType {
  1237  			case jsbridge.MsgTypeAuthRsp:
  1238  				rsp, err := jsbridge.ParseEventDataField(data, "data")
  1239  				if err != nil {
  1240  					PrintError("Error in parsing data from event", err)
  1241  					return
  1242  				}
  1243  				respChan <- rsp
  1244  			case jsbridge.MsgTypeUpload:
  1245  				go sdk.ProcessEventData(*data)
  1246  			case jsbridge.MsgTypeUpdateWallet:
  1247  				fmt.Println("received update wallet event")
  1248  				if err := UpdateWalletWithEventData(data); err != nil {
  1249  					PrintError("Error in updating wallet", err)
  1250  				}
  1251  			default:
  1252  				PrintError("Unknown message type", msgType)
  1253  			}
  1254  		}(event)
  1255  	}
  1256  
  1257  	return nil
  1258  }