diff --git a/main.go b/main.go index 3ecafcec34f1b8675c57d35c2bee12316811394d..b9494921859eaaae223f379eb3606298df0b50c7 100644 --- a/main.go +++ b/main.go @@ -832,9 +832,27 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo if account == nil { return m, nil } - m.previousModel = m.current - m.current = tui.NewStatus("Moving email...") - return m, tea.Batch(m.current.Init(), moveEmailToFolderCmd(account, msg.UID, msg.AccountID, msg.SourceFolder, msg.DestFolder)) + + folderName := folderInbox + if m.folderInbox != nil { + folderName = m.folderInbox.GetCurrentFolder() + m.folderInbox.GetInbox().RemoveEmail(msg.UID, msg.AccountID) + } + + m.removeEmailFromStores(msg.UID, msg.AccountID) + + if emails, ok := m.folderEmails[folderName]; ok { + var filtered []fetcher.Email + for _, e := range emails { + if e.UID != msg.UID || e.AccountID != msg.AccountID { + filtered = append(filtered, e) + } + } + m.folderEmails[folderName] = filtered + go saveFolderEmailsToCache(folderName, filtered) + } + + return m, m.moveEmailToFolderCmd(msg.UID, msg.AccountID, msg.SourceFolder, msg.DestFolder) case tui.UpdatePreviewMsg: // Trigger preview body fetch @@ -921,11 +939,6 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo return tui.RestoreViewMsg{} }) } - // Remove email from current view - if m.folderInbox != nil { - m.folderInbox.RemoveEmail(msg.UID, msg.AccountID) - m.current = m.folderInbox - } return m, nil case tui.CachedEmailsLoadedMsg: @@ -1752,8 +1765,6 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo case tui.DeleteEmailMsg: tui.ClearKittyGraphics() - m.previousModel = m.current - m.current = tui.NewStatus("Deleting email...") account := m.config.GetAccountByID(msg.AccountID) if account == nil { @@ -1765,14 +1776,28 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo folderName := folderInbox if m.folderInbox != nil { + m.current = m.folderInbox folderName = m.folderInbox.GetCurrentFolder() + m.folderInbox.GetInbox().RemoveEmail(msg.UID, msg.AccountID) + } + + m.removeEmailFromStores(msg.UID, msg.AccountID) + + if emails, ok := m.folderEmails[folderName]; ok { + var filtered []fetcher.Email + for _, e := range emails { + if e.UID != msg.UID || e.AccountID != msg.AccountID { + filtered = append(filtered, e) + } + } + m.folderEmails[folderName] = filtered + go saveFolderEmailsToCache(folderName, filtered) } - return m, tea.Batch(m.current.Init(), deleteFolderEmailCmd(account, msg.UID, msg.AccountID, folderName, msg.Mailbox)) + + return m, m.deleteFolderEmailCmd(msg.UID, msg.AccountID, folderName, msg.Mailbox) case tui.ArchiveEmailMsg: tui.ClearKittyGraphics() - m.previousModel = m.current - m.current = tui.NewStatus("Archiving email...") account := m.config.GetAccountByID(msg.AccountID) if account == nil { @@ -1784,9 +1809,25 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo folderName := folderInbox if m.folderInbox != nil { + m.current = m.folderInbox folderName = m.folderInbox.GetCurrentFolder() + m.folderInbox.GetInbox().RemoveEmail(msg.UID, msg.AccountID) } - return m, tea.Batch(m.current.Init(), archiveFolderEmailCmd(account, msg.UID, msg.AccountID, folderName, msg.Mailbox)) + + m.removeEmailFromStores(msg.UID, msg.AccountID) + + if emails, ok := m.folderEmails[folderName]; ok { + var filtered []fetcher.Email + for _, e := range emails { + if e.UID != msg.UID || e.AccountID != msg.AccountID { + filtered = append(filtered, e) + } + } + m.folderEmails[folderName] = filtered + go saveFolderEmailsToCache(folderName, filtered) + } + + return m, m.archiveFolderEmailCmd(msg.UID, msg.AccountID, folderName, msg.Mailbox) case tui.EmailMarkedReadMsg: if msg.Err != nil { @@ -1814,24 +1855,10 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo }) } - // Remove email from stores - m.removeEmailFromStores(msg.UID, msg.AccountID) - - if m.folderInbox != nil { - m.folderInbox.RemoveEmail(msg.UID, msg.AccountID) - m.current = m.folderInbox - m.current, _ = m.current.Update(m.currentWindowSize()) - return m, m.current.Init() - } - m.current = tui.NewChoice() - m.current, _ = m.current.Update(m.currentWindowSize()) - return m, m.current.Init() + return m, nil case tui.BatchDeleteEmailsMsg: tui.ClearKittyGraphics() - m.previousModel = m.current - count := len(msg.UIDs) - m.current = tui.NewStatus(fmt.Sprintf("Deleting %d emails...", count)) account := m.config.GetAccountByID(msg.AccountID) if account == nil { @@ -1844,18 +1871,28 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo folderName := folderInbox if m.folderInbox != nil { folderName = m.folderInbox.GetCurrentFolder() + m.folderInbox.GetInbox().RemoveEmails(msg.UIDs, msg.AccountID) } - return m, tea.Batch( - m.current.Init(), - m.batchDeleteEmailsCmd(account, msg.UIDs, msg.AccountID, folderName, msg.Mailbox, count), - ) + for _, uid := range msg.UIDs { + m.removeEmailFromStores(uid, msg.AccountID) + } + + if emails, ok := m.folderEmails[folderName]; ok { + var filtered []fetcher.Email + for _, e := range emails { + if e.AccountID != msg.AccountID || !slices.Contains(msg.UIDs, e.UID) { + filtered = append(filtered, e) + } + } + m.folderEmails[folderName] = filtered + go saveFolderEmailsToCache(folderName, filtered) + } + + return m, m.batchDeleteEmailsCmd(msg.UIDs, msg.AccountID, folderName, msg.Mailbox, len(msg.UIDs)) case tui.BatchArchiveEmailsMsg: tui.ClearKittyGraphics() - m.previousModel = m.current - count := len(msg.UIDs) - m.current = tui.NewStatus(fmt.Sprintf("Archiving %d emails...", count)) account := m.config.GetAccountByID(msg.AccountID) if account == nil { @@ -1868,12 +1905,25 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo folderName := folderInbox if m.folderInbox != nil { folderName = m.folderInbox.GetCurrentFolder() + m.folderInbox.GetInbox().RemoveEmails(msg.UIDs, msg.AccountID) } - return m, tea.Batch( - m.current.Init(), - m.batchArchiveEmailsCmd(account, msg.UIDs, msg.AccountID, folderName, msg.Mailbox, count), - ) + for _, uid := range msg.UIDs { + m.removeEmailFromStores(uid, msg.AccountID) + } + + if emails, ok := m.folderEmails[folderName]; ok { + var filtered []fetcher.Email + for _, e := range emails { + if e.AccountID != msg.AccountID || !slices.Contains(msg.UIDs, e.UID) { + filtered = append(filtered, e) + } + } + m.folderEmails[folderName] = filtered + go saveFolderEmailsToCache(folderName, filtered) + } + + return m, m.batchArchiveEmailsCmd(msg.UIDs, msg.AccountID, folderName, msg.Mailbox, len(msg.UIDs)) case tui.BatchMoveEmailsMsg: if m.config == nil { @@ -1884,14 +1934,28 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo return m, nil } - count := len(msg.UIDs) - m.previousModel = m.current - m.current = tui.NewStatus(fmt.Sprintf("Moving %d emails...", count)) + folderName := folderInbox + if m.folderInbox != nil { + folderName = m.folderInbox.GetCurrentFolder() + m.folderInbox.GetInbox().RemoveEmails(msg.UIDs, msg.AccountID) + } - return m, tea.Batch( - m.current.Init(), - m.batchMoveEmailsCmd(account, msg.UIDs, msg.AccountID, msg.SourceFolder, msg.DestFolder, count), - ) + for _, uid := range msg.UIDs { + m.removeEmailFromStores(uid, msg.AccountID) + } + + if emails, ok := m.folderEmails[folderName]; ok { + var filtered []fetcher.Email + for _, e := range emails { + if e.AccountID != msg.AccountID || !slices.Contains(msg.UIDs, e.UID) { + filtered = append(filtered, e) + } + } + m.folderEmails[folderName] = filtered + go saveFolderEmailsToCache(folderName, filtered) + } + + return m, m.batchMoveEmailsCmd(msg.UIDs, msg.AccountID, msg.SourceFolder, msg.DestFolder, len(msg.UIDs)) case tui.BatchEmailActionDoneMsg: if msg.Err != nil { @@ -1902,18 +1966,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo }) } - // Success - show brief confirmation - successMsg := fmt.Sprintf("%d emails %sd successfully", msg.SuccessCount, msg.Action) - if msg.FailureCount > 0 { - successMsg = fmt.Sprintf("%d of %d emails %sd (%d failed)", - msg.SuccessCount, msg.Count, msg.Action, msg.FailureCount) - } - - m.current = tui.NewStatus(successMsg) - - return m, tea.Tick(1500*time.Millisecond, func(t time.Time) tea.Msg { - return tui.RestoreViewMsg{} - }) + return m, nil case tui.DownloadAttachmentMsg: m.previousModel = m.current @@ -2931,46 +2984,54 @@ func markEmailAsUnreadCmd(account *config.Account, uid uint32, accountID string, } } -func deleteFolderEmailCmd(account *config.Account, uid uint32, accountID string, folderName string, mailbox tui.MailboxKind) tea.Cmd { +func (m *mainModel) deleteFolderEmailCmd(uid uint32, accountID string, folderName string, mailbox tui.MailboxKind) tea.Cmd { return func() tea.Msg { - err := fetcher.DeleteFolderEmail(account, folderName, uid) + if m.service == nil { + return tui.EmailActionDoneMsg{ + UID: uid, + AccountID: accountID, + Mailbox: mailbox, + Err: fmt.Errorf("service not initialized"), + } + } + err := m.service.DeleteEmails(accountID, folderName, []uint32{uid}) return tui.EmailActionDoneMsg{UID: uid, AccountID: accountID, Mailbox: mailbox, Err: err} } } -func archiveFolderEmailCmd(account *config.Account, uid uint32, accountID string, folderName string, mailbox tui.MailboxKind) tea.Cmd { +func (m *mainModel) archiveFolderEmailCmd(uid uint32, accountID string, folderName string, mailbox tui.MailboxKind) tea.Cmd { return func() tea.Msg { - err := fetcher.ArchiveFolderEmail(account, folderName, uid) + if m.service == nil { + return tui.EmailActionDoneMsg{ + UID: uid, + AccountID: accountID, + Mailbox: mailbox, + Err: fmt.Errorf("service not initialized"), + } + } + err := m.service.ArchiveEmails(accountID, folderName, []uint32{uid}) return tui.EmailActionDoneMsg{UID: uid, AccountID: accountID, Mailbox: mailbox, Err: err} } } -func (m *mainModel) batchDeleteEmailsCmd(account *config.Account, uids []uint32, accountID, folderName string, mailbox tui.MailboxKind, count int) tea.Cmd { +func (m *mainModel) batchDeleteEmailsCmd(uids []uint32, accountID, folderName string, mailbox tui.MailboxKind, count int) tea.Cmd { return func() tea.Msg { - ctx, cancel := context.WithTimeout(context.Background(), httpclient.IMAPBatchActionTimeout) - defer cancel() - - p := m.getProvider(account) - if p == nil { + if m.service == nil { return tui.BatchEmailActionDoneMsg{ - Count: count, - Action: "delete", - Err: fmt.Errorf("provider not found"), + Count: count, + SuccessCount: 0, + FailureCount: count, + Action: "delete", + Mailbox: mailbox, + Err: fmt.Errorf("service not initialized"), } } - err := p.DeleteEmails(ctx, folderName, uids) + err := m.service.DeleteEmails(accountID, folderName, uids) - // Remove emails from local state on success - if err == nil && m.folderInbox != nil { - m.folderInbox.GetInbox().RemoveEmails(uids, accountID) - } - - successCount := count - failureCount := 0 + successCount, failureCount := count, 0 if err != nil { - failureCount = count - successCount = 0 + successCount, failureCount = 0, count } return tui.BatchEmailActionDoneMsg{ @@ -2984,31 +3045,24 @@ func (m *mainModel) batchDeleteEmailsCmd(account *config.Account, uids []uint32, } } -func (m *mainModel) batchArchiveEmailsCmd(account *config.Account, uids []uint32, accountID, folderName string, mailbox tui.MailboxKind, count int) tea.Cmd { +func (m *mainModel) batchArchiveEmailsCmd(uids []uint32, accountID, folderName string, mailbox tui.MailboxKind, count int) tea.Cmd { return func() tea.Msg { - ctx, cancel := context.WithTimeout(context.Background(), httpclient.IMAPBatchActionTimeout) - defer cancel() - - p := m.getProvider(account) - if p == nil { + if m.service == nil { return tui.BatchEmailActionDoneMsg{ - Count: count, - Action: "archive", - Err: fmt.Errorf("provider not found"), + Count: count, + SuccessCount: 0, + FailureCount: count, + Action: "archive", + Mailbox: mailbox, + Err: fmt.Errorf("service not initialized"), } } - err := p.ArchiveEmails(ctx, folderName, uids) - - if err == nil && m.folderInbox != nil { - m.folderInbox.GetInbox().RemoveEmails(uids, accountID) - } + err := m.service.ArchiveEmails(accountID, folderName, uids) - successCount := count - failureCount := 0 + successCount, failureCount := count, 0 if err != nil { - failureCount = count - successCount = 0 + successCount, failureCount = 0, count } return tui.BatchEmailActionDoneMsg{ @@ -3022,31 +3076,23 @@ func (m *mainModel) batchArchiveEmailsCmd(account *config.Account, uids []uint32 } } -func (m *mainModel) batchMoveEmailsCmd(account *config.Account, uids []uint32, accountID, sourceFolder, destFolder string, count int) tea.Cmd { +func (m *mainModel) batchMoveEmailsCmd(uids []uint32, accountID, sourceFolder, destFolder string, count int) tea.Cmd { return func() tea.Msg { - ctx, cancel := context.WithTimeout(context.Background(), httpclient.IMAPBatchActionTimeout) - defer cancel() - - p := m.getProvider(account) - if p == nil { + if m.service == nil { return tui.BatchEmailActionDoneMsg{ - Count: count, - Action: "move", - Err: fmt.Errorf("provider not found"), + Count: count, + SuccessCount: 0, + FailureCount: count, + Action: "move", + Err: fmt.Errorf("service not initialized"), } } - err := p.MoveEmails(ctx, uids, sourceFolder, destFolder) + err := m.service.MoveEmails(accountID, uids, sourceFolder, destFolder) - if err == nil && m.folderInbox != nil { - m.folderInbox.GetInbox().RemoveEmails(uids, accountID) - } - - successCount := count - failureCount := 0 + successCount, failureCount := count, 0 if err != nil { - failureCount = count - successCount = 0 + successCount, failureCount = 0, count } return tui.BatchEmailActionDoneMsg{ @@ -3059,9 +3105,19 @@ func (m *mainModel) batchMoveEmailsCmd(account *config.Account, uids []uint32, a } } -func moveEmailToFolderCmd(account *config.Account, uid uint32, accountID string, sourceFolder, destFolder string) tea.Cmd { +func (m *mainModel) moveEmailToFolderCmd(uid uint32, accountID string, sourceFolder, destFolder string) tea.Cmd { return func() tea.Msg { - err := fetcher.MoveEmailToFolder(account, uid, sourceFolder, destFolder) + if m.service == nil { + return tui.EmailMovedMsg{ + UID: uid, + AccountID: accountID, + SourceFolder: sourceFolder, + DestFolder: destFolder, + Err: fmt.Errorf("service not initialized"), + } + } + + err := m.service.MoveEmails(accountID, []uint32{uid}, sourceFolder, destFolder) return tui.EmailMovedMsg{ UID: uid, AccountID: accountID,