SusiMail: Thread the cache loading and email checking (ticket #2087)

Set Cache-Control header for attachments
Fix rotated attached images
Fix excess debug info in message view
This commit is contained in:
zzz
2018-02-12 14:26:19 +00:00
parent f13f4fcb6e
commit 012fb4cacf
12 changed files with 464 additions and 135 deletions

View File

@@ -43,6 +43,7 @@ import java.util.List;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.util.I2PAppThread;
/**
* @author user
@@ -52,45 +53,92 @@ class MailCache {
public enum FetchMode {
HEADER, ALL, CACHE_ONLY
}
private final POP3MailBox mailbox;
private final Hashtable<String, Mail> mails;
private final PersistentMailCache disk;
private final I2PAppContext _context;
private NewMailListener _loadInProgress;
/** Includes header, headers are generally 1KB to 1.5 KB,
* and bodies will compress well.
*/
private static final int FETCH_ALL_SIZE = 32*1024;
/**
* Does NOT load the mails in. Caller MUST call loadFromDisk().
*
* @param mailbox non-null
*/
MailCache(I2PAppContext ctx, POP3MailBox mailbox,
String host, int port, String user, String pass) {
String host, int port, String user, String pass) throws IOException {
this.mailbox = mailbox;
mails = new Hashtable<String, Mail>();
PersistentMailCache pmc = null;
try {
pmc = new PersistentMailCache(ctx, host, port, user, pass, PersistentMailCache.DIR_FOLDER);
// TODO Drafts, Sent, Trash
} catch (IOException ioe) {
Debug.debug(Debug.ERROR, "Error creating disk cache: " + ioe);
}
disk = pmc;
disk = new PersistentMailCache(ctx, host, port, user, pass, PersistentMailCache.DIR_FOLDER);
// TODO Drafts, Sent, Trash
_context = ctx;
if (disk != null)
loadFromDisk();
}
/**
* Threaded. Returns immediately.
* This will not access the mailbox. Mailbox need not be ready.
*
* @return success false if in progress already and nml will NOT be called back, true if nml will be called back
* @since 0.9.13
*/
public synchronized boolean loadFromDisk(NewMailListener nml) {
if (_loadInProgress != null)
return false;
Thread t = new I2PAppThread(new LoadMailRunner(nml), "Email loader");
_loadInProgress = nml;
try {
t.start();
} catch (Throwable e) {
_loadInProgress = null;
return false;
}
return true;
}
/** @since 0.9.34 */
private class LoadMailRunner implements Runnable {
private final NewMailListener _nml;
public LoadMailRunner(NewMailListener nml) {
_nml = nml;
}
public void run() {
boolean result = false;
try {
blockingLoadFromDisk();
if(!mails.isEmpty())
result = true;
} finally {
synchronized(MailCache.this) {
if (_loadInProgress == _nml)
_loadInProgress = null;
}
_nml.foundNewMail(result);
}
}
}
/**
* Blocking. Only call once!
* This will not access the mailbox. Mailbox need not be ready.
*
* @since 0.9.13
*/
private void loadFromDisk() {
Collection<Mail> dmails = disk.getMails();
for (Mail mail : dmails) {
mails.put(mail.uidl, mail);
private void blockingLoadFromDisk() {
synchronized(mails) {
if (!mails.isEmpty())
throw new IllegalStateException();
Collection<Mail> dmails = disk.getMails();
for (Mail mail : dmails) {
mails.put(mail.uidl, mail);
}
}
}
@@ -98,6 +146,9 @@ class MailCache {
* The ones known locally, which will include any known on the server, if connected.
* Will not include any marked for deletion.
*
* This will not access the mailbox. Mailbox need not be ready.
* loadFromDisk() must have been called first.
*
* @return non-null
* @since 0.9.13
*/
@@ -113,7 +164,8 @@ class MailCache {
}
/**
* Fetch any needed data from pop3 server.
* Fetch any needed data from pop3 server, unless mode is CACHE_ONLY.
* Blocking unless mode is CACHE_ONLY.
*
* @param uidl message id to get
* @param mode CACHE_ONLY to not pull from pop server
@@ -172,6 +224,8 @@ class MailCache {
* After this, call getUIDLs() to get all known mail UIDLs.
* MUST already be connected, otherwise returns false.
*
* Blocking.
*
* @param mode HEADER or ALL only
* @return true if any were fetched
* @since 0.9.13

View File

@@ -7,6 +7,6 @@ package i2p.susi.webmail;
*/
public interface NewMailListener {
public void foundNewMail();
public void foundNewMail(boolean yes);
}

View File

@@ -86,6 +86,9 @@ class PersistentMailCache {
/**
* Use the params to generate a unique directory name.
*
* Does NOT load the mails in. Caller MUST call getMails().
*
* @param pass ignored
* @param folder use DIR_FOLDER
*/

View File

@@ -83,6 +83,7 @@ import net.i2p.data.DataHelper;
import net.i2p.servlet.RequestWrapper;
import net.i2p.servlet.util.ServletUtil;
import net.i2p.servlet.util.WriterOutputStream;
import net.i2p.util.I2PAppThread;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.Translate;
@@ -107,7 +108,7 @@ public class WebMail extends HttpServlet
private static final int DEFAULT_POP3PORT = 7660;
private static final int DEFAULT_SMTPPORT = 7659;
private enum State { AUTH, LIST, SHOW, NEW, CONFIG }
private enum State { AUTH, LOADING, LIST, SHOW, NEW, CONFIG }
// TODO generate from servlet name to allow for renaming or multiple instances
private static final String myself = "/susimail/susimail";
@@ -249,32 +250,6 @@ public class WebMail extends HttpServlet
Debug.setLevel( RELEASE ? Debug.ERROR : Debug.DEBUG );
}
/**
* sorts Mail objects by id field
*
* @author susi
*/
/****
private static class IDSorter implements Comparator<String> {
private final MailCache mailCache;
public IDSorter( MailCache mailCache )
{
this.mailCache = mailCache;
}
public int compare(String arg0, String arg1) {
Mail a = mailCache.getMail( arg0, MailCache.FETCH_HEADER );
Mail b = mailCache.getMail( arg1, MailCache.FETCH_HEADER );
if (a == null)
return (b == null) ? 0 : 1;
if (b == null)
return -1;
return a.id - b.id;
}
}
****/
/**
* Base for the various sorters
*
@@ -442,6 +417,11 @@ public class WebMail extends HttpServlet
POP3MailBox mailbox;
MailCache mailCache;
Folder<String> folder;
boolean isLoading, isFetching;
/** Set by threaded connector. Error or null */
String connectError;
/** Set by threaded connector. -1 if nothing to report, 0 or more after fetch complete */
int newMails = -1;
String user, pass, host, error, info;
String replyTo, replyCC;
String subject, body;
@@ -484,7 +464,9 @@ public class WebMail extends HttpServlet
*
* @since 0.9.13
*/
public void foundNewMail() {
public void foundNewMail(boolean yes) {
if (!yes)
return;
MailCache mc = mailCache;
Folder<String> f = folder;
if (mc != null && f != null) {
@@ -796,9 +778,9 @@ public class WebMail extends HttpServlet
/**
* prepare line for presentation between html tags
*
* - quote html tags
* Escapes html tags
*
* @param line, null OK
* @param line null OK
* @return escaped string or "" for null input
*/
static String quoteHTML( String line )
@@ -891,62 +873,201 @@ public class WebMail extends HttpServlet
}
}
if( doContinue ) {
POP3MailBox mailbox = new POP3MailBox( host, pop3PortNo, user, pass );
if (offline || mailbox.connectToServer()) {
sessionObject.mailbox = mailbox;
sessionObject.user = user;
sessionObject.pass = pass;
sessionObject.host = host;
sessionObject.smtpPort = smtpPortNo;
state = State.LIST;
I2PAppContext ctx = I2PAppContext.getGlobalContext();
MailCache mc = new MailCache(ctx, mailbox, host, pop3PortNo, user, pass);
sessionObject.mailCache = mc;
sessionObject.folder = new Folder<String>();
if (!offline) {
// prime the cache, request all headers at once
// otherwise they are pulled one at a time
// during the sort in folder.setElements() below
mc.getMail(MailCache.FetchMode.HEADER);
}
// setElements() sorts, so configure the sorting first
//sessionObject.folder.addSorter( SORT_ID, new IDSorter( sessionObject.mailCache ) );
sessionObject.folder.addSorter( SORT_SENDER, new SenderSorter( sessionObject.mailCache ) );
sessionObject.folder.addSorter( SORT_SUBJECT, new SubjectSorter( sessionObject.mailCache ) );
sessionObject.folder.addSorter( SORT_DATE, new DateSorter( sessionObject.mailCache ) );
sessionObject.folder.addSorter( SORT_SIZE, new SizeSorter( sessionObject.mailCache ) );
// reverse sort, latest mail first
// TODO get user defaults from config
sessionObject.folder.setSortBy(SORT_DEFAULT, SORT_ORDER_DEFAULT);
// get through cache so we have the disk-only ones too
String[] uidls = mc.getUIDLs();
sessionObject.folder.setElements(uidls);
sessionObject.reallyDelete = false;
if (offline)
Debug.debug(Debug.DEBUG, "OFFLINE MODE");
else
Debug.debug(Debug.DEBUG, "CONNECTED, YAY");
// we do this after the initial priming above
mailbox.setNewMailListener(sessionObject);
} else {
sessionObject.error += mailbox.lastError() + '\n';
Debug.debug(Debug.DEBUG, "LOGIN FAIL, REMOVING SESSION");
HttpSession session = request.getSession();
session.removeAttribute( "sessionObject" );
session.invalidate();
mailbox.destroy();
sessionObject.mailbox = null;
sessionObject.mailCache = null;
Debug.debug(Debug.DEBUG, "NOT CONNECTED, BOO");
}
sessionObject.smtpPort = smtpPortNo;
state = threadedStartup(sessionObject, offline, state,
host, pop3PortNo, user, pass);
}
}
}
return state;
}
/**
* Starts one thread to load the emails from disk,
* and in parallel starts a second thread to connect to the POP3 server
* (unless user clicked the 'read mail offline' at login).
* Either could finish first, but unless the local disk cache is really big,
* the loading will probably finish first.
*
* Once the POP3 connects, it waits for the disk loader to finish, and then
* does the fetching of new emails.
*
* The user may view the local folder once the first (loader) thread is done.
*
* @since 0.9.34
*/
private static State threadedStartup(SessionObject sessionObject, boolean offline, State state,
String host, int pop3PortNo, String user, String pass) {
POP3MailBox mailbox = new POP3MailBox(host, pop3PortNo, user, pass);
I2PAppContext ctx = I2PAppContext.getGlobalContext();
MailCache mc;
try {
mc = new MailCache(ctx, mailbox, host, pop3PortNo, user, pass);
} catch (IOException ioe) {
Debug.debug(Debug.ERROR, "Error creating disk cache", ioe);
sessionObject.error += ioe.toString() + '\n';
return State.AUTH;
}
Folder<String> folder = new Folder<String>();
// setElements() sorts, so configure the sorting first
//sessionObject.folder.addSorter( SORT_ID, new IDSorter( sessionObject.mailCache ) );
folder.addSorter( SORT_SENDER, new SenderSorter(mc));
folder.addSorter( SORT_SUBJECT, new SubjectSorter(mc));
folder.addSorter( SORT_DATE, new DateSorter(mc));
folder.addSorter( SORT_SIZE, new SizeSorter(mc));
// reverse sort, latest mail first
// TODO get user defaults from config
folder.setSortBy(SORT_DEFAULT, SORT_ORDER_DEFAULT);
sessionObject.folder = folder;
sessionObject.mailbox = mailbox;
sessionObject.user = user;
sessionObject.pass = pass;
sessionObject.host = host;
sessionObject.reallyDelete = false;
// Thread the loading and the server connection.
// Either could finish first.
// With a mix of email (10KB median, 100KB average size),
// about 20 emails per second per thread loaded.
// thread 1: mc.loadFromDisk()
sessionObject.mailCache = mc;
sessionObject.isLoading = true;
boolean ok = mc.loadFromDisk(new LoadWaiter(sessionObject));
if (!ok)
sessionObject.isLoading = false;
// thread 2: mailbox.connectToServer()
if (offline) {
Debug.debug(Debug.DEBUG, "OFFLINE MODE");
} else {
sessionObject.isFetching = true;
if (mailbox.connectToServer(new ConnectWaiter(sessionObject)))
sessionObject.isFetching = false;
}
// wait a little while so we avoid the loading page if we can
if (sessionObject.isLoading) {
try {
sessionObject.wait(5000);
} catch (InterruptedException ie) {
Debug.debug(Debug.DEBUG, "Interrupted waiting for load", ie);
}
}
state = sessionObject.isLoading ? State.LOADING : State.LIST;
return state;
}
/**
* Callback from MailCache.loadFromDisk()
* @since 0.9.34
*/
private static class LoadWaiter implements NewMailListener {
private final SessionObject _so;
public LoadWaiter(SessionObject so) {
_so = so;
}
public void foundNewMail(boolean yes) {
synchronized(_so) {
// get through cache so we have the disk-only ones too
MailCache mc = _so.mailCache;
Folder<String> f = _so.folder;
if (mc != null && f != null) {
String[] uidls = mc.getUIDLs();
int added = f.addElements(Arrays.asList(uidls));
if (added > 0)
_so.pageChanged = true;
Debug.debug(Debug.DEBUG, "Folder loaded");
} else {
Debug.debug(Debug.DEBUG, "MailCache/folder vanished?");
}
_so.isLoading = false;
_so.notifyAll();
}
}
}
/**
* Callback from POP3MailBox.connectToServer()
* @since 0.9.34
*/
private static class ConnectWaiter implements NewMailListener, Runnable {
private final SessionObject _so;
private final POP3MailBox _mb;
public ConnectWaiter(SessionObject so) {
_so = so;
_mb = _so.mailbox;
}
/** run this way if already connected */
public void run() {
foundNewMail(true);
}
/** @param connected are we? */
public void foundNewMail(boolean connected) {
MailCache mc = null;
Folder<String> f = null;
boolean found = false;
if (connected) {
Debug.debug(Debug.DEBUG, "CONNECTED, YAY");
// we do this whether new mail was found or not,
// because we may already have UIDLs in the MailCache to fetch
synchronized(_so) {
while (_so.isLoading) {
try {
_so.wait(5000);
} catch (InterruptedException ie) {
Debug.debug(Debug.DEBUG, "Interrupted waiting for load", ie);
return;
}
}
mc = _so.mailCache;
f = _so.folder;
}
Debug.debug(Debug.DEBUG, "Done waiting for folder load");
// fetch the mail outside the lock
// TODO, would be better to add each email as we get it
if (mc != null && f != null) {
found = mc.getMail(MailCache.FetchMode.HEADER);
}
} else {
Debug.debug(Debug.DEBUG, "NOT CONNECTED, BOO");
}
synchronized(_so) {
if (!connected) {
String error = _mb.lastError();
if (error.length() > 0)
_so.connectError = error;
else
_so.connectError = _t("Error connecting to server");
} else if (!found) {
Debug.debug(Debug.DEBUG, "No new emails");
_so.newMails = 0;
_so.connectError = null;
} else if (mc != null && f != null) {
String[] uidls = mc.getUIDLs();
int added = f.addElements(Arrays.asList(uidls));
if (added > 0)
_so.pageChanged = true;
_so.newMails = added;
_so.connectError = null;
Debug.debug(Debug.DEBUG, "Added " + added + " new emails");
} else {
Debug.debug(Debug.DEBUG, "MailCache/folder vanished?");
}
_mb.setNewMailListener(_so);
_so.isFetching = false;
_so.notifyAll();
}
}
}
/**
*
* @param sessionObject
@@ -1000,6 +1121,9 @@ public class WebMail extends HttpServlet
state = processLogout(sessionObject, request, isPOST, state);
if (state == State.AUTH)
return state;
// if loading, we can't get to states LIST/SHOW or it will block
if (sessionObject.isLoading)
return State.LOADING;
/*
* compose dialog
@@ -1292,18 +1416,27 @@ public class WebMail extends HttpServlet
sessionObject.error += _t("Internal error, lost connection.") + '\n';
return State.AUTH;
}
mailbox.refresh();
String error = mailbox.lastError();
sessionObject.error += error + '\n';
sessionObject.mailCache.getMail(MailCache.FetchMode.HEADER);
// get through cache so we have the disk-only ones too
String[] uidls = sessionObject.mailCache.getUIDLs();
int added = sessionObject.folder.addElements(Arrays.asList(uidls));
if (added > 0)
sessionObject.info += ngettext("{0} new message", "{0} new messages", added);
else if (error.length() <= 0)
sessionObject.info += _t("No new messages");
sessionObject.pageChanged = true;
if (sessionObject.isFetching) {
// shouldn't happen, button disabled
return state;
}
sessionObject.isFetching = true;
ConnectWaiter cw = new ConnectWaiter(sessionObject);
if (mailbox.connectToServer(cw)) {
// Already connected, start a thread ourselves
// TODO - But if already connected, we aren't going to find anything new.
// We used to call refresh() from here, which closes first,
// but that isn't threaded.
Debug.debug(Debug.DEBUG, "Already connected, running CW");
Thread t = new I2PAppThread(cw, "Email fetcher");
t.start();
}
// wait if it's going to be quick
try {
sessionObject.wait(3000);
} catch (InterruptedException ie) {
Debug.debug(Debug.DEBUG, "Interrupted waiting for connect", ie);
}
}
return state;
}
@@ -1831,6 +1964,13 @@ public class WebMail extends HttpServlet
return;
}
}
if (state == State.LOADING) {
if (isPOST) {
sendRedirect(httpRequest, response, null);
return;
}
}
// Set in web.xml
//if (oldState == State.AUTH && newState != State.AUTH) {
// int oldIdle = httpSession.getMaxInactiveInterval();
@@ -1856,9 +1996,10 @@ public class WebMail extends HttpServlet
int newPage = processFolderButtons(sessionObject, page, request);
// LIST is from SHOW page, SEND and CANCEL are from NEW page
// OFFLINE and LOGIN from login page
// TODO - REFRESH on list page
// REFRESH on list page
if (newPage != page || buttonPressed(request, LIST) ||
buttonPressed(request, SEND) || buttonPressed(request, CANCEL) ||
buttonPressed(request, REFRESH) ||
buttonPressed(request, LOGIN) || buttonPressed(request, OFFLINE)) {
// P-R-G
String q = '?' + CUR_PAGE + '=' + newPage;
@@ -1972,9 +2113,11 @@ public class WebMail extends HttpServlet
/*
* build subtitle
*/
if( state == State.AUTH )
if (state == State.AUTH) {
subtitle = _t("Login");
else if( state == State.LIST ) {
} else if (state == State.LOADING) {
subtitle = _t("Loading emails, please wait...");
} else if( state == State.LIST ) {
// mailbox.getNumMails() forces a connection, don't use it
// Not only does it slow things down, but a failure causes all our messages to "vanish"
//subtitle = ngettext("1 Message", "{0} Messages", sessionObject.mailbox.getNumMails());
@@ -2023,6 +2166,10 @@ public class WebMail extends HttpServlet
out.println("<script src=\"/susimail/js/compose.js\" type=\"text/javascript\"></script>");
} else if (state == State.LIST) {
out.println("<script src=\"/susimail/js/folder.js\" type=\"text/javascript\"></script>");
} else if (state == State.LOADING) {
// TODO JS?
out.println("<meta http-equiv=\"refresh\" content=\"5;url=" + myself + "\">");
// TODO we don't need the form below
}
out.print("</head>\n<body" + (state == State.LIST ? " onload=\"deleteboxclicked()\">" : ">"));
String nonce = state == State.AUTH ? LOGIN_NONCE :
@@ -2061,10 +2208,36 @@ public class WebMail extends HttpServlet
out.println("<input type=\"hidden\" name=\"" + CURRENT_SORT + "\" value=\"" + fullSort + "\">");
out.println("<input type=\"hidden\" name=\"" + CURRENT_FOLDER + "\" value=\"" + PersistentMailCache.DIR_FOLDER + "\">");
}
if( sessionObject.error != null && sessionObject.error.length() > 0 ) {
boolean showRefresh = false;
if (sessionObject.isLoading) {
sessionObject.info += _t("Loading emails, please wait...") + '\n';
showRefresh = true;
}
if (sessionObject.isFetching) {
sessionObject.info += _t("Checking for new emails on server") + '\n';
showRefresh = true;
} else if (state != State.LOADING && state != State.AUTH && state != State.CONFIG) {
String error = sessionObject.connectError;
if (error != null && error.length() > 0) {
sessionObject.error += error + '\n';
sessionObject.connectError = null;
}
int added = sessionObject.newMails;
if (added > 0) {
sessionObject.info += ngettext("{0} new message", "{0} new messages", added) + '\n';
sessionObject.newMails = -1;
} else if (added == 0) {
sessionObject.info += _t("No new messages") + '\n';
sessionObject.newMails = -1;
}
}
if (showRefresh) {
sessionObject.info += _t("Refresh the page for updates") + '\n';
}
if (sessionObject.error.length() > 0) {
out.println( "<div class=\"notifications\" onclick=\"this.remove()\"><p class=\"error\">" + quoteHTML(sessionObject.error).replace("\n", "<br>") + "</p></div>" );
}
if( sessionObject.info != null && sessionObject.info.length() > 0 ) {
if (sessionObject.info.length() > 0) {
out.println( "<div class=\"notifications\" onclick=\"this.remove()\"><p class=\"info\"><b>" + quoteHTML(sessionObject.info).replace("\n", "<br>") + "</b></p></div>" );
}
/*
@@ -2072,6 +2245,9 @@ public class WebMail extends HttpServlet
*/
if( state == State.AUTH )
showLogin( out );
else if (state == State.LOADING)
showLoading(out, sessionObject, request);
else if( state == State.LIST )
showFolder( out, sessionObject, request );
@@ -2103,7 +2279,7 @@ public class WebMail extends HttpServlet
if (qq >= 0)
url = url.substring(0, qq);
buf.append(url);
if (q.length() > 0)
if (q != null && q.length() > 0)
buf.append(q.replace("&amp;", "&")); // no you don't html escape the redirect header
resp.setHeader("Location", buf.toString());
resp.sendError(302, "Moved");
@@ -2140,6 +2316,7 @@ public class WebMail extends HttpServlet
}
String name2 = FilenameUtil.sanitizeFilename(name);
String name3 = FilenameUtil.encodeFilenameRFC5987(name);
response.setHeader("Cache-Control", "public, max-age=604800");
if (isRaw) {
try {
response.addHeader("Content-Disposition", "inline; filename=\"" + name2 + "\"; " +
@@ -2149,8 +2326,6 @@ public class WebMail extends HttpServlet
if (part.decodedLength >= 0)
response.setContentLength(part.decodedLength);
Debug.debug(Debug.DEBUG, "Sending raw attachment " + name + " length " + part.decodedLength);
// cache-control?
// was 2
part.decode(0, new OutputStreamBuffer(response.getOutputStream()));
shown = true;
} catch (IOException e) {
@@ -2207,7 +2382,7 @@ public class WebMail extends HttpServlet
try {
response.setContentType("message/rfc822");
response.setContentLength(content.getLength());
// cache-control?
response.setHeader("Cache-Control", "public, max-age=604800");
response.addHeader("Content-Disposition", "attachment; filename=\"" + name2 + "\"; " +
"filename*=" + name3);
in = content.getInputStream();
@@ -2497,6 +2672,18 @@ public class WebMail extends HttpServlet
"</table></div>");
}
/**
* @since 0.9.34
*/
private static void showLoading(PrintWriter out, SessionObject sessionObject, RequestWrapper request) {
// TODO make it pretty
out.println("<p><b>");
out.println(_t("Loading emails, please wait..."));
out.println("</b><p><b>");
out.println(_t("Refresh the page for updates"));
out.println("</b>");
}
/**
*
* @param out
@@ -2513,7 +2700,7 @@ public class WebMail extends HttpServlet
//button( REPLYALL, _t("Reply All") ) +
//button( FORWARD, _t("Forward") ) + spacer +
//button( DELETE, _t("Delete") ) + spacer +
out.println(button( REFRESH, _t("Check Mail") ) + spacer);
out.println((sessionObject.isFetching ? button2(REFRESH, _t("Check Mail")) : button(REFRESH, _t("Check Mail"))) + spacer);
//if (Config.hasConfigFile())
// out.println(button( RELOAD, _t("Reload Config") ) + spacer);
out.println(button( LOGOUT, _t("Logout") ));
@@ -2682,7 +2869,7 @@ public class WebMail extends HttpServlet
"</p>");
}
Mail mail = sessionObject.mailCache.getMail(showUIDL, MailCache.FetchMode.ALL);
if(!RELEASE && mail != null && mail.hasBody() && mail.getBody().getLength() < 16384) {
if(!RELEASE && mail != null && mail.hasBody() && mail.getSize() < 16384) {
out.println( "<!--" );
out.println( "Debug: Mail header and body follow");
Buffer body = mail.getBody();

View File

@@ -105,12 +105,12 @@ class BackgroundChecker {
public void run() {
try {
if (mailbox.connectToServer()) {
if (mailbox.blockingConnectToServer()) {
int found = mailbox.getNumMails();
if (found > 0) {
Debug.debug(Debug.DEBUG, "Found " + found + " mails, calling listener");
// may not really be new
mailbox.foundNewMail();
mailbox.foundNewMail(true);
}
}
} finally {

View File

@@ -44,6 +44,7 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PAppThread;
import net.i2p.util.InternalSocket;
/**
@@ -439,7 +440,7 @@ public class POP3MailBox implements NewMailListener {
*
* @return true or false
*/
public boolean isConnected() {
boolean isConnected() {
synchronized(synchronizer) {
if (socket == null) {
connected = false;
@@ -576,8 +577,8 @@ public class POP3MailBox implements NewMailListener {
}
/**
*
*
* Close (why?) and connect to server.
* Blocking.
*/
public void refresh() {
synchronized( synchronizer ) {
@@ -599,11 +600,75 @@ public class POP3MailBox implements NewMailListener {
/**
* Connect to pop3 server if not connected.
* Does nothing if already connected.
* Blocking.
*
* This will NOT call any configured NewMailListener,
* only the one passed in. It will be called with the value
* true if the connect was successful, false if not.
* Call getNumMails() to see if there really was any new mail.
*
* After the callback is executed, the information on new mails, if any,
* is available via getNumMails(), getUIDLs(), and getSize().
* The connection to the server will remain open, so that
* new emails may be retrieved via
* getHeader(), getBody(), and getBodies().
* Failure info is available via lastError().
*
* @return true if connected already and nml will NOT be called back, false if nml will be called back
* @since 0.9.13
*/
public boolean connectToServer(NewMailListener nml) {
synchronized( synchronizer ) {
if (isConnected())
return true;
}
Thread t = new I2PAppThread(new ConnectRunner(nml), "POP3 Connector");
try {
t.start();
} catch (Throwable e) {
// not really, but we won't be calling the callback
return true;
}
return false;
}
/** @since 0.9.34 */
private class ConnectRunner implements Runnable {
private final NewMailListener _nml;
public ConnectRunner(NewMailListener nml) {
_nml = nml;
}
public void run() {
boolean result = false;
try {
result = blockingConnectToServer();
} finally {
_nml.foundNewMail(result);
}
}
}
/**
* Connect to pop3 server if not connected.
* Does nothing if already connected.
* Blocking.
*
* This will NOT call any configured NewMailListener.
*
* After the callback is executed, the information on new mails, if any,
* is available via getNumMails(), getUIDLs(), and getSize().
* The connection to the server will remain open, so that
* new emails may be retrieved via
* getHeader(), getBody(), and getBodies().
* Failure info is available via lastError().
*
* @return true if connected
* @since 0.9.13
*/
public boolean connectToServer() {
boolean blockingConnectToServer() {
synchronized( synchronizer ) {
if (isConnected())
return true;
@@ -613,7 +678,8 @@ public class POP3MailBox implements NewMailListener {
}
/**
* connect to pop3 server, login with USER and PASS and try STAT then
* Closes any existing connection first.
* Then, connect to pop3 server, login with USER and PASS and try STAT then
*
* Caller must sync.
*/
@@ -630,7 +696,7 @@ public class POP3MailBox implements NewMailListener {
try {
socket = InternalSocket.getSocket(host, port);
} catch (IOException e) {
Debug.debug( Debug.DEBUG, "Error connecting: " + e);
Debug.debug(Debug.DEBUG, "Error connecting", e);
lastError = _t("Cannot connect") + " (" + host + ':' + port + ") - " + e.getLocalizedMessage();
return;
}
@@ -640,15 +706,16 @@ public class POP3MailBox implements NewMailListener {
lastError = "";
socket.setSoTimeout(120*1000);
boolean ok = doHandshake();
boolean loginOK = false;
if (ok) {
// TODO APOP (unsupported by postman)
List<SendRecv> cmds = new ArrayList<SendRecv>(4);
cmds.add(new SendRecv("USER " + user, Mode.A1));
cmds.add(new SendRecv("PASS " + pass, Mode.A1));
socket.setSoTimeout(60*1000);
ok = sendCmds(cmds);
loginOK = sendCmds(cmds);
}
if (ok) {
if (loginOK) {
connected = true;
List<SendRecv> cmds = new ArrayList<SendRecv>(4);
SendRecv stat = new SendRecv("STAT", Mode.A1);
@@ -681,6 +748,12 @@ public class POP3MailBox implements NewMailListener {
} else {
if (lastError.equals(""))
lastError = _t("Error connecting to server");
if (ok && !loginOK) {
lastError += '\n' +
_t("Mail server login failed, wrong username or password.") +
'\n' +
_t("Logout and then login again with the correct username and password.");
}
close();
}
}
@@ -1060,7 +1133,7 @@ public class POP3MailBox implements NewMailListener {
}
/**
* @return The most recent error message.
* @return The most recent error message. Probably not terminated with a newline.
*/
public String lastError() {
//Debug.debug(Debug.DEBUG, "lastError()");
@@ -1071,6 +1144,8 @@ public class POP3MailBox implements NewMailListener {
// translate this common error
if (e.trim().equals("Login failed."))
e = _t("Login failed");
else if (e.startsWith("Login failed."))
e = _t("Login failed") + e.substring(13);
return e;
}
@@ -1093,10 +1168,10 @@ public class POP3MailBox implements NewMailListener {
*
* @since 0.9.13
*/
public void foundNewMail() {
public void foundNewMail(boolean yes) {
NewMailListener nml = newMailListener;
if (nml != null)
nml.foundNewMail();
nml.foundNewMail(yes);
}
/**

View File

@@ -1,3 +1,9 @@
2018-02-12 zzz
* SusiMail:
- Background email checking (ticket #2087)
- Set Cache-Control header for attachments
- Fix rotated attached images
2018-02-11 zzz
* Util: Number formatting tweaks (ticket #1913)

View File

@@ -166,6 +166,7 @@ div.attached {
.attached img {
border: 1px solid #cfd6ff;
padding: 2px;
image-orientation: from-image;
}
td#addattach {

View File

@@ -915,6 +915,7 @@ div.attached {
border-radius: 2px;
padding: 2px;
background: #010;
image-orientation: from-image;
}
.attached p.mailbody {

View File

@@ -1390,6 +1390,7 @@ td p.error {
max-width: 100%;
max-width: calc(100% - 25px);
filter: drop-shadow(0 0 1px #999);
image-orientation: from-image;
}
#deleteattached input {

View File

@@ -855,6 +855,7 @@ div.attached {
.attached img {
border: 1px solid #443da0;
border-radius: 2px;
image-orientation: from-image;
}
div#emptymailbox {

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 4;
public final static long BUILD = 5;
/** for example "-test" */
public final static String EXTRA = "";