github.com/hoffie/larasync@v0.0.0-20151025221940-0384d2bddcef/repository/nib/nib.go (about)

     1  package nib
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"reflect"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  
    10  	"github.com/hoffie/larasync/repository/odf"
    11  )
    12  
    13  // NIB (Node Information Block) is a metadata object, which
    14  // exists for every managed file or directory.
    15  // Besides containing administration information on its own,
    16  // it contains references to revisions.
    17  type NIB struct {
    18  	ID            string
    19  	Revisions     []*Revision
    20  	HistoryOffset int64
    21  }
    22  
    23  // ReadFrom fills this NIB's data with the contents supplied by
    24  // the binary representation available through the given reader.
    25  func (n *NIB) ReadFrom(r io.Reader) (int64, error) {
    26  	buf := &bytes.Buffer{}
    27  	read, err := io.Copy(buf, r)
    28  	if err != nil {
    29  		return read, err
    30  	}
    31  	pb := &odf.NIB{}
    32  	err = proto.Unmarshal(buf.Bytes(), pb)
    33  	if err != nil {
    34  		return read, err
    35  	}
    36  	n.ID = pb.GetID()
    37  	n.HistoryOffset = pb.GetHistoryOffset()
    38  	if pb.Revisions != nil {
    39  		for _, pbRev := range pb.Revisions {
    40  			n.AppendRevision(newRevisionFromPb(pbRev))
    41  		}
    42  	}
    43  	return read, nil
    44  }
    45  
    46  // WriteTo encodes this NIB to the supplied Writer in binary form.
    47  // Returns the number of bytes written and an error if applicable.
    48  func (n *NIB) WriteTo(w io.Writer) (int64, error) {
    49  	pb := &odf.NIB{
    50  		ID:            &n.ID,
    51  		HistoryOffset: &n.HistoryOffset,
    52  		Revisions:     make([]*odf.Revision, 0),
    53  	}
    54  	for _, r := range n.Revisions {
    55  		pb.Revisions = append(pb.Revisions, r.toPb())
    56  	}
    57  	buf, err := proto.Marshal(pb)
    58  	if err != nil {
    59  		return 0, err
    60  	}
    61  	written, err := io.Copy(w, bytes.NewBuffer(buf))
    62  	return written, err
    63  }
    64  
    65  // AppendRevision adds a new Revision to the NIB's list of
    66  // revisions at the end.
    67  func (n *NIB) AppendRevision(r *Revision) {
    68  	n.Revisions = append(n.Revisions, r)
    69  }
    70  
    71  // LatestRevision returns the most-recently added revision.
    72  func (n *NIB) LatestRevision() (*Revision, error) {
    73  	l := len(n.Revisions)
    74  	if l < 1 {
    75  		return nil, ErrNoRevision
    76  	}
    77  	return n.Revisions[l-1], nil
    78  }
    79  
    80  // LatestRevisionWithContent returns the most-recent revision whose content matches
    81  // the requested content ids.
    82  func (n *NIB) LatestRevisionWithContent(contentIDs []string) (*Revision, error) {
    83  	for i := len(n.Revisions) - 1; i >= 0; i-- {
    84  		rev := n.Revisions[i]
    85  		if reflect.DeepEqual(rev.ContentIDs, contentIDs) {
    86  			return rev, nil
    87  		}
    88  	}
    89  	return nil, ErrNoRevision
    90  }
    91  
    92  // RevisionsTotal returns the total length of all revisions.
    93  // This is the sum of old revisions as marked by HistoryOffset plus any
    94  // current Revisions.
    95  func (n *NIB) RevisionsTotal() int64 {
    96  	return int64(len(n.Revisions)) + n.HistoryOffset
    97  }
    98  
    99  // AllObjectIDs returns a list of all unique ids which this NIB refers to
   100  func (n *NIB) AllObjectIDs() []string {
   101  	res := []string{}
   102  	lookup := make(map[string]bool)
   103  	appendID := func(id string) {
   104  		if _, exists := lookup[id]; exists {
   105  			return
   106  		}
   107  		lookup[id] = true
   108  		res = append(res, id)
   109  	}
   110  	for _, rev := range n.Revisions {
   111  		appendID(rev.MetadataID)
   112  		for _, contentID := range rev.ContentIDs {
   113  			appendID(contentID)
   114  		}
   115  	}
   116  	return res
   117  }
   118  
   119  // IsParentOf returns true if this nib is part of the history
   120  // of the other NIB.
   121  //
   122  // This is used when checking whether it is ok to overwrite an old
   123  // nib with a newer version.
   124  // The check compares the overall history length and ensures
   125  // that a common head can be found.
   126  func (n *NIB) IsParentOf(other *NIB) bool {
   127  	if n.RevisionsTotal() > other.RevisionsTotal() {
   128  		// if our history is longer than the other's, there is no
   129  		// chance that we can reach the same state as the other NIB.
   130  		return false
   131  	}
   132  	if n.RevisionsTotal() == 0 {
   133  		// when no revisions have been recorded in the lifetime
   134  		// of this NIB, we can always forward to another NIB
   135  		// status.
   136  		return true
   137  	}
   138  	// it's safe to ignore err here as we check for the case of 0
   139  	// revisions before.
   140  	myLatestRev, _ := n.LatestRevision()
   141  	for _, otherRev := range other.Revisions {
   142  		if myLatestRev.HasSameContent(otherRev) {
   143  			// we found a common ancestor, so we can import
   144  			// the changes from the other NIB to synchronize.
   145  			return true
   146  		}
   147  	}
   148  	// no common ancestory => no way to merge this on the NIB level
   149  	return false
   150  }