Added experimental HIGHESTMODSEQ support

pull/197/head
M66B 5 years ago
parent 9867cb1c50
commit 136b4a4744

@ -2460,6 +2460,7 @@ class Core {
boolean sync_flagged = prefs.getBoolean("sync_flagged", false); boolean sync_flagged = prefs.getBoolean("sync_flagged", false);
boolean sync_kept = prefs.getBoolean("sync_kept", true); boolean sync_kept = prefs.getBoolean("sync_kept", true);
boolean delete_unseen = prefs.getBoolean("delete_unseen", false); boolean delete_unseen = prefs.getBoolean("delete_unseen", false);
boolean use_modseq = prefs.getBoolean("use_modseq", BuildConfig.DEBUG);
boolean perform_expunge = prefs.getBoolean("perform_expunge", true); boolean perform_expunge = prefs.getBoolean("perform_expunge", true);
Log.i(folder.name + " start sync after=" + sync_days + "/" + keep_days + Log.i(folder.name + " start sync after=" + sync_days + "/" + keep_days +
@ -2482,6 +2483,23 @@ class Core {
Log.w(folder.name, ex); Log.w(folder.name, ex);
} }
// https://tools.ietf.org/html/rfc4551
// https://wiki.mozilla.org/Thunderbird:IMAP_RFC_4551_Implementation
boolean modified = true;
try {
if (use_modseq &&
!force && initialize == 0 &&
MessageHelper.hasCapability(ifolder, "CONDSTORE")) {
long modseq = ifolder.getHighestModSeq();
Log.i(folder.name + " modseq=" + modseq + "/" + folder.modseq);
modified = (folder.modseq == null || !folder.modseq.equals(modseq));
folder.modseq = modseq;
db.folder().setFolderModSeq(folder.id, folder.modseq);
}
} catch (MessagingException ex) {
Log.w(folder.name, ex);
}
// Get reference times // Get reference times
Calendar cal_sync = Calendar.getInstance(); Calendar cal_sync = Calendar.getInstance();
cal_sync.add(Calendar.DAY_OF_MONTH, -sync_days); cal_sync.add(Calendar.DAY_OF_MONTH, -sync_days);
@ -2562,284 +2580,324 @@ class Core {
stats.search_ms = (SystemClock.elapsedRealtime() - search); stats.search_ms = (SystemClock.elapsedRealtime() - search);
Log.i(folder.name + " remote count=" + imessages.length + " search=" + stats.search_ms + " ms"); Log.i(folder.name + " remote count=" + imessages.length + " search=" + stats.search_ms + " ms");
long fetch = SystemClock.elapsedRealtime(); Long[] ids = new Long[imessages.length];
FetchProfile fp = new FetchProfile(); if (!modified) {
fp.add(UIDFolder.FetchProfileItem.UID); // To check if message exists Log.i(folder.name + " quick check");
fp.add(FetchProfile.Item.FLAGS); // To update existing messages long fetch = SystemClock.elapsedRealtime();
if (account.isGmail())
fp.add(GmailFolder.FetchProfileItem.LABELS);
ifolder.fetch(imessages, fp);
stats.flags = imessages.length;
stats.flags_ms = (SystemClock.elapsedRealtime() - fetch);
Log.i(folder.name + " remote fetched=" + stats.flags_ms + " ms");
// Sort for finding referenced/replied-to messages
// Sorting on date/time would be better, but requires fetching the headers
Arrays.sort(imessages, new Comparator<Message>() {
@Override
public int compare(Message m1, Message m2) {
try {
return Long.compare(ifolder.getUID(m1), ifolder.getUID(m2));
} catch (MessagingException ex) {
return 0;
}
}
});
int expunge = 0; FetchProfile fp = new FetchProfile();
for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++) fp.add(UIDFolder.FetchProfileItem.UID);
try { ifolder.fetch(imessages, fp);
if (perform_expunge && imessages[i].isSet(Flags.Flag.DELETED))
expunge++; stats.flags = imessages.length;
else stats.flags_ms = (SystemClock.elapsedRealtime() - fetch);
uids.remove(ifolder.getUID(imessages[i])); Log.i(folder.name + " remote fetched=" + stats.flags_ms + " ms");
} catch (MessageRemovedException ex) {
Log.w(folder.name, ex);
} catch (Throwable ex) {
Log.e(folder.name, ex);
EntityLog.log(context, folder.name + " " + Log.formatThrowable(ex, false));
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
}
if (expunge > 0)
try { try {
Log.i(folder.name + " expunging=" + expunge); for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++) {
ifolder.expunge(); long uid = ifolder.getUID(imessages[i]);
ids[i] = db.message().getMessageMsgIdByUid(folder.id, uid);
if (ids[i] == null) {
Log.i(folder.name + " missing uid=" + uid);
modified = true;
break;
} else
uids.remove(uid);
}
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(ex); Log.w(ex);
modified = true;
db.folder().setFolderModSeq(folder.id, null);
} }
if (uids.size() > 0) { if (uids.size() > 0) {
// This is done outside of JavaMail to prevent changed notifications Log.i(folder.name + " remaining=" + uids.size());
if (!ifolder.isOpen()) modified = true;
throw new FolderClosedException(ifolder, "UID FETCH"); }
EntityLog.log(context, folder.name + " modified=" + modified);
}
if (modified) {
long fetch = SystemClock.elapsedRealtime();
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID); // To check if message exists
fp.add(FetchProfile.Item.FLAGS); // To update existing messages
if (account.isGmail())
fp.add(GmailFolder.FetchProfileItem.LABELS);
ifolder.fetch(imessages, fp);
stats.flags = imessages.length;
stats.flags_ms = (SystemClock.elapsedRealtime() - fetch);
Log.i(folder.name + " remote fetched=" + stats.flags_ms + " ms");
long getuid = SystemClock.elapsedRealtime(); // Sort for finding referenced/replied-to messages
MessagingException ex = (MessagingException) ifolder.doCommand(new IMAPFolder.ProtocolCommand() { // Sorting on date/time would be better, but requires fetching the headers
Arrays.sort(imessages, new Comparator<Message>() {
@Override @Override
public Object doCommand(IMAPProtocol protocol) { public int compare(Message m1, Message m2) {
try { try {
protocol.select(folder.name); return Long.compare(ifolder.getUID(m1), ifolder.getUID(m2));
} catch (ProtocolException ex) { } catch (MessagingException ex) {
return new MessagingException("UID FETCH", ex); return 0;
} }
}
});
// Build ranges int expunge = 0;
List<Pair<Long, Long>> ranges = new ArrayList<>(); for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++)
long first = -1; try {
long last = -1; if (perform_expunge && imessages[i].isSet(Flags.Flag.DELETED))
for (long uid : uids) expunge++;
if (first < 0) else
first = uid; uids.remove(ifolder.getUID(imessages[i]));
else if ((last < 0 ? first : last) + 1 == uid) } catch (MessageRemovedException ex) {
last = uid; Log.w(folder.name, ex);
else { } catch (Throwable ex) {
ranges.add(new Pair<>(first, last < 0 ? first : last)); Log.e(folder.name, ex);
first = uid; EntityLog.log(context, folder.name + " " + Log.formatThrowable(ex, false));
last = -1; db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
} }
if (first > 0)
ranges.add(new Pair<>(first, last < 0 ? first : last));
List<List<Pair<Long, Long>>> chunks = Helper.chunkList(ranges, SYNC_CHUNCK_SIZE); if (expunge > 0)
try {
Log.i(folder.name + " expunging=" + expunge);
ifolder.expunge();
} catch (Throwable ex) {
Log.w(ex);
}
Log.i(folder.name + " executing uid fetch count=" + uids.size() + if (uids.size() > 0) {
" ranges=" + ranges.size() + " chunks=" + chunks.size()); // This is done outside of JavaMail to prevent changed notifications
for (int c = 0; c < chunks.size(); c++) { if (!ifolder.isOpen())
List<Pair<Long, Long>> chunk = chunks.get(c); throw new FolderClosedException(ifolder, "UID FETCH");
Log.i(folder.name + " chunk #" + c + " size=" + chunk.size());
StringBuilder sb = new StringBuilder(); long getuid = SystemClock.elapsedRealtime();
for (Pair<Long, Long> range : chunk) { MessagingException ex = (MessagingException) ifolder.doCommand(new IMAPFolder.ProtocolCommand() {
if (sb.length() > 0) @Override
sb.append(','); public Object doCommand(IMAPProtocol protocol) {
if (range.first.equals(range.second)) try {
sb.append(range.first); protocol.select(folder.name);
else } catch (ProtocolException ex) {
sb.append(range.first).append(':').append(range.second); return new MessagingException("UID FETCH", ex);
} }
String command = "UID FETCH " + sb + " (UID FLAGS)";
Response[] responses = protocol.command(command, null);
if (responses.length > 0 && responses[responses.length - 1].isOK()) {
for (Response response : responses)
if (response instanceof FetchResponse) {
FetchResponse fr = (FetchResponse) response;
UID uid = fr.getItem(UID.class);
FLAGS flags = fr.getItem(FLAGS.class);
if (uid == null || flags == null)
continue;
if (perform_expunge && flags.contains(Flags.Flag.DELETED))
continue;
uids.remove(uid.uid);
if (force) {
EntityMessage message = db.message().getMessageByUid(folder.id, uid.uid);
if (message != null) {
boolean update = false;
boolean seen = flags.contains(Flags.Flag.SEEN);
boolean answered = flags.contains(Flags.Flag.ANSWERED);
boolean flagged = flags.contains(Flags.Flag.FLAGGED);
boolean deleted = flags.contains(Flags.Flag.DELETED);
if (message.seen != seen) {
update = true;
message.seen = seen;
message.ui_seen = seen;
Log.i("UID fetch seen=" + seen);
}
if (message.answered != answered) {
update = true;
message.answered = answered;
message.ui_answered = answered;
Log.i("UID fetch answered=" + answered);
}
if (message.flagged != flagged) {
update = true;
message.flagged = flagged;
message.ui_flagged = flagged;
Log.i("UID fetch flagged=" + flagged);
}
if (message.deleted != deleted) {
update = true;
message.deleted = deleted;
message.ui_deleted = deleted;
Log.i("UID fetch deleted=" + deleted);
}
if (update) // Build ranges
db.message().updateMessage(message); List<Pair<Long, Long>> ranges = new ArrayList<>();
long first = -1;
long last = -1;
for (long uid : uids)
if (first < 0)
first = uid;
else if ((last < 0 ? first : last) + 1 == uid)
last = uid;
else {
ranges.add(new Pair<>(first, last < 0 ? first : last));
first = uid;
last = -1;
}
if (first > 0)
ranges.add(new Pair<>(first, last < 0 ? first : last));
List<List<Pair<Long, Long>>> chunks = Helper.chunkList(ranges, SYNC_CHUNCK_SIZE);
Log.i(folder.name + " executing uid fetch count=" + uids.size() +
" ranges=" + ranges.size() + " chunks=" + chunks.size());
for (int c = 0; c < chunks.size(); c++) {
List<Pair<Long, Long>> chunk = chunks.get(c);
Log.i(folder.name + " chunk #" + c + " size=" + chunk.size());
StringBuilder sb = new StringBuilder();
for (Pair<Long, Long> range : chunk) {
if (sb.length() > 0)
sb.append(',');
if (range.first.equals(range.second))
sb.append(range.first);
else
sb.append(range.first).append(':').append(range.second);
}
String command = "UID FETCH " + sb + " (UID FLAGS)";
Response[] responses = protocol.command(command, null);
if (responses.length > 0 && responses[responses.length - 1].isOK()) {
for (Response response : responses)
if (response instanceof FetchResponse) {
FetchResponse fr = (FetchResponse) response;
UID uid = fr.getItem(UID.class);
FLAGS flags = fr.getItem(FLAGS.class);
if (uid == null || flags == null)
continue;
if (perform_expunge && flags.contains(Flags.Flag.DELETED))
continue;
uids.remove(uid.uid);
if (force) {
EntityMessage message = db.message().getMessageByUid(folder.id, uid.uid);
if (message != null) {
boolean update = false;
boolean seen = flags.contains(Flags.Flag.SEEN);
boolean answered = flags.contains(Flags.Flag.ANSWERED);
boolean flagged = flags.contains(Flags.Flag.FLAGGED);
boolean deleted = flags.contains(Flags.Flag.DELETED);
if (message.seen != seen) {
update = true;
message.seen = seen;
message.ui_seen = seen;
Log.i("UID fetch seen=" + seen);
}
if (message.answered != answered) {
update = true;
message.answered = answered;
message.ui_answered = answered;
Log.i("UID fetch answered=" + answered);
}
if (message.flagged != flagged) {
update = true;
message.flagged = flagged;
message.ui_flagged = flagged;
Log.i("UID fetch flagged=" + flagged);
}
if (message.deleted != deleted) {
update = true;
message.deleted = deleted;
message.ui_deleted = deleted;
Log.i("UID fetch deleted=" + deleted);
}
if (update)
db.message().updateMessage(message);
}
} }
} }
} } else {
} else { for (Response response : responses)
for (Response response : responses) if (response.isBYE())
if (response.isBYE()) return new MessagingException("UID FETCH", new IOException(response.toString()));
return new MessagingException("UID FETCH", new IOException(response.toString())); else if (response.isNO() || response.isBAD())
else if (response.isNO() || response.isBAD()) return new MessagingException(response.toString());
return new MessagingException(response.toString()); return new MessagingException("UID FETCH failed");
return new MessagingException("UID FETCH failed"); }
} }
}
return null; return null;
} }
}); });
if (ex != null) if (ex != null)
throw ex; throw ex;
stats.uids = uids.size(); stats.uids = uids.size();
stats.uids_ms = (SystemClock.elapsedRealtime() - getuid); stats.uids_ms = (SystemClock.elapsedRealtime() - getuid);
Log.i(folder.name + " remote uids=" + stats.uids_ms + " ms"); Log.i(folder.name + " remote uids=" + stats.uids_ms + " ms");
} }
// Delete local messages not at remote // Delete local messages not at remote
Log.i(folder.name + " delete=" + uids.size()); Log.i(folder.name + " delete=" + uids.size());
for (Long uid : uids) { for (Long uid : uids) {
int count = db.message().deleteMessage(folder.id, uid); int count = db.message().deleteMessage(folder.id, uid);
Log.i(folder.name + " delete local uid=" + uid + " count=" + count); Log.i(folder.name + " delete local uid=" + uid + " count=" + count);
} }
List<EntityRule> rules = db.rule().getEnabledRules(folder.id); List<EntityRule> rules = db.rule().getEnabledRules(folder.id);
fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.ENVELOPE);
//fp.add(FetchProfile.Item.FLAGS); //fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
//fp.add(UIDFolder.FetchProfileItem.UID); //fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.HEADERS); fp.add(IMAPFolder.FetchProfileItem.HEADERS);
//fp.add(IMAPFolder.FetchProfileItem.MESSAGE); //fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
fp.add(FetchProfile.Item.SIZE); fp.add(FetchProfile.Item.SIZE);
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE); fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
if (account.isGmail()) if (account.isGmail())
fp.add(GmailFolder.FetchProfileItem.THRID); fp.add(GmailFolder.FetchProfileItem.THRID);
// Add/update local messages // Add/update local messages
int synced = 0; int synced = 0;
Long[] ids = new Long[imessages.length]; Log.i(folder.name + " add=" + imessages.length);
Log.i(folder.name + " add=" + imessages.length); for (int i = imessages.length - 1; i >= 0 && state.isRunning() && state.isRecoverable(); i -= SYNC_BATCH_SIZE) {
for (int i = imessages.length - 1; i >= 0 && state.isRunning() && state.isRecoverable(); i -= SYNC_BATCH_SIZE) { int from = Math.max(0, i - SYNC_BATCH_SIZE + 1);
int from = Math.max(0, i - SYNC_BATCH_SIZE + 1); Message[] isub = Arrays.copyOfRange(imessages, from, i + 1);
Message[] isub = Arrays.copyOfRange(imessages, from, i + 1);
// Full fetch new/changed messages only
List<Message> full = new ArrayList<>();
for (Message imessage : isub) {
long uid = ifolder.getUID(imessage); // already fetched
EntityMessage message = db.message().getMessageByUid(folder.id, uid);
if (message == null)
full.add(imessage);
}
if (full.size() > 0) {
long headers = SystemClock.elapsedRealtime();
ifolder.fetch(full.toArray(new Message[0]), fp);
stats.headers += full.size();
stats.headers_ms += (SystemClock.elapsedRealtime() - headers);
Log.i(folder.name + " fetched headers=" + full.size() + " " + stats.headers_ms + " ms");
}
int free = Log.getFreeMemMb(); // Full fetch new/changed messages only
Map<String, String> crumb = new HashMap<>(); List<Message> full = new ArrayList<>();
crumb.put("account", account.id + ":" + account.protocol); for (Message imessage : isub) {
crumb.put("folder", folder.id + ":" + folder.type); long uid = ifolder.getUID(imessage); // already fetched
crumb.put("start", Integer.toString(from)); EntityMessage message = db.message().getMessageByUid(folder.id, uid);
crumb.put("end", Integer.toString(i)); if (message == null)
crumb.put("free", Integer.toString(free)); full.add(imessage);
crumb.put("partial", Boolean.toString(account.partial_fetch)); }
Log.breadcrumb("sync", crumb); if (full.size() > 0) {
Log.i("Sync " + from + ".." + i + " free=" + free); long headers = SystemClock.elapsedRealtime();
ifolder.fetch(full.toArray(new Message[0]), fp);
for (int j = isub.length - 1; j >= 0 && state.isRunning() && state.isRecoverable(); j--) stats.headers += full.size();
try { stats.headers_ms += (SystemClock.elapsedRealtime() - headers);
// Some providers erroneously return old messages Log.i(folder.name + " fetched headers=" + full.size() + " " + stats.headers_ms + " ms");
if (full.contains(isub[j])) }
try {
Date received = isub[j].getReceivedDate();
boolean unseen = (sync_unseen && !isub[j].isSet(Flags.Flag.SEEN));
boolean flagged = (sync_flagged && isub[j].isSet(Flags.Flag.FLAGGED));
if (received != null && received.getTime() < keep_time && !unseen && !flagged) {
long uid = ifolder.getUID(isub[j]);
Log.i(folder.name + " Skipping old uid=" + uid + " date=" + received);
ids[from + j] = null;
continue;
}
} catch (Throwable ex) {
Log.w(ex);
}
EntityMessage message = synchronizeMessage( int free = Log.getFreeMemMb();
context, Map<String, String> crumb = new HashMap<>();
account, folder, crumb.put("account", account.id + ":" + account.protocol);
istore, ifolder, (MimeMessage) isub[j], crumb.put("folder", folder.id + ":" + folder.type);
false, download && initialize == 0, crumb.put("start", Integer.toString(from));
rules, state, stats); crumb.put("end", Integer.toString(i));
ids[from + j] = (message == null || message.ui_hide ? null : message.id); crumb.put("free", Integer.toString(free));
crumb.put("partial", Boolean.toString(account.partial_fetch));
Log.breadcrumb("sync", crumb);
Log.i("Sync " + from + ".." + i + " free=" + free);
if (message != null && full.contains(isub[j])) for (int j = isub.length - 1; j >= 0 && state.isRunning() && state.isRecoverable(); j--)
if ((++synced % SYNC_YIELD_COUNT) == 0) try {
// Some providers erroneously return old messages
if (full.contains(isub[j]))
try { try {
Log.i(folder.name + " yield synced=" + synced); Date received = isub[j].getReceivedDate();
Thread.sleep(SYNC_YIELD_DURATION); boolean unseen = (sync_unseen && !isub[j].isSet(Flags.Flag.SEEN));
} catch (InterruptedException ex) { boolean flagged = (sync_flagged && isub[j].isSet(Flags.Flag.FLAGGED));
if (received != null && received.getTime() < keep_time && !unseen && !flagged) {
long uid = ifolder.getUID(isub[j]);
Log.i(folder.name + " Skipping old uid=" + uid + " date=" + received);
ids[from + j] = null;
continue;
}
} catch (Throwable ex) {
Log.w(ex); Log.w(ex);
} }
} catch (MessageRemovedException ex) {
Log.w(folder.name, ex); EntityMessage message = synchronizeMessage(
} catch (FolderClosedException ex) { context,
throw ex; account, folder,
} catch (IOException ex) { istore, ifolder, (MimeMessage) isub[j],
if (ex.getCause() instanceof MessagingException) { false, download && initialize == 0,
rules, state, stats);
ids[from + j] = (message == null || message.ui_hide ? null : message.id);
if (message != null && full.contains(isub[j]))
if ((++synced % SYNC_YIELD_COUNT) == 0)
try {
Log.i(folder.name + " yield synced=" + synced);
Thread.sleep(SYNC_YIELD_DURATION);
} catch (InterruptedException ex) {
Log.w(ex);
}
} catch (MessageRemovedException ex) {
Log.w(folder.name, ex); Log.w(folder.name, ex);
db.folder().setFolderError(folder.id, Log.formatThrowable(ex)); } catch (FolderClosedException ex) {
} else
throw ex; throw ex;
} catch (Throwable ex) { } catch (IOException ex) {
Log.e(folder.name, ex); if (ex.getCause() instanceof MessagingException) {
db.folder().setFolderError(folder.id, Log.formatThrowable(ex)); Log.w(folder.name, ex);
} finally { db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
// Free memory } else
((IMAPMessage) isub[j]).invalidateHeaders(); throw ex;
} } catch (Throwable ex) {
Log.e(folder.name, ex);
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
} finally {
// Free memory
((IMAPMessage) isub[j]).invalidateHeaders();
}
}
} }
// Delete not synchronized messages without uid // Delete not synchronized messages without uid

