forked from I2P_Developers/i2p.i2p
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:
@@ -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
|
||||
|
||||
@@ -7,6 +7,6 @@ package i2p.susi.webmail;
|
||||
*/
|
||||
public interface NewMailListener {
|
||||
|
||||
public void foundNewMail();
|
||||
public void foundNewMail(boolean yes);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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("&", "&")); // 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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ div.attached {
|
||||
.attached img {
|
||||
border: 1px solid #cfd6ff;
|
||||
padding: 2px;
|
||||
image-orientation: from-image;
|
||||
}
|
||||
|
||||
td#addattach {
|
||||
|
||||
@@ -915,6 +915,7 @@ div.attached {
|
||||
border-radius: 2px;
|
||||
padding: 2px;
|
||||
background: #010;
|
||||
image-orientation: from-image;
|
||||
}
|
||||
|
||||
.attached p.mailbody {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -855,6 +855,7 @@ div.attached {
|
||||
.attached img {
|
||||
border: 1px solid #443da0;
|
||||
border-radius: 2px;
|
||||
image-orientation: from-image;
|
||||
}
|
||||
|
||||
div#emptymailbox {
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
Reference in New Issue
Block a user