feat: Enhance error handling and logging in SaveDraft and ensureFolder methods, adding recovery from panics and detailed debug information.
This commit is contained in:
@@ -205,7 +205,18 @@ func (ic *IMAPClient) FetchUnprocessedEmails() ([]Email, error) {
|
|||||||
return emails, nil
|
return emails, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *IMAPClient) SaveDraft(email Email, response string) error {
|
func (ic *IMAPClient) SaveDraft(email Email, response string) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"panic": r,
|
||||||
|
"emailSubject": email.Subject,
|
||||||
|
"draftBox": ic.config.DraftBox,
|
||||||
|
}).Error("Panic occurred in SaveDraft")
|
||||||
|
err = fmt.Errorf("panic in SaveDraft: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if err := ic.ensureConnection(); err != nil {
|
if err := ic.ensureConnection(); err != nil {
|
||||||
return fmt.Errorf("failed to ensure IMAP connection: %v", err)
|
return fmt.Errorf("failed to ensure IMAP connection: %v", err)
|
||||||
}
|
}
|
||||||
@@ -213,13 +224,16 @@ func (ic *IMAPClient) SaveDraft(email Email, response string) error {
|
|||||||
// Get proper folder path and ensure it exists
|
// Get proper folder path and ensure it exists
|
||||||
draftFolder, err := ic.ensureFolder(ic.config.DraftBox)
|
draftFolder, err := ic.ensureFolder(ic.config.DraftBox)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to ensure draft folder exists: %v", err)
|
return fmt.Errorf("failed to ensure draft folder: %s, error: %v", ic.config.DraftBox, err)
|
||||||
}
|
}
|
||||||
|
logger.WithField("draftFolder", draftFolder).Debug("Ensured draft folder path")
|
||||||
|
|
||||||
|
logger.WithField("draftFolder", draftFolder).Debug("Attempting to select draft folder")
|
||||||
_, err = ic.client.Select(draftFolder, false)
|
_, err = ic.client.Select(draftFolder, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to select draft box: %v", err)
|
return fmt.Errorf("failed to select draft box '%s': %v", draftFolder, err)
|
||||||
}
|
}
|
||||||
|
logger.WithField("draftFolder", draftFolder).Debug("Successfully selected draft folder")
|
||||||
|
|
||||||
// Format the draft message with HTML response and original email headers + content
|
// Format the draft message with HTML response and original email headers + content
|
||||||
draft := fmt.Sprintf("From: %s\r\n"+
|
draft := fmt.Sprintf("From: %s\r\n"+
|
||||||
@@ -255,9 +269,14 @@ func (ic *IMAPClient) SaveDraft(email Email, response string) error {
|
|||||||
|
|
||||||
// Save the draft to the proper folder path
|
// Save the draft to the proper folder path
|
||||||
flags := []string{"\\Draft"}
|
flags := []string{"\\Draft"}
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"draftFolder": draftFolder,
|
||||||
|
"flags": flags,
|
||||||
|
}).Debug("Attempting to append draft")
|
||||||
if err := ic.client.Append(draftFolder, flags, time.Now(), literal); err != nil {
|
if err := ic.client.Append(draftFolder, flags, time.Now(), literal); err != nil {
|
||||||
return fmt.Errorf("failed to append draft: %v", err)
|
return fmt.Errorf("failed to append draft to '%s': %v", draftFolder, err)
|
||||||
}
|
}
|
||||||
|
logger.WithField("draftFolder", draftFolder).Debug("Successfully appended draft")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -419,44 +438,170 @@ func fallbackExtractContent(body string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensureFolder makes sure a folder exists and returns its full path using proper delimiters
|
// ensureFolder makes sure a folder exists and returns its full path using proper delimiters
|
||||||
func (ic *IMAPClient) ensureFolder(folderName string) (string, error) {
|
func (ic *IMAPClient) ensureFolder(folderName string) (path string, err error) {
|
||||||
// List all mailboxes to get the delimiter
|
defer func() {
|
||||||
mailboxes := make(chan *imap.MailboxInfo, 10)
|
if r := recover(); r != nil {
|
||||||
done := make(chan error, 1)
|
logger.WithFields(logrus.Fields{
|
||||||
go func() {
|
"panic": r,
|
||||||
done <- ic.client.List("", "*", mailboxes)
|
"folderName": folderName,
|
||||||
|
}).Error("Panic occurred in ensureFolder")
|
||||||
|
err = fmt.Errorf("panic in ensureFolder: %v", r)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
logger.WithField("folderNameInput", folderName).Debug("Ensuring folder exists (ensureFolder start)")
|
||||||
|
|
||||||
|
var folderPath string
|
||||||
|
isGmail := strings.ToLower(ic.config.Server) == "imap.gmail.com"
|
||||||
|
|
||||||
|
if isGmail {
|
||||||
|
logger.Debug("ensureFolder: Detected Gmail server. Using Gmail-specific logic.")
|
||||||
|
// Gmail always uses '/' as a delimiter and folder names are typically as provided.
|
||||||
|
delimiter := "/"
|
||||||
|
folderPath = folderName // Gmail folder names in config should already be correct.
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"folderName": folderName,
|
||||||
|
"assumedDelimiter": delimiter,
|
||||||
|
"derivedFolderPath": folderPath,
|
||||||
|
}).Debug("ensureFolder: Gmail - folder path set")
|
||||||
|
|
||||||
|
// For Gmail, don't try to CREATE system folders like "[Gmail]/..." or "INBOX"
|
||||||
|
// For "INBOX/Subfolder", CREATE is fine as it creates a label.
|
||||||
|
if strings.HasPrefix(folderPath, "[Gmail]/") || folderPath == "INBOX" {
|
||||||
|
logger.WithField("folderPath", folderPath).Debug("ensureFolder: Gmail - Skipping CREATE for system folder.")
|
||||||
|
return folderPath, nil
|
||||||
|
}
|
||||||
|
// For other paths like "INBOX/Done" or "MyCustomLabel", attempt CREATE.
|
||||||
|
logger.WithField("folderPath", folderPath).Debug("ensureFolder: Gmail - Attempting to create folder (label).")
|
||||||
|
if createErr := ic.client.Create(folderPath); createErr != nil {
|
||||||
|
if !strings.Contains(strings.ToLower(createErr.Error()), "already exists") &&
|
||||||
|
!strings.Contains(strings.ToLower(createErr.Error()), "mailbox exists") {
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"folder": folderPath,
|
||||||
|
"error": createErr,
|
||||||
|
}).Warn("ensureFolder: Gmail - Folder creation failed (and not 'already exists')")
|
||||||
|
// Unlike non-Gmail, if CREATE fails for a non-system folder, it's more likely a real issue.
|
||||||
|
// However, subsequent SELECT/APPEND will ultimately determine usability.
|
||||||
|
// For now, we'll proceed and let later operations fail if it's critical.
|
||||||
|
} else {
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"folder": folderPath,
|
||||||
|
"error": createErr,
|
||||||
|
}).Debug("ensureFolder: Gmail - Folder creation failed (likely already exists).")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.WithField("folderPath", folderPath).Info("ensureFolder: Gmail - Successfully created folder/label or it already existed.")
|
||||||
|
}
|
||||||
|
return folderPath, nil
|
||||||
|
|
||||||
|
} else {
|
||||||
|
logger.Debug("ensureFolder: Non-Gmail server. Using generic logic.")
|
||||||
|
// Generic logic for non-Gmail servers (existing logic with timeouts)
|
||||||
|
logger.Debug("ensureFolder: Creating channels mailboxes and listDone")
|
||||||
|
mailboxes := make(chan *imap.MailboxInfo, 10)
|
||||||
|
listDone := make(chan error, 1)
|
||||||
|
logger.Debug("ensureFolder: Channels created. Attempting to start List goroutine")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
logger.Debug("ensureFolder: List goroutine started")
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"panic": r,
|
||||||
|
"folderName": folderName,
|
||||||
|
}).Error("Panic occurred in ensureFolder's List goroutine")
|
||||||
|
listDone <- fmt.Errorf("panic in List goroutine: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
logger.Debug("ensureFolder: List goroutine: Entering select for client.List or timeout")
|
||||||
|
select {
|
||||||
|
case listDone <- ic.client.List("", "*", mailboxes):
|
||||||
|
logger.Debug("ensureFolder: List goroutine: client.List call completed and sent to listDone")
|
||||||
|
case <-time.After(30 * time.Second):
|
||||||
|
logger.Error("Timeout occurred during client.List operation in goroutine")
|
||||||
|
listDone <- fmt.Errorf("client.List timeout after 30 seconds in goroutine")
|
||||||
|
}
|
||||||
|
logger.Debug("ensureFolder: List goroutine finished")
|
||||||
|
}()
|
||||||
|
logger.Debug("ensureFolder: List goroutine launched.")
|
||||||
|
|
||||||
var delimiter string
|
var delimiter string
|
||||||
|
var receivedMailbox bool
|
||||||
|
logger.Debug("ensureFolder: Attempting to range over mailboxes channel")
|
||||||
for m := range mailboxes {
|
for m := range mailboxes {
|
||||||
|
receivedMailbox = true
|
||||||
|
logger.WithField("mailboxName", m.Name).Debug("ensureFolder: Received mailbox from channel")
|
||||||
delimiter = m.Delimiter
|
delimiter = m.Delimiter
|
||||||
break // We just need the first one to get the delimiter
|
logger.WithField("delimiter", delimiter).Debug("ensureFolder: Got delimiter from mailbox info")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
if !receivedMailbox {
|
||||||
|
logger.Debug("ensureFolder: Did not receive any mailboxes from channel (it might have been empty or closed early).")
|
||||||
|
}
|
||||||
|
logger.Debug("ensureFolder: Finished ranging over mailboxes. Attempting to read from listDone channel with timeout")
|
||||||
|
|
||||||
if err := <-done; err != nil {
|
select {
|
||||||
return "", fmt.Errorf("failed to list mailboxes: %v", err)
|
case listErr := <-listDone:
|
||||||
|
if listErr != nil {
|
||||||
|
logger.WithError(listErr).Error("ensureFolder: Error received from listDone channel")
|
||||||
|
return "", fmt.Errorf("failed to list mailboxes to determine delimiter: %v", listErr)
|
||||||
|
}
|
||||||
|
logger.Debug("ensureFolder: Successfully read from listDone channel.")
|
||||||
|
case <-time.After(35 * time.Second):
|
||||||
|
logger.Error("ensureFolder: Timeout waiting for listDone channel")
|
||||||
|
return "", fmt.Errorf("timeout waiting for LIST operation to complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
if delimiter == "" {
|
if delimiter == "" {
|
||||||
delimiter = "/" // fallback to common delimiter
|
logger.Debug("ensureFolder: Delimiter is still empty after processing listDone and mailboxes.")
|
||||||
|
delimiter = "/"
|
||||||
|
logger.Debug("No delimiter returned by server, using fallback '/'")
|
||||||
}
|
}
|
||||||
|
logger.WithField("delimiter", delimiter).Debug("Determined mailbox delimiter")
|
||||||
|
|
||||||
// Replace any forward slashes with the server's delimiter
|
if delimiter != "/" {
|
||||||
folderPath := strings.ReplaceAll(folderName, "/", delimiter)
|
folderPath = strings.ReplaceAll(folderName, "/", delimiter)
|
||||||
|
} else {
|
||||||
|
folderPath = folderName
|
||||||
|
}
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"originalFolderName": folderName,
|
||||||
|
"serverDelimiter": delimiter,
|
||||||
|
"derivedFolderPath": folderPath,
|
||||||
|
}).Debug("Derived folder path using server delimiter")
|
||||||
|
|
||||||
// Try to create the folder if it doesn't exist
|
logger.WithField("folderPath", folderPath).Debug("Attempting to create folder if it doesn't exist")
|
||||||
if err := ic.client.Create(folderPath); err != nil {
|
if createErr := ic.client.Create(folderPath); createErr != nil {
|
||||||
// Ignore errors if the folder already exists
|
if !strings.Contains(strings.ToLower(createErr.Error()), "already exists") &&
|
||||||
|
!strings.Contains(strings.ToLower(createErr.Error()), "mailbox exists") {
|
||||||
logger.WithFields(logrus.Fields{
|
logger.WithFields(logrus.Fields{
|
||||||
"folder": folderPath,
|
"folder": folderPath,
|
||||||
"error": err,
|
"error": createErr,
|
||||||
}).Debug("Folder creation failed (might already exist)")
|
}).Warn("Folder creation failed (and not 'already exists')")
|
||||||
|
} else {
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"folder": folderPath,
|
||||||
|
"error": createErr,
|
||||||
|
}).Debug("Folder creation failed (likely already exists)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.WithField("folderPath", folderPath).Info("Successfully created folder or it already existed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return folderPath, nil
|
return folderPath, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ic *IMAPClient) MarkAsProcessed(email Email) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"panic": r,
|
||||||
|
"emailSubject": email.Subject,
|
||||||
|
"processedBox": ic.config.ProcessedBox,
|
||||||
|
}).Error("Panic occurred in MarkAsProcessed")
|
||||||
|
err = fmt.Errorf("panic in MarkAsProcessed: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
func (ic *IMAPClient) MarkAsProcessed(email Email) error {
|
|
||||||
if err := ic.ensureConnection(); err != nil {
|
if err := ic.ensureConnection(); err != nil {
|
||||||
return fmt.Errorf("failed to ensure IMAP connection: %v", err)
|
return fmt.Errorf("failed to ensure IMAP connection: %v", err)
|
||||||
}
|
}
|
||||||
@@ -468,8 +613,9 @@ func (ic *IMAPClient) MarkAsProcessed(email Email) error {
|
|||||||
// Get proper folder path and ensure it exists
|
// Get proper folder path and ensure it exists
|
||||||
processedFolder, err := ic.ensureFolder(ic.config.ProcessedBox)
|
processedFolder, err := ic.ensureFolder(ic.config.ProcessedBox)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to ensure processed folder exists: %v", err)
|
return fmt.Errorf("failed to ensure processed folder: %s, error: %v", ic.config.ProcessedBox, err)
|
||||||
}
|
}
|
||||||
|
logger.WithField("processedFolder", processedFolder).Debug("Ensured processed folder path")
|
||||||
|
|
||||||
// Select source mailbox
|
// Select source mailbox
|
||||||
_, err = ic.client.Select(ic.config.MailboxIn, false)
|
_, err = ic.client.Select(ic.config.MailboxIn, false)
|
||||||
|
|||||||
Reference in New Issue
Block a user