github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/sharing/revisions.go (about)

     1  package sharing
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/cozy/cozy-stack/model/vfs"
    10  	"github.com/cozy/cozy-stack/pkg/couchdb"
    11  	"github.com/cozy/cozy-stack/pkg/couchdb/revision"
    12  	"github.com/cozy/cozy-stack/pkg/prefixer"
    13  )
    14  
    15  type conflictStatus int
    16  
    17  const (
    18  	// NoConflict is the status when the rev is in the revisions chain (OK)
    19  	NoConflict conflictStatus = iota
    20  	// LostConflict is the status when rev is greater than the last revision of
    21  	// the chain (the resolution is often to abort the update)
    22  	LostConflict
    23  	// WonConflict is the status when rev is not in the chain,
    24  	// but the last revision of the chain is still (the resolution can be to
    25  	// make the update but including rev in the revisions chain)
    26  	WonConflict
    27  )
    28  
    29  // MaxDepth is the maximum number of revisions in a chain that we keep for a
    30  // document.
    31  const MaxDepth = 100
    32  
    33  // RevsStruct is a struct for revisions in bulk methods of CouchDB
    34  type RevsStruct struct {
    35  	Start int      `json:"start"`
    36  	IDs   []string `json:"ids"`
    37  }
    38  
    39  // RevsTree is a tree of revisions, like CouchDB has.
    40  // The revisions are sorted by growing generation (the number before the hyphen).
    41  // http://docs.couchdb.org/en/stable/replication/conflicts.html#revision-tree
    42  type RevsTree struct {
    43  	// Rev is a revision, with the generation and the id
    44  	// e.g. 1-1bad9a88f0a608ea78c12ab49882ac41
    45  	Rev string `json:"rev"`
    46  
    47  	// Branches is the list of revisions that have this revision for parent.
    48  	// The general case is to have only one branch, but we can have more with
    49  	// conflicts.
    50  	Branches []RevsTree `json:"branches,omitempty"`
    51  }
    52  
    53  // Clone duplicates the RevsTree
    54  func (rt *RevsTree) Clone() RevsTree {
    55  	cloned := RevsTree{Rev: rt.Rev}
    56  	cloned.Branches = make([]RevsTree, len(rt.Branches))
    57  	for i, b := range rt.Branches {
    58  		cloned.Branches[i] = b.Clone()
    59  	}
    60  	return cloned
    61  }
    62  
    63  // Generation returns the maximal generation of a revision in this tree
    64  func (rt *RevsTree) Generation() int {
    65  	if len(rt.Branches) == 0 {
    66  		return revision.Generation(rt.Rev)
    67  	}
    68  	max := 0
    69  	for _, b := range rt.Branches {
    70  		if g := b.Generation(); g > max {
    71  			max = g
    72  		}
    73  	}
    74  	return max
    75  }
    76  
    77  // Find returns the sub-tree for the given revision, or nil if not found. It
    78  // also gives the depth of the sub-tree (how many nodes are traversed from the
    79  // root of RevsTree to reach this sub-tree).
    80  func (rt *RevsTree) Find(rev string) (*RevsTree, int) {
    81  	if rt.Rev == rev {
    82  		return rt, 1
    83  	}
    84  	for i := range rt.Branches {
    85  		if sub, depth := rt.Branches[i].Find(rev); sub != nil {
    86  			return sub, depth + 1
    87  		}
    88  	}
    89  	return nil, 0
    90  }
    91  
    92  // ensureMaxDepth will remove the first nodes of the branch that contains parent.
    93  func (rt *RevsTree) ensureMaxDepth(parent string, depth int) {
    94  	current := rt
    95  	for depth > MaxDepth {
    96  		if len(current.Branches) == 0 {
    97  			break
    98  		} else if len(current.Branches) == 1 {
    99  			next := current.Branches[0]
   100  			current.Rev = next.Rev
   101  			current.Branches = next.Branches
   102  			depth--
   103  		} else {
   104  			for i := range current.Branches {
   105  				b := &current.Branches[i]
   106  				if sub, _ := b.Find(parent); sub != nil {
   107  					current = b
   108  					break
   109  				}
   110  			}
   111  		}
   112  	}
   113  }
   114  
   115  // Add inserts the given revision in the main branch
   116  func (rt *RevsTree) Add(rev string) *RevsTree {
   117  	if rev == rt.Rev {
   118  		return rt
   119  	}
   120  
   121  	if revision.Generation(rev) < revision.Generation(rt.Rev) {
   122  		rt.Branches = []RevsTree{
   123  			{Rev: rt.Rev, Branches: rt.Branches},
   124  		}
   125  		rt.Rev = rev
   126  		return rt
   127  	}
   128  
   129  	if len(rt.Branches) > 0 {
   130  		// XXX This condition shouldn't be true, but it can help to limit
   131  		// damage in case bugs happen.
   132  		if rt.Branches[0].Rev == rev {
   133  			return &rt.Branches[0]
   134  		}
   135  		return rt.Branches[0].Add(rev)
   136  	}
   137  
   138  	rt.Branches = []RevsTree{
   139  		{Rev: rev},
   140  	}
   141  	return &rt.Branches[0]
   142  }
   143  
   144  // InsertAfter inserts the given revision in the tree as a child of the second
   145  // revision.
   146  func (rt *RevsTree) InsertAfter(rev, parent string) {
   147  	subtree, depth := rt.Find(parent)
   148  	if subtree == nil {
   149  		// XXX This condition shouldn't be true, but it can help to limit
   150  		// damage in case bugs happen.
   151  		if sub, _ := rt.Find(rev); sub != nil {
   152  			return
   153  		}
   154  		subtree = rt.Add(parent)
   155  	}
   156  
   157  	rt.ensureMaxDepth(parent, depth+1)
   158  
   159  	for _, b := range subtree.Branches {
   160  		if b.Rev == rev {
   161  			return
   162  		}
   163  	}
   164  	subtree.Branches = append(subtree.Branches, RevsTree{Rev: rev})
   165  }
   166  
   167  // InsertChain inserts a chain of revisions, ie the first revision is the
   168  // parent of the second revision, which is itself the parent of the third
   169  // revision, etc. The first revisions of the chain are very probably already in
   170  // the tree, the last one is certainly not.
   171  // TODO ensure the MaxDepth limit is respected
   172  func (rt *RevsTree) InsertChain(chain []string) {
   173  	if len(chain) == 0 {
   174  		return
   175  	}
   176  	common := 0
   177  	var subtree *RevsTree
   178  	var depth int
   179  	for i, rev := range chain {
   180  		subtree, depth = rt.Find(rev)
   181  		if subtree != nil {
   182  			depth += len(chain) - i
   183  			common = i
   184  			break
   185  		}
   186  	}
   187  	if subtree == nil {
   188  		subtree = rt.Add(chain[0])
   189  	}
   190  
   191  	rt.ensureMaxDepth(subtree.Rev, depth)
   192  
   193  	for _, rev := range chain[common+1:] {
   194  		if len(subtree.Branches) > 0 {
   195  			found := false
   196  			for i := range subtree.Branches {
   197  				if subtree.Branches[i].Rev == rev {
   198  					found = true
   199  					subtree = &subtree.Branches[i]
   200  					break
   201  				}
   202  			}
   203  			if found {
   204  				continue
   205  			}
   206  		}
   207  		subtree.Branches = append(subtree.Branches, RevsTree{Rev: rev})
   208  		subtree = &subtree.Branches[0]
   209  	}
   210  }
   211  
   212  // revsMapToStruct builds a RevsStruct from a json unmarshaled to a map
   213  func revsMapToStruct(revs interface{}) *RevsStruct {
   214  	revisions, ok := revs.(map[string]interface{})
   215  	if !ok {
   216  		return nil
   217  	}
   218  	start, ok := revisions["start"].(float64)
   219  	if !ok {
   220  		return nil
   221  	}
   222  	slice, ok := revisions["ids"].([]interface{})
   223  	if !ok {
   224  		return nil
   225  	}
   226  	ids := make([]string, len(slice))
   227  	for i, id := range slice {
   228  		ids[i], _ = id.(string)
   229  	}
   230  	return &RevsStruct{
   231  		Start: int(start),
   232  		IDs:   ids,
   233  	}
   234  }
   235  
   236  // revsChainToStruct transforms revisions from on format to another:
   237  // ["2-aa", "3-bb", "4-cc"] -> { start: 4, ids: ["cc", "bb", "aa"] }
   238  func revsChainToStruct(revs []string) RevsStruct {
   239  	s := RevsStruct{
   240  		IDs: make([]string, len(revs)),
   241  	}
   242  	var last string
   243  	for i, rev := range revs {
   244  		parts := strings.SplitN(rev, "-", 2)
   245  		last = parts[0]
   246  		s.IDs[len(s.IDs)-i-1] = parts[1]
   247  	}
   248  	s.Start, _ = strconv.Atoi(last)
   249  	return s
   250  }
   251  
   252  // revsStructToChain is the reverse of revsChainToStruct:
   253  // { start: 4, ids: ["cc", "bb", "aa"] } -> ["2-aa", "3-bb", "4-cc"]
   254  func revsStructToChain(revs RevsStruct) []string {
   255  	start := revs.Start
   256  	ids := revs.IDs
   257  	chain := make([]string, len(ids))
   258  	for i, id := range ids {
   259  		rev := fmt.Sprintf("%d-%s", start, id)
   260  		chain[len(ids)-i-1] = rev
   261  		start--
   262  	}
   263  	return chain
   264  }
   265  
   266  // detectConflict says if there is a conflict (ie rev is not in the revisions
   267  // chain), and if it is the case, if the update should be made (WonConflict) or
   268  // aborted (LostConflict)
   269  func detectConflict(rev string, chain []string) conflictStatus {
   270  	if len(chain) == 0 {
   271  		return LostConflict
   272  	}
   273  	for _, r := range chain {
   274  		if r == rev {
   275  			return NoConflict
   276  		}
   277  	}
   278  
   279  	last := chain[len(chain)-1]
   280  	genl := revision.Generation(last)
   281  	genr := revision.Generation(rev)
   282  	if genl > genr {
   283  		return WonConflict
   284  	} else if genl < genr {
   285  		return LostConflict
   286  	} else if last > rev {
   287  		return WonConflict
   288  	}
   289  	return LostConflict
   290  }
   291  
   292  // MixupChainToResolveConflict creates a new chain of revisions that can be
   293  // used to resolve a conflict: the new chain will start the old rev and include
   294  // other revisions from the chain with a greater generation.
   295  func MixupChainToResolveConflict(rev string, chain []string) []string {
   296  	gen := revision.Generation(rev)
   297  	mixed := make([]string, 0)
   298  	found := false
   299  	for _, r := range chain {
   300  		if found {
   301  			mixed = append(mixed, r)
   302  		} else if gen == revision.Generation(r) {
   303  			mixed = append(mixed, rev)
   304  			found = true
   305  		}
   306  	}
   307  	return mixed
   308  }
   309  
   310  // addMissingRevsToChain includes the missing doc revisions to the chain, i.e.
   311  // all the doc revisions between the highest revision saved in the
   312  // revisions tree, and the lowest revision of the chain.
   313  // This can occur when both local and remote updates are made to the same doc,
   314  // with the remote ones saved before the local ones.
   315  func addMissingRevsToChain(db prefixer.Prefixer, ref *SharedRef, chain []string) ([]string, error) {
   316  	refHighestGen := ref.Revisions.Generation()
   317  	chainLowestGen := revision.Generation(chain[0])
   318  	if refHighestGen >= chainLowestGen-1 {
   319  		return chain, nil
   320  	}
   321  	docRef := extractDocReferenceFromID(ref.SID)
   322  	doc := &couchdb.JSONDoc{}
   323  	err := couchdb.GetDocWithRevs(db, docRef.Type, docRef.ID, doc)
   324  	if err != nil {
   325  		return chain, err
   326  	}
   327  	revisions := revsMapToStruct(doc.M["_revisions"])
   328  	if len(revisions.IDs) < chainLowestGen-1 {
   329  		return nil, fmt.Errorf("Cannot add the missing revs to io.cozy.shared %s", docRef.ID)
   330  	}
   331  	var oldRevs []string
   332  	for i := refHighestGen + 1; i < chainLowestGen; i++ {
   333  		revID := revisions.IDs[len(revisions.IDs)-i]
   334  		revGen := strconv.Itoa(i)
   335  		rev := revGen + "-" + revID
   336  		oldRevs = append(oldRevs, rev)
   337  	}
   338  	chain = append(oldRevs, chain...)
   339  	return chain, nil
   340  }
   341  
   342  // conflictName generates a new name for a file/folder in conflict with another
   343  // that has the same path. A conflicted file `foo` will be renamed foo (2),
   344  // then foo (3), etc.
   345  func conflictName(indexer vfs.Indexer, dirID, name string, isFile bool) string {
   346  	base, ext := name, ""
   347  	if isFile {
   348  		ext = filepath.Ext(name)
   349  		base = strings.TrimSuffix(base, ext)
   350  	}
   351  	i := 2
   352  	if strings.HasSuffix(base, ")") {
   353  		if idx := strings.LastIndex(base, " ("); idx > 0 {
   354  			num, err := strconv.Atoi(base[idx+2 : len(base)-1])
   355  			if err == nil {
   356  				i = num + 1
   357  				base = base[0:idx]
   358  			}
   359  		}
   360  	}
   361  	for j := 0; j < 1000; j++ {
   362  		newname := fmt.Sprintf("%s (%d)%s", base, i, ext)
   363  		exists, err := indexer.DirChildExists(dirID, newname)
   364  		if err != nil || !exists {
   365  			return newname
   366  		}
   367  		i++
   368  	}
   369  	return fmt.Sprintf("%s (%d)%s", base, i, ext)
   370  }
   371  
   372  // conflictID generates a new ID for a file/folder that has a conflict between
   373  // two versions of its content.
   374  func conflictID(id, rev string) string {
   375  	parts := strings.SplitN(rev, "-", 2)
   376  	key := []byte(parts[1])
   377  	for i, c := range key {
   378  		switch {
   379  		case '0' <= c && c <= '9':
   380  			key[i] = c - '0'
   381  		case 'a' <= c && c <= 'f':
   382  			key[i] = c - 'a' + 10
   383  		case 'A' <= c && c <= 'F':
   384  			key[i] = c - 'A' + 10
   385  		}
   386  	}
   387  	return XorID(id, key)
   388  }
   389  
   390  // CheckSharedError is the type used when checking the io.cozy.shared, and one
   391  // document has two revisions where a child don't its generation equal to the
   392  // generation of the parent plus one.
   393  type CheckSharedError struct {
   394  	Type   string `json:"type"`
   395  	ID     string `json:"_id"`
   396  	Parent string `json:"parent_rev"`
   397  	Child  string `json:"child_rev"`
   398  }
   399  
   400  func (rt *RevsTree) check() *CheckSharedError {
   401  	if len(rt.Branches) == 0 {
   402  		return nil
   403  	}
   404  
   405  	gen := revision.Generation(rt.Rev)
   406  	for _, b := range rt.Branches {
   407  		if revision.Generation(b.Rev) != gen+1 {
   408  			return &CheckSharedError{
   409  				Type:   "invalid_revs_suite",
   410  				Parent: rt.Rev,
   411  				Child:  b.Rev,
   412  			}
   413  		}
   414  	}
   415  
   416  	for _, b := range rt.Branches {
   417  		if check := b.check(); check != nil {
   418  			return check
   419  		}
   420  	}
   421  	return nil
   422  }