github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/kbfs_pin_tlfs.go (about) 1 package teams 2 3 import ( 4 "fmt" 5 "github.com/keybase/client/go/libkb" 6 "github.com/keybase/client/go/protocol/keybase1" 7 insecurerand "math/rand" 8 "time" 9 ) 10 11 func getUnpinnedTLF(m libkb.MetaContext) (res *unpinnedTLF, err error) { 12 13 arg := libkb.NewAPIArg("kbfs/unpinned") 14 arg.SessionType = libkb.APISessionTypeREQUIRED 15 arg.Args = libkb.HTTPArgs{ 16 "n": libkb.I{Val: 1}, 17 } 18 var r unpinnedTLFsRaw 19 err = m.G().API.GetDecode(m, arg, &r) 20 if err != nil { 21 return nil, err 22 } 23 if len(r.TLFs) == 0 { 24 return nil, nil 25 } 26 return &r.TLFs[0], nil 27 } 28 29 func (b *backgroundTLFPinner) pinTLF(m libkb.MetaContext, tlf unpinnedTLF) (err error) { 30 defer m.Trace(fmt.Sprintf("pinTLF(%+v)", tlf), &err)() 31 team, err := GetForTeamManagementByTeamID(m.Ctx(), m.G(), tlf.TeamID, false) 32 if err != nil { 33 return err 34 } 35 ids := team.KBFSTLFIDs() 36 if len(ids) > 0 { 37 m.Debug("Team %+v already has a TLF IDs in chain (%+v); ignoring", tlf.TeamID, ids) 38 return nil 39 } 40 role, err := team.myRole(m.Ctx()) 41 if err != nil { 42 return err 43 } 44 if !role.IsWriterOrAbove() { 45 return fmt.Errorf("permission denied: need writer access (or above)") 46 } 47 return team.AssociateWithTLFID(m.Ctx(), tlf.TlfID) 48 } 49 50 type unpinnedTLF struct { 51 Name string `json:"fq_name"` 52 TeamID keybase1.TeamID `json:"team_id"` 53 TlfID keybase1.TLFID `json:"tlf_id"` 54 } 55 56 type unpinnedTLFsRaw struct { 57 Status libkb.AppStatus `json:"status"` 58 TLFs []unpinnedTLF `json:"tlfs"` 59 } 60 61 func (r *unpinnedTLFsRaw) GetAppStatus() *libkb.AppStatus { 62 return &r.Status 63 } 64 65 func (b *backgroundTLFPinner) pinOneTLF(m libkb.MetaContext) (done bool, err error) { 66 m = m.WithLogTag("PIN") 67 defer m.Trace("pinOneTLF", &err)() 68 69 tlf, err := b.getUnpinnedTLF(m) 70 if err != nil { 71 return false, err 72 } 73 74 if tlf == nil { 75 m.Debug("work done, no TLFs need upgrade") 76 return true, nil 77 } 78 err = b.pinTLF(m, *tlf) 79 if err != nil { 80 return false, err 81 } 82 return false, nil 83 } 84 85 type pinLoopTimer interface { 86 StartupWait(m libkb.MetaContext) error 87 LoopWait(m libkb.MetaContext, lastRes error) error 88 } 89 90 type defaultPinLoopTimer struct{} 91 92 var _ pinLoopTimer = defaultPinLoopTimer{} 93 94 func (p defaultPinLoopTimer) wait(m libkb.MetaContext, why string, rangeMinutes int64, minMinutes int64) error { 95 dur := time.Duration(minMinutes)*time.Minute + time.Duration(insecurerand.Int63n(rangeMinutes*60))*time.Second 96 m.Debug("BackgroundPinLoop: waiting %v before %s", dur, why) 97 wakeAt := m.G().Clock().Now().Add(dur) 98 return libkb.SleepUntilWithContext(m.Ctx(), m.G().Clock(), wakeAt) 99 } 100 101 func (p defaultPinLoopTimer) StartupWait(m libkb.MetaContext) error { 102 // Wait up to 2 hours, but always wait 5 minutes first 103 return p.wait(m, "starting", 60*2, 5) 104 } 105 106 func (p defaultPinLoopTimer) LoopWait(m libkb.MetaContext, lastRes error) error { 107 // Wait at least 1 minute for the next, or 30 minutes if there was an error 108 // of any sort. Jiggle the wait for up to 3 minutes. 109 min := int64(1) 110 if lastRes != nil { 111 min = int64(30) 112 } 113 return p.wait(m, "next iteration", 3, min) 114 } 115 116 type backgroundTLFPinner struct { 117 timer pinLoopTimer 118 getUnpinnedTLF func(m libkb.MetaContext) (res *unpinnedTLF, err error) 119 exitCh chan<- error 120 } 121 122 func newBackgroundTLFPinner() *backgroundTLFPinner { 123 124 // We can override this members for the purposes of testing. 125 return &backgroundTLFPinner{ 126 timer: defaultPinLoopTimer{}, 127 getUnpinnedTLF: getUnpinnedTLF, 128 } 129 } 130 131 func (b *backgroundTLFPinner) run(m libkb.MetaContext) (err error) { 132 m = m.WithLogTag("PIN") 133 134 uv := m.CurrentUserVersion() 135 defer m.Trace(fmt.Sprintf("teams.BackgroundPinTLFLoop(%+v)", uv), &err)() 136 137 // For the purposes of testing, get make a note of when we are done. 138 defer func() { 139 if b.exitCh != nil { 140 b.exitCh <- err 141 } 142 }() 143 144 err = b.timer.StartupWait(m) 145 if err != nil { 146 return err 147 } 148 149 for { 150 uv2 := m.CurrentUserVersion() 151 if !uv.Eq(uv2) { 152 m.Debug("leaving loop since, we changed to new user version %+v (or logged out)", uv2) 153 return libkb.NewLoginRequiredError(fmt.Sprintf("required a login for user %+v", uv)) 154 } 155 done, tmp := b.pinOneTLF(m) 156 if done { 157 m.Debug("done with TLF BG pin operation") 158 return nil 159 } 160 if tmp != nil { 161 m.Warning("TLF Pin operation failed: %v", tmp) 162 } 163 err = b.timer.LoopWait(m, tmp) 164 if err != nil { 165 return err 166 } 167 } 168 } 169 170 func BackgroundPinTLFLoop(m libkb.MetaContext) { 171 _ = newBackgroundTLFPinner().run(m) 172 }