github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/appstate.go (about)

     1  package libkb
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/keybase/client/go/protocol/keybase1"
     9  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    10  )
    11  
    12  // MobileAppState tracks the state of foreground/background status of the app
    13  // in which the service is running in.
    14  type MobileAppState struct {
    15  	Contextified
    16  	sync.Mutex
    17  	state     keybase1.MobileAppState
    18  	updateChs []chan keybase1.MobileAppState
    19  
    20  	// mtime is the time at which the appstate first switched to the current state.
    21  	// It is a monotonic timestamp and should only be used relatively.
    22  	mtime *time.Time
    23  }
    24  
    25  func NewMobileAppState(g *GlobalContext) *MobileAppState {
    26  	return &MobileAppState{
    27  		Contextified: NewContextified(g),
    28  		state:        keybase1.MobileAppState_FOREGROUND,
    29  		mtime:        nil,
    30  	}
    31  }
    32  
    33  // NextUpdate returns a channel that triggers when the app state changes
    34  func (a *MobileAppState) NextUpdate(lastState *keybase1.MobileAppState) chan keybase1.MobileAppState {
    35  	a.Lock()
    36  	defer a.Unlock()
    37  	ch := make(chan keybase1.MobileAppState, 1)
    38  	if lastState != nil && *lastState != a.state {
    39  		ch <- a.state
    40  	} else {
    41  		a.updateChs = append(a.updateChs, ch)
    42  	}
    43  	return ch
    44  }
    45  
    46  func (a *MobileAppState) updateLocked(state keybase1.MobileAppState) {
    47  	if a.state != state {
    48  		a.G().Log.Debug("MobileAppState.Update: useful update: %v, we are currently in state: %v",
    49  			state, a.state)
    50  		a.G().PerfLog.Debug("MobileAppState.Update: useful update: %v, we are currently in state: %v",
    51  			state, a.state)
    52  		a.state = state
    53  		t := time.Now()
    54  		a.mtime = &t // only update mtime if we're changing state
    55  		for _, ch := range a.updateChs {
    56  			ch <- state
    57  		}
    58  		a.updateChs = nil
    59  
    60  		// cancel RPCs if we go into the background
    61  		switch a.state {
    62  		case keybase1.MobileAppState_BACKGROUND:
    63  			a.G().RPCCanceler.CancelLiveContexts(RPCCancelerReasonBackground)
    64  		default:
    65  			// Nothing to do for other states.
    66  		}
    67  	} else {
    68  		a.G().Log.Debug("MobileAppState.Update: ignoring update: %v, we are currently in state: %v",
    69  			state, a.state)
    70  	}
    71  }
    72  
    73  func (a *MobileAppState) UpdateWithCheck(state keybase1.MobileAppState,
    74  	check func(keybase1.MobileAppState) bool) {
    75  	defer a.G().Trace(fmt.Sprintf("MobileAppState.UpdateWithCheck(%v)", state), nil)()
    76  	a.Lock()
    77  	defer a.Unlock()
    78  	if check(a.state) {
    79  		a.updateLocked(state)
    80  	} else {
    81  		a.G().Log.Debug("MobileAppState.UpdateWithCheck: skipping update, failed check")
    82  	}
    83  }
    84  
    85  // Update updates the current app state, and notifies any waiting calls from NextUpdate
    86  func (a *MobileAppState) Update(state keybase1.MobileAppState) {
    87  	defer a.G().Trace(fmt.Sprintf("MobileAppState.Update(%v)", state), nil)()
    88  	a.Lock()
    89  	defer a.Unlock()
    90  	a.updateLocked(state)
    91  }
    92  
    93  // State returns the current app state
    94  func (a *MobileAppState) State() keybase1.MobileAppState {
    95  	a.Lock()
    96  	defer a.Unlock()
    97  	return a.state
    98  }
    99  
   100  func (a *MobileAppState) StateAndMtime() (keybase1.MobileAppState, *time.Time) {
   101  	a.Lock()
   102  	defer a.Unlock()
   103  	return a.state, a.mtime
   104  }
   105  
   106  // --------------------------------------------------
   107  
   108  // MobileNetState tracks the state of the network status of the app in which
   109  // the service is running in.
   110  type MobileNetState struct {
   111  	Contextified
   112  	sync.Mutex
   113  	state     keybase1.MobileNetworkState
   114  	updateChs []chan keybase1.MobileNetworkState
   115  }
   116  
   117  func NewMobileNetState(g *GlobalContext) *MobileNetState {
   118  	return &MobileNetState{
   119  		Contextified: NewContextified(g),
   120  		state:        keybase1.MobileNetworkState_NOTAVAILABLE,
   121  	}
   122  }
   123  
   124  // NextUpdate returns a channel that triggers when the network state changes
   125  func (a *MobileNetState) NextUpdate(lastState *keybase1.MobileNetworkState) chan keybase1.MobileNetworkState {
   126  	a.Lock()
   127  	defer a.Unlock()
   128  	ch := make(chan keybase1.MobileNetworkState, 1)
   129  	if lastState != nil && *lastState != a.state {
   130  		ch <- a.state
   131  	} else {
   132  		a.updateChs = append(a.updateChs, ch)
   133  	}
   134  	return ch
   135  }
   136  
   137  // Update updates the current network state, and notifies any waiting calls
   138  // from NextUpdate
   139  func (a *MobileNetState) Update(state keybase1.MobileNetworkState) {
   140  	defer a.G().Trace(fmt.Sprintf("MobileNetState.Update(%v)", state), nil)()
   141  	a.Lock()
   142  	defer a.Unlock()
   143  	if a.state != state {
   144  		a.G().Log.Debug("MobileNetState.Update: useful update: %v, we are currently in state: %v",
   145  			state, a.state)
   146  		a.state = state
   147  		for _, ch := range a.updateChs {
   148  			ch <- state
   149  		}
   150  		a.updateChs = nil
   151  	} else {
   152  		a.G().Log.Debug("MobileNetState.Update: ignoring update: %v, we are currently in state: %v",
   153  			state, a.state)
   154  	}
   155  }
   156  
   157  // State returns the current network state
   158  func (a *MobileNetState) State() keybase1.MobileNetworkState {
   159  	a.Lock()
   160  	defer a.Unlock()
   161  	return a.state
   162  }
   163  
   164  // --------------------------------------------------
   165  
   166  type DesktopAppState struct {
   167  	Contextified
   168  	sync.Mutex
   169  	provider         rpc.Transporter
   170  	suspended        bool
   171  	locked           bool
   172  	updateSuspendChs []chan bool
   173  }
   174  
   175  func NewDesktopAppState(g *GlobalContext) *DesktopAppState {
   176  	d := &DesktopAppState{Contextified: NewContextified(g)}
   177  	g.PushShutdownHook(func(mctx MetaContext) error {
   178  		d.Lock()
   179  		defer d.Unlock()
   180  		// reset power state on shutdown
   181  		d.resetLocked()
   182  		return nil
   183  	})
   184  	return d
   185  }
   186  
   187  func (a *DesktopAppState) NextSuspendUpdate(lastState *bool) chan bool {
   188  	a.Lock()
   189  	defer a.Unlock()
   190  	ch := make(chan bool, 1)
   191  	if lastState != nil && *lastState != a.suspended {
   192  		ch <- a.suspended
   193  	} else {
   194  		a.updateSuspendChs = append(a.updateSuspendChs, ch)
   195  	}
   196  	return ch
   197  }
   198  
   199  // event from power monitor
   200  // https://electronjs.org/docs/api/power-monitor
   201  func (a *DesktopAppState) Update(mctx MetaContext, event string, provider rpc.Transporter) {
   202  	mctx.Debug("DesktopAppState.Update(%v)", event)
   203  	a.Lock()
   204  	defer a.Unlock()
   205  	a.provider = provider
   206  	switch event {
   207  	case "suspend":
   208  		a.suspended = true
   209  	case "resume":
   210  		a.suspended = false
   211  	case "shutdown":
   212  	case "lock-screen":
   213  		a.locked = true
   214  	case "unlock-screen":
   215  		a.suspended = false
   216  		a.locked = false
   217  	}
   218  	for _, ch := range a.updateSuspendChs {
   219  		ch <- a.suspended
   220  	}
   221  	a.updateSuspendChs = nil
   222  }
   223  
   224  func (a *DesktopAppState) Disconnected(provider rpc.Transporter) {
   225  	a.Lock()
   226  	defer a.Unlock()
   227  	theProvider := provider == a.provider
   228  	a.G().Log.Debug("DesktopAppState.Disconnected(%v)", theProvider)
   229  	if theProvider {
   230  		a.provider = nil
   231  		// The connection to electron has been severed. We won't get any more power
   232  		// status updates from it. So act as though the machine is on in the default state.
   233  		a.resetLocked()
   234  	}
   235  }
   236  
   237  func (a *DesktopAppState) AwakeAndUnlocked(mctx MetaContext) bool {
   238  	a.Lock()
   239  	defer a.Unlock()
   240  	return !a.suspended && !a.locked
   241  }
   242  
   243  func (a *DesktopAppState) resetLocked() {
   244  	a.suspended = false
   245  	a.locked = false
   246  }