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 }