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
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
draftFolder, err := ic.ensureFolder(ic.config.DraftBox)
|
||||
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)
|
||||
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
|
||||
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
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@@ -419,44 +438,170 @@ func fallbackExtractContent(body string) string {
|
||||
}
|
||||
|
||||
// ensureFolder makes sure a folder exists and returns its full path using proper delimiters
|
||||
func (ic *IMAPClient) ensureFolder(folderName string) (string, error) {
|
||||
// List all mailboxes to get the delimiter
|
||||
mailboxes := make(chan *imap.MailboxInfo, 10)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- ic.client.List("", "*", mailboxes)
|
||||
func (ic *IMAPClient) ensureFolder(folderName string) (path string, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"panic": r,
|
||||
"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 delimiter string
|
||||
for m := range mailboxes {
|
||||
delimiter = m.Delimiter
|
||||
break // We just need the first one to get the delimiter
|
||||
}
|
||||
var folderPath string
|
||||
isGmail := strings.ToLower(ic.config.Server) == "imap.gmail.com"
|
||||
|
||||
if err := <-done; err != nil {
|
||||
return "", fmt.Errorf("failed to list mailboxes: %v", err)
|
||||
}
|
||||
|
||||
if delimiter == "" {
|
||||
delimiter = "/" // fallback to common delimiter
|
||||
}
|
||||
|
||||
// Replace any forward slashes with the server's delimiter
|
||||
folderPath := strings.ReplaceAll(folderName, "/", delimiter)
|
||||
|
||||
// Try to create the folder if it doesn't exist
|
||||
if err := ic.client.Create(folderPath); err != nil {
|
||||
// Ignore errors if the folder already exists
|
||||
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{
|
||||
"folder": folderPath,
|
||||
"error": err,
|
||||
}).Debug("Folder creation failed (might already exist)")
|
||||
}
|
||||
"folderName": folderName,
|
||||
"assumedDelimiter": delimiter,
|
||||
"derivedFolderPath": folderPath,
|
||||
}).Debug("ensureFolder: Gmail - folder path set")
|
||||
|
||||
return folderPath, nil
|
||||
// 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 receivedMailbox bool
|
||||
logger.Debug("ensureFolder: Attempting to range over mailboxes channel")
|
||||
for m := range mailboxes {
|
||||
receivedMailbox = true
|
||||
logger.WithField("mailboxName", m.Name).Debug("ensureFolder: Received mailbox from channel")
|
||||
delimiter = m.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")
|
||||
|
||||
select {
|
||||
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 == "" {
|
||||
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")
|
||||
|
||||
if 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")
|
||||
|
||||
logger.WithField("folderPath", folderPath).Debug("Attempting to create folder if it doesn't exist")
|
||||
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("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
|
||||
}
|
||||
}
|
||||
|
||||
func (ic *IMAPClient) MarkAsProcessed(email Email) error {
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := ic.ensureConnection(); err != nil {
|
||||
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
|
||||
processedFolder, err := ic.ensureFolder(ic.config.ProcessedBox)
|
||||
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
|
||||
_, err = ic.client.Select(ic.config.MailboxIn, false)
|
||||
|
||||
Reference in New Issue
Block a user