[IMAP] uid based imap email sync

This commit is contained in:
mbauskar 2017-02-09 16:22:52 +05:30
parent 53d6aefde9
commit d8bfa0210d
4 changed files with 143 additions and 16 deletions

View file

@ -633,6 +633,33 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "uid",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "UID",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -1428,5 +1455,5 @@
"sort_order": "DESC",
"title_field": "subject",
"track_changes": 1,
"track_seen": 0
"track_seen": 1
}

View file

@ -530,6 +530,65 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "UNSEEN",
"depends_on": "eval: doc.enable_incoming",
"fieldname": "email_sync_option",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email Sync Option",
"length": 0,
"no_copy": 0,
"options": "ALL\nUNSEEN",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Total number of emails to sync in initial sync process ",
"fieldname": "initial_sync_count",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Initial Sync Count",
"length": 0,
"no_copy": 0,
"options": "100\n250\n500",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -1138,7 +1197,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "uid_validity",
"fieldname": "uidvalidity",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
@ -1147,7 +1206,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "uid validity",
"label": "UIDVALIDITY",
"length": 0,
"no_copy": 1,
"permlevel": 0,
@ -1176,7 +1235,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "uidnext",
"label": "UIDNEXT",
"length": 0,
"no_copy": 1,
"permlevel": 0,

View file

@ -75,8 +75,6 @@ class EmailAccount(Document):
if self.append_to not in valid_doctypes:
frappe.throw(_("Append To can be one of {0}").format(comma_or(valid_doctypes)))
def on_update(self):
"""Check there is only one default of each type."""
self.there_must_be_only_one_default()
@ -131,7 +129,7 @@ class EmailAccount(Document):
server.password = self.get_password()
server.sess
def get_incoming_server(self, in_receive=False):
def get_incoming_server(self, in_receive=False, email_sync_rule="UNSEEN"):
"""Returns logged in POP3/IMAP connection object."""
if frappe.cache().get_value("workers:no-internet") == True:
return None
@ -142,6 +140,7 @@ class EmailAccount(Document):
"use_ssl": self.use_ssl,
"username": getattr(self, "login_id", None) or self.email_id,
"use_imap": self.use_imap,
"email_sync_rule": email_sync_rule
})
if self.password:
args.password = self.get_password()
@ -221,17 +220,24 @@ class EmailAccount(Document):
def receive(self, test_mails=None):
"""Called by scheduler to receive emails from this EMail account using POP3/IMAP."""
if self.enable_incoming:
uid_list = []
exceptions = []
if frappe.local.flags.in_test:
incoming_mails = test_mails
else:
email_server = self.get_incoming_server(in_receive=True)
incoming_mails = email_server.get_messages()
email_sync_rule = self.build_email_sync_rule()
for msg in incoming_mails:
email_server = self.get_incoming_server(in_receive=True, email_sync_rule=email_sync_rule)
emails = email_server.get_messages()
incoming_mails = emails.get("latest_messages")
uid_list = emails.get("uid_list", [])
for idx, msg in enumerate(incoming_mails):
try:
communication = self.insert_communication(msg)
uid = None if not uid_list else uid_list[idx]
communication = self.insert_communication(msg, uid)
#self.notify_update()
except SentEmailInInbox:
@ -277,12 +283,15 @@ class EmailAccount(Document):
unhandled_email.save()
frappe.db.commit()
def insert_communication(self, msg):
def insert_communication(self, msg, _uid=None):
if isinstance(msg,list):
raw, uid, seen = msg
else:
raw = msg
seen = uid = None
if _uid: uid = _uid
email = Email(raw)
if email.from_email == self.email_id and not email.mail.get("Reply-To"):
@ -511,6 +520,17 @@ class EmailAccount(Document):
def after_rename(self, old, new, merge=False):
frappe.db.set_value("Email Account", new, "email_account_name", new)
def build_email_sync_rule(self):
if not self.use_imap:
return "UNSEEN"
if self.email_sync_option == "ALL":
max_uid = get_max_email_uid(self.name)
last_uid = max_uid + int(self.initial_sync_count or 100) if max_uid == 1 else "*"
return "UID {}:{}".format(max_uid, last_uid)
else:
return self.email_sync_option
@frappe.whitelist()
def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None):
if not txt: txt = ""
@ -584,3 +604,19 @@ def pull_from_email_account(email_account):
'''Runs within a worker process'''
email_account = frappe.get_doc("Email Account", email_account)
email_account.receive()
def get_max_email_uid(email_account):
# get maximum uid of emails
max_uid = 1
result = frappe.db.get_all("Communication", filters={
"communication_medium": "Email",
"sent_or_received": "Received",
"email_account": email_account
}, fields=["ifnull(max(uid), 0) as uid"])
if not result:
return 1
else:
max_uid = int(result[0].get("uid", 0)) + 1
return max_uid

View file

@ -101,12 +101,14 @@ class EmailServer:
if not self.connect():
return []
uid_list = []
try:
# track if errors arised
self.errors = False
self.latest_messages = []
email_list = self.get_new_mails()
uid_list = email_list = self.get_new_mails()
num = num_copy = len(email_list)
# WARNING: Hard coded max no. of messages to be popped
@ -149,13 +151,16 @@ class EmailServer:
else:
self.pop.quit()
return self.latest_messages
out = { "latest_messages": self.latest_messages }
if self.settings.use_imap: out.update({ "uid_list": uid_list })
return out
def get_new_mails(self):
"""Return list of new mails"""
if cint(self.settings.use_imap):
self.imap.select("Inbox")
response, message = self.imap.uid('search', None, "UNSEEN")
response, message = self.imap.uid('search', None, self.settings.email_sync_rule)
email_list = message[0].split()
else:
email_list = self.pop.list()[1]