github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/action_get.go (about)

     1  package holochain
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	. "github.com/holochain/holochain-proto/hash"
     7  	peer "github.com/libp2p/go-libp2p-peer"
     8  	"reflect"
     9  )
    10  
    11  //------------------------------------------------------------
    12  // Get
    13  
    14  type APIFnGet struct {
    15  	action ActionGet
    16  }
    17  
    18  func (fn *APIFnGet) Name() string {
    19  	return fn.action.Name()
    20  }
    21  
    22  func (fn *APIFnGet) Args() []Arg {
    23  	return []Arg{{Name: "hash", Type: HashArg}, {Name: "options", Type: MapArg, MapType: reflect.TypeOf(GetOptions{}), Optional: true}}
    24  }
    25  
    26  func callGet(h *Holochain, req GetReq, options *GetOptions) (response interface{}, err error) {
    27  	a := ActionGet{req: req, options: options}
    28  	fn := &APIFnGet{action: a}
    29  	response, err = fn.Call(h)
    30  	return
    31  }
    32  
    33  func (fn *APIFnGet) Call(h *Holochain) (response interface{}, err error) {
    34  	a := &fn.action
    35  	if a.options.Local {
    36  		response, err = a.getLocal(h.chain)
    37  		return
    38  	}
    39  	if a.options.Bundle {
    40  		bundle := h.Chain().BundleStarted()
    41  		if bundle == nil {
    42  			err = ErrBundleNotStarted
    43  			return
    44  		}
    45  		response, err = a.getLocal(bundle.chain)
    46  		return
    47  	}
    48  	rsp, err := h.dht.Query(a.req.H, GET_REQUEST, a.req)
    49  	if err != nil {
    50  
    51  		// follow the modified hash
    52  		if a.req.StatusMask == StatusDefault && err == ErrHashModified {
    53  			var hash Hash
    54  			hash, err = NewHash(rsp.(GetResp).FollowHash)
    55  			if err != nil {
    56  				return
    57  			}
    58  			if hash.String() == a.req.H.String() {
    59  				err = errors.New("FollowHash loop detected")
    60  				return
    61  			}
    62  			req := GetReq{H: hash, StatusMask: StatusDefault, GetMask: a.options.GetMask}
    63  			modResp, err := callGet(h, req, a.options)
    64  			if err == nil {
    65  				response = modResp
    66  			}
    67  		}
    68  		return
    69  	}
    70  	switch t := rsp.(type) {
    71  	case GetResp:
    72  		response = t
    73  	default:
    74  		err = fmt.Errorf("expected GetResp response from GET_REQUEST, got: %T", t)
    75  		return
    76  	}
    77  	return
    78  }
    79  
    80  type ActionGet struct {
    81  	req     GetReq
    82  	options *GetOptions
    83  }
    84  
    85  func (a *ActionGet) Name() string {
    86  	return "get"
    87  }
    88  
    89  func (a *ActionGet) getLocal(chain *Chain) (resp GetResp, err error) {
    90  	var entry Entry
    91  	var entryType string
    92  	entry, entryType, err = chain.GetEntry(a.req.H)
    93  	if err != nil {
    94  		return
    95  	}
    96  	resp = GetResp{Entry: *entry.(*GobEntry)}
    97  	mask := a.options.GetMask
    98  	resp.EntryType = entryType
    99  	if (mask & GetMaskEntry) != 0 {
   100  		resp.Entry = *entry.(*GobEntry)
   101  		resp.EntryType = entryType
   102  	}
   103  	return
   104  }
   105  
   106  func (a *ActionGet) SysValidation(h *Holochain, def *EntryDef, pkg *Package, sources []peer.ID) (err error) {
   107  	return
   108  }
   109  
   110  func (a *ActionGet) Receive(dht *DHT, msg *Message) (response interface{}, err error) {
   111  	var entryData []byte
   112  	//var status int
   113  	req := msg.Body.(GetReq)
   114  	mask := req.GetMask
   115  	if mask == GetMaskDefault {
   116  		mask = GetMaskEntry
   117  	}
   118  	resp := GetResp{}
   119  	// always get the entry type despite what the mas says because we need it for the switch below.
   120  	entryData, resp.EntryType, resp.Sources, _, err = dht.Get(req.H, req.StatusMask, req.GetMask|GetMaskEntryType)
   121  	if err == nil {
   122  		if (mask & GetMaskEntry) != 0 {
   123  			switch resp.EntryType {
   124  			case DNAEntryType:
   125  				// TODO: make this add the requester to the blockedlist rather than panicing, see ticket #421
   126  				err = errors.New("nobody should actually get the DNA!")
   127  				return
   128  			case KeyEntryType:
   129  				resp.Entry = GobEntry{C: string(entryData)}
   130  			default:
   131  				var e GobEntry
   132  				err = e.Unmarshal(entryData)
   133  				if err != nil {
   134  					return
   135  				}
   136  				resp.Entry = e
   137  			}
   138  		}
   139  	} else {
   140  		if err == ErrHashModified {
   141  			resp.FollowHash = string(entryData)
   142  		} else if err == ErrHashNotFound {
   143  			closest := dht.h.node.betterPeersForHash(&req.H, msg.From, true, CloserPeerCount)
   144  			if len(closest) > 0 {
   145  				err = nil
   146  				resp := CloserPeersResp{}
   147  				resp.CloserPeers = dht.h.node.peers2PeerInfos(closest)
   148  				response = resp
   149  				return
   150  			}
   151  		}
   152  	}
   153  
   154  	response = resp
   155  	return
   156  }