/******************************************************************************* * Copyright (c) 2011 - 2012 Siamak Haschemi & Benjamin Haupt * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package de.bht.fpa.mail.s000000.common.mail.imapsync; import static de.bht.fpa.mail.s000000.common.mail.model.builder.Builders.newFolderBuilder; import java.util.Dictionary; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Properties; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Store; import javax.mail.UIDFolder; import javax.persistence.EntityManager; import javax.persistence.Query; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.swt.widgets.Display; import com.sun.mail.imap.IMAPFolder; import de.bht.fpa.mail.s000000.common.internal.Activator; import de.bht.fpa.mail.s000000.common.mail.model.Account; import de.bht.fpa.mail.s000000.common.mail.model.Folder; import de.bht.fpa.mail.s000000.common.mail.model.Message; import de.bht.fpa.mail.s000000.common.persistence.PersistenceManager; import de.bht.fpa.mail.s000000.common.rcp.exception.ExceptionDetailsErrorDialog; /** * This class allows to synchronize a remote IMAP-based Mail-account with a * local database. * * TODO show typical usage of IMAP sync. * * @author Siamak Haschemi * */ public class ImapHelper { private static final String JAVA_MAIL_IMAPS = "imaps"; private static final String JAVA_MAIL_STORE_PROTOCOL = "mail.store.protocol"; private static final EntityManager entityManager = PersistenceManager.getInstance().createEntityManagerFactory() .createEntityManager(); private static boolean debug = false; private ImapHelper() { } public static void setDebug(boolean debug) { ImapHelper.debug = debug; } /** * Tries to find the account by name. * * @param name * the name of the account * @return an instance of {@link Account} from the database, or * <code>null</code> if {@link Account} is not stored. */ public static Account getAccount(String name) { Query allAccountQuery = entityManager.createQuery("select a from Account a where a.name = :name").setParameter( "name", name); @SuppressWarnings("unchecked") List<Account> list = allAccountQuery.getResultList(); if (list == null || list.size() == 0) { return null; } return list.get(0); } public static void saveAccount(Account account) { entityManager.getTransaction().begin(); entityManager.persist(account); entityManager.getTransaction().commit(); } /** * Synchronizes all folders of an IMAP {@link Account} with a database using * the {@link EntityManager}. The current implementation is only able to add * new messages, but NOT deleted or moved messages. * * @param account * the {@link Account} to use to connect * @param monitor * @throws SynchronizationException */ public static void syncAllFoldersToAccount(Account account, IProgressMonitor monitor) throws SynchronizationException { syncAllFoldersToAccount(account, monitor, new Hashtable<String, String>(), JAVA_MAIL_IMAPS); } /** * Synchronizes all folders of an IMAP {@link Account} with a database using * the {@link EntityManager}. The current implementation is only able to add * new messages, but NOT deleted or moved messages. * * @param account * the {@link Account} to use to connect * @param debug * enable/disable IMAP debugging * @param protocol * the protocol to create the {@link Store} from. See * {@link Session#getStore(String)} * @param properties * a key-value Map to override properties (i.e. for another port). * @throws SynchronizationException */ public static void syncAllFoldersToAccount(final Account account, IProgressMonitor monitor, Dictionary<String, String> properties, String protocol) throws SynchronizationException { try { Properties props = combineProperties(properties); Session session = Session.getInstance(props); Store store = session.getStore(protocol); try { store.connect(account.getHost(), account.getUsername(), account.getPassword()); debug(store.toString()); IMAPFolder imapFolder = (IMAPFolder) store.getDefaultFolder(); int totalNumberOfFolders = caculateTotalNumberOfFolders(imapFolder); monitor.beginTask("Syncing " + totalNumberOfFolders + " IMAP Folders", totalNumberOfFolders); for (javax.mail.Folder subFolder : imapFolder.list()) { if (monitor.isCanceled()) { break; } Folder syncedFolder = syncFolderInternal(account, monitor, (IMAPFolder) subFolder, null); if (!account.getFolders().contains(syncedFolder)) { account.getFolders().add(syncedFolder); syncedFolder.setAccount(account); } } merge(account); } finally { monitor.done(); store.close(); } } catch (final Exception e) { Display.getDefault().syncExec(new Runnable() { @Override public void run() { Status status = new Status(Status.ERROR, Activator.PLUGIN_ID, "Synchronization of Account '" + account.getName() + "' failed", e.getCause()); ExceptionDetailsErrorDialog.openError(Display.getDefault().getActiveShell(), status.getMessage(), null, status); } }); } } private static int caculateTotalNumberOfFolders(IMAPFolder imapFolder) throws MessagingException { javax.mail.Folder[] subFolders = imapFolder.list(); if (subFolders == null || subFolders.length == 0) { return 0; } int temp = subFolders.length; for (javax.mail.Folder f : subFolders) { temp += caculateTotalNumberOfFolders((IMAPFolder) f); } return temp; } private static Properties combineProperties(Dictionary<String, String> properties) { Properties props = System.getProperties(); props.setProperty(JAVA_MAIL_STORE_PROTOCOL, JAVA_MAIL_IMAPS); Enumeration<String> keys = properties.keys(); while (keys.hasMoreElements()) { String nextKey = keys.nextElement(); props.setProperty(nextKey, properties.get(nextKey)); } return props; } private static Folder syncFolderInternal(Account account, IProgressMonitor monitor, IMAPFolder imapFolder, Folder parent) { Folder folder = getFolder(account, imapFolder.getName()); if (parent != null && !parent.getFolders().contains(folder)) { parent.getFolders().add(folder); debug("Added Folder " + folder.getFullName() + " to parent " + parent.getFullName()); } long fromUID = folder.getLastUID() + 1; debug("Sync IMAP-folder " + imapFolder.getFullName() + " with min UID = " + fromUID); try { imapFolder.open(javax.mail.Folder.READ_ONLY); javax.mail.Message[] messages = imapFolder.getMessagesByUID(fromUID, UIDFolder.LASTUID); if (messages == null || messages.length == 0) { debug("No new messages"); return folder; } debug("IMAP-Folder contains " + messages.length + " messages"); for (javax.mail.Message message : messages) { if (monitor.isCanceled()) { break; } try { Message convertedMessage = new MessageConverter().convertJavaxMessage(imapFolder, message); debug("Converted Message ID: " + convertedMessage.getId() + "to: " + convertedMessage.toShortString()); if (!folder.getMessages().contains(convertedMessage)) { debug("Added new Message to Folder"); folder.getMessages().add(convertedMessage); long uid = imapFolder.getUID(message); folder.setLastUID(uid); } else { debug("Message already contained in Folder"); } } catch (MessageConversionException e) { error("Message could not be converted " + e.getMessage()); } } } catch (MessagingException me) { error("Could not sync folder " + folder.getFullName() + ": " + me.getMessage()); } finally { monitor.worked(1); if (imapFolder.isOpen()) { try { imapFolder.close(false); } catch (MessagingException e) { error("Could not close connection to IMAP folder" + folder.getFullName() + ": " + e.getMessage()); } } } try { for (javax.mail.Folder subFolder : imapFolder.list()) { syncFolderInternal(account, monitor, (IMAPFolder) subFolder, folder); } } catch (MessagingException e) { error("Could not sync sub folders of" + folder.getFullName() + ": " + e.getMessage()); } return folder; } private static Folder getFolder(Account account, String fullName) { Query allFoldersWithFullName = entityManager .createQuery("select f from Folder f where f.account.id = :accountId AND f.fullName = :fullName") .setParameter("accountId", account.getId()).setParameter("fullName", fullName); @SuppressWarnings("unchecked") List<Folder> list = allFoldersWithFullName.getResultList(); if (list == null || list.size() == 0) { Folder folder = newFolderBuilder().fullName(fullName).build(); return folder; } return list.get(0); } private static <T> T merge(T t) { entityManager.getTransaction().begin(); T tNew = entityManager.merge(t); entityManager.getTransaction().commit(); return tNew; } private static void debug(String msg) { if (debug) { System.out.println(msg); } } private static void error(String msg) { System.err.println(msg); } }