mirror of
https://github.com/M66B/FairEmail.git
synced 2026-04-05 08:33:37 +02:00
@@ -26,23 +26,23 @@ import android.util.Log;
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
import com.sun.mail.imap.IMAPMessage;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
import com.sun.mail.util.FolderClosedIOException;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.mail.FetchProfile;
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.FolderClosedException;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessageRemovedException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.UIDFolder;
|
||||
import javax.mail.search.AndTerm;
|
||||
import javax.mail.search.BodyTerm;
|
||||
import javax.mail.search.ComparisonTerm;
|
||||
import javax.mail.search.FromStringTerm;
|
||||
import javax.mail.search.OrTerm;
|
||||
import javax.mail.search.ReceivedDateTerm;
|
||||
import javax.mail.search.SubjectTerm;
|
||||
|
||||
import androidx.lifecycle.GenericLifecycleObserver;
|
||||
@@ -54,16 +54,16 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
|
||||
private Context context;
|
||||
private long fid;
|
||||
private String search;
|
||||
private int pageSize;
|
||||
private Handler mainHandler;
|
||||
private IBoundaryCallbackMessages intf;
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
|
||||
|
||||
private boolean enabled = false;
|
||||
private IMAPStore istore = null;
|
||||
private IMAPFolder ifolder = null;
|
||||
private Message[] imessages = null;
|
||||
|
||||
private static final int SEARCH_PAGE_SIZE = 5;
|
||||
private int index;
|
||||
private boolean searching = false;
|
||||
|
||||
interface IBoundaryCallbackMessages {
|
||||
void onLoading();
|
||||
@@ -73,11 +73,12 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
|
||||
void onError(Context context, Throwable ex);
|
||||
}
|
||||
|
||||
BoundaryCallbackMessages(Context context, LifecycleOwner owner, long folder, String search, IBoundaryCallbackMessages intf) {
|
||||
this.context = context;
|
||||
BoundaryCallbackMessages(Context _context, LifecycleOwner owner, long folder, String search, int pageSize, IBoundaryCallbackMessages intf) {
|
||||
this.context = _context;
|
||||
this.fid = folder;
|
||||
this.search = search;
|
||||
this.mainHandler = new Handler(context.getMainLooper());
|
||||
this.pageSize = pageSize;
|
||||
this.mainHandler = new Handler(_context.getMainLooper());
|
||||
this.intf = intf;
|
||||
|
||||
owner.getLifecycle().addObserver(new GenericLifecycleObserver() {
|
||||
@@ -88,12 +89,14 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(Helper.TAG, "Boundary close");
|
||||
DB.getInstance(context).message().deleteFoundMessages();
|
||||
try {
|
||||
if (istore != null)
|
||||
istore.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} finally {
|
||||
context = null;
|
||||
istore = null;
|
||||
ifolder = null;
|
||||
imessages = null;
|
||||
@@ -104,23 +107,29 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
|
||||
});
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
boolean isSearching() {
|
||||
return searching;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onZeroItemsLoaded() {
|
||||
Log.i(Helper.TAG, "onZeroItemsLoaded");
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemAtEndLoaded(final TupleMessageEx itemAtEnd) {
|
||||
Log.i(Helper.TAG, "onItemAtEndLoaded enabled=" + enabled);
|
||||
if (!enabled)
|
||||
return;
|
||||
load(itemAtEnd.received);
|
||||
Log.i(Helper.TAG, "onItemAtEndLoaded");
|
||||
load();
|
||||
}
|
||||
|
||||
void load(final long before) {
|
||||
private void load() {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
searching = true;
|
||||
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -139,23 +148,34 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
|
||||
|
||||
Log.i(Helper.TAG, "Boundary connecting account=" + account.name);
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
Helper.connect(context, istore, account);
|
||||
|
||||
Log.i(Helper.TAG, "Boundary opening folder=" + folder.name);
|
||||
ifolder = (IMAPFolder) istore.getFolder(folder.name);
|
||||
ifolder.open(Folder.READ_WRITE);
|
||||
|
||||
Log.i(Helper.TAG, "Boundary searching=" + search + " before=" + new Date(before));
|
||||
imessages = ifolder.search(
|
||||
new AndTerm(
|
||||
new ReceivedDateTerm(ComparisonTerm.LT, new Date(before)),
|
||||
new OrTerm(
|
||||
new FromStringTerm(search),
|
||||
new OrTerm(
|
||||
new SubjectTerm(search),
|
||||
new BodyTerm(search)))));
|
||||
Log.i(Helper.TAG, "Boundary searching=" + search);
|
||||
if (search == null)
|
||||
imessages = ifolder.getMessages();
|
||||
else
|
||||
imessages = ifolder.search(
|
||||
new OrTerm(
|
||||
new FromStringTerm(search),
|
||||
new OrTerm(
|
||||
new SubjectTerm(search),
|
||||
new BodyTerm(search))));
|
||||
Log.i(Helper.TAG, "Boundary found messages=" + imessages.length);
|
||||
|
||||
index = imessages.length - 1;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
while (index >= 0 && count < pageSize) {
|
||||
Log.i(Helper.TAG, "Boundary index=" + index);
|
||||
int from = Math.max(0, index - (pageSize - count) + 1);
|
||||
Message[] isub = Arrays.copyOfRange(imessages, from, index + 1);
|
||||
index -= (pageSize - count);
|
||||
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
fp.add(FetchProfile.Item.FLAGS);
|
||||
@@ -164,25 +184,43 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
|
||||
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
|
||||
fp.add(FetchProfile.Item.SIZE);
|
||||
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
|
||||
ifolder.fetch(imessages, fp);
|
||||
ifolder.fetch(isub, fp);
|
||||
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
for (int j = isub.length - 1; j >= 0; j--)
|
||||
try {
|
||||
long uid = ifolder.getUID(isub[j]);
|
||||
Log.i(Helper.TAG, "Boundary sync uid=" + uid);
|
||||
if (db.message().getMessageByUid(fid, uid) == null) {
|
||||
ServiceSynchronize.synchronizeMessage(context, folder, ifolder, (IMAPMessage) isub[j], true);
|
||||
count++;
|
||||
}
|
||||
} catch (MessageRemovedException ex) {
|
||||
Log.w(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} catch (FolderClosedException ex) {
|
||||
throw ex;
|
||||
} catch (FolderClosedIOException ex) {
|
||||
throw ex;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} finally {
|
||||
((IMAPMessage) isub[j]).invalidateHeaders();
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
int index = imessages.length - 1;
|
||||
while (index >= 0) {
|
||||
if (imessages[index].getReceivedDate().getTime() < before)
|
||||
try {
|
||||
Log.i(Helper.TAG, "Boundary sync uid=" + ifolder.getUID(imessages[index]));
|
||||
ServiceSynchronize.synchronizeMessage(context, folder, ifolder, (IMAPMessage) imessages[index], true);
|
||||
if (++count >= SEARCH_PAGE_SIZE)
|
||||
break;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
EntityOperation.process(context); // download small attachments
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
intf.onLoaded();
|
||||
}
|
||||
});
|
||||
|
||||
Log.i(Helper.TAG, "Boundary done");
|
||||
} catch (final Throwable ex) {
|
||||
@@ -194,12 +232,7 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
intf.onLoaded();
|
||||
}
|
||||
});
|
||||
searching = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -218,9 +218,6 @@ public interface DaoMessage {
|
||||
@Query("UPDATE message SET ui_found = :found WHERE id = :id")
|
||||
int setMessageFound(long id, boolean found);
|
||||
|
||||
@Query("UPDATE message SET ui_found = 0 WHERE folder = :folder")
|
||||
int resetFound(long folder);
|
||||
|
||||
@Query("UPDATE message SET content = :content WHERE id = :id")
|
||||
int setMessageContent(long id, boolean content);
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ public class FragmentFolder extends FragmentEx {
|
||||
Properties props = MessageHelper.getSessionProperties(context, account.auth_type);
|
||||
Session isession = Session.getInstance(props, null);
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
Helper.connect(context, istore, account);
|
||||
|
||||
if (folder == null) {
|
||||
Log.i(Helper.TAG, "Creating folder=" + name);
|
||||
@@ -239,7 +239,7 @@ public class FragmentFolder extends FragmentEx {
|
||||
Properties props = MessageHelper.getSessionProperties(context, account.auth_type);
|
||||
Session isession = Session.getInstance(props, null);
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
Helper.connect(context, istore, account);
|
||||
|
||||
IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name);
|
||||
ifolder.delete(false);
|
||||
|
||||
@@ -84,7 +84,6 @@ public class FragmentMessages extends FragmentEx {
|
||||
private AdapterMessage.ViewType viewType;
|
||||
private LiveData<PagedList<TupleMessageEx>> messages = null;
|
||||
|
||||
private SearchState searchState = SearchState.Reset;
|
||||
private BoundaryCallbackMessages searchCallback = null;
|
||||
|
||||
private ExecutorService executor = Executors.newCachedThreadPool(Helper.backgroundThreadFactory);
|
||||
@@ -93,8 +92,6 @@ public class FragmentMessages extends FragmentEx {
|
||||
private static final int SEARCH_PAGE_SIZE = 10;
|
||||
private static final int UNDO_TIMEOUT = 5000; // milliseconds
|
||||
|
||||
private enum SearchState {Reset, Database, Boundary}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -667,44 +664,47 @@ public class FragmentMessages extends FragmentEx {
|
||||
messages = new LivePagedListBuilder<>(db.message().pagedUnifiedInbox(sort, debug), MESSAGES_PAGE_SIZE).build();
|
||||
break;
|
||||
case FOLDER:
|
||||
messages = new LivePagedListBuilder<>(db.message().pagedFolder(folder, sort, false, debug), MESSAGES_PAGE_SIZE).build();
|
||||
if (searchCallback == null)
|
||||
searchCallback = new BoundaryCallbackMessages(
|
||||
getContext(), FragmentMessages.this,
|
||||
folder, null, MESSAGES_PAGE_SIZE,
|
||||
new BoundaryCallbackMessages.IBoundaryCallbackMessages() {
|
||||
@Override
|
||||
public void onLoading() {
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Context context, Throwable ex) {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
Helper.unexpectedError(context, ex);
|
||||
}
|
||||
});
|
||||
|
||||
PagedList.Config config = new PagedList.Config.Builder()
|
||||
.setPageSize(MESSAGES_PAGE_SIZE)
|
||||
.setPrefetchDistance(MESSAGES_PAGE_SIZE)
|
||||
.build();
|
||||
LivePagedListBuilder<Integer, TupleMessageEx> builder = new LivePagedListBuilder<>(
|
||||
db.message().pagedFolder(folder, sort, false, debug), config);
|
||||
builder.setBoundaryCallback(searchCallback);
|
||||
messages = builder.build();
|
||||
|
||||
break;
|
||||
case THREAD:
|
||||
messages = new LivePagedListBuilder<>(db.message().pagedThread(thread, sort, debug), MESSAGES_PAGE_SIZE).build();
|
||||
break;
|
||||
}
|
||||
|
||||
messages.observe(getViewLifecycleOwner(), new Observer<PagedList<TupleMessageEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable PagedList<TupleMessageEx> messages) {
|
||||
if (messages == null ||
|
||||
(viewType == AdapterMessage.ViewType.THREAD && messages.size() == 0)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(Helper.TAG, "Submit messages=" + messages.size());
|
||||
adapter.submitList(messages);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
if (messages.size() == 0) {
|
||||
tvNoEmail.setVisibility(View.VISIBLE);
|
||||
rvMessage.setVisibility(View.GONE);
|
||||
} else {
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
rvMessage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.i(Helper.TAG, "Search state=" + searchState);
|
||||
|
||||
if (searchCallback == null)
|
||||
searchCallback = new BoundaryCallbackMessages(
|
||||
getContext(), FragmentMessages.this,
|
||||
folder, search,
|
||||
folder, search, SEARCH_PAGE_SIZE,
|
||||
new BoundaryCallbackMessages.IBoundaryCallbackMessages() {
|
||||
@Override
|
||||
public void onLoading() {
|
||||
@@ -718,79 +718,49 @@ public class FragmentMessages extends FragmentEx {
|
||||
|
||||
@Override
|
||||
public void onError(Context context, Throwable ex) {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
Helper.unexpectedError(context, ex);
|
||||
}
|
||||
});
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("folder", folder);
|
||||
args.putString("search", search);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onLoad(Context context, Bundle args) {
|
||||
if (searchState == SearchState.Reset) {
|
||||
long folder = args.getLong("folder");
|
||||
DB.getInstance(context).message().resetFound(folder);
|
||||
searchState = SearchState.Database;
|
||||
Log.i(Helper.TAG, "Search reset done");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(final Bundle args, Void data) {
|
||||
LivePagedListBuilder<Integer, TupleMessageEx> builder = new LivePagedListBuilder<>(db.message().pagedFolder(folder, "time", true, false), SEARCH_PAGE_SIZE);
|
||||
builder.setBoundaryCallback(searchCallback);
|
||||
LiveData<PagedList<TupleMessageEx>> messages = builder.build();
|
||||
messages.observe(getViewLifecycleOwner(), new Observer<PagedList<TupleMessageEx>>() {
|
||||
@Override
|
||||
public void onChanged(PagedList<TupleMessageEx> messages) {
|
||||
Log.i(Helper.TAG, "Submit found messages=" + messages.size());
|
||||
adapter.submitList(messages);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
new SimpleTask<Long>() {
|
||||
@Override
|
||||
protected Long onLoad(Context context, Bundle args) throws Throwable {
|
||||
long last = 0;
|
||||
if (searchState == SearchState.Database) {
|
||||
last = new Date().getTime();
|
||||
long folder = args.getLong("folder");
|
||||
String search = args.getString("search").toLowerCase();
|
||||
DB db = DB.getInstance(context);
|
||||
for (long id : db.message().getMessageIDs(folder)) {
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message != null) { // Message could be removed in the meantime
|
||||
String from = MessageHelper.getFormattedAddresses(message.from, true);
|
||||
if (from.toLowerCase().contains(search) ||
|
||||
message.subject.toLowerCase().contains(search) ||
|
||||
message.read(context).toLowerCase().contains(search)) {
|
||||
Log.i(Helper.TAG, "Search found id=" + id);
|
||||
db.message().setMessageFound(message.id, true);
|
||||
last = message.received;
|
||||
}
|
||||
}
|
||||
}
|
||||
searchState = SearchState.Boundary;
|
||||
Log.i(Helper.TAG, "Search database done");
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Long last) {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
searchCallback.setEnabled(true);
|
||||
if (last > 0)
|
||||
searchCallback.load(last);
|
||||
}
|
||||
}.load(FragmentMessages.this, args);
|
||||
}
|
||||
}.load(this, args);
|
||||
PagedList.Config config = new PagedList.Config.Builder()
|
||||
.setPageSize(SEARCH_PAGE_SIZE)
|
||||
.setPrefetchDistance(SEARCH_PAGE_SIZE)
|
||||
.build();
|
||||
LivePagedListBuilder<Integer, TupleMessageEx> builder = new LivePagedListBuilder<>(
|
||||
db.message().pagedFolder(folder, "time", true, false), config);
|
||||
builder.setBoundaryCallback(searchCallback);
|
||||
messages = builder.build();
|
||||
}
|
||||
|
||||
messages.observe(getViewLifecycleOwner(), new Observer<PagedList<TupleMessageEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable PagedList<TupleMessageEx> messages) {
|
||||
if (messages == null ||
|
||||
(viewType == AdapterMessage.ViewType.THREAD && messages.size() == 0)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(Helper.TAG, "Submit messages=" + messages.size());
|
||||
adapter.submitList(messages);
|
||||
|
||||
boolean searching = (searchCallback != null && searchCallback.isSearching());
|
||||
|
||||
if (!searching)
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
if (messages.size() == 0 && !searching) {
|
||||
tvNoEmail.setVisibility(View.VISIBLE);
|
||||
rvMessage.setVisibility(View.GONE);
|
||||
} else {
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
rvMessage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void onNewMessages() {
|
||||
|
||||
@@ -39,6 +39,7 @@ import android.widget.Spinner;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -53,7 +54,9 @@ import java.text.DecimalFormat;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.AuthenticationFailedException;
|
||||
import javax.mail.FolderClosedException;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -214,7 +217,20 @@ public class Helper {
|
||||
return filename.substring(index + 1);
|
||||
}
|
||||
|
||||
static String refreshToken(Context context, String type, String name, String current) {
|
||||
static void connect(Context context, IMAPStore istore, EntityAccount account) throws MessagingException {
|
||||
try {
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
} catch (AuthenticationFailedException ex) {
|
||||
if (account.auth_type == Helper.AUTH_TYPE_GMAIL) {
|
||||
account.password = Helper.refreshToken(context, "com.google", account.user, account.password);
|
||||
DB.getInstance(context).account().setAccountPassword(account.id, account.password);
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
} else
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private static String refreshToken(Context context, String type, String name, String current) {
|
||||
try {
|
||||
AccountManager am = AccountManager.get(context);
|
||||
Account[] accounts = am.getAccountsByType(type);
|
||||
@@ -313,7 +329,7 @@ public class Helper {
|
||||
}
|
||||
|
||||
static boolean isPro(Context context) {
|
||||
if (false && BuildConfig.DEBUG)
|
||||
if (true && BuildConfig.DEBUG)
|
||||
return true;
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("pro", false);
|
||||
}
|
||||
|
||||
@@ -94,11 +94,6 @@ public class JobDaily extends JobService {
|
||||
int logs = db.log().deleteLogs(before);
|
||||
Log.i(Helper.TAG, "Deleted logs=" + logs);
|
||||
|
||||
// Cleanup found messages
|
||||
Log.i(Helper.TAG, "Cleanup found messages");
|
||||
int found = db.message().deleteFoundMessages();
|
||||
Log.i(Helper.TAG, "Deleted found messages=" + found);
|
||||
|
||||
Log.i(Helper.TAG, "End daily job");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -560,12 +560,6 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
boolean debug = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("debug", false);
|
||||
System.setProperty("mail.socket.debug", Boolean.toString(debug));
|
||||
|
||||
// Refresh token
|
||||
if (account.auth_type == Helper.AUTH_TYPE_GMAIL) {
|
||||
account.password = Helper.refreshToken(this, "com.google", account.user, account.password);
|
||||
db.account().setAccountPassword(account.id, account.password);
|
||||
}
|
||||
|
||||
// Create session
|
||||
Properties props = MessageHelper.getSessionProperties(this, account.auth_type);
|
||||
final Session isession = Session.getInstance(props, null);
|
||||
@@ -647,8 +641,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
for (EntityFolder folder : db.folder().getFolders(account.id))
|
||||
db.folder().setFolderState(folder.id, null);
|
||||
db.account().setAccountState(account.id, "connecting");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
|
||||
Helper.connect(this, istore, account);
|
||||
db.account().setAccountState(account.id, "connected");
|
||||
db.account().setAccountError(account.id, null);
|
||||
|
||||
@@ -704,7 +697,14 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
|
||||
for (Message imessage : e.getMessages())
|
||||
try {
|
||||
long id = synchronizeMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, false);
|
||||
long id;
|
||||
try {
|
||||
db.beginTransaction();
|
||||
id = synchronizeMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, false);
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, id);
|
||||
} catch (MessageRemovedException ex) {
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
@@ -777,7 +777,14 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
fp.add(IMAPFolder.FetchProfileItem.FLAGS);
|
||||
ifolder.fetch(new Message[]{e.getMessage()}, fp);
|
||||
|
||||
long id = synchronizeMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), false);
|
||||
long id;
|
||||
try {
|
||||
db.beginTransaction();
|
||||
id = synchronizeMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), false);
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), id);
|
||||
} catch (MessageRemovedException ex) {
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
@@ -1235,12 +1242,6 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh token
|
||||
if (ident.auth_type == Helper.AUTH_TYPE_GMAIL) {
|
||||
ident.password = Helper.refreshToken(this, "com.google", ident.user, ident.password);
|
||||
db.identity().setIdentityPassword(ident.id, ident.password);
|
||||
}
|
||||
|
||||
// Create session
|
||||
Properties props = MessageHelper.getSessionProperties(this, ident.auth_type);
|
||||
final Session isession = Session.getInstance(props, null);
|
||||
@@ -1518,11 +1519,13 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
long headers = SystemClock.elapsedRealtime();
|
||||
ifolder.fetch(full.toArray(new Message[0]), fp);
|
||||
Log.i(Helper.TAG, folder.name + " fetched headers=" + full.size() +
|
||||
" " + (SystemClock.elapsedRealtime() - fetch) + " ms");
|
||||
" " + (SystemClock.elapsedRealtime() - headers) + " ms");
|
||||
|
||||
for (int j = isub.length - 1; j >= 0; j--)
|
||||
try {
|
||||
db.beginTransaction();
|
||||
ids[from + j] = synchronizeMessage(this, folder, ifolder, (IMAPMessage) isub[j], false);
|
||||
db.setTransactionSuccessful();
|
||||
} catch (MessageRemovedException ex) {
|
||||
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} catch (FolderClosedException ex) {
|
||||
@@ -1532,6 +1535,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
// Reduce memory usage
|
||||
((IMAPMessage) isub[j]).invalidateHeaders();
|
||||
}
|
||||
@@ -1590,155 +1594,148 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
boolean flagged = helper.getFlagged();
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
// Find message by uid (fast, no headers required)
|
||||
EntityMessage message = db.message().getMessageByUid(folder.id, uid);
|
||||
// Find message by uid (fast, no headers required)
|
||||
EntityMessage message = db.message().getMessageByUid(folder.id, uid);
|
||||
|
||||
// Find message by Message-ID (slow, headers required)
|
||||
// - messages in inbox have same id as message sent to self
|
||||
// - messages in archive have same id as original
|
||||
if (message == null) {
|
||||
// Will fetch headers within database transaction
|
||||
String msgid = helper.getMessageID();
|
||||
String[] refs = helper.getReferences();
|
||||
String reference = (refs.length == 1 && refs[0].indexOf(BuildConfig.APPLICATION_ID) > 0 ? refs[0] : msgid);
|
||||
Log.i(Helper.TAG, "Searching for " + msgid + " / " + reference);
|
||||
for (EntityMessage dup : db.message().getMessageByMsgId(folder.account, msgid, reference)) {
|
||||
EntityFolder dfolder = db.folder().getFolder(dup.folder);
|
||||
boolean outbox = EntityFolder.OUTBOX.equals(dfolder.type);
|
||||
Log.i(Helper.TAG, folder.name + " found as id=" + dup.id +
|
||||
" folder=" + dfolder.type + ":" + dup.folder + "/" + folder.type + ":" + folder.id);
|
||||
// Find message by Message-ID (slow, headers required)
|
||||
// - messages in inbox have same id as message sent to self
|
||||
// - messages in archive have same id as original
|
||||
if (message == null) {
|
||||
// Will fetch headers within database transaction
|
||||
String msgid = helper.getMessageID();
|
||||
String[] refs = helper.getReferences();
|
||||
String reference = (refs.length == 1 && refs[0].indexOf(BuildConfig.APPLICATION_ID) > 0 ? refs[0] : msgid);
|
||||
Log.i(Helper.TAG, "Searching for " + msgid + " / " + reference);
|
||||
for (EntityMessage dup : db.message().getMessageByMsgId(folder.account, msgid, reference)) {
|
||||
EntityFolder dfolder = db.folder().getFolder(dup.folder);
|
||||
boolean outbox = EntityFolder.OUTBOX.equals(dfolder.type);
|
||||
Log.i(Helper.TAG, folder.name + " found as id=" + dup.id +
|
||||
" folder=" + dfolder.type + ":" + dup.folder + "/" + folder.type + ":" + folder.id);
|
||||
|
||||
if (dup.folder.equals(folder.id) || outbox) {
|
||||
Log.i(Helper.TAG, folder.name + " found as id=" + dup.id + " uid=" + dup.uid + " msgid=" + msgid);
|
||||
dup.folder = folder.id;
|
||||
dup.uid = uid;
|
||||
if (TextUtils.isEmpty(dup.thread)) // outbox: only now the uid is known
|
||||
dup.thread = helper.getThreadId(uid);
|
||||
db.message().updateMessage(dup);
|
||||
message = dup;
|
||||
}
|
||||
if (dup.folder.equals(folder.id) || outbox) {
|
||||
Log.i(Helper.TAG, folder.name + " found as id=" + dup.id + " uid=" + dup.uid + " msgid=" + msgid);
|
||||
dup.folder = folder.id;
|
||||
dup.uid = uid;
|
||||
if (TextUtils.isEmpty(dup.thread)) // outbox: only now the uid is known
|
||||
dup.thread = helper.getThreadId(uid);
|
||||
db.message().updateMessage(dup);
|
||||
message = dup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
message = new EntityMessage();
|
||||
message.account = folder.account;
|
||||
message.folder = folder.id;
|
||||
message.uid = uid;
|
||||
|
||||
if (!EntityFolder.ARCHIVE.equals(folder.type)) {
|
||||
message.msgid = helper.getMessageID();
|
||||
if (TextUtils.isEmpty(message.msgid))
|
||||
Log.w(Helper.TAG, "No Message-ID id=" + message.id + " uid=" + message.uid);
|
||||
}
|
||||
|
||||
message.references = TextUtils.join(" ", helper.getReferences());
|
||||
message.inreplyto = helper.getInReplyTo();
|
||||
message.deliveredto = helper.getDeliveredTo();
|
||||
message.thread = helper.getThreadId(uid);
|
||||
message.from = helper.getFrom();
|
||||
message.to = helper.getTo();
|
||||
message.cc = helper.getCc();
|
||||
message.bcc = helper.getBcc();
|
||||
message.reply = helper.getReply();
|
||||
message.subject = imessage.getSubject();
|
||||
message.size = helper.getSize();
|
||||
message.content = false;
|
||||
message.received = imessage.getReceivedDate().getTime();
|
||||
message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime());
|
||||
message.seen = seen;
|
||||
message.ui_seen = seen;
|
||||
message.flagged = false;
|
||||
message.ui_flagged = false;
|
||||
message.ui_hide = false;
|
||||
message.ui_found = found;
|
||||
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
try {
|
||||
if (message.from != null)
|
||||
for (int i = 0; i < message.from.length; i++) {
|
||||
String email = ((InternetAddress) message.from[i]).getAddress();
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
cursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
|
||||
new String[]{
|
||||
ContactsContract.CommonDataKinds.Photo.CONTACT_ID,
|
||||
ContactsContract.Contacts.DISPLAY_NAME
|
||||
},
|
||||
ContactsContract.CommonDataKinds.Email.ADDRESS + " = ?",
|
||||
new String[]{email}, null);
|
||||
if (cursor.moveToNext()) {
|
||||
int colContactId = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo.CONTACT_ID);
|
||||
int colDisplayName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
|
||||
long contactId = cursor.getLong(colContactId);
|
||||
String displayName = cursor.getString(colDisplayName);
|
||||
|
||||
Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
|
||||
message.avatar = uri.toString();
|
||||
|
||||
if (!TextUtils.isEmpty(displayName))
|
||||
((InternetAddress) message.from[i]).setPersonal(displayName);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
message = new EntityMessage();
|
||||
message.account = folder.account;
|
||||
message.folder = folder.id;
|
||||
message.uid = uid;
|
||||
message.id = db.message().insertMessage(message);
|
||||
|
||||
if (!EntityFolder.ARCHIVE.equals(folder.type)) {
|
||||
message.msgid = helper.getMessageID();
|
||||
if (TextUtils.isEmpty(message.msgid))
|
||||
Log.w(Helper.TAG, "No Message-ID id=" + message.id + " uid=" + message.uid);
|
||||
}
|
||||
Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid);
|
||||
|
||||
message.references = TextUtils.join(" ", helper.getReferences());
|
||||
message.inreplyto = helper.getInReplyTo();
|
||||
message.deliveredto = helper.getDeliveredTo();
|
||||
message.thread = helper.getThreadId(uid);
|
||||
message.from = helper.getFrom();
|
||||
message.to = helper.getTo();
|
||||
message.cc = helper.getCc();
|
||||
message.bcc = helper.getBcc();
|
||||
message.reply = helper.getReply();
|
||||
message.subject = imessage.getSubject();
|
||||
message.size = helper.getSize();
|
||||
message.content = false;
|
||||
message.received = imessage.getReceivedDate().getTime();
|
||||
message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime());
|
||||
int sequence = 1;
|
||||
for (EntityAttachment attachment : helper.getAttachments()) {
|
||||
Log.i(Helper.TAG, folder.name + " attachment" +
|
||||
" seq=" + sequence + " name=" + attachment.name + " type=" + attachment.type);
|
||||
attachment.message = message.id;
|
||||
attachment.sequence = sequence++;
|
||||
attachment.id = db.attachment().insertAttachment(attachment);
|
||||
|
||||
if (message.size != null && attachment.size != null)
|
||||
message.size -= attachment.size;
|
||||
}
|
||||
|
||||
db.message().updateMessage(message);
|
||||
} else {
|
||||
if (message.seen != seen || message.seen != message.ui_seen) {
|
||||
message.seen = seen;
|
||||
message.ui_seen = seen;
|
||||
message.flagged = false;
|
||||
message.ui_flagged = false;
|
||||
message.ui_hide = false;
|
||||
message.ui_found = found;
|
||||
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
try {
|
||||
if (message.from != null)
|
||||
for (int i = 0; i < message.from.length; i++) {
|
||||
String email = ((InternetAddress) message.from[i]).getAddress();
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
cursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
|
||||
new String[]{
|
||||
ContactsContract.CommonDataKinds.Photo.CONTACT_ID,
|
||||
ContactsContract.Contacts.DISPLAY_NAME
|
||||
},
|
||||
ContactsContract.CommonDataKinds.Email.ADDRESS + " = ?",
|
||||
new String[]{email}, null);
|
||||
if (cursor.moveToNext()) {
|
||||
int colContactId = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo.CONTACT_ID);
|
||||
int colDisplayName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
|
||||
long contactId = cursor.getLong(colContactId);
|
||||
String displayName = cursor.getString(colDisplayName);
|
||||
|
||||
Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
|
||||
message.avatar = uri.toString();
|
||||
|
||||
if (!TextUtils.isEmpty(displayName))
|
||||
((InternetAddress) message.from[i]).setPersonal(displayName);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
message.id = db.message().insertMessage(message);
|
||||
|
||||
Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid);
|
||||
|
||||
int sequence = 1;
|
||||
for (EntityAttachment attachment : helper.getAttachments()) {
|
||||
Log.i(Helper.TAG, folder.name + " attachment" +
|
||||
" seq=" + sequence + " name=" + attachment.name + " type=" + attachment.type);
|
||||
attachment.message = message.id;
|
||||
attachment.sequence = sequence++;
|
||||
attachment.id = db.attachment().insertAttachment(attachment);
|
||||
|
||||
if (message.size != null && attachment.size != null)
|
||||
message.size -= attachment.size;
|
||||
}
|
||||
|
||||
db.message().updateMessage(message);
|
||||
} else {
|
||||
if (message.seen != seen || message.seen != message.ui_seen) {
|
||||
message.seen = seen;
|
||||
message.ui_seen = seen;
|
||||
db.message().updateMessage(message);
|
||||
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen);
|
||||
}
|
||||
|
||||
if (message.flagged != flagged || message.flagged != message.ui_flagged) {
|
||||
message.flagged = flagged;
|
||||
message.ui_flagged = flagged;
|
||||
db.message().updateMessage(message);
|
||||
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " flagged=" + flagged);
|
||||
}
|
||||
|
||||
if (message.ui_hide) {
|
||||
message.ui_hide = false;
|
||||
db.message().updateMessage(message);
|
||||
Log.i(Helper.TAG, folder.name + " unhidden id=" + message.id + " uid=" + message.uid);
|
||||
}
|
||||
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
if (message.flagged != flagged || message.flagged != message.ui_flagged) {
|
||||
message.flagged = flagged;
|
||||
message.ui_flagged = flagged;
|
||||
db.message().updateMessage(message);
|
||||
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " flagged=" + flagged);
|
||||
}
|
||||
|
||||
return message.id;
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
if (message.ui_hide) {
|
||||
message.ui_hide = false;
|
||||
db.message().updateMessage(message);
|
||||
Log.i(Helper.TAG, folder.name + " unhidden id=" + message.id + " uid=" + message.uid);
|
||||
}
|
||||
}
|
||||
|
||||
return message.id;
|
||||
}
|
||||
|
||||
private static void downloadMessage(Context context, EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage, long id) throws MessagingException, IOException {
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
<string name="title_queued">Sending message</string>
|
||||
|
||||
<string name="title_search">Search</string>
|
||||
<string name="title_search_hint">Search sender/subject/text</string>
|
||||
<string name="title_search_hint">Search on server</string>
|
||||
<string name="title_searching">Searching \'%1$s\'</string>
|
||||
|
||||
<string name="title_sort_on">Sort on</string>
|
||||
|
||||
Reference in New Issue
Block a user