@ -296,6 +296,12 @@ public interface DaoMessage {
" AND uid = :uid") " AND uid = :uid")
EntityMessage getMessageByUid(long folder, long uid); EntityMessage getMessageByUid(long folder, long uid);
@Query("SELECT id" +
" FROM message" +
" WHERE folder = :folder" +
" AND uid = :uid")
Long getMessageMsgIdByUid(long folder, long uid);
@Query("SELECT id" + @Query("SELECT id" +
" FROM message" + " FROM message" +
" WHERE folder = :folder" + " WHERE folder = :folder" +

@ -112,6 +112,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private SwitchCompat swDebug; private SwitchCompat swDebug;
private SwitchCompat swQueries; private SwitchCompat swQueries;
private SwitchCompat swWal; private SwitchCompat swWal;
private SwitchCompat swModSeq;
private SwitchCompat swExpunge; private SwitchCompat swExpunge;
private SwitchCompat swAuthPlain; private SwitchCompat swAuthPlain;
private SwitchCompat swAuthLogin; private SwitchCompat swAuthLogin;
@ -139,7 +140,9 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
"classification", "class_min_probability", "class_min_difference", "classification", "class_min_probability", "class_min_difference",
"language", "watchdog", "updates", "language", "watchdog", "updates",
"experiments", "wal", "query_threads", "crash_reports", "cleanup_attachments", "experiments", "wal", "query_threads", "crash_reports", "cleanup_attachments",
"protocol", "debug", "log_level", "perform_expunge", "auth_plain", "auth_login", "auth_ntlm", "auth_sasl" "protocol", "debug", "log_level",
"use_modseq", "perform_expunge",
"auth_plain", "auth_login", "auth_ntlm", "auth_sasl"
}; };
private final static String[] RESET_QUESTIONS = new String[]{ private final static String[] RESET_QUESTIONS = new String[]{
@ -213,6 +216,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swDebug = view.findViewById(R.id.swDebug); swDebug = view.findViewById(R.id.swDebug);
swQueries = view.findViewById(R.id.swQueries); swQueries = view.findViewById(R.id.swQueries);
swWal = view.findViewById(R.id.swWal); swWal = view.findViewById(R.id.swWal);
swModSeq = view.findViewById(R.id.swModSeq);
swExpunge = view.findViewById(R.id.swExpunge); swExpunge = view.findViewById(R.id.swExpunge);
swAuthPlain = view.findViewById(R.id.swAuthPlain); swAuthPlain = view.findViewById(R.id.swAuthPlain);
swAuthLogin = view.findViewById(R.id.swAuthLogin); swAuthLogin = view.findViewById(R.id.swAuthLogin);
@ -529,11 +533,19 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
} }
}); });
swModSeq.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("use_modseq", checked).apply();
ServiceSynchronize.reload(compoundButton.getContext(), null, true, "use_modseq");
}
});
swExpunge.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { swExpunge.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("perform_expunge", checked).apply(); prefs.edit().putBoolean("perform_expunge", checked).apply();
ServiceSynchronize.reload(getContext(), null, true, "perform_expunge"); ServiceSynchronize.reload(compoundButton.getContext(), null, true, "perform_expunge");
} }
}); });
@ -918,6 +930,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swDebug.setChecked(prefs.getBoolean("debug", false)); swDebug.setChecked(prefs.getBoolean("debug", false));
swQueries.setChecked(prefs.getInt("query_threads", 4) < 4); swQueries.setChecked(prefs.getInt("query_threads", 4) < 4);
swWal.setChecked(prefs.getBoolean("wal", true)); swWal.setChecked(prefs.getBoolean("wal", true));
swModSeq.setChecked(prefs.getBoolean("use_modseq", BuildConfig.DEBUG));
swExpunge.setChecked(prefs.getBoolean("perform_expunge", true)); swExpunge.setChecked(prefs.getBoolean("perform_expunge", true));
swAuthPlain.setChecked(prefs.getBoolean("auth_plain", true)); swAuthPlain.setChecked(prefs.getBoolean("auth_plain", true));
swAuthLogin.setChecked(prefs.getBoolean("auth_login", true)); swAuthLogin.setChecked(prefs.getBoolean("auth_login", true));

