* SusiMail:

- Add persistent cache
   - Remove ID sorter
   - Mail size getter/setter
   - Set mail size when setting body
   - Only send CAPA once
   - Tagged string tweaks
This commit is contained in:
zzz
2014-04-22 11:18:56 +00:00
parent 7bf3ea5200
commit b43ebd2486
7 changed files with 390 additions and 45 deletions

View File

@ -54,7 +54,7 @@ class Mail {
private static final String unknown = "unknown";
public int id, size;
private int size;
public String sender, reply, subject, dateString,
formattedSender, formattedSubject,
formattedDate, // US Locale, UTC
@ -108,6 +108,7 @@ class Mail {
if (header == null)
setHeader(rb);
body = rb;
size = rb.length;
try {
part = new MailPart(rb);
} catch (DecodingException de) {
@ -127,6 +128,16 @@ class Mail {
return part != null;
}
public int getSize() {
return size;
}
public void setSize(int size) {
if (body != null)
return;
this.size = size;
}
/**
*
* @param address E-mail address to be validated

View File

@ -23,10 +23,12 @@
*/
package i2p.susi.webmail;
import i2p.susi.debug.Debug;
import i2p.susi.util.ReadBuffer;
import i2p.susi.webmail.pop3.POP3MailBox;
import i2p.susi.webmail.pop3.POP3MailBox.FetchRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -43,6 +45,7 @@ class MailCache {
private final POP3MailBox mailbox;
private final Hashtable<String, Mail> mails;
private final PersistentMailCache disk;
/** Includes header, headers are generally 1KB to 1.5 KB,
* and bodies will compress well.
@ -52,9 +55,18 @@ class MailCache {
/**
* @param mailbox non-null
*/
MailCache( POP3MailBox mailbox ) {
MailCache(POP3MailBox mailbox,
String host, int port, String user, String pass) {
this.mailbox = mailbox;
mails = new Hashtable<String, Mail>();
PersistentMailCache pmc = null;
try {
pmc = new PersistentMailCache(host, port, user, pass);
// TODO pmc.getMails()
} catch (IOException ioe) {
Debug.debug(Debug.ERROR, "Error creating disk cache: " + ioe);
}
disk = pmc;
}
/**
@ -80,11 +92,11 @@ class MailCache {
}
if( mail == null ) {
mail = newMail;
mail.size = mailbox.getSize( uidl );
mail.setSize(mailbox.getSize(uidl));
}
if (mail.markForDeletion)
return null;
if( mail.size <= FETCH_ALL_SIZE)
if(mail.getSize() <= FETCH_ALL_SIZE)
headerOnly = false;
if( headerOnly ) {
@ -95,6 +107,11 @@ class MailCache {
mail.setBody(mailbox.getBody(uidl));
}
}
if (disk != null) {
if (disk.saveMail(mail) && mail.hasBody()) {
// TODO delete on server
}
}
return mail;
}
@ -126,20 +143,32 @@ class MailCache {
}
if( mail == null ) {
mail = newMail;
mail.size = mailbox.getSize( uidl );
mail.setSize(mailbox.getSize(uidl));
}
if (mail.markForDeletion)
continue;
mr.setMail(mail);
if( mail.size <= FETCH_ALL_SIZE)
if(mail.getSize() <= FETCH_ALL_SIZE)
headerOnly = false;
if( headerOnly ) {
if(!mail.hasHeader()) {
if (disk != null) {
if (disk.getMail(mail, true)) {
Debug.debug(Debug.DEBUG, "Loaded header from disk cache: " + uidl);
continue; // found on disk, woo
}
}
POP3Request pr = new POP3Request(mr, mail, true);
fetches.add(pr);
}
} else {
if(!mail.hasBody()) {
if (disk != null) {
if (disk.getMail(mail, false)) {
Debug.debug(Debug.DEBUG, "Loaded body from disk cache: " + uidl);
continue; // found on disk, woo
}
}
POP3Request pr = new POP3Request(mr, mail, false);
fetches.add(pr);
}
@ -162,6 +191,11 @@ class MailCache {
} else {
mail.setBody(rb);
}
if (disk != null) {
if (disk.saveMail(mail) && mail.hasBody()) {
// TODO delete on server
}
}
}
}
}
@ -188,6 +222,8 @@ class MailCache {
public void delete(Collection<String> uidls) {
List<String> toDelete = new ArrayList<String>(uidls.size());
for (String uidl : uidls) {
if (disk != null)
disk.deleteMail(uidl);
Mail mail = mails.get(uidl);
if (mail == null)
continue;

View File

@ -0,0 +1,280 @@
package i2p.susi.webmail;
import i2p.susi.debug.Debug;
import i2p.susi.webmail.Messages;
import i2p.susi.util.ReadBuffer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.PasswordManager;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SecureFile;
import net.i2p.util.SecureFileOutputStream;
/**
* Manage the on-disk cache.
*
* This is a custom format with subdirectories, gzipped files,
* and the encoded UIDL in the file name.
* We store either the headers or the full message.
* No, it is not Maildir format but we could add Maildir-style
* status suffixes (e.g. ":2.SR") later.
*
* Exporting to a Maildir format would be just ungzipping
* each file to a flat directory.
*
* TODO draft and sent folders, cached server caps and config.
*
* @since 0.9.14
*/
class PersistentMailCache {
private final File _cacheDir;
private static final String DIR_SUSI = "susimail";
private static final String DIR_CACHE = "cache";
private static final String CACHE_PREFIX = "cache-";
private static final String DIR_FOLDER = "cur"; // MailDir-like
private static final String DIR_PREFIX = "s";
private static final String FILE_PREFIX = "mail-";
private static final String HDR_SUFFIX = ".hdr.txt.gz";
private static final String FULL_SUFFIX = ".full.txt.gz";
private static final String B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~";
/**
* Use the params to generate a unique directory name.
* @param pass ignored
*/
public PersistentMailCache(String host, int port, String user, String pass) throws IOException {
_cacheDir = makeCacheDirs(host, port, user, pass);
}
/**
* Fetch all mails from disk.
*
* @return An e-mail or null
*/
public Collection<Mail> getMails() {
List<Mail> rv = new ArrayList<Mail>();
for (int j = 0; j < B64.length(); j++) {
File subdir = new File(_cacheDir, DIR_PREFIX + B64.charAt(j));
File[] files = subdir.listFiles();
if (files == null)
continue;
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (!f.isFile())
continue;
Mail mail = load(f);
if (mail != null)
rv.add(mail);
}
}
return rv;
}
/**
* Fetch any needed data from disk.
*
* @return success
*/
public boolean getMail(Mail mail, boolean headerOnly) {
File f = getFullFile(mail.uidl);
if (f.exists()) {
ReadBuffer rb = read(f);
if (rb != null) {
mail.setBody(rb);
return true;
}
}
f = getHeaderFile(mail.uidl);
if (f.exists()) {
ReadBuffer rb = read(f);
if (rb != null) {
mail.setHeader(rb);
return true;
}
}
return false;
}
/**
* Save data to disk.
*
* @return success
*/
public boolean saveMail(Mail mail) {
ReadBuffer rb = mail.getBody();
if (rb != null) {
File f = getFullFile(mail.uidl);
if (f.exists())
return true; // already there, all good
boolean rv = write(rb, f);
if (rv)
getHeaderFile(mail.uidl).delete();
return rv;
}
rb = mail.getHeader();
if (rb != null) {
File f = getHeaderFile(mail.uidl);
if (f.exists())
return true; // already there, all good
boolean rv = write(rb, f);
return rv;
}
return false;
}
/**
*
* Delete data from disk.
*/
public void deleteMail(Mail mail) {
deleteMail(mail.uidl);
}
/**
*
* Delete data from disk.
*/
public void deleteMail(String uidl) {
getFullFile(uidl).delete();
getHeaderFile(uidl).delete();
}
/**
* ~/.i2p/susimail/cache/cache-xxxxx/cur/s[a-z]/mail-xxxxx.full.txt.gz
* folder1 is the base.
*/
private static File makeCacheDirs(String host, int port, String user, String pass) throws IOException {
File f = new SecureDirectory(I2PAppContext.getGlobalContext().getConfigDir(), DIR_SUSI);
if (!f.exists() && !f.mkdir())
throw new IOException("Cannot create " + f);
f = new SecureDirectory(f, DIR_CACHE);
if (!f.exists() && !f.mkdir())
throw new IOException("Cannot create " + f);
f = new SecureDirectory(f, CACHE_PREFIX + Base64.encode(user + host + port));
if (!f.exists() && !f.mkdir())
throw new IOException("Cannot create " + f);
File base = new SecureDirectory(f, DIR_FOLDER);
if (!base.exists() && !base.mkdir())
throw new IOException("Cannot create " + base);
for (int i = 0; i < B64.length(); i++) {
f = new SecureDirectory(base, DIR_PREFIX + B64.charAt(i));
if (!f.exists() && !f.mkdir())
throw new IOException("Cannot create " + f);
}
return base;
}
private File getHeaderFile(String uidl) {
return getFile(uidl, HDR_SUFFIX);
}
private File getFullFile(String uidl) {
return getFile(uidl, FULL_SUFFIX);
}
private File getFile(String uidl, String suffix) {
byte[] raw = DataHelper.getASCII(uidl);
byte[] md5 = PasswordManager.md5Sum(raw);
String db64 = Base64.encode(md5);
File dir = new File(_cacheDir, DIR_PREFIX + db64.charAt(0));
String b64 = Base64.encode(uidl);
return new SecureFile(dir, FILE_PREFIX + b64 + suffix);
}
/**
* Save data to disk.
*
* @return success
*/
private static boolean write(ReadBuffer rb, File f) {
OutputStream out = null;
try {
out = new BufferedOutputStream(new GZIPOutputStream(new SecureFileOutputStream(f)));
out.write(rb.content, rb.offset, rb.length);
return true;
} catch (IOException ioe) {
Debug.debug(Debug.ERROR, "Error writing: " + f + ": " + ioe);
return false;
} finally {
if (out != null)
try { out.close(); } catch (IOException ioe) {}
}
}
/**
* @return null on failure
*/
private static ReadBuffer read(File f) {
InputStream in = null;
try {
long len = f.length();
if (len > 16 * 1024 * 1024) {
throw new IOException("too big");
}
in = new GZIPInputStream(new BufferedInputStream(new FileInputStream(f)));
ByteArrayOutputStream out = new ByteArrayOutputStream((int) len);
int read = 0;
byte buf[] = new byte[4*1024];
while ( (read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
ReadBuffer rb = new ReadBuffer(out.toByteArray(), 0, out.size());
return rb;
} catch (IOException ioe) {
Debug.debug(Debug.ERROR, "Error reading: " + f + ": " + ioe);
return null;
} finally {
if (in != null)
try { in.close(); } catch (IOException ioe) {}
}
}
/**
* @return null on failure
*/
private static Mail load(File f) {
String name = f.getName();
String uidl;
boolean headerOnly;
if (name.endsWith(FULL_SUFFIX)) {
uidl= Base64.decodeToString(name.substring(FILE_PREFIX.length(), name.length() - FULL_SUFFIX.length()));
headerOnly = false;
} else if (name.endsWith(HDR_SUFFIX)) {
uidl= Base64.decodeToString(name.substring(FILE_PREFIX.length(), name.length() - HDR_SUFFIX.length()));
headerOnly = true;
} else {
return null;
}
if (uidl == null)
return null;
ReadBuffer rb = read(f);
if (rb == null)
return null;
Mail mail = new Mail(uidl);
if (headerOnly)
mail.setHeader(rb);
else
mail.setBody(rb);
return mail;
}
}

View File

@ -172,6 +172,7 @@ public class WebMail extends HttpServlet
private static final String CONFIG_COMPOSER_ROWS = "composer.rows";
private static final String CONFIG_BCC_TO_SELF = "composer.bcc.to.self";
private static final String CONFIG_LEAVE_ON_SERVER = "pop3.leave.on.server";
private static final String CONFIG_DEBUG = "debug";
private static final String RC_PROP_THEME = "routerconsole.theme";
@ -194,21 +195,15 @@ public class WebMail extends HttpServlet
*
* @author susi
*/
/****
private static class IDSorter implements Comparator<String> {
private final MailCache mailCache;
/**
* Set MailCache object, where to get Mails from
* @param mailCache
*/
public IDSorter( MailCache mailCache )
{
this.mailCache = mailCache;
}
/* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(String arg0, String arg1) {
Mail a = mailCache.getMail( arg0, MailCache.FETCH_HEADER );
Mail b = mailCache.getMail( arg1, MailCache.FETCH_HEADER );
@ -219,6 +214,7 @@ public class WebMail extends HttpServlet
return a.id - b.id;
}
}
****/
/**
* sorts Mail objects by sender field
@ -338,7 +334,7 @@ public class WebMail extends HttpServlet
return (b == null) ? 0 : 1;
if (b == null)
return -1;
return a.size - b.size;
return a.getSize() - b.getSize();
}
}
@ -425,7 +421,7 @@ public class WebMail extends HttpServlet
*/
private static String sortHeader( String name, String label, String imgPath )
{
return label + "&nbsp;<a href=\"" + myself + "?" + name + "=up\"><img src=\"" +
return label + "&nbsp;&nbsp;<a href=\"" + myself + "?" + name + "=up\"><img src=\"" +
imgPath + "3up.png\" border=\"0\" alt=\"^\"></a><a href=\"" + myself +
"?" + name + "=down\"><img src=\"" + imgPath + "3down.png\" border=\"0\" alt=\"v\"></a>";
}
@ -556,6 +552,7 @@ public class WebMail extends HttpServlet
}
if( prepareAttachment ) {
if( html ) {
// TODO can we at least show images safely?
out.println( "<hr><p class=\"mailbody\">" );
out.println( "<a target=\"_blank\" href=\"" + myself + "?" + DOWNLOAD + "=" +
mailPart.hashCode() + "\">" + _("Download attachment {0}", ident) + "</a>" +
@ -671,9 +668,10 @@ public class WebMail extends HttpServlet
sessionObject.host = host;
sessionObject.smtpPort = smtpPortNo;
sessionObject.state = STATE_LIST;
MailCache mc = new MailCache(mailbox);
MailCache mc = new MailCache(mailbox, host, pop3PortNo, user, pass);
sessionObject.mailCache = mc;
sessionObject.folder = new Folder<String>();
// TODO get through cache so we have the disk-only ones too
String[] uidls = mailbox.getUIDLs();
sessionObject.folder.setElements(uidls);
if (uidls.length > 0) {
@ -686,7 +684,7 @@ public class WebMail extends HttpServlet
mc.getMail(reqs);
}
sessionObject.folder.addSorter( SORT_ID, new IDSorter( sessionObject.mailCache ) );
//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 ) );
@ -1013,6 +1011,7 @@ public class WebMail extends HttpServlet
}
if( buttonPressed( request, REFRESH ) ) {
sessionObject.mailbox.refresh();
// TODO get through cache so we have the disk-only ones too
String[] uidls = sessionObject.mailbox.getUIDLs();
if (uidls != null)
sessionObject.folder.setElements(uidls);
@ -1436,6 +1435,7 @@ public class WebMail extends HttpServlet
* update folder content
*/
if( sessionObject.state != STATE_AUTH ) {
// TODO get through cache so we have the disk-only ones too
String[] uidls = sessionObject.mailbox.getUIDLs();
if (uidls != null)
sessionObject.folder.setElements(uidls);
@ -1485,7 +1485,7 @@ public class WebMail extends HttpServlet
);
}
out.println( "</head>\n<body>\n" +
"<div class=\"page\"><p><img src=\"" + sessionObject.imgPath + "susimail.png\" alt=\"Susimail\"><br>&nbsp;</p>\n" +
"<div class=\"page\"><p><img src=\"" + sessionObject.imgPath + "susimail.png\" alt=\"Susimail\"></p>\n" +
"<form method=\"POST\" enctype=\"multipart/form-data\" action=\"" + myself + "\" accept-charset=\"UTF-8\">" );
if( sessionObject.error != null && sessionObject.error.length() > 0 ) {
@ -1756,15 +1756,15 @@ public class WebMail extends HttpServlet
out.println( "<table cellspacing=\"0\" cellpadding=\"5\">\n" +
"<tr><td colspan=\"2\" align=\"center\"><hr></td></tr>\n" +
"<tr><td align=\"right\">" + _("From:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_FROM + "\" value=\"" + from + "\" " + ( !fixed.equalsIgnoreCase("false") ? "disabled" : "" ) +"></td></tr>\n" +
"<tr><td align=\"right\">" + _("To:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_TO + "\" value=\"" + to + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Cc:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_CC + "\" value=\"" + cc + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Bcc:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_BCC + "\" value=\"" + bcc + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("From") + ":</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_FROM + "\" value=\"" + from + "\" " + ( !fixed.equalsIgnoreCase("false") ? "disabled" : "" ) +"></td></tr>\n" +
"<tr><td align=\"right\">" + _("To") + ":</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_TO + "\" value=\"" + to + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Cc") + ":</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_CC + "\" value=\"" + cc + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Bcc") + ":</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_BCC + "\" value=\"" + bcc + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Bcc to self") + ": </td><td align=\"left\"><input type=\"checkbox\" class=\"optbox\" name=\"" + NEW_BCC_TO_SELF + "\" value=\"1\" " + (sessionObject.bccToSelf ? "checked" : "" ) + "></td></tr>\n" +
"<tr><td align=\"right\">" + _("Subject:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_SUBJECT + "\" value=\"" + subject + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Subject") + ":</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_SUBJECT + "\" value=\"" + subject + "\"></td></tr>\n" +
"<tr><td colspan=\"2\" align=\"center\"><textarea cols=\"" + Config.getProperty( CONFIG_COMPOSER_COLS, 80 )+ "\" rows=\"" + Config.getProperty( CONFIG_COMPOSER_ROWS, 10 )+ "\" name=\"" + NEW_TEXT + "\">" + text + "</textarea>" +
"<tr><td colspan=\"2\" align=\"center\"><hr></td></tr>\n" +
"<tr><td align=\"right\">" + _("Add Attachment:") + "</td><td align=\"left\"><input type=\"file\" size=\"50%\" name=\"" + NEW_FILENAME + "\" value=\"\"></td></tr>" +
"<tr><td align=\"right\">" + _("Add Attachment") + ":</td><td align=\"left\"><input type=\"file\" size=\"50%\" name=\"" + NEW_FILENAME + "\" value=\"\"></td></tr>" +
// TODO disable/hide in JS if no file selected
"<tr><td>&nbsp;</td><td align=\"left\">" + button(NEW_UPLOAD, _("Add another attachment")) + "</td></tr>");
@ -1772,7 +1772,7 @@ public class WebMail extends HttpServlet
boolean wroteHeader = false;
for( Attachment attachment : sessionObject.attachments ) {
if( !wroteHeader ) {
out.println("<tr><td align=\"right\">" + _("Attachments:") + "</td>");
out.println("<tr><td align=\"right\">" + _("Attachments") + ":</td>");
wroteHeader = true;
} else {
out.println("<tr><td align=\"right\">&nbsp;</td>");
@ -1858,7 +1858,7 @@ public class WebMail extends HttpServlet
out.println("<table id=\"mailbox\" cellspacing=\"0\" cellpadding=\"5\">\n" +
"<tr><td colspan=\"8\"><hr></td></tr>\n<tr>" +
thSpacer + "<th>" + sortHeader( SORT_SENDER, _("Sender"), sessionObject.imgPath ) + "</th>" +
thSpacer + "<th>" + sortHeader( SORT_SENDER, _("From"), sessionObject.imgPath ) + "</th>" +
thSpacer + "<th>" + sortHeader( SORT_SUBJECT, _("Subject"), sessionObject.imgPath ) + "</th>" +
thSpacer + "<th>" + sortHeader( SORT_DATE, _("Date"), sessionObject.imgPath ) +
//sortHeader( SORT_ID, "", sessionObject.imgPath ) +
@ -1893,7 +1893,7 @@ public class WebMail extends HttpServlet
link + mail.shortSender + "</a></td><td>&nbsp;</td><td>" + link + mail.shortSubject + "</a></td><td>&nbsp;</td><td>" +
// don't let date get split across lines
mail.localFormattedDate.replace(" ", "&nbsp;") + "</td><td>&nbsp;</td><td align=\"right\">" +
DataHelper.formatSize2(mail.size) + "B</td></tr>" );
DataHelper.formatSize2(mail.getSize()) + "B</td></tr>" );
bg = 1 - bg;
i++;
}
@ -1907,7 +1907,7 @@ public class WebMail extends HttpServlet
button( CLEAR, _("Clear") ) +
"<br>");
out.println(
_("Page Size:") + "&nbsp;<input type=\"text\" style=\"text-align: right;\" name=\"" + PAGESIZE + "\" size=\"4\" value=\"" + sessionObject.folder.getPageSize() + "\">" +
_("Page Size") + ":&nbsp;<input type=\"text\" style=\"text-align: right;\" name=\"" + PAGESIZE + "\" size=\"4\" value=\"" + sessionObject.folder.getPageSize() + "\">" +
button( SETPAGESIZE, _("Set") ) );
}
}
@ -1944,12 +1944,12 @@ public class WebMail extends HttpServlet
if( mail != null ) {
out.println( "<table cellspacing=\"0\" cellpadding=\"5\">\n" +
"<tr><td colspan=\"2\" align=\"center\"><hr></td></tr>\n" +
"<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("From:") +
"</td><td align=\"left\">" + quoteHTML( mail.sender ) + "</td></tr>\n" +
"<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("Subject:") +
"</td><td align=\"left\">" + quoteHTML( mail.formattedSubject ) + "</td></tr>\n" +
"<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("Date:") +
"</td><td align=\"left\">" + mail.quotedDate + "</td></tr>\n" +
"<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("From") +
":</td><td align=\"left\">" + quoteHTML( mail.sender ) + "</td></tr>\n" +
"<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("Subject") +
":</td><td align=\"left\">" + quoteHTML( mail.formattedSubject ) + "</td></tr>\n" +
"<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("Date") +
":</td><td align=\"left\">" + mail.quotedDate + "</td></tr>\n" +
"<tr><td colspan=\"2\" align=\"center\"><hr></td></tr>" );
if( mail.hasPart()) {
showPart( out, mail.getPart(), 0, SHOW_HTML );

View File

@ -52,6 +52,7 @@ public class POP3MailBox {
private int mails;
private boolean connected;
private boolean gotCAPA;
private boolean supportsPipelining;
private boolean supportsTOP;
private boolean supportsUIDL;
@ -278,6 +279,7 @@ public class POP3MailBox {
}
if (srs.isEmpty())
return;
// TODO don't quit now, just set timer to quit later
SendRecv sr = new SendRecv("QUIT", Mode.A1);
srs.add(sr);
try {
@ -490,7 +492,7 @@ public class POP3MailBox {
private void connect() {
Debug.debug(Debug.DEBUG, "connect()");
if (Debug.getLevel() == Debug.DEBUG)
(new Exception()).printStackTrace();
(new Exception("I did it")).printStackTrace();
clear();
@ -565,16 +567,17 @@ public class POP3MailBox {
* @since 0.9.13
*/
private boolean doHandshake() throws IOException {
// can we always pipeline this ?
supportsPipelining = false;
supportsUIDL = false;
supportsTOP = false;
List<SendRecv> cmds = new ArrayList<SendRecv>(2);
cmds.add(new SendRecv(null, Mode.A1));
SendRecv capa = new SendRecv("CAPA", Mode.LS);
cmds.add(capa);
SendRecv capa = null;
if (gotCAPA) {
Debug.debug(Debug.DEBUG, "Skipping CAPA");
} else {
capa = new SendRecv("CAPA", Mode.LS);
cmds.add(capa);
}
boolean rv = sendCmds(cmds);
if (rv) {
if (rv && capa != null) {
if (capa.ls != null) {
for (String cap : capa.ls) {
String t = cap.trim();
@ -586,10 +589,11 @@ public class POP3MailBox {
supportsTOP = true;
}
}
}
Debug.debug(Debug.DEBUG, "POP3 server caps: pipelining? " + supportsPipelining +
gotCAPA = true;
Debug.debug(Debug.DEBUG, "POP3 server caps: pipelining? " + supportsPipelining +
" UIDL? " + supportsUIDL +
" TOP? " + supportsTOP);
}
return rv;
}

View File

@ -1,3 +1,17 @@
2014-04-22 zzz
* SusiMail:
- Add persistent cache
2014-04-21 zzz
* SusiMail:
- Pipeline all deletes and quit
- Don't reconnect after delete and quit
- Verify connected before each POP3 operation
- Don't clear messages if a reconnection fails
- Use locale-based sorting for strings
- Increase limit for full fetch again
- Increase default page size back again
2014-04-21 dg
* findbugs: mostly stream closure fixes in router, apps, core

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 = 7;
public final static long BUILD = 8;
/** for example "-test" */
public final static String EXTRA = "";