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 }