FIX: IMAP sync email update uniqueness across groups and minor improvements (#10332)

Adds a imap_group_id column to IncomingEmail to deal with an issue where we were trying to update emails in the mailbox, calling IncomingEmail.where(imap_sync: true). However UID and UIDVALIDITY could be the same across accounts. So if group A used IMAP details for Gmail account A, and group B used IMAP details for Gmail account B, and both tried to sync changes to an email with UID of 3 (e.g. changing Labels), one account could affect the other. This even applied to Archiving!

Also in this PR:

* Fix error occurring if we do a uid_fetch and no emails are returned
* Allow for creating labels within the target mailbox (previously we would not do this, only use existing labels)
* Improve consistency for log messages
* Add specs for generic IMAP provider (Gmail specs still to come)
* Add custom archiving support for Gmail
* Only use Message-ID for uniqueness of IncomingEmail if it was generated by us
* Various refactors and improvements
This commit is contained in:
Martin Brennan
2020-08-03 13:10:17 +10:00
committed by GitHub
parent 8a9e4504fe
commit 2920988b3a
9 changed files with 430 additions and 109 deletions

View File

@ -4,13 +4,18 @@ module Imap
module Providers
class Gmail < Generic
X_GM_LABELS = 'X-GM-LABELS'
X_GM_THRID = 'X-GM-THRID'
def imap
@imap ||= super.tap { |imap| apply_gmail_patch(imap) }
end
def emails(uids, fields, opts = {})
fields[fields.index('LABELS')] = X_GM_LABELS
# gmail has a special header for labels
if fields.include?('LABELS')
fields[fields.index('LABELS')] = X_GM_LABELS
end
emails = super(uids, fields, opts)
@ -22,7 +27,7 @@ module Imap
email['LABELS'].flatten!
end
email['LABELS'] << '\\Inbox' if opts[:mailbox] == 'INBOX'
email['LABELS'] << '\\Inbox' if @open_mailbox_name == 'INBOX'
email['LABELS'].uniq!
end
@ -57,6 +62,33 @@ module Imap
super(tag)
end
def archive(uid)
# all emails in the thread must be archived in Gmail for the thread
# to get removed from the inbox
thread_id = thread_id_from_uid(uid)
emails_to_archive = emails_in_thread(thread_id)
emails_to_archive.each do |email|
labels = email['LABELS']
new_labels = labels.reject { |l| l == "\\Inbox" }
store(email["UID"], "LABELS", labels, new_labels)
end
Imap::Sync::Logger.log("[IMAP] Thread ID #{thread_id} (UID #{uid}) archived in Gmail mailbox for #{@username}")
end
def thread_id_from_uid(uid)
fetched = imap.uid_fetch(uid, [X_GM_THRID])
if !fetched
raise "Thread not found for UID #{uid}!"
end
fetched.last.attr[X_GM_THRID]
end
def emails_in_thread(thread_id)
uids_to_fetch = imap.uid_search("#{X_GM_THRID} #{thread_id}")
emails(uids_to_fetch, ["UID", "LABELS"])
end
private
def apply_gmail_patch(imap)