github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/sourceofflinable.go (about)

     1  package chat
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/keybase/client/go/chat/globals"
     8  	"github.com/keybase/client/go/protocol/keybase1"
     9  
    10  	"github.com/keybase/client/go/chat/types"
    11  	"github.com/keybase/client/go/chat/utils"
    12  	"golang.org/x/net/context"
    13  )
    14  
    15  // sourceOfflinable implements the chat/types.Offlinable interface.
    16  // It is meant to be embedded in inbox and conversation sources.
    17  // It's main purpose is that IsOffline() will wait for 4s to see if any
    18  // in progress connections succeed before returning.
    19  type sourceOfflinable struct {
    20  	globals.Contextified
    21  	utils.DebugLabeler
    22  	offline, delayed bool
    23  	connected        chan bool
    24  	sync.Mutex
    25  }
    26  
    27  var _ types.Offlinable = (*sourceOfflinable)(nil)
    28  
    29  func newSourceOfflinable(g *globals.Context, labeler utils.DebugLabeler) *sourceOfflinable {
    30  	return &sourceOfflinable{
    31  		Contextified: globals.NewContextified(g),
    32  		DebugLabeler: labeler,
    33  		connected:    makeConnectedChan(),
    34  	}
    35  }
    36  
    37  func (s *sourceOfflinable) Connected(ctx context.Context) {
    38  	defer s.Trace(ctx, nil, "Connected")()
    39  	s.Lock()
    40  	defer s.Unlock()
    41  	s.Debug(ctx, "connected: offline to false")
    42  	s.offline = false
    43  	s.connected <- true
    44  }
    45  
    46  func (s *sourceOfflinable) Disconnected(ctx context.Context) {
    47  	defer s.Trace(ctx, nil, "Disconnected")()
    48  	s.Lock()
    49  	defer s.Unlock()
    50  	if s.offline {
    51  		s.Debug(ctx, "already disconnected, ignoring disconnected callback")
    52  		return
    53  	}
    54  	s.Debug(ctx, "disconnected: offline to true")
    55  	s.offline = true
    56  	s.delayed = false
    57  	close(s.connected)
    58  	s.connected = makeConnectedChan()
    59  }
    60  
    61  func (s *sourceOfflinable) IsOffline(ctx context.Context) bool {
    62  	s.Lock()
    63  	offline := s.offline
    64  	connected := s.connected
    65  	delayed := s.delayed
    66  	s.Unlock()
    67  
    68  	if offline {
    69  		if delayed {
    70  			s.Debug(ctx, "IsOffline: offline, but skipping delay since we already did it")
    71  			return offline
    72  		}
    73  		if s.G().MobileAppState.State() != keybase1.MobileAppState_FOREGROUND {
    74  			s.Debug(ctx, "IsOffline: offline, but not waiting for anything since not in foreground")
    75  			return offline
    76  		}
    77  		timeoutCh := time.After(5 * time.Second)
    78  		for {
    79  			select {
    80  			case <-connected:
    81  				s.Debug(ctx, "IsOffline: waited and got %v", s.offline)
    82  				s.Lock()
    83  				if s.offline {
    84  					connected = s.connected
    85  					s.Unlock()
    86  					s.Debug(ctx, "IsOffline: since we got word of being offline, we will keep waiting")
    87  					continue
    88  				}
    89  				defer s.Unlock()
    90  				return s.offline
    91  			case <-ctx.Done():
    92  				s.Lock()
    93  				defer s.Unlock()
    94  				s.Debug(ctx, "IsOffline: aborted: %s state: %v", ctx.Err(), s.offline)
    95  				return s.offline
    96  			case <-timeoutCh:
    97  				s.Lock()
    98  				defer s.Unlock()
    99  				select {
   100  				case <-ctx.Done():
   101  					s.Debug(ctx, "IsOffline: timed out, but context canceled so not setting delayed: state: %v",
   102  						s.offline)
   103  					return s.offline
   104  				default:
   105  				}
   106  				s.delayed = true
   107  				s.Debug(ctx, "IsOffline: timed out, setting delay wait: state: %v", s.offline)
   108  				return s.offline
   109  			}
   110  		}
   111  	}
   112  	return offline
   113  }
   114  
   115  // makeConnectedChan creates a buffered channel for Connected to signal that
   116  // a connection happened.  The buffer size is 10 just to be extra-safe that
   117  // a send on the channel won't block during its lifetime (a buffer size of
   118  // 1 should be all that is required).
   119  func makeConnectedChan() chan bool {
   120  	return make(chan bool, 10)
   121  
   122  }