Added experimental HIGHESTMODSEQ support

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

@ -2460,6 +2460,7 @@ class Core {
boolean sync_flagged = prefs.getBoolean("sync_flagged", false);
boolean sync_kept = prefs.getBoolean("sync_kept", true);
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);
Log.i(folder.name + " start sync after=" + sync_days + "/" + keep_days +
@ -2482,6 +2483,23 @@ class Core {
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
Calendar cal_sync = Calendar.getInstance();
cal_sync.add(Calendar.DAY_OF_MONTH, -sync_days);
@ -2562,284 +2580,324 @@ class Core {
stats.search_ms = (SystemClock.elapsedRealtime() - search);
Log.i(folder.name + " remote count=" + imessages.length + " search=" + stats.search_ms + " ms");
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");
// 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;
}
}
});
Long[] ids = new Long[imessages.length];
if (!modified) {
Log.i(folder.name + " quick check");
long fetch = SystemClock.elapsedRealtime();
int expunge = 0;
for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++)
try {
if (perform_expunge && imessages[i].isSet(Flags.Flag.DELETED))
expunge++;
else
uids.remove(ifolder.getUID(imessages[i]));
} 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));
}
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
ifolder.fetch(imessages, fp);
stats.flags = imessages.length;
stats.flags_ms = (SystemClock.elapsedRealtime() - fetch);
Log.i(folder.name + " remote fetched=" + stats.flags_ms + " ms");
if (expunge > 0)
try {
Log.i(folder.name + " expunging=" + expunge);
ifolder.expunge();
for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++) {
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) {
Log.w(ex);
modified = true;
db.folder().setFolderModSeq(folder.id, null);
}
if (uids.size() > 0) {
// This is done outside of JavaMail to prevent changed notifications
if (!ifolder.isOpen())
throw new FolderClosedException(ifolder, "UID FETCH");
if (uids.size() > 0) {
Log.i(folder.name + " remaining=" + uids.size());
modified = true;
}
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();
MessagingException ex = (MessagingException) ifolder.doCommand(new IMAPFolder.ProtocolCommand() {
// 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 Object doCommand(IMAPProtocol protocol) {
public int compare(Message m1, Message m2) {
try {
protocol.select(folder.name);
} catch (ProtocolException ex) {
return new MessagingException("UID FETCH", ex);
return Long.compare(ifolder.getUID(m1), ifolder.getUID(m2));
} catch (MessagingException ex) {
return 0;
}
}
});
// Build ranges
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));
int expunge = 0;
for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++)
try {
if (perform_expunge && imessages[i].isSet(Flags.Flag.DELETED))
expunge++;
else
uids.remove(ifolder.getUID(imessages[i]));
} 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));
}
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() +
" 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());
if (uids.size() > 0) {
// This is done outside of JavaMail to prevent changed notifications
if (!ifolder.isOpen())
throw new FolderClosedException(ifolder, "UID FETCH");
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);
long getuid = SystemClock.elapsedRealtime();
MessagingException ex = (MessagingException) ifolder.doCommand(new IMAPFolder.ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol protocol) {
try {
protocol.select(folder.name);
} catch (ProtocolException ex) {
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)
db.message().updateMessage(message);
// Build ranges
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 {
for (Response response : responses)
if (response.isBYE())
return new MessagingException("UID FETCH", new IOException(response.toString()));
else if (response.isNO() || response.isBAD())
return new MessagingException(response.toString());
return new MessagingException("UID FETCH failed");
} else {
for (Response response : responses)
if (response.isBYE())
return new MessagingException("UID FETCH", new IOException(response.toString()));
else if (response.isNO() || response.isBAD())
return new MessagingException(response.toString());
return new MessagingException("UID FETCH failed");
}
}
}
return null;
}
});
if (ex != null)
throw ex;
return null;
}
});
if (ex != null)
throw ex;
stats.uids = uids.size();
stats.uids_ms = (SystemClock.elapsedRealtime() - getuid);
Log.i(folder.name + " remote uids=" + stats.uids_ms + " ms");
}
stats.uids = uids.size();
stats.uids_ms = (SystemClock.elapsedRealtime() - getuid);
Log.i(folder.name + " remote uids=" + stats.uids_ms + " ms");
}
// Delete local messages not at remote
Log.i(folder.name + " delete=" + uids.size());
for (Long uid : uids) {
int count = db.message().deleteMessage(folder.id, uid);
Log.i(folder.name + " delete local uid=" + uid + " count=" + count);
}
// Delete local messages not at remote
Log.i(folder.name + " delete=" + uids.size());
for (Long uid : uids) {
int count = db.message().deleteMessage(folder.id, uid);
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.FLAGS);
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
//fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
//fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
fp.add(FetchProfile.Item.SIZE);
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
if (account.isGmail())
fp.add(GmailFolder.FetchProfileItem.THRID);
fp.add(FetchProfile.Item.ENVELOPE);
//fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
//fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
//fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
fp.add(FetchProfile.Item.SIZE);
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
if (account.isGmail())
fp.add(GmailFolder.FetchProfileItem.THRID);
// Add/update local messages
int synced = 0;
Long[] ids = new Long[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) {
int from = Math.max(0, i - SYNC_BATCH_SIZE + 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");
}
// Add/update local messages
int synced = 0;
Log.i(folder.name + " add=" + imessages.length);
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);
Message[] isub = Arrays.copyOfRange(imessages, from, i + 1);
int free = Log.getFreeMemMb();
Map<String, String> crumb = new HashMap<>();
crumb.put("account", account.id + ":" + account.protocol);
crumb.put("folder", folder.id + ":" + folder.type);
crumb.put("start", Integer.toString(from));
crumb.put("end", Integer.toString(i));
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);
for (int j = isub.length - 1; j >= 0 && state.isRunning() && state.isRecoverable(); j--)
try {
// Some providers erroneously return old messages
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);
}
// 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");
}
EntityMessage message = synchronizeMessage(
context,
account, folder,
istore, ifolder, (MimeMessage) isub[j],
false, download && initialize == 0,
rules, state, stats);
ids[from + j] = (message == null || message.ui_hide ? null : message.id);
int free = Log.getFreeMemMb();
Map<String, String> crumb = new HashMap<>();
crumb.put("account", account.id + ":" + account.protocol);
crumb.put("folder", folder.id + ":" + folder.type);
crumb.put("start", Integer.toString(from));
crumb.put("end", Integer.toString(i));
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]))
if ((++synced % SYNC_YIELD_COUNT) == 0)
for (int j = isub.length - 1; j >= 0 && state.isRunning() && state.isRecoverable(); j--)
try {
// Some providers erroneously return old messages
if (full.contains(isub[j]))
try {
Log.i(folder.name + " yield synced=" + synced);
Thread.sleep(SYNC_YIELD_DURATION);
} catch (InterruptedException ex) {
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);
}
} catch (MessageRemovedException ex) {
Log.w(folder.name, ex);
} catch (FolderClosedException ex) {
throw ex;
} catch (IOException ex) {
if (ex.getCause() instanceof MessagingException) {
EntityMessage message = synchronizeMessage(
context,
account, folder,
istore, ifolder, (MimeMessage) isub[j],
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);
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
} else
} catch (FolderClosedException ex) {
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();
}
} catch (IOException ex) {
if (ex.getCause() instanceof MessagingException) {
Log.w(folder.name, ex);
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
} else
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

@ -296,6 +296,12 @@ public interface DaoMessage {
" AND uid = :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" +
" FROM message" +
" WHERE folder = :folder" +

@ -112,6 +112,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private SwitchCompat swDebug;
private SwitchCompat swQueries;
private SwitchCompat swWal;
private SwitchCompat swModSeq;
private SwitchCompat swExpunge;
private SwitchCompat swAuthPlain;
private SwitchCompat swAuthLogin;
@ -139,7 +140,9 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
"classification", "class_min_probability", "class_min_difference",
"language", "watchdog", "updates",
"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[]{
@ -213,6 +216,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swDebug = view.findViewById(R.id.swDebug);
swQueries = view.findViewById(R.id.swQueries);
swWal = view.findViewById(R.id.swWal);
swModSeq = view.findViewById(R.id.swModSeq);
swExpunge = view.findViewById(R.id.swExpunge);
swAuthPlain = view.findViewById(R.id.swAuthPlain);
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() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
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));
swQueries.setChecked(prefs.getInt("query_threads", 4) < 4);
swWal.setChecked(prefs.getBoolean("wal", true));
swModSeq.setChecked(prefs.getBoolean("use_modseq", BuildConfig.DEBUG));
swExpunge.setChecked(prefs.getBoolean("perform_expunge", true));
swAuthPlain.setChecked(prefs.getBoolean("auth_plain", true));
swAuthLogin.setChecked(prefs.getBoolean("auth_login", true));

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

@ -536,6 +536,7 @@
<string name="title_advanced_language">Language</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_modseq" translatable="false">MODSEQ (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_login" translatable="false">LOGIN (debug only)</string>

Loading…
Cancel
Save