github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/listworker.go (about)

     1  package sdk
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"math/rand"
     9  	"net/http"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/0chain/errors"
    15  	"github.com/0chain/gosdk/core/common"
    16  	"github.com/0chain/gosdk/zboxcore/blockchain"
    17  	"github.com/0chain/gosdk/zboxcore/fileref"
    18  	l "github.com/0chain/gosdk/zboxcore/logger"
    19  	"github.com/0chain/gosdk/zboxcore/marker"
    20  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    21  )
    22  
    23  const CHUNK_SIZE = 64 * 1024
    24  
    25  type ListRequest struct {
    26  	allocationID       string
    27  	allocationTx       string
    28  	sig                string
    29  	blobbers           []*blockchain.StorageNode
    30  	remotefilepathhash string
    31  	remotefilepath     string
    32  	filename           string
    33  	authToken          *marker.AuthTicket
    34  	ctx                context.Context
    35  	forRepair          bool
    36  	listOnly           bool
    37  	offset             int
    38  	pageLimit          int
    39  	Consensus
    40  }
    41  
    42  type listResponse struct {
    43  	ref         *fileref.Ref
    44  	responseStr string
    45  	blobberIdx  int
    46  	err         error
    47  }
    48  
    49  // ListResult a wrapper around the result of directory listing command.
    50  // It can represent a file or a directory.
    51  type ListResult struct {
    52  	Name                string `json:"name"`
    53  	Path                string `json:"path,omitempty"`
    54  	Type                string `json:"type"`
    55  	Size                int64  `json:"size"`
    56  	Hash                string `json:"hash,omitempty"`
    57  	FileMetaHash        string `json:"file_meta_hash,omitempty"`
    58  	MimeType            string `json:"mimetype,omitempty"`
    59  	NumBlocks           int64  `json:"num_blocks"`
    60  	LookupHash          string `json:"lookup_hash"`
    61  	EncryptionKey       string `json:"encryption_key"`
    62  	ActualSize          int64  `json:"actual_size"`
    63  	ActualNumBlocks     int64  `json:"actual_num_blocks"`
    64  	ThumbnailHash       string `json:"thumbnail_hash"`
    65  	ThumbnailSize       int64  `json:"thumbnail_size"`
    66  	ActualThumbnailHash string `json:"actual_thumbnail_hash"`
    67  	ActualThumbnailSize int64  `json:"actual_thumbnail_size"`
    68  
    69  	CreatedAt  common.Timestamp `json:"created_at"`
    70  	UpdatedAt  common.Timestamp `json:"updated_at"`
    71  	Children   []*ListResult    `json:"list"`
    72  	Consensus  `json:"-"`
    73  	deleteMask zboxutil.Uint128 `json:"-"`
    74  }
    75  
    76  type ListRequestOptions func(req *ListRequest)
    77  
    78  func WithListRequestOffset(offset int) ListRequestOptions {
    79  	return func(req *ListRequest) {
    80  		req.offset = offset
    81  	}
    82  }
    83  
    84  func WithListRequestPageLimit(pageLimit int) ListRequestOptions {
    85  	return func(req *ListRequest) {
    86  		req.pageLimit = pageLimit
    87  	}
    88  }
    89  
    90  func WithListRequestForRepair(forRepair bool) ListRequestOptions {
    91  	return func(req *ListRequest) {
    92  		req.forRepair = forRepair
    93  	}
    94  }
    95  
    96  func (req *ListRequest) getListInfoFromBlobber(blobber *blockchain.StorageNode, blobberIdx int, rspCh chan<- *listResponse) {
    97  	//body := new(bytes.Buffer)
    98  	//formWriter := multipart.NewWriter(body)
    99  
   100  	ref := &fileref.Ref{}
   101  	var s strings.Builder
   102  	var err error
   103  	listRetFn := func() {
   104  		rspCh <- &listResponse{ref: ref, responseStr: s.String(), blobberIdx: blobberIdx, err: err}
   105  	}
   106  	defer listRetFn()
   107  
   108  	if len(req.remotefilepath) > 0 {
   109  		req.remotefilepathhash = fileref.GetReferenceLookup(req.allocationID, req.remotefilepath)
   110  	}
   111  	//formWriter.WriteField("path_hash", req.remotefilepathhash)
   112  	//Logger.Info("Path hash for list dir: ", req.remotefilepathhash)
   113  
   114  	authTokenBytes := make([]byte, 0)
   115  	if req.authToken != nil {
   116  		authTokenBytes, err = json.Marshal(req.authToken)
   117  		if err != nil {
   118  			l.Logger.Error(blobber.Baseurl, " creating auth token bytes", err)
   119  			return
   120  		}
   121  		//formWriter.WriteField("auth_token", string(authTokenBytes))
   122  	}
   123  
   124  	//formWriter.Close()
   125  	if req.forRepair {
   126  		req.listOnly = true
   127  	}
   128  	httpreq, err := zboxutil.NewListRequest(blobber.Baseurl, req.allocationID, req.allocationTx, req.remotefilepath, req.remotefilepathhash, string(authTokenBytes), req.listOnly, req.offset, req.pageLimit)
   129  	if err != nil {
   130  		l.Logger.Error("List info request error: ", err.Error())
   131  		return
   132  	}
   133  
   134  	//httpreq.Header.Add("Content-Type", formWriter.FormDataContentType())
   135  	ctx, cncl := context.WithTimeout(req.ctx, (time.Second * 10))
   136  	err = zboxutil.HttpDo(ctx, cncl, httpreq, func(resp *http.Response, err error) error {
   137  		if err != nil {
   138  			l.Logger.Error("List : ", err)
   139  			return err
   140  		}
   141  		defer resp.Body.Close()
   142  		resp_body, err := ioutil.ReadAll(resp.Body)
   143  		if err != nil {
   144  			return errors.Wrap(err, "Error: Resp")
   145  		}
   146  		s.WriteString(string(resp_body))
   147  		if resp.StatusCode == http.StatusOK {
   148  			listResult := &fileref.ListResult{}
   149  			err = json.Unmarshal(resp_body, listResult)
   150  			if err != nil {
   151  				return errors.Wrap(err, "list entities response parse error:")
   152  			}
   153  			ref, err = listResult.GetDirTree(req.allocationID)
   154  			if err != nil {
   155  				return errors.Wrap(err, "error getting the dir tree from list response:")
   156  			}
   157  			return nil
   158  		}
   159  
   160  		return fmt.Errorf("error from server list response: %s", s.String())
   161  
   162  	})
   163  }
   164  
   165  func (req *ListRequest) getlistFromBlobbers() ([]*listResponse, error) {
   166  	numList := len(req.blobbers)
   167  	rspCh := make(chan *listResponse, numList)
   168  	for i := 0; i < numList; i++ {
   169  		go req.getListInfoFromBlobber(req.blobbers[i], i, rspCh)
   170  	}
   171  	listInfos := make([]*listResponse, numList)
   172  	consensusMap := make(map[string][]*blockchain.StorageNode)
   173  	var consensusHash string
   174  	errCnt := 0
   175  	for i := 0; i < numList; i++ {
   176  		listInfos[i] = <-rspCh
   177  		if !req.forRepair {
   178  			if listInfos[i].err != nil || listInfos[i].ref == nil {
   179  				if listInfos[i].err != nil {
   180  					errCnt++
   181  				}
   182  				continue
   183  			}
   184  			hash := listInfos[i].ref.FileMetaHash
   185  			consensusMap[hash] = append(consensusMap[hash], req.blobbers[listInfos[i].blobberIdx])
   186  			if len(consensusMap[hash]) >= req.consensusThresh {
   187  				consensusHash = hash
   188  				break
   189  			}
   190  		}
   191  	}
   192  	if req.listOnly {
   193  		return listInfos, nil
   194  	}
   195  
   196  	var err error
   197  	listLen := len(consensusMap[consensusHash])
   198  	if listLen < req.consensusThresh {
   199  		if req.fullconsensus-errCnt >= req.consensusThresh && !req.listOnly {
   200  			req.listOnly = true
   201  			return req.getlistFromBlobbers()
   202  		}
   203  		return listInfos, listInfos[0].err
   204  	}
   205  	req.listOnly = true
   206  	listInfos = listInfos[:1]
   207  	listOnlyRespCh := make(chan *listResponse, 1)
   208  	for i := 0; i < listLen; i++ {
   209  		var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
   210  		num := rnd.Intn(listLen)
   211  		randomBlobber := consensusMap[consensusHash][num]
   212  		go req.getListInfoFromBlobber(randomBlobber, 0, listOnlyRespCh)
   213  		listInfos[0] = <-listOnlyRespCh
   214  		if listInfos[0].err == nil {
   215  			return listInfos, nil
   216  		}
   217  		err = listInfos[0].err
   218  	}
   219  	return listInfos, err
   220  }
   221  
   222  func (req *ListRequest) GetListFromBlobbers() (*ListResult, error) {
   223  	l.Logger.Debug("Getting list info from blobbers")
   224  	lR, err := req.getlistFromBlobbers()
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	result := &ListResult{
   229  		deleteMask: zboxutil.NewUint128(1).Lsh(uint64(len(req.blobbers))).Sub64(1),
   230  	}
   231  	selected := make(map[string]*ListResult)
   232  	childResultMap := make(map[string]*ListResult)
   233  	if !req.forRepair {
   234  		req.consensusThresh = 1
   235  	}
   236  	for i := 0; i < len(lR); i++ {
   237  		ti := lR[i]
   238  		if ti.err != nil {
   239  			result.deleteMask = result.deleteMask.And(zboxutil.NewUint128(1).Lsh(uint64(ti.blobberIdx)).Not())
   240  			continue
   241  		}
   242  		if ti.ref == nil {
   243  			continue
   244  		}
   245  
   246  		result.Name = ti.ref.Name
   247  		result.Path = ti.ref.Path
   248  		result.Type = ti.ref.Type
   249  		result.CreatedAt = ti.ref.CreatedAt
   250  		result.UpdatedAt = ti.ref.UpdatedAt
   251  		result.LookupHash = ti.ref.LookupHash
   252  		result.FileMetaHash = ti.ref.FileMetaHash
   253  		result.ActualSize = ti.ref.ActualSize
   254  		result.ThumbnailHash = ti.ref.ThumbnailHash
   255  		result.ThumbnailSize = ti.ref.ThumbnailSize
   256  		result.ActualThumbnailHash = ti.ref.ActualThumbnailHash
   257  		result.ActualThumbnailSize = ti.ref.ActualThumbnailSize
   258  
   259  		if ti.ref.ActualSize > 0 {
   260  			result.ActualNumBlocks = (ti.ref.ActualSize + CHUNK_SIZE - 1) / CHUNK_SIZE
   261  		}
   262  		result.Size += ti.ref.Size
   263  		result.NumBlocks += ti.ref.NumBlocks
   264  
   265  		if len(lR[i].ref.Children) > 0 {
   266  			result.populateChildren(lR[i].ref.Children, childResultMap, selected, req)
   267  		}
   268  		req.consensus++
   269  		if req.isConsensusOk() && !req.forRepair {
   270  			break
   271  		}
   272  	}
   273  	if req.forRepair {
   274  		for _, child := range childResultMap {
   275  			if child.consensus < child.fullconsensus {
   276  				if _, ok := selected[child.LookupHash]; !ok {
   277  					result.Children = append(result.Children, child)
   278  					selected[child.LookupHash] = child
   279  				}
   280  			}
   281  		}
   282  	}
   283  
   284  	return result, nil
   285  }
   286  
   287  // populateChildren calculates the children of a directory and populates the list result.
   288  func (lr *ListResult) populateChildren(children []fileref.RefEntity, childResultMap map[string]*ListResult, selected map[string]*ListResult, req *ListRequest) {
   289  
   290  	for _, child := range children {
   291  		actualHash := child.GetFileMetaHash()
   292  
   293  		var childResult *ListResult
   294  		if _, ok := childResultMap[actualHash]; !ok {
   295  			childResult = &ListResult{
   296  				Name:      child.GetName(),
   297  				Path:      child.GetPath(),
   298  				Type:      child.GetType(),
   299  				CreatedAt: child.GetCreatedAt(),
   300  				UpdatedAt: child.GetUpdatedAt(),
   301  				Consensus: Consensus{
   302  					RWMutex:         &sync.RWMutex{},
   303  					consensusThresh: req.consensusThresh,
   304  					consensus:       0,
   305  					fullconsensus:   req.fullconsensus,
   306  				},
   307  				LookupHash: child.GetLookupHash(),
   308  			}
   309  			childResultMap[actualHash] = childResult
   310  		}
   311  		childResult = childResultMap[actualHash]
   312  		childResult.consensus++
   313  		if child.GetType() == fileref.FILE {
   314  			childResult.Hash = (child.(*fileref.FileRef)).ActualFileHash
   315  			childResult.MimeType = (child.(*fileref.FileRef)).MimeType
   316  			childResult.EncryptionKey = (child.(*fileref.FileRef)).EncryptedKey
   317  			childResult.ActualSize = (child.(*fileref.FileRef)).ActualFileSize
   318  			childResult.ThumbnailHash = (child.(*fileref.FileRef)).ThumbnailHash
   319  			childResult.ThumbnailSize = (child.(*fileref.FileRef)).ThumbnailSize
   320  			childResult.ActualThumbnailHash = (child.(*fileref.FileRef)).ActualThumbnailHash
   321  			childResult.ActualThumbnailSize = (child.(*fileref.FileRef)).ActualThumbnailSize
   322  		} else {
   323  			childResult.ActualSize = (child.(*fileref.Ref)).ActualSize
   324  		}
   325  		if childResult.ActualSize > 0 {
   326  			childResult.ActualNumBlocks = (childResult.ActualSize + CHUNK_SIZE - 1) / CHUNK_SIZE
   327  		}
   328  		childResult.Size += child.GetSize()
   329  		childResult.NumBlocks += child.GetNumBlocks()
   330  		childResult.FileMetaHash = child.GetFileMetaHash()
   331  		if childResult.isConsensusOk() && !req.forRepair {
   332  			if _, ok := selected[child.GetLookupHash()]; !ok {
   333  				lr.Children = append(lr.Children, childResult)
   334  				selected[child.GetLookupHash()] = childResult
   335  			}
   336  		}
   337  	}
   338  }