@@ -6,11 +6,14 @@ import (
66 "log"
77 "os"
88 "os/exec"
9+ "sync"
910 "time"
1011
1112 "github.com/floatpane/matcha/backend"
13+ _ "github.com/floatpane/matcha/backend/imap" // register imap backend for directService
1214 _ "github.com/floatpane/matcha/backend/jmap" // register jmap backend for directService
1315 _ "github.com/floatpane/matcha/backend/maildir" // register maildir backend for directService
16+ _ "github.com/floatpane/matcha/backend/pop3" // register pop3 backend for directService
1417 "github.com/floatpane/matcha/config"
1518 "github.com/floatpane/matcha/daemonrpc"
1619 "github.com/floatpane/matcha/fetcher"
@@ -264,6 +267,7 @@ type directService struct {
264267 cfg * config.Config
265268 providers map [string ]backend.Provider
266269 events chan * daemonrpc.Event
270+ mu sync.RWMutex
267271}
268272
269273func newDirectService (cfg * config.Config ) * directService {
@@ -277,6 +281,9 @@ func newDirectService(cfg *config.Config) *directService {
277281}
278282
279283func (s * directService ) initProviders () {
284+ s .mu .Lock ()
285+ defer s .mu .Unlock ()
286+
280287 for i := range s .cfg .Accounts {
281288 acct := & s .cfg .Accounts [i ]
282289 if _ , ok := s .providers [acct .ID ]; ok {
@@ -291,11 +298,47 @@ func (s *directService) initProviders() {
291298 }
292299}
293300
301+ // getProvider returns the provider for an account, creating it on demand.
302+ // A provider missing from the map (account added after the service was built,
303+ // or a constructor that failed once at startup) used to fail every later
304+ // operation with "no provider for account <id>".
294305func (s * directService ) getProvider (accountID string ) (backend.Provider , error ) {
306+ s .mu .RLock ()
295307 p , ok := s .providers [accountID ]
296- if ! ok {
297- return nil , & daemonrpc.Error {Code : daemonrpc .ErrCodeInternal , Message : "no provider for account " + accountID }
308+ acct := s .cfg .GetAccountByID (accountID )
309+ s .mu .RUnlock ()
310+ if ok {
311+ return p , nil
312+ }
313+
314+ if acct == nil {
315+ // Config in memory may be stale — reload from disk before giving up.
316+ if err := s .ReloadConfig (); err != nil {
317+ return nil , & daemonrpc.Error {Code : daemonrpc .ErrCodeInternal , Message : "no provider for account " + accountID + ": " + err .Error ()}
318+ }
319+ s .mu .RLock ()
320+ p , ok = s .providers [accountID ]
321+ s .mu .RUnlock ()
322+ if ok {
323+ return p , nil
324+ }
325+ return nil , & daemonrpc.Error {Code : daemonrpc .ErrCodeInternal , Message : "no provider for account " + accountID + ": account not found in config" }
326+ }
327+
328+ s .mu .Lock ()
329+ defer s .mu .Unlock ()
330+ if p , ok := s .providers [accountID ]; ok {
331+ return p , nil
298332 }
333+ acct = s .cfg .GetAccountByID (accountID )
334+ if acct == nil {
335+ return nil , & daemonrpc.Error {Code : daemonrpc .ErrCodeInternal , Message : "no provider for account " + accountID + ": account not found in config" }
336+ }
337+ p , err := backend .New (acct )
338+ if err != nil {
339+ return nil , & daemonrpc.Error {Code : daemonrpc .ErrCodeInternal , Message : "create provider for " + acct .Email + ": " + err .Error ()}
340+ }
341+ s .providers [accountID ] = p
299342 return p , nil
300343}
301344
@@ -392,7 +435,19 @@ func (s *directService) ReloadConfig() error {
392435 if err != nil {
393436 return err
394437 }
438+
439+ // Providers point into the old Accounts slice; drop them so the reloaded
440+ // credentials (keyring / pass_cmd) actually take effect.
441+ s .mu .Lock ()
395442 s .cfg = cfg
443+ old := s .providers
444+ s .providers = make (map [string ]backend.Provider , len (cfg .Accounts ))
445+ s .mu .Unlock ()
446+
447+ for _ , p := range old {
448+ p .Close () //nolint:errcheck,gosec
449+ }
450+
396451 s .initProviders ()
397452 return nil
398453}
@@ -404,6 +459,8 @@ func (s *directService) Events() <-chan *daemonrpc.Event {
404459func (s * directService ) IsDaemon () bool { return false }
405460
406461func (s * directService ) Close () error {
462+ s .mu .Lock ()
463+ defer s .mu .Unlock ()
407464 for _ , p := range s .providers {
408465 p .Close () //nolint:errcheck,gosec
409466 }
@@ -412,7 +469,9 @@ func (s *directService) Close() error {
412469}
413470
414471func (s * directService ) QueueEmail (accountID string , to , cc , bcc []string , subject , body , htmlBody string , images map [string ][]byte , attachments map [string ][]byte , inReplyTo string , references []string , signSMIME , encryptSMIME , signPGP , encryptPGP bool , _ int ) (string , error ) {
472+ s .mu .RLock ()
415473 acct := s .cfg .GetAccountByID (accountID )
474+ s .mu .RUnlock ()
416475 if acct == nil {
417476 return "" , fmt .Errorf ("no account for %s" , accountID )
418477 }
0 commit comments