]> git.giacomolli.uk - onechat/blobdiff - whatsapp.go
Name timeouts for the operations they limit
[onechat] / whatsapp.go
index 851cfd1c348053a8dba203afc24a20e7e4a55ec3..97b1e857d50cebcf45a59f1d08251cfd76bbcb76 100644 (file)
@@ -54,17 +54,17 @@ const (
        // whatsappTimeLayout is the layout of the times in the answers.
        whatsappTimeLayout = "2006-01-02 15:04"
 
-       // whatsappPairTime is the longest time of a pairing. The library closes
-       // the pairing connection after about 160 seconds.
-       whatsappPairTime = 3 * time.Minute
+       // whatsappPairTimeout is the longest time of a pairing. The library
+       // closes the pairing connection after about 160 seconds.
+       whatsappPairTimeout = 3 * time.Minute
 
-       // whatsappLogoutTime is the longest time of the logout call of an
+       // whatsappLogoutTimeout is the longest time of the logout call of an
        // unlink.
-       whatsappLogoutTime = 30 * time.Second
+       whatsappLogoutTimeout = 30 * time.Second
 
-       // whatsappNoticeDelay is how long a disconnection lasts before the user
-       // gets a notice.
-       whatsappNoticeDelay = 10 * time.Minute
+       // whatsappOutageNoticeDelay is the delay from a disconnection to its
+       // notice to the user.
+       whatsappOutageNoticeDelay = 10 * time.Minute
 
        // whatsappRetryDelay is the first wait after a failed starting
        // connection.
@@ -77,10 +77,6 @@ const (
        // wait for Run.
        whatsappRequests = 8
 
-       // whatsappLookupTime is the longest time of a read of the names in the
-       // session database or of the groups from WhatsApp.
-       whatsappLookupTime = 10 * time.Second
-
        // whatsappNotLinked is the answer of a WhatsApp tool without a link.
        whatsappNotLinked = "WhatsApp is not linked. Tell the user to run /whatsapp pair PHONE in the terminal or in Telegram."
 )
@@ -95,6 +91,7 @@ type WhatsAppClient interface {
        PairPhone(ctx context.Context, phone string, showPushNotification bool, clientType whatsmeow.PairClientType, clientDisplayName string) (string, error)
        Logout(ctx context.Context) error
        GetJoinedGroups(ctx context.Context) ([]*types.GroupInfo, error)
+       Download(ctx context.Context, message whatsmeow.DownloadableMessage) ([]byte, error)
        GenerateMessageID() types.MessageID
        SendMessage(ctx context.Context, to types.JID, message *waE2E.Message, extra ...whatsmeow.SendRequestExtra) (whatsmeow.SendResponse, error)
 }
@@ -106,24 +103,24 @@ type WhatsAppClient interface {
 // the client of a device with its event handler.
 func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logger *slog.Logger, newClient func(device *store.Device, handle func(event any) bool) WhatsAppClient) (*WhatsApp, error) {
        w := &WhatsApp{
-               dir:         filepath.Join(dir, whatsappDirName),
-               clock:       clock,
-               logger:      logger,
-               newClient:   newClient,
-               noticeDelay: whatsappNoticeDelay,
-               requests:    make(chan WhatsAppRequest, whatsappRequests),
-               logouts:     make(chan struct{}, 1),
-               groups:      newWhatsAppGroups(logger),
+               ctx:       ctx,
+               dir:       filepath.Join(dir, whatsappDirName),
+               clock:     clock,
+               logger:    logger,
+               newClient: newClient,
+               requests:  make(chan WhatsAppRequest, whatsappRequests),
+               logouts:   make(chan struct{}, 1),
+               groups:    newWhatsAppGroups(logger),
        }
 
        var err error
 
-       w.state, err = openWhatsAppState(filepath.Join(w.dir, whatsappStateFileName), whatsappBatchQuiet, whatsappBatchWindow)
+       w.state, err = openWhatsAppState(filepath.Join(w.dir, whatsappStateFileName))
        if err != nil {
                return nil, err
        }
 
-       w.inbox = newWhatsAppInbox(w.state, clock, w.groups)
+       w.inbox = newWhatsAppInbox(w.state, clock, w.groups, &ImageStore{dir: filepath.Join(dir, imageDirName)}, logger)
 
        info, err := os.Stat(w.sessionPath())
        if errors.Is(err, os.ErrNotExist) {
@@ -168,9 +165,12 @@ func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logge
 // creation, close, removal, and snapshots. It is held before mutex, and stays
 // held when removal releases mutex for client callbacks.
 //
-// The inbox puts the text messages into batches and logs them, and groups
-// holds the names of the groups. Both get a WhatsAppLink from link for each
-// call, so they use the current device and client.
+// ctx is the caller context until Run sets its context for library callbacks.
+// The mutex protects it, so callbacks follow daemon cancellation.
+//
+// The inbox puts messages into batches and logs them, and groups holds the
+// names of the groups. Both get a WhatsAppLink from link for each call, so they
+// use the current device and client.
 //
 // The library logs nothing, because its log lines hold the ID of the own
 // account, which is the phone number of the user. The own number and the own
@@ -180,17 +180,17 @@ func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logge
 // log, and those of private chats hold phone numbers, because the agent needs
 // them to send.
 type WhatsApp struct {
-       dir         string
-       clock       func() time.Time
-       logger      *slog.Logger
-       newClient   func(device *store.Device, handle func(event any) bool) WhatsAppClient
-       noticeDelay time.Duration
-       requests    chan WhatsAppRequest
-       logouts     chan struct{}
-       state       *WhatsAppState
-       groups      *WhatsAppGroups
-       inbox       *WhatsAppInbox
-       routes      *Routes
+       ctx       context.Context
+       dir       string
+       clock     func() time.Time
+       logger    *slog.Logger
+       newClient func(device *store.Device, handle func(event any) bool) WhatsAppClient
+       requests  chan WhatsAppRequest
+       logouts   chan struct{}
+       state     *WhatsAppState
+       groups    *WhatsAppGroups
+       inbox     *WhatsAppInbox
+       routes    *Routes
 
        sessionMutex   sync.Mutex
        mutex          sync.Mutex
@@ -223,6 +223,7 @@ type WhatsApp struct {
 func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) error {
        w.mutex.Lock()
 
+       w.ctx = ctx
        w.notify = notify
        linked := w.linked
        w.retrying = linked
@@ -308,7 +309,7 @@ func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) err
                        case w.isLinked():
                                request.answer("WhatsApp is linked already. Run /whatsapp unlink first.")
                        default:
-                               pairCtx, cancelPair := context.WithTimeout(ctx, whatsappPairTime)
+                               pairCtx, cancelPair := context.WithTimeout(ctx, whatsappPairTimeout)
                                cancel = cancelPair
                                pairing.Go(func() {
                                        defer cancelPair()
@@ -568,7 +569,7 @@ func (w *WhatsApp) startOutage() bool {
                w.disconnectedAt = w.clock()
                w.disconnections++
                disconnections := w.disconnections
-               w.timer = time.AfterFunc(w.noticeDelay, func() {
+               w.timer = time.AfterFunc(whatsappOutageNoticeDelay, func() {
                        w.disconnectedLong(disconnections)
                })
        }
@@ -653,7 +654,7 @@ func (w *WhatsApp) unlink(ctx context.Context, l *TurnLoop, answer func(text str
        var logoutErr error
 
        if client != nil {
-               ctx, cancel := context.WithTimeout(ctx, whatsappLogoutTime)
+               ctx, cancel := context.WithTimeout(ctx, whatsappLogoutTimeout)
                defer cancel()
 
                logoutErr = client.Logout(ctx)
@@ -889,19 +890,25 @@ func (w *WhatsApp) linkedBy(client WhatsAppClient) {
 // handle takes one event of the library and tells if it was handled. A message
 // goes to the inbox. The names of the groups follow the events of the groups. A
 // connection starts a catch-up of the inbox, which ends at OfflineSyncCompleted
-// or after its catchupLimit. A disconnection keeps the time of its start while
-// Run or the library tries again. After noticeDelay of a disconnection, all
-// clients get one notice, and they get one more when the connection comes back.
-// A disconnection that the library does not try again gives a notice at once.
+// or after whatsappCatchupTimeout. A disconnection keeps the time of its start
+// while Run or the library tries again. After whatsappOutageNoticeDelay of a
+// disconnection, all clients get one notice, and they get one more when the
+// connection comes back. A disconnection that the library does not try again
+// gives a notice at once.
 //
 // The log of the daemon gets one line for each change of the connection of the
 // linked device: connected, a failed first connection, a lost connection, and
 // connected again with the length of the outage. The retries of the library and
 // Run between them log nothing.
 func (w *WhatsApp) handle(event any) bool {
+       w.mutex.Lock()
+
+       ctx := w.ctx
+       w.mutex.Unlock()
+
        switch event := event.(type) {
        case *events.Message:
-               return w.inbox.receive(w.link(), event)
+               return w.inbox.receive(ctx, w.link(), event)
        case *events.GroupInfo:
                if event.Name != nil {
                        w.groups.set(event.JID.String(), event.Name.Name)
@@ -986,7 +993,7 @@ func (w *WhatsApp) handle(event any) bool {
        w.mutex.Unlock()
 
        if line != "" {
-               w.logger.Log(context.Background(), level, line, attrs...)
+               w.logger.Log(ctx, level, line, attrs...)
        }
 
        if notice != "" {