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 }