@ -529,6 +529,18 @@
app:layout_constraintTop_toBottomOf="@id/tvQueriesRemark" app:layout_constraintTop_toBottomOf="@id/tvQueriesRemark"
app:switchPadding="12dp" /> app:switchPadding="12dp" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swModSeq"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:checked="true"
android:text="@string/title_advanced_modseq"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swWal"
app:switchPadding="12dp" />
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/swExpunge" android:id="@+id/swExpunge"
android:layout_width="0dp" android:layout_width="0dp"
@ -538,7 +550,7 @@
android:text="@string/title_advanced_expunge" android:text="@string/title_advanced_expunge"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swWal" app:layout_constraintTop_toBottomOf="@id/swModSeq"
app:switchPadding="12dp" /> app:switchPadding="12dp" />
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
@ -694,7 +706,7 @@
android:layout_height="0dp" android:layout_height="0dp"
app:constraint_referenced_ids=" app:constraint_referenced_ids="
swQueries,tvQueriesHint,tvQueriesRemark,swWal, swQueries,tvQueriesHint,tvQueriesRemark,swWal,
swExpunge, swModSeq,swExpunge,
swAuthPlain,swAuthLogin,swAuthNtlm,swAuthSasl, swAuthPlain,swAuthLogin,swAuthNtlm,swAuthSasl,
tvProcessors,tvMemoryClass,tvMemoryUsage,tvStorageUsage, tvProcessors,tvMemoryClass,tvMemoryUsage,tvStorageUsage,
tvFingerprint, tvFingerprint,

@ -536,6 +536,7 @@
<string name="title_advanced_language">Language</string> <string name="title_advanced_language">Language</string>
<string name="title_advanced_language_system">System</string> <string name="title_advanced_language_system">System</string>
<string name="title_advanced_watchdog">Periodically check if FairEmail is still active</string> <string name="title_advanced_watchdog">Periodically check if FairEmail is still active</string>
<string name="title_advanced_modseq" translatable="false">MODSEQ (debug only)</string>
<string name="title_advanced_expunge" translatable="false">EXPUNGE (debug only)</string> <string name="title_advanced_expunge" translatable="false">EXPUNGE (debug only)</string>
<string name="title_advanced_auth_plain" translatable="false">PLAIN (debug only)</string> <string name="title_advanced_auth_plain" translatable="false">PLAIN (debug only)</string>
<string name="title_advanced_auth_login" translatable="false">LOGIN (debug only)</string> <string name="title_advanced_auth_login" translatable="false">LOGIN (debug only)</string>

Loading…
Cancel
Save