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 }