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 }