mirror of
https://github.com/ghostersk/gowebmail.git
synced 2026-04-17 16:46:01 +01:00
first commit
This commit is contained in:
188
internal/syncer/syncer.go
Normal file
188
internal/syncer/syncer.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// Package syncer provides background IMAP synchronisation for all active accounts.
|
||||
package syncer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/yourusername/gomail/internal/db"
|
||||
"github.com/yourusername/gomail/internal/email"
|
||||
"github.com/yourusername/gomail/internal/models"
|
||||
)
|
||||
|
||||
// Scheduler runs background sync for all active accounts according to their
|
||||
// individual sync_interval settings.
|
||||
type Scheduler struct {
|
||||
db *db.DB
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// New creates a new Scheduler. Call Start() to begin background syncing.
|
||||
func New(database *db.DB) *Scheduler {
|
||||
return &Scheduler{db: database, stop: make(chan struct{})}
|
||||
}
|
||||
|
||||
// Start launches the scheduler goroutine. Ticks every minute and checks
|
||||
// which accounts are due for sync based on last_sync and sync_interval.
|
||||
func (s *Scheduler) Start() {
|
||||
go func() {
|
||||
log.Println("Background sync scheduler started")
|
||||
s.runDue()
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.runDue()
|
||||
case <-s.stop:
|
||||
log.Println("Background sync scheduler stopped")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop signals the scheduler to exit.
|
||||
func (s *Scheduler) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
func (s *Scheduler) runDue() {
|
||||
accounts, err := s.db.ListAllActiveAccounts()
|
||||
if err != nil {
|
||||
log.Printf("Sync scheduler: list accounts: %v", err)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
for _, account := range accounts {
|
||||
if account.SyncInterval <= 0 {
|
||||
continue
|
||||
}
|
||||
nextSync := account.LastSync.Add(time.Duration(account.SyncInterval) * time.Minute)
|
||||
if account.LastSync.IsZero() || now.After(nextSync) {
|
||||
go s.syncAccount(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SyncAccountNow performs an immediate sync of one account. Returns messages synced.
|
||||
func (s *Scheduler) SyncAccountNow(accountID int64) (int, error) {
|
||||
account, err := s.db.GetAccount(accountID)
|
||||
if err != nil || account == nil {
|
||||
return 0, fmt.Errorf("account %d not found", accountID)
|
||||
}
|
||||
return s.doSync(account)
|
||||
}
|
||||
|
||||
// SyncFolderNow syncs a single folder for an account.
|
||||
func (s *Scheduler) SyncFolderNow(accountID, folderID int64) (int, error) {
|
||||
account, err := s.db.GetAccount(accountID)
|
||||
if err != nil || account == nil {
|
||||
return 0, fmt.Errorf("account %d not found", accountID)
|
||||
}
|
||||
folder, err := s.db.GetFolderByID(folderID)
|
||||
if err != nil || folder == nil || folder.AccountID != accountID {
|
||||
return 0, fmt.Errorf("folder %d not found", folderID)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
c, err := email.Connect(ctx, account)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer c.Close()
|
||||
days := account.SyncDays
|
||||
if days <= 0 || account.SyncMode == "all" {
|
||||
days = 36500 // ~100 years = full mailbox
|
||||
}
|
||||
messages, err := c.FetchMessages(folder.FullPath, days)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
synced := 0
|
||||
for _, msg := range messages {
|
||||
msg.FolderID = folder.ID
|
||||
if err := s.db.UpsertMessage(msg); err == nil {
|
||||
synced++
|
||||
}
|
||||
}
|
||||
s.db.UpdateFolderCounts(folder.ID)
|
||||
s.db.UpdateAccountLastSync(accountID)
|
||||
return synced, nil
|
||||
}
|
||||
|
||||
func (s *Scheduler) syncAccount(account *models.EmailAccount) {
|
||||
synced, err := s.doSync(account)
|
||||
if err != nil {
|
||||
log.Printf("Sync [%s]: %v", account.EmailAddress, err)
|
||||
s.db.SetAccountError(account.ID, err.Error())
|
||||
s.db.WriteAudit(nil, models.AuditAppError,
|
||||
"sync error for "+account.EmailAddress+": "+err.Error(), "", "")
|
||||
return
|
||||
}
|
||||
s.db.ClearAccountError(account.ID)
|
||||
if synced > 0 {
|
||||
log.Printf("Synced %d messages for %s", synced, account.EmailAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scheduler) doSync(account *models.EmailAccount) (int, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
c, err := email.Connect(ctx, account)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
mailboxes, err := c.ListMailboxes()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("list mailboxes: %w", err)
|
||||
}
|
||||
|
||||
synced := 0
|
||||
for _, mb := range mailboxes {
|
||||
folderType := email.InferFolderType(mb.Name, mb.Attributes)
|
||||
|
||||
folder := &models.Folder{
|
||||
AccountID: account.ID,
|
||||
Name: mb.Name,
|
||||
FullPath: mb.Name,
|
||||
FolderType: folderType,
|
||||
}
|
||||
if err := s.db.UpsertFolder(folder); err != nil {
|
||||
log.Printf("Upsert folder %s: %v", mb.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
dbFolder, _ := s.db.GetFolderByPath(account.ID, mb.Name)
|
||||
if dbFolder == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
days := account.SyncDays
|
||||
if days <= 0 || account.SyncMode == "all" {
|
||||
days = 36500 // ~100 years = full mailbox
|
||||
}
|
||||
messages, err := c.FetchMessages(mb.Name, days)
|
||||
if err != nil {
|
||||
log.Printf("Fetch %s/%s: %v", account.EmailAddress, mb.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, msg := range messages {
|
||||
msg.FolderID = dbFolder.ID
|
||||
if err := s.db.UpsertMessage(msg); err == nil {
|
||||
synced++
|
||||
}
|
||||
}
|
||||
|
||||
s.db.UpdateFolderCounts(dbFolder.ID)
|
||||
}
|
||||
|
||||
s.db.UpdateAccountLastSync(account.ID)
|
||||
return synced, nil
|
||||
}
|
||||
Reference in New Issue
Block a user