github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }