github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/repo/refstore.go (about)

     1  package repo
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/qri-io/qri/dsref"
     8  	reporef "github.com/qri-io/qri/repo/ref"
     9  )
    10  
    11  // Refstore keeps a collection of dataset references, Refstores require complete
    12  // references (with both alias and identifiers), and can carry only one of a
    13  // given alias eg: putting peer/dataset@a/ipfs/b when a ref with alias peer/dataset
    14  // is already in the store will overwrite the stored reference
    15  type Refstore interface {
    16  	// PutRef adds a reference to the store. References must be complete with
    17  	// Peername, Name, and Path/FSIPath specified
    18  	PutRef(ref reporef.DatasetRef) error
    19  	// GetRef "completes" a passed in alias (reporef.DatasetRef with at least Peername
    20  	// and Name field specified), filling in missing fields with a stored ref
    21  	GetRef(ref reporef.DatasetRef) (reporef.DatasetRef, error)
    22  	// DeleteRef removes a reference from the store
    23  	DeleteRef(ref reporef.DatasetRef) error
    24  	// References returns a set of references from the store
    25  	References(offset, limit int) ([]reporef.DatasetRef, error)
    26  	// RefCount returns the number of references in the store
    27  	RefCount() (int, error)
    28  }
    29  
    30  // ListVersionInfoShim wraps a call to References, converting results to a
    31  // slice of VersionInfos
    32  func ListVersionInfoShim(r Repo, offset, limit int) ([]dsref.VersionInfo, error) {
    33  	refs, err := r.References(offset, limit)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	res := make([]dsref.VersionInfo, len(refs))
    39  	for i, rref := range refs {
    40  		res[i] = reporef.ConvertToVersionInfo(&rref)
    41  	}
    42  	return res, nil
    43  }
    44  
    45  // GetVersionInfoShim is a shim for getting away from the old
    46  // DatasetRef, CanonicalizeDatasetRef, RefStore stack
    47  func GetVersionInfoShim(r Repo, ref dsref.Ref) (*dsref.VersionInfo, error) {
    48  	rref, err := r.GetRef(reporef.RefFromDsref(ref))
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	vi := reporef.ConvertToVersionInfo(&rref)
    53  	return &vi, nil
    54  }
    55  
    56  // PutVersionInfoShim is a shim for getting away from old stack of
    57  // DatasetRef, CanonicalizeDatasetRef, and RefStores
    58  // while still safely interacting with the repo.Refstore API
    59  func PutVersionInfoShim(ctx context.Context, r Repo, vi *dsref.VersionInfo) error {
    60  	// attempt to look up peerIDs when not set
    61  	if vi.ProfileID == "" && vi.Username != "" {
    62  		rref := &reporef.DatasetRef{Peername: vi.Username}
    63  		if err := canonicalizeProfile(ctx, r, rref); err == nil {
    64  			vi.ProfileID = rref.ProfileID.Encode()
    65  		}
    66  	}
    67  	return r.PutRef(reporef.RefFromVersionInfo(vi))
    68  }
    69  
    70  // DeleteVersionInfoShim is a shim for getting away from the old stack of
    71  // DatasetRef, CanonicalizeDatasetRef, and RefStore
    72  // while still safely interacting with the repo.Refstore API
    73  func DeleteVersionInfoShim(ctx context.Context, r Repo, ref dsref.Ref) (*dsref.VersionInfo, error) {
    74  	rref := reporef.RefFromDsref(ref)
    75  	if err := canonicalizeDatasetRef(ctx, r, &rref); err != nil && err != ErrNoHistory {
    76  		return nil, err
    77  	}
    78  	if err := r.DeleteRef(rref); err != nil {
    79  		return nil, err
    80  	}
    81  	vi := reporef.ConvertToVersionInfo(&rref)
    82  	return &vi, nil
    83  }
    84  
    85  // ListDatasetsShim gets away from the old stack of DatasetRef,
    86  // CanonicalizeDatasetRef and RefStore
    87  func ListDatasetsShim(r Repo, offset, limit int) ([]dsref.VersionInfo, error) {
    88  	rrefs, err := r.References(offset, limit)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	vis := make([]dsref.VersionInfo, 0, limit)
    94  	for _, ref := range rrefs {
    95  		if offset > 0 {
    96  			offset--
    97  			continue
    98  		}
    99  		vis = append(vis, reporef.ConvertToVersionInfo(&ref))
   100  		if len(vis) == limit {
   101  			return vis, nil
   102  		}
   103  	}
   104  
   105  	return vis, nil
   106  }
   107  
   108  // canonicalizeDatasetRef uses the user's repo to turn any local aliases into full dataset
   109  // references using known canonical peernames and paths. If the provided reference is not
   110  // in the local repo, still do the work of handling aliases, but return a repo.ErrNotFound
   111  // error, which callers can respond to by possibly contacting remote repos.
   112  func canonicalizeDatasetRef(ctx context.Context, r Repo, ref *reporef.DatasetRef) error {
   113  	if ref.IsEmpty() {
   114  		return ErrEmptyRef
   115  	}
   116  
   117  	if err := canonicalizeProfile(ctx, r, ref); err != nil {
   118  		return err
   119  	}
   120  
   121  	got, err := r.GetRef(*ref)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	// TODO (b5) - this is the assign pattern, refactor into a method on reporef.DatasetRef
   127  	if ref.Path == "" {
   128  		ref.Path = got.Path
   129  	}
   130  	if ref.ProfileID == "" {
   131  		ref.ProfileID = got.ProfileID
   132  	}
   133  	if ref.Name == "" {
   134  		ref.Name = got.Name
   135  	}
   136  	if ref.Peername == "" || ref.Peername != got.Peername {
   137  		ref.Peername = got.Peername
   138  	}
   139  	ref.Published = got.Published
   140  	if ref.FSIPath == "" {
   141  		ref.FSIPath = got.FSIPath
   142  	}
   143  	if ref.ProfileID != got.ProfileID || ref.Name != got.Name {
   144  		return fmt.Errorf("Given datasetRef %s does not match datasetRef on file: %s", ref.String(), got.String())
   145  	}
   146  
   147  	if got.Path == "" {
   148  		return ErrNoHistory
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  // canonicalizeProfile populates dataset reporef.DatasetRef ProfileID and Peername properties,
   155  // changing aliases to known names, and adding ProfileID from a peerstore
   156  func canonicalizeProfile(ctx context.Context, r Repo, ref *reporef.DatasetRef) error {
   157  	if ref.Peername == "" && ref.ProfileID == "" {
   158  		return nil
   159  	}
   160  
   161  	p := r.Profiles().Owner(ctx)
   162  
   163  	// If this is a dataset ref that a peer of the user owns.
   164  	if ref.Peername == "me" || ref.Peername == p.Peername || ref.ProfileID == p.ID {
   165  		if ref.Peername == "me" {
   166  			ref.ProfileID = p.ID
   167  			ref.Peername = p.Peername
   168  		}
   169  
   170  		if ref.Peername != "" && ref.ProfileID != "" {
   171  			if ref.Peername == p.Peername && ref.ProfileID != p.ID {
   172  				return fmt.Errorf("Peername and ProfileID combination not valid: Peername = %s, ProfileID = %s, but was given ProfileID = %s", p.Peername, p.ID, ref.ProfileID)
   173  			}
   174  			if ref.ProfileID == p.ID && ref.Peername != p.Peername {
   175  				// The peer renamed itself at some point, but the profileID matches. Use the
   176  				// new peername.
   177  				ref.Peername = p.Peername
   178  				return nil
   179  			}
   180  			if ref.Peername == p.Peername && ref.ProfileID == p.ID {
   181  				return nil
   182  			}
   183  		}
   184  
   185  		if ref.Peername != "" {
   186  			if ref.Peername != p.Peername {
   187  				return nil
   188  			}
   189  		}
   190  
   191  		if ref.ProfileID != "" {
   192  			if ref.ProfileID != p.ID {
   193  				return nil
   194  			}
   195  		}
   196  
   197  		ref.Peername = p.Peername
   198  		ref.ProfileID = p.ID
   199  		return nil
   200  	}
   201  
   202  	if ref.ProfileID != "" {
   203  		if profile, err := r.Profiles().GetProfile(ctx, ref.ProfileID); err == nil {
   204  
   205  			if ref.Peername == "" {
   206  				ref.Peername = profile.Peername
   207  				return nil
   208  			}
   209  			if ref.Peername != profile.Peername {
   210  				return fmt.Errorf("Peername and ProfileID combination not valid: ProfileID = %s, Peername = %s, but was given Peername = %s", profile.ID, profile.Peername, ref.Peername)
   211  			}
   212  		}
   213  	}
   214  
   215  	if ref.Peername != "" {
   216  		if id, err := r.Profiles().PeernameID(ctx, ref.Peername); err == nil {
   217  			// if err != nil {
   218  			// 	return fmt.Errorf("error fetching peer from store: %s", err)
   219  			// }
   220  			if ref.ProfileID == "" {
   221  				ref.ProfileID = id
   222  				return nil
   223  			}
   224  			if ref.ProfileID != id {
   225  				return fmt.Errorf("Peername and ProfileID combination not valid: Peername = %s, ProfileID = %s, but was given ProfileID = %s", ref.Peername, id.Encode(), ref.ProfileID)
   226  			}
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  // CompareDatasetRef compares two Dataset References, returning an error
   233  // describing any difference between the two references
   234  func CompareDatasetRef(a, b reporef.DatasetRef) error {
   235  	if a.ProfileID != b.ProfileID {
   236  		return fmt.Errorf("PeerID mismatch. %s != %s", a.ProfileID.Encode(), b.ProfileID.Encode())
   237  	}
   238  	if a.Peername != b.Peername {
   239  		return fmt.Errorf("Peername mismatch. %s != %s", a.Peername, b.Peername)
   240  	}
   241  	if a.Name != b.Name {
   242  		return fmt.Errorf("Name mismatch. %s != %s", a.Name, b.Name)
   243  	}
   244  	if a.Path != b.Path {
   245  		return fmt.Errorf("Path mismatch. %s != %s", a.Path, b.Path)
   246  	}
   247  	if a.Published != b.Published {
   248  		return fmt.Errorf("Published mismatch: %t != %t", a.Published, b.Published)
   249  	}
   250  	return nil
   251  }