/* * Created on Nov 23, 2004 * * This file is part of susimail project, see http://susi.i2p/ * * Copyright (C) 2004-2005 <susi23@mail.i2p> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Revision: 1.2 $ */ package i2p.susi.webmail; import i2p.susi.debug.Debug; import i2p.susi.util.Config; 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; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; /** * @author user */ class MailCache { public enum FetchMode { HEADER, ALL, CACHE_ONLY } 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. */ private static final int FETCH_ALL_SIZE = 8192; /** * @param mailbox non-null */ 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); } catch (IOException ioe) { Debug.debug(Debug.ERROR, "Error creating disk cache: " + ioe); } disk = pmc; if (disk != null) loadFromDisk(); } /** * * @since 0.9.13 */ private void loadFromDisk() { Collection<Mail> dmails = disk.getMails(); for (Mail mail : dmails) { mails.put(mail.uidl, mail); } } /** * The ones known locally, which will include any known on the server, if connected. * Will not include any marked for deletion. * * @since 0.9.13 */ public String[] getUIDLs() { List<String> uidls = new ArrayList<String>(mails.size()); synchronized(mails) { for (Mail mail : mails.values()) { if (!mail.markForDeletion) uidls.add(mail.uidl); } } return uidls.toArray(new String[uidls.size()]); } /** * Fetch any needed data from pop3 server. * * @param uidl message id to get * @param mode CACHE_ONLY to not pull from pop server * @return An e-mail or null */ @SuppressWarnings({"unchecked", "rawtypes"}) public Mail getMail(String uidl, FetchMode mode) { Mail mail = null, newMail = null; /* * synchronize update to hashtable */ synchronized(mails) { mail = mails.get( uidl ); if( mail == null ) { newMail = new Mail(uidl); mails.put( uidl, newMail ); } } if( mail == null ) { mail = newMail; mail.setSize(mailbox.getSize(uidl)); } if (mail.markForDeletion) return null; int sz = mail.getSize(); if (mode == FetchMode.HEADER && sz > 0 && sz <= FETCH_ALL_SIZE) mode = FetchMode.ALL; if (mode == FetchMode.HEADER) { if(!mail.hasHeader()) mail.setHeader(mailbox.getHeader(uidl)); } else if (mode == FetchMode.ALL) { if(!mail.hasBody()) { ReadBuffer rb = mailbox.getBody(uidl); if (rb != null) { mail.setBody(rb); if (disk != null && disk.saveMail(mail) && !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) { mailbox.queueForDeletion(mail.uidl); } } } } else { // else if it wasn't in cache, too bad } return mail; } /** * Fetch any needed data from pop3 server. * Mail objects are inserted into the requests. * After this, call getUIDLs() to get all known mail UIDLs. * MUST already be connected, otherwise returns false. * * @param mode HEADER or ALL only * @return true if any were fetched * @since 0.9.13 */ @SuppressWarnings({"unchecked", "rawtypes"}) public boolean getMail(FetchMode mode) { if (mode == FetchMode.CACHE_ONLY) throw new IllegalArgumentException(); boolean hOnly = mode == FetchMode.HEADER; Collection<String> popKnown = mailbox.getUIDLs(); if (popKnown == null) return false; List<POP3Request> fetches = new ArrayList<POP3Request>(); // Fill in the answers from the cache and make a list of // requests.to send off for (String uidl : popKnown) { Mail mail = null, newMail = null; boolean headerOnly = hOnly; /* * synchronize update to hashtable */ synchronized(mails) { mail = mails.get( uidl ); if( mail == null ) { newMail = new Mail(uidl); mails.put( uidl, newMail ); } } if( mail == null ) { mail = newMail; mail.setSize(mailbox.getSize(uidl)); } if (mail.markForDeletion) continue; int sz = mail.getSize(); if (sz > 0 && sz <= 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); // note that disk loaded the full body if it had it if (mail.hasBody() && !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) { // we already have it, send delete mailbox.queueForDeletion(mail.uidl); } continue; // found on disk, woo } } POP3Request pr = new POP3Request(mail, true); fetches.add(pr); } else { if (mail.hasBody() && !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) { // we already have it, send delete mailbox.queueForDeletion(mail.uidl); } } } else { if(!mail.hasBody()) { if (disk != null) { if (disk.getMail(mail, false)) { Debug.debug(Debug.DEBUG, "Loaded body from disk cache: " + uidl); // note that disk loaded the full body if it had it if (!Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) { // we already have it, send delete mailbox.queueForDeletion(mail.uidl); } continue; // found on disk, woo } } POP3Request pr = new POP3Request(mail, false); fetches.add(pr); } else { if (!Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) { // we already have it, send delete mailbox.queueForDeletion(mail.uidl); } } } } boolean rv = false; if (!fetches.isEmpty()) { // Send off the fetches // gaah compiler List foo = fetches; List<FetchRequest> bar = foo; mailbox.getBodies(bar); // Process results for (POP3Request pr : fetches) { ReadBuffer rb = pr.buf; if (rb != null) { Mail mail = pr.mail; if (!mail.hasHeader()) mail.setNew(true); if (pr.getHeaderOnly()) { mail.setHeader(rb); } else { mail.setBody(rb); } rv = true; if (disk != null) { if (disk.saveMail(mail) && mail.hasBody() && !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) { mailbox.queueForDeletion(mail.uidl); } } } } } return rv; } /** * Mark mail for deletion locally. * Send delete requests to POP3 then quit and reconnect. * No success/failure indication is returned. * * @since 0.9.13 */ public void delete(String uidl) { delete(Collections.singleton(uidl)); } /** * Mark mail for deletion locally. * Send delete requests to POP3 then quit and reconnect. * No success/failure indication is returned. * * @since 0.9.13 */ public void delete(Collection<String> uidls) { List<String> toDelete = new ArrayList<String>(uidls.size()); for (String uidl : uidls) { if (disk != null) disk.deleteMail(uidl); synchronized(mails) { Mail mail = mails.get(uidl); if (mail == null) continue; mail.markForDeletion = true; // now replace it with an empty one to save memory mail = new Mail(uidl); mail.markForDeletion = true; mails.put(uidl, mail); } toDelete.add(uidl); } if (toDelete.isEmpty()) return; mailbox.queueForDeletion(toDelete); } /** * Outgoing to POP3 */ private static class POP3Request implements FetchRequest { public final Mail mail; private final boolean headerOnly; public ReadBuffer buf; public POP3Request(Mail m, boolean hOnly) { mail = m; headerOnly = hOnly; } public String getUIDL() { return mail.uidl; } public boolean getHeaderOnly() { return headerOnly; } public void setBuffer(ReadBuffer buffer) { buf = buffer; } } }