github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/kbfsedits/user_history.go (about)

     1  // Copyright 2018 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package kbfsedits
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"sort"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/keybase/client/go/kbfs/tlf"
    15  	"github.com/keybase/client/go/libkb"
    16  	"github.com/keybase/client/go/logger"
    17  	"github.com/keybase/client/go/protocol/keybase1"
    18  )
    19  
    20  const (
    21  	// MaxClusters is the max number of TLF writer clusters to return
    22  	// in a user history.
    23  	MaxClusters = 10
    24  
    25  	// The minimum number of self-clusters that should appear in the
    26  	// history for the logged-in user.
    27  	minNumSelfClusters = 3
    28  )
    29  
    30  type tlfKey struct {
    31  	tlfName tlf.CanonicalName
    32  	tlfType tlf.Type
    33  }
    34  
    35  // UserHistory keeps a sorted list of the top known TLF edit
    36  // histories, and can convert those histories into keybase1 protocol
    37  // structs.  TLF histories must be updated by an external caller
    38  // whenever they change.
    39  type UserHistory struct {
    40  	lock      sync.RWMutex
    41  	histories map[tlfKey]writersByRevision
    42  	log       logger.Logger
    43  	vlog      *libkb.VDebugLog
    44  }
    45  
    46  // NewUserHistory constructs a UserHistory instance.
    47  func NewUserHistory(log logger.Logger, vlog *libkb.VDebugLog) *UserHistory {
    48  	return &UserHistory{
    49  		histories: make(map[tlfKey]writersByRevision),
    50  		log:       log,
    51  		vlog:      vlog,
    52  	}
    53  }
    54  
    55  // UpdateHistory should be called whenever the edit history for a
    56  // given TLF gets new information.
    57  func (uh *UserHistory) UpdateHistory(
    58  	tlfName tlf.CanonicalName, tlfType tlf.Type, tlfHistory *TlfHistory,
    59  	loggedInUser string) {
    60  	uh.vlog.CLogf(
    61  		context.TODO(), libkb.VLog1, "Updating user history for TLF %s, "+
    62  			"user %s", tlfName, loggedInUser)
    63  	history := tlfHistory.getHistory(loggedInUser)
    64  	key := tlfKey{tlfName, tlfType}
    65  
    66  	uh.lock.Lock()
    67  	defer uh.lock.Unlock()
    68  	uh.histories[key] = history
    69  }
    70  
    71  func (uh *UserHistory) getTlfHistoryLocked(
    72  	tlfName tlf.CanonicalName, tlfType tlf.Type) (
    73  	history keybase1.FSFolderEditHistory) {
    74  	key := tlfKey{tlfName, tlfType}
    75  	tlfHistory, ok := uh.histories[key]
    76  	if !ok {
    77  		return keybase1.FSFolderEditHistory{}
    78  	}
    79  
    80  	folder := keybase1.Folder{
    81  		Name:       string(tlfName),
    82  		FolderType: tlfType.FolderType(),
    83  		Private:    tlfType == tlf.Private,
    84  	}
    85  	history.Folder = folder
    86  	if len(tlfHistory) == 0 {
    87  		return history
    88  	}
    89  	if len(tlfHistory[0].notifications) > 0 {
    90  		history.ServerTime = keybase1.ToTime(
    91  			tlfHistory[0].notifications[0].Time)
    92  		// If there are no notifications (only deletes), leave
    93  		// `ServerTime` unset.
    94  	}
    95  	history.History = make(
    96  		[]keybase1.FSFolderWriterEditHistory, len(tlfHistory))
    97  	for i, wn := range tlfHistory {
    98  		history.History[i].WriterName = wn.writerName
    99  		history.History[i].Edits = make(
   100  			[]keybase1.FSFolderWriterEdit, len(wn.notifications))
   101  		for j, n := range wn.notifications {
   102  			history.History[i].Edits[j].Filename = n.Filename
   103  			history.History[i].Edits[j].ServerTime = keybase1.ToTime(n.Time)
   104  			switch n.Type {
   105  			case NotificationCreate:
   106  				history.History[i].Edits[j].NotificationType =
   107  					keybase1.FSNotificationType_FILE_CREATED
   108  			case NotificationModify:
   109  				history.History[i].Edits[j].NotificationType =
   110  					keybase1.FSNotificationType_FILE_MODIFIED
   111  			default:
   112  				panic(fmt.Sprintf("Unknown notification type %s", n.Type))
   113  			}
   114  		}
   115  
   116  		history.History[i].Deletes = make(
   117  			[]keybase1.FSFolderWriterEdit, len(wn.deletes))
   118  		for j, n := range wn.deletes {
   119  			history.History[i].Deletes[j].Filename = n.Filename
   120  			history.History[i].Deletes[j].ServerTime = keybase1.ToTime(n.Time)
   121  			history.History[i].Deletes[j].NotificationType =
   122  				keybase1.FSNotificationType_FILE_DELETED
   123  		}
   124  	}
   125  	return history
   126  }
   127  
   128  // GetTlfHistory returns the edit history of a given TLF, converted to
   129  // keybase1 protocol structs.
   130  func (uh *UserHistory) GetTlfHistory(
   131  	tlfName tlf.CanonicalName, tlfType tlf.Type) (
   132  	history keybase1.FSFolderEditHistory) {
   133  	uh.lock.RLock()
   134  	defer uh.lock.RUnlock()
   135  	return uh.getTlfHistoryLocked(tlfName, tlfType)
   136  }
   137  
   138  type historyClusters []keybase1.FSFolderEditHistory
   139  
   140  func (hc historyClusters) Len() int {
   141  	return len(hc)
   142  }
   143  
   144  func (hc historyClusters) Less(i, j int) bool {
   145  	iTime := hc[i].ServerTime
   146  	jTime := hc[j].ServerTime
   147  
   148  	if iTime == 0 && jTime == 0 {
   149  		// If both are zero, use the times of the first delete instead.
   150  		if len(hc[i].History[0].Deletes) > 0 {
   151  			iTime = hc[i].History[0].Deletes[0].ServerTime
   152  		}
   153  		if len(hc[j].History[0].Deletes) > 0 {
   154  			jTime = hc[j].History[0].Deletes[0].ServerTime
   155  		}
   156  	}
   157  
   158  	return iTime > jTime
   159  }
   160  
   161  func (hc historyClusters) Swap(i, j int) {
   162  	hc[i], hc[j] = hc[j], hc[i]
   163  }
   164  
   165  // Get returns the full edit history for the user, converted to
   166  // keybase1 protocol structs.
   167  func (uh *UserHistory) Get(loggedInUser string) (
   168  	history []keybase1.FSFolderEditHistory) {
   169  	uh.lock.RLock()
   170  	defer uh.lock.RUnlock()
   171  	uh.vlog.CLogf(
   172  		context.TODO(), libkb.VLog1, "User history requested: %s", loggedInUser)
   173  	var clusters historyClusters
   174  	for key := range uh.histories {
   175  		history := uh.getTlfHistoryLocked(key.tlfName, key.tlfType)
   176  
   177  		// Only include public TLFs if they match the logged-in user.
   178  		if history.Folder.FolderType == keybase1.FolderType_PUBLIC {
   179  			names := strings.Split(history.Folder.Name, ",")
   180  			match := false
   181  			for _, name := range names {
   182  				if name == loggedInUser {
   183  					match = true
   184  					break
   185  				}
   186  			}
   187  			if !match {
   188  				continue
   189  			}
   190  		}
   191  
   192  		// Break it up into individual clusters
   193  		for _, wh := range history.History {
   194  			if len(wh.Edits) > 0 || len(wh.Deletes) > 0 {
   195  				var serverTime keybase1.Time
   196  				if len(wh.Edits) > 0 {
   197  					serverTime = wh.Edits[0].ServerTime
   198  				}
   199  				clusters = append(clusters, keybase1.FSFolderEditHistory{
   200  					Folder:     history.Folder,
   201  					ServerTime: serverTime,
   202  					History:    []keybase1.FSFolderWriterEditHistory{wh},
   203  				})
   204  			}
   205  		}
   206  	}
   207  
   208  	// We need to sort these by the ServerTime of these particular edits,
   209  	// not by the full TLF time.
   210  	sort.Sort(clusters)
   211  
   212  	// TODO: consolidate neighboring clusters that share the same folder?
   213  	if len(clusters) > MaxClusters {
   214  		// Find the top self-clusters.
   215  		loggedInIndices := make([]int, 0, minNumSelfClusters)
   216  		for i, c := range clusters {
   217  			if c.History[0].WriterName == loggedInUser {
   218  				loggedInIndices = append(loggedInIndices, i)
   219  			}
   220  			if len(loggedInIndices) == minNumSelfClusters {
   221  				break
   222  			}
   223  		}
   224  
   225  		// Move each self-cluster into its rightful spot at the end of
   226  		// the slice, unless it's already at a lower index.
   227  		for i, loggedInIndex := range loggedInIndices {
   228  			newLoggedInIndex := MaxClusters - (len(loggedInIndices) - i)
   229  			if loggedInIndex < newLoggedInIndex {
   230  				continue
   231  			}
   232  			clusters[newLoggedInIndex] = clusters[loggedInIndex]
   233  		}
   234  
   235  		return clusters[:MaxClusters]
   236  	}
   237  	return clusters
   238  }
   239  
   240  // Clear erases all saved histories; TLFs must be re-added.
   241  func (uh *UserHistory) Clear() {
   242  	uh.lock.Lock()
   243  	defer uh.lock.Unlock()
   244  	uh.histories = make(map[tlfKey]writersByRevision)
   245  }
   246  
   247  // ClearTLF removes a TLF from this UserHistory.
   248  func (uh *UserHistory) ClearTLF(tlfName tlf.CanonicalName, tlfType tlf.Type) {
   249  	key := tlfKey{tlfName, tlfType}
   250  	uh.lock.Lock()
   251  	defer uh.lock.Unlock()
   252  	delete(uh.histories, key)
   253  }