Added service holder

pull/160/head
M66B 5 years ago
parent 2c63a12e0a
commit ae5cbb3ea5

@ -32,7 +32,6 @@ import com.sun.mail.iap.Argument;
import com.sun.mail.iap.Response;
import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPMessage;
import com.sun.mail.imap.IMAPStore;
import com.sun.mail.imap.protocol.IMAPProtocol;
import com.sun.mail.imap.protocol.IMAPResponse;
@ -80,7 +79,7 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
private List<Long> messages = null;
private IMAPStore istore = null;
private ConnectionHelper.ServiceHolder iservice = null;
private IMAPFolder ifolder = null;
private Message[] imessages = null;
@ -281,11 +280,11 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
isession.setDebug(debug);
Log.i("Boundary server connecting account=" + account.name);
istore = (IMAPStore) isession.getStore(protocol);
ConnectionHelper.connect(context, istore, account);
iservice = new ConnectionHelper.ServiceHolder(protocol, isession);
ConnectionHelper.connect(context, iservice, account);
Log.i("Boundary server opening folder=" + browsable.name);
ifolder = (IMAPFolder) istore.getFolder(browsable.name);
ifolder = (IMAPFolder) iservice.getStore().getFolder(browsable.name);
ifolder.open(Folder.READ_WRITE);
Log.i("Boundary server query=" + query);
@ -314,7 +313,7 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
try {
// https://tools.ietf.org/html/rfc3501#section-6.4.4
Argument arg = new Argument();
if (query.startsWith("raw:") && istore.hasCapability("X-GM-EXT-1")) {
if (query.startsWith("raw:") && iservice.getStore().hasCapability("X-GM-EXT-1")) {
// https://support.google.com/mail/answer/7190
// https://developers.google.com/gmail/imap/imap-extensions#extension_of_the_search_command_x-gm-raw
arg.writeAtom("X-GM-RAW");
@ -480,8 +479,8 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
public void run() {
Log.i("Boundary destroy");
try {
if (istore != null)
istore.close();
if (iservice != null)
iservice.close();
} catch (Throwable ex) {
Log.e("Boundary", ex);
}

@ -11,6 +11,7 @@ import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import com.bugsnag.android.BreadcrumbType;
@ -22,7 +23,6 @@ import org.xbill.DNS.Lookup;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.Type;
import java.lang.reflect.Field;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
@ -35,6 +35,8 @@ import java.util.Map;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Service;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
@ -259,86 +261,33 @@ public class ConnectionHelper {
return true;
}
static void connect(Context context, IMAPStore istore, EntityAccount account) throws MessagingException {
static void connect(Context context, @NonNull ServiceHolder istore, EntityAccount account) throws MessagingException {
connect(context, istore, account.host, account.port, account.user, account.password);
}
static void connect(Context context, SMTPTransport itransport, EntityIdentity identity) throws MessagingException {
connect(context, itransport, identity.host, identity.port, identity.user, identity.password);
static void connect(Context context, ServiceHolder iservice, EntityIdentity identity) throws MessagingException {
connect(context, iservice, identity.host, identity.port, identity.user, identity.password);
}
static void connect(Context context, IMAPStore istore, String host, int port, String user, String password) throws MessagingException {
static void connect(Context context, ServiceHolder iservice, String host, int port, String user, String password) throws MessagingException {
try {
istore.connect(host, port, user, password);
iservice.connect(context, host, port, user, password);
} catch (MessagingException ex) {
if (!hasIPv6(host))
throw ex;
try {
Log.i("Binding to " + any4);
Field fSession = getDeclaredField(istore.getClass(), "session");
fSession.setAccessible(true);
Session isession = (Session) fSession.get(istore);
isession.getProperties().put("mail.imap.localaddress", any4);
isession.getProperties().put("mail.imaps.localaddress", any4);
istore.connect(host, port, user, password);
iservice.getSession().getProperties().put("mail.imap.localaddress", any4);
iservice.getSession().getProperties().put("mail.imaps.localaddress", any4);
iservice.getSession().getProperties().put("mail.smtp.localaddress", any4);
iservice.getSession().getProperties().put("mail.smtps.localaddress", any4);
iservice.connect(context, host, port, user, password);
} catch (Throwable ex1) {
Log.w(ex1);
throw ex;
}
}
// https://www.ietf.org/rfc/rfc2971.txt
if (istore.hasCapability("ID"))
try {
Map<String, String> id = new LinkedHashMap<>();
id.put("name", context.getString(R.string.app_name));
id.put("version", BuildConfig.VERSION_NAME);
Map<String, String> sid = istore.id(id);
if (sid != null) {
Map<String, String> crumb = new HashMap<>();
for (String key : sid.keySet()) {
crumb.put(key, sid.get(key));
EntityLog.log(context, "Server " + key + "=" + sid.get(key));
}
Bugsnag.leaveBreadcrumb("server", BreadcrumbType.LOG, crumb);
}
} catch (MessagingException ex) {
Log.w(ex);
}
}
static void connect(Context context, SMTPTransport itransport, String host, int port, String user, String password) throws MessagingException {
try {
itransport.connect(host, port, user, password);
} catch (MessagingException ex) {
if (!hasIPv6(host))
throw ex;
try {
Log.i("Binding to " + any4);
Field fSession = getDeclaredField(itransport.getClass(), "session");
fSession.setAccessible(true);
Session isession = (Session) fSession.get(itransport);
isession.getProperties().put("mail.smtp.localaddress", any4);
isession.getProperties().put("mail.smtps.localaddress", any4);
itransport.connect(host, port, user, password);
} catch (Throwable ex1) {
Log.w(ex1);
throw ex;
}
}
}
private static Field getDeclaredField(Class clazz, String name) throws NoSuchFieldException {
while (clazz != null) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException ex) {
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException(name);
}
private static boolean hasIPv6(String host) {
@ -424,4 +373,66 @@ public class ConnectionHelper {
return ok;
}
static class ServiceHolder implements AutoCloseable {
private String protocol;
private Session issesion;
private Service iservice;
private ServiceHolder() {
}
ServiceHolder(String protocol, Session issesion) {
this.protocol = protocol;
this.issesion = issesion;
}
void connect(Context context, String host, int port, String user, String password) throws MessagingException {
if ("imap".equals(protocol) || "imaps".equals(protocol)) {
iservice = issesion.getStore(protocol);
iservice.connect(host, port, user, password);
// https://www.ietf.org/rfc/rfc2971.txt
if (getStore().hasCapability("ID"))
try {
Map<String, String> id = new LinkedHashMap<>();
id.put("name", context.getString(R.string.app_name));
id.put("version", BuildConfig.VERSION_NAME);
Map<String, String> sid = getStore().id(id);
if (sid != null) {
Map<String, String> crumb = new HashMap<>();
for (String key : sid.keySet()) {
crumb.put(key, sid.get(key));
EntityLog.log(context, "Server " + key + "=" + sid.get(key));
}
Bugsnag.leaveBreadcrumb("server", BreadcrumbType.LOG, crumb);
}
} catch (MessagingException ex) {
Log.w(ex);
}
} else if ("smtp".equals(protocol) || "smtps".equals(protocol)) {
iservice = issesion.getTransport(protocol);
iservice.connect(host, port, user, password);
} else
throw new NoSuchProviderException(protocol);
}
Session getSession() {
return this.issesion;
}
IMAPStore getStore() {
return (IMAPStore) this.iservice;
}
SMTPTransport getTransport() {
return (SMTPTransport) this.iservice;
}
public void close() throws MessagingException {
if (iservice != null)
iservice.close();
}
}
}

@ -59,7 +59,6 @@ import androidx.lifecycle.Lifecycle;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPStore;
import com.sun.mail.imap.protocol.IMAPProtocol;
import java.net.UnknownHostException;
@ -535,10 +534,11 @@ public class FragmentAccount extends FragmentBase {
Properties props = MessageHelper.getSessionProperties(realm, insecure);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
try (IMAPStore istore = (IMAPStore) isession.getStore("imap" + (starttls ? "" : "s"))) {
ConnectionHelper.connect(context, istore, host, Integer.parseInt(port), user, password);
try (ConnectionHelper.ServiceHolder iservice =
new ConnectionHelper.ServiceHolder("imap" + (starttls ? "" : "s"), isession)) {
ConnectionHelper.connect(context, iservice, host, Integer.parseInt(port), user, password);
result.idle = istore.hasCapability("IDLE");
result.idle = iservice.getStore().hasCapability("IDLE");
boolean inbox = false;
boolean archive = false;
@ -552,7 +552,7 @@ public class FragmentAccount extends FragmentBase {
EntityFolder altSent = null;
EntityFolder altJunk = null;
for (Folder ifolder : istore.getDefaultFolder().list("*")) {
for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) {
// Check folder attributes
String fullName = ifolder.getFullName();
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
@ -892,10 +892,11 @@ public class FragmentAccount extends FragmentBase {
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
try (IMAPStore istore = (IMAPStore) isession.getStore("imap" + (starttls ? "" : "s"))) {
ConnectionHelper.connect(context, istore, host, Integer.parseInt(port), user, password);
try (ConnectionHelper.ServiceHolder iservice
= new ConnectionHelper.ServiceHolder("imap" + (starttls ? "" : "s"), isession)) {
ConnectionHelper.connect(context, iservice, host, Integer.parseInt(port), user, password);
for (Folder ifolder : istore.getDefaultFolder().list("*")) {
for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) {
// Check folder attributes
String fullName = ifolder.getFullName();
String[] attrs = ((IMAPFolder) ifolder).getAttributes();

@ -63,7 +63,6 @@ import androidx.lifecycle.Lifecycle;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
import com.sun.mail.smtp.SMTPTransport;
import java.net.Inet4Address;
import java.net.Inet6Address;
@ -702,8 +701,9 @@ public class FragmentIdentity extends FragmentBase {
isession.setDebug(true);
// Create transport
try (SMTPTransport itransport = (SMTPTransport) isession.getTransport(protocol)) {
ConnectionHelper.connect(context, itransport, host, Integer.parseInt(port), user, password);
try (ConnectionHelper.ServiceHolder iservice =
new ConnectionHelper.ServiceHolder(protocol, isession)) {
ConnectionHelper.connect(context, iservice, host, Integer.parseInt(port), user, password);
}
}

@ -52,8 +52,6 @@ import androidx.constraintlayout.widget.Group;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPStore;
import com.sun.mail.smtp.SMTPTransport;
import java.net.UnknownHostException;
import java.util.ArrayList;
@ -256,13 +254,14 @@ public class FragmentQuickSetup extends FragmentBase {
Properties props = MessageHelper.getSessionProperties(null, false);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
try (IMAPStore istore = (IMAPStore) isession.getStore(provider.imap_starttls ? "imap" : "imaps")) {
ConnectionHelper.connect(context, istore, provider.imap_host, provider.imap_port, user, password);
try (ConnectionHelper.ServiceHolder iservice =
new ConnectionHelper.ServiceHolder(provider.imap_starttls ? "imap" : "imaps", isession)) {
ConnectionHelper.connect(context, iservice, provider.imap_host, provider.imap_port, user, password);
boolean inbox = false;
boolean drafts = false;
EntityFolder altDrafts = null;
for (Folder ifolder : istore.getDefaultFolder().list("*")) {
for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) {
String fullName = ifolder.getFullName();
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
String type = EntityFolder.getType(attrs, fullName, true);
@ -320,8 +319,9 @@ public class FragmentQuickSetup extends FragmentBase {
Properties props = MessageHelper.getSessionProperties(null, false);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
try (SMTPTransport itransport = (SMTPTransport) isession.getTransport(provider.smtp_starttls ? "smtp" : "smtps")) {
ConnectionHelper.connect(context, itransport, provider.smtp_host, provider.smtp_port, user, password);
try (ConnectionHelper.ServiceHolder iservice
= new ConnectionHelper.ServiceHolder(provider.smtp_starttls ? "smtp" : "smtps", isession)) {
ConnectionHelper.connect(context, iservice, provider.smtp_host, provider.smtp_port, user, password);
}
}

@ -36,8 +36,6 @@ import androidx.core.content.ContextCompat;
import androidx.lifecycle.Observer;
import androidx.preference.PreferenceManager;
import com.sun.mail.smtp.SMTPTransport;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
@ -365,15 +363,15 @@ public class ServiceSend extends ServiceBase {
}
// Create transport
try (SMTPTransport itransport = (SMTPTransport) isession.getTransport(protocol)) {
try (ConnectionHelper.ServiceHolder iservice = new ConnectionHelper.ServiceHolder(protocol, isession)) {
// Connect transport
db.identity().setIdentityState(ident.id, "connecting");
ConnectionHelper.connect(this, itransport, ident);
ConnectionHelper.connect(this, iservice, ident);
db.identity().setIdentityState(ident.id, "connected");
// Send message
Address[] to = imessage.getAllRecipients();
itransport.sendMessage(imessage, to);
iservice.getTransport().sendMessage(imessage, to);
long time = new Date().getTime();
EntityLog.log(this,
"Sent via " + ident.host + "/" + ident.user +

@ -46,7 +46,6 @@ import com.bugsnag.android.BreadcrumbType;
import com.bugsnag.android.Bugsnag;
import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPMessage;
import com.sun.mail.imap.IMAPStore;
import java.io.IOException;
import java.text.DateFormat;
@ -625,13 +624,60 @@ public class ServiceSynchronize extends ServiceBase {
isession.setDebug(debug);
// adb -t 1 logcat | grep "fairemail\|System.out"
final IMAPStore istore = (IMAPStore) isession.getStore(account.getProtocol());
final ConnectionHelper.ServiceHolder iservice =
new ConnectionHelper.ServiceHolder(account.getProtocol(), isession);
final Map<EntityFolder, IMAPFolder> mapFolders = new HashMap<>();
List<Thread> idlers = new ArrayList<>();
try {
// Initiate connection
EntityLog.log(this, account.name + " connecting");
db.folder().setFolderStates(account.id, null);
db.account().setAccountState(account.id, "connecting");
try {
ConnectionHelper.connect(this, iservice, account);
} catch (Throwable ex) {
if (ex instanceof AuthenticationFailedException) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify("receive:" + account.id, 1,
Core.getNotificationError(this, "error", account.name, ex)
.build());
throw ex;
}
// Report account connection error
if (account.last_connected != null && !ConnectionHelper.airplaneMode(this)) {
EntityLog.log(this, account.name + " last connected: " + new Date(account.last_connected));
long now = new Date().getTime();
long delayed = now - account.last_connected - account.poll_interval * 60 * 1000L;
if (delayed > ACCOUNT_ERROR_AFTER * 60 * 1000L && backoff > BACKOFF_ERROR_AFTER) {
Log.i("Reporting sync error after=" + delayed);
Throwable warning = new Throwable(
getString(R.string.title_no_sync,
Helper.getDateTimeInstance(this, DateFormat.SHORT, DateFormat.SHORT)
.format(account.last_connected)), ex);
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify("receive:" + account.id, 1,
Core.getNotificationError(this, "warning", account.name, warning)
.build());
}
}
throw ex;
}
final boolean capIdle = iservice.getStore().hasCapability("IDLE");
Log.i(account.name + " idle=" + capIdle);
db.account().setAccountState(account.id, "connected");
db.account().setAccountError(account.id, null);
db.account().setAccountWarning(account.id, null);
EntityLog.log(this, account.name + " connected");
// Listen for store events
istore.addStoreListener(new StoreListener() {
iservice.getStore().addStoreListener(new StoreListener() {
@Override
public void notification(StoreEvent e) {
if (e.getMessageType() == StoreEvent.NOTICE)
@ -664,7 +710,7 @@ public class ServiceSynchronize extends ServiceBase {
});
// Listen for folder events
istore.addFolderListener(new FolderAdapter() {
iservice.getStore().addFolderListener(new FolderAdapter() {
@Override
public void folderCreated(FolderEvent e) {
try {
@ -713,7 +759,7 @@ public class ServiceSynchronize extends ServiceBase {
});
// Listen for connection events
istore.addConnectionListener(new ConnectionAdapter() {
iservice.getStore().addConnectionListener(new ConnectionAdapter() {
@Override
public void opened(ConnectionEvent e) {
Log.i(account.name + " opened event");
@ -730,55 +776,8 @@ public class ServiceSynchronize extends ServiceBase {
}
});
// Initiate connection
EntityLog.log(this, account.name + " connecting");
db.folder().setFolderStates(account.id, null);
db.account().setAccountState(account.id, "connecting");
try {
ConnectionHelper.connect(this, istore, account);
} catch (Throwable ex) {
if (ex instanceof AuthenticationFailedException) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify("receive:" + account.id, 1,
Core.getNotificationError(this, "error", account.name, ex)
.build());
throw ex;
}
// Report account connection error
if (account.last_connected != null && !ConnectionHelper.airplaneMode(this)) {
EntityLog.log(this, account.name + " last connected: " + new Date(account.last_connected));
long now = new Date().getTime();
long delayed = now - account.last_connected - account.poll_interval * 60 * 1000L;
if (delayed > ACCOUNT_ERROR_AFTER * 60 * 1000L && backoff > BACKOFF_ERROR_AFTER) {
Log.i("Reporting sync error after=" + delayed);
Throwable warning = new Throwable(
getString(R.string.title_no_sync,
Helper.getDateTimeInstance(this, DateFormat.SHORT, DateFormat.SHORT)
.format(account.last_connected)), ex);
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify("receive:" + account.id, 1,
Core.getNotificationError(this, "warning", account.name, warning)
.build());
}
}
throw ex;
}
final boolean capIdle = istore.hasCapability("IDLE");
Log.i(account.name + " idle=" + capIdle);
db.account().setAccountState(account.id, "connected");
db.account().setAccountError(account.id, null);
db.account().setAccountWarning(account.id, null);
EntityLog.log(this, account.name + " connected");
// Update folder list
Core.onSynchronizeFolders(this, account, istore, state);
Core.onSynchronizeFolders(this, account, iservice.getStore(), state);
// Open synchronizing folders
final ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
@ -803,7 +802,7 @@ public class ServiceSynchronize extends ServiceBase {
db.folder().setFolderState(folder.id, "connecting");
final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name);
final IMAPFolder ifolder = (IMAPFolder) iservice.getStore().getFolder(folder.name);
try {
if (BuildConfig.DEBUG && "Postausgang".equals(folder.name))
throw new ReadOnlyFolderException(ifolder);
@ -1080,7 +1079,7 @@ public class ServiceSynchronize extends ServiceBase {
db.folder().setFolderState(folder.id, "connecting");
ifolder = istore.getFolder(folder.name);
ifolder = iservice.getStore().getFolder(folder.name);
ifolder.open(Folder.READ_WRITE);
db.folder().setFolderState(folder.id, "connected");
@ -1090,7 +1089,7 @@ public class ServiceSynchronize extends ServiceBase {
Core.processOperations(ServiceSynchronize.this,
account, folder,
isession, istore, ifolder,
isession, iservice.getStore(), ifolder,
state);
} catch (Throwable ex) {
@ -1148,18 +1147,18 @@ public class ServiceSynchronize extends ServiceBase {
try {
while (state.running()) {
if (!state.recoverable())
throw new StoreClosedException(istore, "Unrecoverable");
throw new StoreClosedException(iservice.getStore(), "Unrecoverable");
// Sends store NOOP
if (!istore.isConnected())
throw new StoreClosedException(istore, "NOOP");
if (!iservice.getStore().isConnected())
throw new StoreClosedException(iservice.getStore(), "NOOP");
for (EntityFolder folder : mapFolders.keySet())
if (folder.synchronize)
if (!folder.poll && capIdle) {
// Sends folder NOOP
if (!mapFolders.get(folder).isOpen())
throw new StoreClosedException(istore, folder.name);
throw new StoreClosedException(iservice.getStore(), folder.name);
} else
EntityOperation.sync(this, folder.id, false);
@ -1231,7 +1230,7 @@ public class ServiceSynchronize extends ServiceBase {
// Close store
try {
EntityLog.log(this, account.name + " store closing");
istore.close();
iservice.close();
EntityLog.log(this, account.name + " store closed");
} catch (Throwable ex) {
Log.w(account.name, ex);

Loading…
Cancel
Save