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

     1  // Copyright 2016 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 libkbfs
     6  
     7  import (
     8  	"reflect"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/keybase/client/go/kbfs/kbfsmd"
    13  	"github.com/keybase/client/go/kbfs/tlf"
    14  	kbname "github.com/keybase/client/go/kbun"
    15  	"github.com/keybase/client/go/protocol/keybase1"
    16  	"golang.org/x/net/context"
    17  )
    18  
    19  // FolderBranchStatus is a simple data structure describing the
    20  // current status of a particular folder-branch.  It is suitable for
    21  // encoding directly as JSON.
    22  type FolderBranchStatus struct {
    23  	Staged              bool
    24  	BranchID            string
    25  	HeadWriter          kbname.NormalizedUsername
    26  	DiskUsage           uint64
    27  	RekeyPending        bool
    28  	LatestKeyGeneration kbfsmd.KeyGen
    29  	FolderID            string
    30  	Revision            kbfsmd.Revision
    31  	LastGCRevision      kbfsmd.Revision
    32  	MDVersion           kbfsmd.MetadataVer
    33  	RootBlockID         string
    34  	SyncEnabled         bool
    35  	PrefetchStatus      string
    36  	UsageBytes          int64
    37  	ArchiveBytes        int64
    38  	LimitBytes          int64
    39  	GitUsageBytes       int64
    40  	GitArchiveBytes     int64
    41  	GitLimitBytes       int64
    42  	LocalTimestamp      time.Time
    43  
    44  	// DirtyPaths are files that have been written, but not flushed.
    45  	// They do not represent unstaged changes in your local instance.
    46  	DirtyPaths []string
    47  
    48  	// If we're in the staged state, these summaries show the
    49  	// diverging operations per-file
    50  	Unmerged []*crChainSummary
    51  	Merged   []*crChainSummary
    52  
    53  	ConflictResolutionAttempts []conflictRecord            `json:",omitempty"`
    54  	ConflictStatus             keybase1.FolderConflictType `json:",omitempty"`
    55  
    56  	Journal *TLFJournalStatus `json:",omitempty"`
    57  
    58  	PermanentErr string `json:",omitempty"`
    59  }
    60  
    61  // KBFSStatus represents the content of the top-level status file. It is
    62  // suitable for encoding directly as JSON.
    63  // TODO: implement magical status update like FolderBranchStatus
    64  type KBFSStatus struct {
    65  	CurrentUser          string
    66  	IsConnected          bool
    67  	UsageBytes           int64
    68  	ArchiveBytes         int64
    69  	LimitBytes           int64
    70  	GitUsageBytes        int64
    71  	GitArchiveBytes      int64
    72  	GitLimitBytes        int64
    73  	FailingServices      map[string]error
    74  	JournalManager       *JournalManagerStatus           `json:",omitempty"`
    75  	DiskBlockCacheStatus map[string]DiskBlockCacheStatus `json:",omitempty"`
    76  	DiskMDCacheStatus    DiskMDCacheStatus               `json:",omitempty"`
    77  	DiskQuotaCacheStatus DiskQuotaCacheStatus            `json:",omitempty"`
    78  }
    79  
    80  // StatusUpdate is a dummy type used to indicate status has been updated.
    81  type StatusUpdate struct{}
    82  
    83  // folderBranchStatusKeeper holds and updates the status for a given
    84  // folder-branch, and produces FolderBranchStatus instances suitable
    85  // for callers outside this package to consume.
    86  type folderBranchStatusKeeper struct {
    87  	config    Config
    88  	nodeCache NodeCache
    89  
    90  	dataMutex  sync.Mutex
    91  	md         ImmutableRootMetadata
    92  	permErr    error
    93  	dirtyNodes map[NodeID]Node
    94  	unmerged   []*crChainSummary
    95  	merged     []*crChainSummary
    96  
    97  	fboIDBytes []byte
    98  
    99  	updateChan  chan StatusUpdate
   100  	updateMutex sync.Mutex
   101  }
   102  
   103  func newFolderBranchStatusKeeper(
   104  	config Config, nodeCache NodeCache,
   105  	fboIDBytes []byte) *folderBranchStatusKeeper {
   106  	return &folderBranchStatusKeeper{
   107  		config:     config,
   108  		nodeCache:  nodeCache,
   109  		dirtyNodes: make(map[NodeID]Node),
   110  		updateChan: make(chan StatusUpdate, 1),
   111  		fboIDBytes: fboIDBytes,
   112  	}
   113  }
   114  
   115  // dataMutex should be taken by the caller
   116  func (fbsk *folderBranchStatusKeeper) signalChangeLocked() {
   117  	fbsk.updateMutex.Lock()
   118  	defer fbsk.updateMutex.Unlock()
   119  	close(fbsk.updateChan)
   120  	fbsk.updateChan = make(chan StatusUpdate, 1)
   121  }
   122  
   123  // setRootMetadata sets the current head metadata for the
   124  // corresponding folder-branch.
   125  func (fbsk *folderBranchStatusKeeper) setRootMetadata(md ImmutableRootMetadata) {
   126  	fbsk.dataMutex.Lock()
   127  	defer fbsk.dataMutex.Unlock()
   128  	if fbsk.md.MdID() == md.MdID() {
   129  		return
   130  	}
   131  	fbsk.md = md
   132  	fbsk.signalChangeLocked()
   133  }
   134  
   135  func (fbsk *folderBranchStatusKeeper) setCRSummary(unmerged []*crChainSummary,
   136  	merged []*crChainSummary) {
   137  	fbsk.dataMutex.Lock()
   138  	defer fbsk.dataMutex.Unlock()
   139  	if reflect.DeepEqual(unmerged, fbsk.unmerged) &&
   140  		reflect.DeepEqual(merged, fbsk.merged) {
   141  		return
   142  	}
   143  	fbsk.unmerged = unmerged
   144  	fbsk.merged = merged
   145  	fbsk.signalChangeLocked()
   146  }
   147  
   148  func (fbsk *folderBranchStatusKeeper) setPermErr(err error) {
   149  	fbsk.dataMutex.Lock()
   150  	defer fbsk.dataMutex.Unlock()
   151  	fbsk.permErr = err
   152  	fbsk.signalChangeLocked()
   153  }
   154  
   155  func (fbsk *folderBranchStatusKeeper) addNode(m map[NodeID]Node, n Node) bool {
   156  	fbsk.dataMutex.Lock()
   157  	defer fbsk.dataMutex.Unlock()
   158  	id := n.GetID()
   159  	_, ok := m[id]
   160  	if ok {
   161  		return false
   162  	}
   163  	m[id] = n
   164  	fbsk.signalChangeLocked()
   165  	return true
   166  }
   167  
   168  func (fbsk *folderBranchStatusKeeper) rmNode(m map[NodeID]Node, n Node) bool {
   169  	fbsk.dataMutex.Lock()
   170  	defer fbsk.dataMutex.Unlock()
   171  	id := n.GetID()
   172  	_, ok := m[id]
   173  	if !ok {
   174  		return false
   175  	}
   176  	delete(m, id)
   177  	fbsk.signalChangeLocked()
   178  	return true
   179  }
   180  
   181  func (fbsk *folderBranchStatusKeeper) addDirtyNode(n Node) bool {
   182  	return fbsk.addNode(fbsk.dirtyNodes, n)
   183  }
   184  
   185  func (fbsk *folderBranchStatusKeeper) rmDirtyNode(n Node) bool {
   186  	return fbsk.rmNode(fbsk.dirtyNodes, n)
   187  }
   188  
   189  // dataMutex should be taken by the caller
   190  func (fbsk *folderBranchStatusKeeper) convertNodesToPathsLocked(
   191  	m map[NodeID]Node) []string {
   192  	var ret []string
   193  	for _, n := range m {
   194  		ret = append(ret, fbsk.nodeCache.PathFromNode(n).String())
   195  	}
   196  	return ret
   197  }
   198  
   199  func (fbsk *folderBranchStatusKeeper) getStatusWithoutJournaling(
   200  	ctx context.Context) (
   201  	FolderBranchStatus, <-chan StatusUpdate, tlf.ID, error) {
   202  	fbsk.dataMutex.Lock()
   203  	defer fbsk.dataMutex.Unlock()
   204  	fbsk.updateMutex.Lock()
   205  	defer fbsk.updateMutex.Unlock()
   206  
   207  	var fbs FolderBranchStatus
   208  
   209  	tlfID := tlf.NullID
   210  	if fbsk.md != (ImmutableRootMetadata{}) {
   211  		tlfID = fbsk.md.TlfID()
   212  		fbs.Staged = fbsk.md.IsUnmergedSet()
   213  		fbs.BranchID = fbsk.md.BID().String()
   214  		name, err := fbsk.config.KBPKI().GetNormalizedUsername(
   215  			ctx, fbsk.md.LastModifyingWriter().AsUserOrTeam(),
   216  			fbsk.config.OfflineAvailabilityForID(tlfID))
   217  		if err != nil {
   218  			return FolderBranchStatus{}, nil, tlf.NullID, err
   219  		}
   220  		fbs.HeadWriter = name
   221  		fbs.DiskUsage = fbsk.md.DiskUsage()
   222  		fbs.RekeyPending = fbsk.config.RekeyQueue().IsRekeyPending(fbsk.md.TlfID())
   223  		fbs.LatestKeyGeneration = fbsk.md.LatestKeyGeneration()
   224  		fbs.FolderID = fbsk.md.TlfID().String()
   225  		fbs.Revision = fbsk.md.Revision()
   226  		fbs.LastGCRevision = fbsk.md.data.LastGCRevision
   227  		fbs.MDVersion = fbsk.md.Version()
   228  		fbs.SyncEnabled = fbsk.config.IsSyncedTlf(fbsk.md.TlfID())
   229  		prefetchStatus := fbsk.config.PrefetchStatus(ctx, fbsk.md.TlfID(),
   230  			fbsk.md.Data().Dir.BlockPointer)
   231  		fbs.PrefetchStatus = prefetchStatus.String()
   232  		fbs.RootBlockID = fbsk.md.Data().Dir.BlockPointer.ID.String()
   233  		fbs.LocalTimestamp = fbsk.md.localTimestamp
   234  
   235  		chargedTo, err := chargedToForTLF(
   236  			ctx, fbsk.config.KBPKI(), fbsk.config.KBPKI(),
   237  			fbsk.config, fbsk.md.GetTlfHandle())
   238  		if err != nil {
   239  			return FolderBranchStatus{}, nil, tlf.NullID, err
   240  		}
   241  		qu := fbsk.config.GetQuotaUsage(chargedTo)
   242  		_, usageBytes, archiveBytes, limitBytes,
   243  			gitUsageBytes, gitArchiveBytes, gitLimitBytes, quErr :=
   244  			qu.GetAllTypes(
   245  				ctx, quotaUsageStaleTolerance/2, quotaUsageStaleTolerance)
   246  		if quErr != nil {
   247  			// The error is ignored here so that other fields can
   248  			// still be populated even if this fails.
   249  			log := fbsk.config.MakeLogger("")
   250  			log.CDebugf(ctx, "Getting quota usage error: %v", quErr)
   251  		}
   252  		fbs.UsageBytes = usageBytes
   253  		fbs.ArchiveBytes = archiveBytes
   254  		fbs.LimitBytes = limitBytes
   255  		fbs.GitUsageBytes = gitUsageBytes
   256  		fbs.GitArchiveBytes = gitArchiveBytes
   257  		fbs.GitLimitBytes = gitLimitBytes
   258  	}
   259  
   260  	var crErr error
   261  	fbs.ConflictResolutionAttempts, crErr = getAndDeserializeConflicts(
   262  		fbsk.config, fbsk.config.GetConflictResolutionDB(), fbsk.fboIDBytes)
   263  	if crErr != nil {
   264  		// The error is ignored here so that other fields can
   265  		// still be populated even if this fails.
   266  		log := fbsk.config.MakeLogger("")
   267  		log.CDebugf(ctx, "Getting CR status error: %+v", crErr)
   268  	}
   269  	if isCRStuckFromRecords(fbs.ConflictResolutionAttempts) {
   270  		fbs.ConflictStatus = keybase1.FolderConflictType_IN_CONFLICT_AND_STUCK
   271  	} else if fbs.BranchID != kbfsmd.NullBranchID.String() {
   272  		fbs.ConflictStatus = keybase1.FolderConflictType_IN_CONFLICT
   273  	}
   274  
   275  	fbs.DirtyPaths = fbsk.convertNodesToPathsLocked(fbsk.dirtyNodes)
   276  
   277  	fbs.Unmerged = fbsk.unmerged
   278  	fbs.Merged = fbsk.merged
   279  
   280  	if fbsk.permErr != nil {
   281  		fbs.PermanentErr = fbsk.permErr.Error()
   282  	}
   283  
   284  	return fbs, fbsk.updateChan, tlfID, nil
   285  }
   286  
   287  // getStatus returns a FolderBranchStatus-representation of the
   288  // current status. If blocks != nil, the paths of any unflushed files
   289  // in the journals will be included in the status. The returned
   290  // channel is closed whenever the status changes, except for journal
   291  // status changes.
   292  func (fbsk *folderBranchStatusKeeper) getStatus(ctx context.Context,
   293  	blocks *folderBlockOps) (FolderBranchStatus, <-chan StatusUpdate, error) {
   294  	fbs, ch, tlfID, err := fbsk.getStatusWithoutJournaling(ctx)
   295  	if err != nil {
   296  		return FolderBranchStatus{}, nil, err
   297  	}
   298  	if tlfID == tlf.NullID {
   299  		return fbs, ch, nil
   300  	}
   301  
   302  	// Fetch journal info without holding any locks, to avoid possible
   303  	// deadlocks with folderBlockOps.
   304  
   305  	// TODO: Ideally, the journal would push status
   306  	// updates to this object instead, so we can notify
   307  	// listeners.
   308  	jManager, err := GetJournalManager(fbsk.config)
   309  	if err != nil {
   310  		return fbs, ch, nil
   311  	}
   312  
   313  	var jStatus TLFJournalStatus
   314  	if blocks != nil {
   315  		jStatus, err = jManager.JournalStatusWithPaths(ctx, tlfID, blocks)
   316  	} else {
   317  		jStatus, err = jManager.JournalStatus(tlfID)
   318  	}
   319  	if err != nil {
   320  		log := fbsk.config.MakeLogger("")
   321  		log.CWarningf(ctx, "Error getting journal status for %s: %v",
   322  			tlfID, err)
   323  	} else {
   324  		fbs.Journal = &jStatus
   325  	}
   326  	return fbs, ch, nil
   327  }