diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java index 90002c7931..bc5524332d 100644 --- a/app/src/main/java/eu/faircode/email/DaoMessage.java +++ b/app/src/main/java/eu/faircode/email/DaoMessage.java @@ -425,6 +425,11 @@ public interface DaoMessage { " AND inreplyto = :inreplyto") List getMessagesByInReplyTo(long account, String inreplyto); + @Query("SELECT thread, msgid, hash, inreplyto FROM message" + + " WHERE account = :account" + + " AND (msgid IN (:msgids) OR inreplyto IN (:msgids))") + List getThreadInfo(long account, List msgids); + @Query("SELECT * FROM message" + " WHERE account = :account" + " AND sender = :sender" + diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index e9eeb2a671..0324de1d98 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -1287,7 +1287,10 @@ public class MessageHelper { String getThreadId(Context context, long account, long folder, long uid) throws MessagingException { if (threadId == null) - threadId = _getThreadId(context, account, folder, uid); + if (BuildConfig.DEBUG) + threadId = _getThreadIdAlt(context, account, folder, uid); + else + threadId = _getThreadId(context, account, folder, uid); return threadId; } @@ -1370,6 +1373,90 @@ public class MessageHelper { return thread; } + private String _getThreadIdAlt(Context context, long account, long folder, long uid) throws MessagingException { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (imessage instanceof GmailMessage) { + // https://developers.google.com/gmail/imap/imap-extensions#access_to_the_gmail_thread_id_x-gm-thrid + boolean gmail_thread_id = prefs.getBoolean("gmail_thread_id", false); + if (gmail_thread_id) { + long thrid = ((GmailMessage) imessage).getThrId(); + if (thrid > 0) + return "gmail:" + thrid; + } + } + + String thread = null; + String msgid = getMessageID(); + + List refs = new ArrayList<>(); + for (String ref : getReferences()) + if (!TextUtils.isEmpty(ref) && !refs.contains(ref)) + refs.add(ref); + + String inreplyto = getInReplyTo(); + if (!TextUtils.isEmpty(inreplyto) && !refs.contains(inreplyto)) + refs.add(inreplyto); + + DB db = DB.getInstance(context); + + List all = new ArrayList<>(refs); + all.add(msgid); + List infos = db.message().getThreadInfo(account, all); + + // References, In-Reply-To (before) + for (TupleThreadInfo info : infos) + if (info.isReference(msgid) && !TextUtils.isEmpty(info.thread)) { + thread = info.thread; + break; + } + + // Similar + if (thread == null) { + for (TupleThreadInfo info : infos) + if (info.isSelf(msgid) && + !TextUtils.isEmpty(info.thread) && Objects.equals(info.hash, getHash())) { + thread = info.thread; + break; + } + } + + if (thread == null) + thread = getHash() + ":" + uid; + + // Received before + for (TupleThreadInfo info : infos) + if (info.isReference(msgid) && + !thread.equals(info.thread)) { + Log.w("Updating before thread from " + info.thread + " to " + thread); + db.message().updateMessageThread(account, info.thread, thread, null); + } + + // Received after + for (TupleThreadInfo info : infos) + if (info.isInReplyto(msgid) && !thread.equals(info.thread)) { + Log.w("Updating after thread from " + info.thread + " to " + thread); + db.message().updateMessageThread(account, info.thread, thread, null); + } + + boolean subject_threading = prefs.getBoolean("subject_threading", false); + if (subject_threading && !isReport()) { + String sender = getSortKey(getFrom()); + String subject = getSubject(); + long since = new Date().getTime() - MAX_SUBJECT_AGE * 3600 * 1000L; + if (!TextUtils.isEmpty(sender) && !TextUtils.isEmpty(subject)) { + List subjects = db.message().getMessagesBySubject(account, sender, subject, since); + for (EntityMessage message : subjects) + if (!thread.equals(message.thread)) { + Log.w("Updating subject thread from " + message.thread + " to " + thread); + db.message().updateMessageThread(message.account, message.thread, thread, since); + } + } + } + + return thread; + } + String[] getLabels() throws MessagingException { //ensureMessage(false); diff --git a/app/src/main/java/eu/faircode/email/TupleThreadInfo.java b/app/src/main/java/eu/faircode/email/TupleThreadInfo.java new file mode 100644 index 0000000000..5fcf9687b7 --- /dev/null +++ b/app/src/main/java/eu/faircode/email/TupleThreadInfo.java @@ -0,0 +1,41 @@ +package eu.faircode.email; + +/* + This file is part of FairEmail. + + FairEmail is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + FairEmail is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FairEmail. If not, see . + + Copyright 2018-2022 by Marcel Bokhorst (M66B) +*/ + +import java.util.Objects; + +public class TupleThreadInfo { + public String thread; + public String msgid; + public String hash; + public String inreplyto; + + public boolean isSelf(String msgid) { + return Objects.equals(this.msgid, msgid); + } + + public boolean isInReplyto(String msgid) { + return Objects.equals(this.inreplyto, msgid); + } + + public boolean isReference(String msgid) { + return !isSelf(msgid) && !isInReplyto(msgid); + } +}